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; } } }