diff --git a/Directory.Packages.props b/Directory.Packages.props index 7ef244b355..f4e677d631 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -61,8 +61,8 @@ - - + + diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index 91ba686906..7f4416e4aa 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -14,7 +14,7 @@ - + @@ -27,22 +27,22 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index a238c8ea1c..b930710971 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Threading; using Avalonia.Logging; @@ -8,9 +7,9 @@ namespace Avalonia.FreeDesktop { internal static class DBusHelper { - private static Connection? s_defaultConntection; + private static DBusConnection? s_defaultConntection; private static bool s_defaultConnectionFailed; - public static Connection? DefaultConnection + public static DBusConnection? DefaultConnection { get { @@ -25,14 +24,14 @@ namespace Avalonia.FreeDesktop } } - public static Connection? TryCreateNewConnection(string? dbusAddress = null) + public static DBusConnection? TryCreateNewConnection(string? dbusAddress = null) { var oldContext = SynchronizationContext.Current; - Connection? conn = null; + DBusConnection? conn = null; try { SynchronizationContext.SetSynchronizationContext(null); - conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session!) + conn = new DBusConnection(new DBusConnectionOptions(dbusAddress ?? DBusAddress.Session!) { AutoConnect = false, }); diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs index a735c09577..cfd28b29aa 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs @@ -1,14 +1,13 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Logging; using Tmds.DBus.Protocol; -using Tmds.DBus.SourceGenerator; namespace Avalonia.FreeDesktop.DBusIme { @@ -32,8 +31,9 @@ namespace Avalonia.FreeDesktop.DBusIme { private List _disposables = new List(); private Queue _onlineNamesQueue = new Queue(); - protected Connection Connection { get; } + protected DBusConnection Connection { get; } private readonly string[] _knownNames; + private CancellationTokenSource? _watchCts; private bool _connecting; private string? _currentName; private DBusCallQueue _queue; @@ -47,39 +47,62 @@ namespace Avalonia.FreeDesktop.DBusIme protected bool IsConnected => _currentName != null; - public DBusTextInputMethodBase(Connection connection, params string[] knownNames) + public DBusTextInputMethodBase(DBusConnection connection, params string[] knownNames) { _queue = new DBusCallQueue(QueueOnErrorAsync); Connection = connection; _knownNames = knownNames; - _ = WatchAsync(); + WatchAsync(); } public TextInputMethodClient? Client => _client; public bool IsActive => _client is not null; - private async Task WatchAsync() + private void WatchAsync() { - var dbus = new OrgFreedesktopDBusProxy(Connection, "org.freedesktop.DBus", "/org/freedesktop/DBus"); - try - { - _disposables.Add(await dbus.WatchNameOwnerChangedAsync(OnNameChange)); - } - catch (DBusException e) - { - Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"WatchNameOwnerChangedAsync failed: {e}"); - } + _watchCts = new CancellationTokenSource(); + var cancellationToken = _watchCts.Token; foreach (var name in _knownNames) + WatchNameAsync(name); + + async void WatchNameAsync(string name) { try { - var nameOwner = await dbus.GetNameOwnerAsync(name); - OnNameChange(null, (name, null, nameOwner)); + using var watcher = await Connection.WatchNameOwnerAsync(name); + var owner = watcher.GetCurrentOwner(); + OnOwnerChanged(name, owner); + while (!cancellationToken.IsCancellationRequested) + { + if (owner is not null) + { + var ct = watcher.GetOwnerChangedCancellationToken(owner); + using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct, cancellationToken); + try + { + await Task.Delay(Timeout.Infinite, linked.Token); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { } + } + else + { + try + { + await watcher.WaitForOwnerAsync(cancellationToken); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { } + } + owner = watcher.GetCurrentOwner(); + OnOwnerChanged(name, owner); + } } - catch (DBusException e) + catch (Exception e) when (!cancellationToken.IsCancellationRequested) { - Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"GetNameOwnerAsync failed: {e}"); + Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform) + ?.Log(this, $"WatchNameOwner for '{name}' failed: {e}"); } } } @@ -89,22 +112,11 @@ namespace Avalonia.FreeDesktop.DBusIme protected string GetAppName() => Application.Current?.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia"; - private async void OnNameChange(Exception? e, (string ServiceName, string? OldOwner, string? NewOwner) args) + private async void OnOwnerChanged(string serviceName, string? newOwner) { - if (e is not null) - { - Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnNameChange failed: {e}"); - return; - } - - if (!_knownNames.Contains(args.ServiceName)) + if (newOwner is not null && _currentName is null) { - return; - } - - if (args.NewOwner is not null && _currentName is null) - { - _onlineNamesQueue.Enqueue(args.ServiceName); + _onlineNamesQueue.Enqueue(serviceName); if (!_connecting) { _connecting = true; @@ -134,11 +146,10 @@ namespace Avalonia.FreeDesktop.DBusIme _connecting = false; } } - } // IME has crashed - if (args.NewOwner is null && args.ServiceName == _currentName) + if (newOwner is null && serviceName == _currentName) { _currentName = null; foreach (var s in _disposables) @@ -147,9 +158,6 @@ namespace Avalonia.FreeDesktop.DBusIme OnDisconnected(); Reset(); - - // Watch again - _ = WatchAsync(); } } @@ -196,6 +204,7 @@ namespace Avalonia.FreeDesktop.DBusIme public async void Dispose() { + _watchCts?.Cancel(); foreach(var d in _disposables) d.Dispose(); _disposables.Clear(); diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs index 295a36b060..514986dd3b 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs @@ -1,21 +1,21 @@ using System; using System.Threading.Tasks; using Avalonia.Reactive; -using Tmds.DBus.SourceGenerator; +using Avalonia.FreeDesktop.DBusIme.Fcitx.DBus; namespace Avalonia.FreeDesktop.DBusIme.Fcitx { internal class FcitxICWrapper { - private readonly OrgFcitxFcitxInputContext1Proxy? _modern; - private readonly OrgFcitxFcitxInputContextProxy? _old; + private readonly InputContext1? _modern; + private readonly InputContext? _old; - public FcitxICWrapper(OrgFcitxFcitxInputContextProxy old) + public FcitxICWrapper(InputContext old) { _old = old; } - public FcitxICWrapper(OrgFcitxFcitxInputContext1Proxy modern) + public FcitxICWrapper(InputContext1 modern) { _modern = modern; } @@ -38,18 +38,18 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx return await (_modern?.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time) ?? Task.FromResult(false)); } - public ValueTask WatchCommitStringAsync(Action handler) => + public ValueTask WatchCommitStringAsync(Action handler) => _old?.WatchCommitStringAsync(handler) ?? _modern?.WatchCommitStringAsync(handler) ?? new ValueTask(Disposable.Empty); - public ValueTask WatchForwardKeyAsync(Action handler) => + public ValueTask WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler) => _old?.WatchForwardKeyAsync(handler) - ?? _modern?.WatchForwardKeyAsync((e, ev) => handler.Invoke(e, (ev.Keyval, ev.State, ev.Type ? 1 : 0))) + ?? _modern?.WatchForwardKeyAsync(ev => handler.Invoke((ev.Keyval, ev.State, ev.Type ? 1 : 0))) ?? new ValueTask(Disposable.Empty); public ValueTask WatchUpdateFormattedPreeditAsync( - Action handler) => + Action<((string?, int)[]? str, int cursorpos)> handler) => _old?.WatchUpdateFormattedPreeditAsync(handler!) ?? _modern?.WatchUpdateFormattedPreeditAsync(handler!) ?? new ValueTask(Disposable.Empty); diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index ebf84aab06..cda1c33187 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Diagnostics; using System.Linq; @@ -9,7 +8,7 @@ using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Logging; using Tmds.DBus.Protocol; -using Tmds.DBus.SourceGenerator; +using Avalonia.FreeDesktop.DBusIme.Fcitx.DBus; namespace Avalonia.FreeDesktop.DBusIme.Fcitx { @@ -20,24 +19,24 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx private FcitxCapabilityFlags _optionFlags; private FcitxCapabilityFlags _capabilityFlags; - public FcitxX11TextInputMethod(Connection connection) : base(connection, "org.fcitx.Fcitx", "org.freedesktop.portal.Fcitx") { } + public FcitxX11TextInputMethod(DBusConnection connection) : base(connection, "org.fcitx.Fcitx", "org.freedesktop.portal.Fcitx") { } protected override async Task Connect(string name) { if (name == "org.fcitx.Fcitx") { - var method = new OrgFcitxFcitxInputMethodProxy(Connection, name, "/inputmethod"); + var method = new DBus.InputMethod(Connection, name, "/inputmethod"); var resp = await method.CreateICv3Async(GetAppName(), Process.GetCurrentProcess().Id); - var proxy = new OrgFcitxFcitxInputContextProxy(Connection, name, $"/inputcontext_{resp.Icid}"); + var proxy = new InputContext(Connection, name, $"/inputcontext_{resp.Icid}"); _context = new FcitxICWrapper(proxy); } else { - var method = new OrgFcitxFcitxInputMethod1Proxy(Connection, name, "/inputmethod"); + var method = new InputMethod1(Connection, name, "/inputmethod"); var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) }); - var proxy = new OrgFcitxFcitxInputContext1Proxy(Connection, name, resp.Item1); + var proxy = new InputContext1(Connection, name, resp.Item1); _context = new FcitxICWrapper(proxy); } @@ -47,7 +46,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx return true; } - private void OnPreedit(Exception? arg1, ((string?, int)[]? str, int cursorpos) args) + private void OnPreedit(((string?, int)[]? str, int cursorpos) args) { int? cursor = null; string? preeditString = null; @@ -169,7 +168,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx return PushFlagsIfNeeded(); }); - private void OnForward(Exception? e, (uint keyval, uint state, int type) ev) + private void OnForward((uint keyval, uint state, int type) ev) { var state = (FcitxKeyState)ev.state; KeyModifiers mods = default; @@ -193,14 +192,8 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx }); } - private void OnCommitString(Exception? e, string s) + private void OnCommitString(string s) { - if (e is not null) - { - Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnCommitString failed: {e}"); - return; - } - FireCommit(s); } } diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 338f088d95..b3dc6313f0 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Threading.Tasks; using Avalonia.Input; @@ -7,27 +6,27 @@ using Avalonia.Input.TextInput; using Avalonia.Logging; using Avalonia.Media.TextFormatting.Unicode; using Tmds.DBus.Protocol; -using Tmds.DBus.SourceGenerator; +using Avalonia.FreeDesktop.DBusIme.IBus.DBus; namespace Avalonia.FreeDesktop.DBusIme.IBus { internal class IBusX11TextInputMethod : DBusTextInputMethodBase { - private OrgFreedesktopIBusServiceProxy? _service; - private OrgFreedesktopIBusInputContextProxy? _context; + private Service? _service; + private InputContext? _context; private string _preeditText = ""; private int _preeditCursor; private int _insideReset; - public IBusX11TextInputMethod(Connection connection) : base(connection, "org.freedesktop.portal.IBus") { } + public IBusX11TextInputMethod(DBusConnection connection) : base(connection, "org.freedesktop.portal.IBus") { } protected override async Task Connect(string name) { - var portal = new OrgFreedesktopIBusPortalProxy(Connection, name, "/org/freedesktop/IBus"); + var portal = new Portal(Connection, name, "/org/freedesktop/IBus"); var path = await portal.CreateInputContextAsync(GetAppName()); - _service = new OrgFreedesktopIBusServiceProxy(Connection, name, path); - _context = new OrgFreedesktopIBusInputContextProxy(Connection, name, path); + _service = new Service(Connection, name, path); + _context = new InputContext(Connection, name, path); AddDisposable(await _context.WatchCommitTextAsync(OnCommitText)); AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey)); AddDisposable(await _context.WatchUpdatePreeditTextAsync(OnUpdatePreedit)); @@ -37,23 +36,23 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus return true; } - private void OnHidePreedit(Exception? obj) + private void OnHidePreedit() { if (Client?.SupportsPreedit != true || string.IsNullOrEmpty(_preeditText)) { return; } - + _preeditText = ""; - + Client?.SetPreeditText(_preeditText, 0); } - private void OnShowPreedit(Exception? obj) + private void OnShowPreedit() { } - private void OnUpdatePreedit(Exception? arg1, (VariantValue Text, uint CursorPos, bool Visible) preeditComponents) + private void OnUpdatePreedit((VariantValue Text, uint CursorPos, bool Visible) preeditComponents) { string? preeditText; @@ -81,14 +80,8 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus Client.SetPreeditText(_preeditText, _preeditCursor); } - private void OnForwardKey(Exception? e, (uint keyval, uint keycode, uint state) k) + private void OnForwardKey((uint keyval, uint keycode, uint state) k) { - if (e is not null) - { - Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnForwardKey failed: {e}"); - return; - } - var state = (IBusModifierMask)k.state; KeyModifiers mods = default; if (state.HasAllFlags(IBusModifierMask.ControlMask)) @@ -107,7 +100,7 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus }); } - private void OnCommitText(Exception? e, VariantValue variantItem) + private void OnCommitText(VariantValue variantItem) { if (_insideReset > 0) { @@ -117,11 +110,6 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus // check if we have any pending Reset calls and ignore the signal here return; } - if (e is not null) - { - Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, $"OnCommitText failed: {e}"); - return; - } if (variantItem.Count >= 3 && variantItem.GetItem(2) is { Type: VariantValueType.String } stringItem) FireCommit(stringItem.GetString()); diff --git a/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs b/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs index 3ca9bf5eda..1aefdcd3fe 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Collections.Generic; using Avalonia.FreeDesktop.DBusIme.Fcitx; @@ -9,7 +8,7 @@ namespace Avalonia.FreeDesktop.DBusIme { internal class X11DBusImeHelper { - private static readonly Dictionary> KnownMethods = new() + private static readonly Dictionary> KnownMethods = new() { ["fcitx"] = static conn => new DBusInputMethodFactory(_ => new FcitxX11TextInputMethod(conn)), @@ -19,7 +18,7 @@ namespace Avalonia.FreeDesktop.DBusIme new DBusInputMethodFactory(_ => new IBusX11TextInputMethod(conn)) }; - private static Func? DetectInputMethod() + private static Func? DetectInputMethod() { foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" }) { diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 74e5157bc1..2326c00811 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -1,4 +1,3 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Collections.Generic; using System.Collections.Specialized; @@ -12,7 +11,7 @@ using Avalonia.Platform; using Avalonia.Threading; using Avalonia.Logging; using Tmds.DBus.Protocol; -using Tmds.DBus.SourceGenerator; +using Avalonia.FreeDesktop.DBus; namespace Avalonia.FreeDesktop { @@ -21,51 +20,53 @@ namespace Avalonia.FreeDesktop public static ITopLevelNativeMenuExporter? TryCreateTopLevelNativeMenu(IntPtr xid) => DBusHelper.DefaultConnection is {} conn ? new DBusMenuExporterImpl(conn, xid) : null; - public static INativeMenuExporter TryCreateDetachedNativeMenu(string path, Connection currentConnection) => + public static INativeMenuExporter TryCreateDetachedNativeMenu(string path, DBusConnection currentConnection) => new DBusMenuExporterImpl(currentConnection, path); public static string GenerateDBusMenuObjPath => $"/net/avaloniaui/dbusmenu/{Guid.NewGuid():N}"; - private sealed class DBusMenuExporterImpl : ComCanonicalDbusmenuHandler, ITopLevelNativeMenuExporter, IDisposable + private sealed class DBusMenuExporterImpl : DBusHandler, IdbusmenuHandler, IdbusmenuProperties, ITopLevelNativeMenuExporter, IDisposable { private readonly Dictionary _idsToItems = new(); private readonly Dictionary _itemsToIds = new(); private readonly HashSet _menus = []; - private readonly PathHandler _pathHandler; private readonly uint _xid; private readonly bool _appMenu = true; - private ComCanonicalAppMenuRegistrarProxy? _registrar; + private Registrar? _registrar; private NativeMenu? _menu; private bool _disposed; private uint _revision = 1; private bool _resetQueued; private int _nextId = 1; - public DBusMenuExporterImpl(Connection connection, IntPtr xid) + public DBusMenuExporterImpl(DBusConnection connection, IntPtr xid) + : base(connection, GenerateDBusMenuObjPath, handlesChildPaths: false) { - Version = 4; - Connection = connection; _xid = (uint)xid.ToInt32(); - _pathHandler = new PathHandler(GenerateDBusMenuObjPath); - _pathHandler.Add(this); SetNativeMenu([]); _ = InitializeAsync(); } - public DBusMenuExporterImpl(Connection connection, string path) + public DBusMenuExporterImpl(DBusConnection connection, string path) + : base(connection, path, handlesChildPaths: false) { - Version = 4; - Connection = connection; _appMenu = false; - _pathHandler = new PathHandler(path); - _pathHandler.Add(this); SetNativeMenu([]); _ = InitializeAsync(); } - public override Connection Connection { get; } + uint IdbusmenuProperties.Version => 4; + string IdbusmenuProperties.TextDirection => "ltr"; + string IdbusmenuProperties.Status => "normal"; + string[] IdbusmenuProperties.IconThemePath => []; - protected override ValueTask<(uint Revision, (int, Dictionary, VariantValue[]) Layout)> OnGetLayoutAsync(Message message, int parentId, int recursionDepth, string[] propertyNames) + ValueTask IdbusmenuHandler.HandleGetPropertyAsync(IdbusmenuHandler.GetPropertyContext context) + => context.Handle(this); + + ValueTask IdbusmenuHandler.HandleGetAllPropertiesAsync(IdbusmenuHandler.GetAllPropertiesContext context) + => context.Handle(this); + + ValueTask<(uint Revision, (int, Dictionary, VariantValue[]) Layout)> IdbusmenuHandler.GetLayoutAsync(int parentId, int recursionDepth, string[] propertyNames) { var menu = GetMenu(parentId); var layout = GetLayout(menu.item, menu.menu, recursionDepth, propertyNames); @@ -78,41 +79,41 @@ namespace Avalonia.FreeDesktop return new ValueTask<(uint, (int, Dictionary, VariantValue[]))>((_revision, layout)); } - protected override ValueTask<(int, Dictionary)[]> OnGetGroupPropertiesAsync(Message message, int[] ids, string[] propertyNames) + ValueTask<(int, Dictionary)[]> IdbusmenuHandler.GetGroupPropertiesAsync(int[] ids, string[] propertyNames) => new(ids.Select(id => (id, GetProperties(GetMenu(id), propertyNames))).ToArray()); - protected override ValueTask OnGetPropertyAsync(Message message, int id, string name) => + ValueTask IdbusmenuHandler.GetPropertyAsync(int id, string name) => new(GetProperty(GetMenu(id), name) ?? VariantValue.Int32(0)); - protected override ValueTask OnEventAsync(Message message, int id, string eventId, VariantValue data, uint timestamp) + ValueTask IdbusmenuHandler.EventAsync(int id, string eventId, VariantValue data, uint timestamp) { HandleEvent(id, eventId); return new ValueTask(); } - protected override ValueTask OnEventGroupAsync(Message message, (int, string, VariantValue, uint)[] events) + ValueTask IdbusmenuHandler.EventGroupAsync((int, string, VariantValue, uint)[] events) { foreach (var e in events) HandleEvent(e.Item1, e.Item2); return new ValueTask([]); } - protected override ValueTask OnAboutToShowAsync(Message message, int id) => new(false); + ValueTask IdbusmenuHandler.AboutToShowAsync(int id) => new(false); - protected override ValueTask<(int[] UpdatesNeeded, int[] IdErrors)> OnAboutToShowGroupAsync(Message message, int[] ids) => + ValueTask<(int[] UpdatesNeeded, int[] IdErrors)> IdbusmenuHandler.AboutToShowGroupAsync(int[] ids) => new(([], [])); private async Task InitializeAsync() { - Connection.AddMethodHandler(_pathHandler); + Connection.AddMethodHandler(this); if (!_appMenu) return; - _registrar = new ComCanonicalAppMenuRegistrarProxy(Connection, "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar"); + _registrar = new Registrar(Connection, "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar"); try { if (!_disposed) - await _registrar.RegisterWindowAsync(_xid, _pathHandler.Path); + await _registrar.RegisterWindowAsync(_xid, Path); } catch { @@ -138,8 +139,7 @@ namespace Avalonia.FreeDesktop ?.Log(this, "DBusMenu UnregisterWindowAsync failed: {Exception}", t.Exception); } }, TaskContinuationOptions.OnlyOnFaulted); - _pathHandler.Remove(this); - Connection.RemoveMethodHandler(_pathHandler.Path); + Connection.RemoveMethodHandler(Path); } public bool IsNativeMenuExported { get; private set; } @@ -176,7 +176,7 @@ namespace Avalonia.FreeDesktop _idsToItems.Clear(); _itemsToIds.Clear(); _revision++; - EmitLayoutUpdated(_revision, 0); + Connection.EmitLayoutUpdated(Path, _revision, 0); } private void QueueReset() diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs index 1d819bc34e..882219f9a1 100644 --- a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs +++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs @@ -1,17 +1,16 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. -using System; +using System; using System.Threading.Tasks; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Threading; using Tmds.DBus.Protocol; -using Tmds.DBus.SourceGenerator; +using Avalonia.FreeDesktop.DBus; namespace Avalonia.FreeDesktop { internal class DBusPlatformSettings : DefaultPlatformSettings { - private readonly OrgFreedesktopPortalSettingsProxy? _settings; + private readonly Settings? _settings; private PlatformColorValues? _lastColorValues; private PlatformThemeVariant? _themeVariant; @@ -22,7 +21,7 @@ namespace Avalonia.FreeDesktop if (DBusHelper.DefaultConnection is not { } conn) return; - _settings = new OrgFreedesktopPortalSettingsProxy(conn, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + _settings = new Settings(conn, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); _ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); _ = TryGetInitialValuesAsync(); } @@ -42,7 +41,7 @@ namespace Avalonia.FreeDesktop { try { - var version = await _settings!.GetVersionPropertyAsync(); + var version = await _settings!.GetVersionAsync(); VariantValue value; if (version >= 2) value = await _settings!.ReadOneAsync("org.freedesktop.appearance", "color-scheme"); @@ -51,7 +50,7 @@ namespace Avalonia.FreeDesktop value = (await _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme")).GetVariantValue(); return ToColorScheme(value.GetUInt32()); } - catch (DBusException) + catch (DBusExceptionBase) { return null; } @@ -61,7 +60,7 @@ namespace Avalonia.FreeDesktop { try { - var version = await _settings!.GetVersionPropertyAsync(); + var version = await _settings!.GetVersionAsync(); VariantValue value; if (version >= 2) value = await _settings!.ReadOneAsync("org.freedesktop.appearance", "accent-color"); @@ -69,17 +68,14 @@ namespace Avalonia.FreeDesktop value = await _settings!.ReadAsync("org.freedesktop.appearance", "accent-color"); return ToAccentColor(value); } - catch (DBusException) + catch (DBusExceptionBase) { return null; } } - private void SettingsChangedHandler(Exception? exception, (string Namespace, string Key, VariantValue Value) tuple) + private void SettingsChangedHandler((string Namespace, string Key, VariantValue Value) tuple) { - if (exception is not null) - return; - switch (tuple) { case ("org.freedesktop.appearance", "color-scheme", var colorScheme): diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index 4434400475..078a79ca90 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -1,6 +1,6 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -10,7 +10,7 @@ using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Avalonia.Threading; using Tmds.DBus.Protocol; -using Tmds.DBus.SourceGenerator; +using Avalonia.FreeDesktop.DBus; namespace Avalonia.FreeDesktop { @@ -32,12 +32,12 @@ namespace Avalonia.FreeDesktop using var restoreContext = AvaloniaSynchronizationContext.Ensure(DispatcherPriority.Input); - var dbusFileChooser = new OrgFreedesktopPortalFileChooserProxy(conn, "org.freedesktop.portal.Desktop", + var dbusFileChooser = new FileChooser(conn, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); uint version; try { - version = await dbusFileChooser.GetVersionPropertyAsync(); + version = await dbusFileChooser.GetVersionAsync(); } catch { @@ -47,13 +47,13 @@ namespace Avalonia.FreeDesktop return new DBusSystemDialog(conn, parentLeaseProvider, dbusFileChooser, version); } - private readonly Connection _connection; - private readonly OrgFreedesktopPortalFileChooserProxy _fileChooser; + private readonly DBusConnection _connection; + private readonly FileChooser _fileChooser; private readonly Func>? _parentLeaseProvider; private readonly uint _version; - private DBusSystemDialog(Connection connection, Func>? parentLeaseProvider, - OrgFreedesktopPortalFileChooserProxy fileChooser, uint version) + private DBusSystemDialog(DBusConnection connection, Func>? parentLeaseProvider, + FileChooser fileChooser, uint version) { _connection = connection; _fileChooser = fileChooser; @@ -74,7 +74,7 @@ namespace Avalonia.FreeDesktop { await using var parentLease = await AcquireParentLeaseAsync().ConfigureAwait(false); var parentWindow = parentLease?.Handle ?? string.Empty; - ObjectPath objectPath; + var (expectedPath, token) = CreateRequestToken(); var chooserOptions = new Dictionary(); if (TryParseFilters(options.FileTypeFilter, options.SuggestedFileType, out var filters, @@ -89,19 +89,20 @@ namespace Avalonia.FreeDesktop chooserOptions.Add("current_folder", VariantValue.Array(Encoding.UTF8.GetBytes(folderPath + "\0"))); chooserOptions.Add("multiple", VariantValue.Bool(options.AllowMultiple)); + chooserOptions.Add("handle_token", token); - objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - - var request = - new OrgFreedesktopPortalRequestProxy(_connection, "org.freedesktop.portal.Desktop", objectPath); + var request = new Request(_connection, "org.freedesktop.portal.Desktop", expectedPath); var tsc = new TaskCompletionSource(); - using var disposable = await request.WatchResponseAsync((e, x) => + using var disposable = await request.WatchResponseAsync(notification => { - if (e is not null) - tsc.TrySetException(e); + if (notification.IsCompletion) + tsc.TrySetException(notification.Exception); else - tsc.TrySetResult(x.Results["uris"].GetArray()); - }); + tsc.TrySetResult(notification.Value.Results["uris"].GetArray()); + }, ObserverFlags.EmitAll); + + var actualPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); + VerifyRequestPath(tsc, expectedPath, actualPath); var uris = await tsc.Task ?? []; return uris.Select(static path => new BclStorageFile(new FileInfo(new Uri(path).LocalPath))).ToList(); @@ -124,7 +125,7 @@ namespace Avalonia.FreeDesktop { await using var parentLease = await AcquireParentLeaseAsync().ConfigureAwait(false); var parentWindow = parentLease?.Handle ?? string.Empty; - ObjectPath objectPath; + var (expectedPath, token) = CreateRequestToken(); var chooserOptions = new Dictionary(); if (TryParseFilters(options.FileTypeChoices, options.SuggestedFileType, out var filters, out var currentFilter)) @@ -138,21 +139,20 @@ namespace Avalonia.FreeDesktop chooserOptions.Add("current_name", VariantValue.String(currentName)); if (options.SuggestedStartLocation?.TryGetLocalPath() is { } folderPath) chooserOptions.Add("current_folder", VariantValue.Array(Encoding.UTF8.GetBytes(folderPath + "\0"))); + chooserOptions.Add("handle_token", token); - objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions) - .ConfigureAwait(false); - var request = - new OrgFreedesktopPortalRequestProxy(_connection, "org.freedesktop.portal.Desktop", objectPath); + var request = new Request(_connection, "org.freedesktop.portal.Desktop", expectedPath); var tsc = new TaskCompletionSource(); FilePickerFileType? selectedType = null; - using var disposable = await request.WatchResponseAsync((e, x) => + using var disposable = await request.WatchResponseAsync(notification => { - if (e is not null) + if (notification.IsCompletion) { - tsc.TrySetException(e); + tsc.TrySetException(notification.Exception); } else { + var x = notification.Value; if (x.Results.TryGetValue("current_filter", out var currentFilter)) { var name = currentFilter.GetItem(0).GetString(); @@ -166,19 +166,23 @@ namespace Avalonia.FreeDesktop else patterns.Add(t.GetItem(1).GetString()); } - + // Reuse the file type objects from options // so the consuming code can match exactly the // file type selected instead of spawning one. selectedType = options.FileTypeChoices?.FirstOrDefault(type => type.Name == name && ( (type.MimeTypes?.All(y => mimeTypes.Contains(y)) ?? false) || - (type.Patterns?.All(y => patterns.Contains(y)) ?? false))) + (type.Patterns?.All(y => patterns.Contains(y)) ?? false))) ?? new FilePickerFileType(name) { MimeTypes = mimeTypes, Patterns = patterns }; } tsc.TrySetResult(x.Results["uris"].GetArray()); } - }).ConfigureAwait(false); + }, ObserverFlags.EmitAll).ConfigureAwait(false); + + var actualPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions) + .ConfigureAwait(false); + VerifyRequestPath(tsc, expectedPath, actualPath); var uris = await tsc.Task.ConfigureAwait(false); var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).LocalPath : null; @@ -198,6 +202,7 @@ namespace Avalonia.FreeDesktop await using var parentLease = await AcquireParentLeaseAsync().ConfigureAwait(false); var parentWindow = parentLease?.Handle ?? string.Empty; + var (expectedPath, token) = CreateRequestToken(); var chooserOptions = new Dictionary { { "directory", VariantValue.Bool(true) }, { "multiple", VariantValue.Bool(options.AllowMultiple) } @@ -207,19 +212,20 @@ namespace Avalonia.FreeDesktop chooserOptions.Add("current_name", VariantValue.String(currentName)); if (options.SuggestedStartLocation?.TryGetLocalPath() is { } folderPath) chooserOptions.Add("current_folder", VariantValue.Array(Encoding.UTF8.GetBytes(folderPath + "\0"))); + chooserOptions.Add("handle_token", token); - var objectPath = - await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - var request = - new OrgFreedesktopPortalRequestProxy(_connection, "org.freedesktop.portal.Desktop", objectPath); + var request = new Request(_connection, "org.freedesktop.portal.Desktop", expectedPath); var tsc = new TaskCompletionSource(); - using var disposable = await request.WatchResponseAsync((e, x) => + using var disposable = await request.WatchResponseAsync(notification => { - if (e is not null) - tsc.TrySetException(e); + if (notification.IsCompletion) + tsc.TrySetException(notification.Exception); else - tsc.TrySetResult(x.Results["uris"].GetArray()); - }); + tsc.TrySetResult(notification.Value.Results["uris"].GetArray()); + }, ObserverFlags.EmitAll); + + var actualPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); + VerifyRequestPath(tsc, expectedPath, actualPath); var uris = await tsc.Task ?? Array.Empty(); return uris @@ -273,5 +279,21 @@ namespace Avalonia.FreeDesktop result = filters.AsVariantValue(); return true; } + + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.portal.Request.html + // Subscribe to the Response signal before making the portal call to avoid a race condition. + private (ObjectPath ExpectedPath, string Token) CreateRequestToken() + { + string sender = (_connection.UniqueName ?? "").TrimStart(':').Replace(".", "_"); + string token = "Avalonia_" + Stopwatch.GetTimestamp().ToString(); + ObjectPath expectedPath = $"/org/freedesktop/portal/desktop/request/{sender}/{token}"; + return (expectedPath, token); + } + + private static void VerifyRequestPath(TaskCompletionSource tsc, ObjectPath expectedPath, ObjectPath actualPath) + { + if (actualPath != expectedPath) + tsc.TrySetException(new InvalidOperationException($"Portal returned unexpected request path '{actualPath}', expected '{expectedPath}'.")); + } } } diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index a48a18d2dc..6f628a6814 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -1,13 +1,13 @@ -#pragma warning disable CS0618 // TODO: Temporary workaround until Tmds is replaced. using System; using System.Diagnostics; +using System.Threading; using System.Threading.Tasks; using Avalonia.Controls.Platform; using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Threading; using Tmds.DBus.Protocol; -using Tmds.DBus.SourceGenerator; +using Avalonia.FreeDesktop.DBus; namespace Avalonia.FreeDesktop { @@ -16,13 +16,10 @@ namespace Avalonia.FreeDesktop private static int s_trayIconInstanceId; public static readonly (int, int, byte[]) EmptyPixmap = (1, 1, [255, 0, 0, 0]); - private readonly Connection? _connection; - private readonly OrgFreedesktopDBusProxy? _dBus; - - private IDisposable? _serviceWatchDisposable; - private readonly PathHandler _pathHandler = new("/StatusNotifierItem"); + private readonly DBusConnection? _connection; + private CancellationTokenSource? _watchCts; private readonly StatusNotifierItemDbusObj? _statusNotifierItemDbusObj; - private OrgKdeStatusNotifierWatcherProxy? _statusNotifierWatcher; + private StatusNotifierWatcher? _statusNotifierWatcher; private (int, int, byte[]) _icon; private string? _sysTrayServiceName; @@ -51,14 +48,12 @@ namespace Avalonia.FreeDesktop IsActive = true; - _dBus = new OrgFreedesktopDBusProxy(_connection, "org.freedesktop.DBus", "/org/freedesktop/DBus"); var dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(dbusMenuPath, _connection); _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_connection, dbusMenuPath); - _pathHandler.Add(_statusNotifierItemDbusObj); - _connection.AddMethodHandler(_pathHandler); + _connection.AddMethodHandler(_statusNotifierItemDbusObj); _statusNotifierItemDbusObj.ActivationDelegate += () => OnClicked?.Invoke(); WatchAsync(); @@ -68,27 +63,52 @@ namespace Avalonia.FreeDesktop { try { - _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item1, x.Item3)); - var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher"); - OnNameChange("org.kde.StatusNotifierWatcher", nameOwner); + _watchCts = new CancellationTokenSource(); + using var watcher = await _connection!.WatchNameOwnerAsync("org.kde.StatusNotifierWatcher"); + var owner = watcher.GetCurrentOwner(); + OnOwnerChanged(owner); + while (!_watchCts.IsCancellationRequested) + { + if (owner is not null) + { + var ct = watcher.GetOwnerChangedCancellationToken(owner); + using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct, _watchCts.Token); + try + { + await Task.Delay(Timeout.Infinite, linked.Token); + } + catch (OperationCanceledException) when (!_watchCts.IsCancellationRequested) + { } + } + else + { + try + { + await watcher.WaitForOwnerAsync(_watchCts.Token); + } + catch (OperationCanceledException) when (!_watchCts.IsCancellationRequested) + { } + } + owner = watcher.GetCurrentOwner(); + OnOwnerChanged(owner); + } } - catch (Exception e) + catch (Exception e) when (!_isDisposed) { - _serviceWatchDisposable = null; Logger.TryGet(LogEventLevel.Error, "DBUS") ?.Log(this, "Interface 'org.kde.StatusNotifierWatcher' is unavailable.\n{Exception}", e); } } - private void OnNameChange(string name, string? newOwner) + private void OnOwnerChanged(string? newOwner) { - if (_isDisposed || _connection is null || name != "org.kde.StatusNotifierWatcher") + if (_isDisposed || _connection is null) return; if (!_serviceConnected && newOwner is not null) { _serviceConnected = true; - _statusNotifierWatcher = new OrgKdeStatusNotifierWatcherProxy(_connection, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher"); + _statusNotifierWatcher = new StatusNotifierWatcher(_connection, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher"); DestroyTrayIcon(); @@ -114,15 +134,11 @@ namespace Avalonia.FreeDesktop #endif var tid = s_trayIconInstanceId++; - // make sure not to add the path handle and connection method handler twice - if (_statusNotifierItemDbusObj!.PathHandler is null) - _pathHandler.Add(_statusNotifierItemDbusObj!); - - _connection.RemoveMethodHandler(_pathHandler.Path); - _connection.AddMethodHandler(_pathHandler); + _connection.RemoveMethodHandler(_statusNotifierItemDbusObj!.Path); + _connection.AddMethodHandler(_statusNotifierItemDbusObj); _sysTrayServiceName = FormattableString.Invariant($"org.kde.StatusNotifierItem-{pid}-{tid}"); - await _dBus!.RequestNameAsync(_sysTrayServiceName, 0); + await _connection.RequestNameAsync(_sysTrayServiceName); await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); _statusNotifierItemDbusObj!.SetTitleAndTooltip(_tooltipText); @@ -134,9 +150,8 @@ namespace Avalonia.FreeDesktop if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierItemDbusObj is null || _sysTrayServiceName is null) return; - _dBus!.ReleaseNameAsync(_sysTrayServiceName); - _pathHandler.Remove(_statusNotifierItemDbusObj); - _connection.RemoveMethodHandler(_pathHandler.Path); + _connection!.ReleaseNameAsync(_sysTrayServiceName); + _connection.RemoveMethodHandler(_statusNotifierItemDbusObj.Path); } public void Dispose() @@ -144,7 +159,7 @@ namespace Avalonia.FreeDesktop IsActive = false; DestroyTrayIcon(); (MenuExporter as IDisposable)?.Dispose(); - _serviceWatchDisposable?.Dispose(); + _watchCts?.Cancel(); _isDisposed = true; } @@ -221,43 +236,72 @@ namespace Avalonia.FreeDesktop /// /// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html /// - internal class StatusNotifierItemDbusObj : OrgKdeStatusNotifierItemHandler + internal class StatusNotifierItemDbusObj : DBusHandler, IStatusNotifierItemHandler, IStatusNotifierItemProperties { - public StatusNotifierItemDbusObj(Connection connection, ObjectPath dbusMenuPath) + // The item is active, is more important that the item will be shown in some way to the user. + private const string StatusActive = "Active"; + private string _category = ""; + private string _id = ""; + private string _title = ""; + private ObjectPath _menu; + private (int, int, byte[])[] _iconPixmap = []; + + public StatusNotifierItemDbusObj(DBusConnection connection, ObjectPath dbusMenuPath) + : base(connection, "/StatusNotifierItem", handlesChildPaths: false) { - Connection = connection; - Menu = dbusMenuPath; + _menu = dbusMenuPath; } - public override Connection Connection { get; } - public event Action? ActivationDelegate; - protected override ValueTask OnContextMenuAsync(Message message, int x, int y) => new(); - - protected override ValueTask OnActivateAsync(Message message, int x, int y) + string IStatusNotifierItemProperties.Category => _category; + string IStatusNotifierItemProperties.Id => _id; + string IStatusNotifierItemProperties.Title => _title; + string IStatusNotifierItemProperties.Status => StatusActive; + int IStatusNotifierItemProperties.WindowId => 0; + string IStatusNotifierItemProperties.IconThemePath => ""; + ObjectPath IStatusNotifierItemProperties.Menu => _menu; + bool IStatusNotifierItemProperties.ItemIsMenu => false; + string IStatusNotifierItemProperties.IconName => ""; + (int, int, byte[])[] IStatusNotifierItemProperties.IconPixmap => _iconPixmap; + string IStatusNotifierItemProperties.OverlayIconName => ""; + (int, int, byte[])[] IStatusNotifierItemProperties.OverlayIconPixmap => []; + string IStatusNotifierItemProperties.AttentionIconName => ""; + (int, int, byte[])[] IStatusNotifierItemProperties.AttentionIconPixmap => []; + string IStatusNotifierItemProperties.AttentionMovieName => ""; + (string, (int, int, byte[])[], string, string) IStatusNotifierItemProperties.ToolTip => ("", [], "", ""); + + ValueTask IStatusNotifierItemHandler.HandleGetPropertyAsync(IStatusNotifierItemHandler.GetPropertyContext context) + => context.Handle(this); + + ValueTask IStatusNotifierItemHandler.HandleGetAllPropertiesAsync(IStatusNotifierItemHandler.GetAllPropertiesContext context) + => context.Handle(this); + + ValueTask IStatusNotifierItemHandler.ContextMenuAsync(int x, int y) => new(); + + ValueTask IStatusNotifierItemHandler.ActivateAsync(int x, int y) { ActivationDelegate?.Invoke(); return new ValueTask(); } - protected override ValueTask OnSecondaryActivateAsync(Message message, int x, int y) => new(); + ValueTask IStatusNotifierItemHandler.SecondaryActivateAsync(int x, int y) => new(); - protected override ValueTask OnScrollAsync(Message message, int delta, string orientation) => new(); + ValueTask IStatusNotifierItemHandler.ScrollAsync(int delta, string orientation) => new(); public void InvalidateAll() { - EmitNewTitle(); - EmitNewIcon(); - EmitNewAttentionIcon(); - EmitNewOverlayIcon(); - EmitNewToolTip(); - EmitNewStatus(Status); + Connection.EmitNewTitle(Path); + Connection.EmitNewIcon(Path); + Connection.EmitNewAttentionIcon(Path); + Connection.EmitNewOverlayIcon(Path); + Connection.EmitNewToolTip(Path); + Connection.EmitNewStatus(Path, StatusActive); } public void SetIcon((int, int, byte[]) dbusPixmap) { - IconPixmap = [dbusPixmap]; + _iconPixmap = [dbusPixmap]; InvalidateAll(); } @@ -266,10 +310,9 @@ namespace Avalonia.FreeDesktop if (text is null) return; - Id = text; - Category = "ApplicationStatus"; - Status = text; - Title = text; + _id = text; + _category = "ApplicationStatus"; + _title = text; InvalidateAll(); } }