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