From c4b0b99027491c78988a81559244eddc99151e4c Mon Sep 17 00:00:00 2001 From: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Fri, 22 Oct 2021 15:05:35 +0800 Subject: [PATCH] Gracefully handle tray service restarts --- .../DbusSNITrayIconImpl.cs | 129 +++++++++++++----- src/Avalonia.X11/X11TrayIconImpl.cs | 11 +- src/Avalonia.X11/XEmbedTrayIconImpl.cs | 16 ++- 3 files changed, 113 insertions(+), 43 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DbusSNITrayIconImpl.cs b/src/Avalonia.FreeDesktop/DbusSNITrayIconImpl.cs index 1fb74f132a..6ca05efe50 100644 --- a/src/Avalonia.FreeDesktop/DbusSNITrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DbusSNITrayIconImpl.cs @@ -6,49 +6,55 @@ using System.Reactive.Disposables; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Avalonia.Controls.Platform; -using Avalonia.FreeDesktop; using Avalonia.Logging; -using Avalonia.Platform; using Tmds.DBus; [assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] namespace Avalonia.FreeDesktop { - public class DbusSNITrayIconImpl + public class DbusSNITrayIconImpl { - private static int s_trayIconInstanceId; + private static int s_trayIconInstanceId = 0; private readonly ObjectPath _dbusMenuPath; private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj; private readonly Connection? _connection; private DbusPixmap _icon; - private IStatusNotifierWatcher? _statusNotifierWatcher; - private string? _sysTrayServiceName; private string? _tooltipText; - private bool _isActive; private bool _isDisposed; - private readonly bool _ctorFinished; + private bool _serviceConnected; + private readonly IDisposable _serviceWatchDisposable; + private bool _isVisible; public INativeMenuExporter? MenuExporter { get; } public Action? OnClicked { get; set; } - public bool IsActive => _isActive; - - public DbusSNITrayIconImpl(Connection connection) + public bool IsActive => _serviceConnected; + + public DbusSNITrayIconImpl() { - _connection = connection; + _connection = DBusHelper.TryGetConnection(); + + if (_connection is null) + { + Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) + ?.Log(this, "Unable to get a dbus connection for system tray icons."); + + return; + } + _dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection); + InitializeSNWService(); CreateTrayIcon(); - _ctorFinished = true; + _serviceWatchDisposable = Watch(); } - public async void CreateTrayIcon() + private void InitializeSNWService() { - if (_connection is null) - return; + if (_connection is null || _isDisposed) return; try { @@ -61,38 +67,83 @@ namespace Avalonia.FreeDesktop Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) ?.Log(this, "DBUS: org.kde.StatusNotifierWatcher service is not available on this system. System Tray Icons will not work without it."); + + return; } - if (_statusNotifierWatcher is null) + _serviceConnected = true; + } + + + private async Task Watch() => + await _connection?.ResolveServiceOwnerAsync("org.kde.StatusNotifierWatcher", OnNameChange)!; + + + private void OnNameChange(ServiceOwnerChangedEventArgs obj) + { + if (_isDisposed) return; + if (!_serviceConnected & obj.NewOwner != null) + { + _serviceConnected = true; + + if (_isVisible) + { + DestroyTrayIcon(); + CreateTrayIcon(); + } + else + { + DestroyTrayIcon(); + } + } + else if (_serviceConnected & obj.NewOwner is null) + { + s_trayIconInstanceId = 0; + _serviceConnected = false; + } + } + + public void CreateTrayIcon() + { + if (_connection is null || !_serviceConnected || _isDisposed) + return; + + var pid = Process.GetCurrentProcess().Id; var tid = s_trayIconInstanceId++; _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath); - await _connection.RegisterObjectAsync(_statusNotifierItemDbusObj); - - await _connection.RegisterServiceAsync(_sysTrayServiceName); - await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); + try + { + _connection.RegisterObjectAsync(_statusNotifierItemDbusObj); + _connection.RegisterServiceAsync(_sysTrayServiceName); + _statusNotifierWatcher?.RegisterStatusNotifierItemAsync(_sysTrayServiceName); + } + catch (Exception e) + { + _serviceConnected = false; + } _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); _statusNotifierItemDbusObj.SetIcon(_icon); _statusNotifierItemDbusObj.ActivationDelegate += OnClicked; - - _isActive = true; + _isVisible = true; } - public async void DestroyTrayIcon() + public void DestroyTrayIcon() { - if (_connection is null) + if (_connection is null || !_serviceConnected || _isDisposed) return; + _connection.UnregisterObject(_statusNotifierItemDbusObj); - await _connection.UnregisterServiceAsync(_sysTrayServiceName); - _isActive = false; + _connection.UnregisterServiceAsync(_sysTrayServiceName); + _isVisible = false; } public void Dispose() @@ -100,12 +151,13 @@ namespace Avalonia.FreeDesktop _isDisposed = true; DestroyTrayIcon(); _connection?.Dispose(); + _serviceWatchDisposable?.Dispose(); } public void SetIcon(UIntPtr[] x11iconData) { if (_isDisposed) - return; + return; var w = (int)x11iconData[0]; var h = (int)x11iconData[1]; @@ -128,15 +180,15 @@ namespace Avalonia.FreeDesktop public void SetIsVisible(bool visible) { - if (_isDisposed || !_ctorFinished) + if (_isDisposed) return; - if (visible & !_isActive) + if (visible && !_isVisible) { DestroyTrayIcon(); CreateTrayIcon(); } - else if (!visible & _isActive) + else if (!visible && _isVisible) { DestroyTrayIcon(); } @@ -239,7 +291,20 @@ namespace Avalonia.FreeDesktop return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler)); } - public Task GetAsync(string prop) => Task.FromResult(new object()); + public async Task GetAsync(string prop) + { + return prop switch + { + nameof(_backingProperties.Category) => _backingProperties.Category, + nameof(_backingProperties.Id) => _backingProperties.Id, + nameof(_backingProperties.Menu) => _backingProperties.Menu, + nameof(_backingProperties.IconPixmap) => _backingProperties.IconPixmap, + nameof(_backingProperties.Status) => _backingProperties.Status, + nameof(_backingProperties.Title) => _backingProperties.Title, + nameof(_backingProperties.ToolTip) => _backingProperties.ToolTip, + _ => null + }; + } public Task GetAllAsync() => Task.FromResult(_backingProperties); diff --git a/src/Avalonia.X11/X11TrayIconImpl.cs b/src/Avalonia.X11/X11TrayIconImpl.cs index ca8ed8ec35..9e03dcd604 100644 --- a/src/Avalonia.X11/X11TrayIconImpl.cs +++ b/src/Avalonia.X11/X11TrayIconImpl.cs @@ -11,16 +11,7 @@ namespace Avalonia.X11 public X11TrayIconImpl() { _xEmbedTrayIcon = new XEmbedTrayIconImpl(); - - var _connection = DBusHelper.TryGetConnection(); - - if (_connection is null) - { - Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) - ?.Log(this, "Unable to get a dbus connection for system tray icons."); - } - - _dbusSniTrayIcon = new DbusSNITrayIconImpl(_connection); + _dbusSniTrayIcon = new DbusSNITrayIconImpl(); } private readonly DbusSNITrayIconImpl _dbusSniTrayIcon; diff --git a/src/Avalonia.X11/XEmbedTrayIconImpl.cs b/src/Avalonia.X11/XEmbedTrayIconImpl.cs index 4b5f0d0a57..c2247565be 100644 --- a/src/Avalonia.X11/XEmbedTrayIconImpl.cs +++ b/src/Avalonia.X11/XEmbedTrayIconImpl.cs @@ -9,25 +9,39 @@ namespace Avalonia.X11 { public XEmbedTrayIconImpl() { + } + + private bool IsCalled; + + private void NotImplemented() + { + if(IsCalled) return; + Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) ?.Log(this, "TODO: XEmbed System Tray Icons is not implemented yet. Tray icons won't be available on this system."); - } + IsCalled = true; + } + public void Dispose() { + NotImplemented(); } public void SetIcon(IWindowIconImpl? icon) { + NotImplemented(); } public void SetToolTipText(string? text) { + NotImplemented(); } public void SetIsVisible(bool visible) { + NotImplemented(); } public INativeMenuExporter? MenuExporter { get; }