diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index f659c710a1..bc0350f321 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -10,13 +10,16 @@ using Tmds.DBus; namespace Avalonia.FreeDesktop.DBusSystemTray { - public class SNIDBus + public class SNIDBus : IDisposable { public SNIDBus() { } private static int trayinstanceID = 0; + private IStatusNotifierWatcher _snw; + private string _sysTraySrvName; + private StatusNotifierItem _statusNotifierItem; private static int GetTID() { @@ -29,32 +32,55 @@ namespace Avalonia.FreeDesktop.DBusSystemTray var x = Process.GetCurrentProcess().Id; var y = GetTID(); - var sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}"; - var tx = new StatusNotifierItem(); + _sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}"; + _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(sysTraySrvName, tx.ObjectPath); + var yx = con.CreateProxy(_sysTraySrvName, _statusNotifierItem.ObjectPath); - var snw = + _snw = DBusHelper.Connection.CreateProxy("org.kde.StatusNotifierWatcher", "/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("-", ""); ObjectPath = new ObjectPath($"/StatusNotifierItem"); - props = new StatusNotifierItemProperties(); - var dummyicons = new[] { (1, 1, new byte[8 * 4]), (1, 1, new byte[8 * 4]), }; - - - - props.Title = "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; + props = new StatusNotifierItemProperties + { + Title = "Avalonia Test Tray", + Status = "Avalonia Test Tray", + Id = "Avalonia Test Tray" + }; } - - - // static class StatusNotifierItemExtensions - // { - // public static Task GetCategoryAsync(this IStatusNotifierItem o) => o.GetAsync("Category"); - // public static Task GetIdAsync(this IStatusNotifierItem o) => o.GetAsync("Id"); - // public static Task GetTitleAsync(this IStatusNotifierItem o) => o.GetAsync("Title"); - // public static Task GetStatusAsync(this IStatusNotifierItem o) => o.GetAsync("Status"); - // public static Task GetWindowIdAsync(this IStatusNotifierItem o) => o.GetAsync("WindowId"); - // - // public static Task GetIconThemePathAsync(this IStatusNotifierItem o) => - // o.GetAsync("IconThemePath"); - // - // public static Task GetMenuAsync(this IStatusNotifierItem o) => o.GetAsync("Menu"); - // public static Task GetItemIsMenuAsync(this IStatusNotifierItem o) => o.GetAsync("ItemIsMenu"); - // public static Task GetIconNameAsync(this IStatusNotifierItem o) => o.GetAsync("IconName"); - // - // public static Task<(int, int, byte[])[]> GetIconPixmapAsync(this IStatusNotifierItem o) => - // o.GetAsync<(int, int, byte[])[]>("IconPixmap"); - // - // public static Task GetOverlayIconNameAsync(this IStatusNotifierItem o) => - // o.GetAsync("OverlayIconName"); - // - // public static Task<(int, int, byte[])[]> GetOverlayIconPixmapAsync(this IStatusNotifierItem o) => - // o.GetAsync<(int, int, byte[])[]>("OverlayIconPixmap"); - // - // public static Task GetAttentionIconNameAsync(this IStatusNotifierItem o) => - // o.GetAsync("AttentionIconName"); - // - // public static Task<(int, int, byte[])[]> GetAttentionIconPixmapAsync(this IStatusNotifierItem o) => - // o.GetAsync<(int, int, byte[])[]>("AttentionIconPixmap"); - // - // public static Task GetAttentionMovieNameAsync(this IStatusNotifierItem o) => - // o.GetAsync("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) { } @@ -162,6 +130,14 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { } + + public void InvalidateAll() + { + OnTitleChanged?.Invoke(); + OnIconChanged?.Invoke(); + OnOverlayIconChanged?.Invoke(); + } + public async Task WatchNewTitleAsync(Action handler, Action onError = null) { OnTitleChanged += handler; @@ -202,20 +178,35 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async Task 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 NewStatusAsync { get; set; } - public async Task GetAllAsync() { return props; } - public async Task SetAsync(string prop, object val) - { - throw new NotImplementedException(); - } + public Action NewStatusAsync { get; set; } + + public Task SetAsync(string prop, object val) => Task.CompletedTask; public async Task WatchPropertiesAsync(Action handler) { @@ -223,9 +214,10 @@ namespace Avalonia.FreeDesktop.DBusSystemTray 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] 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 @@ -300,7 +292,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray [Dictionary] class ApplicationProperties { - public bool Busy { get; set; } = default; + public bool Busy; } static class ApplicationExtensions @@ -330,7 +322,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray [Dictionary] class ProfilerProperties { - public IDictionary Capabilities { get; set; } = default; + public IDictionary Capabilities; } static class ProfilerExtensions @@ -362,36 +354,50 @@ namespace Avalonia.FreeDesktop.DBusSystemTray [Dictionary] 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; + } + } } diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 0f42a15cae..3eb8e8d1b6 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Avalonia.Controls.Platform; using Avalonia.FreeDesktop.DBusSystemTray; using Avalonia.Platform; @@ -17,27 +19,74 @@ namespace Avalonia.X11 public X11TrayIconImpl(AvaloniaX11Platform avaloniaX11Platform) { _avaloniaX11Platform = avaloniaX11Platform; - } + sni.Initialize(); + } 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) { - + 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 SetToolTipText(string text) { - sni.Initialize(); } } }