Browse Source

converting x11 icons to pixmap

pull/6610/head
Jumar Macato 5 years ago
parent
commit
7656558b96
  1. 216
      src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs
  2. 61
      src/Avalonia.X11/X11TrayIconImpl.cs

216
src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs

@ -10,13 +10,16 @@ using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusSystemTray namespace Avalonia.FreeDesktop.DBusSystemTray
{ {
public class SNIDBus public class SNIDBus : IDisposable
{ {
public SNIDBus() public SNIDBus()
{ {
} }
private static int trayinstanceID = 0; private static int trayinstanceID = 0;
private IStatusNotifierWatcher _snw;
private string _sysTraySrvName;
private StatusNotifierItem _statusNotifierItem;
private static int GetTID() private static int GetTID()
{ {
@ -29,32 +32,55 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
var x = Process.GetCurrentProcess().Id; var x = Process.GetCurrentProcess().Id;
var y = GetTID(); var y = GetTID();
var sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}"; _sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}";
var tx = new StatusNotifierItem(); _statusNotifierItem = new StatusNotifierItem();
var con = DBusHelper.Connection;
await DBusHelper.Connection.RegisterObjectAsync(tx); await con.RegisterObjectAsync(_statusNotifierItem);
await DBusHelper.Connection.RegisterServiceAsync(sysTraySrvName, () => await con.RegisterServiceAsync(_sysTraySrvName, () =>
{ {
}); });
while (!await DBusHelper.Connection.IsServiceActiveAsync(sysTraySrvName)) while (!await con.IsServiceActiveAsync(_sysTraySrvName))
{ {
await Task.Delay(1000); await Task.Delay(150);
} }
var yx = DBusHelper.Connection.CreateProxy<IStatusNotifierItem>(sysTraySrvName, tx.ObjectPath); var yx = con.CreateProxy<IStatusNotifierItem>(_sysTraySrvName, _statusNotifierItem.ObjectPath);
var snw = _snw =
DBusHelper.Connection.CreateProxy<IStatusNotifierWatcher>("org.kde.StatusNotifierWatcher", DBusHelper.Connection.CreateProxy<IStatusNotifierWatcher>("org.kde.StatusNotifierWatcher",
"/StatusNotifierWatcher"); "/StatusNotifierWatcher");
while (!await DBusHelper.Connection.IsServiceActiveAsync("org.kde.StatusNotifierWatcher")) while (!await DBusHelper.Connection.IsServiceActiveAsync("org.kde.StatusNotifierWatcher"))
{ {
await Task.Delay(1000); await Task.Delay(150);
} }
await snw.RegisterStatusNotifierItemAsync(sysTraySrvName); await _snw.RegisterStatusNotifierItemAsync(_sysTraySrvName);
//
// Task.Run(async () =>
// {
// await Task.Delay(2000);
// tx.InvalidateAll();
// });
}
public async void Dispose()
{
var con = DBusHelper.Connection;
if (await con.UnregisterServiceAsync(_sysTraySrvName))
{
con.UnregisterObject(_statusNotifierItem);
}
}
public void SetIcon(Pixmap pixmap)
{
_statusNotifierItem.SetIcon(pixmap);
} }
} }
@ -77,73 +103,15 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
{ {
var ID = Guid.NewGuid().ToString().Replace("-", ""); var ID = Guid.NewGuid().ToString().Replace("-", "");
ObjectPath = new ObjectPath($"/StatusNotifierItem"); ObjectPath = new ObjectPath($"/StatusNotifierItem");
props = new StatusNotifierItemProperties();
var dummyicons = new[] { (1, 1, new byte[8 * 4]), (1, 1, new byte[8 * 4]), }; props = new StatusNotifierItemProperties
{
Title = "Avalonia Test Tray",
Status = "Avalonia Test Tray",
props.Title = "Avalonia Test Tray"; Id = "Avalonia Test Tray"
props.IconPixmap = dummyicons; };
props.Id = ID;
props.IconName = "Avalonia";
props.ToolTip = ("Avalonia Test Tooltip", dummyicons, "Avalonia Test Tooltip Message", "And another one");
//
//
// public string Category { get; set; } = default;
//
// public string Id { get; set; } = default;
//
// public string Title { get; set; } = default;
//
// public string Status { get; set; } = default;
//
// public int WindowId { get; set; } = default;
} }
// static class StatusNotifierItemExtensions
// {
// public static Task<string> GetCategoryAsync(this IStatusNotifierItem o) => o.GetAsync<string>("Category");
// public static Task<string> GetIdAsync(this IStatusNotifierItem o) => o.GetAsync<string>("Id");
// public static Task<string> GetTitleAsync(this IStatusNotifierItem o) => o.GetAsync<string>("Title");
// public static Task<string> GetStatusAsync(this IStatusNotifierItem o) => o.GetAsync<string>("Status");
// public static Task<int> GetWindowIdAsync(this IStatusNotifierItem o) => o.GetAsync<int>("WindowId");
//
// public static Task<string> GetIconThemePathAsync(this IStatusNotifierItem o) =>
// o.GetAsync<string>("IconThemePath");
//
// public static Task<ObjectPath> GetMenuAsync(this IStatusNotifierItem o) => o.GetAsync<ObjectPath>("Menu");
// public static Task<bool> GetItemIsMenuAsync(this IStatusNotifierItem o) => o.GetAsync<bool>("ItemIsMenu");
// public static Task<string> GetIconNameAsync(this IStatusNotifierItem o) => o.GetAsync<string>("IconName");
//
// public static Task<(int, int, byte[])[]> GetIconPixmapAsync(this IStatusNotifierItem o) =>
// o.GetAsync<(int, int, byte[])[]>("IconPixmap");
//
// public static Task<string> GetOverlayIconNameAsync(this IStatusNotifierItem o) =>
// o.GetAsync<string>("OverlayIconName");
//
// public static Task<(int, int, byte[])[]> GetOverlayIconPixmapAsync(this IStatusNotifierItem o) =>
// o.GetAsync<(int, int, byte[])[]>("OverlayIconPixmap");
//
// public static Task<string> GetAttentionIconNameAsync(this IStatusNotifierItem o) =>
// o.GetAsync<string>("AttentionIconName");
//
// public static Task<(int, int, byte[])[]> GetAttentionIconPixmapAsync(this IStatusNotifierItem o) =>
// o.GetAsync<(int, int, byte[])[]>("AttentionIconPixmap");
//
// public static Task<string> GetAttentionMovieNameAsync(this IStatusNotifierItem o) =>
// o.GetAsync<string>("AttentionMovieName");
//
// public static Task<(string, (int, int, byte[])[], string, string)>
// GetToolTipAsync(this IStatusNotifierItem o) =>
// o.GetAsync<(string, (int, int, byte[])[], string, string)>("ToolTip");
// }
public async Task ContextMenuAsync(int X, int Y) public async Task ContextMenuAsync(int X, int Y)
{ {
} }
@ -162,6 +130,14 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
{ {
} }
public void InvalidateAll()
{
OnTitleChanged?.Invoke();
OnIconChanged?.Invoke();
OnOverlayIconChanged?.Invoke();
}
public async Task<IDisposable> WatchNewTitleAsync(Action handler, Action<Exception> onError = null) public async Task<IDisposable> WatchNewTitleAsync(Action handler, Action<Exception> onError = null)
{ {
OnTitleChanged += handler; OnTitleChanged += handler;
@ -202,20 +178,35 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
public async Task<object> GetAsync(string prop) public async Task<object> GetAsync(string prop)
{ {
return default; return prop switch
{
"Category" => props.Category,
"Id" => props.Id,
"Title" => props.Title,
"Status" => props.Status,
"WindowId" => props.WindowId,
"IconThemePath" => props.IconThemePath,
"ItemIsMenu" => props.ItemIsMenu,
"IconName" => props.IconName,
"IconPixmap" => props.IconPixmap,
"OverlayIconName" => props.OverlayIconName,
"OverlayIconPixmap" => props.OverlayIconPixmap,
"AttentionIconName" => props.AttentionIconName,
"AttentionIconPixmap" => props.AttentionIconPixmap,
"AttentionMovieName" => props.AttentionMovieName,
"ToolTip" => props.ToolTip,
_ => default
};
} }
public Action<string> NewStatusAsync { get; set; }
public async Task<StatusNotifierItemProperties> GetAllAsync() public async Task<StatusNotifierItemProperties> GetAllAsync()
{ {
return props; return props;
} }
public async Task SetAsync(string prop, object val) public Action<string> NewStatusAsync { get; set; }
{
throw new NotImplementedException(); public Task SetAsync(string prop, object val) => Task.CompletedTask;
}
public async Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler) public async Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler)
{ {
@ -223,9 +214,10 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
return Disposable.Create(() => OnPropertyChange -= handler); return Disposable.Create(() => OnPropertyChange -= handler);
} }
public void Ready() public void SetIcon(Pixmap pixmap)
{ {
OnTitleChanged?.Invoke(); props.IconPixmap = pixmap;
InvalidateAll();
} }
} }
@ -251,11 +243,11 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
[Dictionary] [Dictionary]
class StatusNotifierWatcherProperties class StatusNotifierWatcherProperties
{ {
public string[] RegisteredStatusNotifierItems { get; set; } = default; public string[] RegisteredStatusNotifierItems;
public bool IsStatusNotifierHostRegistered { get; set; } = default; public bool IsStatusNotifierHostRegistered;
public int ProtocolVersion { get; set; } = default; public int ProtocolVersion;
} }
static class StatusNotifierWatcherExtensions static class StatusNotifierWatcherExtensions
@ -300,7 +292,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
[Dictionary] [Dictionary]
class ApplicationProperties class ApplicationProperties
{ {
public bool Busy { get; set; } = default; public bool Busy;
} }
static class ApplicationExtensions static class ApplicationExtensions
@ -330,7 +322,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
[Dictionary] [Dictionary]
class ProfilerProperties class ProfilerProperties
{ {
public IDictionary<string, object> Capabilities { get; set; } = default; public IDictionary<string, object> Capabilities;
} }
static class ProfilerExtensions static class ProfilerExtensions
@ -362,36 +354,50 @@ namespace Avalonia.FreeDesktop.DBusSystemTray
[Dictionary] [Dictionary]
class StatusNotifierItemProperties class StatusNotifierItemProperties
{ {
public string Category { get; set; } = default; public string Category;
public string Id { get; set; } = default; public string Id;
public string Title { get; set; } = default; public string Title;
public string Status { get; set; } = default; public string Status;
public int WindowId { get; set; } = default; public int WindowId;
public string IconThemePath { get; set; } = default; public string IconThemePath;
public ObjectPath Menu { get; set; } = default; public ObjectPath Menu;
public bool ItemIsMenu { get; set; } = default; public bool ItemIsMenu;
public string IconName { get; set; } = default; public string IconName;
public (int, int, byte[])[] IconPixmap { get; set; } = default; public Pixmap IconPixmap;
public string OverlayIconName { get; set; } = default; public string OverlayIconName;
public (int, int, byte[])[] OverlayIconPixmap { get; set; } = default; public Pixmap OverlayIconPixmap;
public string AttentionIconName { get; set; } = default; public string AttentionIconName;
public (int, int, byte[])[] AttentionIconPixmap { get; set; } = default; public Pixmap AttentionIconPixmap;
public string AttentionMovieName { get; set; } = default; public string AttentionMovieName;
public (string, (int, int, byte[])[], string, string) ToolTip { get; set; } = default; public (string, Pixmap, string, string) ToolTip;
} }
public struct Pixmap
{
private readonly int Width;
private readonly int Height;
private readonly byte[] Data;
public Pixmap(int width, int height, byte[] data)
{
Width = width;
Height = height;
Data = data;
}
}
} }

61
src/Avalonia.X11/X11TrayIconImpl.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop.DBusSystemTray; using Avalonia.FreeDesktop.DBusSystemTray;
using Avalonia.Platform; using Avalonia.Platform;
@ -17,27 +19,74 @@ namespace Avalonia.X11
public X11TrayIconImpl(AvaloniaX11Platform avaloniaX11Platform) public X11TrayIconImpl(AvaloniaX11Platform avaloniaX11Platform)
{ {
_avaloniaX11Platform = avaloniaX11Platform; _avaloniaX11Platform = avaloniaX11Platform;
} sni.Initialize();
}
public void Dispose() public void Dispose()
{ {
sni?.Dispose();
}
[StructLayout(LayoutKind.Explicit)]
readonly struct BGRA32
{
[FieldOffset(3)] public readonly byte A;
[FieldOffset(2)] public readonly byte R;
[FieldOffset(1)] public readonly byte G;
[FieldOffset(0)] public readonly byte B;
}
static unsafe class X11IconToPixmap
{
} }
public void SetIcon(IWindowIconImpl icon) public void SetIcon(IWindowIconImpl icon)
{ {
if (icon is X11IconData x11icon)
{
unsafe
{
using var l = x11icon.Lock();
if (l.Format != PixelFormat.Bgra8888) return;
var h = l.Size.Height;
var w = l.Size.Width;
var totalPixels = w * h;
var totalBytes = totalPixels * 4;
var _bgraBuf = new BGRA32[totalPixels];
var byteBuf = new byte[totalBytes];
fixed (void* src = &x11icon.Data[0])
fixed (void* dst = &_bgraBuf[0])
Buffer.MemoryCopy(src, dst, (uint)totalBytes, (uint)totalBytes);
var byteCount = 0;
foreach (var curPix in _bgraBuf)
{
byteBuf[byteCount++] = curPix.A;
byteBuf[byteCount++] = curPix.R;
byteBuf[byteCount++] = curPix.G;
byteBuf[byteCount++] = curPix.B;
}
sni.SetIcon(new Pixmap(w, h, byteBuf));
}
}
;
} }
public void SetIsVisible(bool visible) public void SetIsVisible(bool visible)
{ {
} }
public void SetToolTipText(string text) public void SetToolTipText(string text)
{ {
sni.Initialize();
} }
} }
} }

Loading…
Cancel
Save