From a189f52bcbdb06bd50c3265a3636f5f11c07d40d Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Fri, 17 Sep 2021 15:01:43 +0800 Subject: [PATCH 01/29] testing stuff for now --- .../DBusSystemTray/SNIDBus.cs | 194 ++++++++++++++++++ src/Avalonia.X11/X11TrayIconImpl.cs | 7 +- 2 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs new file mode 100644 index 0000000000..a4bc659d20 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -0,0 +1,194 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Tmds.DBus; + +[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] +namespace Avalonia.FreeDesktop.DBusSystemTray +{ + public class SNIDBus + { + public SNIDBus() + { + + } + + public async void Initialize() + { + + var serviceName = $"AvaloniaSNITest_{Guid.NewGuid()}"; + + var path = + Connection.Session.CreateProxy("org.kde.StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher"); + await path.RegisterStatusNotifierHostAsync(serviceName); + + await path.WatchStatusNotifierHostRegisteredAsync(() => + { + + }, z => + { + + }); + + + + } + } + + [DBusInterface("org.kde.StatusNotifierWatcher")] + interface IStatusNotifierWatcher : IDBusObject + { + Task RegisterStatusNotifierItemAsync(string Service); + Task RegisterStatusNotifierHostAsync(string Service); + Task WatchStatusNotifierItemRegisteredAsync(Action handler, Action onError = null); + Task WatchStatusNotifierItemUnregisteredAsync(Action handler, Action onError = null); + Task WatchStatusNotifierHostRegisteredAsync(Action handler, Action onError = null); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class StatusNotifierWatcherProperties + { + private string[] _RegisteredStatusNotifierItems = default(string[]); + public string[] RegisteredStatusNotifierItems + { + get + { + return _RegisteredStatusNotifierItems; + } + + set + { + _RegisteredStatusNotifierItems = (value); + } + } + + private bool _IsStatusNotifierHostRegistered = default(bool); + public bool IsStatusNotifierHostRegistered + { + get + { + return _IsStatusNotifierHostRegistered; + } + + set + { + _IsStatusNotifierHostRegistered = (value); + } + } + + private int _ProtocolVersion = default(int); + public int ProtocolVersion + { + get + { + return _ProtocolVersion; + } + + set + { + _ProtocolVersion = (value); + } + } + } + + static class StatusNotifierWatcherExtensions + { + public static Task GetRegisteredStatusNotifierItemsAsync(this IStatusNotifierWatcher o) => o.GetAsync("RegisteredStatusNotifierItems"); + public static Task GetIsStatusNotifierHostRegisteredAsync(this IStatusNotifierWatcher o) => o.GetAsync("IsStatusNotifierHostRegistered"); + public static Task GetProtocolVersionAsync(this IStatusNotifierWatcher o) => o.GetAsync("ProtocolVersion"); + } + + [DBusInterface("org.gtk.Actions")] + interface IActions : IDBusObject + { + Task ListAsync(); + Task<(bool description, Signature, object[])> DescribeAsync(string ActionName); + Task> DescribeAllAsync(); + Task ActivateAsync(string ActionName, object[] Parameter, IDictionary PlatformData); + Task SetStateAsync(string ActionName, object Value, IDictionary PlatformData); + Task WatchChangedAsync(Action<(string[] removals, IDictionary enableChanges, IDictionary stateChanges, IDictionary additions)> handler, Action onError = null); + } + + [DBusInterface("org.gtk.Application")] + interface IApplication : IDBusObject + { + Task ActivateAsync(IDictionary PlatformData); + Task OpenAsync(string[] Uris, string Hint, IDictionary PlatformData); + Task CommandLineAsync(ObjectPath Path, byte[][] Arguments, IDictionary PlatformData); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class ApplicationProperties + { + private bool _Busy = default(bool); + public bool Busy + { + get + { + return _Busy; + } + + set + { + _Busy = (value); + } + } + } + + static class ApplicationExtensions + { + public static Task GetBusyAsync(this IApplication o) => o.GetAsync("Busy"); + } + + [DBusInterface("org.freedesktop.Application")] + interface IApplication0 : IDBusObject + { + Task ActivateAsync(IDictionary PlatformData); + Task OpenAsync(string[] Uris, IDictionary PlatformData); + Task ActivateActionAsync(string ActionName, object[] Parameter, IDictionary PlatformData); + } + + [DBusInterface("org.gnome.Sysprof3.Profiler")] + interface IProfiler : IDBusObject + { + Task StartAsync(IDictionary Options, CloseSafeHandle Fd); + Task StopAsync(); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + class ProfilerProperties + { + private IDictionary _Capabilities = default(IDictionary); + public IDictionary Capabilities + { + get + { + return _Capabilities; + } + + set + { + _Capabilities = (value); + } + } + } + + static class ProfilerExtensions + { + public static Task> GetCapabilitiesAsync(this IProfiler o) => o.GetAsync>("Capabilities"); + } +} diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 8909a4604b..0ab34434fb 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Controls.Platform; +using Avalonia.FreeDesktop.DBusSystemTray; using Avalonia.Platform; namespace Avalonia.X11 @@ -9,10 +10,12 @@ namespace Avalonia.X11 public INativeMenuExporter MenuExporter => null; public Action OnClicked { get; set; } - + private SNIDBus sni = new SNIDBus(); + public void Dispose() { + } public void SetIcon(IWindowIconImpl icon) @@ -27,7 +30,7 @@ namespace Avalonia.X11 public void SetToolTipText(string text) { - + sni.Initialize(); } } } From 6016c56e6d627c54a41e6584f4fda386a6aa2f13 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Fri, 17 Sep 2021 15:17:31 +0800 Subject: [PATCH 02/29] connection made --- src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index a4bc659d20..f3d1073e29 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -21,9 +21,15 @@ namespace Avalonia.FreeDesktop.DBusSystemTray var path = Connection.Session.CreateProxy("org.kde.StatusNotifierWatcher", - "org.kde.StatusNotifierWatcher"); + "/StatusNotifierWatcher"); + await path.RegisterStatusNotifierHostAsync(serviceName); + await path.WatchPropertiesAsync(x => + { + + }); + await path.WatchStatusNotifierHostRegisteredAsync(() => { From b216b1afbe79d2028880abcea4005c059ce9d2b5 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Fri, 17 Sep 2021 20:03:43 +0800 Subject: [PATCH 03/29] some progress.... --- .../DBusSystemTray/SNIDBus.cs | 358 +++++++++++++----- src/Avalonia.X11/X11Platform.cs | 2 +- src/Avalonia.X11/X11TrayIconImpl.cs | 11 +- 3 files changed, 278 insertions(+), 93 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index f3d1073e29..fc62d24a7d 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -1,55 +1,229 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Reactive.Disposables; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Tmds.DBus; [assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] + namespace Avalonia.FreeDesktop.DBusSystemTray { public class SNIDBus { public SNIDBus() { - } - public async void Initialize() + private static int trayinstanceID = 0; + + private static int GetTID() + { + trayinstanceID = (int)new Random().Next(0, 100); + return trayinstanceID; + } + + public async void Initialize() { + var x = Process.GetCurrentProcess().Id; + var y = GetTID(); - var serviceName = $"AvaloniaSNITest_{Guid.NewGuid()}"; + var sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}"; + var tx = new StatusNotifierItem(); - var path = - Connection.Session.CreateProxy("org.kde.StatusNotifierWatcher", + await DBusHelper.Connection.RegisterObjectAsync(tx); + + await DBusHelper.Connection.RegisterServiceAsync(sysTraySrvName, () => + { + }); + + while (!await DBusHelper.Connection.IsServiceActiveAsync(sysTraySrvName)) + { + await Task.Delay(1000); + } + + var yx = DBusHelper.Connection.CreateProxy(sysTraySrvName, tx.ObjectPath); + + var snw = + DBusHelper.Connection.CreateProxy("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher"); - - await path.RegisterStatusNotifierHostAsync(serviceName); - await path.WatchPropertiesAsync(x => - { + while (!await DBusHelper.Connection.IsServiceActiveAsync("org.kde.StatusNotifierWatcher")) + { + await Task.Delay(1000); + } + + await snw.RegisterStatusNotifierItemAsync(sysTraySrvName); + + tx.Ready(); + + await yx.ActivateAsync(1, 1); + } + } + + internal class StatusNotifierItem : IStatusNotifierItem + { + private event Action OnPropertyChange; + + public event Action OnTitleChanged; + public event Action OnIconChanged; + public event Action OnAttentionIconChanged; + public event Action OnOverlayIconChanged; - }); - await path.WatchStatusNotifierHostRegisteredAsync(() => - { + public Action NewToolTipAsync; + public ObjectPath ObjectPath { get; } - }, z => - { + readonly StatusNotifierItemProperties props; + + public StatusNotifierItem() + { + var ID = Guid.NewGuid().ToString().Replace("-", ""); + ObjectPath = new ObjectPath($"/StatusNotifierItem"); + props = new StatusNotifierItemProperties(); + + props.Title = "Avalonia Test Tray"; + props.Id = ID; +// + // + // 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 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) + { + } + + public async Task ActivateAsync(int X, int Y) + { + // OnPropertyChange?.Invoke(new PropertyChanges()); + } + + public async Task SecondaryActivateAsync(int X, int Y) + { + throw new NotImplementedException(); + } + + public async Task ScrollAsync(int Delta, string Orientation) + { + } + + public async Task WatchNewTitleAsync(Action handler, Action onError = null) + { + OnTitleChanged += handler; + return Disposable.Create(() => OnTitleChanged -= handler); + } + + + public async Task WatchNewIconAsync(Action handler, Action onError = null) + { + OnIconChanged += handler; + return Disposable.Create(() => OnIconChanged -= handler); + } + + public async Task WatchNewAttentionIconAsync(Action handler, Action onError = null) + { + OnAttentionIconChanged += handler; + return Disposable.Create(() => OnAttentionIconChanged -= handler); + } + + + public async Task WatchNewOverlayIconAsync(Action handler, Action onError = null) + { + OnOverlayIconChanged += handler; + return Disposable.Create(() => OnOverlayIconChanged -= handler); + } + + public async Task WatchNewToolTipAsync(Action handler, Action onError = null) + { + NewToolTipAsync += handler; + return Disposable.Create(() => NewToolTipAsync -= handler); + } + + public async Task WatchNewStatusAsync(Action handler, Action onError = null) + { + NewStatusAsync += handler; + return Disposable.Create(() => NewStatusAsync -= handler); + } + + public Action NewStatusAsync { get; set; } + + public async Task GetAllAsync() + { + return props; + } + + public async Task WatchPropertiesAsync(Action handler) + { + OnPropertyChange += handler; + return Disposable.Create(() => OnPropertyChange -= handler); + } + + public void Ready() + { + OnTitleChanged?.Invoke(); } } - + [DBusInterface("org.kde.StatusNotifierWatcher")] interface IStatusNotifierWatcher : IDBusObject { Task RegisterStatusNotifierItemAsync(string Service); Task RegisterStatusNotifierHostAsync(string Service); - Task WatchStatusNotifierItemRegisteredAsync(Action handler, Action onError = null); - Task WatchStatusNotifierItemUnregisteredAsync(Action handler, Action onError = null); + + Task WatchStatusNotifierItemRegisteredAsync(Action handler, + Action onError = null); + + Task WatchStatusNotifierItemUnregisteredAsync(Action handler, + Action onError = null); + Task WatchStatusNotifierHostRegisteredAsync(Action handler, Action onError = null); Task GetAsync(string prop); Task GetAllAsync(); @@ -60,54 +234,23 @@ namespace Avalonia.FreeDesktop.DBusSystemTray [Dictionary] class StatusNotifierWatcherProperties { - private string[] _RegisteredStatusNotifierItems = default(string[]); - public string[] RegisteredStatusNotifierItems - { - get - { - return _RegisteredStatusNotifierItems; - } + public string[] RegisteredStatusNotifierItems { get; set; } = default; - set - { - _RegisteredStatusNotifierItems = (value); - } - } - - private bool _IsStatusNotifierHostRegistered = default(bool); - public bool IsStatusNotifierHostRegistered - { - get - { - return _IsStatusNotifierHostRegistered; - } + public bool IsStatusNotifierHostRegistered { get; set; } = default; - set - { - _IsStatusNotifierHostRegistered = (value); - } - } - - private int _ProtocolVersion = default(int); - public int ProtocolVersion - { - get - { - return _ProtocolVersion; - } - - set - { - _ProtocolVersion = (value); - } - } + public int ProtocolVersion { get; set; } = default; } static class StatusNotifierWatcherExtensions { - public static Task GetRegisteredStatusNotifierItemsAsync(this IStatusNotifierWatcher o) => o.GetAsync("RegisteredStatusNotifierItems"); - public static Task GetIsStatusNotifierHostRegisteredAsync(this IStatusNotifierWatcher o) => o.GetAsync("IsStatusNotifierHostRegistered"); - public static Task GetProtocolVersionAsync(this IStatusNotifierWatcher o) => o.GetAsync("ProtocolVersion"); + public static Task GetRegisteredStatusNotifierItemsAsync(this IStatusNotifierWatcher o) => + o.GetAsync("RegisteredStatusNotifierItems"); + + public static Task GetIsStatusNotifierHostRegisteredAsync(this IStatusNotifierWatcher o) => + o.GetAsync("IsStatusNotifierHostRegistered"); + + public static Task GetProtocolVersionAsync(this IStatusNotifierWatcher o) => + o.GetAsync("ProtocolVersion"); } [DBusInterface("org.gtk.Actions")] @@ -118,7 +261,11 @@ namespace Avalonia.FreeDesktop.DBusSystemTray Task> DescribeAllAsync(); Task ActivateAsync(string ActionName, object[] Parameter, IDictionary PlatformData); Task SetStateAsync(string ActionName, object Value, IDictionary PlatformData); - Task WatchChangedAsync(Action<(string[] removals, IDictionary enableChanges, IDictionary stateChanges, IDictionary additions)> handler, Action onError = null); + + Task WatchChangedAsync( + Action<(string[] removals, IDictionary enableChanges, IDictionary stateChanges + , IDictionary additions)> handler, + Action onError = null); } [DBusInterface("org.gtk.Application")] @@ -136,19 +283,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray [Dictionary] class ApplicationProperties { - private bool _Busy = default(bool); - public bool Busy - { - get - { - return _Busy; - } - - set - { - _Busy = (value); - } - } + public bool Busy { get; set; } = default; } static class ApplicationExtensions @@ -178,23 +313,66 @@ namespace Avalonia.FreeDesktop.DBusSystemTray [Dictionary] class ProfilerProperties { - private IDictionary _Capabilities = default(IDictionary); - public IDictionary Capabilities - { - get - { - return _Capabilities; - } - - set - { - _Capabilities = (value); - } - } + public IDictionary Capabilities { get; set; } = default; } static class ProfilerExtensions { - public static Task> GetCapabilitiesAsync(this IProfiler o) => o.GetAsync>("Capabilities"); + public static Task> GetCapabilitiesAsync(this IProfiler o) => + o.GetAsync>("Capabilities"); + } + + [DBusInterface("org.kde.StatusNotifierItem")] + interface IStatusNotifierItem : IDBusObject + { + Task ContextMenuAsync(int X, int Y); + Task ActivateAsync(int X, int Y); + Task SecondaryActivateAsync(int X, int Y); + Task ScrollAsync(int Delta, string Orientation); + Task WatchNewTitleAsync(Action handler, Action onError = null); + Task WatchNewIconAsync(Action handler, Action onError = null); + Task WatchNewAttentionIconAsync(Action handler, Action onError = null); + Task WatchNewOverlayIconAsync(Action handler, Action onError = null); + Task WatchNewToolTipAsync(Action handler, Action onError = null); + Task WatchNewStatusAsync(Action handler, Action onError = null); + Task GetAllAsync(); + Task WatchPropertiesAsync(Action handler); + } + + + [Dictionary] + class StatusNotifierItemProperties + { + 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; + + public string IconThemePath { get; set; } = default; + + public ObjectPath Menu { get; set; } = default; + + public bool ItemIsMenu { get; set; } = default; + + public string IconName { get; set; } = default; + + public (int, int, byte[])[] IconPixmap { get; set; } = default; + + public string OverlayIconName { get; set; } = default; + + public (int, int, byte[])[] OverlayIconPixmap { get; set; } = default; + + public string AttentionIconName { get; set; } = default; + + public (int, int, byte[])[] AttentionIconPixmap { get; set; } = default; + + public string AttentionMovieName { get; set; } = default; + + public (string, (int, int, byte[])[], string, string) ToolTip { get; set; } = default; } } diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index 5d80c860a7..eb805c0f0b 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -103,7 +103,7 @@ namespace Avalonia.X11 public ITrayIconImpl CreateTrayIcon () { - return new X11TrayIconImpl(); + return new X11TrayIconImpl(this); } public IWindowImpl CreateWindow() diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 0ab34434fb..0f42a15cae 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Avalonia.Controls.Platform; using Avalonia.FreeDesktop.DBusSystemTray; using Avalonia.Platform; @@ -7,11 +8,17 @@ namespace Avalonia.X11 { class X11TrayIconImpl : ITrayIconImpl { + private readonly AvaloniaX11Platform _avaloniaX11Platform; public INativeMenuExporter MenuExporter => null; public Action OnClicked { get; set; } private SNIDBus sni = new SNIDBus(); - + + public X11TrayIconImpl(AvaloniaX11Platform avaloniaX11Platform) + { + _avaloniaX11Platform = avaloniaX11Platform; + } + public void Dispose() { @@ -30,7 +37,7 @@ namespace Avalonia.X11 public void SetToolTipText(string text) { - sni.Initialize(); + sni.Initialize(); } } } From a19b2027071b003083deabca49f4db09b7db62b7 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Fri, 17 Sep 2021 21:54:24 +0800 Subject: [PATCH 04/29] more updates --- .../DBusSystemTray/SNIDBus.cs | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index fc62d24a7d..f659c710a1 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -55,10 +55,6 @@ namespace Avalonia.FreeDesktop.DBusSystemTray } await snw.RegisterStatusNotifierItemAsync(sysTraySrvName); - - tx.Ready(); - - await yx.ActivateAsync(1, 1); } } @@ -83,9 +79,19 @@ namespace Avalonia.FreeDesktop.DBusSystemTray 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; // @@ -98,6 +104,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray // public int WindowId { get; set; } = default; } + // static class StatusNotifierItemExtensions // { @@ -148,7 +155,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async Task SecondaryActivateAsync(int X, int Y) { - throw new NotImplementedException(); + //throw new NotImplementedException(); } public async Task ScrollAsync(int Delta, string Orientation) @@ -193,6 +200,11 @@ namespace Avalonia.FreeDesktop.DBusSystemTray return Disposable.Create(() => NewStatusAsync -= handler); } + public async Task GetAsync(string prop) + { + return default; + } + public Action NewStatusAsync { get; set; } public async Task GetAllAsync() @@ -200,6 +212,11 @@ namespace Avalonia.FreeDesktop.DBusSystemTray return props; } + public async Task SetAsync(string prop, object val) + { + throw new NotImplementedException(); + } + public async Task WatchPropertiesAsync(Action handler) { OnPropertyChange += handler; @@ -335,7 +352,9 @@ namespace Avalonia.FreeDesktop.DBusSystemTray Task WatchNewOverlayIconAsync(Action handler, Action onError = null); Task WatchNewToolTipAsync(Action handler, Action onError = null); Task WatchNewStatusAsync(Action handler, Action onError = null); + Task GetAsync(string prop); Task GetAllAsync(); + Task SetAsync(string prop, object val); Task WatchPropertiesAsync(Action handler); } From 7656558b9647fee89e4054d7463053ed3088b9ee Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 00:03:07 +0800 Subject: [PATCH 05/29] converting x11 icons to pixmap --- .../DBusSystemTray/SNIDBus.cs | 216 +++++++++--------- src/Avalonia.X11/X11TrayIconImpl.cs | 61 ++++- 2 files changed, 166 insertions(+), 111 deletions(-) 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(); } } } From e2dc0ead8b38ab444c2b5e240654a5a0e6f28fba Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 00:40:24 +0800 Subject: [PATCH 06/29] sigh... icons still not working here --- .../DBusSystemTray/SNIDBus.cs | 74 ++++++++++++------- src/Avalonia.X11/X11TrayIconImpl.cs | 2 - 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index bc0350f321..ab0294e2d3 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -23,7 +23,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray private static int GetTID() { - trayinstanceID = (int)new Random().Next(0, 100); + trayinstanceID = 4; return trayinstanceID; } @@ -65,13 +65,12 @@ namespace Avalonia.FreeDesktop.DBusSystemTray // await Task.Delay(2000); // tx.InvalidateAll(); // }); - } public async void Dispose() { var con = DBusHelper.Connection; - + if (await con.UnregisterServiceAsync(_sysTraySrvName)) { con.UnregisterObject(_statusNotifierItem); @@ -107,8 +106,10 @@ namespace Avalonia.FreeDesktop.DBusSystemTray props = new StatusNotifierItemProperties { Title = "Avalonia Test Tray", - Status = "Avalonia Test Tray", - Id = "Avalonia Test Tray" + Status = "Avalonia Test Tray", + Id = "Avalonia Test Tray", + AttentionIconPixmap = new[] { new Pixmap(0, 0, new byte[] { }), new Pixmap(0, 0, new byte[] { }) }, + // IconPixmap = new[] { new Pixmap(1, 1, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }) } }; } @@ -123,21 +124,21 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async Task SecondaryActivateAsync(int X, int Y) { - //throw new NotImplementedException(); + //throw new NotImplementedException();5 } public async Task ScrollAsync(int Delta, string Orientation) { } - - + public void InvalidateAll() { OnTitleChanged?.Invoke(); OnIconChanged?.Invoke(); OnOverlayIconChanged?.Invoke(); + OnAttentionIconChanged?.Invoke(); } - + public async Task WatchNewTitleAsync(Action handler, Action onError = null) { OnTitleChanged += handler; @@ -216,8 +217,8 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public void SetIcon(Pixmap pixmap) { - props.IconPixmap = pixmap; - InvalidateAll(); + props.IconPixmap = new[] { pixmap }; + InvalidateAll(); } } @@ -372,32 +373,49 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public string IconName; - public Pixmap IconPixmap; + public Pixmap[] IconPixmap; public string OverlayIconName; - public Pixmap OverlayIconPixmap; + public Pixmap[] OverlayIconPixmap; public string AttentionIconName; - public Pixmap AttentionIconPixmap; + public Pixmap[] AttentionIconPixmap; public string AttentionMovieName; - public (string, Pixmap, string, string) ToolTip; + public ToolTip 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; - } - } + public readonly struct ToolTip + { + public readonly string First; + public readonly Pixmap[] Second; + public readonly string Third; + public readonly string Fourth; + + public ToolTip(string first, Pixmap[] second, string third, string fourth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + } + } + + + public readonly struct Pixmap + { + public readonly int Width; + public readonly int Height; + public 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 3eb8e8d1b6..6006107d21 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -77,8 +77,6 @@ namespace Avalonia.X11 sni.SetIcon(new Pixmap(w, h, byteBuf)); } } - - ; } public void SetIsVisible(bool visible) From a582f0fa103dd12dcdaab87d9aa6a10161f439eb Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 00:49:40 +0800 Subject: [PATCH 07/29] clean stuff up --- .../DBusSystemTray/SNIDBus.cs | 40 +++++-------------- 1 file changed, 11 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index ab0294e2d3..f0476f6b52 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -29,42 +29,24 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async void Initialize() { + var con = DBusHelper.Connection; + + _snw = con.CreateProxy("org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher"); + var x = Process.GetCurrentProcess().Id; var y = GetTID(); _sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}"; _statusNotifierItem = new StatusNotifierItem(); - var con = DBusHelper.Connection; await con.RegisterObjectAsync(_statusNotifierItem); await con.RegisterServiceAsync(_sysTraySrvName, () => { }); - - while (!await con.IsServiceActiveAsync(_sysTraySrvName)) - { - await Task.Delay(150); - } - - var yx = con.CreateProxy(_sysTraySrvName, _statusNotifierItem.ObjectPath); - - _snw = - DBusHelper.Connection.CreateProxy("org.kde.StatusNotifierWatcher", - "/StatusNotifierWatcher"); - - while (!await DBusHelper.Connection.IsServiceActiveAsync("org.kde.StatusNotifierWatcher")) - { - await Task.Delay(150); - } - - await _snw.RegisterStatusNotifierItemAsync(_sysTraySrvName); - // - // Task.Run(async () => - // { - // await Task.Delay(2000); - // tx.InvalidateAll(); - // }); + + await _snw.RegisterStatusNotifierItemAsync(_sysTraySrvName); } public async void Dispose() @@ -108,7 +90,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray Title = "Avalonia Test Tray", Status = "Avalonia Test Tray", Id = "Avalonia Test Tray", - AttentionIconPixmap = new[] { new Pixmap(0, 0, new byte[] { }), new Pixmap(0, 0, new byte[] { }) }, + AttentionIconPixmap = new[] { new Pixmap(0, 0, new byte[] { }), new Pixmap(0, 0, new byte[] { }) }, // IconPixmap = new[] { new Pixmap(1, 1, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }) } }; } @@ -130,7 +112,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async Task ScrollAsync(int Delta, string Orientation) { } - + public void InvalidateAll() { OnTitleChanged?.Invoke(); @@ -217,8 +199,8 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public void SetIcon(Pixmap pixmap) { - props.IconPixmap = new[] { pixmap }; - InvalidateAll(); + props.IconPixmap = new[] { pixmap }; + InvalidateAll(); } } From efb260f3fb8adcc02873f0696b97c219f29627e8 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 08:49:07 +0800 Subject: [PATCH 08/29] test upload --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 2 ++ src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index b5e35db969..1c58ff3ecf 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -65,6 +65,8 @@ namespace Avalonia.FreeDesktop // and it's not important to know if it succeeds // since even if we register the window it's not guaranteed that // menu will be actually exported + + Dispose(); } } diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index f0476f6b52..210f9e3159 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -93,6 +93,8 @@ namespace Avalonia.FreeDesktop.DBusSystemTray AttentionIconPixmap = new[] { new Pixmap(0, 0, new byte[] { }), new Pixmap(0, 0, new byte[] { }) }, // IconPixmap = new[] { new Pixmap(1, 1, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }) } }; + + InvalidateAll(); } public async Task ContextMenuAsync(int X, int Y) From d32714559bb5cc0b56f262159d1b7b4127ce10a7 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 12:05:16 +0800 Subject: [PATCH 09/29] more stuff --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 47 ++++++-- .../DBusSystemTray/SNIDBus.cs | 109 ++++-------------- src/Avalonia.X11/X11IconLoader.cs | 4 +- src/Avalonia.X11/X11TrayIconImpl.cs | 82 ++++++++----- src/Avalonia.X11/X11Window.cs | 2 +- 5 files changed, 121 insertions(+), 123 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 1c58ff3ecf..95aea86dfe 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -16,16 +16,30 @@ namespace Avalonia.FreeDesktop { public class DBusMenuExporter { - public static ITopLevelNativeMenuExporter TryCreate(IntPtr xid) + public static ITopLevelNativeMenuExporter TryCreateTopLevelNativeMenu(IntPtr xid) { + return null; + if (DBusHelper.Connection == null) return null; return new DBusMenuExporterImpl(DBusHelper.Connection, xid); } + + public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path) + { + if (DBusHelper.Connection == null) + return null; + + return new DBusMenuExporterImpl(DBusHelper.Connection, path); + } + + public static ObjectPath GenerateDBusMenuObjPath = "/net/avaloniaui/dbusmenu/" + + Guid.NewGuid().ToString().Replace("-", ""); class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable { + private readonly string _targetServiceName; private readonly Connection _dbus; private readonly uint _xid; private IRegistrar _registar; @@ -37,26 +51,41 @@ namespace Avalonia.FreeDesktop private readonly HashSet _menus = new HashSet(); private bool _resetQueued; private int _nextId = 1; + private bool AppMenu = true; public DBusMenuExporterImpl(Connection dbus, IntPtr xid) { _dbus = dbus; _xid = (uint)xid.ToInt32(); - ObjectPath = new ObjectPath("/net/avaloniaui/dbusmenu/" - + Guid.NewGuid().ToString().Replace("-", "")); + ObjectPath = GenerateDBusMenuObjPath; SetNativeMenu(new NativeMenu()); Init(); } + public DBusMenuExporterImpl(Connection dbus, ObjectPath path) + { + _dbus = dbus; + AppMenu = false; + ObjectPath = path; + SetNativeMenu(new NativeMenu()); + Init(); + } async void Init() { try { - await _dbus.RegisterObjectAsync(this); - _registar = DBusHelper.Connection.CreateProxy( - "com.canonical.AppMenu.Registrar", - "/com/canonical/AppMenu/Registrar"); - if (!_disposed) - await _registar.RegisterWindowAsync(_xid, ObjectPath); + if (AppMenu) + { + await _dbus.RegisterObjectAsync(this); + _registar = DBusHelper.Connection.CreateProxy( + "com.canonical.AppMenu.Registrar", + "/com/canonical/AppMenu/Registrar"); + if (!_disposed) + await _registar.RegisterWindowAsync(_xid, ObjectPath); + } + else + { + await _dbus.RegisterObjectAsync(this); + } } catch (Exception e) { diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs index 210f9e3159..398411a81a 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Reactive.Disposables; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using Avalonia.Controls.Platform; using Tmds.DBus; [assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] @@ -20,11 +21,11 @@ namespace Avalonia.FreeDesktop.DBusSystemTray private IStatusNotifierWatcher _snw; private string _sysTraySrvName; private StatusNotifierItem _statusNotifierItem; + public INativeMenuExporter NativeMenuExporter; private static int GetTID() { - trayinstanceID = 4; - return trayinstanceID; + return trayinstanceID = new Random().Next(0, 100); } public async void Initialize() @@ -42,11 +43,12 @@ namespace Avalonia.FreeDesktop.DBusSystemTray await con.RegisterObjectAsync(_statusNotifierItem); - await con.RegisterServiceAsync(_sysTraySrvName, () => - { - }); - - await _snw.RegisterStatusNotifierItemAsync(_sysTraySrvName); + await con.RegisterServiceAsync(_sysTraySrvName); + + await _snw.RegisterStatusNotifierItemAsync(_sysTraySrvName); + + NativeMenuExporter = _statusNotifierItem.NativeMenuExporter; + } public async void Dispose() @@ -68,7 +70,6 @@ namespace Avalonia.FreeDesktop.DBusSystemTray internal class StatusNotifierItem : IStatusNotifierItem { private event Action OnPropertyChange; - public event Action OnTitleChanged; public event Action OnIconChanged; public event Action OnAttentionIconChanged; @@ -84,19 +85,26 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { var ID = Guid.NewGuid().ToString().Replace("-", ""); ObjectPath = new ObjectPath($"/StatusNotifierItem"); + var blankPixmaps = new[] { new Pixmap(0, 0, new byte[] { }), new Pixmap(0, 0, new byte[] { }) }; + + var dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; + + NativeMenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(dbusmenuPath); props = new StatusNotifierItemProperties { + Menu = "/MenuBar", // Needs a dbus menu somehow + ItemIsMenu = false, + ToolTip = new ToolTip("", blankPixmaps, "Avalonia Test Tray", ""), + Category = "", Title = "Avalonia Test Tray", Status = "Avalonia Test Tray", Id = "Avalonia Test Tray", - AttentionIconPixmap = new[] { new Pixmap(0, 0, new byte[] { }), new Pixmap(0, 0, new byte[] { }) }, - // IconPixmap = new[] { new Pixmap(1, 1, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF }) } }; - - InvalidateAll(); } + public INativeMenuExporter NativeMenuExporter; + public async Task ContextMenuAsync(int X, int Y) { } @@ -207,7 +215,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray } [DBusInterface("org.kde.StatusNotifierWatcher")] - interface IStatusNotifierWatcher : IDBusObject + internal interface IStatusNotifierWatcher : IDBusObject { Task RegisterStatusNotifierItemAsync(string Service); Task RegisterStatusNotifierHostAsync(string Service); @@ -226,7 +234,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray } [Dictionary] - class StatusNotifierWatcherProperties + internal class StatusNotifierWatcherProperties { public string[] RegisteredStatusNotifierItems; @@ -235,7 +243,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public int ProtocolVersion; } - static class StatusNotifierWatcherExtensions + internal static class StatusNotifierWatcherExtensions { public static Task GetRegisteredStatusNotifierItemsAsync(this IStatusNotifierWatcher o) => o.GetAsync("RegisteredStatusNotifierItems"); @@ -247,74 +255,6 @@ namespace Avalonia.FreeDesktop.DBusSystemTray o.GetAsync("ProtocolVersion"); } - [DBusInterface("org.gtk.Actions")] - interface IActions : IDBusObject - { - Task ListAsync(); - Task<(bool description, Signature, object[])> DescribeAsync(string ActionName); - Task> DescribeAllAsync(); - Task ActivateAsync(string ActionName, object[] Parameter, IDictionary PlatformData); - Task SetStateAsync(string ActionName, object Value, IDictionary PlatformData); - - Task WatchChangedAsync( - Action<(string[] removals, IDictionary enableChanges, IDictionary stateChanges - , IDictionary additions)> handler, - Action onError = null); - } - - [DBusInterface("org.gtk.Application")] - interface IApplication : IDBusObject - { - Task ActivateAsync(IDictionary PlatformData); - Task OpenAsync(string[] Uris, string Hint, IDictionary PlatformData); - Task CommandLineAsync(ObjectPath Path, byte[][] Arguments, IDictionary PlatformData); - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); - } - - [Dictionary] - class ApplicationProperties - { - public bool Busy; - } - - static class ApplicationExtensions - { - public static Task GetBusyAsync(this IApplication o) => o.GetAsync("Busy"); - } - - [DBusInterface("org.freedesktop.Application")] - interface IApplication0 : IDBusObject - { - Task ActivateAsync(IDictionary PlatformData); - Task OpenAsync(string[] Uris, IDictionary PlatformData); - Task ActivateActionAsync(string ActionName, object[] Parameter, IDictionary PlatformData); - } - - [DBusInterface("org.gnome.Sysprof3.Profiler")] - interface IProfiler : IDBusObject - { - Task StartAsync(IDictionary Options, CloseSafeHandle Fd); - Task StopAsync(); - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); - } - - [Dictionary] - class ProfilerProperties - { - public IDictionary Capabilities; - } - - static class ProfilerExtensions - { - public static Task> GetCapabilitiesAsync(this IProfiler o) => - o.GetAsync>("Capabilities"); - } [DBusInterface("org.kde.StatusNotifierItem")] interface IStatusNotifierItem : IDBusObject @@ -337,7 +277,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray [Dictionary] - class StatusNotifierItemProperties + internal class StatusNotifierItemProperties { public string Category; @@ -388,7 +328,6 @@ namespace Avalonia.FreeDesktop.DBusSystemTray } } - public readonly struct Pixmap { public readonly int Width; diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index 093f2b12c1..27843fd354 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -48,8 +48,8 @@ namespace Avalonia.X11 public X11IconData(Bitmap bitmap) { - _width = Math.Min(bitmap.PixelSize.Width, 128); - _height = Math.Min(bitmap.PixelSize.Height, 128); + _width = Math.Min(bitmap.PixelSize.Width, 256); + _height = Math.Min(bitmap.PixelSize.Height, 256); _bdata = new uint[_width * _height]; fixed (void* ptr = _bdata) { diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 6006107d21..352ac2d833 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -11,7 +12,7 @@ namespace Avalonia.X11 class X11TrayIconImpl : ITrayIconImpl { private readonly AvaloniaX11Platform _avaloniaX11Platform; - public INativeMenuExporter MenuExporter => null; + public INativeMenuExporter MenuExporter { get; } public Action OnClicked { get; set; } private SNIDBus sni = new SNIDBus(); @@ -20,6 +21,7 @@ namespace Avalonia.X11 { _avaloniaX11Platform = avaloniaX11Platform; sni.Initialize(); + MenuExporter = sni.NativeMenuExporter; } public void Dispose() @@ -37,6 +39,35 @@ namespace Avalonia.X11 [FieldOffset(1)] public readonly byte G; [FieldOffset(0)] public readonly byte B; + + public BGRA32(byte a, byte r, byte g, byte b) + { + A = a; + R = r; + G = g; + B = b; + } + } + + + [StructLayout(LayoutKind.Explicit)] + readonly struct ARGB32 + { + [FieldOffset(0)] public readonly byte A; + + [FieldOffset(1)] public readonly byte R; + + [FieldOffset(2)] public readonly byte G; + + [FieldOffset(3)] public readonly byte B; + + public ARGB32(byte a, byte r, byte g, byte b) + { + A = a; + R = r; + G = g; + B = b; + } } static unsafe class X11IconToPixmap @@ -47,38 +78,37 @@ namespace Avalonia.X11 { if (icon is X11IconData x11icon) { - unsafe + var w = 6; + var h = 6; + var rb = 4; + var pixelBuf = new ARGB32[w * h]; + + + var gold = new ARGB32(255, 212, 175, 55); + var red = new ARGB32(255, 255, 0, 0); + var blue = new ARGB32(255, 0, 0, 255); + + var ix = 0; + for (var y = 0; y < h; y++) { - 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) + var offset = y * w; + for (var x = 0; x < w; x++) { - byteBuf[byteCount++] = curPix.A; - byteBuf[byteCount++] = curPix.R; - byteBuf[byteCount++] = curPix.G; - byteBuf[byteCount++] = curPix.B; + pixelBuf[offset + x] = (ix % 2 == 1) ? gold : blue; + ix++; } - - sni.SetIcon(new Pixmap(w, h, byteBuf)); + ix++; } + + var pixmapBytes = MemoryMarshal.Cast(pixelBuf.AsSpan()).ToArray(); + + sni.SetIcon(new Pixmap(w, h, pixmapBytes)); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int PixCoord(int x, int y, int w) => x + (y *w); + public void SetIsVisible(bool visible) { } diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index f0d2d5ca8a..e39be6fc04 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -190,7 +190,7 @@ namespace Avalonia.X11 if(_popup) PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize)); if (platform.Options.UseDBusMenu) - NativeMenuExporter = DBusMenuExporter.TryCreate(_handle); + NativeMenuExporter = DBusMenuExporter.TryCreateTopLevelNativeMenu(_handle); NativeControlHost = new X11NativeControlHost(_platform, this); DispatcherTimer.Run(() => { From 98d8b20e716535529fae9111e10aa44bbd717494 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 12:34:08 +0800 Subject: [PATCH 10/29] dont throw the underlying data in X11IconLoader.cs --- src/Avalonia.X11/X11IconLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index 27843fd354..9a202bcf9e 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -57,10 +57,12 @@ namespace Avalonia.X11 iptr[0] = _width; iptr[1] = _height; } + using(var rt = AvaloniaLocator.Current.GetService().CreateRenderTarget(new[]{this})) using (var ctx = rt.CreateDrawingContext(null)) ctx.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(0, 0, _width, _height)); + Data = new UIntPtr[_width * _height + 2]; Data[0] = new UIntPtr((uint)_width); Data[1] = new UIntPtr((uint)_height); @@ -70,8 +72,6 @@ namespace Avalonia.X11 for (var x = 0; x < _width; x++) Data[r + x] = new UIntPtr(_bdata[r + x]); } - - _bdata = null; } public void Save(Stream outputStream) From a70997b68b679124893885489cb8cff306f99b72 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 13:03:06 +0800 Subject: [PATCH 11/29] fix conversion --- src/Avalonia.X11/X11TrayIconImpl.cs | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 352ac2d833..caaf31862a 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -1,6 +1,4 @@ using System; -using System.IO; -using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Avalonia.Controls.Platform; @@ -40,16 +38,13 @@ namespace Avalonia.X11 [FieldOffset(0)] public readonly byte B; - public BGRA32(byte a, byte r, byte g, byte b) + public ARGB32 ToARGB32() { - A = a; - R = r; - G = g; - B = b; + return new ARGB32(A, R, G, B); } } - - + + [StructLayout(LayoutKind.Explicit)] readonly struct ARGB32 { @@ -70,44 +65,32 @@ namespace Avalonia.X11 } } - static unsafe class X11IconToPixmap - { - } - public void SetIcon(IWindowIconImpl icon) { if (icon is X11IconData x11icon) { - var w = 6; - var h = 6; - var rb = 4; - var pixelBuf = new ARGB32[w * h]; - - - var gold = new ARGB32(255, 212, 175, 55); - var red = new ARGB32(255, 255, 0, 0); - var blue = new ARGB32(255, 0, 0, 255); + var w = (int)x11icon.Data[0]; + var h = (int)x11icon.Data[1]; + + using var fb = x11icon.Lock(); - var ix = 0; - for (var y = 0; y < h; y++) + var pixLength = w * h; + var pixelArray = new ARGB32[pixLength]; + + for (var i = 0; i < pixLength; i++) { - var offset = y * w; - for (var x = 0; x < w; x++) - { - pixelBuf[offset + x] = (ix % 2 == 1) ? gold : blue; - ix++; - } - ix++; + var ins = new IntPtr(fb.Address.ToInt64() + i * 4); + pixelArray[i] = Marshal.PtrToStructure(ins).ToARGB32(); } - - var pixmapBytes = MemoryMarshal.Cast(pixelBuf.AsSpan()).ToArray(); + + var pixmapBytes = MemoryMarshal.Cast(pixelArray).ToArray(); sni.SetIcon(new Pixmap(w, h, pixmapBytes)); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int PixCoord(int x, int y, int w) => x + (y *w); + private int PixCoord(int x, int y, int w) => x + (y * w); public void SetIsVisible(bool visible) { From eae2f5a6d3b4348a8b9c4ac1b6eb7073da37cfba Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:16:44 +0800 Subject: [PATCH 12/29] reuse Data array from X11IconLoader.cs --- src/Avalonia.X11/X11IconLoader.cs | 8 ++--- src/Avalonia.X11/X11TrayIconImpl.cs | 45 ++++++++++++++--------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.X11/X11IconLoader.cs b/src/Avalonia.X11/X11IconLoader.cs index 9a202bcf9e..093f2b12c1 100644 --- a/src/Avalonia.X11/X11IconLoader.cs +++ b/src/Avalonia.X11/X11IconLoader.cs @@ -48,8 +48,8 @@ namespace Avalonia.X11 public X11IconData(Bitmap bitmap) { - _width = Math.Min(bitmap.PixelSize.Width, 256); - _height = Math.Min(bitmap.PixelSize.Height, 256); + _width = Math.Min(bitmap.PixelSize.Width, 128); + _height = Math.Min(bitmap.PixelSize.Height, 128); _bdata = new uint[_width * _height]; fixed (void* ptr = _bdata) { @@ -57,12 +57,10 @@ namespace Avalonia.X11 iptr[0] = _width; iptr[1] = _height; } - using(var rt = AvaloniaLocator.Current.GetService().CreateRenderTarget(new[]{this})) using (var ctx = rt.CreateDrawingContext(null)) ctx.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size), new Rect(0, 0, _width, _height)); - Data = new UIntPtr[_width * _height + 2]; Data[0] = new UIntPtr((uint)_width); Data[1] = new UIntPtr((uint)_height); @@ -72,6 +70,8 @@ namespace Avalonia.X11 for (var x = 0; x < _width; x++) Data[r + x] = new UIntPtr(_bdata[r + x]); } + + _bdata = null; } public void Save(Stream outputStream) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index caaf31862a..8fa5cea427 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -43,8 +43,8 @@ namespace Avalonia.X11 return new ARGB32(A, R, G, B); } } - - + + [StructLayout(LayoutKind.Explicit)] readonly struct ARGB32 { @@ -67,31 +67,30 @@ namespace Avalonia.X11 public void SetIcon(IWindowIconImpl icon) { - if (icon is X11IconData x11icon) + if (!(icon is X11IconData x11icon)) return; + + var w = (int)x11icon.Data[0]; + var h = (int)x11icon.Data[1]; + + var rx = x11icon.Data.AsSpan(2); + var pixLength = w * h; + var pixelArray = new ARGB32[pixLength]; + + for (var i = 0; i < pixLength; i++) { - var w = (int)x11icon.Data[0]; - var h = (int)x11icon.Data[1]; - - using var fb = x11icon.Lock(); - - var pixLength = w * h; - var pixelArray = new ARGB32[pixLength]; - - for (var i = 0; i < pixLength; i++) - { - var ins = new IntPtr(fb.Address.ToInt64() + i * 4); - pixelArray[i] = Marshal.PtrToStructure(ins).ToARGB32(); - } - - var pixmapBytes = MemoryMarshal.Cast(pixelArray).ToArray(); - - sni.SetIcon(new Pixmap(w, h, pixmapBytes)); + var u = rx[i].ToUInt32(); + var a = (byte)((u & 0xFF000000) >> 24); + var r = (byte)((u & 0x00FF0000) >> 16); + var g = (byte)((u & 0x0000FF00) >> 8); + var b = (byte)((u & 0x000000FF)); + pixelArray[i] = new ARGB32(a, r, g, b); } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private int PixCoord(int x, int y, int w) => x + (y * w); + var pixmapBytes = MemoryMarshal.Cast(pixelArray).ToArray(); + sni.SetIcon(new Pixmap(w, h, pixmapBytes)); + } + public void SetIsVisible(bool visible) { } From 060e2b6600a450c5209402940f4a73f64f433112 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:33:22 +0800 Subject: [PATCH 13/29] simplifying stuff --- .../{SNIDBus.cs => DBusSysTray.cs} | 28 +++---- src/Avalonia.X11/X11Platform.cs | 2 +- src/Avalonia.X11/X11TrayIconImpl.cs | 73 +++++-------------- 3 files changed, 29 insertions(+), 74 deletions(-) rename src/Avalonia.FreeDesktop/DBusSystemTray/{SNIDBus.cs => DBusSysTray.cs} (94%) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs similarity index 94% rename from src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs rename to src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs index 398411a81a..82091d3af7 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/SNIDBus.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs @@ -8,24 +8,19 @@ using Avalonia.Controls.Platform; using Tmds.DBus; [assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] - namespace Avalonia.FreeDesktop.DBusSystemTray { - public class SNIDBus : IDisposable - { - public SNIDBus() - { - } - + public class DBusSysTray : IDisposable + { private static int trayinstanceID = 0; private IStatusNotifierWatcher _snw; private string _sysTraySrvName; - private StatusNotifierItem _statusNotifierItem; + private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; public INativeMenuExporter NativeMenuExporter; private static int GetTID() { - return trayinstanceID = new Random().Next(0, 100); + return trayinstanceID++; } public async void Initialize() @@ -39,16 +34,15 @@ namespace Avalonia.FreeDesktop.DBusSystemTray var y = GetTID(); _sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}"; - _statusNotifierItem = new StatusNotifierItem(); + _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(); - await con.RegisterObjectAsync(_statusNotifierItem); + await con.RegisterObjectAsync(_statusNotifierItemDbusObj); await con.RegisterServiceAsync(_sysTraySrvName); await _snw.RegisterStatusNotifierItemAsync(_sysTraySrvName); - NativeMenuExporter = _statusNotifierItem.NativeMenuExporter; - + NativeMenuExporter = _statusNotifierItemDbusObj.NativeMenuExporter; } public async void Dispose() @@ -57,17 +51,17 @@ namespace Avalonia.FreeDesktop.DBusSystemTray if (await con.UnregisterServiceAsync(_sysTraySrvName)) { - con.UnregisterObject(_statusNotifierItem); + con.UnregisterObject(_statusNotifierItemDbusObj); } } public void SetIcon(Pixmap pixmap) { - _statusNotifierItem.SetIcon(pixmap); + _statusNotifierItemDbusObj.SetIcon(pixmap); } } - internal class StatusNotifierItem : IStatusNotifierItem + internal class StatusNotifierItemDbusObj : IStatusNotifierItem { private event Action OnPropertyChange; public event Action OnTitleChanged; @@ -81,7 +75,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray readonly StatusNotifierItemProperties props; - public StatusNotifierItem() + public StatusNotifierItemDbusObj() { var ID = Guid.NewGuid().ToString().Replace("-", ""); ObjectPath = new ObjectPath($"/StatusNotifierItem"); diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs index eb805c0f0b..5d80c860a7 100644 --- a/src/Avalonia.X11/X11Platform.cs +++ b/src/Avalonia.X11/X11Platform.cs @@ -103,7 +103,7 @@ namespace Avalonia.X11 public ITrayIconImpl CreateTrayIcon () { - return new X11TrayIconImpl(this); + return new X11TrayIconImpl(); } public IWindowImpl CreateWindow() diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 8fa5cea427..459bc72342 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -9,60 +9,21 @@ namespace Avalonia.X11 { class X11TrayIconImpl : ITrayIconImpl { - private readonly AvaloniaX11Platform _avaloniaX11Platform; public INativeMenuExporter MenuExporter { get; } - public Action OnClicked { get; set; } - private SNIDBus sni = new SNIDBus(); - - public X11TrayIconImpl(AvaloniaX11Platform avaloniaX11Platform) - { - _avaloniaX11Platform = avaloniaX11Platform; - sni.Initialize(); - MenuExporter = sni.NativeMenuExporter; - } - public void Dispose() - { - sni?.Dispose(); - } + private DBusSysTray dBusSysTray; - [StructLayout(LayoutKind.Explicit)] - readonly struct BGRA32 + public X11TrayIconImpl() { - [FieldOffset(3)] public readonly byte A; - - [FieldOffset(2)] public readonly byte R; - - [FieldOffset(1)] public readonly byte G; - - [FieldOffset(0)] public readonly byte B; - - public ARGB32 ToARGB32() - { - return new ARGB32(A, R, G, B); - } + dBusSysTray = new DBusSysTray(); + dBusSysTray.Initialize(); + MenuExporter = dBusSysTray.NativeMenuExporter; } - - [StructLayout(LayoutKind.Explicit)] - readonly struct ARGB32 + public void Dispose() { - [FieldOffset(0)] public readonly byte A; - - [FieldOffset(1)] public readonly byte R; - - [FieldOffset(2)] public readonly byte G; - - [FieldOffset(3)] public readonly byte B; - - public ARGB32(byte a, byte r, byte g, byte b) - { - A = a; - R = r; - G = g; - B = b; - } + dBusSysTray?.Dispose(); } public void SetIcon(IWindowIconImpl icon) @@ -74,25 +35,25 @@ namespace Avalonia.X11 var rx = x11icon.Data.AsSpan(2); var pixLength = w * h; - var pixelArray = new ARGB32[pixLength]; + + var pixByteArrayCounter = 0; + var pixByteArray = new byte[w * h * 4]; for (var i = 0; i < pixLength; i++) { var u = rx[i].ToUInt32(); - var a = (byte)((u & 0xFF000000) >> 24); - var r = (byte)((u & 0x00FF0000) >> 16); - var g = (byte)((u & 0x0000FF00) >> 8); - var b = (byte)((u & 0x000000FF)); - pixelArray[i] = new ARGB32(a, r, g, b); + pixByteArray[pixByteArrayCounter++] = (byte)((u & 0xFF000000) >> 24); + pixByteArray[pixByteArrayCounter++] = (byte)((u & 0xFF0000) >> 16); + pixByteArray[pixByteArrayCounter++] = (byte)((u & 0xFF00) >> 8); + pixByteArray[pixByteArrayCounter++] = (byte)(u & 0xFF); } - var pixmapBytes = MemoryMarshal.Cast(pixelArray).ToArray(); - - sni.SetIcon(new Pixmap(w, h, pixmapBytes)); + dBusSysTray.SetIcon(new Pixmap(w, h, pixByteArray)); } - + public void SetIsVisible(bool visible) { + } public void SetToolTipText(string text) From f4624819d275620c0c32d89d3cf628aaee5be895 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:47:42 +0800 Subject: [PATCH 14/29] Make title/tooltip work --- .../DBusSystemTray/DBusSysTray.cs | 118 ++++++++++-------- src/Avalonia.X11/X11TrayIconImpl.cs | 13 +- 2 files changed, 76 insertions(+), 55 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs index 82091d3af7..596efd4a92 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs @@ -8,39 +8,38 @@ using Avalonia.Controls.Platform; using Tmds.DBus; [assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] + namespace Avalonia.FreeDesktop.DBusSystemTray { public class DBusSysTray : IDisposable - { - private static int trayinstanceID = 0; - private IStatusNotifierWatcher _snw; - private string _sysTraySrvName; + { + private static int s_trayIconInstanceId = 0; + private IStatusNotifierWatcher _statusNotifierWatcher; + private string _sysTrayServiceName; private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; - public INativeMenuExporter NativeMenuExporter; - private static int GetTID() - { - return trayinstanceID++; - } + public INativeMenuExporter NativeMenuExporter { get; private set; } + + private static int GetTID() => s_trayIconInstanceId++; public async void Initialize() { var con = DBusHelper.Connection; - _snw = con.CreateProxy("org.kde.StatusNotifierWatcher", + _statusNotifierWatcher = con.CreateProxy("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher"); - var x = Process.GetCurrentProcess().Id; - var y = GetTID(); + var pid = Process.GetCurrentProcess().Id; + var tid = GetTID(); - _sysTraySrvName = $"org.kde.StatusNotifierItem-{x}-{y}"; + _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(); await con.RegisterObjectAsync(_statusNotifierItemDbusObj); - await con.RegisterServiceAsync(_sysTraySrvName); + await con.RegisterServiceAsync(_sysTrayServiceName); - await _snw.RegisterStatusNotifierItemAsync(_sysTraySrvName); + await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); NativeMenuExporter = _statusNotifierItemDbusObj.NativeMenuExporter; } @@ -49,7 +48,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { var con = DBusHelper.Connection; - if (await con.UnregisterServiceAsync(_sysTraySrvName)) + if (await con.UnregisterServiceAsync(_sysTrayServiceName)) { con.UnregisterObject(_statusNotifierItemDbusObj); } @@ -59,6 +58,11 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { _statusNotifierItemDbusObj.SetIcon(pixmap); } + + public void SetTitleAndTooltip(string text) + { + _statusNotifierItemDbusObj.SetTitleAndTooltip(text); + } } internal class StatusNotifierItemDbusObj : IStatusNotifierItem @@ -68,32 +72,26 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public event Action OnIconChanged; public event Action OnAttentionIconChanged; public event Action OnOverlayIconChanged; - - - public Action NewToolTipAsync; + public event Action OnTooltipChanged; + public ObjectPath ObjectPath { get; } - readonly StatusNotifierItemProperties props; + readonly StatusNotifierItemProperties _backingProperties; public StatusNotifierItemDbusObj() { var ID = Guid.NewGuid().ToString().Replace("-", ""); ObjectPath = new ObjectPath($"/StatusNotifierItem"); - var blankPixmaps = new[] { new Pixmap(0, 0, new byte[] { }), new Pixmap(0, 0, new byte[] { }) }; var dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; NativeMenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(dbusmenuPath); - props = new StatusNotifierItemProperties + _backingProperties = new StatusNotifierItemProperties { Menu = "/MenuBar", // Needs a dbus menu somehow ItemIsMenu = false, - ToolTip = new ToolTip("", blankPixmaps, "Avalonia Test Tray", ""), - Category = "", - Title = "Avalonia Test Tray", - Status = "Avalonia Test Tray", - Id = "Avalonia Test Tray", + ToolTip = new ToolTip("") }; } @@ -123,6 +121,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray OnIconChanged?.Invoke(); OnOverlayIconChanged?.Invoke(); OnAttentionIconChanged?.Invoke(); + OnTooltipChanged?.Invoke(); } public async Task WatchNewTitleAsync(Action handler, Action onError = null) @@ -143,8 +142,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray OnAttentionIconChanged += handler; return Disposable.Create(() => OnAttentionIconChanged -= handler); } - - + public async Task WatchNewOverlayIconAsync(Action handler, Action onError = null) { OnOverlayIconChanged += handler; @@ -153,8 +151,8 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async Task WatchNewToolTipAsync(Action handler, Action onError = null) { - NewToolTipAsync += handler; - return Disposable.Create(() => NewToolTipAsync -= handler); + OnTooltipChanged += handler; + return Disposable.Create(() => OnTooltipChanged -= handler); } public async Task WatchNewStatusAsync(Action handler, Action onError = null) @@ -167,28 +165,28 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { 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, + "Category" => _backingProperties.Category, + "Id" => _backingProperties.Id, + "Title" => _backingProperties.Title, + "Status" => _backingProperties.Status, + "WindowId" => _backingProperties.WindowId, + "IconThemePath" => _backingProperties.IconThemePath, + "ItemIsMenu" => _backingProperties.ItemIsMenu, + "IconName" => _backingProperties.IconName, + "IconPixmap" => _backingProperties.IconPixmap, + "OverlayIconName" => _backingProperties.OverlayIconName, + "OverlayIconPixmap" => _backingProperties.OverlayIconPixmap, + "AttentionIconName" => _backingProperties.AttentionIconName, + "AttentionIconPixmap" => _backingProperties.AttentionIconPixmap, + "AttentionMovieName" => _backingProperties.AttentionMovieName, + "ToolTip" => _backingProperties.ToolTip, _ => default }; } public async Task GetAllAsync() { - return props; + return _backingProperties; } public Action NewStatusAsync { get; set; } @@ -203,7 +201,18 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public void SetIcon(Pixmap pixmap) { - props.IconPixmap = new[] { pixmap }; + _backingProperties.IconPixmap = new[] { pixmap }; + InvalidateAll(); + } + + public void SetTitleAndTooltip(string text) + { + _backingProperties.Id = text; + _backingProperties.Category = "ApplicationStatus"; + _backingProperties.Status = text; + _backingProperties.Title = text; + _backingProperties.ToolTip = new ToolTip(text); + InvalidateAll(); } } @@ -306,13 +315,24 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public ToolTip ToolTip; } - public readonly struct ToolTip + public struct ToolTip { public readonly string First; public readonly Pixmap[] Second; public readonly string Third; public readonly string Fourth; + private static readonly Pixmap[] s_blankPixmaps = + { + new Pixmap(0, 0, new byte[] { }), + new Pixmap(0, 0, new byte[] { }) + }; + + public ToolTip(string message) : this("", s_blankPixmaps, message, "") + { + + } + public ToolTip(string first, Pixmap[] second, string third, string fourth) { First = first; diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 459bc72342..069b501245 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -12,18 +12,18 @@ namespace Avalonia.X11 public INativeMenuExporter MenuExporter { get; } public Action OnClicked { get; set; } - private DBusSysTray dBusSysTray; + private readonly DBusSysTray _dBusSysTray; public X11TrayIconImpl() { - dBusSysTray = new DBusSysTray(); - dBusSysTray.Initialize(); - MenuExporter = dBusSysTray.NativeMenuExporter; + _dBusSysTray = new DBusSysTray(); + _dBusSysTray.Initialize(); + MenuExporter = _dBusSysTray.NativeMenuExporter; } public void Dispose() { - dBusSysTray?.Dispose(); + _dBusSysTray?.Dispose(); } public void SetIcon(IWindowIconImpl icon) @@ -48,7 +48,7 @@ namespace Avalonia.X11 pixByteArray[pixByteArrayCounter++] = (byte)(u & 0xFF); } - dBusSysTray.SetIcon(new Pixmap(w, h, pixByteArray)); + _dBusSysTray.SetIcon(new Pixmap(w, h, pixByteArray)); } public void SetIsVisible(bool visible) @@ -58,6 +58,7 @@ namespace Avalonia.X11 public void SetToolTipText(string text) { + _dBusSysTray.SetTitleAndTooltip(text); } } } From 0ae373f597fe37f56ccb6c8857fa901cb905dd58 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 16:48:30 +0800 Subject: [PATCH 15/29] remove unnecessary code --- .../DBusSystemTray/DBusSysTray.cs | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs index 596efd4a92..83ae9686d1 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs @@ -163,25 +163,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async Task GetAsync(string prop) { - return prop switch - { - "Category" => _backingProperties.Category, - "Id" => _backingProperties.Id, - "Title" => _backingProperties.Title, - "Status" => _backingProperties.Status, - "WindowId" => _backingProperties.WindowId, - "IconThemePath" => _backingProperties.IconThemePath, - "ItemIsMenu" => _backingProperties.ItemIsMenu, - "IconName" => _backingProperties.IconName, - "IconPixmap" => _backingProperties.IconPixmap, - "OverlayIconName" => _backingProperties.OverlayIconName, - "OverlayIconPixmap" => _backingProperties.OverlayIconPixmap, - "AttentionIconName" => _backingProperties.AttentionIconName, - "AttentionIconPixmap" => _backingProperties.AttentionIconPixmap, - "AttentionMovieName" => _backingProperties.AttentionMovieName, - "ToolTip" => _backingProperties.ToolTip, - _ => default - }; + return default; } public async Task GetAllAsync() From f3a9a3b73b693092bdc974736a2260722a038e03 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 17:04:41 +0800 Subject: [PATCH 16/29] 80% done --- .../DBusSystemTray/DBusSysTray.cs | 33 +++++++++++-------- src/Avalonia.X11/X11TrayIconImpl.cs | 19 ++++++++++- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs index 83ae9686d1..8fd324e84c 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs @@ -17,6 +17,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray private IStatusNotifierWatcher _statusNotifierWatcher; private string _sysTrayServiceName; private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; + private Action _activateDelegate; public INativeMenuExporter NativeMenuExporter { get; private set; } @@ -63,21 +64,33 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { _statusNotifierItemDbusObj.SetTitleAndTooltip(text); } - } + public void SetActivationDelegate(Action activationDelegate) + { + _statusNotifierItemDbusObj.ActivationDelegate = activationDelegate; + } + } + + /// + /// DBus Object used for setting system tray icons. + /// + /// + /// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html + /// internal class StatusNotifierItemDbusObj : IStatusNotifierItem { + private readonly StatusNotifierItemProperties _backingProperties; private event Action OnPropertyChange; public event Action OnTitleChanged; public event Action OnIconChanged; public event Action OnAttentionIconChanged; public event Action OnOverlayIconChanged; public event Action OnTooltipChanged; - + public INativeMenuExporter NativeMenuExporter { get; set; } + public Action NewStatusAsync { get; set; } + public Action ActivationDelegate { get; set; } public ObjectPath ObjectPath { get; } - - readonly StatusNotifierItemProperties _backingProperties; - + public StatusNotifierItemDbusObj() { var ID = Guid.NewGuid().ToString().Replace("-", ""); @@ -95,20 +108,17 @@ namespace Avalonia.FreeDesktop.DBusSystemTray }; } - public INativeMenuExporter NativeMenuExporter; - public async Task ContextMenuAsync(int X, int Y) { } public async Task ActivateAsync(int X, int Y) { - // OnPropertyChange?.Invoke(new PropertyChanges()); + ActivationDelegate?.Invoke(); } public async Task SecondaryActivateAsync(int X, int Y) { - //throw new NotImplementedException();5 } public async Task ScrollAsync(int Delta, string Orientation) @@ -129,8 +139,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray OnTitleChanged += handler; return Disposable.Create(() => OnTitleChanged -= handler); } - - + public async Task WatchNewIconAsync(Action handler, Action onError = null) { OnIconChanged += handler; @@ -171,8 +180,6 @@ namespace Avalonia.FreeDesktop.DBusSystemTray return _backingProperties; } - public Action NewStatusAsync { get; set; } - public Task SetAsync(string prop, object val) => Task.CompletedTask; public async Task WatchPropertiesAsync(Action handler) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 069b501245..0971ee51ee 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -14,6 +14,9 @@ namespace Avalonia.X11 private readonly DBusSysTray _dBusSysTray; + private X11IconData lastIcon; + + public X11TrayIconImpl() { _dBusSysTray = new DBusSysTray(); @@ -30,6 +33,8 @@ namespace Avalonia.X11 { if (!(icon is X11IconData x11icon)) return; + lastIcon = x11icon; + var w = (int)x11icon.Data[0]; var h = (int)x11icon.Data[1]; @@ -49,11 +54,23 @@ namespace Avalonia.X11 } _dBusSysTray.SetIcon(new Pixmap(w, h, pixByteArray)); + + _dBusSysTray.SetActivationDelegate(() => + { + OnClicked?.Invoke(); + }); } public void SetIsVisible(bool visible) { - + if (visible && lastIcon != null) + { + SetIcon(lastIcon); + } + else + { + _dBusSysTray.SetIcon(new Pixmap(1, 1, new byte[] { 0, 0, 0, 0 })); + } } public void SetToolTipText(string text) From f6a4bd90eb69e9125502878b8ec33aa875b3552c Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 17:41:01 +0800 Subject: [PATCH 17/29] some fixes --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 4 - .../DBusSystemTray/DBusSysTray.cs | 89 ++++++------------- src/Avalonia.X11/X11TrayIconImpl.cs | 16 ++-- 3 files changed, 36 insertions(+), 73 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 95aea86dfe..52450560c0 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -18,8 +18,6 @@ namespace Avalonia.FreeDesktop { public static ITopLevelNativeMenuExporter TryCreateTopLevelNativeMenu(IntPtr xid) { - return null; - if (DBusHelper.Connection == null) return null; @@ -94,8 +92,6 @@ namespace Avalonia.FreeDesktop // and it's not important to know if it succeeds // since even if we register the window it's not guaranteed that // menu will be actually exported - - Dispose(); } } diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs index 8fd324e84c..dc5b98a41f 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs @@ -55,9 +55,9 @@ namespace Avalonia.FreeDesktop.DBusSystemTray } } - public void SetIcon(Pixmap pixmap) + public void SetIcon(DbusPixmap dbusPixmap) { - _statusNotifierItemDbusObj.SetIcon(pixmap); + _statusNotifierItemDbusObj.SetIcon(dbusPixmap); } public void SetTitleAndTooltip(string text) @@ -70,7 +70,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray _statusNotifierItemDbusObj.ActivationDelegate = activationDelegate; } } - + /// /// DBus Object used for setting system tray icons. /// @@ -90,7 +90,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public Action NewStatusAsync { get; set; } public Action ActivationDelegate { get; set; } public ObjectPath ObjectPath { get; } - + public StatusNotifierItemDbusObj() { var ID = Guid.NewGuid().ToString().Replace("-", ""); @@ -102,10 +102,11 @@ namespace Avalonia.FreeDesktop.DBusSystemTray _backingProperties = new StatusNotifierItemProperties { - Menu = "/MenuBar", // Needs a dbus menu somehow - ItemIsMenu = false, + Menu = dbusmenuPath, // Needs a dbus menu somehow ToolTip = new ToolTip("") }; + + InvalidateAll(); } public async Task ContextMenuAsync(int X, int Y) @@ -139,7 +140,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray OnTitleChanged += handler; return Disposable.Create(() => OnTitleChanged -= handler); } - + public async Task WatchNewIconAsync(Action handler, Action onError = null) { OnIconChanged += handler; @@ -151,7 +152,7 @@ namespace Avalonia.FreeDesktop.DBusSystemTray OnAttentionIconChanged += handler; return Disposable.Create(() => OnAttentionIconChanged -= handler); } - + public async Task WatchNewOverlayIconAsync(Action handler, Action onError = null) { OnOverlayIconChanged += handler; @@ -172,6 +173,10 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public async Task GetAsync(string prop) { + if (prop.Contains("Menu")) + { + return _backingProperties.Menu; + } return default; } @@ -188,9 +193,9 @@ namespace Avalonia.FreeDesktop.DBusSystemTray return Disposable.Create(() => OnPropertyChange -= handler); } - public void SetIcon(Pixmap pixmap) + public void SetIcon(DbusPixmap dbusPixmap) { - _backingProperties.IconPixmap = new[] { pixmap }; + _backingProperties.IconPixmap = new[] { dbusPixmap }; InvalidateAll(); } @@ -211,43 +216,8 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { Task RegisterStatusNotifierItemAsync(string Service); Task RegisterStatusNotifierHostAsync(string Service); - - Task WatchStatusNotifierItemRegisteredAsync(Action handler, - Action onError = null); - - Task WatchStatusNotifierItemUnregisteredAsync(Action handler, - Action onError = null); - - Task WatchStatusNotifierHostRegisteredAsync(Action handler, Action onError = null); - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); } - [Dictionary] - internal class StatusNotifierWatcherProperties - { - public string[] RegisteredStatusNotifierItems; - - public bool IsStatusNotifierHostRegistered; - - public int ProtocolVersion; - } - - internal static class StatusNotifierWatcherExtensions - { - public static Task GetRegisteredStatusNotifierItemsAsync(this IStatusNotifierWatcher o) => - o.GetAsync("RegisteredStatusNotifierItems"); - - public static Task GetIsStatusNotifierHostRegisteredAsync(this IStatusNotifierWatcher o) => - o.GetAsync("IsStatusNotifierHostRegistered"); - - public static Task GetProtocolVersionAsync(this IStatusNotifierWatcher o) => - o.GetAsync("ProtocolVersion"); - } - - [DBusInterface("org.kde.StatusNotifierItem")] interface IStatusNotifierItem : IDBusObject { @@ -267,7 +237,6 @@ namespace Avalonia.FreeDesktop.DBusSystemTray Task WatchPropertiesAsync(Action handler); } - [Dictionary] internal class StatusNotifierItemProperties { @@ -289,40 +258,38 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public string IconName; - public Pixmap[] IconPixmap; + public DbusPixmap[] IconPixmap; public string OverlayIconName; - public Pixmap[] OverlayIconPixmap; + public DbusPixmap[] OverlayIconPixmap; public string AttentionIconName; - public Pixmap[] AttentionIconPixmap; + public DbusPixmap[] AttentionIconPixmap; public string AttentionMovieName; public ToolTip ToolTip; } - public struct ToolTip + internal struct ToolTip { public readonly string First; - public readonly Pixmap[] Second; + public readonly DbusPixmap[] Second; public readonly string Third; public readonly string Fourth; - private static readonly Pixmap[] s_blankPixmaps = + private static readonly DbusPixmap[] s_blank = { - new Pixmap(0, 0, new byte[] { }), - new Pixmap(0, 0, new byte[] { }) + new DbusPixmap(0, 0, new byte[] { }), new DbusPixmap(0, 0, new byte[] { }) }; - - public ToolTip(string message) : this("", s_blankPixmaps, message, "") + + public ToolTip(string message) : this("", s_blank, message, "") { - } - - public ToolTip(string first, Pixmap[] second, string third, string fourth) + + public ToolTip(string first, DbusPixmap[] second, string third, string fourth) { First = first; Second = second; @@ -331,13 +298,13 @@ namespace Avalonia.FreeDesktop.DBusSystemTray } } - public readonly struct Pixmap + public readonly struct DbusPixmap { public readonly int Width; public readonly int Height; public readonly byte[] Data; - public Pixmap(int width, int height, byte[] data) + public DbusPixmap(int width, int height, byte[] data) { Width = width; Height = height; diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 0971ee51ee..74aae49606 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -7,16 +7,16 @@ using Avalonia.Platform; namespace Avalonia.X11 { - class X11TrayIconImpl : ITrayIconImpl + internal class X11TrayIconImpl : ITrayIconImpl { - public INativeMenuExporter MenuExporter { get; } - public Action OnClicked { get; set; } - + private readonly DBusSysTray _dBusSysTray; - private X11IconData lastIcon; + + public INativeMenuExporter MenuExporter { get; } + public Action OnClicked { get; set; } - + public X11TrayIconImpl() { _dBusSysTray = new DBusSysTray(); @@ -53,7 +53,7 @@ namespace Avalonia.X11 pixByteArray[pixByteArrayCounter++] = (byte)(u & 0xFF); } - _dBusSysTray.SetIcon(new Pixmap(w, h, pixByteArray)); + _dBusSysTray.SetIcon(new DbusPixmap(w, h, pixByteArray)); _dBusSysTray.SetActivationDelegate(() => { @@ -69,7 +69,7 @@ namespace Avalonia.X11 } else { - _dBusSysTray.SetIcon(new Pixmap(1, 1, new byte[] { 0, 0, 0, 0 })); + _dBusSysTray.SetIcon(new DbusPixmap(1, 1, new byte[] { 0, 0, 0, 0 })); } } From f811655ae718f3d4c74972e6ff02c13337be4cc5 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 17:41:23 +0800 Subject: [PATCH 18/29] some fixes --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 52450560c0..c08e4da02c 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -37,7 +37,6 @@ namespace Avalonia.FreeDesktop class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable { - private readonly string _targetServiceName; private readonly Connection _dbus; private readonly uint _xid; private IRegistrar _registar; From 557e35eac019485bb060eee21b3a21ac4a0da9f9 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 19:52:58 +0800 Subject: [PATCH 19/29] generate instead of set on guid --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index c08e4da02c..52bb7c7ad9 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -32,10 +32,10 @@ namespace Avalonia.FreeDesktop return new DBusMenuExporterImpl(DBusHelper.Connection, path); } - public static ObjectPath GenerateDBusMenuObjPath = "/net/avaloniaui/dbusmenu/" + public static ObjectPath GenerateDBusMenuObjPath => "/net/avaloniaui/dbusmenu/" + Guid.NewGuid().ToString().Replace("-", ""); - class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable + private class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable { private readonly Connection _dbus; private readonly uint _xid; From 602dcff35dc686f9094d5baeb2f6046e48947487 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 20:03:20 +0800 Subject: [PATCH 20/29] make native menu work --- .../DBusSystemTray/DBusSysTray.cs | 28 +++++++------------ src/Avalonia.X11/X11TrayIconImpl.cs | 11 ++++++-- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs index dc5b98a41f..7c0b0f6e55 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs @@ -17,13 +17,10 @@ namespace Avalonia.FreeDesktop.DBusSystemTray private IStatusNotifierWatcher _statusNotifierWatcher; private string _sysTrayServiceName; private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; - private Action _activateDelegate; - - public INativeMenuExporter NativeMenuExporter { get; private set; } - + private static int GetTID() => s_trayIconInstanceId++; - public async void Initialize() + public async void Initialize(ObjectPath dbusmenuPath) { var con = DBusHelper.Connection; @@ -34,16 +31,14 @@ namespace Avalonia.FreeDesktop.DBusSystemTray var tid = GetTID(); _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; - _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(); + _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(dbusmenuPath); await con.RegisterObjectAsync(_statusNotifierItemDbusObj); await con.RegisterServiceAsync(_sysTrayServiceName); await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); - - NativeMenuExporter = _statusNotifierItemDbusObj.NativeMenuExporter; - } + } public async void Dispose() { @@ -86,26 +81,22 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public event Action OnAttentionIconChanged; public event Action OnOverlayIconChanged; public event Action OnTooltipChanged; - public INativeMenuExporter NativeMenuExporter { get; set; } + public Action SetNativeMenuExporter { get; set; } public Action NewStatusAsync { get; set; } public Action ActivationDelegate { get; set; } public ObjectPath ObjectPath { get; } - public StatusNotifierItemDbusObj() + public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath) { var ID = Guid.NewGuid().ToString().Replace("-", ""); ObjectPath = new ObjectPath($"/StatusNotifierItem"); - - var dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; - - NativeMenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(dbusmenuPath); - + _backingProperties = new StatusNotifierItemProperties { Menu = dbusmenuPath, // Needs a dbus menu somehow ToolTip = new ToolTip("") }; - + InvalidateAll(); } @@ -175,8 +166,9 @@ namespace Avalonia.FreeDesktop.DBusSystemTray { if (prop.Contains("Menu")) { - return _backingProperties.Menu; + return _backingProperties.Menu; } + return default; } diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 74aae49606..9dcce74e90 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Avalonia.Controls.Platform; +using Avalonia.FreeDesktop; using Avalonia.FreeDesktop.DBusSystemTray; using Avalonia.Platform; @@ -20,8 +21,14 @@ namespace Avalonia.X11 public X11TrayIconImpl() { _dBusSysTray = new DBusSysTray(); - _dBusSysTray.Initialize(); - MenuExporter = _dBusSysTray.NativeMenuExporter; + + + var dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; + MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(dbusmenuPath); + + + _dBusSysTray.Initialize(dbusmenuPath); + } public void Dispose() From 691c4b78fee34e2465582f3fee19040800aa283d Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Sat, 18 Sep 2021 20:46:46 +0800 Subject: [PATCH 21/29] test change --- src/Avalonia.X11/X11TrayIconImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 9dcce74e90..dc81a85b7a 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -9,7 +9,7 @@ using Avalonia.Platform; namespace Avalonia.X11 { internal class X11TrayIconImpl : ITrayIconImpl - { + { private readonly DBusSysTray _dBusSysTray; private X11IconData lastIcon; From 069a8d4ba57af72e89e42ee55a0563c7c7208054 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Tue, 21 Sep 2021 23:28:46 +0800 Subject: [PATCH 22/29] fix dbus menu icon loading courtesy of @danwalmsley --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 52bb7c7ad9..b4b3b23942 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -8,6 +8,7 @@ using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.FreeDesktop.DBusMenu; using Avalonia.Input; +using Avalonia.Platform; using Avalonia.Threading; using Tmds.DBus; #pragma warning disable 1998 @@ -66,6 +67,7 @@ namespace Avalonia.FreeDesktop SetNativeMenu(new NativeMenu()); Init(); } + async void Init() { try @@ -274,17 +276,24 @@ namespace Avalonia.FreeDesktop if (item.ToggleType != NativeMenuItemToggleType.None) return item.IsChecked ? 1 : 0; } - + if (name == "icon-data") { if (item.Icon != null) { - var ms = new MemoryStream(); - item.Icon.Save(ms); - return ms.ToArray(); + var loader = AvaloniaLocator.Current.GetService(); + + if (loader != null) + { + var icon = loader.LoadIcon(item.Icon.PlatformImpl.Item); + + using var ms = new MemoryStream(); + icon.Save(ms); + return ms.ToArray(); + } } } - + if (name == "children-display") return menu != null ? "submenu" : null; } From ce7333413bbfb927a57e64c2d2990ba564b094e3 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 23 Sep 2021 10:14:07 +0800 Subject: [PATCH 23/29] Make a separate DBus connections so that things doesnt mix up in one session and cause ObjectPath conflicts. --- src/Avalonia.FreeDesktop/DBusHelper.cs | 7 ++- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 7 +-- .../DBusSystemTray/DBusSysTray.cs | 13 +++-- src/Avalonia.X11/X11TrayIconImpl.cs | 47 +++++++++---------- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index 7996a94dd0..4e23711ed4 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -51,8 +51,11 @@ namespace Avalonia.FreeDesktop public static Connection TryInitialize(string dbusAddress = null) { - if (Connection != null) - return Connection; + return Connection ?? TryGetConnection(dbusAddress); + } + + public static Connection TryGetConnection(string dbusAddress = null) + { var oldContext = SynchronizationContext.Current; try { diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index b4b3b23942..dbfa04c1f0 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -25,12 +25,9 @@ namespace Avalonia.FreeDesktop return new DBusMenuExporterImpl(DBusHelper.Connection, xid); } - public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path) + public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path, Connection currentConection) { - if (DBusHelper.Connection == null) - return null; - - return new DBusMenuExporterImpl(DBusHelper.Connection, path); + return new DBusMenuExporterImpl(currentConection, path); } public static ObjectPath GenerateDBusMenuObjPath => "/net/avaloniaui/dbusmenu/" diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs index 7c0b0f6e55..ce3e7da726 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs @@ -14,16 +14,22 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public class DBusSysTray : IDisposable { private static int s_trayIconInstanceId = 0; + private IStatusNotifierWatcher _statusNotifierWatcher; private string _sysTrayServiceName; private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; - + + private Connection con; + private static int GetTID() => s_trayIconInstanceId++; + public DBusSysTray(Connection connection) + { + con = connection; + } + public async void Initialize(ObjectPath dbusmenuPath) { - var con = DBusHelper.Connection; - _statusNotifierWatcher = con.CreateProxy("org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher"); @@ -88,7 +94,6 @@ namespace Avalonia.FreeDesktop.DBusSystemTray public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath) { - var ID = Guid.NewGuid().ToString().Replace("-", ""); ObjectPath = new ObjectPath($"/StatusNotifierItem"); _backingProperties = new StatusNotifierItemProperties diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index dc81a85b7a..3d6a3cc20b 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -5,30 +5,33 @@ using Avalonia.Controls.Platform; using Avalonia.FreeDesktop; using Avalonia.FreeDesktop.DBusSystemTray; using Avalonia.Platform; +using Tmds.DBus; namespace Avalonia.X11 { - internal class X11TrayIconImpl : ITrayIconImpl - { - - private readonly DBusSysTray _dBusSysTray; - private X11IconData lastIcon; - + internal class X11TrayIconImpl : ITrayIconImpl + { + private DBusSysTray _dBusSysTray; + private readonly ObjectPath _dbusmenuPath; + public INativeMenuExporter MenuExporter { get; } public Action OnClicked { get; set; } - + public X11TrayIconImpl() { - _dBusSysTray = new DBusSysTray(); - + var con = DBusHelper.TryGetConnection(); - var dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; - MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(dbusmenuPath); + _dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; + MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusmenuPath, con); - - _dBusSysTray.Initialize(dbusmenuPath); - + _dBusSysTray = new DBusSysTray(con); + _dBusSysTray.Initialize(_dbusmenuPath); + + _dBusSysTray.SetActivationDelegate(() => + { + OnClicked?.Invoke(); + }); } public void Dispose() @@ -39,9 +42,7 @@ namespace Avalonia.X11 public void SetIcon(IWindowIconImpl icon) { if (!(icon is X11IconData x11icon)) return; - - lastIcon = x11icon; - + var w = (int)x11icon.Data[0]; var h = (int)x11icon.Data[1]; @@ -61,22 +62,18 @@ namespace Avalonia.X11 } _dBusSysTray.SetIcon(new DbusPixmap(w, h, pixByteArray)); - - _dBusSysTray.SetActivationDelegate(() => - { - OnClicked?.Invoke(); - }); } public void SetIsVisible(bool visible) { - if (visible && lastIcon != null) + if (visible) { - SetIcon(lastIcon); + // _dBusSysTray = new DBusSysTray(); + // _dBusSysTray.Initialize(_dbusmenuPath); } else { - _dBusSysTray.SetIcon(new DbusPixmap(1, 1, new byte[] { 0, 0, 0, 0 })); + // _dBusSysTray?.Dispose(); } } From a06198d4c52a694e04b161ec1afe49937b268cc9 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 23 Sep 2021 11:06:52 +0800 Subject: [PATCH 24/29] make visibility work somewhat --- .../DBusSystemTray/DBusSysTray.cs | 311 ----------------- src/Avalonia.X11/X11TrayIconImpl.cs | 329 +++++++++++++++++- 2 files changed, 312 insertions(+), 328 deletions(-) delete mode 100644 src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs diff --git a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs b/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs deleted file mode 100644 index ce3e7da726..0000000000 --- a/src/Avalonia.FreeDesktop/DBusSystemTray/DBusSysTray.cs +++ /dev/null @@ -1,311 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reactive.Disposables; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Avalonia.Controls.Platform; -using Tmds.DBus; - -[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] - -namespace Avalonia.FreeDesktop.DBusSystemTray -{ - public class DBusSysTray : IDisposable - { - private static int s_trayIconInstanceId = 0; - - private IStatusNotifierWatcher _statusNotifierWatcher; - private string _sysTrayServiceName; - private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; - - private Connection con; - - private static int GetTID() => s_trayIconInstanceId++; - - public DBusSysTray(Connection connection) - { - con = connection; - } - - public async void Initialize(ObjectPath dbusmenuPath) - { - _statusNotifierWatcher = con.CreateProxy("org.kde.StatusNotifierWatcher", - "/StatusNotifierWatcher"); - - var pid = Process.GetCurrentProcess().Id; - var tid = GetTID(); - - _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; - _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(dbusmenuPath); - - await con.RegisterObjectAsync(_statusNotifierItemDbusObj); - - await con.RegisterServiceAsync(_sysTrayServiceName); - - await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); - } - - public async void Dispose() - { - var con = DBusHelper.Connection; - - if (await con.UnregisterServiceAsync(_sysTrayServiceName)) - { - con.UnregisterObject(_statusNotifierItemDbusObj); - } - } - - public void SetIcon(DbusPixmap dbusPixmap) - { - _statusNotifierItemDbusObj.SetIcon(dbusPixmap); - } - - public void SetTitleAndTooltip(string text) - { - _statusNotifierItemDbusObj.SetTitleAndTooltip(text); - } - - public void SetActivationDelegate(Action activationDelegate) - { - _statusNotifierItemDbusObj.ActivationDelegate = activationDelegate; - } - } - - /// - /// DBus Object used for setting system tray icons. - /// - /// - /// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html - /// - internal class StatusNotifierItemDbusObj : IStatusNotifierItem - { - private readonly StatusNotifierItemProperties _backingProperties; - private event Action OnPropertyChange; - public event Action OnTitleChanged; - public event Action OnIconChanged; - public event Action OnAttentionIconChanged; - public event Action OnOverlayIconChanged; - public event Action OnTooltipChanged; - public Action SetNativeMenuExporter { get; set; } - public Action NewStatusAsync { get; set; } - public Action ActivationDelegate { get; set; } - public ObjectPath ObjectPath { get; } - - public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath) - { - ObjectPath = new ObjectPath($"/StatusNotifierItem"); - - _backingProperties = new StatusNotifierItemProperties - { - Menu = dbusmenuPath, // Needs a dbus menu somehow - ToolTip = new ToolTip("") - }; - - InvalidateAll(); - } - - public async Task ContextMenuAsync(int X, int Y) - { - } - - public async Task ActivateAsync(int X, int Y) - { - ActivationDelegate?.Invoke(); - } - - public async Task SecondaryActivateAsync(int X, int Y) - { - } - - public async Task ScrollAsync(int Delta, string Orientation) - { - } - - public void InvalidateAll() - { - OnTitleChanged?.Invoke(); - OnIconChanged?.Invoke(); - OnOverlayIconChanged?.Invoke(); - OnAttentionIconChanged?.Invoke(); - OnTooltipChanged?.Invoke(); - } - - public async Task WatchNewTitleAsync(Action handler, Action onError = null) - { - OnTitleChanged += handler; - return Disposable.Create(() => OnTitleChanged -= handler); - } - - public async Task WatchNewIconAsync(Action handler, Action onError = null) - { - OnIconChanged += handler; - return Disposable.Create(() => OnIconChanged -= handler); - } - - public async Task WatchNewAttentionIconAsync(Action handler, Action onError = null) - { - OnAttentionIconChanged += handler; - return Disposable.Create(() => OnAttentionIconChanged -= handler); - } - - public async Task WatchNewOverlayIconAsync(Action handler, Action onError = null) - { - OnOverlayIconChanged += handler; - return Disposable.Create(() => OnOverlayIconChanged -= handler); - } - - public async Task WatchNewToolTipAsync(Action handler, Action onError = null) - { - OnTooltipChanged += handler; - return Disposable.Create(() => OnTooltipChanged -= handler); - } - - public async Task WatchNewStatusAsync(Action handler, Action onError = null) - { - NewStatusAsync += handler; - return Disposable.Create(() => NewStatusAsync -= handler); - } - - public async Task GetAsync(string prop) - { - if (prop.Contains("Menu")) - { - return _backingProperties.Menu; - } - - return default; - } - - public async Task GetAllAsync() - { - return _backingProperties; - } - - public Task SetAsync(string prop, object val) => Task.CompletedTask; - - public async Task WatchPropertiesAsync(Action handler) - { - OnPropertyChange += handler; - return Disposable.Create(() => OnPropertyChange -= handler); - } - - public void SetIcon(DbusPixmap dbusPixmap) - { - _backingProperties.IconPixmap = new[] { dbusPixmap }; - InvalidateAll(); - } - - public void SetTitleAndTooltip(string text) - { - _backingProperties.Id = text; - _backingProperties.Category = "ApplicationStatus"; - _backingProperties.Status = text; - _backingProperties.Title = text; - _backingProperties.ToolTip = new ToolTip(text); - - InvalidateAll(); - } - } - - [DBusInterface("org.kde.StatusNotifierWatcher")] - internal interface IStatusNotifierWatcher : IDBusObject - { - Task RegisterStatusNotifierItemAsync(string Service); - Task RegisterStatusNotifierHostAsync(string Service); - } - - [DBusInterface("org.kde.StatusNotifierItem")] - interface IStatusNotifierItem : IDBusObject - { - Task ContextMenuAsync(int X, int Y); - Task ActivateAsync(int X, int Y); - Task SecondaryActivateAsync(int X, int Y); - Task ScrollAsync(int Delta, string Orientation); - Task WatchNewTitleAsync(Action handler, Action onError = null); - Task WatchNewIconAsync(Action handler, Action onError = null); - Task WatchNewAttentionIconAsync(Action handler, Action onError = null); - Task WatchNewOverlayIconAsync(Action handler, Action onError = null); - Task WatchNewToolTipAsync(Action handler, Action onError = null); - Task WatchNewStatusAsync(Action handler, Action onError = null); - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); - } - - [Dictionary] - internal class StatusNotifierItemProperties - { - public string Category; - - public string Id; - - public string Title; - - public string Status; - - public int WindowId; - - public string IconThemePath; - - public ObjectPath Menu; - - public bool ItemIsMenu; - - public string IconName; - - public DbusPixmap[] IconPixmap; - - public string OverlayIconName; - - public DbusPixmap[] OverlayIconPixmap; - - public string AttentionIconName; - - public DbusPixmap[] AttentionIconPixmap; - - public string AttentionMovieName; - - public ToolTip ToolTip; - } - - internal struct ToolTip - { - public readonly string First; - public readonly DbusPixmap[] Second; - public readonly string Third; - public readonly string Fourth; - - private static readonly DbusPixmap[] s_blank = - { - new DbusPixmap(0, 0, new byte[] { }), new DbusPixmap(0, 0, new byte[] { }) - }; - - public ToolTip(string message) : this("", s_blank, message, "") - { - } - - public ToolTip(string first, DbusPixmap[] second, string third, string fourth) - { - First = first; - Second = second; - Third = third; - Fourth = fourth; - } - } - - public readonly struct DbusPixmap - { - public readonly int Width; - public readonly int Height; - public readonly byte[] Data; - - public DbusPixmap(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 3d6a3cc20b..682e431c9a 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -1,48 +1,99 @@ using System; +using System.Diagnostics; +using System.Reactive.Disposables; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Avalonia.Controls.Platform; using Avalonia.FreeDesktop; -using Avalonia.FreeDesktop.DBusSystemTray; using Avalonia.Platform; using Tmds.DBus; +[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] + namespace Avalonia.X11 { internal class X11TrayIconImpl : ITrayIconImpl { - private DBusSysTray _dBusSysTray; private readonly ObjectPath _dbusmenuPath; + private static int s_trayIconInstanceId = 0; + + private IStatusNotifierWatcher _statusNotifierWatcher; + private string _sysTrayServiceName; + private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; + + private Connection con; + private DbusPixmap _icon; + private string _tooltipText; + private bool _isActive; + private bool _isDisposed; + private readonly bool _ctorFinished; + + private static int GetTID() => s_trayIconInstanceId++; + public INativeMenuExporter MenuExporter { get; } public Action OnClicked { get; set; } public X11TrayIconImpl() { - var con = DBusHelper.TryGetConnection(); - + con = DBusHelper.TryGetConnection(); _dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusmenuPath, con); + CreateTrayIcon(); + _ctorFinished = true; + } + - _dBusSysTray = new DBusSysTray(con); - _dBusSysTray.Initialize(_dbusmenuPath); + public async void CreateTrayIcon() + { + _statusNotifierWatcher = con.CreateProxy("org.kde.StatusNotifierWatcher", + "/StatusNotifierWatcher"); + + var pid = Process.GetCurrentProcess().Id; + var tid = GetTID(); + + _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; + _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusmenuPath); + + await con.RegisterObjectAsync(_statusNotifierItemDbusObj); - _dBusSysTray.SetActivationDelegate(() => + await con.RegisterServiceAsync(_sysTrayServiceName); + + await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); + + _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); + _statusNotifierItemDbusObj.SetIcon(_icon); + + _statusNotifierItemDbusObj.ActivationDelegate = () => { OnClicked?.Invoke(); - }); + }; + + _isActive = true; } + public async void DestroyTrayIcon() + { + con.UnregisterObject(_statusNotifierItemDbusObj); + await con.UnregisterServiceAsync(_sysTrayServiceName); + _isActive = false; + } + + public void Dispose() { - _dBusSysTray?.Dispose(); + _isDisposed = true; + DestroyTrayIcon(); + con.Dispose(); } public void SetIcon(IWindowIconImpl icon) { + if (con == null || _isDisposed) return; if (!(icon is X11IconData x11icon)) return; - + var w = (int)x11icon.Data[0]; var h = (int)x11icon.Data[1]; @@ -61,25 +112,269 @@ namespace Avalonia.X11 pixByteArray[pixByteArrayCounter++] = (byte)(u & 0xFF); } - _dBusSysTray.SetIcon(new DbusPixmap(w, h, pixByteArray)); + _icon = new DbusPixmap(w, h, pixByteArray); + _statusNotifierItemDbusObj.SetIcon(_icon); } + public void SetIsVisible(bool visible) { - if (visible) + if (con == null || _isDisposed || !_ctorFinished) return; + + if (visible & !_isActive) { - // _dBusSysTray = new DBusSysTray(); - // _dBusSysTray.Initialize(_dbusmenuPath); + DestroyTrayIcon(); + CreateTrayIcon(); } - else + else if (!visible & _isActive) { - // _dBusSysTray?.Dispose(); + DestroyTrayIcon(); } } public void SetToolTipText(string text) { - _dBusSysTray.SetTitleAndTooltip(text); + if (con == null || _isDisposed) return; + _tooltipText = text; + _statusNotifierItemDbusObj?.SetTitleAndTooltip(_tooltipText); + } + } + + + /// + /// DBus Object used for setting system tray icons. + /// + /// + /// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html + /// + internal class StatusNotifierItemDbusObj : IStatusNotifierItem + { + private readonly StatusNotifierItemProperties _backingProperties; + private event Action OnPropertyChange; + public event Action OnTitleChanged; + public event Action OnIconChanged; + public event Action OnAttentionIconChanged; + public event Action OnOverlayIconChanged; + public event Action OnTooltipChanged; + public Action SetNativeMenuExporter { get; set; } + public Action NewStatusAsync { get; set; } + public Action ActivationDelegate { get; set; } + public ObjectPath ObjectPath { get; } + + public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath) + { + ObjectPath = new ObjectPath($"/StatusNotifierItem"); + + _backingProperties = new StatusNotifierItemProperties + { + Menu = dbusmenuPath, // Needs a dbus menu somehow + ToolTip = new ToolTip("") + }; + + InvalidateAll(); + } + + public async Task ContextMenuAsync(int X, int Y) + { + } + + public async Task ActivateAsync(int X, int Y) + { + ActivationDelegate?.Invoke(); + } + + public async Task SecondaryActivateAsync(int X, int Y) + { + } + + public async Task ScrollAsync(int Delta, string Orientation) + { + } + + public void InvalidateAll() + { + OnTitleChanged?.Invoke(); + OnIconChanged?.Invoke(); + OnOverlayIconChanged?.Invoke(); + OnAttentionIconChanged?.Invoke(); + OnTooltipChanged?.Invoke(); + } + + public async Task WatchNewTitleAsync(Action handler, Action onError = null) + { + OnTitleChanged += handler; + return Disposable.Create(() => OnTitleChanged -= handler); + } + + public async Task WatchNewIconAsync(Action handler, Action onError = null) + { + OnIconChanged += handler; + return Disposable.Create(() => OnIconChanged -= handler); + } + + public async Task WatchNewAttentionIconAsync(Action handler, Action onError = null) + { + OnAttentionIconChanged += handler; + return Disposable.Create(() => OnAttentionIconChanged -= handler); + } + + public async Task WatchNewOverlayIconAsync(Action handler, Action onError = null) + { + OnOverlayIconChanged += handler; + return Disposable.Create(() => OnOverlayIconChanged -= handler); + } + + public async Task WatchNewToolTipAsync(Action handler, Action onError = null) + { + OnTooltipChanged += handler; + return Disposable.Create(() => OnTooltipChanged -= handler); + } + + public async Task WatchNewStatusAsync(Action handler, Action onError = null) + { + NewStatusAsync += handler; + return Disposable.Create(() => NewStatusAsync -= handler); + } + + public async Task GetAsync(string prop) + { + if (prop.Contains("Menu")) + { + return _backingProperties.Menu; + } + + return default; + } + + public async Task GetAllAsync() + { + return _backingProperties; + } + + public Task SetAsync(string prop, object val) => Task.CompletedTask; + + public async Task WatchPropertiesAsync(Action handler) + { + OnPropertyChange += handler; + return Disposable.Create(() => OnPropertyChange -= handler); + } + + public void SetIcon(DbusPixmap dbusPixmap) + { + _backingProperties.IconPixmap = new[] { dbusPixmap }; + InvalidateAll(); + } + + public void SetTitleAndTooltip(string text) + { + _backingProperties.Id = text; + _backingProperties.Category = "ApplicationStatus"; + _backingProperties.Status = text; + _backingProperties.Title = text; + _backingProperties.ToolTip = new ToolTip(text); + + InvalidateAll(); + } + } + + [DBusInterface("org.kde.StatusNotifierWatcher")] + internal interface IStatusNotifierWatcher : IDBusObject + { + Task RegisterStatusNotifierItemAsync(string Service); + Task RegisterStatusNotifierHostAsync(string Service); + } + + [DBusInterface("org.kde.StatusNotifierItem")] + interface IStatusNotifierItem : IDBusObject + { + Task ContextMenuAsync(int X, int Y); + Task ActivateAsync(int X, int Y); + Task SecondaryActivateAsync(int X, int Y); + Task ScrollAsync(int Delta, string Orientation); + Task WatchNewTitleAsync(Action handler, Action onError = null); + Task WatchNewIconAsync(Action handler, Action onError = null); + Task WatchNewAttentionIconAsync(Action handler, Action onError = null); + Task WatchNewOverlayIconAsync(Action handler, Action onError = null); + Task WatchNewToolTipAsync(Action handler, Action onError = null); + Task WatchNewStatusAsync(Action handler, Action onError = null); + Task GetAsync(string prop); + Task GetAllAsync(); + Task SetAsync(string prop, object val); + Task WatchPropertiesAsync(Action handler); + } + + [Dictionary] + internal class StatusNotifierItemProperties + { + public string Category; + + public string Id; + + public string Title; + + public string Status; + + public int WindowId; + + public string IconThemePath; + + public ObjectPath Menu; + + public bool ItemIsMenu; + + public string IconName; + + public DbusPixmap[] IconPixmap; + + public string OverlayIconName; + + public DbusPixmap[] OverlayIconPixmap; + + public string AttentionIconName; + + public DbusPixmap[] AttentionIconPixmap; + + public string AttentionMovieName; + + public ToolTip ToolTip; + } + + internal struct ToolTip + { + public readonly string First; + public readonly DbusPixmap[] Second; + public readonly string Third; + public readonly string Fourth; + + private static readonly DbusPixmap[] s_blank = + { + new DbusPixmap(0, 0, new byte[] { }), new DbusPixmap(0, 0, new byte[] { }) + }; + + public ToolTip(string message) : this("", s_blank, message, "") + { + } + + public ToolTip(string first, DbusPixmap[] second, string third, string fourth) + { + First = first; + Second = second; + Third = third; + Fourth = fourth; + } + } + + public readonly struct DbusPixmap + { + public readonly int Width; + public readonly int Height; + public readonly byte[] Data; + + public DbusPixmap(int width, int height, byte[] data) + { + Width = width; + Height = height; + Data = data; } } } From a7f3fbe988614ccd71ed7f636825be21e67d684c Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Thu, 23 Sep 2021 11:08:00 +0800 Subject: [PATCH 25/29] more refactoring --- src/Avalonia.X11/X11TrayIconImpl.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 682e431c9a..1d8db8f929 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -120,15 +120,15 @@ namespace Avalonia.X11 public void SetIsVisible(bool visible) { if (con == null || _isDisposed || !_ctorFinished) return; - + if (visible & !_isActive) { - DestroyTrayIcon(); - CreateTrayIcon(); + DestroyTrayIcon(); + CreateTrayIcon(); } else if (!visible & _isActive) { - DestroyTrayIcon(); + DestroyTrayIcon(); } } @@ -139,8 +139,7 @@ namespace Avalonia.X11 _statusNotifierItemDbusObj?.SetTitleAndTooltip(_tooltipText); } } - - + /// /// DBus Object used for setting system tray icons. /// @@ -285,7 +284,7 @@ namespace Avalonia.X11 } [DBusInterface("org.kde.StatusNotifierItem")] - interface IStatusNotifierItem : IDBusObject + internal interface IStatusNotifierItem : IDBusObject { Task ContextMenuAsync(int X, int Y); Task ActivateAsync(int X, int Y); @@ -364,7 +363,7 @@ namespace Avalonia.X11 } } - public readonly struct DbusPixmap + internal readonly struct DbusPixmap { public readonly int Width; public readonly int Height; From 19003c0f2e9e1ca27883e543e872b9f98bcef7c2 Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Mon, 27 Sep 2021 22:22:56 +0800 Subject: [PATCH 26/29] Use single connection + new guid per SNI object... --- src/Avalonia.X11/X11TrayIconImpl.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 1d8db8f929..49ac1a0757 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -38,7 +38,7 @@ namespace Avalonia.X11 public X11TrayIconImpl() { - con = DBusHelper.TryGetConnection(); + con = DBusHelper.TryInitialize(); _dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusmenuPath, con); CreateTrayIcon(); @@ -113,7 +113,7 @@ namespace Avalonia.X11 } _icon = new DbusPixmap(w, h, pixByteArray); - _statusNotifierItemDbusObj.SetIcon(_icon); + _statusNotifierItemDbusObj?.SetIcon(_icon); } @@ -139,7 +139,7 @@ namespace Avalonia.X11 _statusNotifierItemDbusObj?.SetTitleAndTooltip(_tooltipText); } } - + /// /// DBus Object used for setting system tray icons. /// @@ -162,7 +162,8 @@ namespace Avalonia.X11 public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath) { - ObjectPath = new ObjectPath($"/StatusNotifierItem"); + var guid = Guid.NewGuid().ToString().Replace("-", ""); + ObjectPath = new ObjectPath($"/net/avaloniaui/sni/{guid}"); _backingProperties = new StatusNotifierItemProperties { From fa6a12c5ef371253331b9009737aff25ab5b4e5e Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Mon, 27 Sep 2021 22:26:36 +0800 Subject: [PATCH 27/29] finally silence that good ol appmenu error in linux --- src/Avalonia.Base/Logging/LogArea.cs | 5 +++++ src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Logging/LogArea.cs b/src/Avalonia.Base/Logging/LogArea.cs index 2ad220dddd..08ed8669ef 100644 --- a/src/Avalonia.Base/Logging/LogArea.cs +++ b/src/Avalonia.Base/Logging/LogArea.cs @@ -39,5 +39,10 @@ namespace Avalonia.Logging /// The log event comes from Win32Platform. /// public const string Win32Platform = nameof(Win32Platform); + + /// + /// The log event comes from Win32Platform. + /// + public const string X11Platform = nameof(X11Platform); } } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index dbfa04c1f0..932b876088 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -85,7 +85,9 @@ namespace Avalonia.FreeDesktop } catch (Exception e) { - Console.Error.WriteLine(e); + Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.X11Platform) + ?.Log(this, e.Message); + // It's not really important if this code succeeds, // and it's not important to know if it succeeds // since even if we register the window it's not guaranteed that From 1a97c6ce39f19cf30512bd1cce706ac0e1b2455c Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Wed, 29 Sep 2021 20:19:10 +0800 Subject: [PATCH 28/29] Revert "Use single connection + new guid per SNI object..." This reverts commit 19003c0f --- src/Avalonia.X11/X11TrayIconImpl.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 49ac1a0757..1d8db8f929 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -38,7 +38,7 @@ namespace Avalonia.X11 public X11TrayIconImpl() { - con = DBusHelper.TryInitialize(); + con = DBusHelper.TryGetConnection(); _dbusmenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusmenuPath, con); CreateTrayIcon(); @@ -113,7 +113,7 @@ namespace Avalonia.X11 } _icon = new DbusPixmap(w, h, pixByteArray); - _statusNotifierItemDbusObj?.SetIcon(_icon); + _statusNotifierItemDbusObj.SetIcon(_icon); } @@ -139,7 +139,7 @@ namespace Avalonia.X11 _statusNotifierItemDbusObj?.SetTitleAndTooltip(_tooltipText); } } - + /// /// DBus Object used for setting system tray icons. /// @@ -162,8 +162,7 @@ namespace Avalonia.X11 public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath) { - var guid = Guid.NewGuid().ToString().Replace("-", ""); - ObjectPath = new ObjectPath($"/net/avaloniaui/sni/{guid}"); + ObjectPath = new ObjectPath($"/StatusNotifierItem"); _backingProperties = new StatusNotifierItemProperties { From f570e3528d84191c9a81b60104a07e3d90b4da9c Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Wed, 29 Sep 2021 20:30:26 +0800 Subject: [PATCH 29/29] Clean up a little bit --- src/Avalonia.X11/X11TrayIconImpl.cs | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index 1d8db8f929..99a850dabb 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -15,27 +15,25 @@ namespace Avalonia.X11 { internal class X11TrayIconImpl : ITrayIconImpl { - private readonly ObjectPath _dbusmenuPath; - + private static int s_trayIconInstanceId = 0; - - private IStatusNotifierWatcher _statusNotifierWatcher; - private string _sysTrayServiceName; + private static int GetTID() => s_trayIconInstanceId++; + private ObjectPath _dbusmenuPath; private StatusNotifierItemDbusObj _statusNotifierItemDbusObj; - private Connection con; private DbusPixmap _icon; + + private IStatusNotifierWatcher _statusNotifierWatcher; + + private string _sysTrayServiceName; private string _tooltipText; private bool _isActive; private bool _isDisposed; private readonly bool _ctorFinished; - - private static int GetTID() => s_trayIconInstanceId++; - + public INativeMenuExporter MenuExporter { get; } public Action OnClicked { get; set; } - - + public X11TrayIconImpl() { con = DBusHelper.TryGetConnection(); @@ -44,8 +42,7 @@ namespace Avalonia.X11 CreateTrayIcon(); _ctorFinished = true; } - - + public async void CreateTrayIcon() { _statusNotifierWatcher = con.CreateProxy("org.kde.StatusNotifierWatcher", @@ -81,7 +78,6 @@ namespace Avalonia.X11 _isActive = false; } - public void Dispose() { _isDisposed = true; @@ -115,8 +111,7 @@ namespace Avalonia.X11 _icon = new DbusPixmap(w, h, pixByteArray); _statusNotifierItemDbusObj.SetIcon(_icon); } - - + public void SetIsVisible(bool visible) { if (con == null || _isDisposed || !_ctorFinished) return;