From d7904b34cd7995935b4dce91bc057c5480973031 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Thu, 29 Dec 2022 20:55:14 +0100 Subject: [PATCH 001/155] Use Tmds.DBus.Protocol --- .gitmodules | 3 + Avalonia.Desktop.slnf | 7 +- Avalonia.sln | 7 + NuGet.Config | 1 + .../AppMenuRegistrar.DBus.cs | 176 ++++ .../Avalonia.FreeDesktop.csproj | 3 +- src/Avalonia.FreeDesktop/DBus.DBus.cs | 643 ++++++++++++ src/Avalonia.FreeDesktop/DBusFileChooser.cs | 32 - src/Avalonia.FreeDesktop/DBusHelper.cs | 54 +- .../DBusIme/DBusTextInputMethodBase.cs | 58 +- .../DBusIme/Fcitx/Fcitx.DBus.cs | 627 ++++++++++++ .../DBusIme/Fcitx/FcitxDBus.cs | 69 -- .../DBusIme/Fcitx/FcitxICWrapper.cs | 32 +- .../DBusIme/Fcitx/FcitxX11TextInputMethod.cs | 47 +- .../DBusIme/IBus/IBus.DBus.cs | 513 ++++++++++ .../DBusIme/IBus/IBusDBus.cs | 52 - .../DBusIme/IBus/IBusEnums.cs | 2 +- .../DBusIme/IBus/IBusX11TextInputMethod.cs | 44 +- .../DBusIme/X11DBusImeHelper.cs | 25 +- src/Avalonia.FreeDesktop/DBusMenu.DBus.cs | 463 +++++++++ src/Avalonia.FreeDesktop/DBusMenu.cs | 56 -- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 432 ++++---- src/Avalonia.FreeDesktop/DBusRequest.cs | 16 - src/Avalonia.FreeDesktop/DBusSystemDialog.cs | 107 +- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 415 ++++---- .../FreeDesktopPortalDesktop.DBus.cs | 937 ++++++++++++++++++ .../StatusNotifierWatcher.DBus.cs | 351 +++++++ src/Avalonia.X11/X11Window.cs | 6 +- src/Linux/Tmds.DBus | 1 + 29 files changed, 4272 insertions(+), 907 deletions(-) create mode 100644 src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs create mode 100644 src/Avalonia.FreeDesktop/DBus.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusFileChooser.cs create mode 100644 src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs create mode 100644 src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs create mode 100644 src/Avalonia.FreeDesktop/DBusMenu.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusMenu.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusRequest.cs create mode 100644 src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs create mode 100644 src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs create mode 160000 src/Linux/Tmds.DBus diff --git a/.gitmodules b/.gitmodules index 032bc879cc..16bc977251 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "nukebuild/il-repack"] path = nukebuild/il-repack url = https://github.com/Gillibald/il-repack +[submodule "Tmds.DBus"] + path = src/Linux/Tmds.DBus + url = https://github.com/affederaffe/Tmds.DBus diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 3fa8e969c8..72eb13d0a9 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -7,9 +7,9 @@ "samples\\ControlCatalog\\ControlCatalog.csproj", "samples\\IntegrationTestApp\\IntegrationTestApp.csproj", "samples\\MiniMvvm\\MiniMvvm.csproj", + "samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj", "samples\\SampleControls\\ControlSamples.csproj", "samples\\Sandbox\\Sandbox.csproj", - "samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj", "src\\Avalonia.Base\\Avalonia.Base.csproj", "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj", "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj", @@ -31,15 +31,16 @@ "src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj", "src\\Avalonia.X11\\Avalonia.X11.csproj", "src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj", + "src\\Linux\\Tmds.DBus\\src\\Tmds.DBus.Protocol\\Tmds.DBus.Protocol.csproj", "src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj", "src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj", "src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj", "src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj", + "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", + "src\\tools\\DevGenerators\\DevGenerators.csproj", "src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj", "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj", "src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj", - "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", - "src\\tools\\DevGenerators\\DevGenerators.csproj", "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj", "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj", "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj", diff --git a/Avalonia.sln b/Avalonia.sln index fc42a5d63b..e399483896 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -231,6 +231,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.Protocol", "src\Linux\Tmds.DBus\src\Tmds.DBus.Protocol\Tmds.DBus.Protocol.csproj", "{29E25263-3CC3-4D55-A042-00BA136867D4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -542,6 +544,10 @@ Global {75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Debug|Any CPU.Build.0 = Debug|Any CPU {75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.ActiveCfg = Release|Any CPU {75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = Release|Any CPU + {29E25263-3CC3-4D55-A042-00BA136867D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29E25263-3CC3-4D55-A042-00BA136867D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29E25263-3CC3-4D55-A042-00BA136867D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29E25263-3CC3-4D55-A042-00BA136867D4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -606,6 +612,7 @@ Global {15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098} {90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098} {75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {29E25263-3CC3-4D55-A042-00BA136867D4} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/NuGet.Config b/NuGet.Config index 7d2bd8abd2..7a9dd7993c 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -6,5 +6,6 @@ + diff --git a/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs b/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs new file mode 100644 index 0000000000..a45d1642fc --- /dev/null +++ b/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs @@ -0,0 +1,176 @@ +using System; +using System.Threading.Tasks; + +using Tmds.DBus.Protocol; + + +namespace Avalonia.FreeDesktop +{ + internal class Registrar : AppMenuRegistrarObject + { + private const string Interface = "com.canonical.AppMenu.Registrar"; + + public Registrar(RegistrarService service, ObjectPath path) : base(service, path) { } + + public Task RegisterWindowAsync(uint windowId, ObjectPath menuObjectPath) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "uo", + member: "RegisterWindow", + flags: MessageFlags.NoReplyExpected); + writer.WriteUInt32(windowId); + writer.WriteObjectPath(menuObjectPath); + return writer.CreateMessage(); + } + } + + public Task UnregisterWindowAsync(uint windowId) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "u", + member: "UnregisterWindow"); + writer.WriteUInt32(windowId); + return writer.CreateMessage(); + } + } + + public Task<(string Service, ObjectPath MenuObjectPath)> GetMenuForWindowAsync(uint windowId) + { + return Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_so(m, (AppMenuRegistrarObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "u", + member: "GetMenuForWindow"); + writer.WriteUInt32(windowId); + return writer.CreateMessage(); + } + } + } + + internal class RegistrarService + { + public RegistrarService(Connection connection, string destination) + => (Connection, Destination) = (connection, destination); + + public Connection Connection { get; } + public string Destination { get; } + public Registrar CreateRegistrar(string path) => new Registrar(this, path); + } + + internal class AppMenuRegistrarObject + { + protected AppMenuRegistrarObject(RegistrarService service, ObjectPath path) + => (Service, Path) = (service, path); + + public RegistrarService Service { get; } + public ObjectPath Path { get; } + protected Connection Connection => Service.Connection; + + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + + protected ValueTask WatchPropertiesChangedAsync(string @interface, + MessageValueReader> reader, Action> handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return Connection.AddMatchAsync(rule, reader, + (Exception? ex, PropertyChanges changes, object? rs, object? hs) => + ((Action>)hs!).Invoke(ex, changes), + this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, + MessageValueReader reader, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, reader, + (Exception? ex, TArg arg, object? rs, object? hs) => ((Action)hs!).Invoke(ex, arg), + this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, (Message message, object? state) => null!, + (Exception? ex, object v, object? rs, object? hs) => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + } + + protected static (string, ObjectPath) ReadMessage_so(Message message, AppMenuRegistrarObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadString(); + var arg1 = reader.ReadObjectPath(); + return (arg0, arg1); + } + } +} diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index 3b1c6cc7b1..a33fa7b32d 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -12,7 +12,8 @@ - + + diff --git a/src/Avalonia.FreeDesktop/DBus.DBus.cs b/src/Avalonia.FreeDesktop/DBus.DBus.cs new file mode 100644 index 0000000000..4cf2b76b45 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBus.DBus.cs @@ -0,0 +1,643 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Tmds.DBus.Protocol; + + +namespace Avalonia.FreeDesktop +{ + internal record DBusProperties + { + public string[] Features { get; set; } = default!; + public string[] Interfaces { get; set; } = default!; + } + + internal class DBus : DBusObject + { + private const string Interface = "org.freedesktop.DBus"; + + public DBus(DBusService service, ObjectPath path) : base(service, path) { } + + public Task HelloAsync() + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_s(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "Hello"); + return writer.CreateMessage(); + } + } + + public Task RequestNameAsync(string a0, uint a1) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "su", + member: "RequestName"); + writer.WriteString(a0); + writer.WriteUInt32(a1); + return writer.CreateMessage(); + } + } + + public Task ReleaseNameAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "ReleaseName"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task StartServiceByNameAsync(string a0, uint a1) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "su", + member: "StartServiceByName"); + writer.WriteString(a0); + writer.WriteUInt32(a1); + return writer.CreateMessage(); + } + } + + public Task UpdateActivationEnvironmentAsync(Dictionary a0) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "a{ss}", + member: "UpdateActivationEnvironment"); + writer.WriteDictionary(a0); + return writer.CreateMessage(); + } + } + + public Task NameHasOwnerAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_b(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "NameHasOwner"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task ListNamesAsync() + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_as(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "ListNames"); + return writer.CreateMessage(); + } + } + + public Task ListActivatableNamesAsync() + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_as(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "ListActivatableNames"); + return writer.CreateMessage(); + } + } + + public Task AddMatchAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "AddMatch"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task RemoveMatchAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "RemoveMatch"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task GetNameOwnerAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_s(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetNameOwner"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task ListQueuedOwnersAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_as(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "ListQueuedOwners"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task GetConnectionUnixUserAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetConnectionUnixUser"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task GetConnectionUnixProcessIDAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetConnectionUnixProcessID"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task GetAdtAuditSessionDataAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_ay(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetAdtAuditSessionData"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task GetConnectionSELinuxSecurityContextAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_ay(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetConnectionSELinuxSecurityContext"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public Task ReloadConfigAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "ReloadConfig"); + return writer.CreateMessage(); + } + } + + public Task GetIdAsync() + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_s(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "GetId"); + return writer.CreateMessage(); + } + } + + public Task> GetConnectionCredentialsAsync(string a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_aesv(m, (DBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetConnectionCredentials"); + writer.WriteString(a0); + return writer.CreateMessage(); + } + } + + public ValueTask WatchNameOwnerChangedAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "NameOwnerChanged", static (m, s) => + ReadMessage_sss(m, (DBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchNameLostAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "NameLost", static (m, s) => + ReadMessage_s(m, (DBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchNameAcquiredAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "NameAcquired", static (m, s) => + ReadMessage_s(m, (DBusObject)s!), handler, emitOnCapturedContext); + + public Task SetFeaturesAsync(string[] value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("Features"); + writer.WriteSignature("as"); + writer.WriteArray(value); + return writer.CreateMessage(); + } + } + + public Task SetInterfacesAsync(string[] value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("Interfaces"); + writer.WriteSignature("as"); + writer.WriteArray(value); + return writer.CreateMessage(); + } + } + + public Task GetFeaturesAsync() => + Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Features"), static (m, s) => + ReadMessage_v_as(m, (DBusObject)s!), this); + + public Task GetInterfacesAsync() => + Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Interfaces"), static (m, s) => + ReadMessage_v_as(m, (DBusObject)s!), this); + + public Task GetPropertiesAsync() + { + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, s) => + ReadMessage(m, (DBusObject)s!), this); + + static DBusProperties ReadMessage(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) + { + return base.WatchPropertiesChangedAsync(Interface, static (m, s) => + ReadMessage(m, (DBusObject)s!), handler, emitOnCapturedContext); + + static PropertyChanges ReadMessage(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); + } + + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + var headersEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(headersEnd)) + { + invalidated ??= new List(); + var property = reader.ReadString(); + switch (property) + { + case "Features": + invalidated.Add("Features"); + break; + case "Interfaces": + invalidated.Add("Interfaces"); + break; + } + } + + return invalidated?.ToArray() ?? Array.Empty(); + } + } + + private static DBusProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new DBusProperties(); + var headersEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(headersEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "Features": + reader.ReadSignature("as"); + props.Features = reader.ReadArray(); + changedList?.Add("Features"); + break; + case "Interfaces": + reader.ReadSignature("as"); + props.Interfaces = reader.ReadArray(); + changedList?.Add("Interfaces"); + break; + default: + reader.ReadVariant(); + break; + } + } + + return props; + } + } + + internal class DBusService + { + public DBusService(Connection connection, string destination) + => (Connection, Destination) = (connection, destination); + + public Connection Connection { get; } + public string Destination { get; } + public DBus CreateDBus(string path) => new(this, path); + } + + internal class DBusObject + { + protected DBusObject(DBusService service, ObjectPath path) + { + Service = service; + Path = path; + } + + public DBusService Service { get; } + public ObjectPath Path { get; } + protected Connection Connection => Service.Connection; + + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + + protected ValueTask WatchPropertiesChangedAsync(string @interface, + MessageValueReader> reader, Action> handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => + ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, + MessageValueReader reader, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => + ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, static (_, _) => null!, static (ex, _, _, hs) => + ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + } + + protected static string ReadMessage_s(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadString(); + } + + protected static uint ReadMessage_u(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadUInt32(); + } + + protected static bool ReadMessage_b(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadBool(); + } + + protected static string[] ReadMessage_as(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadArray(); + } + + protected static byte[] ReadMessage_ay(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadArray(); + } + + protected static Dictionary ReadMessage_aesv(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadDictionary(); + } + + protected static (string, string, string) ReadMessage_sss(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadString(); + var arg1 = reader.ReadString(); + var arg2 = reader.ReadString(); + return (arg0, arg1, arg2); + } + + protected static string[] ReadMessage_v_as(Message message, DBusObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("as"); + return reader.ReadArray(); + } + } +} diff --git a/src/Avalonia.FreeDesktop/DBusFileChooser.cs b/src/Avalonia.FreeDesktop/DBusFileChooser.cs deleted file mode 100644 index 24db614a02..0000000000 --- a/src/Avalonia.FreeDesktop/DBusFileChooser.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Tmds.DBus; - -[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] -namespace Avalonia.FreeDesktop -{ - [DBusInterface("org.freedesktop.portal.FileChooser")] - internal interface IFileChooser : IDBusObject - { - Task OpenFileAsync(string ParentWindow, string Title, IDictionary Options); - Task SaveFileAsync(string ParentWindow, string Title, IDictionary Options); - Task SaveFilesAsync(string ParentWindow, string Title, IDictionary Options); - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); - } - - [Dictionary] - internal class FileChooserProperties - { - public uint Version { get; set; } - } - - internal static class FileChooserExtensions - { - public static Task GetVersionAsync(this IFileChooser o) => o.GetAsync("version"); - } -} diff --git a/src/Avalonia.FreeDesktop/DBusHelper.cs b/src/Avalonia.FreeDesktop/DBusHelper.cs index ef99838208..e0445cc95c 100644 --- a/src/Avalonia.FreeDesktop/DBusHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusHelper.cs @@ -1,51 +1,12 @@ using System; using System.Threading; using Avalonia.Logging; -using Avalonia.Threading; -using Tmds.DBus; +using Tmds.DBus.Protocol; namespace Avalonia.FreeDesktop { public static class DBusHelper { - /// - /// This class uses synchronous execution at DBus connection establishment stage - /// then switches to using AvaloniaSynchronizationContext - /// - private class DBusSyncContext : SynchronizationContext - { - private readonly object _lock = new(); - private SynchronizationContext? _ctx; - - public override void Post(SendOrPostCallback d, object? state) - { - lock (_lock) - { - if (_ctx is not null) - _ctx?.Post(d, state); - else - d(state); - } - } - - public override void Send(SendOrPostCallback d, object? state) - { - lock (_lock) - { - if (_ctx is not null) - _ctx?.Send(d, state); - else - d(state); - } - } - - public void Initialized() - { - lock (_lock) - _ctx = new AvaloniaSynchronizationContext(); - } - } - public static Connection? Connection { get; private set; } public static Connection? TryInitialize(string? dbusAddress = null) @@ -56,19 +17,14 @@ namespace Avalonia.FreeDesktop var oldContext = SynchronizationContext.Current; try { - - var dbusContext = new DBusSyncContext(); - SynchronizationContext.SetSynchronizationContext(dbusContext); - var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session) + var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session!) { - AutoConnect = false, - SynchronizationContext = dbusContext + AutoConnect = false }); + // Connect synchronously - conn.ConnectAsync().Wait(); + conn.ConnectAsync().GetAwaiter().GetResult(); - // Initialize a brand new sync-context - dbusContext.Initialized(); Connection = conn; } catch (Exception e) diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs index 899288b4a8..c7bf530cfd 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Logging; -using Tmds.DBus; +using Tmds.DBus.Protocol; namespace Avalonia.FreeDesktop.DBusIme { @@ -46,7 +46,7 @@ namespace Avalonia.FreeDesktop.DBusIme public DBusTextInputMethodBase(Connection connection, params string[] knownNames) { - _queue = new DBusCallQueue(QueueOnError); + _queue = new DBusCallQueue(QueueOnErrorAsync); Connection = connection; _knownNames = knownNames; Watch(); @@ -54,12 +54,17 @@ namespace Avalonia.FreeDesktop.DBusIme public ITextInputMethodClient Client => _client; - public bool IsActive => _client != null; + public bool IsActive => _client is not null; - async void Watch() + private async void Watch() { foreach (var name in _knownNames) - _disposables.Add(await Connection.ResolveServiceOwnerAsync(name, OnNameChange)); + { + var dbus = new DBusService(Connection, name).CreateDBus("/org/freedesktop/DBus"); + _disposables.Add(await dbus.WatchNameOwnerChangedAsync(OnNameChange)); + var nameOwner = await dbus.GetNameOwnerAsync(name); + OnNameChange(null, (name, null, nameOwner)); + } } protected abstract Task Connect(string name); @@ -67,9 +72,9 @@ namespace Avalonia.FreeDesktop.DBusIme protected string GetAppName() => Application.Current?.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia"; - private async void OnNameChange(ServiceOwnerChangedEventArgs args) + private async void OnNameChange(Exception? e, (string ServiceName, string? OldOwner, string? NewOwner) args) { - if (args.NewOwner != null && _currentName == null) + if (args.NewOwner is not null && _currentName is null) { _onlineNamesQueue.Enqueue(args.ServiceName); if (!_connecting) @@ -89,10 +94,10 @@ namespace Avalonia.FreeDesktop.DBusIme return; } } - catch (Exception e) + catch (Exception ex) { Logger.TryGet(LogEventLevel.Error, "IME") - ?.Log(this, "Unable to create IME input context:\n" + e); + ?.Log(this, "Unable to create IME input context:\n" + ex); } } } @@ -105,7 +110,7 @@ namespace Avalonia.FreeDesktop.DBusIme } // IME has crashed - if (args.NewOwner == null && args.ServiceName == _currentName) + if (args.NewOwner is null && args.ServiceName == _currentName) { _currentName = null; foreach (var s in _disposables) @@ -120,7 +125,7 @@ namespace Avalonia.FreeDesktop.DBusIme } } - protected virtual Task Disconnect() + protected virtual Task DisconnectAsync() { return Task.CompletedTask; } @@ -136,13 +141,13 @@ namespace Avalonia.FreeDesktop.DBusIme _imeActive = null; } - async Task QueueOnError(Exception e) + private async Task QueueOnErrorAsync(Exception e) { Logger.TryGet(LogEventLevel.Error, "IME") ?.Log(this, "Error:\n" + e); try { - await Disconnect(); + await DisconnectAsync(); } catch (Exception ex) { @@ -160,20 +165,13 @@ namespace Avalonia.FreeDesktop.DBusIme if(d is { }) _disposables.Add(d); } - + public void Dispose() { foreach(var d in _disposables) d.Dispose(); _disposables.Clear(); - try - { - Disconnect().ContinueWith(_ => { }); - } - catch - { - // fire and forget - } + DisconnectAsync(); _currentName = null; } @@ -182,13 +180,13 @@ namespace Avalonia.FreeDesktop.DBusIme protected abstract Task ResetContextCore(); protected abstract Task HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode); - void UpdateActive() + private void UpdateActive() { _queue.Enqueue(async () => { if(!IsConnected) return; - + var active = _windowActive && _controlActive; if (active != _imeActive) { @@ -204,7 +202,7 @@ namespace Avalonia.FreeDesktop.DBusIme _windowActive = active; UpdateActive(); } - + void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client) { _client = client; @@ -227,7 +225,7 @@ namespace Avalonia.FreeDesktop.DBusIme // Error, disconnect catch (Exception e) { - await QueueOnError(e); + await QueueOnErrorAsync(e); return false; } } @@ -240,7 +238,7 @@ namespace Avalonia.FreeDesktop.DBusIme } protected void FireCommit(string s) => _onCommit?.Invoke(s); - + private Action? _onForward; event Action IX11InputMethodControl.ForwardKey { @@ -249,8 +247,8 @@ namespace Avalonia.FreeDesktop.DBusIme } protected void FireForward(X11InputMethodForwardedKey k) => _onForward?.Invoke(k); - - void UpdateCursorRect() + + private void UpdateCursorRect() { _queue.Enqueue(async () => { @@ -265,7 +263,7 @@ namespace Avalonia.FreeDesktop.DBusIme } }); } - + void IX11InputMethodControl.UpdateWindowInfo(PixelPoint position, double scaling) { _windowPosition = position; diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs new file mode 100644 index 0000000000..f9e4497892 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs @@ -0,0 +1,627 @@ +using System; +using System.Threading.Tasks; +using Tmds.DBus.Protocol; + +namespace Avalonia.FreeDesktop.DBusIme.Fcitx +{ + internal class InputContext : FcitxObject + { + private const string Interface = "org.fcitx.Fcitx.InputContext"; + + public InputContext(FcitxService service, ObjectPath path) : base(service, path) { } + + public Task FocusInAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "FocusIn"); + return writer.CreateMessage(); + } + } + + public Task FocusOutAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "FocusOut"); + return writer.CreateMessage(); + } + } + + public Task ResetAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "Reset"); + return writer.CreateMessage(); + } + } + + public Task SetCursorRectAsync(int x, int y, int w, int h) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "iiii", + member: "SetCursorRect"); + writer.WriteInt32(x); + writer.WriteInt32(y); + writer.WriteInt32(w); + writer.WriteInt32(h); + return writer.CreateMessage(); + } + } + + public Task SetCapacityAsync(uint caps) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "u", + member: "SetCapacity"); + writer.WriteUInt32(caps); + return writer.CreateMessage(); + } + } + + public Task SetSurroundingTextAsync(string text, uint cursor, uint anchor) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "suu", + member: "SetSurroundingText"); + writer.WriteString(text); + writer.WriteUInt32(cursor); + writer.WriteUInt32(anchor); + return writer.CreateMessage(); + } + } + + public Task SetSurroundingTextPositionAsync(uint cursor, uint anchor) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "uu", + member: "SetSurroundingTextPosition"); + writer.WriteUInt32(cursor); + writer.WriteUInt32(anchor); + return writer.CreateMessage(); + } + } + + public Task DestroyICAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "DestroyIC"); + return writer.CreateMessage(); + } + } + + public Task ProcessKeyEventAsync(uint keyval, uint keycode, uint state, int type, uint time) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_i(m, (FcitxObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "uuuiu", + member: "ProcessKeyEvent"); + writer.WriteUInt32(keyval); + writer.WriteUInt32(keycode); + writer.WriteUInt32(state); + writer.WriteInt32(type); + writer.WriteUInt32(time); + return writer.CreateMessage(); + } + } + + public ValueTask WatchCommitStringAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "CommitString", static (m, s) => + ReadMessage_s(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchCurrentIMAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "CurrentIM", static (m, s) => + ReadMessage_sss(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchUpdateFormattedPreeditAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "UpdateFormattedPreedit", static (m, s) => + ReadMessage_arsizi(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchForwardKeyAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKey", static (m, s) => + ReadMessage_uui(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchDeleteSurroundingTextAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "DeleteSurroundingText", static (m, s) => + ReadMessage_iu(m, (FcitxObject)s!), handler, emitOnCapturedContext); + } + + internal class InputMethod : FcitxObject + { + private const string Interface = "org.fcitx.Fcitx.InputMethod"; + + public InputMethod(FcitxService service, ObjectPath path) : base(service, path) { } + + public Task<(int Icid, bool Enable, uint Keyval1, uint State1, uint Keyval2, uint State2)> CreateICv3Async(string appname, int pid) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_ibuuuu(m, (FcitxObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "si", + member: "CreateICv3"); + writer.WriteString(appname); + writer.WriteInt32(pid); + return writer.CreateMessage(); + } + } + } + + internal class InputContext1 : FcitxObject + { + private const string Interface = "org.fcitx.Fcitx.InputContext1"; + + public InputContext1(FcitxService service, ObjectPath path) : base(service, path) { } + + public Task FocusInAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "FocusIn"); + return writer.CreateMessage(); + } + } + + public Task FocusOutAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "FocusOut"); + return writer.CreateMessage(); + } + } + + public Task ResetAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "Reset"); + return writer.CreateMessage(); + } + } + + public Task SetCursorRectAsync(int x, int y, int w, int h) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "iiii", + member: "SetCursorRect"); + writer.WriteInt32(x); + writer.WriteInt32(y); + writer.WriteInt32(w); + writer.WriteInt32(h); + return writer.CreateMessage(); + } + } + + public Task SetCapabilityAsync(ulong caps) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "t", + member: "SetCapability"); + writer.WriteUInt64(caps); + return writer.CreateMessage(); + } + } + + public Task SetSurroundingTextAsync(string text, uint cursor, uint anchor) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "suu", + member: "SetSurroundingText"); + writer.WriteString(text); + writer.WriteUInt32(cursor); + writer.WriteUInt32(anchor); + return writer.CreateMessage(); + } + } + + public Task SetSurroundingTextPositionAsync(uint cursor, uint anchor) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "uu", + member: "SetSurroundingTextPosition"); + writer.WriteUInt32(cursor); + writer.WriteUInt32(anchor); + return writer.CreateMessage(); + } + } + + public Task DestroyICAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "DestroyIC"); + return writer.CreateMessage(); + } + } + + public Task ProcessKeyEventAsync(uint keyval, uint keycode, uint state, bool type, uint time) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_b(m, (FcitxObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "uuubu", + member: "ProcessKeyEvent"); + writer.WriteUInt32(keyval); + writer.WriteUInt32(keycode); + writer.WriteUInt32(state); + writer.WriteBool(type); + writer.WriteUInt32(time); + return writer.CreateMessage(); + } + } + + public ValueTask WatchCommitStringAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "CommitString", static (m, s) => + ReadMessage_s(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchCurrentIMAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "CurrentIM", static (m, s) => + ReadMessage_sss(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchUpdateFormattedPreeditAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "UpdateFormattedPreedit", static (m, s) => + ReadMessage_arsizi(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchForwardKeyAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKey", static (m, s) => + ReadMessage_uub(m, (FcitxObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchDeleteSurroundingTextAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "DeleteSurroundingText", static (m, s) => + ReadMessage_iu(m, (FcitxObject)s!), handler, emitOnCapturedContext); + } + + internal class InputMethod1 : FcitxObject + { + private const string Interface = "org.fcitx.Fcitx.InputMethod1"; + + public InputMethod1(FcitxService service, ObjectPath path) : base(service, path) { } + + public Task<(ObjectPath A0, byte[] A1)> CreateInputContextAsync((string, string)[] a0) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_oay(m, (FcitxObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "a(ss)", + member: "CreateInputContext"); + writer.WriteArray(a0); + return writer.CreateMessage(); + } + } + } + + internal class FcitxService + { + public FcitxService(Connection connection, string destination) + => (Connection, Destination) = (connection, destination); + + public Connection Connection { get; } + public string Destination { get; } + public InputContext CreateInputContext(string path) => new(this, path); + public InputMethod CreateInputMethod(string path) => new(this, path); + public InputContext1 CreateInputContext1(string path) => new(this, path); + public InputMethod1 CreateInputMethod1(string path) => new(this, path); + } + + internal class FcitxObject + { + protected FcitxObject(FcitxService service, ObjectPath path) + => (Service, Path) = (service, path); + + public FcitxService Service { get; } + public ObjectPath Path { get; } + protected Connection Connection => Service.Connection; + + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + + protected ValueTask WatchPropertiesChangedAsync(string @interface, + MessageValueReader> reader, Action> handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => + ((Action>)hs!).Invoke(ex, changes), + this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, + MessageValueReader reader, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => + ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, static (_, _) => + null!, static (ex, _, _, hs) => + ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + } + + protected static int ReadMessage_i(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadInt32(); + } + + protected static string ReadMessage_s(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadString(); + } + + protected static (string, string, string) ReadMessage_sss(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadString(); + var arg1 = reader.ReadString(); + var arg2 = reader.ReadString(); + return (arg0, arg1, arg2); + } + + protected static ((string, int)[], int) ReadMessage_arsizi(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadArray<(string, int)>(); + var arg1 = reader.ReadInt32(); + return (arg0, arg1); + } + + protected static (uint, uint, int) ReadMessage_uui(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadUInt32(); + var arg1 = reader.ReadUInt32(); + var arg2 = reader.ReadInt32(); + return (arg0, arg1, arg2); + } + + protected static (int, uint) ReadMessage_iu(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadInt32(); + var arg1 = reader.ReadUInt32(); + return (arg0, arg1); + } + + protected static (int, bool, uint, uint, uint, uint) ReadMessage_ibuuuu(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadInt32(); + var arg1 = reader.ReadBool(); + var arg2 = reader.ReadUInt32(); + var arg3 = reader.ReadUInt32(); + var arg4 = reader.ReadUInt32(); + var arg5 = reader.ReadUInt32(); + return (arg0, arg1, arg2, arg3, arg4, arg5); + } + + protected static bool ReadMessage_b(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadBool(); + } + + protected static (uint, uint, bool) ReadMessage_uub(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadUInt32(); + var arg1 = reader.ReadUInt32(); + var arg2 = reader.ReadBool(); + return (arg0, arg1, arg2); + } + + protected static (ObjectPath, byte[]) ReadMessage_oay(Message message, FcitxObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadObjectPath(); + var arg1 = reader.ReadArray(); + return (arg0, arg1); + } + } +} diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs deleted file mode 100644 index 06afacaa29..0000000000 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Tmds.DBus; - -[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] -namespace Avalonia.FreeDesktop.DBusIme.Fcitx -{ - [DBusInterface("org.fcitx.Fcitx.InputMethod")] - interface IFcitxInputMethod : IDBusObject - { - Task<(int icid, bool enable, uint keyval1, uint state1, uint keyval2, uint state2)> CreateICv3Async( - string Appname, int Pid); - } - - - [DBusInterface("org.fcitx.Fcitx.InputContext")] - interface IFcitxInputContext : IDBusObject - { - Task EnableICAsync(); - Task CloseICAsync(); - Task FocusInAsync(); - Task FocusOutAsync(); - Task ResetAsync(); - Task MouseEventAsync(int X); - Task SetCursorLocationAsync(int X, int Y); - Task SetCursorRectAsync(int X, int Y, int W, int H); - Task SetCapacityAsync(uint Caps); - Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor); - Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor); - Task DestroyICAsync(); - Task ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time); - Task WatchEnableIMAsync(Action handler, Action? onError = null); - Task WatchCloseIMAsync(Action handler, Action? onError = null); - Task WatchCommitStringAsync(Action handler, Action? onError = null); - Task WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action? onError = null); - Task WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action? onError = null); - Task WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action? onError = null); - Task WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action? onError = null); - Task WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action? onError = null); - Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action? onError = null); - } - - [DBusInterface("org.fcitx.Fcitx.InputContext1")] - interface IFcitxInputContext1 : IDBusObject - { - Task FocusInAsync(); - Task FocusOutAsync(); - Task ResetAsync(); - Task SetCursorRectAsync(int X, int Y, int W, int H); - Task SetCapabilityAsync(ulong Caps); - Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor); - Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor); - Task DestroyICAsync(); - Task ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time); - Task WatchCommitStringAsync(Action handler, Action? onError = null); - Task WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action? onError = null); - Task WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action? onError = null); - Task WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action? onError = null); - Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action? onError = null); - } - - [DBusInterface("org.fcitx.Fcitx.InputMethod1")] - interface IFcitxInputMethod1 : IDBusObject - { - Task<(ObjectPath path, byte[] data)> CreateInputContextAsync((string, string)[] arg0); - } -} diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs index 6c503edb41..b71ca55cf8 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs @@ -5,15 +5,15 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx { internal class FcitxICWrapper { - private readonly IFcitxInputContext1? _modern; - private readonly IFcitxInputContext? _old; + private readonly InputContext1? _modern; + private readonly InputContext? _old; - public FcitxICWrapper(IFcitxInputContext old) + public FcitxICWrapper(InputContext old) { _old = old; } - public FcitxICWrapper(IFcitxInputContext1 modern) + public FcitxICWrapper(InputContext1 modern) { _modern = modern; } @@ -21,32 +21,30 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx public Task FocusInAsync() => _old?.FocusInAsync() ?? _modern?.FocusInAsync() ?? Task.CompletedTask; public Task FocusOutAsync() => _old?.FocusOutAsync() ?? _modern?.FocusOutAsync() ?? Task.CompletedTask; - + public Task ResetAsync() => _old?.ResetAsync() ?? _modern?.ResetAsync() ?? Task.CompletedTask; public Task SetCursorRectAsync(int x, int y, int w, int h) => _old?.SetCursorRectAsync(x, y, w, h) ?? _modern?.SetCursorRectAsync(x, y, w, h) ?? Task.CompletedTask; + public Task DestroyICAsync() => _old?.DestroyICAsync() ?? _modern?.DestroyICAsync() ?? Task.CompletedTask; public async Task ProcessKeyEventAsync(uint keyVal, uint keyCode, uint state, int type, uint time) { - if(_old!=null) + if (_old is not null) return await _old.ProcessKeyEventAsync(keyVal, keyCode, state, type, time) != 0; return await (_modern?.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time) ?? Task.FromResult(false)); } - public Task WatchCommitStringAsync(Action handler) => - _old?.WatchCommitStringAsync(handler) - ?? _modern?.WatchCommitStringAsync(handler) - ?? Task.FromResult(default(IDisposable?)); + public ValueTask WatchCommitStringAsync(Action handler) => + _old?.WatchCommitStringAsync(handler) + ?? _modern?.WatchCommitStringAsync(handler) + ?? new ValueTask(default(IDisposable?)); - public Task WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler) - { - return _old?.WatchForwardKeyAsync(handler) - ?? _modern?.WatchForwardKeyAsync(ev => - handler((ev.keyval, ev.state, ev.type ? 1 : 0))) - ?? Task.FromResult(default(IDisposable?)); - } + public ValueTask WatchForwardKeyAsync(Action handler) => + _old?.WatchForwardKeyAsync(handler) + ?? _modern?.WatchForwardKeyAsync((e, ev) => handler.Invoke(e, (ev.Keyval, ev.State, ev.Type ? 1 : 0))) + ?? new ValueTask(default(IDisposable?)); public Task SetCapacityAsync(uint flags) => _old?.SetCapacityAsync(flags) ?? _modern?.SetCapabilityAsync(flags) ?? Task.CompletedTask; diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 791431dfa7..2a5260b1cf 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -1,12 +1,10 @@ using System; using System.Diagnostics; -using System.Reactive.Concurrency; -using System.Reflection; using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; -using Tmds.DBus; +using Tmds.DBus.Protocol; namespace Avalonia.FreeDesktop.DBusIme.Fcitx { @@ -15,32 +13,25 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx private FcitxICWrapper? _context; private FcitxCapabilityFlags? _lastReportedFlags; - public FcitxX11TextInputMethod(Connection connection) : base(connection, - "org.fcitx.Fcitx", - "org.freedesktop.portal.Fcitx" - ) - { - - } + public FcitxX11TextInputMethod(Connection connection) : base(connection, "org.fcitx.Fcitx", "org.freedesktop.portal.Fcitx") { } protected override async Task Connect(string name) { + var service = new FcitxService(Connection, name); if (name == "org.fcitx.Fcitx") { - var method = Connection.CreateProxy(name, "/inputmethod"); + var method = service.CreateInputMethod("/inputmethod"); var resp = await method.CreateICv3Async(GetAppName(), Process.GetCurrentProcess().Id); - var proxy = Connection.CreateProxy(name, - "/inputcontext_" + resp.icid); - + var proxy = service.CreateInputContext($"/inputcontext_{resp.Icid}"); _context = new FcitxICWrapper(proxy); } else { - var method = Connection.CreateProxy(name, "/inputmethod"); + var method = service.CreateInputMethod1("/inputmethod"); var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) }); - var proxy = Connection.CreateProxy(name, resp.path); + var proxy = service.CreateInputContext1(resp.A0); _context = new FcitxICWrapper(proxy); } @@ -49,7 +40,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx return true; } - protected override Task Disconnect() => _context?.DestroyICAsync() ?? Task.CompletedTask; + protected override Task DisconnectAsync() => _context?.DestroyICAsync() ?? Task.CompletedTask; protected override void OnDisconnected() => _context = null; @@ -64,14 +55,12 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx Math.Max(1, cursorRect.Height)) ?? Task.CompletedTask; - protected override Task SetActiveCore(bool active)=> (active + protected override Task SetActiveCore(bool active)=> (active ? _context?.FocusInAsync() : _context?.FocusOutAsync()) ?? Task.CompletedTask; - - protected override Task ResetContextCore() => _context?.ResetAsync() - ?? Task.CompletedTask; + protected override Task ResetContextCore() => _context?.ResetAsync() ?? Task.CompletedTask; protected override async Task HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode) { @@ -88,17 +77,13 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx var type = args.Type == RawKeyEventType.KeyDown ? FcitxKeyEventType.FCITX_PRESS_KEY : FcitxKeyEventType.FCITX_RELEASE_KEY; - if (_context is { }) - { + if (_context is not null) return await _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state, (int)type, (uint)args.Timestamp).ConfigureAwait(false); - } - else - { - return false; - } + + return false; } - + public override void SetOptions(TextInputOptions options) => Enqueue(async () => { @@ -128,7 +113,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx } }); - private void OnForward((uint keyval, uint state, int type) ev) + private void OnForward(Exception? e, (uint keyval, uint state, int type) ev) { var state = (FcitxKeyState)ev.state; KeyModifiers mods = default; @@ -150,6 +135,6 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx }); } - private void OnCommitString(string s) => FireCommit(s); + private void OnCommitString(Exception? e, string s) => FireCommit(s); } } diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs new file mode 100644 index 0000000000..e0f9681766 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs @@ -0,0 +1,513 @@ +using System; +using System.Threading.Tasks; +using Tmds.DBus.Protocol; + +namespace Avalonia.FreeDesktop.DBusIme.IBus +{ + internal class Portal : IBusObject + { + private const string Interface = "org.freedesktop.IBus.Portal"; + + public Portal(IBusService service, ObjectPath path) : base(service, path) { } + + public Task CreateInputContextAsync(string clientName) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (IBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "CreateInputContext"); + writer.WriteString(clientName); + return writer.CreateMessage(); + } + } + } + + internal class InputContext : IBusObject + { + private const string Interface = "org.freedesktop.IBus.InputContext"; + + public InputContext(IBusService service, ObjectPath path) : base(service, path) { } + + public Task ProcessKeyEventAsync(uint keyval, uint keycode, uint state) + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_b(m, (IBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "uuu", + member: "ProcessKeyEvent"); + writer.WriteUInt32(keyval); + writer.WriteUInt32(keycode); + writer.WriteUInt32(state); + return writer.CreateMessage(); + } + } + + public Task SetCursorLocationAsync(int x, int y, int w, int h) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "iiii", + member: "SetCursorLocation"); + writer.WriteInt32(x); + writer.WriteInt32(y); + writer.WriteInt32(w); + writer.WriteInt32(h); + return writer.CreateMessage(); + } + } + + public Task SetCursorLocationRelativeAsync(int x, int y, int w, int h) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "iiii", + member: "SetCursorLocationRelative"); + writer.WriteInt32(x); + writer.WriteInt32(y); + writer.WriteInt32(w); + writer.WriteInt32(h); + return writer.CreateMessage(); + } + } + + public Task ProcessHandWritingEventAsync(double[] coordinates) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "ad", + member: "ProcessHandWritingEvent"); + writer.WriteArray(coordinates); + return writer.CreateMessage(); + } + } + + public Task CancelHandWritingAsync(uint nStrokes) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "u", + member: "CancelHandWriting"); + writer.WriteUInt32(nStrokes); + return writer.CreateMessage(); + } + } + + public Task FocusInAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "FocusIn"); + return writer.CreateMessage(); + } + } + + public Task FocusOutAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "FocusOut"); + return writer.CreateMessage(); + } + } + + public Task ResetAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "Reset"); + return writer.CreateMessage(); + } + } + + public Task SetCapabilitiesAsync(uint caps) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "u", + member: "SetCapabilities"); + writer.WriteUInt32(caps); + return writer.CreateMessage(); + } + } + + public Task PropertyActivateAsync(string name, uint state) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "su", + member: "PropertyActivate"); + writer.WriteString(name); + writer.WriteUInt32(state); + return writer.CreateMessage(); + } + } + + public Task SetEngineAsync(string name) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "SetEngine"); + writer.WriteString(name); + return writer.CreateMessage(); + } + } + + public Task GetEngineAsync() + { + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_v(m, (IBusObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "GetEngine"); + return writer.CreateMessage(); + } + } + + public Task SetSurroundingTextAsync(object text, uint cursorPos, uint anchorPos) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "vuu", + member: "SetSurroundingText"); + writer.WriteVariant(text); + writer.WriteUInt32(cursorPos); + writer.WriteUInt32(anchorPos); + return writer.CreateMessage(); + } + } + + public ValueTask WatchCommitTextAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "CommitText", static (m, s) => + ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchForwardKeyEventAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKeyEvent", static (m, s) => + ReadMessage_uuu(m, (IBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchUpdatePreeditTextAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "UpdatePreeditText", static (m, s) => + ReadMessage_vub(m, (IBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchUpdatePreeditTextWithModeAsync( + Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "UpdatePreeditTextWithMode", static (m, s) => + ReadMessage_vubu(m, (IBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchShowPreeditTextAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "ShowPreeditText", handler, emitOnCapturedContext); + + public ValueTask WatchHidePreeditTextAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "HidePreeditText", handler, emitOnCapturedContext); + + public ValueTask WatchUpdateAuxiliaryTextAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "UpdateAuxiliaryText", static (m, s) => + ReadMessage_vb(m, (IBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchShowAuxiliaryTextAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "ShowAuxiliaryText", handler, emitOnCapturedContext); + + public ValueTask WatchHideAuxiliaryTextAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "HideAuxiliaryText", handler, emitOnCapturedContext); + + public ValueTask WatchUpdateLookupTableAsync(Action handler, + bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "UpdateLookupTable", static (m, s) => + ReadMessage_vb(m, (IBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchShowLookupTableAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "ShowLookupTable", handler, emitOnCapturedContext); + + public ValueTask WatchHideLookupTableAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "HideLookupTable", handler, emitOnCapturedContext); + + public ValueTask WatchPageUpLookupTableAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "PageUpLookupTable", handler, emitOnCapturedContext); + + public ValueTask WatchPageDownLookupTableAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "PageDownLookupTable", handler, emitOnCapturedContext); + + public ValueTask WatchCursorUpLookupTableAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "CursorUpLookupTable", handler, emitOnCapturedContext); + + public ValueTask WatchCursorDownLookupTableAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "CursorDownLookupTable", handler, emitOnCapturedContext); + + public ValueTask WatchRegisterPropertiesAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "RegisterProperties", static (m, s) => ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchUpdatePropertyAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "UpdateProperty", static (m, s) => ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext); + } + + internal class Service : IBusObject + { + private const string Interface = "org.freedesktop.IBus.Service"; + + public Service(IBusService service, ObjectPath path) : base(service, path) { } + + public Task DestroyAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "Destroy"); + return writer.CreateMessage(); + } + } + } + + internal class IBusService + { + public IBusService(Connection connection, string destination) + => (Connection, Destination) = (connection, destination); + + public Connection Connection { get; } + public string Destination { get; } + public Portal CreatePortal(string path) => new(this, path); + public InputContext CreateInputContext(string path) => new(this, path); + public Service CreateService(string path) => new(this, path); + } + + internal class IBusObject + { + protected IBusObject(IBusService service, ObjectPath path) + => (Service, Path) = (service, path); + + public IBusService Service { get; } + public ObjectPath Path { get; } + protected Connection Connection => Service.Connection; + + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + + protected ValueTask WatchPropertiesChangedAsync(string @interface, + MessageValueReader> reader, Action> handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => + ((Action>)hs!).Invoke(ex, changes), + this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, + MessageValueReader reader, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => ((Action)hs!).Invoke(ex, arg), + this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, static (_, _) => null!, static (ex, _, _, hs) => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + } + + protected static ObjectPath ReadMessage_o(Message message, IBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadObjectPath(); + } + + protected static bool ReadMessage_b(Message message, IBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadBool(); + } + + protected static object ReadMessage_v(Message message, IBusObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadVariant(); + } + + protected static (uint, uint, uint) ReadMessage_uuu(Message message, IBusObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadUInt32(); + var arg1 = reader.ReadUInt32(); + var arg2 = reader.ReadUInt32(); + return (arg0, arg1, arg2); + } + + protected static (object, uint, bool) ReadMessage_vub(Message message, IBusObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadVariant(); + var arg1 = reader.ReadUInt32(); + var arg2 = reader.ReadBool(); + return (arg0, arg1, arg2); + } + + protected static (object, uint, bool, uint) ReadMessage_vubu(Message message, IBusObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadVariant(); + var arg1 = reader.ReadUInt32(); + var arg2 = reader.ReadBool(); + var arg3 = reader.ReadUInt32(); + return (arg0, arg1, arg2, arg3); + } + + protected static (object, bool) ReadMessage_vb(Message message, IBusObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadVariant(); + var arg1 = reader.ReadBool(); + return (arg0, arg1); + } + } +} diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs deleted file mode 100644 index 4ef034adb9..0000000000 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Tmds.DBus; - -[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] -namespace Avalonia.FreeDesktop.DBusIme.IBus -{ - [DBusInterface("org.freedesktop.IBus.InputContext")] - interface IIBusInputContext : IDBusObject - { - Task ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State); - Task SetCursorLocationAsync(int X, int Y, int W, int H); - Task FocusInAsync(); - Task FocusOutAsync(); - Task ResetAsync(); - Task SetCapabilitiesAsync(uint Caps); - Task PropertyActivateAsync(string Name, int State); - Task SetEngineAsync(string Name); - Task GetEngineAsync(); - Task DestroyAsync(); - Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos); - Task WatchCommitTextAsync(Action cb, Action? onError = null); - Task WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action? onError = null); - Task WatchRequireSurroundingTextAsync(Action handler, Action? onError = null); - Task WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action? onError = null); - Task WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action? onError = null); - Task WatchShowPreeditTextAsync(Action handler, Action? onError = null); - Task WatchHidePreeditTextAsync(Action handler, Action? onError = null); - Task WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action? onError = null); - Task WatchShowAuxiliaryTextAsync(Action handler, Action? onError = null); - Task WatchHideAuxiliaryTextAsync(Action handler, Action? onError = null); - Task WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action? onError = null); - Task WatchShowLookupTableAsync(Action handler, Action? onError = null); - Task WatchHideLookupTableAsync(Action handler, Action? onError = null); - Task WatchPageUpLookupTableAsync(Action handler, Action? onError = null); - Task WatchPageDownLookupTableAsync(Action handler, Action? onError = null); - Task WatchCursorUpLookupTableAsync(Action handler, Action? onError = null); - Task WatchCursorDownLookupTableAsync(Action handler, Action? onError = null); - Task WatchRegisterPropertiesAsync(Action handler, Action? onError = null); - Task WatchUpdatePropertyAsync(Action handler, Action? onError = null); - } - - - [DBusInterface("org.freedesktop.IBus.Portal")] - interface IIBusPortal : IDBusObject - { - Task CreateInputContextAsync(string Name); - } -} diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs index 3070f51a8e..b8ac816694 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs @@ -18,7 +18,7 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus Button3Mask = 1 << 10, Button4Mask = 1 << 11, Button5Mask = 1 << 12, - + HandledMask = 1 << 24, ForwardMask = 1 << 25, IgnoredMask = ForwardMask, diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 2324ca44a7..f3ab8617a4 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -1,35 +1,32 @@ -using System.Collections.Generic; +using System; using System.Threading.Tasks; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; -using Tmds.DBus; +using Tmds.DBus.Protocol; namespace Avalonia.FreeDesktop.DBusIme.IBus { internal class IBusX11TextInputMethod : DBusTextInputMethodBase { - private IIBusInputContext? _context; + private Service? _service; + private InputContext? _context; - public IBusX11TextInputMethod(Connection connection) : base(connection, - "org.freedesktop.portal.IBus") - { - } + public IBusX11TextInputMethod(Connection connection) : base(connection, "org.freedesktop.portal.IBus") { } protected override async Task Connect(string name) { - var path = - await Connection.CreateProxy(name, "/org/freedesktop/IBus") - .CreateInputContextAsync(GetAppName()); - - _context = Connection.CreateProxy(name, path); + var service = new IBusService(Connection, name); + var path = await service.CreatePortal("/org/freedesktop/IBus").CreateInputContextAsync(GetAppName()); + _context = service.CreateInputContext(path); + _service = service.CreateService(path); AddDisposable(await _context.WatchCommitTextAsync(OnCommitText)); AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey)); Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus)); return true; } - private void OnForwardKey((uint keyval, uint keycode, uint state) k) + private void OnForwardKey(Exception? e, (uint keyval, uint keycode, uint state) k) { var state = (IBusModifierMask)k.state; KeyModifiers mods = default; @@ -49,8 +46,7 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus }); } - - private void OnCommitText(object wtf) + private void OnCommitText(Exception? e, object wtf) { // Hello darkness, my old friend if (wtf.GetType().GetField("Item3") is { } prop) @@ -61,16 +57,16 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus } } - protected override Task Disconnect() => _context?.DestroyAsync() - ?? Task.CompletedTask; + protected override Task DisconnectAsync() => _service?.DestroyAsync() ?? Task.CompletedTask; protected override void OnDisconnected() { + _service = null; _context = null; base.OnDisconnected(); } - protected override Task SetCursorRectCore(PixelRect rect) + protected override Task SetCursorRectCore(PixelRect rect) => _context?.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height) ?? Task.CompletedTask; @@ -96,20 +92,12 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus if (args.Type == RawKeyEventType.KeyUp) state |= IBusModifierMask.ReleaseMask; - if(_context is { }) - { - return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state); - } - else - { - return Task.FromResult(false); - } - + return _context is not null ? _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state) : Task.FromResult(false); } public override void SetOptions(TextInputOptions options) { - // No-op, because ibus + // No-op, because ibus } } } diff --git a/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs b/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs index 86978c8b60..c68e10043b 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs @@ -2,44 +2,43 @@ using System; using System.Collections.Generic; using Avalonia.FreeDesktop.DBusIme.Fcitx; using Avalonia.FreeDesktop.DBusIme.IBus; -using Tmds.DBus; +using Tmds.DBus.Protocol; namespace Avalonia.FreeDesktop.DBusIme { public class X11DBusImeHelper { - private static readonly Dictionary> KnownMethods = - new Dictionary> + private static readonly Dictionary> KnownMethods = new() { - ["fcitx"] = conn => + ["fcitx"] = static conn => new DBusInputMethodFactory(_ => new FcitxX11TextInputMethod(conn)), - ["ibus"] = conn => + ["ibus"] = static conn => new DBusInputMethodFactory(_ => new IBusX11TextInputMethod(conn)) }; - - static Func? DetectInputMethod() + + private static Func? DetectInputMethod() { foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" }) { var value = Environment.GetEnvironmentVariable(name); - + if (value == "none") return null; - - if (value != null && KnownMethods.TryGetValue(value, out var factory)) + + if (value is not null && KnownMethods.TryGetValue(value, out var factory)) return factory; } return null; } - + public static bool DetectAndRegister() { var factory = DetectInputMethod(); - if (factory != null) + if (factory is not null) { var conn = DBusHelper.TryInitialize(); - if (conn != null) + if (conn is not null) { AvaloniaLocator.CurrentMutable.Bind().ToConstant(factory(conn)); return true; diff --git a/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs b/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs new file mode 100644 index 0000000000..d9016d8644 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs @@ -0,0 +1,463 @@ +using System; +using Tmds.DBus.Protocol; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Avalonia.FreeDesktop +{ + internal record DBusMenuProperties + { + public uint Version { get; set; } + public string TextDirection { get; set; } = default!; + public string Status { get; set; } = default!; + public string[] IconThemePath { get; set; } = default!; + } + + internal class DBusMenu : DBusMenuObject + { + private const string Interface = "com.canonical.dbusmenu"; + public DBusMenu(DBusMenuService service, ObjectPath path) : base(service, path) + { } + public Task<(uint Revision, (int, Dictionary, object[]) Layout)> GetLayoutAsync(int parentId, int recursionDepth, string[] propertyNames) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_uriaesvavz(m, (DBusMenuObject)s!), this); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "iias", + member: "GetLayout"); + writer.WriteInt32(parentId); + writer.WriteInt32(recursionDepth); + writer.WriteArray(propertyNames); + return writer.CreateMessage(); + } + } + public Task<(int, Dictionary)[]> GetGroupPropertiesAsync(int[] ids, string[] propertyNames) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_ariaesvz(m, (DBusMenuObject)s!), this); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "aias", + member: "GetGroupProperties"); + writer.WriteArray(ids); + writer.WriteArray(propertyNames); + return writer.CreateMessage(); + } + } + public Task GetPropertyAsync(int id, string name) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_v(m, (DBusMenuObject)s!), this); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "is", + member: "GetProperty"); + writer.WriteInt32(id); + writer.WriteString(name); + return writer.CreateMessage(); + } + } + public Task EventAsync(int id, string eventId, object data, uint timestamp) + { + return Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "isvu", + member: "Event"); + writer.WriteInt32(id); + writer.WriteString(eventId); + writer.WriteVariant(data); + writer.WriteUInt32(timestamp); + return writer.CreateMessage(); + } + } + public Task EventGroupAsync((int, string, object, uint)[] events) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_ai(m, (DBusMenuObject)s!), this); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "a(isvu)", + member: "EventGroup"); + writer.WriteArray(events); + return writer.CreateMessage(); + } + } + public Task AboutToShowAsync(int id) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_b(m, (DBusMenuObject)s!), this); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "i", + member: "AboutToShow"); + writer.WriteInt32(id); + return writer.CreateMessage(); + } + } + public Task<(int[] UpdatesNeeded, int[] IdErrors)> AboutToShowGroupAsync(int[] ids) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_aiai(m, (DBusMenuObject)s!), this); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "ai", + member: "AboutToShowGroup"); + writer.WriteArray(ids); + return writer.CreateMessage(); + } + } + public ValueTask WatchItemsPropertiesUpdatedAsync(Action)[] UpdatedProps, (int, string[])[] RemovedProps)> handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "ItemsPropertiesUpdated", (m, s) => ReadMessage_ariaesvzariasz(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); + public ValueTask WatchLayoutUpdatedAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "LayoutUpdated", (m, s) => ReadMessage_ui(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); + public ValueTask WatchItemActivationRequestedAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "ItemActivationRequested", (m, s) => ReadMessage_iu(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); + public Task SetVersionAsync(uint value) + { + return Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("Version"); + writer.WriteSignature("u"); + writer.WriteUInt32(value); + return writer.CreateMessage(); + } + } + public Task SetTextDirectionAsync(string value) + { + return Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("TextDirection"); + writer.WriteSignature("s"); + writer.WriteString(value); + return writer.CreateMessage(); + } + } + public Task SetStatusAsync(string value) + { + return Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("Status"); + writer.WriteSignature("s"); + writer.WriteString(value); + return writer.CreateMessage(); + } + } + public Task SetIconThemePathAsync(string[] value) + { + return Connection.CallMethodAsync(CreateMessage()); + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("IconThemePath"); + writer.WriteSignature("as"); + writer.WriteArray(value); + return writer.CreateMessage(); + } + } + public Task GetVersionAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Version"), (m, s) => ReadMessage_v_u(m, (DBusMenuObject)s!), this); + public Task GetTextDirectionAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "TextDirection"), (m, s) => ReadMessage_v_s(m, (DBusMenuObject)s!), this); + public Task GetStatusAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Status"), (m, s) => ReadMessage_v_s(m, (DBusMenuObject)s!), this); + public Task GetIconThemePathAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IconThemePath"), (m, s) => ReadMessage_v_as(m, (DBusMenuObject)s!), this); + public Task GetPropertiesAsync() + { + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), (m, s) => ReadMessage(m, (DBusMenuObject)s!), this); + static DBusMenuProperties ReadMessage(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) + { + return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); + static PropertyChanges ReadMessage(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(), invalidated = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); + } + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(headersEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "Version": invalidated.Add("Version"); break; + case "TextDirection": invalidated.Add("TextDirection"); break; + case "Status": invalidated.Add("Status"); break; + case "IconThemePath": invalidated.Add("IconThemePath"); break; + } + } + return invalidated?.ToArray() ?? Array.Empty(); + } + } + private static DBusMenuProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new DBusMenuProperties(); + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(headersEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "Version": + reader.ReadSignature("u"); + props.Version = reader.ReadUInt32(); + changedList?.Add("Version"); + break; + case "TextDirection": + reader.ReadSignature("s"); + props.TextDirection = reader.ReadString(); + changedList?.Add("TextDirection"); + break; + case "Status": + reader.ReadSignature("s"); + props.Status = reader.ReadString(); + changedList?.Add("Status"); + break; + case "IconThemePath": + reader.ReadSignature("as"); + props.IconThemePath = reader.ReadArray(); + changedList?.Add("IconThemePath"); + break; + default: + reader.ReadVariant(); + break; + } + } + return props; + } + } + + internal class DBusMenuService + { + public Connection Connection { get; } + public string Destination { get; } + public DBusMenuService(Connection connection, string destination) + => (Connection, Destination) = (connection, destination); + public DBusMenu CreateDbusmenu(string path) => new DBusMenu(this, path); + } + + internal class DBusMenuObject + { + public DBusMenuService Service { get; } + public ObjectPath Path { get; } + protected Connection Connection => Service.Connection; + protected DBusMenuObject(DBusMenuService service, ObjectPath path) + => (Service, Path) = (service, path); + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + protected ValueTask WatchPropertiesChangedAsync(string @interface, MessageValueReader> reader, Action> handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return Connection.AddMatchAsync(rule, reader, + (ex, changes, rs, hs) => ((Action>)hs!).Invoke(ex, changes), + this, handler, emitOnCapturedContext); + } + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, reader, + (ex, arg, rs, hs) => ((Action)hs!).Invoke(ex, arg), + this, handler, emitOnCapturedContext); + } + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, (message, state) => null!, + (ex, v, rs, hs) => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + } + protected static (uint, (int, Dictionary, object[])) ReadMessage_uriaesvavz(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadUInt32(); + var arg1 = reader.ReadStruct, object[]>(); + return (arg0, arg1); + } + protected static (int, Dictionary)[] ReadMessage_ariaesvz(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadArray<(int, Dictionary)>(); + } + protected static object ReadMessage_v(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadVariant(); + } + protected static int[] ReadMessage_ai(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadArray(); + } + protected static bool ReadMessage_b(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadBool(); + } + protected static (int[], int[]) ReadMessage_aiai(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadArray(); + var arg1 = reader.ReadArray(); + return (arg0, arg1); + } + protected static ((int, Dictionary)[], (int, string[])[]) ReadMessage_ariaesvzariasz(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadArray<(int, Dictionary)>(); + var arg1 = reader.ReadArray<(int, string[])>(); + return (arg0, arg1); + } + protected static (uint, int) ReadMessage_ui(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadUInt32(); + var arg1 = reader.ReadInt32(); + return (arg0, arg1); + } + protected static (int, uint) ReadMessage_iu(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadInt32(); + var arg1 = reader.ReadUInt32(); + return (arg0, arg1); + } + protected static uint ReadMessage_v_u(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("u"); + return reader.ReadUInt32(); + } + protected static string ReadMessage_v_s(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("s"); + return reader.ReadString(); + } + protected static string[] ReadMessage_v_as(Message message, DBusMenuObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("as"); + return reader.ReadArray(); + } + } +} diff --git a/src/Avalonia.FreeDesktop/DBusMenu.cs b/src/Avalonia.FreeDesktop/DBusMenu.cs deleted file mode 100644 index 3a1c65e7c9..0000000000 --- a/src/Avalonia.FreeDesktop/DBusMenu.cs +++ /dev/null @@ -1,56 +0,0 @@ - -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Tmds.DBus; - -[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)] -namespace Avalonia.FreeDesktop.DBusMenu -{ - - [DBusInterface("org.freedesktop.DBus.Properties")] - interface IFreeDesktopDBusProperties : IDBusObject - { - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); - } - - [DBusInterface("com.canonical.dbusmenu")] - interface IDBusMenu : IFreeDesktopDBusProperties - { - Task<(uint revision, (int, KeyValuePair[], object[]) layout)> GetLayoutAsync(int ParentId, int RecursionDepth, string[] PropertyNames); - Task<(int, KeyValuePair[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames); - Task GetPropertyAsync(int Id, string Name); - Task EventAsync(int Id, string EventId, object Data, uint Timestamp); - Task EventGroupAsync((int id, string eventId, object data, uint timestamp)[] events); - Task AboutToShowAsync(int Id); - Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids); - Task WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action? onError = null); - Task WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action? onError = null); - Task WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action? onError = null); - } - - [Dictionary] - class DBusMenuProperties - { - public uint Version { get; set; } = default (uint); - public string? TextDirection { get; set; } = default (string); - public string? Status { get; set; } = default (string); - public string[]? IconThemePath { get; set; } = default (string[]); - } - - - [DBusInterface("com.canonical.AppMenu.Registrar")] - interface IRegistrar : IDBusObject - { - Task RegisterWindowAsync(uint WindowId, ObjectPath MenuObjectPath); - Task UnregisterWindowAsync(uint WindowId); - Task<(string service, ObjectPath menuObjectPath)> GetMenuForWindowAsync(uint WindowId); - Task<(uint, string, ObjectPath)[]> GetMenusAsync(); - Task WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action? onError = null); - Task WatchWindowUnregisteredAsync(Action handler, Action? onError = null); - } -} diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 657e324010..682434bc8d 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -2,98 +2,81 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; -using System.Reactive.Disposables; +using System.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; -using Avalonia.FreeDesktop.DBusMenu; using Avalonia.Input; using Avalonia.Platform; using Avalonia.Threading; -using Tmds.DBus; +using Tmds.DBus.Protocol; + #pragma warning disable 1998 namespace Avalonia.FreeDesktop { public class DBusMenuExporter { - public static ITopLevelNativeMenuExporter? TryCreateTopLevelNativeMenu(IntPtr xid) - { - if (DBusHelper.Connection == null) - return null; + public static ITopLevelNativeMenuExporter? TryCreateTopLevelNativeMenu(IntPtr xid) => + DBusHelper.Connection is null ? null : new DBusMenuExporterImpl(DBusHelper.Connection, xid); - return new DBusMenuExporterImpl(DBusHelper.Connection, xid); - } - - public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path, Connection currentConection) - { - return new DBusMenuExporterImpl(currentConection, path); - } + public static INativeMenuExporter TryCreateDetachedNativeMenu(string path, Connection currentConnection) => + new DBusMenuExporterImpl(currentConnection, path); - public static ObjectPath GenerateDBusMenuObjPath => "/net/avaloniaui/dbusmenu/" - + Guid.NewGuid().ToString("N"); + public static string GenerateDBusMenuObjPath => $"/net/avaloniaui/dbusmenu/{Guid.NewGuid():N}"; - private class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IDBusMenu, IDisposable + private class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IMethodHandler, IDisposable { - private readonly Connection _dbus; + private readonly Connection _connection; + private readonly DBusMenuProperties _dBusMenuProperties = new() { Version = 2, Status = "normal" }; + private readonly Dictionary _idsToItems = new(); + private readonly Dictionary _itemsToIds = new(); + private readonly HashSet _menus = new(); private readonly uint _xid; - private IRegistrar? _registrar; + private readonly bool _appMenu = true; + private Registrar? _registrar; + private NativeMenu? _menu; private bool _disposed; private uint _revision = 1; - private NativeMenu? _menu; - private readonly Dictionary _idsToItems = new Dictionary(); - private readonly Dictionary _itemsToIds = new Dictionary(); - private readonly HashSet _menus = new HashSet(); private bool _resetQueued; private int _nextId = 1; - private bool _appMenu = true; - - public DBusMenuExporterImpl(Connection dbus, IntPtr xid) + + public DBusMenuExporterImpl(Connection connection, IntPtr xid) { - _dbus = dbus; + _connection = connection; _xid = (uint)xid.ToInt32(); - ObjectPath = GenerateDBusMenuObjPath; + Path = GenerateDBusMenuObjPath; SetNativeMenu(new NativeMenu()); Init(); } - public DBusMenuExporterImpl(Connection dbus, ObjectPath path) + public DBusMenuExporterImpl(Connection connection, string path) { - _dbus = dbus; + _connection = connection; _appMenu = false; - ObjectPath = path; + Path = path; SetNativeMenu(new NativeMenu()); Init(); } - - async void Init() - { - try - { - if (_appMenu) - { - await _dbus.RegisterObjectAsync(this); - _registrar = DBusHelper.Connection?.CreateProxy( - "com.canonical.AppMenu.Registrar", - "/com/canonical/AppMenu/Registrar"); - if (!_disposed && _registrar is { }) - await _registrar.RegisterWindowAsync(_xid, ObjectPath); - } - else - { - await _dbus.RegisterObjectAsync(this); - } - } - catch (Exception e) - { - Logging.Logger.TryGet(Logging.LogEventLevel.Error, Logging.LogArea.X11Platform) - ?.Log(this, e.Message); - // It's not really important if this code succeeds, - // and it's not important to know if it succeeds - // since even if we register the window it's not guaranteed that - // menu will be actually exported - } + public string Path { get; } + + private async void Init() + { + _connection.AddMethodHandler(this); + if (!_appMenu) + return; + var services = await _connection.ListServicesAsync(); + if (!services.Contains("com.canonical.AppMenu.Registrar")) + return; + _registrar = new RegistrarService(_connection, "com.canonical.AppMenu.Registrar") + .CreateRegistrar("/com/canonical/AppMenu/Registrar"); + if (!_disposed) + await _registrar.RegisterWindowAsync(_xid, Path); + // It's not really important if this code succeeds, + // and it's not important to know if it succeeds + // since even if we register the window it's not guaranteed that + // menu will be actually exported } public void Dispose() @@ -101,7 +84,7 @@ namespace Avalonia.FreeDesktop if (_disposed) return; _disposed = true; - _dbus.UnregisterObject(this); + // Fire and forget _registrar?.UnregisterWindowAsync(_xid); } @@ -113,17 +96,17 @@ namespace Avalonia.FreeDesktop public void SetNativeMenu(NativeMenu? menu) { - if (menu == null) + if (menu is null) menu = new NativeMenu(); - if (_menu != null) + if (_menu is not null) ((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged; _menu = menu; ((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged; - + DoLayoutReset(); } - + /* This is basic initial implementation, so we don't actually track anything and just reset the whole layout on *ANY* change @@ -131,10 +114,10 @@ namespace Avalonia.FreeDesktop This is not how it should work and will prevent us from implementing various features, but that's the fastest way to get things working, so... */ - void DoLayoutReset() + private void DoLayoutReset() { _resetQueued = false; - foreach (var i in _idsToItems.Values) + foreach (var i in _idsToItems.Values) i.PropertyChanged -= OnItemPropertyChanged; foreach(var menu in _menus) ((INotifyCollectionChanged)menu.Items).CollectionChanged -= OnMenuItemsChanged; @@ -142,10 +125,10 @@ namespace Avalonia.FreeDesktop _idsToItems.Clear(); _itemsToIds.Clear(); _revision++; - LayoutUpdated?.Invoke((_revision, 0)); + EmitUIntIntSignal("LayoutUpdated", _revision, 0); } - void QueueReset() + private void QueueReset() { if(_resetQueued) return; @@ -163,10 +146,10 @@ namespace Avalonia.FreeDesktop private void EnsureSubscribed(NativeMenu? menu) { - if(menu!=null && _menus.Add(menu)) + if (menu is not null && _menus.Add(menu)) ((INotifyCollectionChanged)menu.Items).CollectionChanged += OnMenuItemsChanged; } - + private int GetId(NativeMenuItemBase item) { if (_itemsToIds.TryGetValue(item, out var id)) @@ -190,33 +173,11 @@ namespace Avalonia.FreeDesktop QueueReset(); } - public ObjectPath ObjectPath { get; } - - - async Task IFreeDesktopDBusProperties.GetAsync(string prop) - { - if (prop == "Version") - return 2; - if (prop == "Status") - return "normal"; - return 0; - } - - async Task IFreeDesktopDBusProperties.GetAllAsync() - { - return new DBusMenuProperties - { - Version = 2, - Status = "normal", - }; - } - - private static string[] AllProperties = new[] - { + private static readonly string[] AllProperties = { "type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data" }; - - object? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) + + private object? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) { var (it, menu) = i; @@ -228,24 +189,20 @@ namespace Avalonia.FreeDesktop else if (it is NativeMenuItem item) { if (name == "type") - { return null; - } if (name == "label") - return item?.Header ?? ""; + return item.Header ?? ""; if (name == "enabled") { - if (item == null) - return null; - if (item.Menu != null && item.Menu.Items.Count == 0) + if (item.Menu is not null && item.Menu.Items.Count == 0) return false; - if (item.IsEnabled == false) + if (!item.IsEnabled) return false; return null; } if (name == "shortcut") { - if (item?.Gesture == null) + if (item.Gesture is null) return null; if (item.Gesture.KeyModifiers == 0) return null; @@ -271,19 +228,16 @@ namespace Avalonia.FreeDesktop return "radio"; } - if (name == "toggle-state") - { - if (item.ToggleType != NativeMenuItemToggleType.None) - return item.IsChecked ? 1 : 0; - } - + if (name == "toggle-state" && item.ToggleType != NativeMenuItemToggleType.None) + return item.IsChecked ? 1 : 0; + if (name == "icon-data") { - if (item.Icon != null) + if (item.Icon is not null) { var loader = AvaloniaLocator.Current.GetService(); - if (loader != null) + if (loader is not null) { var icon = loader.LoadIcon(item.Icon.PlatformImpl.Item); @@ -293,155 +247,199 @@ namespace Avalonia.FreeDesktop } } } - + if (name == "children-display") - return menu != null ? "submenu" : null; + return menu is not null ? "submenu" : null; } return null; } - private List> _reusablePropertyList = new List>(); - KeyValuePair[] GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) + private Dictionary GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) { - if (names?.Length > 0 != true) + if (names.Length == 0) names = AllProperties; - _reusablePropertyList.Clear(); + var properties = new Dictionary(); foreach (var n in names) { var v = GetProperty(i, n); - if (v != null) - _reusablePropertyList.Add(new KeyValuePair(n, v)); + if (v is not null) + properties.Add(n, v); } - return _reusablePropertyList.ToArray(); + return properties; } - - public Task SetAsync(string prop, object val) => Task.CompletedTask; - - public Task<(uint revision, (int, KeyValuePair[], object[]) layout)> GetLayoutAsync( - int ParentId, int RecursionDepth, string[] PropertyNames) + private (int, Dictionary, object[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames) { - var menu = GetMenu(ParentId); - var rv = (_revision, GetLayout(menu.item, menu.menu, RecursionDepth, PropertyNames)); - if (!IsNativeMenuExported) - { - IsNativeMenuExported = true; - Dispatcher.UIThread.Post(() => - { - OnIsNativeMenuExportedChanged?.Invoke(this, EventArgs.Empty); - }); - } - return Task.FromResult(rv); - } - - (int, KeyValuePair[], object[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames) - { - var id = item == null ? 0 : GetId(item); + var id = item is null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); - var children = (depth == 0 || menu == null) ? Array.Empty() : new object[menu.Items.Count]; - if(menu != null) + var children = depth == 0 || menu is null ? Array.Empty() : new object[menu.Items.Count]; + if (menu is not null) + { for (var c = 0; c < children.Length; c++) { var ch = menu.Items[c]; - children[c] = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames); } - - return (id, props, children); - } - - public Task<(int, KeyValuePair[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames) - { - var arr = new (int, KeyValuePair[])[Ids.Length]; - for (var c = 0; c < Ids.Length; c++) - { - var id = Ids[c]; - var item = GetMenu(id); - var props = GetProperties(item, PropertyNames); - arr[c] = (id, props); } - return Task.FromResult(arr); - } - - public async Task GetPropertyAsync(int Id, string Name) - { - return GetProperty(GetMenu(Id), Name) ?? 0; + return (id, props, children); } - - public void HandleEvent(int id, string eventId, object data, uint timestamp) + private void HandleEvent(int id, string eventId, object data, uint timestamp) { if (eventId == "clicked") { var item = GetMenu(id).item; - - if (item is NativeMenuItem menuItem && item is INativeMenuItemExporterEventsImplBridge bridge) - { - if (menuItem?.IsEnabled == true) - bridge?.RaiseClicked(); - } + if (item is NativeMenuItem { IsEnabled: true } and INativeMenuItemExporterEventsImplBridge bridge) + bridge.RaiseClicked(); } } - - public Task EventAsync(int Id, string EventId, object Data, uint Timestamp) - { - HandleEvent(Id, EventId, Data, Timestamp); - return Task.CompletedTask; - } - public Task EventGroupAsync((int id, string eventId, object data, uint timestamp)[] Events) + public ValueTask HandleMethodAsync(MethodContext context) { - foreach (var e in Events) - HandleEvent(e.id, e.eventId, e.data, e.timestamp); - return Task.FromResult(Array.Empty()); - } - - public async Task AboutToShowAsync(int Id) - { - return false; - } - - public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids) - { - return (Array.Empty(), Array.Empty()); - } + switch (context.Request.InterfaceAsString) + { + case "com.canonical.dbusmenu": + switch (context.Request.MemberAsString, context.Request.SignatureAsString) + { + case ("GetLayout", "iias"): + { + using var writer = context.CreateReplyWriter("u(ia{sv}av)"); + var reader = context.Request.GetBodyReader(); + var parentId = reader.ReadInt32(); + var recursionDepth = reader.ReadInt32(); + var propertyNames = reader.ReadArray(); + var menu = GetMenu(parentId); + var layout = GetLayout(menu.item, menu.menu, recursionDepth, propertyNames); + writer.WriteUInt32(_revision); + writer.WriteStruct(layout); + if (!IsNativeMenuExported) + { + IsNativeMenuExported = true; + Dispatcher.UIThread.Post(() => OnIsNativeMenuExportedChanged?.Invoke(this, EventArgs.Empty)); + } + + context.Reply(writer.CreateMessage()); + break; + } + case ("GetGroupProperties", "aias"): + { + using var writer = context.CreateReplyWriter("a(ia{sv})"); + var reader = context.Request.GetBodyReader(); + var ids = reader.ReadArray(); + var propertyNames = reader.ReadArray(); + var arrayStart = writer.WriteArrayStart(DBusType.Struct); + foreach (var id in ids) + { + var item = GetMenu(id); + var props = GetProperties(item, propertyNames); + writer.WriteStruct((id, props)); + } + + writer.WriteArrayEnd(arrayStart); + context.Reply(writer.CreateMessage()); + break; + } + case ("GetProperty", "is"): + { + using var writer = context.CreateReplyWriter("v"); + var reader = context.Request.GetBodyReader(); + var id = reader.ReadInt32(); + var name = reader.ReadString(); + writer.WriteVariant(GetProperty(GetMenu(id), name) ?? 0); + context.Reply(writer.CreateMessage()); + break; + } + case ("Event", "isvu"): + { + var reader = context.Request.GetBodyReader(); + var id = reader.ReadInt32(); + var eventId = reader.ReadString(); + var data = reader.ReadVariant(); + var timestamp = reader.ReadUInt32(); + Dispatcher.UIThread.Post(() => HandleEvent(id, eventId, data, timestamp)); + break; + } + case ("EventGroup", "a(isvu)"): + { + using var writer = context.CreateReplyWriter("ai"); + var reader = context.Request.GetBodyReader(); + var events = reader.ReadArray<(int Id, string EventId, object Data, uint Timestamp)>(); + foreach (var e in events) + Dispatcher.UIThread.Post(() => HandleEvent(e.Id, e.EventId, e.Data, e.Timestamp)); + writer.WriteArray(Array.Empty()); + context.Reply(writer.CreateMessage()); + break; + } + case ("AboutToShow", "i"): + { + using var writer = context.CreateReplyWriter("b"); + writer.WriteBool(false); + context.Reply(writer.CreateMessage()); + break; + } + case ("AboutToShowGroup", "ai"): + { + using var writer = context.CreateReplyWriter("aiai"); + writer.WriteStruct((Array.Empty(), Array.Empty())); + context.Reply(writer.CreateMessage()); + break; + } + } - #region Events + break; + case "org.freedesktop.DBus.Properties": + switch (context.Request.MemberAsString, context.Request.SignatureAsString) + { + case ("Version", "u"): + { + using var writer = context.CreateReplyWriter("u"); + writer.WriteUInt32(_dBusMenuProperties.Version); + context.Reply(writer.CreateMessage()); + break; + } + case ("TextDirection", "s"): + { + using var writer = context.CreateReplyWriter("s"); + writer.WriteString(_dBusMenuProperties.TextDirection); + context.Reply(writer.CreateMessage()); + break; + } + case ("Status", "s"): + { + using var writer = context.CreateReplyWriter("s"); + writer.WriteString(_dBusMenuProperties.Status); + context.Reply(writer.CreateMessage()); + break; + } + case ("IconThemePath", "as"): + { + using var writer = context.CreateReplyWriter("as"); + writer.WriteArray(_dBusMenuProperties.IconThemePath); + context.Reply(writer.CreateMessage()); + break; + } + } - private event Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> - ItemsPropertiesUpdated { add { } remove { } } - private event Action<(uint revision, int parent)>? LayoutUpdated; - private event Action<(int id, uint timestamp)> ItemActivationRequested { add { } remove { } } - private event Action PropertiesChanged { add { } remove { } } + break; + } - async Task IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary)[] updatedProps, (int, string[])[] removedProps)> handler, Action? onError) - { - ItemsPropertiesUpdated += handler; - return Disposable.Create(() => ItemsPropertiesUpdated -= handler); - } - async Task IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action? onError) - { - LayoutUpdated += handler; - return Disposable.Create(() => LayoutUpdated -= handler); + return default; } - async Task IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action? onError) - { - ItemActivationRequested+= handler; - return Disposable.Create(() => ItemActivationRequested -= handler); - } + public bool RunMethodHandlerSynchronously(Message message) => true; - async Task IFreeDesktopDBusProperties.WatchPropertiesAsync(Action handler) + private void EmitUIntIntSignal(string member, uint arg0, int arg1) { - PropertiesChanged += handler; - return Disposable.Create(() => PropertiesChanged -= handler); + using var writer = _connection.GetMessageWriter(); + writer.WriteSignalHeader(null, Path, "com.canonical.dbusmenu", member, "ui"); + writer.WriteUInt32(arg0); + writer.WriteInt32(arg1); + _connection.TrySendMessage(writer.CreateMessage()); } - - #endregion } } } diff --git a/src/Avalonia.FreeDesktop/DBusRequest.cs b/src/Avalonia.FreeDesktop/DBusRequest.cs deleted file mode 100644 index d84905324f..0000000000 --- a/src/Avalonia.FreeDesktop/DBusRequest.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; -using Tmds.DBus; - -[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] -namespace Avalonia.FreeDesktop -{ - [DBusInterface("org.freedesktop.portal.Request")] - internal interface IRequest : IDBusObject - { - Task CloseAsync(); - Task WatchResponseAsync(Action<(uint response, IDictionary results)> handler, Action? onError = null); - } -} diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index 8597f3922a..d59af3b5dd 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -4,45 +4,33 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; -using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; - -using Tmds.DBus; +using Tmds.DBus.Protocol; namespace Avalonia.FreeDesktop { internal class DBusSystemDialog : BclStorageProvider { - private static readonly Lazy s_fileChooser = new(() => DBusHelper.Connection? - .CreateProxy("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")); - - internal static async Task TryCreate(IPlatformHandle handle) + internal static async Task TryCreateAsync(IPlatformHandle handle) { - if (handle.HandleDescriptor == "XID" && s_fileChooser.Value is { } fileChooser) - { - try - { - await fileChooser.GetVersionAsync(); - return new DBusSystemDialog(fileChooser, handle); - } - catch (Exception e) - { - Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform)?.Log(null, $"Unable to connect to org.freedesktop.portal.Desktop: {e.Message}"); - return null; - } - } - - return null; + if (DBusHelper.Connection is null) + return null; + var services = await DBusHelper.Connection.ListServicesAsync(); + if (!services.Contains("org.freedesktop.portal.Desktop", StringComparer.Ordinal)) + return null; + return new DBusSystemDialog(new DesktopService(DBusHelper.Connection, "org.freedesktop.portal.Desktop"), handle); } - private readonly IFileChooser _fileChooser; + private readonly DesktopService _desktopService; + private readonly FileChooser _fileChooser; private readonly IPlatformHandle _handle; - private DBusSystemDialog(IFileChooser fileChooser, IPlatformHandle handle) + private DBusSystemDialog(DesktopService desktopService, IPlatformHandle handle) { - _fileChooser = fileChooser; + _desktopService = desktopService; + _fileChooser = desktopService.CreateFileChooser("/org/freedesktop/portal/desktop"); _handle = handle; } @@ -59,20 +47,24 @@ namespace Avalonia.FreeDesktop var chooserOptions = new Dictionary(); var filters = ParseFilters(options.FileTypeFilter); if (filters.Any()) - { chooserOptions.Add("filters", filters); - } chooserOptions.Add("multiple", options.AllowMultiple); objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - var request = DBusHelper.Connection!.CreateProxy("org.freedesktop.portal.Request", objectPath); + var request = _desktopService.CreateRequest(objectPath); var tsc = new TaskCompletionSource(); - using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); - var uris = await tsc.Task ?? Array.Empty(); + using var disposable = await request.WatchResponseAsync((e, x) => + { + if (e is not null) + tsc.TrySetException(e); + else + tsc.TrySetResult(x.results["uris"] as string[]); + }); - return uris.Select(path => new BclStorageFile(new FileInfo(new Uri(path).LocalPath))).ToList(); + var uris = await tsc.Task ?? Array.Empty(); + return uris.Select(static path => new BclStorageFile(new FileInfo(new Uri(path).LocalPath))).ToList(); } public override async Task SaveFilePickerAsync(FilePickerSaveOptions options) @@ -82,33 +74,33 @@ namespace Avalonia.FreeDesktop var chooserOptions = new Dictionary(); var filters = ParseFilters(options.FileTypeChoices); if (filters.Any()) - { chooserOptions.Add("filters", filters); - } if (options.SuggestedFileName is { } currentName) chooserOptions.Add("current_name", currentName); if (options.SuggestedStartLocation?.TryGetUri(out var currentFolder) == true) chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString())); - objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - var request = DBusHelper.Connection!.CreateProxy("org.freedesktop.portal.Request", objectPath); + objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); + var request = _desktopService.CreateRequest(objectPath); var tsc = new TaskCompletionSource(); - using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); + using var disposable = await request.WatchResponseAsync((e, x) => + { + if (e is not null) + tsc.TrySetException(e); + else + tsc.TrySetResult(x.results["uris"] as string[]); + }); + var uris = await tsc.Task; var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).LocalPath : null; if (path is null) - { return null; - } - else - { - // WSL2 freedesktop automatically adds extension from selected file type, but we can't pass "default ext". So apply it manually. - path = StorageProviderHelpers.NameWithExtension(path, options.DefaultExtension, null); - return new BclStorageFile(new FileInfo(path)); - } + // WSL2 freedesktop automatically adds extension from selected file type, but we can't pass "default ext". So apply it manually. + path = StorageProviderHelpers.NameWithExtension(path, options.DefaultExtension, null); + return new BclStorageFile(new FileInfo(path)); } public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) @@ -119,27 +111,31 @@ namespace Avalonia.FreeDesktop { "directory", true }, { "multiple", options.AllowMultiple } }; + var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - var request = DBusHelper.Connection!.CreateProxy("org.freedesktop.portal.Request", objectPath); + var request = _desktopService.CreateRequest(objectPath); var tsc = new TaskCompletionSource(); - using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); - var uris = await tsc.Task ?? Array.Empty(); + using var disposable = await request.WatchResponseAsync((e, x) => + { + if (e is not null) + tsc.TrySetException(e); + else + tsc.TrySetResult(x.results["uris"] as string[]); + }); + var uris = await tsc.Task ?? Array.Empty(); return uris - .Select(path => new Uri(path).LocalPath) + .Select(static path => new Uri(path).LocalPath) // WSL2 freedesktop allows to select files as well in directory picker, filter it out. .Where(Directory.Exists) - .Select(path => new BclStorageFolder(new DirectoryInfo(path))).ToList(); + .Select(static path => new BclStorageFolder(new DirectoryInfo(path))).ToList(); } private static (string name, (uint style, string extension)[])[] ParseFilters(IReadOnlyList? fileTypes) { // Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] - if (fileTypes is null) - { return Array.Empty<(string name, (uint style, string extension)[])>(); - } var filters = new List<(string name, (uint style, string extension)[])>(); foreach (var fileType in fileTypes) @@ -150,18 +146,11 @@ namespace Avalonia.FreeDesktop var extensions = Enumerable.Empty<(uint, string)>(); if (fileType.Patterns is { } patterns) - { extensions = extensions.Concat(patterns.Select(static x => (globStyle, x))); - } else if (fileType.MimeTypes is { } mimeTypes) - { extensions = extensions.Concat(mimeTypes.Select(static x => (mimeStyle, x))); - } - if (extensions.Any()) - { filters.Add((fileType.Name, extensions.ToArray())); - } } return filters.ToArray(); diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index e37067d05c..ed041cb2ca 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -1,16 +1,12 @@ -#nullable enable - -using System; +using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Reactive.Disposables; -using System.Runtime.CompilerServices; +using System.Linq; using System.Threading.Tasks; using Avalonia.Controls.Platform; using Avalonia.Logging; using Avalonia.Platform; -using Tmds.DBus; - -[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] +using Tmds.DBus.Protocol; namespace Avalonia.FreeDesktop { @@ -20,11 +16,12 @@ namespace Avalonia.FreeDesktop private readonly ObjectPath _dbusMenuPath; private readonly Connection? _connection; - private IDisposable? _serviceWatchDisposable; + private readonly DBus? _dBus; + private IDisposable? _serviceWatchDisposable; private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj; - private IStatusNotifierWatcher? _statusNotifierWatcher; - private DbusPixmap _icon; + private StatusNotifierWatcher? _statusNotifierWatcher; + private (int, int, byte[]) _icon; private string? _sysTrayServiceName; private string? _tooltipText; @@ -51,6 +48,7 @@ namespace Avalonia.FreeDesktop IsActive = true; + _dBus = new DBusService(_connection, "org.freedesktop.DBus").CreateDBus("/org/freedesktop/DBus"); _dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection); @@ -60,47 +58,31 @@ namespace Avalonia.FreeDesktop private void InitializeSNWService() { - if (_connection is null || _isDisposed) return; - - try - { - _statusNotifierWatcher = _connection.CreateProxy( - "org.kde.StatusNotifierWatcher", - "/StatusNotifierWatcher"); - } - catch - { - Logger.TryGet(LogEventLevel.Error, "DBUS") - ?.Log(this, - "org.kde.StatusNotifierWatcher service is not available on this system. Tray Icons will not work without it."); - + if (_connection is null || _isDisposed) return; - } + _statusNotifierWatcher = new StatusNotifierWatcherService(_connection, "org.kde.StatusNotifierWatcher") + .CreateStatusNotifierWatcher("/StatusNotifierWatcher"); _serviceConnected = true; } private async void WatchAsync() { - try - { - _serviceWatchDisposable = - await _connection?.ResolveServiceOwnerAsync("org.kde.StatusNotifierWatcher", OnNameChange)!; - } - catch (Exception e) - { - Logger.TryGet(LogEventLevel.Error, "DBUS") - ?.Log(this, - $"Unable to hook watcher method on org.kde.StatusNotifierWatcher: {e}"); - } + var services = await _connection!.ListServicesAsync(); + if (!services.Contains("org.kde.StatusNotifierWatcher", StringComparer.Ordinal)) + return; + + _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((e, x) => { OnNameChange(x.A2); }); + var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher"); + OnNameChange(nameOwner); } - private void OnNameChange(ServiceOwnerChangedEventArgs obj) + private void OnNameChange(string? newOwner) { if (_isDisposed) return; - if (!_serviceConnected & obj.NewOwner != null) + if (!_serviceConnected & newOwner is not null) { _serviceConnected = true; InitializeSNWService(); @@ -108,55 +90,45 @@ namespace Avalonia.FreeDesktop DestroyTrayIcon(); if (_isVisible) - { CreateTrayIcon(); - } } - else if (_serviceConnected & obj.NewOwner is null) + else if (_serviceConnected & newOwner is null) { DestroyTrayIcon(); _serviceConnected = false; } } - private void CreateTrayIcon() + private async void CreateTrayIcon() { if (_connection is null || !_serviceConnected || _isDisposed) return; +#if NET5_0_OR_GREATER + var pid = Environment.ProcessId; +#else var pid = Process.GetCurrentProcess().Id; +#endif var tid = s_trayIconInstanceId++; _sysTrayServiceName = FormattableString.Invariant($"org.kde.StatusNotifierItem-{pid}-{tid}"); - _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath); - - try - { - _connection.RegisterObjectAsync(_statusNotifierItemDbusObj); - _connection.RegisterServiceAsync(_sysTrayServiceName); - _statusNotifierWatcher?.RegisterStatusNotifierItemAsync(_sysTrayServiceName); - } - catch (Exception e) - { - Logger.TryGet(LogEventLevel.Error, "DBUS") - ?.Log(this, $"Error creating a DBus tray icon: {e}."); + _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_connection, _dbusMenuPath); - _serviceConnected = false; - } + _connection.AddMethodHandler(_statusNotifierItemDbusObj); + await _dBus!.RequestNameAsync(_sysTrayServiceName, 0); + await _statusNotifierWatcher!.RegisterStatusNotifierItemAsync(_sysTrayServiceName); _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); _statusNotifierItemDbusObj.SetIcon(_icon); - _statusNotifierItemDbusObj.ActivationDelegate += OnClicked; } private void DestroyTrayIcon() { - if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierItemDbusObj is null) + if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierItemDbusObj is null || _sysTrayServiceName is null) return; - _connection.UnregisterObject(_statusNotifierItemDbusObj); - _connection.UnregisterServiceAsync(_sysTrayServiceName); + _dBus.ReleaseNameAsync(_sysTrayServiceName); } public void Dispose() @@ -175,13 +147,14 @@ namespace Avalonia.FreeDesktop if (icon is null) { - _statusNotifierItemDbusObj?.SetIcon(DbusPixmap.EmptyPixmap); + _statusNotifierItemDbusObj?.SetIcon((1, 1, new byte[] { 255, 0, 0, 0 })); return; } var x11iconData = IconConverterDelegate(icon); - if (x11iconData.Length == 0) return; + if (x11iconData.Length == 0) + return; var w = (int)x11iconData[0]; var h = (int)x11iconData[1]; @@ -199,7 +172,7 @@ namespace Avalonia.FreeDesktop pixByteArray[pixByteArrayCounter++] = (byte)(rawPixel & 0xFF); } - _icon = new DbusPixmap(w, h, pixByteArray); + _icon = (w, h, pixByteArray); _statusNotifierItemDbusObj?.SetIcon(_icon); } @@ -237,111 +210,38 @@ namespace Avalonia.FreeDesktop /// /// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html /// - internal class StatusNotifierItemDbusObj : IStatusNotifierItem + internal class StatusNotifierItemDbusObj : IMethodHandler { + private readonly Connection _connection; private readonly StatusNotifierItemProperties _backingProperties; - public event Action? OnTitleChanged; - public event Action? OnIconChanged; - public event Action? OnAttentionIconChanged; - public event Action? OnOverlayIconChanged; - public event Action? OnTooltipChanged; - public Action? NewStatusAsync { get; set; } - public Action? ActivationDelegate { get; set; } - public ObjectPath ObjectPath { get; } - - public StatusNotifierItemDbusObj(ObjectPath dbusmenuPath) - { - ObjectPath = new ObjectPath($"/StatusNotifierItem"); + public StatusNotifierItemDbusObj(Connection connection, ObjectPath dbusMenuPath) + { + _connection = connection; _backingProperties = new StatusNotifierItemProperties { - Menu = dbusmenuPath, // Needs a dbus menu somehow - ToolTip = new ToolTip("") + Menu = dbusMenuPath, // Needs a dbus menu somehow + ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), string.Empty, string.Empty) }; InvalidateAll(); } - public Task ContextMenuAsync(int x, int y) => Task.CompletedTask; - - public Task ActivateAsync(int x, int y) - { - ActivationDelegate?.Invoke(); - return Task.CompletedTask; - } + public string Path => "/StatusNotifierItem"; - public Task SecondaryActivateAsync(int x, int y) => Task.CompletedTask; - - public Task ScrollAsync(int delta, string orientation) => Task.CompletedTask; + public event Action? ActivationDelegate; public void InvalidateAll() { - OnTitleChanged?.Invoke(); - OnIconChanged?.Invoke(); - OnOverlayIconChanged?.Invoke(); - OnAttentionIconChanged?.Invoke(); - OnTooltipChanged?.Invoke(); - } - - public Task WatchNewTitleAsync(Action handler, Action onError) - { - OnTitleChanged += handler; - return Task.FromResult(Disposable.Create(() => OnTitleChanged -= handler)); - } - - public Task WatchNewIconAsync(Action handler, Action onError) - { - OnIconChanged += handler; - return Task.FromResult(Disposable.Create(() => OnIconChanged -= handler)); - } - - public Task WatchNewAttentionIconAsync(Action handler, Action onError) - { - OnAttentionIconChanged += handler; - return Task.FromResult(Disposable.Create(() => OnAttentionIconChanged -= handler)); + EmitVoidSignal("NewTitle"); + EmitVoidSignal("NewIcon"); + EmitVoidSignal("NewAttentionIcon"); + EmitVoidSignal("NewOverlayIcon"); + EmitVoidSignal("NewToolTip"); + EmitStringSignal("NewStatus", _backingProperties.Status ?? string.Empty); } - public Task WatchNewOverlayIconAsync(Action handler, Action onError) - { - OnOverlayIconChanged += handler; - return Task.FromResult(Disposable.Create(() => OnOverlayIconChanged -= handler)); - } - - public Task WatchNewToolTipAsync(Action handler, Action onError) - { - OnTooltipChanged += handler; - return Task.FromResult(Disposable.Create(() => OnTooltipChanged -= handler)); - } - - public Task WatchNewStatusAsync(Action handler, Action onError) - { - NewStatusAsync += handler; - return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler)); - } - - public Task GetAsync(string prop) - { - return Task.FromResult(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); - - public Task SetAsync(string prop, object val) => Task.CompletedTask; - - public Task WatchPropertiesAsync(Action handler) => - Task.FromResult(Disposable.Empty); - - public void SetIcon(DbusPixmap dbusPixmap) + public void SetIcon((int, int, byte[]) dbusPixmap) { _backingProperties.IconPixmap = new[] { dbusPixmap }; InvalidateAll(); @@ -356,98 +256,153 @@ namespace Avalonia.FreeDesktop _backingProperties.Category = "ApplicationStatus"; _backingProperties.Status = text; _backingProperties.Title = text; - _backingProperties.ToolTip = new ToolTip(text); - + _backingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), text, string.Empty); InvalidateAll(); } - } - - [DBusInterface("org.kde.StatusNotifierWatcher")] - internal interface IStatusNotifierWatcher : IDBusObject - { - Task RegisterStatusNotifierItemAsync(string Service); - Task RegisterStatusNotifierHostAsync(string Service); - } - [DBusInterface("org.kde.StatusNotifierItem")] - internal interface IStatusNotifierItem : IDBusObject - { - Task ContextMenuAsync(int x, int y); - Task ActivateAsync(int x, int y); - Task SecondaryActivateAsync(int x, int y); - Task ScrollAsync(int delta, string orientation); - Task WatchNewTitleAsync(Action handler, Action onError); - Task WatchNewIconAsync(Action handler, Action onError); - Task WatchNewAttentionIconAsync(Action handler, Action onError); - Task WatchNewOverlayIconAsync(Action handler, Action onError); - Task WatchNewToolTipAsync(Action handler, Action onError); - Task WatchNewStatusAsync(Action handler, Action onError); - Task GetAsync(string prop); - Task GetAllAsync(); - Task SetAsync(string prop, object val); - Task WatchPropertiesAsync(Action handler); - } - - // This class is used by Tmds.Dbus to ferry properties - // from the SNI spec. - // Don't change this to actual C# properties since - // Tmds.Dbus will get confused. - [Dictionary] - internal class StatusNotifierItemProperties - { - public string? Category; - - public string? Id; - - public string? Title; + public bool RunMethodHandlerSynchronously(Message message) => false; - public string? Status; - - public ObjectPath Menu; - - public DbusPixmap[]? IconPixmap; + public ValueTask HandleMethodAsync(MethodContext context) + { + switch (context.Request.InterfaceAsString) + { + case "org.kde.StatusNotifierItem": + switch (context.Request.MemberAsString, context.Request.SignatureAsString) + { + case ("ContextMenu", "ii"): + break; + case ("Activate", "ii"): + ActivationDelegate?.Invoke(); + break; + case ("SecondaryActivate", "ii"): + break; + case ("Scroll", "is"): + break; + } - public ToolTip ToolTip; - } + break; + case "org.freedesktop.DBus.Properties": + switch (context.Request.MemberAsString, context.Request.SignatureAsString) + { + case ("Get", "ss"): + { + var reader = context.Request.GetBodyReader(); + var interfaceName = reader.ReadString(); + var member = reader.ReadString(); + switch (member) + { + case "Category": + { + using var writer = context.CreateReplyWriter("s"); + writer.WriteString(_backingProperties.Category); + context.Reply(writer.CreateMessage()); + break; + } + case "Id": + { + using var writer = context.CreateReplyWriter("s"); + writer.WriteString(_backingProperties.Id); + context.Reply(writer.CreateMessage()); + break; + } + case "Title": + { + using var writer = context.CreateReplyWriter("s"); + writer.WriteString(_backingProperties.Title); + context.Reply(writer.CreateMessage()); + break; + } + case "Status": + { + using var writer = context.CreateReplyWriter("s"); + writer.WriteString(_backingProperties.Status); + context.Reply(writer.CreateMessage()); + break; + } + case "Menu": + { + using var writer = context.CreateReplyWriter("o"); + writer.WriteObjectPath(_backingProperties.Menu); + context.Reply(writer.CreateMessage()); + break; + } + case "IconPixmap": + { + using var writer = context.CreateReplyWriter("a(iiay)"); + writer.WriteArray(_backingProperties.IconPixmap); + context.Reply(writer.CreateMessage()); + break; + } + case "ToolTip": + { + using var writer = context.CreateReplyWriter("(sa(iiay)ss)"); + writer.WriteStruct(_backingProperties.ToolTip); + context.Reply(writer.CreateMessage()); + break; + } + } + + break; + } + case ("GetAll", "s"): + { + var writer = context.CreateReplyWriter("a{sv}"); + var dict = new Dictionary + { + { "Category", _backingProperties.Category ?? string.Empty }, + { "Id", _backingProperties.Id ?? string.Empty }, + { "Title", _backingProperties.Title ?? string.Empty }, + { "Status", _backingProperties.Status ?? string.Empty }, + { "Menu", _backingProperties.Menu }, + { "IconPixmap", _backingProperties.IconPixmap }, + { "ToolTip", _backingProperties.ToolTip } + }; + + writer.WriteDictionary(dict); + context.Reply(writer.CreateMessage()); + break; + } + } - internal struct ToolTip - { - public readonly string First; - public readonly DbusPixmap[] Second; - public readonly string Third; - public readonly string Fourth; + break; + } - private static readonly DbusPixmap[] s_blank = - { - new DbusPixmap(0, 0, Array.Empty()), new DbusPixmap(0, 0, Array.Empty()) - }; + return default; + } - public ToolTip(string message) : this("", s_blank, message, "") + private void EmitVoidSignal(string member) { + using var writer = _connection.GetMessageWriter(); + writer.WriteSignalHeader(null, Path, "org.kde.StatusNotifierItem", member); + _connection.TrySendMessage(writer.CreateMessage()); } - public ToolTip(string first, DbusPixmap[] second, string third, string fourth) + private void EmitStringSignal(string member, string value) { - First = first; - Second = second; - Third = third; - Fourth = fourth; + using var writer = _connection.GetMessageWriter(); + writer.WriteSignalHeader(null, Path, "org.kde.StatusNotifierItem", member, "s"); + writer.WriteString(value); + _connection.TrySendMessage(writer.CreateMessage()); } - } - internal readonly struct DbusPixmap - { - public readonly int Width; - public readonly int Height; - public readonly byte[] Data; - - public DbusPixmap(int width, int height, byte[] data) + private record StatusNotifierItemProperties { - Width = width; - Height = height; - Data = data; + public string? Category { get; set; } + public string? Id { get; set; } + public string? Title { get; set; } + public string? Status { get; set; } + public int WindowId { get; set; } + public string? IconThemePath { get; set; } + public ObjectPath Menu { get; set; } + public bool ItemIsMenu { get; set; } + public string? IconName { get; set; } + public (int, int, byte[])[]? IconPixmap { get; set; } + public string? OverlayIconName { get; set; } + public (int, int, byte[])[]? OverlayIconPixmap { get; set; } + public string? AttentionIconName { get; set; } + public (int, int, byte[])[]? AttentionIconPixmap { get; set; } + public string? AttentionMovieName { get; set; } + public (string, (int, int, byte[])[], string, string) ToolTip { get; set; } } - - public static DbusPixmap EmptyPixmap = new DbusPixmap(1, 1, new byte[] { 255, 0, 0, 0 }); } } diff --git a/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs b/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs new file mode 100644 index 0000000000..a0ca8abc97 --- /dev/null +++ b/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs @@ -0,0 +1,937 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Tmds.DBus.Protocol; + +namespace Avalonia.FreeDesktop +{ + internal record NotificationProperties + { + public uint Version { get; set; } + } + + internal class Notification : DesktopObject + { + private const string Interface = "org.freedesktop.portal.Notification"; + + public Notification(DesktopService service, ObjectPath path) : base(service, path) { } + + public Task AddNotificationAsync(string id, Dictionary notification) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "sa{sv}", + member: "AddNotification"); + writer.WriteString(id); + writer.WriteDictionary(notification); + return writer.CreateMessage(); + } + } + + public Task RemoveNotificationAsync(string id) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "RemoveNotification"); + writer.WriteString(id); + return writer.CreateMessage(); + } + } + + public ValueTask WatchActionInvokedAsync(Action handler, + bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "ActionInvoked", + (m, s) => ReadMessage_ssav(m, (DesktopObject)s!), handler, emitOnCapturedContext); + + public Task SetVersionAsync(uint value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("version"); + writer.WriteSignature("u"); + writer.WriteUInt32(value); + return writer.CreateMessage(); + } + } + + public Task GetVersionAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), + (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + + public Task GetPropertiesAsync() + { + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), + (m, s) => ReadMessage(m, (DesktopObject)s!), this); + + static NotificationProperties ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + + public ValueTask WatchPropertiesChangedAsync(Action> handler, + bool emitOnCapturedContext = true) + { + return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, + emitOnCapturedContext); + + static PropertyChanges ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), + ReadInvalidated(ref reader)); + } + + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(headersEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "version": + invalidated.Add("Version"); + break; + } + } + + return invalidated?.ToArray() ?? Array.Empty(); + } + } + + private static NotificationProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new NotificationProperties(); + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(headersEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "version": + reader.ReadSignature("u"); + props.Version = reader.ReadUInt32(); + changedList?.Add("Version"); + break; + default: + reader.ReadVariant(); + break; + } + } + + return props; + } + } + + internal record OpenURIProperties + { + public uint Version { get; set; } + } + + internal class OpenURI : DesktopObject + { + private const string Interface = "org.freedesktop.portal.OpenURI"; + + public OpenURI(DesktopService service, ObjectPath path) : base(service, path) { } + + public Task OpenUriAsync(string parentWindow, string uri, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "ssa{sv}", + member: "OpenURI"); + writer.WriteString(parentWindow); + writer.WriteString(uri); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task OpenFileAsync(string parentWindow, SafeHandle fd, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "sha{sv}", + member: "OpenFile"); + writer.WriteString(parentWindow); + writer.WriteHandle(fd); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task OpenDirectoryAsync(string parentWindow, SafeHandle fd, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "sha{sv}", + member: "OpenDirectory"); + writer.WriteString(parentWindow); + writer.WriteHandle(fd); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task SetVersionAsync(uint value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("version"); + writer.WriteSignature("u"); + writer.WriteUInt32(value); + return writer.CreateMessage(); + } + } + + public Task GetVersionAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), + (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + + public Task GetPropertiesAsync() + { + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), + (m, s) => ReadMessage(m, (DesktopObject)s!), this); + + static OpenURIProperties ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + + public ValueTask WatchPropertiesChangedAsync(Action> handler, + bool emitOnCapturedContext = true) + { + return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, + emitOnCapturedContext); + + static PropertyChanges ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); + } + + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(headersEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "version": + invalidated.Add("Version"); + break; + } + } + + return invalidated?.ToArray() ?? Array.Empty(); + } + } + + private static OpenURIProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new OpenURIProperties(); + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(headersEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "version": + reader.ReadSignature("u"); + props.Version = reader.ReadUInt32(); + changedList?.Add("Version"); + break; + default: + reader.ReadVariant(); + break; + } + } + + return props; + } + } + + internal record DynamicLauncherProperties + { + public uint SupportedLauncherTypes { get; set; } + public uint Version { get; set; } + } + + internal class DynamicLauncher : DesktopObject + { + private const string Interface = "org.freedesktop.portal.DynamicLauncher"; + + public DynamicLauncher(DesktopService service, ObjectPath path) : base(service, path) { } + + public Task InstallAsync(string token, string desktopFileId, string desktopEntry, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "sssa{sv}", + member: "Install"); + writer.WriteString(token); + writer.WriteString(desktopFileId); + writer.WriteString(desktopEntry); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task PrepareInstallAsync(string parentWindow, string name, object iconV, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "ssva{sv}", + member: "PrepareInstall"); + writer.WriteString(parentWindow); + writer.WriteString(name); + writer.WriteVariant(iconV); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task RequestInstallTokenAsync(string name, object iconV, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_s(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "sva{sv}", + member: "RequestInstallToken"); + writer.WriteString(name); + writer.WriteVariant(iconV); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task UninstallAsync(string desktopFileId, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "sa{sv}", + member: "Uninstall"); + writer.WriteString(desktopFileId); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task GetDesktopEntryAsync(string desktopFileId) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_s(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetDesktopEntry"); + writer.WriteString(desktopFileId); + return writer.CreateMessage(); + } + } + + public Task<(object IconV, string IconFormat, uint IconSize)> GetIconAsync(string desktopFileId) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_vsu(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "GetIcon"); + writer.WriteString(desktopFileId); + return writer.CreateMessage(); + } + } + + public Task LaunchAsync(string desktopFileId, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "sa{sv}", + member: "Launch"); + writer.WriteString(desktopFileId); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task SetSupportedLauncherTypesAsync(uint value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("SupportedLauncherTypes"); + writer.WriteSignature("u"); + writer.WriteUInt32(value); + return writer.CreateMessage(); + } + } + + public Task SetVersionAsync(uint value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("version"); + writer.WriteSignature("u"); + writer.WriteUInt32(value); + return writer.CreateMessage(); + } + } + + public Task GetSupportedLauncherTypesAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "SupportedLauncherTypes"), + (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + + public Task GetVersionAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), + (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + + public Task GetPropertiesAsync() + { + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), + (m, s) => ReadMessage(m, (DesktopObject)s!), this); + + static DynamicLauncherProperties ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + + public ValueTask WatchPropertiesChangedAsync(Action> handler, + bool emitOnCapturedContext = true) + { + return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, + emitOnCapturedContext); + + static PropertyChanges ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), + ReadInvalidated(ref reader)); + } + + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(headersEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "SupportedLauncherTypes": + invalidated.Add("SupportedLauncherTypes"); + break; + case "version": + invalidated.Add("Version"); + break; + } + } + + return invalidated?.ToArray() ?? Array.Empty(); + } + } + + private static DynamicLauncherProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new DynamicLauncherProperties(); + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(headersEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "SupportedLauncherTypes": + reader.ReadSignature("u"); + props.SupportedLauncherTypes = reader.ReadUInt32(); + changedList?.Add("SupportedLauncherTypes"); + break; + case "version": + reader.ReadSignature("u"); + props.Version = reader.ReadUInt32(); + changedList?.Add("Version"); + break; + default: + reader.ReadVariant(); + break; + } + } + + return props; + } + } + + internal record FileChooserProperties + { + public uint Version { get; set; } + } + + internal class FileChooser : DesktopObject + { + private const string Interface = "org.freedesktop.portal.FileChooser"; + + public FileChooser(DesktopService service, ObjectPath path) : base(service, path) { } + + public Task OpenFileAsync(string parentWindow, string title, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "ssa{sv}", + member: "OpenFile"); + writer.WriteString(parentWindow); + writer.WriteString(title); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task SaveFileAsync(string parentWindow, string title, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "ssa{sv}", + member: "SaveFile"); + writer.WriteString(parentWindow); + writer.WriteString(title); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task SaveFilesAsync(string parentWindow, string title, Dictionary options) + { + return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "ssa{sv}", + member: "SaveFiles"); + writer.WriteString(parentWindow); + writer.WriteString(title); + writer.WriteDictionary(options); + return writer.CreateMessage(); + } + } + + public Task SetVersionAsync(uint value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("version"); + writer.WriteSignature("u"); + writer.WriteUInt32(value); + return writer.CreateMessage(); + } + } + + public Task GetVersionAsync() + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), + (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + + public Task GetPropertiesAsync() + { + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), + (m, s) => ReadMessage(m, (DesktopObject)s!), this); + + static FileChooserProperties ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + + public ValueTask WatchPropertiesChangedAsync(Action> handler, + bool emitOnCapturedContext = true) + { + return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, + emitOnCapturedContext); + + static PropertyChanges ReadMessage(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), + ReadInvalidated(ref reader)); + } + + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(headersEnd)) + { + invalidated ??= new(); + var property = reader.ReadString(); + switch (property) + { + case "version": + invalidated.Add("Version"); + break; + } + } + + return invalidated?.ToArray() ?? Array.Empty(); + } + } + + private static FileChooserProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new FileChooserProperties(); + ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(headersEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "version": + reader.ReadSignature("u"); + props.Version = reader.ReadUInt32(); + changedList?.Add("Version"); + break; + default: + reader.ReadVariant(); + break; + } + } + + return props; + } + } + + internal class Request : DesktopObject + { + private const string Interface = "org.freedesktop.portal.Request"; + + public Request(DesktopService service, ObjectPath path) : base(service, path) { } + + public Task CloseAsync() + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + "Close"); + return writer.CreateMessage(); + } + } + + public ValueTask WatchResponseAsync(Action results)> handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "Response", + static (m, s) => ReadMessage_uaesv(m, (DesktopObject)s!), handler, emitOnCapturedContext); + } + + internal class DesktopService + { + public DesktopService(Connection connection, string destination) + => (Connection, Destination) = (connection, destination); + + public Connection Connection { get; } + public string Destination { get; } + public Notification CreateNotification(string path) => new(this, path); + public OpenURI CreateOpenUri(string path) => new(this, path); + public DynamicLauncher CreateDynamicLauncher(string path) => new(this, path); + public FileChooser CreateFileChooser(string path) => new(this, path); + public Request CreateRequest(string path) => new(this, path); + } + + internal class DesktopObject + { + protected DesktopObject(DesktopService service, ObjectPath path) + => (Service, Path) = (service, path); + + public DesktopService Service { get; } + public ObjectPath Path { get; } + protected Connection Connection => Service.Connection; + + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + + protected ValueTask WatchPropertiesChangedAsync(string @interface, + MessageValueReader> reader, Action> handler, + bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return Connection.AddMatchAsync(rule, reader, + (ex, changes, rs, hs) => + ((Action>)hs!).Invoke(ex, changes), + this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, + MessageValueReader reader, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, reader, + (ex, arg, rs, hs) => ((Action)hs!).Invoke(ex, arg), + this, handler, emitOnCapturedContext); + } + + protected static ObjectPath ReadMessage_o(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadObjectPath(); + } + + protected static uint ReadMessage_v_u(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("u"); + return reader.ReadUInt32(); + } + + protected static (string, string, object[]) ReadMessage_ssav(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadString(); + var arg1 = reader.ReadString(); + var arg2 = reader.ReadArray(); + return (arg0, arg1, arg2); + } + + protected static (uint, Dictionary) ReadMessage_uaesv(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadUInt32(); + var arg1 = reader.ReadDictionary(); + return (arg0, arg1); + } + + protected static string ReadMessage_s(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadString(); + } + + protected static (object, string, uint) ReadMessage_vsu(Message message, DesktopObject _) + { + var reader = message.GetBodyReader(); + var arg0 = reader.ReadVariant(); + var arg1 = reader.ReadString(); + var arg2 = reader.ReadUInt32(); + return (arg0, arg1, arg2); + } + } + + internal class PropertyChanges + { + public PropertyChanges(TProperties properties, string[] invalidated, string[] changed) + => (Properties, Invalidated, Changed) = (properties, invalidated, changed); + + public TProperties Properties { get; } + public string[] Invalidated { get; } + public string[] Changed { get; } + public bool HasChanged(string property) => Array.IndexOf(Changed, property) != -1; + public bool IsInvalidated(string property) => Array.IndexOf(Invalidated, property) != -1; + } +} diff --git a/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs b/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs new file mode 100644 index 0000000000..5cd44905af --- /dev/null +++ b/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs @@ -0,0 +1,351 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Tmds.DBus.Protocol; + +namespace Avalonia.FreeDesktop +{ + internal record StatusNotifierWatcherProperties + { + public string[] RegisteredStatusNotifierItems { get; set; } = default!; + public bool IsStatusNotifierHostRegistered { get; set; } + public int ProtocolVersion { get; set; } + } + + internal class StatusNotifierWatcher : StatusNotifierWatcherObject + { + private const string Interface = "org.kde.StatusNotifierWatcher"; + + public StatusNotifierWatcher(StatusNotifierWatcherService service, ObjectPath path) : base(service, path) { } + + public Task RegisterStatusNotifierItemAsync(string service) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "RegisterStatusNotifierItem"); + writer.WriteString(service); + return writer.CreateMessage(); + } + } + + public Task RegisterStatusNotifierHostAsync(string service) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + Interface, + signature: "s", + member: "RegisterStatusNotifierHost"); + writer.WriteString(service); + return writer.CreateMessage(); + } + } + + public ValueTask WatchStatusNotifierItemRegisteredAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierItemRegistered", static (m, s) => + ReadMessage_s(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchStatusNotifierItemUnregisteredAsync(Action handler, bool emitOnCapturedContext = true) + => WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierItemUnregistered", static (m, s) + => ReadMessage_s(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext); + + public ValueTask WatchStatusNotifierHostRegisteredAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierHostRegistered", handler, emitOnCapturedContext); + + public ValueTask WatchStatusNotifierHostUnregisteredAsync(Action handler, bool emitOnCapturedContext = true) => + WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierHostUnregistered", handler, emitOnCapturedContext); + + public Task SetRegisteredStatusNotifierItemsAsync(string[] value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("RegisteredStatusNotifierItems"); + writer.WriteSignature("as"); + writer.WriteArray(value); + return writer.CreateMessage(); + } + } + + public Task SetIsStatusNotifierHostRegisteredAsync(bool value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("IsStatusNotifierHostRegistered"); + writer.WriteSignature("b"); + writer.WriteBool(value); + return writer.CreateMessage(); + } + } + + public Task SetProtocolVersionAsync(int value) + { + return Connection.CallMethodAsync(CreateMessage()); + + MessageBuffer CreateMessage() + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ssv", + member: "Set"); + writer.WriteString(Interface); + writer.WriteString("ProtocolVersion"); + writer.WriteSignature("i"); + writer.WriteInt32(value); + return writer.CreateMessage(); + } + } + + public Task GetRegisteredStatusNotifierItemsAsync() => + Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "RegisteredStatusNotifierItems"), static (m, s) + => ReadMessage_v_as(m, (StatusNotifierWatcherObject)s!), this); + + public Task GetIsStatusNotifierHostRegisteredAsync() => + Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IsStatusNotifierHostRegistered"), static (m, s) => + ReadMessage_v_b(m, (StatusNotifierWatcherObject)s!), this); + + public Task GetProtocolVersionAsync() => + Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "ProtocolVersion"), static (m, s) + => ReadMessage_v_i(m, (StatusNotifierWatcherObject)s!), this); + + public Task GetPropertiesAsync() + { + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, s) + => ReadMessage(m, (StatusNotifierWatcherObject)s!), this); + + static StatusNotifierWatcherProperties ReadMessage(Message message, StatusNotifierWatcherObject _) + { + var reader = message.GetBodyReader(); + return ReadProperties(ref reader); + } + } + + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) + { + return base.WatchPropertiesChangedAsync(Interface, static (m, s) => ReadMessage(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext); + + static PropertyChanges ReadMessage(Message message, StatusNotifierWatcherObject _) + { + var reader = message.GetBodyReader(); + reader.ReadString(); // interface + List changed = new(); + return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), + ReadInvalidated(ref reader)); + } + + static string[] ReadInvalidated(ref Reader reader) + { + List? invalidated = null; + var headersEnd = reader.ReadArrayStart(DBusType.String); + while (reader.HasNext(headersEnd)) + { + invalidated ??= new List(); + var property = reader.ReadString(); + switch (property) + { + case "RegisteredStatusNotifierItems": + invalidated.Add("RegisteredStatusNotifierItems"); + break; + case "IsStatusNotifierHostRegistered": + invalidated.Add("IsStatusNotifierHostRegistered"); + break; + case "ProtocolVersion": + invalidated.Add("ProtocolVersion"); + break; + } + } + + return invalidated?.ToArray() ?? Array.Empty(); + } + } + + private static StatusNotifierWatcherProperties ReadProperties(ref Reader reader, List? changedList = null) + { + var props = new StatusNotifierWatcherProperties(); + var headersEnd = reader.ReadArrayStart(DBusType.Struct); + while (reader.HasNext(headersEnd)) + { + var property = reader.ReadString(); + switch (property) + { + case "RegisteredStatusNotifierItems": + reader.ReadSignature("as"); + props.RegisteredStatusNotifierItems = reader.ReadArray(); + changedList?.Add("RegisteredStatusNotifierItems"); + break; + case "IsStatusNotifierHostRegistered": + reader.ReadSignature("b"); + props.IsStatusNotifierHostRegistered = reader.ReadBool(); + changedList?.Add("IsStatusNotifierHostRegistered"); + break; + case "ProtocolVersion": + reader.ReadSignature("i"); + props.ProtocolVersion = reader.ReadInt32(); + changedList?.Add("ProtocolVersion"); + break; + default: + reader.ReadVariant(); + break; + } + } + + return props; + } + } + + internal class StatusNotifierWatcherService + { + public StatusNotifierWatcherService(Connection connection, string destination) => (Connection, Destination) = (connection, destination); + + public Connection Connection { get; } + public string Destination { get; } + + public StatusNotifierWatcher CreateStatusNotifierWatcher(string path) => new(this, path); + } + + internal class StatusNotifierWatcherObject + { + protected StatusNotifierWatcherObject(StatusNotifierWatcherService service, ObjectPath path) + { + (Service, Path) = (service, path); + } + + public StatusNotifierWatcherService Service { get; } + public ObjectPath Path { get; } + protected Connection Connection => Service.Connection; + + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "ss", + member: "Get"); + writer.WriteString(@interface); + writer.WriteString(property); + return writer.CreateMessage(); + } + + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) + { + using var writer = Connection.GetMessageWriter(); + writer.WriteMethodCallHeader( + Service.Destination, + Path, + "org.freedesktop.DBus.Properties", + signature: "s", + member: "GetAll"); + writer.WriteString(@interface); + return writer.CreateMessage(); + } + + protected ValueTask WatchPropertiesChangedAsync(string @interface, MessageValueReader> reader, Action> handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = Service.Destination, + Path = Path, + Interface = "org.freedesktop.DBus.Properties", + Member = "PropertiesChanged", + Arg0 = @interface + }; + return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => + ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, reader, + (ex, arg, _, hs) => ((Action)hs!).Invoke(ex, arg), + this, handler, emitOnCapturedContext); + } + + public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) + { + var rule = new MatchRule + { + Type = MessageType.Signal, + Sender = sender, + Path = path, + Member = signal, + Interface = @interface + }; + return Connection.AddMatchAsync(rule, static (_, _) + => null!, static (Exception? ex, object _, object? _, object? hs) + => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + } + + protected static string ReadMessage_s(Message message, StatusNotifierWatcherObject _) + { + var reader = message.GetBodyReader(); + return reader.ReadString(); + } + + protected static string[] ReadMessage_v_as(Message message, StatusNotifierWatcherObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("as"); + return reader.ReadArray(); + } + + protected static bool ReadMessage_v_b(Message message, StatusNotifierWatcherObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("b"); + return reader.ReadBool(); + } + + protected static int ReadMessage_v_i(Message message, StatusNotifierWatcherObject _) + { + var reader = message.GetBodyReader(); + reader.ReadSignature("i"); + return reader.ReadInt32(); + } + } +} diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs index 810a806c8a..e0d6cf3e55 100644 --- a/src/Avalonia.X11/X11Window.cs +++ b/src/Avalonia.X11/X11Window.cs @@ -212,10 +212,10 @@ namespace Avalonia.X11 _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1); } - StorageProvider = new CompositeStorageProvider(new Func>[] + StorageProvider = new CompositeStorageProvider(new[] { - () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreate(Handle) : Task.FromResult(null), - () => GtkSystemDialog.TryCreate(this), + () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreateAsync(Handle) : Task.FromResult(null), + () => GtkSystemDialog.TryCreate(this) }); } diff --git a/src/Linux/Tmds.DBus b/src/Linux/Tmds.DBus new file mode 160000 index 0000000000..bfca94ab05 --- /dev/null +++ b/src/Linux/Tmds.DBus @@ -0,0 +1 @@ +Subproject commit bfca94ab052683c7ccef51e4a036098f539cc676 From d5df9298bd2642492654b56505180da89c4faf6e Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:34:10 +0100 Subject: [PATCH 002/155] Fix warnings --- .../AppMenuRegistrar.DBus.cs | 25 ++- src/Avalonia.FreeDesktop/DBus.DBus.cs | 14 +- src/Avalonia.FreeDesktop/DBusCallQueue.cs | 13 +- .../DBusIme/Fcitx/FcitxEnums.cs | 64 ++++---- .../DBusIme/IBus/IBusEnums.cs | 2 +- src/Avalonia.FreeDesktop/DBusMenu.DBus.cs | 131 ++++++++++------ src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 3 +- src/Avalonia.FreeDesktop/DBusSystemDialog.cs | 18 +-- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 32 ++-- .../FreeDesktopPortalDesktop.DBus.cs | 143 +++++++++--------- src/Avalonia.FreeDesktop/IX11InputMethod.cs | 4 +- src/Avalonia.FreeDesktop/NativeMethods.cs | 8 +- .../StatusNotifierWatcher.DBus.cs | 25 ++- 13 files changed, 258 insertions(+), 224 deletions(-) diff --git a/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs b/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs index a45d1642fc..75c8ce61a5 100644 --- a/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs +++ b/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs @@ -1,9 +1,7 @@ using System; using System.Threading.Tasks; - using Tmds.DBus.Protocol; - namespace Avalonia.FreeDesktop { internal class Registrar : AppMenuRegistrarObject @@ -52,7 +50,7 @@ namespace Avalonia.FreeDesktop public Task<(string Service, ObjectPath MenuObjectPath)> GetMenuForWindowAsync(uint windowId) { - return Connection.CallMethodAsync(CreateMessage(), (Message m, object? s) => ReadMessage_so(m, (AppMenuRegistrarObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_so(m, (AppMenuRegistrarObject)s!), this); MessageBuffer CreateMessage() { @@ -76,7 +74,7 @@ namespace Avalonia.FreeDesktop public Connection Connection { get; } public string Destination { get; } - public Registrar CreateRegistrar(string path) => new Registrar(this, path); + public Registrar CreateRegistrar(string path) => new(this, path); } internal class AppMenuRegistrarObject @@ -84,8 +82,8 @@ namespace Avalonia.FreeDesktop protected AppMenuRegistrarObject(RegistrarService service, ObjectPath path) => (Service, Path) = (service, path); - public RegistrarService Service { get; } - public ObjectPath Path { get; } + protected RegistrarService Service { get; } + protected ObjectPath Path { get; } protected Connection Connection => Service.Connection; protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) @@ -128,9 +126,8 @@ namespace Avalonia.FreeDesktop Member = "PropertiesChanged", Arg0 = @interface }; - return Connection.AddMatchAsync(rule, reader, - (Exception? ex, PropertyChanges changes, object? rs, object? hs) => - ((Action>)hs!).Invoke(ex, changes), + return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) + => ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); } @@ -145,9 +142,8 @@ namespace Avalonia.FreeDesktop Member = signal, Interface = @interface }; - return Connection.AddMatchAsync(rule, reader, - (Exception? ex, TArg arg, object? rs, object? hs) => ((Action)hs!).Invoke(ex, arg), - this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => + ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); } public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, @@ -161,8 +157,9 @@ namespace Avalonia.FreeDesktop Member = signal, Interface = @interface }; - return Connection.AddMatchAsync(rule, (Message message, object? state) => null!, - (Exception? ex, object v, object? rs, object? hs) => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, static (_, _) + => null!, static (ex, _, _, hs) + => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); } protected static (string, ObjectPath) ReadMessage_so(Message message, AppMenuRegistrarObject _) diff --git a/src/Avalonia.FreeDesktop/DBus.DBus.cs b/src/Avalonia.FreeDesktop/DBus.DBus.cs index 4cf2b76b45..0f83ddaa4a 100644 --- a/src/Avalonia.FreeDesktop/DBus.DBus.cs +++ b/src/Avalonia.FreeDesktop/DBus.DBus.cs @@ -1,10 +1,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; - using Tmds.DBus.Protocol; - namespace Avalonia.FreeDesktop { internal record DBusProperties @@ -417,10 +415,10 @@ namespace Avalonia.FreeDesktop public Task GetPropertiesAsync() { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, s) => - ReadMessage(m, (DBusObject)s!), this); + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) => + ReadMessage(m), this); - static DBusProperties ReadMessage(Message message, DBusObject _) + static DBusProperties ReadMessage(Message message) { var reader = message.GetBodyReader(); return ReadProperties(ref reader); @@ -429,10 +427,10 @@ namespace Avalonia.FreeDesktop public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) { - return base.WatchPropertiesChangedAsync(Interface, static (m, s) => - ReadMessage(m, (DBusObject)s!), handler, emitOnCapturedContext); + return base.WatchPropertiesChangedAsync(Interface, static (m, _) => + ReadMessage(m), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message, DBusObject _) + static PropertyChanges ReadMessage(Message message) { var reader = message.GetBodyReader(); reader.ReadString(); // interface diff --git a/src/Avalonia.FreeDesktop/DBusCallQueue.cs b/src/Avalonia.FreeDesktop/DBusCallQueue.cs index e7c07dcbf9..b853626d45 100644 --- a/src/Avalonia.FreeDesktop/DBusCallQueue.cs +++ b/src/Avalonia.FreeDesktop/DBusCallQueue.cs @@ -7,19 +7,20 @@ namespace Avalonia.FreeDesktop class DBusCallQueue { private readonly Func _errorHandler; + private readonly Queue _q = new(); - record Item(Func Callback) + private bool _processing; + + private record Item(Func Callback) { public Action? OnFinish; } - private Queue _q = new Queue(); - private bool _processing; public DBusCallQueue(Func errorHandler) { _errorHandler = errorHandler; } - + public void Enqueue(Func cb) { _q.Enqueue(new Item(cb)); @@ -42,7 +43,7 @@ namespace Avalonia.FreeDesktop Process(); return tcs.Task; } - + public Task EnqueueAsync(Func> cb) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -62,7 +63,7 @@ namespace Avalonia.FreeDesktop return tcs.Task; } - async void Process() + private async void Process() { if(_processing) return; diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs index 6510a5877a..fafc66b6b9 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs @@ -2,45 +2,45 @@ using System; namespace Avalonia.FreeDesktop.DBusIme.Fcitx { - enum FcitxKeyEventType + internal enum FcitxKeyEventType { FCITX_PRESS_KEY, FCITX_RELEASE_KEY - }; - + } + [Flags] - enum FcitxCapabilityFlags + internal enum FcitxCapabilityFlags { CAPACITY_NONE = 0, - CAPACITY_CLIENT_SIDE_UI = (1 << 0), - CAPACITY_PREEDIT = (1 << 1), - CAPACITY_CLIENT_SIDE_CONTROL_STATE = (1 << 2), - CAPACITY_PASSWORD = (1 << 3), - CAPACITY_FORMATTED_PREEDIT = (1 << 4), - CAPACITY_CLIENT_UNFOCUS_COMMIT = (1 << 5), - CAPACITY_SURROUNDING_TEXT = (1 << 6), - CAPACITY_EMAIL = (1 << 7), - CAPACITY_DIGIT = (1 << 8), - CAPACITY_UPPERCASE = (1 << 9), - CAPACITY_LOWERCASE = (1 << 10), - CAPACITY_NOAUTOUPPERCASE = (1 << 11), - CAPACITY_URL = (1 << 12), - CAPACITY_DIALABLE = (1 << 13), - CAPACITY_NUMBER = (1 << 14), - CAPACITY_NO_ON_SCREEN_KEYBOARD = (1 << 15), - CAPACITY_SPELLCHECK = (1 << 16), - CAPACITY_NO_SPELLCHECK = (1 << 17), - CAPACITY_WORD_COMPLETION = (1 << 18), - CAPACITY_UPPERCASE_WORDS = (1 << 19), - CAPACITY_UPPERCASE_SENTENCES = (1 << 20), - CAPACITY_ALPHA = (1 << 21), - CAPACITY_NAME = (1 << 22), - CAPACITY_GET_IM_INFO_ON_FOCUS = (1 << 23), - CAPACITY_RELATIVE_CURSOR_RECT = (1 << 24), - }; + CAPACITY_CLIENT_SIDE_UI = 1 << 0, + CAPACITY_PREEDIT = 1 << 1, + CAPACITY_CLIENT_SIDE_CONTROL_STATE = 1 << 2, + CAPACITY_PASSWORD = 1 << 3, + CAPACITY_FORMATTED_PREEDIT = 1 << 4, + CAPACITY_CLIENT_UNFOCUS_COMMIT = 1 << 5, + CAPACITY_SURROUNDING_TEXT = 1 << 6, + CAPACITY_EMAIL = 1 << 7, + CAPACITY_DIGIT = 1 << 8, + CAPACITY_UPPERCASE = 1 << 9, + CAPACITY_LOWERCASE = 1 << 10, + CAPACITY_NOAUTOUPPERCASE = 1 << 11, + CAPACITY_URL = 1 << 12, + CAPACITY_DIALABLE = 1 << 13, + CAPACITY_NUMBER = 1 << 14, + CAPACITY_NO_ON_SCREEN_KEYBOARD = 1 << 15, + CAPACITY_SPELLCHECK = 1 << 16, + CAPACITY_NO_SPELLCHECK = 1 << 17, + CAPACITY_WORD_COMPLETION = 1 << 18, + CAPACITY_UPPERCASE_WORDS = 1 << 19, + CAPACITY_UPPERCASE_SENTENCES = 1 << 20, + CAPACITY_ALPHA = 1 << 21, + CAPACITY_NAME = 1 << 22, + CAPACITY_GET_IM_INFO_ON_FOCUS = 1 << 23, + CAPACITY_RELATIVE_CURSOR_RECT = 1 << 24 + } [Flags] - enum FcitxKeyState + internal enum FcitxKeyState { FcitxKeyState_None = 0, FcitxKeyState_Shift = 1 << 0, @@ -63,5 +63,5 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx FcitxKeyState_Hyper = 1 << 27, FcitxKeyState_Meta = 1 << 28, FcitxKeyState_UsedMask = 0x5c001fff - }; + } } diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs index b8ac816694..1b430f9c90 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs @@ -40,6 +40,6 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus CapLookupTable = 1 << 2, CapFocus = 1 << 3, CapProperty = 1 << 4, - CapSurroundingText = 1 << 5, + CapSurroundingText = 1 << 5 } } diff --git a/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs b/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs index d9016d8644..e3928cfbb2 100644 --- a/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs +++ b/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs @@ -16,11 +16,12 @@ namespace Avalonia.FreeDesktop internal class DBusMenu : DBusMenuObject { private const string Interface = "com.canonical.dbusmenu"; - public DBusMenu(DBusMenuService service, ObjectPath path) : base(service, path) - { } + + public DBusMenu(DBusMenuService service, ObjectPath path) : base(service, path) { } + public Task<(uint Revision, (int, Dictionary, object[]) Layout)> GetLayoutAsync(int parentId, int recursionDepth, string[] propertyNames) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_uriaesvavz(m, (DBusMenuObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_uriaesvavz(m), this); MessageBuffer CreateMessage() { using var writer = Connection.GetMessageWriter(); @@ -36,9 +37,10 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task<(int, Dictionary)[]> GetGroupPropertiesAsync(int[] ids, string[] propertyNames) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_ariaesvz(m, (DBusMenuObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_ariaesvz(m), this); MessageBuffer CreateMessage() { using var writer = Connection.GetMessageWriter(); @@ -53,9 +55,10 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task GetPropertyAsync(int id, string name) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_v(m, (DBusMenuObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_v(m), this); MessageBuffer CreateMessage() { using var writer = Connection.GetMessageWriter(); @@ -70,6 +73,7 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task EventAsync(int id, string eventId, object data, uint timestamp) { return Connection.CallMethodAsync(CreateMessage()); @@ -89,9 +93,10 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task EventGroupAsync((int, string, object, uint)[] events) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_ai(m, (DBusMenuObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_ai(m), this); MessageBuffer CreateMessage() { using var writer = Connection.GetMessageWriter(); @@ -105,9 +110,10 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task AboutToShowAsync(int id) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_b(m, (DBusMenuObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_b(m), this); MessageBuffer CreateMessage() { using var writer = Connection.GetMessageWriter(); @@ -121,9 +127,10 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task<(int[] UpdatesNeeded, int[] IdErrors)> AboutToShowGroupAsync(int[] ids) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_aiai(m, (DBusMenuObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_aiai(m), this); MessageBuffer CreateMessage() { using var writer = Connection.GetMessageWriter(); @@ -137,12 +144,16 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public ValueTask WatchItemsPropertiesUpdatedAsync(Action)[] UpdatedProps, (int, string[])[] RemovedProps)> handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ItemsPropertiesUpdated", (m, s) => ReadMessage_ariaesvzariasz(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); + => WatchSignalAsync(Service.Destination, Interface, Path, "ItemsPropertiesUpdated", static (m, _) => ReadMessage_ariaesvzariasz(m), handler, emitOnCapturedContext); + public ValueTask WatchLayoutUpdatedAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "LayoutUpdated", (m, s) => ReadMessage_ui(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); + => WatchSignalAsync(Service.Destination, Interface, Path, "LayoutUpdated", static (m, _) => ReadMessage_ui(m), handler, emitOnCapturedContext); + public ValueTask WatchItemActivationRequestedAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ItemActivationRequested", (m, s) => ReadMessage_iu(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); + => WatchSignalAsync(Service.Destination, Interface, Path, "ItemActivationRequested", static (m, _) => ReadMessage_iu(m), handler, emitOnCapturedContext); + public Task SetVersionAsync(uint value) { return Connection.CallMethodAsync(CreateMessage()); @@ -162,6 +173,7 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task SetTextDirectionAsync(string value) { return Connection.CallMethodAsync(CreateMessage()); @@ -181,6 +193,7 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task SetStatusAsync(string value) { return Connection.CallMethodAsync(CreateMessage()); @@ -200,6 +213,7 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task SetIconThemePathAsync(string[] value) { return Connection.CallMethodAsync(CreateMessage()); @@ -219,40 +233,46 @@ namespace Avalonia.FreeDesktop return writer.CreateMessage(); } } + public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Version"), (m, s) => ReadMessage_v_u(m, (DBusMenuObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Version"), static (m, _) => ReadMessage_v_u(m), this); + public Task GetTextDirectionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "TextDirection"), (m, s) => ReadMessage_v_s(m, (DBusMenuObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "TextDirection"), static (m, _) => ReadMessage_v_s(m), this); + public Task GetStatusAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Status"), (m, s) => ReadMessage_v_s(m, (DBusMenuObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Status"), static (m, _) => ReadMessage_v_s(m), this); + public Task GetIconThemePathAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IconThemePath"), (m, s) => ReadMessage_v_as(m, (DBusMenuObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IconThemePath"), static (m, _) => ReadMessage_v_as(m), this); + public Task GetPropertiesAsync() { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), (m, s) => ReadMessage(m, (DBusMenuObject)s!), this); - static DBusMenuProperties ReadMessage(Message message, DBusMenuObject _) + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) => ReadMessage(m), this); + static DBusMenuProperties ReadMessage(Message message) { var reader = message.GetBodyReader(); return ReadProperties(ref reader); } } + public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) { - return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DBusMenuObject)s!), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message, DBusMenuObject _) + return base.WatchPropertiesChangedAsync(Interface, static (m, _) => ReadMessage(m), handler, emitOnCapturedContext); + static PropertyChanges ReadMessage(Message message) { var reader = message.GetBodyReader(); reader.ReadString(); // interface - List changed = new(), invalidated = new(); + List changed = new(); return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); } static string[] ReadInvalidated(ref Reader reader) { List? invalidated = null; - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + var headersEnd = reader.ReadArrayStart(DBusType.String); while (reader.HasNext(headersEnd)) { - invalidated ??= new(); + invalidated ??= new List(); var property = reader.ReadString(); switch (property) { @@ -265,10 +285,11 @@ namespace Avalonia.FreeDesktop return invalidated?.ToArray() ?? Array.Empty(); } } - private static DBusMenuProperties ReadProperties(ref Reader reader, List? changedList = null) + + private static DBusMenuProperties ReadProperties(ref Reader reader, ICollection? changedList = null) { var props = new DBusMenuProperties(); - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + var headersEnd = reader.ReadArrayStart(DBusType.Struct); while (reader.HasNext(headersEnd)) { var property = reader.ReadString(); @@ -309,7 +330,7 @@ namespace Avalonia.FreeDesktop public string Destination { get; } public DBusMenuService(Connection connection, string destination) => (Connection, Destination) = (connection, destination); - public DBusMenu CreateDbusmenu(string path) => new DBusMenu(this, path); + public DBusMenu CreateDbusmenu(string path) => new(this, path); } internal class DBusMenuObject @@ -319,6 +340,7 @@ namespace Avalonia.FreeDesktop protected Connection Connection => Service.Connection; protected DBusMenuObject(DBusMenuService service, ObjectPath path) => (Service, Path) = (service, path); + protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) { using var writer = Connection.GetMessageWriter(); @@ -332,6 +354,7 @@ namespace Avalonia.FreeDesktop writer.WriteString(property); return writer.CreateMessage(); } + protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) { using var writer = Connection.GetMessageWriter(); @@ -344,6 +367,7 @@ namespace Avalonia.FreeDesktop writer.WriteString(@interface); return writer.CreateMessage(); } + protected ValueTask WatchPropertiesChangedAsync(string @interface, MessageValueReader> reader, Action> handler, bool emitOnCapturedContext) { var rule = new MatchRule @@ -355,11 +379,11 @@ namespace Avalonia.FreeDesktop Member = "PropertiesChanged", Arg0 = @interface }; - return Connection.AddMatchAsync(rule, reader, - (ex, changes, rs, hs) => ((Action>)hs!).Invoke(ex, changes), - this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) + => ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); } - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) + + protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) { var rule = new MatchRule { @@ -369,11 +393,11 @@ namespace Avalonia.FreeDesktop Member = signal, Interface = @interface }; - return Connection.AddMatchAsync(rule, reader, - (ex, arg, rs, hs) => ((Action)hs!).Invoke(ex, arg), - this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) + => ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); } - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) + + protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) { var rule = new MatchRule { @@ -383,77 +407,90 @@ namespace Avalonia.FreeDesktop Member = signal, Interface = @interface }; - return Connection.AddMatchAsync(rule, (message, state) => null!, - (ex, v, rs, hs) => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, static (_, _) + => null!, static (ex, _, _, hs) + => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); } - protected static (uint, (int, Dictionary, object[])) ReadMessage_uriaesvavz(Message message, DBusMenuObject _) + + protected static (uint, (int, Dictionary, object[])) ReadMessage_uriaesvavz(Message message) { var reader = message.GetBodyReader(); var arg0 = reader.ReadUInt32(); var arg1 = reader.ReadStruct, object[]>(); return (arg0, arg1); } - protected static (int, Dictionary)[] ReadMessage_ariaesvz(Message message, DBusMenuObject _) + + protected static (int, Dictionary)[] ReadMessage_ariaesvz(Message message) { var reader = message.GetBodyReader(); return reader.ReadArray<(int, Dictionary)>(); } - protected static object ReadMessage_v(Message message, DBusMenuObject _) + + protected static object ReadMessage_v(Message message) { var reader = message.GetBodyReader(); return reader.ReadVariant(); } - protected static int[] ReadMessage_ai(Message message, DBusMenuObject _) + + protected static int[] ReadMessage_ai(Message message) { var reader = message.GetBodyReader(); return reader.ReadArray(); } - protected static bool ReadMessage_b(Message message, DBusMenuObject _) + + protected static bool ReadMessage_b(Message message) { var reader = message.GetBodyReader(); return reader.ReadBool(); } - protected static (int[], int[]) ReadMessage_aiai(Message message, DBusMenuObject _) + + protected static (int[], int[]) ReadMessage_aiai(Message message) { var reader = message.GetBodyReader(); var arg0 = reader.ReadArray(); var arg1 = reader.ReadArray(); return (arg0, arg1); } - protected static ((int, Dictionary)[], (int, string[])[]) ReadMessage_ariaesvzariasz(Message message, DBusMenuObject _) + + protected static ((int, Dictionary)[], (int, string[])[]) ReadMessage_ariaesvzariasz(Message message) { var reader = message.GetBodyReader(); var arg0 = reader.ReadArray<(int, Dictionary)>(); var arg1 = reader.ReadArray<(int, string[])>(); return (arg0, arg1); } - protected static (uint, int) ReadMessage_ui(Message message, DBusMenuObject _) + + protected static (uint, int) ReadMessage_ui(Message message) { var reader = message.GetBodyReader(); var arg0 = reader.ReadUInt32(); var arg1 = reader.ReadInt32(); return (arg0, arg1); } - protected static (int, uint) ReadMessage_iu(Message message, DBusMenuObject _) + + protected static (int, uint) ReadMessage_iu(Message message) { var reader = message.GetBodyReader(); var arg0 = reader.ReadInt32(); var arg1 = reader.ReadUInt32(); return (arg0, arg1); } - protected static uint ReadMessage_v_u(Message message, DBusMenuObject _) + + protected static uint ReadMessage_v_u(Message message) { var reader = message.GetBodyReader(); reader.ReadSignature("u"); return reader.ReadUInt32(); } - protected static string ReadMessage_v_s(Message message, DBusMenuObject _) + + protected static string ReadMessage_v_s(Message message) { var reader = message.GetBodyReader(); reader.ReadSignature("s"); return reader.ReadString(); } - protected static string[] ReadMessage_v_as(Message message, DBusMenuObject _) + + protected static string[] ReadMessage_v_as(Message message) { var reader = message.GetBodyReader(); reader.ReadSignature("as"); diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 682434bc8d..69e2eed72f 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -96,8 +96,7 @@ namespace Avalonia.FreeDesktop public void SetNativeMenu(NativeMenu? menu) { - if (menu is null) - menu = new NativeMenu(); + menu ??= new NativeMenu(); if (_menu is not null) ((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged; diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index d59af3b5dd..7d1a0bbfc4 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -18,9 +18,9 @@ namespace Avalonia.FreeDesktop if (DBusHelper.Connection is null) return null; var services = await DBusHelper.Connection.ListServicesAsync(); - if (!services.Contains("org.freedesktop.portal.Desktop", StringComparer.Ordinal)) - return null; - return new DBusSystemDialog(new DesktopService(DBusHelper.Connection, "org.freedesktop.portal.Desktop"), handle); + return services.Contains("org.freedesktop.portal.Desktop", StringComparer.Ordinal) + ? new DBusSystemDialog(new DesktopService(DBusHelper.Connection, "org.freedesktop.portal.Desktop"), handle) + : null; } private readonly DesktopService _desktopService; @@ -140,15 +140,15 @@ namespace Avalonia.FreeDesktop var filters = new List<(string name, (uint style, string extension)[])>(); foreach (var fileType in fileTypes) { - const uint globStyle = 0u; - const uint mimeStyle = 1u; + const uint GlobStyle = 0u; + const uint MimeStyle = 1u; var extensions = Enumerable.Empty<(uint, string)>(); - if (fileType.Patterns is { } patterns) - extensions = extensions.Concat(patterns.Select(static x => (globStyle, x))); - else if (fileType.MimeTypes is { } mimeTypes) - extensions = extensions.Concat(mimeTypes.Select(static x => (mimeStyle, x))); + if (fileType.Patterns is not null) + extensions = extensions.Concat(fileType.Patterns.Select(static x => (globStyle: GlobStyle, x))); + else if (fileType.MimeTypes is not null) + extensions = extensions.Concat(fileType.MimeTypes.Select(static x => (mimeStyle: MimeStyle, x))); if (extensions.Any()) filters.Add((fileType.Name, extensions.ToArray())); } diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index ed041cb2ca..d6518f7246 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -72,7 +72,7 @@ namespace Avalonia.FreeDesktop if (!services.Contains("org.kde.StatusNotifierWatcher", StringComparer.Ordinal)) return; - _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((e, x) => { OnNameChange(x.A2); }); + _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.A2) ); var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher"); OnNameChange(nameOwner); } @@ -128,7 +128,7 @@ namespace Avalonia.FreeDesktop if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierItemDbusObj is null || _sysTrayServiceName is null) return; - _dBus.ReleaseNameAsync(_sysTrayServiceName); + _dBus!.ReleaseNameAsync(_sysTrayServiceName); } public void Dispose() @@ -147,7 +147,7 @@ namespace Avalonia.FreeDesktop if (icon is null) { - _statusNotifierItemDbusObj?.SetIcon((1, 1, new byte[] { 255, 0, 0, 0 })); + _statusNotifierItemDbusObj?.SetIcon(StatusNotifierItemDbusObj.StatusNotifierItemProperties.EmptyPixmap); return; } @@ -238,7 +238,7 @@ namespace Avalonia.FreeDesktop EmitVoidSignal("NewAttentionIcon"); EmitVoidSignal("NewOverlayIcon"); EmitVoidSignal("NewToolTip"); - EmitStringSignal("NewStatus", _backingProperties.Status ?? string.Empty); + EmitStringSignal("NewStatus", _backingProperties.Status); } public void SetIcon((int, int, byte[]) dbusPixmap) @@ -287,7 +287,7 @@ namespace Avalonia.FreeDesktop case ("Get", "ss"): { var reader = context.Request.GetBodyReader(); - var interfaceName = reader.ReadString(); + _ = reader.ReadString(); var member = reader.ReadString(); switch (member) { @@ -349,10 +349,10 @@ namespace Avalonia.FreeDesktop var writer = context.CreateReplyWriter("a{sv}"); var dict = new Dictionary { - { "Category", _backingProperties.Category ?? string.Empty }, - { "Id", _backingProperties.Id ?? string.Empty }, - { "Title", _backingProperties.Title ?? string.Empty }, - { "Status", _backingProperties.Status ?? string.Empty }, + { "Category", _backingProperties.Category }, + { "Id", _backingProperties.Id }, + { "Title", _backingProperties.Title }, + { "Status", _backingProperties.Status }, { "Menu", _backingProperties.Menu }, { "IconPixmap", _backingProperties.IconPixmap }, { "ToolTip", _backingProperties.ToolTip } @@ -385,24 +385,26 @@ namespace Avalonia.FreeDesktop _connection.TrySendMessage(writer.CreateMessage()); } - private record StatusNotifierItemProperties + internal record StatusNotifierItemProperties { - public string? Category { get; set; } - public string? Id { get; set; } - public string? Title { get; set; } - public string? Status { get; set; } + public string Category { get; set; } = string.Empty; + public string Id { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; + public string Status { get; set; } = string.Empty; public int WindowId { get; set; } public string? IconThemePath { get; set; } public ObjectPath Menu { get; set; } public bool ItemIsMenu { get; set; } public string? IconName { get; set; } - public (int, int, byte[])[]? IconPixmap { get; set; } + public (int, int, byte[])[] IconPixmap { get; set; } = { EmptyPixmap }; public string? OverlayIconName { get; set; } public (int, int, byte[])[]? OverlayIconPixmap { get; set; } public string? AttentionIconName { get; set; } public (int, int, byte[])[]? AttentionIconPixmap { get; set; } public string? AttentionMovieName { get; set; } public (string, (int, int, byte[])[], string, string) ToolTip { get; set; } + + public static (int, int, byte[]) EmptyPixmap = (1, 1, new byte[] { 255, 0, 0, 0 }); } } } diff --git a/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs b/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs index a0ca8abc97..23f1dcf0e7 100644 --- a/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs +++ b/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs @@ -56,8 +56,8 @@ namespace Avalonia.FreeDesktop public ValueTask WatchActionInvokedAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ActionInvoked", - (m, s) => ReadMessage_ssav(m, (DesktopObject)s!), handler, emitOnCapturedContext); + => WatchSignalAsync(Service.Destination, Interface, Path, "ActionInvoked", static (m, s) + => ReadMessage_ssav(m, (DesktopObject)s!), handler, emitOnCapturedContext); public Task SetVersionAsync(uint value) { @@ -81,15 +81,15 @@ namespace Avalonia.FreeDesktop } public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), - (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) + => ReadMessage_v_u(m, (DesktopObject)s!), this); public Task GetPropertiesAsync() { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), - (m, s) => ReadMessage(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) + => ReadMessage(m), this); - static NotificationProperties ReadMessage(Message message, DesktopObject _) + static NotificationProperties ReadMessage(Message message) { var reader = message.GetBodyReader(); return ReadProperties(ref reader); @@ -99,10 +99,10 @@ namespace Avalonia.FreeDesktop public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) { - return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, - emitOnCapturedContext); + return base.WatchPropertiesChangedAsync(Interface, static (m, _) + => ReadMessage(m), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message, DesktopObject _) + static PropertyChanges ReadMessage(Message message) { var reader = message.GetBodyReader(); reader.ReadString(); // interface @@ -114,10 +114,10 @@ namespace Avalonia.FreeDesktop static string[] ReadInvalidated(ref Reader reader) { List? invalidated = null; - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + var headersEnd = reader.ReadArrayStart(DBusType.String); while (reader.HasNext(headersEnd)) { - invalidated ??= new(); + invalidated ??= new List(); var property = reader.ReadString(); switch (property) { @@ -131,10 +131,10 @@ namespace Avalonia.FreeDesktop } } - private static NotificationProperties ReadProperties(ref Reader reader, List? changedList = null) + private static NotificationProperties ReadProperties(ref Reader reader, ICollection? changedList = null) { var props = new NotificationProperties(); - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + var headersEnd = reader.ReadArrayStart(DBusType.Struct); while (reader.HasNext(headersEnd)) { var property = reader.ReadString(); @@ -168,7 +168,7 @@ namespace Avalonia.FreeDesktop public Task OpenUriAsync(string parentWindow, string uri, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -188,7 +188,7 @@ namespace Avalonia.FreeDesktop public Task OpenFileAsync(string parentWindow, SafeHandle fd, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -208,7 +208,7 @@ namespace Avalonia.FreeDesktop public Task OpenDirectoryAsync(string parentWindow, SafeHandle fd, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -248,15 +248,15 @@ namespace Avalonia.FreeDesktop } public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), - (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) + => ReadMessage_v_u(m, (DesktopObject)s!), this); public Task GetPropertiesAsync() { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), - (m, s) => ReadMessage(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) + => ReadMessage(m), this); - static OpenURIProperties ReadMessage(Message message, DesktopObject _) + static OpenURIProperties ReadMessage(Message message) { var reader = message.GetBodyReader(); return ReadProperties(ref reader); @@ -266,10 +266,10 @@ namespace Avalonia.FreeDesktop public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) { - return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, - emitOnCapturedContext); + return base.WatchPropertiesChangedAsync(Interface, static (m, _) + => ReadMessage(m), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message, DesktopObject _) + static PropertyChanges ReadMessage(Message message) { var reader = message.GetBodyReader(); reader.ReadString(); // interface @@ -280,10 +280,10 @@ namespace Avalonia.FreeDesktop static string[] ReadInvalidated(ref Reader reader) { List? invalidated = null; - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + var headersEnd = reader.ReadArrayStart(DBusType.String); while (reader.HasNext(headersEnd)) { - invalidated ??= new(); + invalidated ??= new List(); var property = reader.ReadString(); switch (property) { @@ -297,10 +297,10 @@ namespace Avalonia.FreeDesktop } } - private static OpenURIProperties ReadProperties(ref Reader reader, List? changedList = null) + private static OpenURIProperties ReadProperties(ref Reader reader, ICollection? changedList = null) { var props = new OpenURIProperties(); - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + var headersEnd = reader.ReadArrayStart(DBusType.Struct); while (reader.HasNext(headersEnd)) { var property = reader.ReadString(); @@ -356,7 +356,7 @@ namespace Avalonia.FreeDesktop public Task PrepareInstallAsync(string parentWindow, string name, object iconV, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -377,7 +377,8 @@ namespace Avalonia.FreeDesktop public Task RequestInstallTokenAsync(string name, object iconV, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_s(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) + => ReadMessage_s(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -416,7 +417,8 @@ namespace Avalonia.FreeDesktop public Task GetDesktopEntryAsync(string desktopFileId) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_s(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) + => ReadMessage_s(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -434,7 +436,8 @@ namespace Avalonia.FreeDesktop public Task<(object IconV, string IconFormat, uint IconSize)> GetIconAsync(string desktopFileId) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_vsu(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) + => ReadMessage_vsu(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -512,19 +515,19 @@ namespace Avalonia.FreeDesktop } public Task GetSupportedLauncherTypesAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "SupportedLauncherTypes"), - (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "SupportedLauncherTypes"), static (m, s) + => ReadMessage_v_u(m, (DesktopObject)s!), this); public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), - (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) + => ReadMessage_v_u(m, (DesktopObject)s!), this); public Task GetPropertiesAsync() { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), - (m, s) => ReadMessage(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) + => ReadMessage(m), this); - static DynamicLauncherProperties ReadMessage(Message message, DesktopObject _) + static DynamicLauncherProperties ReadMessage(Message message) { var reader = message.GetBodyReader(); return ReadProperties(ref reader); @@ -534,10 +537,11 @@ namespace Avalonia.FreeDesktop public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) { - return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, + return base.WatchPropertiesChangedAsync(Interface, static (m, _) + => ReadMessage(m), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message, DesktopObject _) + static PropertyChanges ReadMessage(Message message) { var reader = message.GetBodyReader(); reader.ReadString(); // interface @@ -549,10 +553,10 @@ namespace Avalonia.FreeDesktop static string[] ReadInvalidated(ref Reader reader) { List? invalidated = null; - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + var headersEnd = reader.ReadArrayStart(DBusType.String); while (reader.HasNext(headersEnd)) { - invalidated ??= new(); + invalidated ??= new List(); var property = reader.ReadString(); switch (property) { @@ -569,10 +573,10 @@ namespace Avalonia.FreeDesktop } } - private static DynamicLauncherProperties ReadProperties(ref Reader reader, List? changedList = null) + private static DynamicLauncherProperties ReadProperties(ref Reader reader, ICollection? changedList = null) { var props = new DynamicLauncherProperties(); - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + var headersEnd = reader.ReadArrayStart(DBusType.Struct); while (reader.HasNext(headersEnd)) { var property = reader.ReadString(); @@ -611,7 +615,7 @@ namespace Avalonia.FreeDesktop public Task OpenFileAsync(string parentWindow, string title, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -631,7 +635,7 @@ namespace Avalonia.FreeDesktop public Task SaveFileAsync(string parentWindow, string title, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -651,7 +655,7 @@ namespace Avalonia.FreeDesktop public Task SaveFilesAsync(string parentWindow, string title, Dictionary options) { - return Connection.CallMethodAsync(CreateMessage(), (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); MessageBuffer CreateMessage() { @@ -691,15 +695,15 @@ namespace Avalonia.FreeDesktop } public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), - (m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this); + => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) + => ReadMessage_v_u(m, (DesktopObject)s!), this); public Task GetPropertiesAsync() { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), - (m, s) => ReadMessage(m, (DesktopObject)s!), this); + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) + => ReadMessage(m), this); - static FileChooserProperties ReadMessage(Message message, DesktopObject _) + static FileChooserProperties ReadMessage(Message message) { var reader = message.GetBodyReader(); return ReadProperties(ref reader); @@ -709,10 +713,10 @@ namespace Avalonia.FreeDesktop public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) { - return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler, - emitOnCapturedContext); + return base.WatchPropertiesChangedAsync(Interface, static (m, _) + => ReadMessage(m), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message, DesktopObject _) + static PropertyChanges ReadMessage(Message message) { var reader = message.GetBodyReader(); reader.ReadString(); // interface @@ -724,10 +728,10 @@ namespace Avalonia.FreeDesktop static string[] ReadInvalidated(ref Reader reader) { List? invalidated = null; - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.String); + var headersEnd = reader.ReadArrayStart(DBusType.String); while (reader.HasNext(headersEnd)) { - invalidated ??= new(); + invalidated ??= new List(); var property = reader.ReadString(); switch (property) { @@ -741,10 +745,10 @@ namespace Avalonia.FreeDesktop } } - private static FileChooserProperties ReadProperties(ref Reader reader, List? changedList = null) + private static FileChooserProperties ReadProperties(ref Reader reader, ICollection? changedList = null) { var props = new FileChooserProperties(); - ArrayEnd headersEnd = reader.ReadArrayStart(DBusType.Struct); + var headersEnd = reader.ReadArrayStart(DBusType.Struct); while (reader.HasNext(headersEnd)) { var property = reader.ReadString(); @@ -811,8 +815,8 @@ namespace Avalonia.FreeDesktop protected DesktopObject(DesktopService service, ObjectPath path) => (Service, Path) = (service, path); - public DesktopService Service { get; } - public ObjectPath Path { get; } + protected DesktopService Service { get; } + protected ObjectPath Path { get; } protected Connection Connection => Service.Connection; protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) @@ -855,13 +859,11 @@ namespace Avalonia.FreeDesktop Member = "PropertiesChanged", Arg0 = @interface }; - return Connection.AddMatchAsync(rule, reader, - (ex, changes, rs, hs) => - ((Action>)hs!).Invoke(ex, changes), - this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) + => ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); } - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, + protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) { var rule = new MatchRule @@ -872,9 +874,8 @@ namespace Avalonia.FreeDesktop Member = signal, Interface = @interface }; - return Connection.AddMatchAsync(rule, reader, - (ex, arg, rs, hs) => ((Action)hs!).Invoke(ex, arg), - this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => + ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); } protected static ObjectPath ReadMessage_o(Message message, DesktopObject _) diff --git a/src/Avalonia.FreeDesktop/IX11InputMethod.cs b/src/Avalonia.FreeDesktop/IX11InputMethod.cs index 9fa9c1809e..6ae63ea322 100644 --- a/src/Avalonia.FreeDesktop/IX11InputMethod.cs +++ b/src/Avalonia.FreeDesktop/IX11InputMethod.cs @@ -19,7 +19,7 @@ namespace Avalonia.FreeDesktop public KeyModifiers Modifiers { get; set; } public RawKeyEventType Type { get; set; } } - + public interface IX11InputMethodControl : IDisposable { void SetWindowActive(bool active); @@ -27,7 +27,7 @@ namespace Avalonia.FreeDesktop ValueTask HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode); event Action Commit; event Action ForwardKey; - + void UpdateWindowInfo(PixelPoint position, double scaling); } } diff --git a/src/Avalonia.FreeDesktop/NativeMethods.cs b/src/Avalonia.FreeDesktop/NativeMethods.cs index 147955b6a3..df22f46323 100644 --- a/src/Avalonia.FreeDesktop/NativeMethods.cs +++ b/src/Avalonia.FreeDesktop/NativeMethods.cs @@ -15,18 +15,18 @@ namespace Avalonia.FreeDesktop public static string ReadLink(string path) { var symlinkSize = Encoding.UTF8.GetByteCount(path); - var bufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated + const int BufferSize = 4097; // PATH_MAX is (usually?) 4096, but we need to know if the result was truncated var symlink = ArrayPool.Shared.Rent(symlinkSize + 1); - var buffer = ArrayPool.Shared.Rent(bufferSize); + var buffer = ArrayPool.Shared.Rent(BufferSize); try { Encoding.UTF8.GetBytes(path, 0, path.Length, symlink, 0); symlink[symlinkSize] = 0; - var size = readlink(symlink, buffer, bufferSize); - Debug.Assert(size < bufferSize); // if this fails, we need to increase the buffer size (dynamically?) + var size = readlink(symlink, buffer, BufferSize); + Debug.Assert(size < BufferSize); // if this fails, we need to increase the buffer size (dynamically?) return Encoding.UTF8.GetString(buffer, 0, (int)size); } diff --git a/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs b/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs index 5cd44905af..4e6ce3bd43 100644 --- a/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs +++ b/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs @@ -145,10 +145,10 @@ namespace Avalonia.FreeDesktop public Task GetPropertiesAsync() { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, s) - => ReadMessage(m, (StatusNotifierWatcherObject)s!), this); + return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) + => ReadMessage(m), this); - static StatusNotifierWatcherProperties ReadMessage(Message message, StatusNotifierWatcherObject _) + static StatusNotifierWatcherProperties ReadMessage(Message message) { var reader = message.GetBodyReader(); return ReadProperties(ref reader); @@ -157,9 +157,9 @@ namespace Avalonia.FreeDesktop public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) { - return base.WatchPropertiesChangedAsync(Interface, static (m, s) => ReadMessage(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext); + return base.WatchPropertiesChangedAsync(Interface, static (m, _) => ReadMessage(m), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message, StatusNotifierWatcherObject _) + static PropertyChanges ReadMessage(Message message) { var reader = message.GetBodyReader(); reader.ReadString(); // interface @@ -194,7 +194,7 @@ namespace Avalonia.FreeDesktop } } - private static StatusNotifierWatcherProperties ReadProperties(ref Reader reader, List? changedList = null) + private static StatusNotifierWatcherProperties ReadProperties(ref Reader reader, ICollection? changedList = null) { var props = new StatusNotifierWatcherProperties(); var headersEnd = reader.ReadArrayStart(DBusType.Struct); @@ -245,8 +245,8 @@ namespace Avalonia.FreeDesktop (Service, Path) = (service, path); } - public StatusNotifierWatcherService Service { get; } - public ObjectPath Path { get; } + protected StatusNotifierWatcherService Service { get; } + protected ObjectPath Path { get; } protected Connection Connection => Service.Connection; protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) @@ -291,7 +291,7 @@ namespace Avalonia.FreeDesktop ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); } - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) + protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) { var rule = new MatchRule { @@ -301,12 +301,11 @@ namespace Avalonia.FreeDesktop Member = signal, Interface = @interface }; - return Connection.AddMatchAsync(rule, reader, - (ex, arg, _, hs) => ((Action)hs!).Invoke(ex, arg), - this, handler, emitOnCapturedContext); + return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) + => ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); } - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) + protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) { var rule = new MatchRule { From e5985aa675221931c25cbc27b168b9fac3a254a4 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:34:31 +0100 Subject: [PATCH 003/155] Import TrimmingEnable.props --- src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index a33fa7b32d..e80a90e38a 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -5,6 +5,9 @@ enable + + + @@ -15,7 +18,9 @@ + + From 2c2eed724eb6e821b41d0d61134737bdb4525068 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Fri, 6 Jan 2023 16:26:36 +0100 Subject: [PATCH 004/155] Use source generator --- .gitmodules | 3 + Avalonia.Desktop.slnf | 1 + Avalonia.sln | 7 + .../AppMenuRegistrar.DBus.cs | 173 ---- .../Avalonia.FreeDesktop.csproj | 5 + src/Avalonia.FreeDesktop/DBus.DBus.cs | 641 ------------ .../DBusIme/DBusTextInputMethodBase.cs | 2 +- .../DBusIme/Fcitx/Fcitx.DBus.cs | 627 ------------ .../DBusIme/Fcitx/FcitxICWrapper.cs | 10 +- .../DBusIme/Fcitx/FcitxX11TextInputMethod.cs | 9 +- .../DBusIme/IBus/IBus.DBus.cs | 513 ---------- .../DBusIme/IBus/IBusX11TextInputMethod.cs | 12 +- src/Avalonia.FreeDesktop/DBusInterfaces.cs | 18 + src/Avalonia.FreeDesktop/DBusMenu.DBus.cs | 500 ---------- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 178 +--- src/Avalonia.FreeDesktop/DBusSystemDialog.cs | 18 +- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 190 +--- .../FreeDesktopPortalDesktop.DBus.cs | 938 ------------------ .../StatusNotifierWatcher.DBus.cs | 350 ------- src/tools/Tmds.DBus.SourceGenerator | 1 + 20 files changed, 130 insertions(+), 4066 deletions(-) delete mode 100644 src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/DBus.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs create mode 100644 src/Avalonia.FreeDesktop/DBusInterfaces.cs delete mode 100644 src/Avalonia.FreeDesktop/DBusMenu.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs delete mode 100644 src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs create mode 160000 src/tools/Tmds.DBus.SourceGenerator diff --git a/.gitmodules b/.gitmodules index 16bc977251..2b899e8f5a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "Tmds.DBus"] path = src/Linux/Tmds.DBus url = https://github.com/affederaffe/Tmds.DBus +[submodule "src/tools/Tmds.DBus.SourceGenerator"] + path = src/tools/Tmds.DBus.SourceGenerator + url = https://github.com/affederaffe/Tmds.DBus.SourceGenerator diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf index 72eb13d0a9..165245781b 100644 --- a/Avalonia.Desktop.slnf +++ b/Avalonia.Desktop.slnf @@ -38,6 +38,7 @@ "src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj", "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj", "src\\tools\\DevGenerators\\DevGenerators.csproj", + "src\\tools\\Tmds.DBus.SourceGenerator\\Tmds.DBus.SourceGenerator\\Tmds.DBus.SourceGenerator.csproj", "src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj", "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj", "src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj", diff --git a/Avalonia.sln b/Avalonia.sln index e399483896..47ed2b9140 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -233,6 +233,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\R EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.Protocol", "src\Linux\Tmds.DBus\src\Tmds.DBus.Protocol\Tmds.DBus.Protocol.csproj", "{29E25263-3CC3-4D55-A042-00BA136867D4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tmds.DBus.SourceGenerator", "src\tools\Tmds.DBus.SourceGenerator\Tmds.DBus.SourceGenerator\Tmds.DBus.SourceGenerator.csproj", "{5E9C0032-E460-4BC1-BCC7-6448F34DD679}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -548,6 +550,10 @@ Global {29E25263-3CC3-4D55-A042-00BA136867D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {29E25263-3CC3-4D55-A042-00BA136867D4}.Release|Any CPU.ActiveCfg = Release|Any CPU {29E25263-3CC3-4D55-A042-00BA136867D4}.Release|Any CPU.Build.0 = Release|Any CPU + {5E9C0032-E460-4BC1-BCC7-6448F34DD679}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E9C0032-E460-4BC1-BCC7-6448F34DD679}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E9C0032-E460-4BC1-BCC7-6448F34DD679}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E9C0032-E460-4BC1-BCC7-6448F34DD679}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -613,6 +619,7 @@ Global {90B08091-9BBD-4362-B712-E9F2CC62B218} = {9B9E3891-2366-4253-A952-D08BCEB71098} {75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098} {29E25263-3CC3-4D55-A042-00BA136867D4} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B} + {5E9C0032-E460-4BC1-BCC7-6448F34DD679} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} diff --git a/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs b/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs deleted file mode 100644 index 75c8ce61a5..0000000000 --- a/src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System; -using System.Threading.Tasks; -using Tmds.DBus.Protocol; - -namespace Avalonia.FreeDesktop -{ - internal class Registrar : AppMenuRegistrarObject - { - private const string Interface = "com.canonical.AppMenu.Registrar"; - - public Registrar(RegistrarService service, ObjectPath path) : base(service, path) { } - - public Task RegisterWindowAsync(uint windowId, ObjectPath menuObjectPath) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "uo", - member: "RegisterWindow", - flags: MessageFlags.NoReplyExpected); - writer.WriteUInt32(windowId); - writer.WriteObjectPath(menuObjectPath); - return writer.CreateMessage(); - } - } - - public Task UnregisterWindowAsync(uint windowId) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "u", - member: "UnregisterWindow"); - writer.WriteUInt32(windowId); - return writer.CreateMessage(); - } - } - - public Task<(string Service, ObjectPath MenuObjectPath)> GetMenuForWindowAsync(uint windowId) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_so(m, (AppMenuRegistrarObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "u", - member: "GetMenuForWindow"); - writer.WriteUInt32(windowId); - return writer.CreateMessage(); - } - } - } - - internal class RegistrarService - { - public RegistrarService(Connection connection, string destination) - => (Connection, Destination) = (connection, destination); - - public Connection Connection { get; } - public string Destination { get; } - public Registrar CreateRegistrar(string path) => new(this, path); - } - - internal class AppMenuRegistrarObject - { - protected AppMenuRegistrarObject(RegistrarService service, ObjectPath path) - => (Service, Path) = (service, path); - - protected RegistrarService Service { get; } - protected ObjectPath Path { get; } - protected Connection Connection => Service.Connection; - - protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ss", - member: "Get"); - writer.WriteString(@interface); - writer.WriteString(property); - return writer.CreateMessage(); - } - - protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "s", - member: "GetAll"); - writer.WriteString(@interface); - return writer.CreateMessage(); - } - - protected ValueTask WatchPropertiesChangedAsync(string @interface, - MessageValueReader> reader, Action> handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = Service.Destination, - Path = Path, - Interface = "org.freedesktop.DBus.Properties", - Member = "PropertiesChanged", - Arg0 = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) - => ((Action>)hs!).Invoke(ex, changes), - this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, - MessageValueReader reader, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => - ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, static (_, _) - => null!, static (ex, _, _, hs) - => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); - } - - protected static (string, ObjectPath) ReadMessage_so(Message message, AppMenuRegistrarObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadString(); - var arg1 = reader.ReadObjectPath(); - return (arg0, arg1); - } - } -} diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index e80a90e38a..e1e0a636ea 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -17,10 +17,15 @@ + + + + + diff --git a/src/Avalonia.FreeDesktop/DBus.DBus.cs b/src/Avalonia.FreeDesktop/DBus.DBus.cs deleted file mode 100644 index 0f83ddaa4a..0000000000 --- a/src/Avalonia.FreeDesktop/DBus.DBus.cs +++ /dev/null @@ -1,641 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Tmds.DBus.Protocol; - -namespace Avalonia.FreeDesktop -{ - internal record DBusProperties - { - public string[] Features { get; set; } = default!; - public string[] Interfaces { get; set; } = default!; - } - - internal class DBus : DBusObject - { - private const string Interface = "org.freedesktop.DBus"; - - public DBus(DBusService service, ObjectPath path) : base(service, path) { } - - public Task HelloAsync() - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_s(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "Hello"); - return writer.CreateMessage(); - } - } - - public Task RequestNameAsync(string a0, uint a1) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "su", - member: "RequestName"); - writer.WriteString(a0); - writer.WriteUInt32(a1); - return writer.CreateMessage(); - } - } - - public Task ReleaseNameAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "ReleaseName"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task StartServiceByNameAsync(string a0, uint a1) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "su", - member: "StartServiceByName"); - writer.WriteString(a0); - writer.WriteUInt32(a1); - return writer.CreateMessage(); - } - } - - public Task UpdateActivationEnvironmentAsync(Dictionary a0) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "a{ss}", - member: "UpdateActivationEnvironment"); - writer.WriteDictionary(a0); - return writer.CreateMessage(); - } - } - - public Task NameHasOwnerAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_b(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "NameHasOwner"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task ListNamesAsync() - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_as(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "ListNames"); - return writer.CreateMessage(); - } - } - - public Task ListActivatableNamesAsync() - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_as(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "ListActivatableNames"); - return writer.CreateMessage(); - } - } - - public Task AddMatchAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "AddMatch"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task RemoveMatchAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "RemoveMatch"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task GetNameOwnerAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_s(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetNameOwner"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task ListQueuedOwnersAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_as(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "ListQueuedOwners"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task GetConnectionUnixUserAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetConnectionUnixUser"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task GetConnectionUnixProcessIDAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_u(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetConnectionUnixProcessID"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task GetAdtAuditSessionDataAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_ay(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetAdtAuditSessionData"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task GetConnectionSELinuxSecurityContextAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_ay(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetConnectionSELinuxSecurityContext"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public Task ReloadConfigAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "ReloadConfig"); - return writer.CreateMessage(); - } - } - - public Task GetIdAsync() - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_s(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "GetId"); - return writer.CreateMessage(); - } - } - - public Task> GetConnectionCredentialsAsync(string a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_aesv(m, (DBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetConnectionCredentials"); - writer.WriteString(a0); - return writer.CreateMessage(); - } - } - - public ValueTask WatchNameOwnerChangedAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "NameOwnerChanged", static (m, s) => - ReadMessage_sss(m, (DBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchNameLostAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "NameLost", static (m, s) => - ReadMessage_s(m, (DBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchNameAcquiredAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "NameAcquired", static (m, s) => - ReadMessage_s(m, (DBusObject)s!), handler, emitOnCapturedContext); - - public Task SetFeaturesAsync(string[] value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("Features"); - writer.WriteSignature("as"); - writer.WriteArray(value); - return writer.CreateMessage(); - } - } - - public Task SetInterfacesAsync(string[] value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("Interfaces"); - writer.WriteSignature("as"); - writer.WriteArray(value); - return writer.CreateMessage(); - } - } - - public Task GetFeaturesAsync() => - Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Features"), static (m, s) => - ReadMessage_v_as(m, (DBusObject)s!), this); - - public Task GetInterfacesAsync() => - Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Interfaces"), static (m, s) => - ReadMessage_v_as(m, (DBusObject)s!), this); - - public Task GetPropertiesAsync() - { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) => - ReadMessage(m), this); - - static DBusProperties ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - return ReadProperties(ref reader); - } - } - - public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) - { - return base.WatchPropertiesChangedAsync(Interface, static (m, _) => - ReadMessage(m), handler, emitOnCapturedContext); - - static PropertyChanges ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadString(); // interface - List changed = new(); - return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); - } - - static string[] ReadInvalidated(ref Reader reader) - { - List? invalidated = null; - var headersEnd = reader.ReadArrayStart(DBusType.String); - while (reader.HasNext(headersEnd)) - { - invalidated ??= new List(); - var property = reader.ReadString(); - switch (property) - { - case "Features": - invalidated.Add("Features"); - break; - case "Interfaces": - invalidated.Add("Interfaces"); - break; - } - } - - return invalidated?.ToArray() ?? Array.Empty(); - } - } - - private static DBusProperties ReadProperties(ref Reader reader, List? changedList = null) - { - var props = new DBusProperties(); - var headersEnd = reader.ReadArrayStart(DBusType.Struct); - while (reader.HasNext(headersEnd)) - { - var property = reader.ReadString(); - switch (property) - { - case "Features": - reader.ReadSignature("as"); - props.Features = reader.ReadArray(); - changedList?.Add("Features"); - break; - case "Interfaces": - reader.ReadSignature("as"); - props.Interfaces = reader.ReadArray(); - changedList?.Add("Interfaces"); - break; - default: - reader.ReadVariant(); - break; - } - } - - return props; - } - } - - internal class DBusService - { - public DBusService(Connection connection, string destination) - => (Connection, Destination) = (connection, destination); - - public Connection Connection { get; } - public string Destination { get; } - public DBus CreateDBus(string path) => new(this, path); - } - - internal class DBusObject - { - protected DBusObject(DBusService service, ObjectPath path) - { - Service = service; - Path = path; - } - - public DBusService Service { get; } - public ObjectPath Path { get; } - protected Connection Connection => Service.Connection; - - protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ss", - member: "Get"); - writer.WriteString(@interface); - writer.WriteString(property); - return writer.CreateMessage(); - } - - protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "s", - member: "GetAll"); - writer.WriteString(@interface); - return writer.CreateMessage(); - } - - protected ValueTask WatchPropertiesChangedAsync(string @interface, - MessageValueReader> reader, Action> handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = Service.Destination, - Path = Path, - Interface = "org.freedesktop.DBus.Properties", - Member = "PropertiesChanged", - Arg0 = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => - ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, - MessageValueReader reader, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => - ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, static (_, _) => null!, static (ex, _, _, hs) => - ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); - } - - protected static string ReadMessage_s(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadString(); - } - - protected static uint ReadMessage_u(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadUInt32(); - } - - protected static bool ReadMessage_b(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadBool(); - } - - protected static string[] ReadMessage_as(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadArray(); - } - - protected static byte[] ReadMessage_ay(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadArray(); - } - - protected static Dictionary ReadMessage_aesv(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadDictionary(); - } - - protected static (string, string, string) ReadMessage_sss(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadString(); - var arg1 = reader.ReadString(); - var arg2 = reader.ReadString(); - return (arg0, arg1, arg2); - } - - protected static string[] ReadMessage_v_as(Message message, DBusObject _) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("as"); - return reader.ReadArray(); - } - } -} diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs index c7bf530cfd..08d2a0c219 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs @@ -60,7 +60,7 @@ namespace Avalonia.FreeDesktop.DBusIme { foreach (var name in _knownNames) { - var dbus = new DBusService(Connection, name).CreateDBus("/org/freedesktop/DBus"); + var dbus = new OrgFreedesktopDBus(Connection, name, "/org/freedesktop/DBus"); _disposables.Add(await dbus.WatchNameOwnerChangedAsync(OnNameChange)); var nameOwner = await dbus.GetNameOwnerAsync(name); OnNameChange(null, (name, null, nameOwner)); diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs deleted file mode 100644 index f9e4497892..0000000000 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs +++ /dev/null @@ -1,627 +0,0 @@ -using System; -using System.Threading.Tasks; -using Tmds.DBus.Protocol; - -namespace Avalonia.FreeDesktop.DBusIme.Fcitx -{ - internal class InputContext : FcitxObject - { - private const string Interface = "org.fcitx.Fcitx.InputContext"; - - public InputContext(FcitxService service, ObjectPath path) : base(service, path) { } - - public Task FocusInAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "FocusIn"); - return writer.CreateMessage(); - } - } - - public Task FocusOutAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "FocusOut"); - return writer.CreateMessage(); - } - } - - public Task ResetAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "Reset"); - return writer.CreateMessage(); - } - } - - public Task SetCursorRectAsync(int x, int y, int w, int h) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "iiii", - member: "SetCursorRect"); - writer.WriteInt32(x); - writer.WriteInt32(y); - writer.WriteInt32(w); - writer.WriteInt32(h); - return writer.CreateMessage(); - } - } - - public Task SetCapacityAsync(uint caps) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "u", - member: "SetCapacity"); - writer.WriteUInt32(caps); - return writer.CreateMessage(); - } - } - - public Task SetSurroundingTextAsync(string text, uint cursor, uint anchor) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "suu", - member: "SetSurroundingText"); - writer.WriteString(text); - writer.WriteUInt32(cursor); - writer.WriteUInt32(anchor); - return writer.CreateMessage(); - } - } - - public Task SetSurroundingTextPositionAsync(uint cursor, uint anchor) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "uu", - member: "SetSurroundingTextPosition"); - writer.WriteUInt32(cursor); - writer.WriteUInt32(anchor); - return writer.CreateMessage(); - } - } - - public Task DestroyICAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "DestroyIC"); - return writer.CreateMessage(); - } - } - - public Task ProcessKeyEventAsync(uint keyval, uint keycode, uint state, int type, uint time) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_i(m, (FcitxObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "uuuiu", - member: "ProcessKeyEvent"); - writer.WriteUInt32(keyval); - writer.WriteUInt32(keycode); - writer.WriteUInt32(state); - writer.WriteInt32(type); - writer.WriteUInt32(time); - return writer.CreateMessage(); - } - } - - public ValueTask WatchCommitStringAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "CommitString", static (m, s) => - ReadMessage_s(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchCurrentIMAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "CurrentIM", static (m, s) => - ReadMessage_sss(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchUpdateFormattedPreeditAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "UpdateFormattedPreedit", static (m, s) => - ReadMessage_arsizi(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchForwardKeyAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKey", static (m, s) => - ReadMessage_uui(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchDeleteSurroundingTextAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "DeleteSurroundingText", static (m, s) => - ReadMessage_iu(m, (FcitxObject)s!), handler, emitOnCapturedContext); - } - - internal class InputMethod : FcitxObject - { - private const string Interface = "org.fcitx.Fcitx.InputMethod"; - - public InputMethod(FcitxService service, ObjectPath path) : base(service, path) { } - - public Task<(int Icid, bool Enable, uint Keyval1, uint State1, uint Keyval2, uint State2)> CreateICv3Async(string appname, int pid) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_ibuuuu(m, (FcitxObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "si", - member: "CreateICv3"); - writer.WriteString(appname); - writer.WriteInt32(pid); - return writer.CreateMessage(); - } - } - } - - internal class InputContext1 : FcitxObject - { - private const string Interface = "org.fcitx.Fcitx.InputContext1"; - - public InputContext1(FcitxService service, ObjectPath path) : base(service, path) { } - - public Task FocusInAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "FocusIn"); - return writer.CreateMessage(); - } - } - - public Task FocusOutAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "FocusOut"); - return writer.CreateMessage(); - } - } - - public Task ResetAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "Reset"); - return writer.CreateMessage(); - } - } - - public Task SetCursorRectAsync(int x, int y, int w, int h) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "iiii", - member: "SetCursorRect"); - writer.WriteInt32(x); - writer.WriteInt32(y); - writer.WriteInt32(w); - writer.WriteInt32(h); - return writer.CreateMessage(); - } - } - - public Task SetCapabilityAsync(ulong caps) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "t", - member: "SetCapability"); - writer.WriteUInt64(caps); - return writer.CreateMessage(); - } - } - - public Task SetSurroundingTextAsync(string text, uint cursor, uint anchor) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "suu", - member: "SetSurroundingText"); - writer.WriteString(text); - writer.WriteUInt32(cursor); - writer.WriteUInt32(anchor); - return writer.CreateMessage(); - } - } - - public Task SetSurroundingTextPositionAsync(uint cursor, uint anchor) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "uu", - member: "SetSurroundingTextPosition"); - writer.WriteUInt32(cursor); - writer.WriteUInt32(anchor); - return writer.CreateMessage(); - } - } - - public Task DestroyICAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "DestroyIC"); - return writer.CreateMessage(); - } - } - - public Task ProcessKeyEventAsync(uint keyval, uint keycode, uint state, bool type, uint time) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_b(m, (FcitxObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "uuubu", - member: "ProcessKeyEvent"); - writer.WriteUInt32(keyval); - writer.WriteUInt32(keycode); - writer.WriteUInt32(state); - writer.WriteBool(type); - writer.WriteUInt32(time); - return writer.CreateMessage(); - } - } - - public ValueTask WatchCommitStringAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "CommitString", static (m, s) => - ReadMessage_s(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchCurrentIMAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "CurrentIM", static (m, s) => - ReadMessage_sss(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchUpdateFormattedPreeditAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "UpdateFormattedPreedit", static (m, s) => - ReadMessage_arsizi(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchForwardKeyAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKey", static (m, s) => - ReadMessage_uub(m, (FcitxObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchDeleteSurroundingTextAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "DeleteSurroundingText", static (m, s) => - ReadMessage_iu(m, (FcitxObject)s!), handler, emitOnCapturedContext); - } - - internal class InputMethod1 : FcitxObject - { - private const string Interface = "org.fcitx.Fcitx.InputMethod1"; - - public InputMethod1(FcitxService service, ObjectPath path) : base(service, path) { } - - public Task<(ObjectPath A0, byte[] A1)> CreateInputContextAsync((string, string)[] a0) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_oay(m, (FcitxObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "a(ss)", - member: "CreateInputContext"); - writer.WriteArray(a0); - return writer.CreateMessage(); - } - } - } - - internal class FcitxService - { - public FcitxService(Connection connection, string destination) - => (Connection, Destination) = (connection, destination); - - public Connection Connection { get; } - public string Destination { get; } - public InputContext CreateInputContext(string path) => new(this, path); - public InputMethod CreateInputMethod(string path) => new(this, path); - public InputContext1 CreateInputContext1(string path) => new(this, path); - public InputMethod1 CreateInputMethod1(string path) => new(this, path); - } - - internal class FcitxObject - { - protected FcitxObject(FcitxService service, ObjectPath path) - => (Service, Path) = (service, path); - - public FcitxService Service { get; } - public ObjectPath Path { get; } - protected Connection Connection => Service.Connection; - - protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ss", - member: "Get"); - writer.WriteString(@interface); - writer.WriteString(property); - return writer.CreateMessage(); - } - - protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "s", - member: "GetAll"); - writer.WriteString(@interface); - return writer.CreateMessage(); - } - - protected ValueTask WatchPropertiesChangedAsync(string @interface, - MessageValueReader> reader, Action> handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = Service.Destination, - Path = Path, - Interface = "org.freedesktop.DBus.Properties", - Member = "PropertiesChanged", - Arg0 = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => - ((Action>)hs!).Invoke(ex, changes), - this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, - MessageValueReader reader, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => - ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, static (_, _) => - null!, static (ex, _, _, hs) => - ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); - } - - protected static int ReadMessage_i(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadInt32(); - } - - protected static string ReadMessage_s(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadString(); - } - - protected static (string, string, string) ReadMessage_sss(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadString(); - var arg1 = reader.ReadString(); - var arg2 = reader.ReadString(); - return (arg0, arg1, arg2); - } - - protected static ((string, int)[], int) ReadMessage_arsizi(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadArray<(string, int)>(); - var arg1 = reader.ReadInt32(); - return (arg0, arg1); - } - - protected static (uint, uint, int) ReadMessage_uui(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadUInt32(); - var arg1 = reader.ReadUInt32(); - var arg2 = reader.ReadInt32(); - return (arg0, arg1, arg2); - } - - protected static (int, uint) ReadMessage_iu(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadInt32(); - var arg1 = reader.ReadUInt32(); - return (arg0, arg1); - } - - protected static (int, bool, uint, uint, uint, uint) ReadMessage_ibuuuu(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadInt32(); - var arg1 = reader.ReadBool(); - var arg2 = reader.ReadUInt32(); - var arg3 = reader.ReadUInt32(); - var arg4 = reader.ReadUInt32(); - var arg5 = reader.ReadUInt32(); - return (arg0, arg1, arg2, arg3, arg4, arg5); - } - - protected static bool ReadMessage_b(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadBool(); - } - - protected static (uint, uint, bool) ReadMessage_uub(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadUInt32(); - var arg1 = reader.ReadUInt32(); - var arg2 = reader.ReadBool(); - return (arg0, arg1, arg2); - } - - protected static (ObjectPath, byte[]) ReadMessage_oay(Message message, FcitxObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadObjectPath(); - var arg1 = reader.ReadArray(); - return (arg0, arg1); - } - } -} diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs index b71ca55cf8..b651f126fb 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs @@ -5,15 +5,15 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx { internal class FcitxICWrapper { - private readonly InputContext1? _modern; - private readonly InputContext? _old; + private readonly OrgFcitxFcitxInputContext1? _modern; + private readonly OrgFcitxFcitxInputContext? _old; - public FcitxICWrapper(InputContext old) + public FcitxICWrapper(OrgFcitxFcitxInputContext old) { _old = old; } - public FcitxICWrapper(InputContext1 modern) + public FcitxICWrapper(OrgFcitxFcitxInputContext1 modern) { _modern = modern; } @@ -43,7 +43,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx public ValueTask WatchForwardKeyAsync(Action handler) => _old?.WatchForwardKeyAsync(handler) - ?? _modern?.WatchForwardKeyAsync((e, ev) => handler.Invoke(e, (ev.Keyval, ev.State, ev.Type ? 1 : 0))) + ?? _modern?.WatchForwardKeyAsync((e, ev) => handler.Invoke(e, (ev.keyval, ev.state, ev.type ? 1 : 0))) ?? new ValueTask(default(IDisposable?)); public Task SetCapacityAsync(uint flags) => diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 2a5260b1cf..6b30c5424a 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -17,21 +17,20 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx protected override async Task Connect(string name) { - var service = new FcitxService(Connection, name); if (name == "org.fcitx.Fcitx") { - var method = service.CreateInputMethod("/inputmethod"); + var method = new OrgFcitxFcitxInputMethod(Connection, name, "/inputmethod"); var resp = await method.CreateICv3Async(GetAppName(), Process.GetCurrentProcess().Id); - var proxy = service.CreateInputContext($"/inputcontext_{resp.Icid}"); + var proxy = new OrgFcitxFcitxInputContext(Connection, name, $"/inputcontext_{resp.icid}"); _context = new FcitxICWrapper(proxy); } else { - var method = service.CreateInputMethod1("/inputmethod"); + var method = new OrgFcitxFcitxInputMethod1(Connection, name, "/inputmethod"); var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) }); - var proxy = service.CreateInputContext1(resp.A0); + var proxy = new OrgFcitxFcitxInputContext1(Connection, name, resp.Item1); _context = new FcitxICWrapper(proxy); } diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs deleted file mode 100644 index e0f9681766..0000000000 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs +++ /dev/null @@ -1,513 +0,0 @@ -using System; -using System.Threading.Tasks; -using Tmds.DBus.Protocol; - -namespace Avalonia.FreeDesktop.DBusIme.IBus -{ - internal class Portal : IBusObject - { - private const string Interface = "org.freedesktop.IBus.Portal"; - - public Portal(IBusService service, ObjectPath path) : base(service, path) { } - - public Task CreateInputContextAsync(string clientName) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (IBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "CreateInputContext"); - writer.WriteString(clientName); - return writer.CreateMessage(); - } - } - } - - internal class InputContext : IBusObject - { - private const string Interface = "org.freedesktop.IBus.InputContext"; - - public InputContext(IBusService service, ObjectPath path) : base(service, path) { } - - public Task ProcessKeyEventAsync(uint keyval, uint keycode, uint state) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_b(m, (IBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "uuu", - member: "ProcessKeyEvent"); - writer.WriteUInt32(keyval); - writer.WriteUInt32(keycode); - writer.WriteUInt32(state); - return writer.CreateMessage(); - } - } - - public Task SetCursorLocationAsync(int x, int y, int w, int h) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "iiii", - member: "SetCursorLocation"); - writer.WriteInt32(x); - writer.WriteInt32(y); - writer.WriteInt32(w); - writer.WriteInt32(h); - return writer.CreateMessage(); - } - } - - public Task SetCursorLocationRelativeAsync(int x, int y, int w, int h) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "iiii", - member: "SetCursorLocationRelative"); - writer.WriteInt32(x); - writer.WriteInt32(y); - writer.WriteInt32(w); - writer.WriteInt32(h); - return writer.CreateMessage(); - } - } - - public Task ProcessHandWritingEventAsync(double[] coordinates) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "ad", - member: "ProcessHandWritingEvent"); - writer.WriteArray(coordinates); - return writer.CreateMessage(); - } - } - - public Task CancelHandWritingAsync(uint nStrokes) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "u", - member: "CancelHandWriting"); - writer.WriteUInt32(nStrokes); - return writer.CreateMessage(); - } - } - - public Task FocusInAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "FocusIn"); - return writer.CreateMessage(); - } - } - - public Task FocusOutAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "FocusOut"); - return writer.CreateMessage(); - } - } - - public Task ResetAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "Reset"); - return writer.CreateMessage(); - } - } - - public Task SetCapabilitiesAsync(uint caps) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "u", - member: "SetCapabilities"); - writer.WriteUInt32(caps); - return writer.CreateMessage(); - } - } - - public Task PropertyActivateAsync(string name, uint state) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "su", - member: "PropertyActivate"); - writer.WriteString(name); - writer.WriteUInt32(state); - return writer.CreateMessage(); - } - } - - public Task SetEngineAsync(string name) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "SetEngine"); - writer.WriteString(name); - return writer.CreateMessage(); - } - } - - public Task GetEngineAsync() - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_v(m, (IBusObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "GetEngine"); - return writer.CreateMessage(); - } - } - - public Task SetSurroundingTextAsync(object text, uint cursorPos, uint anchorPos) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "vuu", - member: "SetSurroundingText"); - writer.WriteVariant(text); - writer.WriteUInt32(cursorPos); - writer.WriteUInt32(anchorPos); - return writer.CreateMessage(); - } - } - - public ValueTask WatchCommitTextAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "CommitText", static (m, s) => - ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchForwardKeyEventAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKeyEvent", static (m, s) => - ReadMessage_uuu(m, (IBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchUpdatePreeditTextAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "UpdatePreeditText", static (m, s) => - ReadMessage_vub(m, (IBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchUpdatePreeditTextWithModeAsync( - Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "UpdatePreeditTextWithMode", static (m, s) => - ReadMessage_vubu(m, (IBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchShowPreeditTextAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ShowPreeditText", handler, emitOnCapturedContext); - - public ValueTask WatchHidePreeditTextAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "HidePreeditText", handler, emitOnCapturedContext); - - public ValueTask WatchUpdateAuxiliaryTextAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "UpdateAuxiliaryText", static (m, s) => - ReadMessage_vb(m, (IBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchShowAuxiliaryTextAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ShowAuxiliaryText", handler, emitOnCapturedContext); - - public ValueTask WatchHideAuxiliaryTextAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "HideAuxiliaryText", handler, emitOnCapturedContext); - - public ValueTask WatchUpdateLookupTableAsync(Action handler, - bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "UpdateLookupTable", static (m, s) => - ReadMessage_vb(m, (IBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchShowLookupTableAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ShowLookupTable", handler, emitOnCapturedContext); - - public ValueTask WatchHideLookupTableAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "HideLookupTable", handler, emitOnCapturedContext); - - public ValueTask WatchPageUpLookupTableAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "PageUpLookupTable", handler, emitOnCapturedContext); - - public ValueTask WatchPageDownLookupTableAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "PageDownLookupTable", handler, emitOnCapturedContext); - - public ValueTask WatchCursorUpLookupTableAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "CursorUpLookupTable", handler, emitOnCapturedContext); - - public ValueTask WatchCursorDownLookupTableAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "CursorDownLookupTable", handler, emitOnCapturedContext); - - public ValueTask WatchRegisterPropertiesAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "RegisterProperties", static (m, s) => ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchUpdatePropertyAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "UpdateProperty", static (m, s) => ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext); - } - - internal class Service : IBusObject - { - private const string Interface = "org.freedesktop.IBus.Service"; - - public Service(IBusService service, ObjectPath path) : base(service, path) { } - - public Task DestroyAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "Destroy"); - return writer.CreateMessage(); - } - } - } - - internal class IBusService - { - public IBusService(Connection connection, string destination) - => (Connection, Destination) = (connection, destination); - - public Connection Connection { get; } - public string Destination { get; } - public Portal CreatePortal(string path) => new(this, path); - public InputContext CreateInputContext(string path) => new(this, path); - public Service CreateService(string path) => new(this, path); - } - - internal class IBusObject - { - protected IBusObject(IBusService service, ObjectPath path) - => (Service, Path) = (service, path); - - public IBusService Service { get; } - public ObjectPath Path { get; } - protected Connection Connection => Service.Connection; - - protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ss", - member: "Get"); - writer.WriteString(@interface); - writer.WriteString(property); - return writer.CreateMessage(); - } - - protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "s", - member: "GetAll"); - writer.WriteString(@interface); - return writer.CreateMessage(); - } - - protected ValueTask WatchPropertiesChangedAsync(string @interface, - MessageValueReader> reader, Action> handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = Service.Destination, - Path = Path, - Interface = "org.freedesktop.DBus.Properties", - Member = "PropertiesChanged", - Arg0 = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => - ((Action>)hs!).Invoke(ex, changes), - this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, - MessageValueReader reader, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => ((Action)hs!).Invoke(ex, arg), - this, handler, emitOnCapturedContext); - } - - public ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, static (_, _) => null!, static (ex, _, _, hs) => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); - } - - protected static ObjectPath ReadMessage_o(Message message, IBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadObjectPath(); - } - - protected static bool ReadMessage_b(Message message, IBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadBool(); - } - - protected static object ReadMessage_v(Message message, IBusObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadVariant(); - } - - protected static (uint, uint, uint) ReadMessage_uuu(Message message, IBusObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadUInt32(); - var arg1 = reader.ReadUInt32(); - var arg2 = reader.ReadUInt32(); - return (arg0, arg1, arg2); - } - - protected static (object, uint, bool) ReadMessage_vub(Message message, IBusObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadVariant(); - var arg1 = reader.ReadUInt32(); - var arg2 = reader.ReadBool(); - return (arg0, arg1, arg2); - } - - protected static (object, uint, bool, uint) ReadMessage_vubu(Message message, IBusObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadVariant(); - var arg1 = reader.ReadUInt32(); - var arg2 = reader.ReadBool(); - var arg3 = reader.ReadUInt32(); - return (arg0, arg1, arg2, arg3); - } - - protected static (object, bool) ReadMessage_vb(Message message, IBusObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadVariant(); - var arg1 = reader.ReadBool(); - return (arg0, arg1); - } - } -} diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index f3ab8617a4..4db0d95d3c 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -9,17 +9,17 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus { internal class IBusX11TextInputMethod : DBusTextInputMethodBase { - private Service? _service; - private InputContext? _context; + private OrgFreedesktopIBusService? _service; + private OrgFreedesktopIBusInputContext? _context; public IBusX11TextInputMethod(Connection connection) : base(connection, "org.freedesktop.portal.IBus") { } protected override async Task Connect(string name) { - var service = new IBusService(Connection, name); - var path = await service.CreatePortal("/org/freedesktop/IBus").CreateInputContextAsync(GetAppName()); - _context = service.CreateInputContext(path); - _service = service.CreateService(path); + var portal = new OrgFreedesktopIBusPortal(Connection, name, "/org/freedesktop/IBus"); + var path = await portal.CreateInputContextAsync(GetAppName()); + _service = new OrgFreedesktopIBusService(Connection, name, path); + _context = new OrgFreedesktopIBusInputContext(Connection, name, path); AddDisposable(await _context.WatchCommitTextAsync(OnCommitText)); AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey)); Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus)); diff --git a/src/Avalonia.FreeDesktop/DBusInterfaces.cs b/src/Avalonia.FreeDesktop/DBusInterfaces.cs new file mode 100644 index 0000000000..3bece27ab1 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusInterfaces.cs @@ -0,0 +1,18 @@ +using Tmds.DBus.SourceGenerator; + +namespace Avalonia.FreeDesktop +{ + [DBusInterface("./DBusXml/DBus.xml")] + [DBusInterface("./DBusXml/StatusNotifierWatcher.xml")] + [DBusInterface("./DBusXml/com.canonical.AppMenu.Registrar.xml")] + [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputContext.xml")] + [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputMethod.xml")] + [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputContext1.xml")] + [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputMethod1.xml")] + [DBusInterface("./DBusXml/org.freedesktop.portal.FileChooser.xml")] + [DBusInterface("./DBusXml/org.freedesktop.portal.Request.xml")] + [DBusInterface("./DBusXml/org.freedesktop.IBus.Portal.xml")] + [DBusHandler("./DBusXml/DBusMenu.xml")] + [DBusHandler("./DBusXml/StatusNotifierItem.xml")] + internal class DBusInterfaces { } +} diff --git a/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs b/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs deleted file mode 100644 index e3928cfbb2..0000000000 --- a/src/Avalonia.FreeDesktop/DBusMenu.DBus.cs +++ /dev/null @@ -1,500 +0,0 @@ -using System; -using Tmds.DBus.Protocol; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Avalonia.FreeDesktop -{ - internal record DBusMenuProperties - { - public uint Version { get; set; } - public string TextDirection { get; set; } = default!; - public string Status { get; set; } = default!; - public string[] IconThemePath { get; set; } = default!; - } - - internal class DBusMenu : DBusMenuObject - { - private const string Interface = "com.canonical.dbusmenu"; - - public DBusMenu(DBusMenuService service, ObjectPath path) : base(service, path) { } - - public Task<(uint Revision, (int, Dictionary, object[]) Layout)> GetLayoutAsync(int parentId, int recursionDepth, string[] propertyNames) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_uriaesvavz(m), this); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "iias", - member: "GetLayout"); - writer.WriteInt32(parentId); - writer.WriteInt32(recursionDepth); - writer.WriteArray(propertyNames); - return writer.CreateMessage(); - } - } - - public Task<(int, Dictionary)[]> GetGroupPropertiesAsync(int[] ids, string[] propertyNames) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_ariaesvz(m), this); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "aias", - member: "GetGroupProperties"); - writer.WriteArray(ids); - writer.WriteArray(propertyNames); - return writer.CreateMessage(); - } - } - - public Task GetPropertyAsync(int id, string name) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_v(m), this); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "is", - member: "GetProperty"); - writer.WriteInt32(id); - writer.WriteString(name); - return writer.CreateMessage(); - } - } - - public Task EventAsync(int id, string eventId, object data, uint timestamp) - { - return Connection.CallMethodAsync(CreateMessage()); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "isvu", - member: "Event"); - writer.WriteInt32(id); - writer.WriteString(eventId); - writer.WriteVariant(data); - writer.WriteUInt32(timestamp); - return writer.CreateMessage(); - } - } - - public Task EventGroupAsync((int, string, object, uint)[] events) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_ai(m), this); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "a(isvu)", - member: "EventGroup"); - writer.WriteArray(events); - return writer.CreateMessage(); - } - } - - public Task AboutToShowAsync(int id) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_b(m), this); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "i", - member: "AboutToShow"); - writer.WriteInt32(id); - return writer.CreateMessage(); - } - } - - public Task<(int[] UpdatesNeeded, int[] IdErrors)> AboutToShowGroupAsync(int[] ids) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, _) => ReadMessage_aiai(m), this); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "ai", - member: "AboutToShowGroup"); - writer.WriteArray(ids); - return writer.CreateMessage(); - } - } - - public ValueTask WatchItemsPropertiesUpdatedAsync(Action)[] UpdatedProps, (int, string[])[] RemovedProps)> handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ItemsPropertiesUpdated", static (m, _) => ReadMessage_ariaesvzariasz(m), handler, emitOnCapturedContext); - - public ValueTask WatchLayoutUpdatedAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "LayoutUpdated", static (m, _) => ReadMessage_ui(m), handler, emitOnCapturedContext); - - public ValueTask WatchItemActivationRequestedAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ItemActivationRequested", static (m, _) => ReadMessage_iu(m), handler, emitOnCapturedContext); - - public Task SetVersionAsync(uint value) - { - return Connection.CallMethodAsync(CreateMessage()); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("Version"); - writer.WriteSignature("u"); - writer.WriteUInt32(value); - return writer.CreateMessage(); - } - } - - public Task SetTextDirectionAsync(string value) - { - return Connection.CallMethodAsync(CreateMessage()); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("TextDirection"); - writer.WriteSignature("s"); - writer.WriteString(value); - return writer.CreateMessage(); - } - } - - public Task SetStatusAsync(string value) - { - return Connection.CallMethodAsync(CreateMessage()); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("Status"); - writer.WriteSignature("s"); - writer.WriteString(value); - return writer.CreateMessage(); - } - } - - public Task SetIconThemePathAsync(string[] value) - { - return Connection.CallMethodAsync(CreateMessage()); - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("IconThemePath"); - writer.WriteSignature("as"); - writer.WriteArray(value); - return writer.CreateMessage(); - } - } - - public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Version"), static (m, _) => ReadMessage_v_u(m), this); - - public Task GetTextDirectionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "TextDirection"), static (m, _) => ReadMessage_v_s(m), this); - - public Task GetStatusAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Status"), static (m, _) => ReadMessage_v_s(m), this); - - public Task GetIconThemePathAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IconThemePath"), static (m, _) => ReadMessage_v_as(m), this); - - public Task GetPropertiesAsync() - { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) => ReadMessage(m), this); - static DBusMenuProperties ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - return ReadProperties(ref reader); - } - } - - public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) - { - return base.WatchPropertiesChangedAsync(Interface, static (m, _) => ReadMessage(m), handler, emitOnCapturedContext); - static PropertyChanges ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadString(); // interface - List changed = new(); - return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); - } - static string[] ReadInvalidated(ref Reader reader) - { - List? invalidated = null; - var headersEnd = reader.ReadArrayStart(DBusType.String); - while (reader.HasNext(headersEnd)) - { - invalidated ??= new List(); - var property = reader.ReadString(); - switch (property) - { - case "Version": invalidated.Add("Version"); break; - case "TextDirection": invalidated.Add("TextDirection"); break; - case "Status": invalidated.Add("Status"); break; - case "IconThemePath": invalidated.Add("IconThemePath"); break; - } - } - return invalidated?.ToArray() ?? Array.Empty(); - } - } - - private static DBusMenuProperties ReadProperties(ref Reader reader, ICollection? changedList = null) - { - var props = new DBusMenuProperties(); - var headersEnd = reader.ReadArrayStart(DBusType.Struct); - while (reader.HasNext(headersEnd)) - { - var property = reader.ReadString(); - switch (property) - { - case "Version": - reader.ReadSignature("u"); - props.Version = reader.ReadUInt32(); - changedList?.Add("Version"); - break; - case "TextDirection": - reader.ReadSignature("s"); - props.TextDirection = reader.ReadString(); - changedList?.Add("TextDirection"); - break; - case "Status": - reader.ReadSignature("s"); - props.Status = reader.ReadString(); - changedList?.Add("Status"); - break; - case "IconThemePath": - reader.ReadSignature("as"); - props.IconThemePath = reader.ReadArray(); - changedList?.Add("IconThemePath"); - break; - default: - reader.ReadVariant(); - break; - } - } - return props; - } - } - - internal class DBusMenuService - { - public Connection Connection { get; } - public string Destination { get; } - public DBusMenuService(Connection connection, string destination) - => (Connection, Destination) = (connection, destination); - public DBusMenu CreateDbusmenu(string path) => new(this, path); - } - - internal class DBusMenuObject - { - public DBusMenuService Service { get; } - public ObjectPath Path { get; } - protected Connection Connection => Service.Connection; - protected DBusMenuObject(DBusMenuService service, ObjectPath path) - => (Service, Path) = (service, path); - - protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ss", - member: "Get"); - writer.WriteString(@interface); - writer.WriteString(property); - return writer.CreateMessage(); - } - - protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "s", - member: "GetAll"); - writer.WriteString(@interface); - return writer.CreateMessage(); - } - - protected ValueTask WatchPropertiesChangedAsync(string @interface, MessageValueReader> reader, Action> handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = Service.Destination, - Path = Path, - Interface = "org.freedesktop.DBus.Properties", - Member = "PropertiesChanged", - Arg0 = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) - => ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); - } - - protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) - => ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); - } - - protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, static (_, _) - => null!, static (ex, _, _, hs) - => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); - } - - protected static (uint, (int, Dictionary, object[])) ReadMessage_uriaesvavz(Message message) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadUInt32(); - var arg1 = reader.ReadStruct, object[]>(); - return (arg0, arg1); - } - - protected static (int, Dictionary)[] ReadMessage_ariaesvz(Message message) - { - var reader = message.GetBodyReader(); - return reader.ReadArray<(int, Dictionary)>(); - } - - protected static object ReadMessage_v(Message message) - { - var reader = message.GetBodyReader(); - return reader.ReadVariant(); - } - - protected static int[] ReadMessage_ai(Message message) - { - var reader = message.GetBodyReader(); - return reader.ReadArray(); - } - - protected static bool ReadMessage_b(Message message) - { - var reader = message.GetBodyReader(); - return reader.ReadBool(); - } - - protected static (int[], int[]) ReadMessage_aiai(Message message) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadArray(); - var arg1 = reader.ReadArray(); - return (arg0, arg1); - } - - protected static ((int, Dictionary)[], (int, string[])[]) ReadMessage_ariaesvzariasz(Message message) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadArray<(int, Dictionary)>(); - var arg1 = reader.ReadArray<(int, string[])>(); - return (arg0, arg1); - } - - protected static (uint, int) ReadMessage_ui(Message message) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadUInt32(); - var arg1 = reader.ReadInt32(); - return (arg0, arg1); - } - - protected static (int, uint) ReadMessage_iu(Message message) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadInt32(); - var arg1 = reader.ReadUInt32(); - return (arg0, arg1); - } - - protected static uint ReadMessage_v_u(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("u"); - return reader.ReadUInt32(); - } - - protected static string ReadMessage_v_s(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("s"); - return reader.ReadString(); - } - - protected static string[] ReadMessage_v_as(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("as"); - return reader.ReadArray(); - } - } -} diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 69e2eed72f..e8d4a7b6b4 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; -using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -25,16 +24,15 @@ namespace Avalonia.FreeDesktop public static string GenerateDBusMenuObjPath => $"/net/avaloniaui/dbusmenu/{Guid.NewGuid():N}"; - private class DBusMenuExporterImpl : ITopLevelNativeMenuExporter, IMethodHandler, IDisposable + private class DBusMenuExporterImpl : ComCanonicalDbusmenu, ITopLevelNativeMenuExporter, IDisposable { private readonly Connection _connection; - private readonly DBusMenuProperties _dBusMenuProperties = new() { Version = 2, Status = "normal" }; private readonly Dictionary _idsToItems = new(); private readonly Dictionary _itemsToIds = new(); private readonly HashSet _menus = new(); private readonly uint _xid; private readonly bool _appMenu = true; - private Registrar? _registrar; + private ComCanonicalAppMenuRegistrar? _registrar; private NativeMenu? _menu; private bool _disposed; private uint _revision = 1; @@ -59,7 +57,40 @@ namespace Avalonia.FreeDesktop Init(); } - public string Path { get; } + public override string Path { get; } + + protected override (uint revision, (int, Dictionary, object[]) layout) OnGetLayout(int parentId, int recursionDepth, string[] propertyNames) + { + var menu = GetMenu(parentId); + var layout = GetLayout(menu.item, menu.menu, recursionDepth, propertyNames); + if (!IsNativeMenuExported) + { + IsNativeMenuExported = true; + Dispatcher.UIThread.Post(() => OnIsNativeMenuExportedChanged?.Invoke(this, EventArgs.Empty)); + } + + return (_revision, layout); + } + + protected override (int, Dictionary)[] OnGetGroupProperties(int[] ids, string[] propertyNames) => + ids.Select(id => (id, GetProperties(GetMenu(id), propertyNames))).ToArray(); + + protected override object OnGetProperty(int id, string name) => GetProperty(GetMenu(id), name) ?? 0; + + protected override void OnEvent(int id, string eventId, object data, uint timestamp) => + Dispatcher.UIThread.Post(() => HandleEvent(id, eventId, data, timestamp)); + + protected override int[] OnEventGroup((int, string, object, uint)[] events) + { + foreach (var e in events) + Dispatcher.UIThread.Post(() => HandleEvent(e.Item1, e.Item2, e.Item3, e.Item4)); + return Array.Empty(); + } + + protected override bool OnAboutToShow(int id) => false; + + protected override (int[] updatesNeeded, int[] idErrors) OnAboutToShowGroup(int[] ids) => + (Array.Empty(), Array.Empty()); private async void Init() { @@ -69,8 +100,7 @@ namespace Avalonia.FreeDesktop var services = await _connection.ListServicesAsync(); if (!services.Contains("com.canonical.AppMenu.Registrar")) return; - _registrar = new RegistrarService(_connection, "com.canonical.AppMenu.Registrar") - .CreateRegistrar("/com/canonical/AppMenu/Registrar"); + _registrar = new ComCanonicalAppMenuRegistrar(_connection, "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar"); if (!_disposed) await _registrar.RegisterWindowAsync(_xid, Path); // It's not really important if this code succeeds, @@ -297,140 +327,6 @@ namespace Avalonia.FreeDesktop } } - public ValueTask HandleMethodAsync(MethodContext context) - { - switch (context.Request.InterfaceAsString) - { - case "com.canonical.dbusmenu": - switch (context.Request.MemberAsString, context.Request.SignatureAsString) - { - case ("GetLayout", "iias"): - { - using var writer = context.CreateReplyWriter("u(ia{sv}av)"); - var reader = context.Request.GetBodyReader(); - var parentId = reader.ReadInt32(); - var recursionDepth = reader.ReadInt32(); - var propertyNames = reader.ReadArray(); - var menu = GetMenu(parentId); - var layout = GetLayout(menu.item, menu.menu, recursionDepth, propertyNames); - writer.WriteUInt32(_revision); - writer.WriteStruct(layout); - if (!IsNativeMenuExported) - { - IsNativeMenuExported = true; - Dispatcher.UIThread.Post(() => OnIsNativeMenuExportedChanged?.Invoke(this, EventArgs.Empty)); - } - - context.Reply(writer.CreateMessage()); - break; - } - case ("GetGroupProperties", "aias"): - { - using var writer = context.CreateReplyWriter("a(ia{sv})"); - var reader = context.Request.GetBodyReader(); - var ids = reader.ReadArray(); - var propertyNames = reader.ReadArray(); - var arrayStart = writer.WriteArrayStart(DBusType.Struct); - foreach (var id in ids) - { - var item = GetMenu(id); - var props = GetProperties(item, propertyNames); - writer.WriteStruct((id, props)); - } - - writer.WriteArrayEnd(arrayStart); - context.Reply(writer.CreateMessage()); - break; - } - case ("GetProperty", "is"): - { - using var writer = context.CreateReplyWriter("v"); - var reader = context.Request.GetBodyReader(); - var id = reader.ReadInt32(); - var name = reader.ReadString(); - writer.WriteVariant(GetProperty(GetMenu(id), name) ?? 0); - context.Reply(writer.CreateMessage()); - break; - } - case ("Event", "isvu"): - { - var reader = context.Request.GetBodyReader(); - var id = reader.ReadInt32(); - var eventId = reader.ReadString(); - var data = reader.ReadVariant(); - var timestamp = reader.ReadUInt32(); - Dispatcher.UIThread.Post(() => HandleEvent(id, eventId, data, timestamp)); - break; - } - case ("EventGroup", "a(isvu)"): - { - using var writer = context.CreateReplyWriter("ai"); - var reader = context.Request.GetBodyReader(); - var events = reader.ReadArray<(int Id, string EventId, object Data, uint Timestamp)>(); - foreach (var e in events) - Dispatcher.UIThread.Post(() => HandleEvent(e.Id, e.EventId, e.Data, e.Timestamp)); - writer.WriteArray(Array.Empty()); - context.Reply(writer.CreateMessage()); - break; - } - case ("AboutToShow", "i"): - { - using var writer = context.CreateReplyWriter("b"); - writer.WriteBool(false); - context.Reply(writer.CreateMessage()); - break; - } - case ("AboutToShowGroup", "ai"): - { - using var writer = context.CreateReplyWriter("aiai"); - writer.WriteStruct((Array.Empty(), Array.Empty())); - context.Reply(writer.CreateMessage()); - break; - } - } - - break; - case "org.freedesktop.DBus.Properties": - switch (context.Request.MemberAsString, context.Request.SignatureAsString) - { - case ("Version", "u"): - { - using var writer = context.CreateReplyWriter("u"); - writer.WriteUInt32(_dBusMenuProperties.Version); - context.Reply(writer.CreateMessage()); - break; - } - case ("TextDirection", "s"): - { - using var writer = context.CreateReplyWriter("s"); - writer.WriteString(_dBusMenuProperties.TextDirection); - context.Reply(writer.CreateMessage()); - break; - } - case ("Status", "s"): - { - using var writer = context.CreateReplyWriter("s"); - writer.WriteString(_dBusMenuProperties.Status); - context.Reply(writer.CreateMessage()); - break; - } - case ("IconThemePath", "as"): - { - using var writer = context.CreateReplyWriter("as"); - writer.WriteArray(_dBusMenuProperties.IconThemePath); - context.Reply(writer.CreateMessage()); - break; - } - } - - break; - } - - return default; - } - - public bool RunMethodHandlerSynchronously(Message message) => true; - private void EmitUIntIntSignal(string member, uint arg0, int arg1) { using var writer = _connection.GetMessageWriter(); diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index 7d1a0bbfc4..81233f3b2f 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -19,18 +19,18 @@ namespace Avalonia.FreeDesktop return null; var services = await DBusHelper.Connection.ListServicesAsync(); return services.Contains("org.freedesktop.portal.Desktop", StringComparer.Ordinal) - ? new DBusSystemDialog(new DesktopService(DBusHelper.Connection, "org.freedesktop.portal.Desktop"), handle) + ? new DBusSystemDialog(DBusHelper.Connection, handle) : null; } - private readonly DesktopService _desktopService; - private readonly FileChooser _fileChooser; + private readonly Connection _connection; + private readonly OrgFreedesktopPortalFileChooser _fileChooser; private readonly IPlatformHandle _handle; - private DBusSystemDialog(DesktopService desktopService, IPlatformHandle handle) + private DBusSystemDialog(Connection connection, IPlatformHandle handle) { - _desktopService = desktopService; - _fileChooser = desktopService.CreateFileChooser("/org/freedesktop/portal/desktop"); + _connection = connection; + _fileChooser = new OrgFreedesktopPortalFileChooser(connection, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); _handle = handle; } @@ -53,7 +53,7 @@ namespace Avalonia.FreeDesktop objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - var request = _desktopService.CreateRequest(objectPath); + var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath); var tsc = new TaskCompletionSource(); using var disposable = await request.WatchResponseAsync((e, x) => { @@ -82,7 +82,7 @@ namespace Avalonia.FreeDesktop chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString())); objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - var request = _desktopService.CreateRequest(objectPath); + var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath); var tsc = new TaskCompletionSource(); using var disposable = await request.WatchResponseAsync((e, x) => { @@ -113,7 +113,7 @@ namespace Avalonia.FreeDesktop }; var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); - var request = _desktopService.CreateRequest(objectPath); + var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath); var tsc = new TaskCompletionSource(); using var disposable = await request.WatchResponseAsync((e, x) => { diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index d6518f7246..215408a0e3 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -1,8 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; using Avalonia.Controls.Platform; using Avalonia.Logging; using Avalonia.Platform; @@ -13,14 +11,15 @@ namespace Avalonia.FreeDesktop internal class DBusTrayIconImpl : ITrayIconImpl { private static int s_trayIconInstanceId; + public static readonly (int, int, byte[]) EmptyPixmap = (1, 1, new byte[] { 255, 0, 0, 0 }); private readonly ObjectPath _dbusMenuPath; private readonly Connection? _connection; - private readonly DBus? _dBus; + private readonly OrgFreedesktopDBus? _dBus; private IDisposable? _serviceWatchDisposable; private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj; - private StatusNotifierWatcher? _statusNotifierWatcher; + private OrgKdeStatusNotifierWatcher? _statusNotifierWatcher; private (int, int, byte[]) _icon; private string? _sysTrayServiceName; @@ -48,7 +47,7 @@ namespace Avalonia.FreeDesktop IsActive = true; - _dBus = new DBusService(_connection, "org.freedesktop.DBus").CreateDBus("/org/freedesktop/DBus"); + _dBus = new OrgFreedesktopDBus(_connection, "org.freedesktop.DBus", "/org/freedesktop/DBus"); _dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection); @@ -61,8 +60,7 @@ namespace Avalonia.FreeDesktop if (_connection is null || _isDisposed) return; - _statusNotifierWatcher = new StatusNotifierWatcherService(_connection, "org.kde.StatusNotifierWatcher") - .CreateStatusNotifierWatcher("/StatusNotifierWatcher"); + _statusNotifierWatcher = new OrgKdeStatusNotifierWatcher(_connection, "org.kde.StatusNotifierWatcher", "/StatusNotifierWatcher"); _serviceConnected = true; } @@ -72,7 +70,7 @@ namespace Avalonia.FreeDesktop if (!services.Contains("org.kde.StatusNotifierWatcher", StringComparer.Ordinal)) return; - _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.A2) ); + _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item2) ); var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher"); OnNameChange(nameOwner); } @@ -147,7 +145,7 @@ namespace Avalonia.FreeDesktop if (icon is null) { - _statusNotifierItemDbusObj?.SetIcon(StatusNotifierItemDbusObj.StatusNotifierItemProperties.EmptyPixmap); + _statusNotifierItemDbusObj?.SetIcon(EmptyPixmap); return; } @@ -210,27 +208,37 @@ namespace Avalonia.FreeDesktop /// /// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html /// - internal class StatusNotifierItemDbusObj : IMethodHandler + internal class StatusNotifierItemDbusObj : OrgKdeStatusNotifierItem { private readonly Connection _connection; - private readonly StatusNotifierItemProperties _backingProperties; public StatusNotifierItemDbusObj(Connection connection, ObjectPath dbusMenuPath) { _connection = connection; - _backingProperties = new StatusNotifierItemProperties - { - Menu = dbusMenuPath, // Needs a dbus menu somehow - ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), string.Empty, string.Empty) - }; - + BackingProperties.Menu = dbusMenuPath; + BackingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), string.Empty, string.Empty); + BackingProperties.IconName = string.Empty; + BackingProperties.AttentionIconName = string.Empty; + BackingProperties.AttentionIconPixmap = new []{ DBusTrayIconImpl.EmptyPixmap }; + BackingProperties.AttentionMovieName = string.Empty; + BackingProperties.IconThemePath = string.Empty; + BackingProperties.OverlayIconName = string.Empty; + BackingProperties.OverlayIconPixmap = new []{ DBusTrayIconImpl.EmptyPixmap }; InvalidateAll(); } - public string Path => "/StatusNotifierItem"; + public override string Path => "/StatusNotifierItem"; public event Action? ActivationDelegate; + protected override void OnContextMenu(int x, int y) { } + + protected override void OnActivate(int x, int y) => ActivationDelegate?.Invoke(); + + protected override void OnSecondaryActivate(int x, int y) { } + + protected override void OnScroll(int delta, string orientation) { } + public void InvalidateAll() { EmitVoidSignal("NewTitle"); @@ -238,12 +246,12 @@ namespace Avalonia.FreeDesktop EmitVoidSignal("NewAttentionIcon"); EmitVoidSignal("NewOverlayIcon"); EmitVoidSignal("NewToolTip"); - EmitStringSignal("NewStatus", _backingProperties.Status); + EmitStringSignal("NewStatus", BackingProperties.Status); } public void SetIcon((int, int, byte[]) dbusPixmap) { - _backingProperties.IconPixmap = new[] { dbusPixmap }; + BackingProperties.IconPixmap = new[] { dbusPixmap }; InvalidateAll(); } @@ -252,124 +260,14 @@ namespace Avalonia.FreeDesktop if (text is null) return; - _backingProperties.Id = text; - _backingProperties.Category = "ApplicationStatus"; - _backingProperties.Status = text; - _backingProperties.Title = text; - _backingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), text, string.Empty); + BackingProperties.Id = text; + BackingProperties.Category = "ApplicationStatus"; + BackingProperties.Status = text; + BackingProperties.Title = text; + BackingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), text, string.Empty); InvalidateAll(); } - public bool RunMethodHandlerSynchronously(Message message) => false; - - public ValueTask HandleMethodAsync(MethodContext context) - { - switch (context.Request.InterfaceAsString) - { - case "org.kde.StatusNotifierItem": - switch (context.Request.MemberAsString, context.Request.SignatureAsString) - { - case ("ContextMenu", "ii"): - break; - case ("Activate", "ii"): - ActivationDelegate?.Invoke(); - break; - case ("SecondaryActivate", "ii"): - break; - case ("Scroll", "is"): - break; - } - - break; - case "org.freedesktop.DBus.Properties": - switch (context.Request.MemberAsString, context.Request.SignatureAsString) - { - case ("Get", "ss"): - { - var reader = context.Request.GetBodyReader(); - _ = reader.ReadString(); - var member = reader.ReadString(); - switch (member) - { - case "Category": - { - using var writer = context.CreateReplyWriter("s"); - writer.WriteString(_backingProperties.Category); - context.Reply(writer.CreateMessage()); - break; - } - case "Id": - { - using var writer = context.CreateReplyWriter("s"); - writer.WriteString(_backingProperties.Id); - context.Reply(writer.CreateMessage()); - break; - } - case "Title": - { - using var writer = context.CreateReplyWriter("s"); - writer.WriteString(_backingProperties.Title); - context.Reply(writer.CreateMessage()); - break; - } - case "Status": - { - using var writer = context.CreateReplyWriter("s"); - writer.WriteString(_backingProperties.Status); - context.Reply(writer.CreateMessage()); - break; - } - case "Menu": - { - using var writer = context.CreateReplyWriter("o"); - writer.WriteObjectPath(_backingProperties.Menu); - context.Reply(writer.CreateMessage()); - break; - } - case "IconPixmap": - { - using var writer = context.CreateReplyWriter("a(iiay)"); - writer.WriteArray(_backingProperties.IconPixmap); - context.Reply(writer.CreateMessage()); - break; - } - case "ToolTip": - { - using var writer = context.CreateReplyWriter("(sa(iiay)ss)"); - writer.WriteStruct(_backingProperties.ToolTip); - context.Reply(writer.CreateMessage()); - break; - } - } - - break; - } - case ("GetAll", "s"): - { - var writer = context.CreateReplyWriter("a{sv}"); - var dict = new Dictionary - { - { "Category", _backingProperties.Category }, - { "Id", _backingProperties.Id }, - { "Title", _backingProperties.Title }, - { "Status", _backingProperties.Status }, - { "Menu", _backingProperties.Menu }, - { "IconPixmap", _backingProperties.IconPixmap }, - { "ToolTip", _backingProperties.ToolTip } - }; - - writer.WriteDictionary(dict); - context.Reply(writer.CreateMessage()); - break; - } - } - - break; - } - - return default; - } - private void EmitVoidSignal(string member) { using var writer = _connection.GetMessageWriter(); @@ -384,27 +282,5 @@ namespace Avalonia.FreeDesktop writer.WriteString(value); _connection.TrySendMessage(writer.CreateMessage()); } - - internal record StatusNotifierItemProperties - { - public string Category { get; set; } = string.Empty; - public string Id { get; set; } = string.Empty; - public string Title { get; set; } = string.Empty; - public string Status { get; set; } = string.Empty; - public int WindowId { get; set; } - public string? IconThemePath { get; set; } - public ObjectPath Menu { get; set; } - public bool ItemIsMenu { get; set; } - public string? IconName { get; set; } - public (int, int, byte[])[] IconPixmap { get; set; } = { EmptyPixmap }; - public string? OverlayIconName { get; set; } - public (int, int, byte[])[]? OverlayIconPixmap { get; set; } - public string? AttentionIconName { get; set; } - public (int, int, byte[])[]? AttentionIconPixmap { get; set; } - public string? AttentionMovieName { get; set; } - public (string, (int, int, byte[])[], string, string) ToolTip { get; set; } - - public static (int, int, byte[]) EmptyPixmap = (1, 1, new byte[] { 255, 0, 0, 0 }); - } } } diff --git a/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs b/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs deleted file mode 100644 index 23f1dcf0e7..0000000000 --- a/src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs +++ /dev/null @@ -1,938 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading.Tasks; -using Tmds.DBus.Protocol; - -namespace Avalonia.FreeDesktop -{ - internal record NotificationProperties - { - public uint Version { get; set; } - } - - internal class Notification : DesktopObject - { - private const string Interface = "org.freedesktop.portal.Notification"; - - public Notification(DesktopService service, ObjectPath path) : base(service, path) { } - - public Task AddNotificationAsync(string id, Dictionary notification) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "sa{sv}", - member: "AddNotification"); - writer.WriteString(id); - writer.WriteDictionary(notification); - return writer.CreateMessage(); - } - } - - public Task RemoveNotificationAsync(string id) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "RemoveNotification"); - writer.WriteString(id); - return writer.CreateMessage(); - } - } - - public ValueTask WatchActionInvokedAsync(Action handler, - bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "ActionInvoked", static (m, s) - => ReadMessage_ssav(m, (DesktopObject)s!), handler, emitOnCapturedContext); - - public Task SetVersionAsync(uint value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("version"); - writer.WriteSignature("u"); - writer.WriteUInt32(value); - return writer.CreateMessage(); - } - } - - public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) - => ReadMessage_v_u(m, (DesktopObject)s!), this); - - public Task GetPropertiesAsync() - { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) - => ReadMessage(m), this); - - static NotificationProperties ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - return ReadProperties(ref reader); - } - } - - public ValueTask WatchPropertiesChangedAsync(Action> handler, - bool emitOnCapturedContext = true) - { - return base.WatchPropertiesChangedAsync(Interface, static (m, _) - => ReadMessage(m), handler, emitOnCapturedContext); - - static PropertyChanges ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadString(); // interface - List changed = new(); - return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), - ReadInvalidated(ref reader)); - } - - static string[] ReadInvalidated(ref Reader reader) - { - List? invalidated = null; - var headersEnd = reader.ReadArrayStart(DBusType.String); - while (reader.HasNext(headersEnd)) - { - invalidated ??= new List(); - var property = reader.ReadString(); - switch (property) - { - case "version": - invalidated.Add("Version"); - break; - } - } - - return invalidated?.ToArray() ?? Array.Empty(); - } - } - - private static NotificationProperties ReadProperties(ref Reader reader, ICollection? changedList = null) - { - var props = new NotificationProperties(); - var headersEnd = reader.ReadArrayStart(DBusType.Struct); - while (reader.HasNext(headersEnd)) - { - var property = reader.ReadString(); - switch (property) - { - case "version": - reader.ReadSignature("u"); - props.Version = reader.ReadUInt32(); - changedList?.Add("Version"); - break; - default: - reader.ReadVariant(); - break; - } - } - - return props; - } - } - - internal record OpenURIProperties - { - public uint Version { get; set; } - } - - internal class OpenURI : DesktopObject - { - private const string Interface = "org.freedesktop.portal.OpenURI"; - - public OpenURI(DesktopService service, ObjectPath path) : base(service, path) { } - - public Task OpenUriAsync(string parentWindow, string uri, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "ssa{sv}", - member: "OpenURI"); - writer.WriteString(parentWindow); - writer.WriteString(uri); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task OpenFileAsync(string parentWindow, SafeHandle fd, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "sha{sv}", - member: "OpenFile"); - writer.WriteString(parentWindow); - writer.WriteHandle(fd); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task OpenDirectoryAsync(string parentWindow, SafeHandle fd, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "sha{sv}", - member: "OpenDirectory"); - writer.WriteString(parentWindow); - writer.WriteHandle(fd); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task SetVersionAsync(uint value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("version"); - writer.WriteSignature("u"); - writer.WriteUInt32(value); - return writer.CreateMessage(); - } - } - - public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) - => ReadMessage_v_u(m, (DesktopObject)s!), this); - - public Task GetPropertiesAsync() - { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) - => ReadMessage(m), this); - - static OpenURIProperties ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - return ReadProperties(ref reader); - } - } - - public ValueTask WatchPropertiesChangedAsync(Action> handler, - bool emitOnCapturedContext = true) - { - return base.WatchPropertiesChangedAsync(Interface, static (m, _) - => ReadMessage(m), handler, emitOnCapturedContext); - - static PropertyChanges ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadString(); // interface - List changed = new(); - return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader)); - } - - static string[] ReadInvalidated(ref Reader reader) - { - List? invalidated = null; - var headersEnd = reader.ReadArrayStart(DBusType.String); - while (reader.HasNext(headersEnd)) - { - invalidated ??= new List(); - var property = reader.ReadString(); - switch (property) - { - case "version": - invalidated.Add("Version"); - break; - } - } - - return invalidated?.ToArray() ?? Array.Empty(); - } - } - - private static OpenURIProperties ReadProperties(ref Reader reader, ICollection? changedList = null) - { - var props = new OpenURIProperties(); - var headersEnd = reader.ReadArrayStart(DBusType.Struct); - while (reader.HasNext(headersEnd)) - { - var property = reader.ReadString(); - switch (property) - { - case "version": - reader.ReadSignature("u"); - props.Version = reader.ReadUInt32(); - changedList?.Add("Version"); - break; - default: - reader.ReadVariant(); - break; - } - } - - return props; - } - } - - internal record DynamicLauncherProperties - { - public uint SupportedLauncherTypes { get; set; } - public uint Version { get; set; } - } - - internal class DynamicLauncher : DesktopObject - { - private const string Interface = "org.freedesktop.portal.DynamicLauncher"; - - public DynamicLauncher(DesktopService service, ObjectPath path) : base(service, path) { } - - public Task InstallAsync(string token, string desktopFileId, string desktopEntry, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "sssa{sv}", - member: "Install"); - writer.WriteString(token); - writer.WriteString(desktopFileId); - writer.WriteString(desktopEntry); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task PrepareInstallAsync(string parentWindow, string name, object iconV, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "ssva{sv}", - member: "PrepareInstall"); - writer.WriteString(parentWindow); - writer.WriteString(name); - writer.WriteVariant(iconV); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task RequestInstallTokenAsync(string name, object iconV, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) - => ReadMessage_s(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "sva{sv}", - member: "RequestInstallToken"); - writer.WriteString(name); - writer.WriteVariant(iconV); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task UninstallAsync(string desktopFileId, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "sa{sv}", - member: "Uninstall"); - writer.WriteString(desktopFileId); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task GetDesktopEntryAsync(string desktopFileId) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) - => ReadMessage_s(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetDesktopEntry"); - writer.WriteString(desktopFileId); - return writer.CreateMessage(); - } - } - - public Task<(object IconV, string IconFormat, uint IconSize)> GetIconAsync(string desktopFileId) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) - => ReadMessage_vsu(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "GetIcon"); - writer.WriteString(desktopFileId); - return writer.CreateMessage(); - } - } - - public Task LaunchAsync(string desktopFileId, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "sa{sv}", - member: "Launch"); - writer.WriteString(desktopFileId); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task SetSupportedLauncherTypesAsync(uint value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("SupportedLauncherTypes"); - writer.WriteSignature("u"); - writer.WriteUInt32(value); - return writer.CreateMessage(); - } - } - - public Task SetVersionAsync(uint value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("version"); - writer.WriteSignature("u"); - writer.WriteUInt32(value); - return writer.CreateMessage(); - } - } - - public Task GetSupportedLauncherTypesAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "SupportedLauncherTypes"), static (m, s) - => ReadMessage_v_u(m, (DesktopObject)s!), this); - - public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) - => ReadMessage_v_u(m, (DesktopObject)s!), this); - - public Task GetPropertiesAsync() - { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) - => ReadMessage(m), this); - - static DynamicLauncherProperties ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - return ReadProperties(ref reader); - } - } - - public ValueTask WatchPropertiesChangedAsync(Action> handler, - bool emitOnCapturedContext = true) - { - return base.WatchPropertiesChangedAsync(Interface, static (m, _) - => ReadMessage(m), handler, - emitOnCapturedContext); - - static PropertyChanges ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadString(); // interface - List changed = new(); - return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), - ReadInvalidated(ref reader)); - } - - static string[] ReadInvalidated(ref Reader reader) - { - List? invalidated = null; - var headersEnd = reader.ReadArrayStart(DBusType.String); - while (reader.HasNext(headersEnd)) - { - invalidated ??= new List(); - var property = reader.ReadString(); - switch (property) - { - case "SupportedLauncherTypes": - invalidated.Add("SupportedLauncherTypes"); - break; - case "version": - invalidated.Add("Version"); - break; - } - } - - return invalidated?.ToArray() ?? Array.Empty(); - } - } - - private static DynamicLauncherProperties ReadProperties(ref Reader reader, ICollection? changedList = null) - { - var props = new DynamicLauncherProperties(); - var headersEnd = reader.ReadArrayStart(DBusType.Struct); - while (reader.HasNext(headersEnd)) - { - var property = reader.ReadString(); - switch (property) - { - case "SupportedLauncherTypes": - reader.ReadSignature("u"); - props.SupportedLauncherTypes = reader.ReadUInt32(); - changedList?.Add("SupportedLauncherTypes"); - break; - case "version": - reader.ReadSignature("u"); - props.Version = reader.ReadUInt32(); - changedList?.Add("Version"); - break; - default: - reader.ReadVariant(); - break; - } - } - - return props; - } - } - - internal record FileChooserProperties - { - public uint Version { get; set; } - } - - internal class FileChooser : DesktopObject - { - private const string Interface = "org.freedesktop.portal.FileChooser"; - - public FileChooser(DesktopService service, ObjectPath path) : base(service, path) { } - - public Task OpenFileAsync(string parentWindow, string title, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "ssa{sv}", - member: "OpenFile"); - writer.WriteString(parentWindow); - writer.WriteString(title); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task SaveFileAsync(string parentWindow, string title, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "ssa{sv}", - member: "SaveFile"); - writer.WriteString(parentWindow); - writer.WriteString(title); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task SaveFilesAsync(string parentWindow, string title, Dictionary options) - { - return Connection.CallMethodAsync(CreateMessage(), static (m, s) => ReadMessage_o(m, (DesktopObject)s!), this); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "ssa{sv}", - member: "SaveFiles"); - writer.WriteString(parentWindow); - writer.WriteString(title); - writer.WriteDictionary(options); - return writer.CreateMessage(); - } - } - - public Task SetVersionAsync(uint value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("version"); - writer.WriteSignature("u"); - writer.WriteUInt32(value); - return writer.CreateMessage(); - } - } - - public Task GetVersionAsync() - => Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"), static (m, s) - => ReadMessage_v_u(m, (DesktopObject)s!), this); - - public Task GetPropertiesAsync() - { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) - => ReadMessage(m), this); - - static FileChooserProperties ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - return ReadProperties(ref reader); - } - } - - public ValueTask WatchPropertiesChangedAsync(Action> handler, - bool emitOnCapturedContext = true) - { - return base.WatchPropertiesChangedAsync(Interface, static (m, _) - => ReadMessage(m), handler, emitOnCapturedContext); - - static PropertyChanges ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadString(); // interface - List changed = new(); - return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), - ReadInvalidated(ref reader)); - } - - static string[] ReadInvalidated(ref Reader reader) - { - List? invalidated = null; - var headersEnd = reader.ReadArrayStart(DBusType.String); - while (reader.HasNext(headersEnd)) - { - invalidated ??= new List(); - var property = reader.ReadString(); - switch (property) - { - case "version": - invalidated.Add("Version"); - break; - } - } - - return invalidated?.ToArray() ?? Array.Empty(); - } - } - - private static FileChooserProperties ReadProperties(ref Reader reader, ICollection? changedList = null) - { - var props = new FileChooserProperties(); - var headersEnd = reader.ReadArrayStart(DBusType.Struct); - while (reader.HasNext(headersEnd)) - { - var property = reader.ReadString(); - switch (property) - { - case "version": - reader.ReadSignature("u"); - props.Version = reader.ReadUInt32(); - changedList?.Add("Version"); - break; - default: - reader.ReadVariant(); - break; - } - } - - return props; - } - } - - internal class Request : DesktopObject - { - private const string Interface = "org.freedesktop.portal.Request"; - - public Request(DesktopService service, ObjectPath path) : base(service, path) { } - - public Task CloseAsync() - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - "Close"); - return writer.CreateMessage(); - } - } - - public ValueTask WatchResponseAsync(Action results)> handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "Response", - static (m, s) => ReadMessage_uaesv(m, (DesktopObject)s!), handler, emitOnCapturedContext); - } - - internal class DesktopService - { - public DesktopService(Connection connection, string destination) - => (Connection, Destination) = (connection, destination); - - public Connection Connection { get; } - public string Destination { get; } - public Notification CreateNotification(string path) => new(this, path); - public OpenURI CreateOpenUri(string path) => new(this, path); - public DynamicLauncher CreateDynamicLauncher(string path) => new(this, path); - public FileChooser CreateFileChooser(string path) => new(this, path); - public Request CreateRequest(string path) => new(this, path); - } - - internal class DesktopObject - { - protected DesktopObject(DesktopService service, ObjectPath path) - => (Service, Path) = (service, path); - - protected DesktopService Service { get; } - protected ObjectPath Path { get; } - protected Connection Connection => Service.Connection; - - protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ss", - member: "Get"); - writer.WriteString(@interface); - writer.WriteString(property); - return writer.CreateMessage(); - } - - protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "s", - member: "GetAll"); - writer.WriteString(@interface); - return writer.CreateMessage(); - } - - protected ValueTask WatchPropertiesChangedAsync(string @interface, - MessageValueReader> reader, Action> handler, - bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = Service.Destination, - Path = Path, - Interface = "org.freedesktop.DBus.Properties", - Member = "PropertiesChanged", - Arg0 = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) - => ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); - } - - protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, - MessageValueReader reader, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) => - ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); - } - - protected static ObjectPath ReadMessage_o(Message message, DesktopObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadObjectPath(); - } - - protected static uint ReadMessage_v_u(Message message, DesktopObject _) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("u"); - return reader.ReadUInt32(); - } - - protected static (string, string, object[]) ReadMessage_ssav(Message message, DesktopObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadString(); - var arg1 = reader.ReadString(); - var arg2 = reader.ReadArray(); - return (arg0, arg1, arg2); - } - - protected static (uint, Dictionary) ReadMessage_uaesv(Message message, DesktopObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadUInt32(); - var arg1 = reader.ReadDictionary(); - return (arg0, arg1); - } - - protected static string ReadMessage_s(Message message, DesktopObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadString(); - } - - protected static (object, string, uint) ReadMessage_vsu(Message message, DesktopObject _) - { - var reader = message.GetBodyReader(); - var arg0 = reader.ReadVariant(); - var arg1 = reader.ReadString(); - var arg2 = reader.ReadUInt32(); - return (arg0, arg1, arg2); - } - } - - internal class PropertyChanges - { - public PropertyChanges(TProperties properties, string[] invalidated, string[] changed) - => (Properties, Invalidated, Changed) = (properties, invalidated, changed); - - public TProperties Properties { get; } - public string[] Invalidated { get; } - public string[] Changed { get; } - public bool HasChanged(string property) => Array.IndexOf(Changed, property) != -1; - public bool IsInvalidated(string property) => Array.IndexOf(Invalidated, property) != -1; - } -} diff --git a/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs b/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs deleted file mode 100644 index 4e6ce3bd43..0000000000 --- a/src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs +++ /dev/null @@ -1,350 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Tmds.DBus.Protocol; - -namespace Avalonia.FreeDesktop -{ - internal record StatusNotifierWatcherProperties - { - public string[] RegisteredStatusNotifierItems { get; set; } = default!; - public bool IsStatusNotifierHostRegistered { get; set; } - public int ProtocolVersion { get; set; } - } - - internal class StatusNotifierWatcher : StatusNotifierWatcherObject - { - private const string Interface = "org.kde.StatusNotifierWatcher"; - - public StatusNotifierWatcher(StatusNotifierWatcherService service, ObjectPath path) : base(service, path) { } - - public Task RegisterStatusNotifierItemAsync(string service) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "RegisterStatusNotifierItem"); - writer.WriteString(service); - return writer.CreateMessage(); - } - } - - public Task RegisterStatusNotifierHostAsync(string service) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - Interface, - signature: "s", - member: "RegisterStatusNotifierHost"); - writer.WriteString(service); - return writer.CreateMessage(); - } - } - - public ValueTask WatchStatusNotifierItemRegisteredAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierItemRegistered", static (m, s) => - ReadMessage_s(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchStatusNotifierItemUnregisteredAsync(Action handler, bool emitOnCapturedContext = true) - => WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierItemUnregistered", static (m, s) - => ReadMessage_s(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext); - - public ValueTask WatchStatusNotifierHostRegisteredAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierHostRegistered", handler, emitOnCapturedContext); - - public ValueTask WatchStatusNotifierHostUnregisteredAsync(Action handler, bool emitOnCapturedContext = true) => - WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierHostUnregistered", handler, emitOnCapturedContext); - - public Task SetRegisteredStatusNotifierItemsAsync(string[] value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("RegisteredStatusNotifierItems"); - writer.WriteSignature("as"); - writer.WriteArray(value); - return writer.CreateMessage(); - } - } - - public Task SetIsStatusNotifierHostRegisteredAsync(bool value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("IsStatusNotifierHostRegistered"); - writer.WriteSignature("b"); - writer.WriteBool(value); - return writer.CreateMessage(); - } - } - - public Task SetProtocolVersionAsync(int value) - { - return Connection.CallMethodAsync(CreateMessage()); - - MessageBuffer CreateMessage() - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ssv", - member: "Set"); - writer.WriteString(Interface); - writer.WriteString("ProtocolVersion"); - writer.WriteSignature("i"); - writer.WriteInt32(value); - return writer.CreateMessage(); - } - } - - public Task GetRegisteredStatusNotifierItemsAsync() => - Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "RegisteredStatusNotifierItems"), static (m, s) - => ReadMessage_v_as(m, (StatusNotifierWatcherObject)s!), this); - - public Task GetIsStatusNotifierHostRegisteredAsync() => - Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IsStatusNotifierHostRegistered"), static (m, s) => - ReadMessage_v_b(m, (StatusNotifierWatcherObject)s!), this); - - public Task GetProtocolVersionAsync() => - Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "ProtocolVersion"), static (m, s) - => ReadMessage_v_i(m, (StatusNotifierWatcherObject)s!), this); - - public Task GetPropertiesAsync() - { - return Connection.CallMethodAsync(CreateGetAllPropertiesMessage(Interface), static (m, _) - => ReadMessage(m), this); - - static StatusNotifierWatcherProperties ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - return ReadProperties(ref reader); - } - } - - public ValueTask WatchPropertiesChangedAsync(Action> handler, bool emitOnCapturedContext = true) - { - return base.WatchPropertiesChangedAsync(Interface, static (m, _) => ReadMessage(m), handler, emitOnCapturedContext); - - static PropertyChanges ReadMessage(Message message) - { - var reader = message.GetBodyReader(); - reader.ReadString(); // interface - List changed = new(); - return new PropertyChanges(ReadProperties(ref reader, changed), changed.ToArray(), - ReadInvalidated(ref reader)); - } - - static string[] ReadInvalidated(ref Reader reader) - { - List? invalidated = null; - var headersEnd = reader.ReadArrayStart(DBusType.String); - while (reader.HasNext(headersEnd)) - { - invalidated ??= new List(); - var property = reader.ReadString(); - switch (property) - { - case "RegisteredStatusNotifierItems": - invalidated.Add("RegisteredStatusNotifierItems"); - break; - case "IsStatusNotifierHostRegistered": - invalidated.Add("IsStatusNotifierHostRegistered"); - break; - case "ProtocolVersion": - invalidated.Add("ProtocolVersion"); - break; - } - } - - return invalidated?.ToArray() ?? Array.Empty(); - } - } - - private static StatusNotifierWatcherProperties ReadProperties(ref Reader reader, ICollection? changedList = null) - { - var props = new StatusNotifierWatcherProperties(); - var headersEnd = reader.ReadArrayStart(DBusType.Struct); - while (reader.HasNext(headersEnd)) - { - var property = reader.ReadString(); - switch (property) - { - case "RegisteredStatusNotifierItems": - reader.ReadSignature("as"); - props.RegisteredStatusNotifierItems = reader.ReadArray(); - changedList?.Add("RegisteredStatusNotifierItems"); - break; - case "IsStatusNotifierHostRegistered": - reader.ReadSignature("b"); - props.IsStatusNotifierHostRegistered = reader.ReadBool(); - changedList?.Add("IsStatusNotifierHostRegistered"); - break; - case "ProtocolVersion": - reader.ReadSignature("i"); - props.ProtocolVersion = reader.ReadInt32(); - changedList?.Add("ProtocolVersion"); - break; - default: - reader.ReadVariant(); - break; - } - } - - return props; - } - } - - internal class StatusNotifierWatcherService - { - public StatusNotifierWatcherService(Connection connection, string destination) => (Connection, Destination) = (connection, destination); - - public Connection Connection { get; } - public string Destination { get; } - - public StatusNotifierWatcher CreateStatusNotifierWatcher(string path) => new(this, path); - } - - internal class StatusNotifierWatcherObject - { - protected StatusNotifierWatcherObject(StatusNotifierWatcherService service, ObjectPath path) - { - (Service, Path) = (service, path); - } - - protected StatusNotifierWatcherService Service { get; } - protected ObjectPath Path { get; } - protected Connection Connection => Service.Connection; - - protected MessageBuffer CreateGetPropertyMessage(string @interface, string property) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "ss", - member: "Get"); - writer.WriteString(@interface); - writer.WriteString(property); - return writer.CreateMessage(); - } - - protected MessageBuffer CreateGetAllPropertiesMessage(string @interface) - { - using var writer = Connection.GetMessageWriter(); - writer.WriteMethodCallHeader( - Service.Destination, - Path, - "org.freedesktop.DBus.Properties", - signature: "s", - member: "GetAll"); - writer.WriteString(@interface); - return writer.CreateMessage(); - } - - protected ValueTask WatchPropertiesChangedAsync(string @interface, MessageValueReader> reader, Action> handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = Service.Destination, - Path = Path, - Interface = "org.freedesktop.DBus.Properties", - Member = "PropertiesChanged", - Arg0 = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, changes, _, hs) => - ((Action>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext); - } - - protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, MessageValueReader reader, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, reader, static (ex, arg, _, hs) - => ((Action)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext); - } - - protected ValueTask WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action handler, bool emitOnCapturedContext) - { - var rule = new MatchRule - { - Type = MessageType.Signal, - Sender = sender, - Path = path, - Member = signal, - Interface = @interface - }; - return Connection.AddMatchAsync(rule, static (_, _) - => null!, static (Exception? ex, object _, object? _, object? hs) - => ((Action)hs!).Invoke(ex), this, handler, emitOnCapturedContext); - } - - protected static string ReadMessage_s(Message message, StatusNotifierWatcherObject _) - { - var reader = message.GetBodyReader(); - return reader.ReadString(); - } - - protected static string[] ReadMessage_v_as(Message message, StatusNotifierWatcherObject _) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("as"); - return reader.ReadArray(); - } - - protected static bool ReadMessage_v_b(Message message, StatusNotifierWatcherObject _) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("b"); - return reader.ReadBool(); - } - - protected static int ReadMessage_v_i(Message message, StatusNotifierWatcherObject _) - { - var reader = message.GetBodyReader(); - reader.ReadSignature("i"); - return reader.ReadInt32(); - } - } -} diff --git a/src/tools/Tmds.DBus.SourceGenerator b/src/tools/Tmds.DBus.SourceGenerator new file mode 160000 index 0000000000..1cf4faba30 --- /dev/null +++ b/src/tools/Tmds.DBus.SourceGenerator @@ -0,0 +1 @@ +Subproject commit 1cf4faba30741f799a33313a2842cf70eeb6c67e From ebc77f9ba1dbcc15ab98ae72b4f0f92ef808da42 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Fri, 6 Jan 2023 16:41:42 +0100 Subject: [PATCH 005/155] Add xml files --- src/Avalonia.FreeDesktop/DBusXml/DBus.xml | 89 ++++ src/Avalonia.FreeDesktop/DBusXml/DBusMenu.xml | 437 ++++++++++++++++++ .../DBusXml/StatusNotifierItem.xml | 96 ++++ .../DBusXml/StatusNotifierWatcher.xml | 42 ++ .../com.canonical.AppMenu.Registrar.xml | 56 +++ .../DBusXml/org.fcitx.Fcitx.InputContext.xml | 64 +++ .../DBusXml/org.fcitx.Fcitx.InputContext1.xml | 64 +++ .../DBusXml/org.fcitx.Fcitx.InputMethod.xml | 16 + .../DBusXml/org.fcitx.Fcitx.InputMethod1.xml | 12 + .../DBusXml/org.freedesktop.IBus.Portal.xml | 139 ++++++ .../org.freedesktop.portal.FileChooser.xml | 377 +++++++++++++++ .../org.freedesktop.portal.Request.xml | 86 ++++ 12 files changed, 1478 insertions(+) create mode 100644 src/Avalonia.FreeDesktop/DBusXml/DBus.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/DBusMenu.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/StatusNotifierItem.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/StatusNotifierWatcher.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/com.canonical.AppMenu.Registrar.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext1.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod1.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.IBus.Portal.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.FileChooser.xml create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Request.xml diff --git a/src/Avalonia.FreeDesktop/DBusXml/DBus.xml b/src/Avalonia.FreeDesktop/DBusXml/DBus.xml new file mode 100644 index 0000000000..a7ecce70f2 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/DBus.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/DBusMenu.xml b/src/Avalonia.FreeDesktop/DBusXml/DBusMenu.xml new file mode 100644 index 0000000000..de6868cb3e --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/DBusMenu.xml @@ -0,0 +1,437 @@ + + + + + + + + Name + Type + Description + Default Value + + + type + String + Can be one of: + - "standard": an item which can be clicked to trigger an action or + show another menu + - "separator": a separator + + Vendor specific types can be added by prefixing them with + "x--". + + "standard" + + + label + string + Text of the item, except that: + -# two consecutive underscore characters "__" are displayed as a + single underscore, + -# any remaining underscore characters are not displayed at all, + -# the first of those remaining underscore characters (unless it is + the last character in the string) indicates that the following + character is the access key. + + "" + + + enabled + boolean + Whether the item can be activated or not. + true + + + visible + boolean + True if the item is visible in the menu. + true + + + icon-name + string + Icon name of the item, following the freedesktop.org icon spec. + "" + + + icon-data + binary + PNG data of the icon. + Empty + + + shortcut + array of arrays of strings + The shortcut of the item. Each array represents the key press + in the list of keypresses. Each list of strings contains a list of + modifiers and then the key that is used. The modifier strings + allowed are: "Control", "Alt", "Shift" and "Super". + + - A simple shortcut like Ctrl+S is represented as: + [["Control", "S"]] + - A complex shortcut like Ctrl+Q, Alt+X is represented as: + [["Control", "Q"], ["Alt", "X"]] + Empty + + + toggle-type + string + + If the item can be toggled, this property should be set to: + - "checkmark": Item is an independent togglable item + - "radio": Item is part of a group where only one item can be + toggled at a time + - "": Item cannot be toggled + + "" + + + toggle-state + int + + Describe the current state of a "togglable" item. Can be one of: + - 0 = off + - 1 = on + - anything else = indeterminate + + Note: + The implementation does not itself handle ensuring that only one + item in a radio group is set to "on", or that a group does not have + "on" and "indeterminate" items simultaneously; maintaining this + policy is up to the toolkit wrappers. + + -1 + + + children-display + string + + If the menu item has children this property should be set to + "submenu". + + "" + + + disposition + string + + How the menuitem feels the information it's displaying to the + user should be presented. + - "normal" a standard menu item + - "informative" providing additional information to the user + - "warning" looking at potentially harmful results + - "alert" something bad could potentially happen + + "normal" + + + + Vendor specific properties can be added by prefixing them with + "x--". + ]]> + + + + + Provides the version of the DBusmenu API that this API is + implementing. + + + + + + Represents the way the text direction of the application. This + allows the server to handle mismatches intelligently. For left- + to-right the string is "ltr" for right-to-left it is "rtl". + + + + + + Tells if the menus are in a normal state or they believe that they + could use some attention. Cases for showing them would be if help + were referring to them or they accessors were being highlighted. + This property can have two values: "normal" in almost all cases and + "notice" when they should have a higher priority to be shown. + + + + + + A list of directories that should be used for finding icons using + the icon naming spec. Idealy there should only be one for the icon + theme, but additional ones are often added by applications for + app specific icons. + + + + + + + + Provides the layout and propertiers that are attached to the entries + that are in the layout. It only gives the items that are children + of the item that is specified in @a parentId. It will return all of the + properties or specific ones depending of the value in @a propertyNames. + + The format is recursive, where the second 'v' is in the same format + as the original 'a(ia{sv}av)'. Its content depends on the value + of @a recursionDepth. + + + The ID of the parent node for the layout. For + grabbing the layout from the root node use zero. + + + + The amount of levels of recursion to use. This affects the + content of the second variant array. + - -1: deliver all the items under the @a parentId. + - 0: no recursion, the array will be empty. + - n: array will contains items up to 'n' level depth. + + + + + The list of item properties we are + interested in. If there are no entries in the list all of + the properties will be sent. + + + + The revision number of the layout. For matching + with layoutUpdated signals. + + + The layout, as a recursive structure. + + + + + + Returns the list of items which are children of @a parentId. + + + + A list of ids that we should be finding the properties + on. If the list is empty, all menu items should be sent. + + + + + The list of item properties we are + interested in. If there are no entries in the list all of + the properties will be sent. + + + + + An array of property values. + An item in this area is represented as a struct following + this format: + @li id unsigned the item id + @li properties map(string => variant) the requested item properties + + + + + + + Get a signal property on a single item. This is not useful if you're + going to implement this interface, it should only be used if you're + debugging via a commandline tool. + + + the id of the item which received the event + + + the name of the property to get + + + the value of the property + + + + + -" + ]]> + + the id of the item which received the event + + + the type of event + + + event-specific data + + + The time that the event occured if available or the time the message was sent if not + + + + + + Used to pass a set of events as a single message for possibily several + different menuitems. This is done to optimize DBus traffic. + + + + An array of all the events that should be passed. This tuple should + match the parameters of the 'Event' signal. Which is roughly: + id, eventID, data and timestamp. + + + + + I list of menuitem IDs that couldn't be found. If none of the ones + in the list can be found, a DBus error is returned. + + + + + + + This is called by the applet to notify the application that it is about + to show the menu under the specified item. + + + + Which menu item represents the parent of the item about to be shown. + + + + + Whether this AboutToShow event should result in the menu being updated. + + + + + + + A function to tell several menus being shown that they are about to + be shown to the user. This is likely only useful for programitc purposes + so while the return values are returned, in general, the singular function + should be used in most user interacation scenarios. + + + + The IDs of the menu items who's submenus are being shown. + + + + + The IDs of the menus that need updates. Note: if no update information + is needed the DBus message should set the no reply flag. + + + + + I list of menuitem IDs that couldn't be found. If none of the ones + in the list can be found, a DBus error is returned. + + + + + + + + Triggered when there are lots of property updates across many items + so they all get grouped into a single dbus message. The format is + the ID of the item with a hashtable of names and values for those + properties. + + + + + + + Triggered by the application to notify display of a layout update, up to + revision + + + The revision of the layout that we're currently on + + + + If the layout update is only of a subtree, this is the + parent item for the entries that have changed. It is zero if + the whole layout should be considered invalid. + + + + + + The server is requesting that all clients displaying this + menu open it to the user. This would be for things like + hotkeys that when the user presses them the menu should + open and display itself to the user. + + + ID of the menu that should be activated + + + The time that the event occured + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/StatusNotifierItem.xml b/src/Avalonia.FreeDesktop/DBusXml/StatusNotifierItem.xml new file mode 100644 index 0000000000..7866a74639 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/StatusNotifierItem.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/StatusNotifierWatcher.xml b/src/Avalonia.FreeDesktop/DBusXml/StatusNotifierWatcher.xml new file mode 100644 index 0000000000..2eb1a7a0b8 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/StatusNotifierWatcher.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/com.canonical.AppMenu.Registrar.xml b/src/Avalonia.FreeDesktop/DBusXml/com.canonical.AppMenu.Registrar.xml new file mode 100644 index 0000000000..42a71707b6 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/com.canonical.AppMenu.Registrar.xml @@ -0,0 +1,56 @@ + + + + + + An interface to register a menu from an application's window to be displayed in another + window.  This manages that association between XWindow Window IDs and the dbus + address and object that provides the menu using the dbusmenu dbus interface. + + + + + The XWindow ID of the window + + + The object on the dbus interface implementing the dbusmenu interface + + + + + A method to allow removing a window from the database. Windows will also be removed + when the client drops off DBus so this is not required. It is polite though. And + important for testing. + + + The XWindow ID of the window + + + + Gets the registered menu for a given window ID. + + The XWindow ID of the window to get + + + The address of the connection on DBus (e.g. :1.23 or org.example.service) + + + The path to the object which implements the com.canonical.dbusmenu interface. + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext.xml b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext.xml new file mode 100644 index 0000000000..b30d94cebf --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext1.xml b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext1.xml new file mode 100644 index 0000000000..6cb130d48a --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputContext1.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod.xml b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod.xml new file mode 100644 index 0000000000..b8d60f0d37 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod1.xml b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod1.xml new file mode 100644 index 0000000000..0cc358a09a --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.fcitx.Fcitx.InputMethod1.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.IBus.Portal.xml b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.IBus.Portal.xml new file mode 100644 index 0000000000..376ad424d4 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.IBus.Portal.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.FileChooser.xml b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.FileChooser.xml new file mode 100644 index 0000000000..2ae3546955 --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.FileChooser.xml @@ -0,0 +1,377 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Request.xml b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Request.xml new file mode 100644 index 0000000000..c1abb4eb7b --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Request.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + From 18737db8f4b0f486067069724e74b7b86b0bc8a7 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Mon, 9 Jan 2023 18:12:32 +0100 Subject: [PATCH 006/155] Use generated signal emit methods --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 33 +++++++------------- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 33 ++++++-------------- src/tools/Tmds.DBus.SourceGenerator | 2 +- 3 files changed, 22 insertions(+), 46 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 77f5048cd3..cb6def056f 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -2,8 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.IO; -using Avalonia.Reactive; -using System.Threading.Tasks; +using System.Linq; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -27,7 +26,6 @@ namespace Avalonia.FreeDesktop private class DBusMenuExporterImpl : ComCanonicalDbusmenu, ITopLevelNativeMenuExporter, IDisposable { - private readonly Connection _connection; private readonly Dictionary _idsToItems = new(); private readonly Dictionary _itemsToIds = new(); private readonly HashSet _menus = new(); @@ -42,7 +40,7 @@ namespace Avalonia.FreeDesktop public DBusMenuExporterImpl(Connection connection, IntPtr xid) { - _connection = connection; + Connection = connection; _xid = (uint)xid.ToInt32(); Path = GenerateDBusMenuObjPath; SetNativeMenu(new NativeMenu()); @@ -51,13 +49,15 @@ namespace Avalonia.FreeDesktop public DBusMenuExporterImpl(Connection connection, string path) { - _connection = connection; + Connection = connection; _appMenu = false; Path = path; SetNativeMenu(new NativeMenu()); Init(); } + protected override Connection Connection { get; } + public override string Path { get; } protected override (uint revision, (int, Dictionary, object[]) layout) OnGetLayout(int parentId, int recursionDepth, string[] propertyNames) @@ -79,12 +79,12 @@ namespace Avalonia.FreeDesktop protected override object OnGetProperty(int id, string name) => GetProperty(GetMenu(id), name) ?? 0; protected override void OnEvent(int id, string eventId, object data, uint timestamp) => - Dispatcher.UIThread.Post(() => HandleEvent(id, eventId, data, timestamp)); + Dispatcher.UIThread.Post(() => HandleEvent(id, eventId)); protected override int[] OnEventGroup((int, string, object, uint)[] events) { foreach (var e in events) - Dispatcher.UIThread.Post(() => HandleEvent(e.Item1, e.Item2, e.Item3, e.Item4)); + Dispatcher.UIThread.Post(() => HandleEvent(e.Item1, e.Item2)); return Array.Empty(); } @@ -95,13 +95,13 @@ namespace Avalonia.FreeDesktop private async void Init() { - _connection.AddMethodHandler(this); + Connection.AddMethodHandler(this); if (!_appMenu) return; - var services = await _connection.ListServicesAsync(); + var services = await Connection.ListServicesAsync(); if (!services.Contains("com.canonical.AppMenu.Registrar")) return; - _registrar = new ComCanonicalAppMenuRegistrar(_connection, "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar"); + _registrar = new ComCanonicalAppMenuRegistrar(Connection, "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar"); if (!_disposed) await _registrar.RegisterWindowAsync(_xid, Path); // It's not really important if this code succeeds, @@ -155,7 +155,7 @@ namespace Avalonia.FreeDesktop _idsToItems.Clear(); _itemsToIds.Clear(); _revision++; - EmitUIntIntSignal("LayoutUpdated", _revision, 0); + EmitLayoutUpdated(_revision, 0); } private void QueueReset() @@ -318,7 +318,7 @@ namespace Avalonia.FreeDesktop } - private void HandleEvent(int id, string eventId, object data, uint timestamp) + private void HandleEvent(int id, string eventId) { if (eventId == "clicked") { @@ -327,15 +327,6 @@ namespace Avalonia.FreeDesktop bridge.RaiseClicked(); } } - - private void EmitUIntIntSignal(string member, uint arg0, int arg1) - { - using var writer = _connection.GetMessageWriter(); - writer.WriteSignalHeader(null, Path, "com.canonical.dbusmenu", member, "ui"); - writer.WriteUInt32(arg0); - writer.WriteInt32(arg1); - _connection.TrySendMessage(writer.CreateMessage()); - } } } } diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index 4e9c495665..16b1a1d686 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -211,11 +211,9 @@ namespace Avalonia.FreeDesktop /// internal class StatusNotifierItemDbusObj : OrgKdeStatusNotifierItem { - private readonly Connection _connection; - public StatusNotifierItemDbusObj(Connection connection, ObjectPath dbusMenuPath) { - _connection = connection; + Connection = connection; BackingProperties.Menu = dbusMenuPath; BackingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), string.Empty, string.Empty); BackingProperties.IconName = string.Empty; @@ -228,6 +226,8 @@ namespace Avalonia.FreeDesktop InvalidateAll(); } + protected override Connection Connection { get; } + public override string Path => "/StatusNotifierItem"; public event Action? ActivationDelegate; @@ -242,12 +242,12 @@ namespace Avalonia.FreeDesktop public void InvalidateAll() { - EmitVoidSignal("NewTitle"); - EmitVoidSignal("NewIcon"); - EmitVoidSignal("NewAttentionIcon"); - EmitVoidSignal("NewOverlayIcon"); - EmitVoidSignal("NewToolTip"); - EmitStringSignal("NewStatus", BackingProperties.Status); + EmitNewTitle(); + EmitNewIcon(); + EmitNewAttentionIcon(); + EmitNewOverlayIcon(); + EmitNewToolTip(); + EmitNewStatus(BackingProperties.Status); } public void SetIcon((int, int, byte[]) dbusPixmap) @@ -268,20 +268,5 @@ namespace Avalonia.FreeDesktop BackingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), text, string.Empty); InvalidateAll(); } - - private void EmitVoidSignal(string member) - { - using var writer = _connection.GetMessageWriter(); - writer.WriteSignalHeader(null, Path, "org.kde.StatusNotifierItem", member); - _connection.TrySendMessage(writer.CreateMessage()); - } - - private void EmitStringSignal(string member, string value) - { - using var writer = _connection.GetMessageWriter(); - writer.WriteSignalHeader(null, Path, "org.kde.StatusNotifierItem", member, "s"); - writer.WriteString(value); - _connection.TrySendMessage(writer.CreateMessage()); - } } } diff --git a/src/tools/Tmds.DBus.SourceGenerator b/src/tools/Tmds.DBus.SourceGenerator index 1cf4faba30..6007f27d04 160000 --- a/src/tools/Tmds.DBus.SourceGenerator +++ b/src/tools/Tmds.DBus.SourceGenerator @@ -1 +1 @@ -Subproject commit 1cf4faba30741f799a33313a2842cf70eeb6c67e +Subproject commit 6007f27d04691f7c4c35722df3dc65bf4b558f18 From b570bfa1a9f0fad0af88f109788a8af05256f040 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Mon, 9 Jan 2023 23:12:09 +0100 Subject: [PATCH 007/155] Fix ObjectDisposedException on Dispose --- .../DBusIme/DBusTextInputMethodBase.cs | 7 +++++-- .../DBusIme/Fcitx/FcitxX11TextInputMethod.cs | 8 +++++++- .../DBusIme/IBus/IBusX11TextInputMethod.cs | 6 ++++++ src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 1 - 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs index 08d2a0c219..9fd58402c6 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs @@ -60,7 +60,7 @@ namespace Avalonia.FreeDesktop.DBusIme { foreach (var name in _knownNames) { - var dbus = new OrgFreedesktopDBus(Connection, name, "/org/freedesktop/DBus"); + var dbus = new OrgFreedesktopDBus(Connection, "org.freedesktop.DBus", "/org/freedesktop/DBus"); _disposables.Add(await dbus.WatchNameOwnerChangedAsync(OnNameChange)); var nameOwner = await dbus.GetNameOwnerAsync(name); OnNameChange(null, (name, null, nameOwner)); @@ -74,6 +74,9 @@ namespace Avalonia.FreeDesktop.DBusIme private async void OnNameChange(Exception? e, (string ServiceName, string? OldOwner, string? NewOwner) args) { + if (e is not null) + return; + if (args.NewOwner is not null && _currentName is null) { _onlineNamesQueue.Enqueue(args.ServiceName); @@ -162,7 +165,7 @@ namespace Avalonia.FreeDesktop.DBusIme protected void AddDisposable(IDisposable? d) { - if(d is { }) + if (d is { }) _disposables.Add(d); } diff --git a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs index 6b30c5424a..be88b94707 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs @@ -134,6 +134,12 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx }); } - private void OnCommitString(Exception? e, string s) => FireCommit(s); + private void OnCommitString(Exception? e, string s) + { + if (e is not null) + return; + + FireCommit(s); + } } } diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 4db0d95d3c..57bb10a885 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -28,6 +28,9 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus private void OnForwardKey(Exception? e, (uint keyval, uint keycode, uint state) k) { + if (e is not null) + return; + var state = (IBusModifierMask)k.state; KeyModifiers mods = default; if (state.HasAllFlags(IBusModifierMask.ControlMask)) @@ -48,6 +51,9 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus private void OnCommitText(Exception? e, object wtf) { + if (e is not null) + return; + // Hello darkness, my old friend if (wtf.GetType().GetField("Item3") is { } prop) { diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index 16b1a1d686..a34a9a0a84 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using Avalonia.Reactive; using System.Linq; using Avalonia.Controls.Platform; using Avalonia.Logging; From 86b61050a0ec49fdefe225c79e01ea67af4deb0a Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Thu, 19 Jan 2023 19:47:35 +0100 Subject: [PATCH 008/155] Use DBusVariantItem to manually encode/decode variants Note: Tray-Icon submenus don't work, needs investigation --- .../DBusIme/IBus/IBusX11TextInputMethod.cs | 13 +-- src/Avalonia.FreeDesktop/DBusInterfaces.cs | 3 +- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 72 +++++++------- .../DBusPlatformSettings.cs | 85 ++++++++-------- src/Avalonia.FreeDesktop/DBusSettings.cs | 16 --- src/Avalonia.FreeDesktop/DBusSystemDialog.cs | 74 +++++++------- .../org.freedesktop.portal.Settings.xml | 99 +++++++++++++++++++ src/Linux/Tmds.DBus | 2 +- src/tools/Tmds.DBus.SourceGenerator | 2 +- 9 files changed, 226 insertions(+), 140 deletions(-) delete mode 100644 src/Avalonia.FreeDesktop/DBusSettings.cs create mode 100644 src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 57bb10a885..59e9ecd1cf 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -4,6 +4,8 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Tmds.DBus.Protocol; +using Tmds.DBus.SourceGenerator; + namespace Avalonia.FreeDesktop.DBusIme.IBus { @@ -49,18 +51,13 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus }); } - private void OnCommitText(Exception? e, object wtf) + private void OnCommitText(Exception? e, DBusVariantItem variantItem) { if (e is not null) return; - // Hello darkness, my old friend - if (wtf.GetType().GetField("Item3") is { } prop) - { - var text = prop.GetValue(wtf) as string; - if (!string.IsNullOrEmpty(text)) - FireCommit(text!); - } + if (variantItem.Value is DBusStructItem { Count: >= 3 } structItem && structItem[2] is DBusStringItem stringItem) + FireCommit(stringItem.Value); } protected override Task DisconnectAsync() => _service?.DestroyAsync() ?? Task.CompletedTask; diff --git a/src/Avalonia.FreeDesktop/DBusInterfaces.cs b/src/Avalonia.FreeDesktop/DBusInterfaces.cs index 3bece27ab1..fb2f0ca2f3 100644 --- a/src/Avalonia.FreeDesktop/DBusInterfaces.cs +++ b/src/Avalonia.FreeDesktop/DBusInterfaces.cs @@ -9,9 +9,10 @@ namespace Avalonia.FreeDesktop [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputMethod.xml")] [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputContext1.xml")] [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputMethod1.xml")] + [DBusInterface("./DBusXml/org.freedesktop.IBus.Portal.xml")] [DBusInterface("./DBusXml/org.freedesktop.portal.FileChooser.xml")] [DBusInterface("./DBusXml/org.freedesktop.portal.Request.xml")] - [DBusInterface("./DBusXml/org.freedesktop.IBus.Portal.xml")] + [DBusInterface("./DBusXml/org.freedesktop.portal.Settings.xml")] [DBusHandler("./DBusXml/DBusMenu.xml")] [DBusHandler("./DBusXml/StatusNotifierItem.xml")] internal class DBusInterfaces { } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index cb6def056f..1978dfba81 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -9,8 +9,7 @@ using Avalonia.Input; using Avalonia.Platform; using Avalonia.Threading; using Tmds.DBus.Protocol; - -#pragma warning disable 1998 +using Tmds.DBus.SourceGenerator; namespace Avalonia.FreeDesktop { @@ -60,7 +59,7 @@ namespace Avalonia.FreeDesktop public override string Path { get; } - protected override (uint revision, (int, Dictionary, object[]) layout) OnGetLayout(int parentId, int recursionDepth, string[] propertyNames) + protected override (uint revision, (int, Dictionary, DBusVariantItem[]) layout) OnGetLayout(int parentId, int recursionDepth, string[] propertyNames) { var menu = GetMenu(parentId); var layout = GetLayout(menu.item, menu.menu, recursionDepth, propertyNames); @@ -73,15 +72,15 @@ namespace Avalonia.FreeDesktop return (_revision, layout); } - protected override (int, Dictionary)[] OnGetGroupProperties(int[] ids, string[] propertyNames) => + protected override (int, Dictionary)[] OnGetGroupProperties(int[] ids, string[] propertyNames) => ids.Select(id => (id, GetProperties(GetMenu(id), propertyNames))).ToArray(); - protected override object OnGetProperty(int id, string name) => GetProperty(GetMenu(id), name) ?? 0; + protected override DBusVariantItem OnGetProperty(int id, string name) => GetProperty(GetMenu(id), name) ?? new DBusVariantItem("i", new DBusInt32Item(0)); - protected override void OnEvent(int id, string eventId, object data, uint timestamp) => + protected override void OnEvent(int id, string eventId, DBusVariantItem data, uint timestamp) => Dispatcher.UIThread.Post(() => HandleEvent(id, eventId)); - protected override int[] OnEventGroup((int, string, object, uint)[] events) + protected override int[] OnEventGroup((int, string, DBusVariantItem, uint)[] events) { foreach (var e in events) Dispatcher.UIThread.Post(() => HandleEvent(e.Item1, e.Item2)); @@ -115,14 +114,14 @@ namespace Avalonia.FreeDesktop if (_disposed) return; _disposed = true; - // Fire and forget - _registrar?.UnregisterWindowAsync(_xid); + _ = _registrar?.UnregisterWindowAsync(_xid); } public bool IsNativeMenuExported { get; private set; } + public event EventHandler? OnIsNativeMenuExportedChanged; public void SetNativeMenu(NativeMenu? menu) @@ -203,31 +202,31 @@ namespace Avalonia.FreeDesktop QueueReset(); } - private static readonly string[] AllProperties = { + private static readonly string[] s_allProperties = { "type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data" }; - private object? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) + private DBusVariantItem? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) { var (it, menu) = i; if (it is NativeMenuItemSeparator) { if (name == "type") - return "separator"; + return new DBusVariantItem("s", new DBusStringItem("separator")); } else if (it is NativeMenuItem item) { if (name == "type") return null; if (name == "label") - return item.Header ?? ""; + return new DBusVariantItem("s", new DBusStringItem(item.Header ?? "")); if (name == "enabled") { if (item.Menu is not null && item.Menu.Items.Count == 0) - return false; + return new DBusVariantItem("b", new DBusBoolItem(false)); if (!item.IsEnabled) - return false; + return new DBusVariantItem("b", new DBusBoolItem(false)); return null; } if (name == "shortcut") @@ -236,30 +235,30 @@ namespace Avalonia.FreeDesktop return null; if (item.Gesture.KeyModifiers == 0) return null; - var lst = new List(); + var lst = new List(); var mod = item.Gesture; if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Control)) - lst.Add("Control"); + lst.Add(new DBusStringItem("Control")); if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Alt)) - lst.Add("Alt"); + lst.Add(new DBusStringItem("Alt")); if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Shift)) - lst.Add("Shift"); + lst.Add(new DBusStringItem("Shift")); if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Meta)) - lst.Add("Super"); - lst.Add(item.Gesture.Key.ToString()); - return new[] { lst.ToArray() }; + lst.Add(new DBusStringItem("Super")); + lst.Add(new DBusStringItem(item.Gesture.Key.ToString())); + return new DBusVariantItem("aas", new DBusArrayItem(DBusType.Array, new[] { new DBusArrayItem(DBusType.String, lst) })); } if (name == "toggle-type") { if (item.ToggleType == NativeMenuItemToggleType.CheckBox) - return "checkmark"; + return new DBusVariantItem("s", new DBusStringItem("checkmark")); if (item.ToggleType == NativeMenuItemToggleType.Radio) - return "radio"; + return new DBusVariantItem("s", new DBusStringItem("radio")); } if (name == "toggle-state" && item.ToggleType != NativeMenuItemToggleType.None) - return item.IsChecked ? 1 : 0; + return new DBusVariantItem("i", new DBusInt32Item(item.IsChecked ? 1 : 0)); if (name == "icon-data") { @@ -273,23 +272,24 @@ namespace Avalonia.FreeDesktop using var ms = new MemoryStream(); icon.Save(ms); - return ms.ToArray(); + return new DBusVariantItem("ay", + new DBusArrayItem(DBusType.Byte, ms.ToArray().Select(static x => new DBusByteItem(x)))); } } } if (name == "children-display") - return menu is not null ? "submenu" : null; + return menu is not null ? new DBusVariantItem("s", new DBusStringItem("submenu")) : null; } return null; } - private Dictionary GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) + private Dictionary GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) { if (names.Length == 0) - names = AllProperties; - var properties = new Dictionary(); + names = s_allProperties; + var properties = new Dictionary(); foreach (var n in names) { var v = GetProperty(i, n); @@ -300,17 +300,23 @@ namespace Avalonia.FreeDesktop return properties; } - private (int, Dictionary, object[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames) + private (int, Dictionary, DBusVariantItem[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames) { var id = item is null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); - var children = depth == 0 || menu is null ? Array.Empty() : new object[menu.Items.Count]; + var children = depth == 0 || menu is null ? Array.Empty() : new DBusVariantItem[menu.Items.Count]; if (menu is not null) { for (var c = 0; c < children.Length; c++) { var ch = menu.Items[c]; - children[c] = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames); + var layout = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames); + children[c] = new DBusVariantItem("(ia{sv}av)", new DBusStructItem(new DBusItem[] + { + new DBusInt32Item(layout.Item1), + new DBusArrayItem(DBusType.DictEntry, layout.Item2.Select(static x => new DBusDictEntryItem(new DBusStringItem(x.Key), x.Value))), + new DBusArrayItem(DBusType.Variant, layout.Item3) + })); } } diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs index 039fc7c088..a25bb68458 100644 --- a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs +++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs @@ -2,44 +2,35 @@ using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.Platform; +using Tmds.DBus.SourceGenerator; -namespace Avalonia.FreeDesktop; - -internal class DBusPlatformSettings : DefaultPlatformSettings +namespace Avalonia.FreeDesktop { - private readonly IDBusSettings? _settings; - private PlatformColorValues? _lastColorValues; - - public DBusPlatformSettings() + internal class DBusPlatformSettings : DefaultPlatformSettings { - _settings = DBusHelper.TryInitialize()? - .CreateProxy("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + private readonly OrgFreedesktopPortalSettings? _settings; + private PlatformColorValues? _lastColorValues; - if (_settings is not null) + public DBusPlatformSettings() { - _ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); + if (DBusHelper.Connection is null) + return; - _ = TryGetInitialValue(); + _settings = new OrgFreedesktopPortalSettings(DBusHelper.Connection, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + _ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); + _ = TryGetInitialValueAsync(); } - } - - public override PlatformColorValues GetColorValues() - { - return _lastColorValues ?? base.GetColorValues(); - } - private async Task TryGetInitialValue() - { - var colorSchemeTask = _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme"); - if (colorSchemeTask.Status == TaskStatus.RanToCompletion) + public override PlatformColorValues GetColorValues() { - _lastColorValues = GetColorValuesFromSetting(colorSchemeTask.Result); + return _lastColorValues ?? base.GetColorValues(); } - else + + private async Task TryGetInitialValueAsync() { try { - var value = await colorSchemeTask; + var value = await _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme"); _lastColorValues = GetColorValuesFromSetting(value); OnColorValuesChanged(_lastColorValues); } @@ -49,29 +40,31 @@ internal class DBusPlatformSettings : DefaultPlatformSettings Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get setting value", ex); } } - } - - private void SettingsChangedHandler((string @namespace, string key, object value) tuple) - { - if (tuple.@namespace == "org.freedesktop.appearance" - && tuple.key == "color-scheme") + + private void SettingsChangedHandler(Exception? exception, (string @namespace, string key, DBusVariantItem value) valueTuple) { - /* - 0: No preference - 1: Prefer dark appearance - 2: Prefer light appearance - */ - _lastColorValues = GetColorValuesFromSetting(tuple.value); - OnColorValuesChanged(_lastColorValues); + if (exception is not null) + return; + + if (valueTuple is ("org.freedesktop.appearance", "color-scheme", { } value)) + { + /* + 0: No preference + 1: Prefer dark appearance + 2: Prefer light appearance + */ + _lastColorValues = GetColorValuesFromSetting(value); + OnColorValuesChanged(_lastColorValues); + } } - } - - private static PlatformColorValues GetColorValuesFromSetting(object value) - { - var isDark = value?.ToString() == "1"; - return new PlatformColorValues + + private static PlatformColorValues GetColorValuesFromSetting(DBusVariantItem value) { - ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light - }; + var isDark = ((value.Value as DBusVariantItem)!.Value as DBusUInt32Item)!.Value == 1; + return new PlatformColorValues + { + ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light + }; + } } } diff --git a/src/Avalonia.FreeDesktop/DBusSettings.cs b/src/Avalonia.FreeDesktop/DBusSettings.cs deleted file mode 100644 index 05911981c7..0000000000 --- a/src/Avalonia.FreeDesktop/DBusSettings.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Tmds.DBus; - -namespace Avalonia.FreeDesktop; - -[DBusInterface("org.freedesktop.portal.Settings")] -internal interface IDBusSettings : IDBusObject -{ - Task<(string @namespace, IDictionary)> ReadAllAsync(string[] namespaces); - - Task ReadAsync(string @namespace, string key); - - Task WatchSettingChangedAsync(Action<(string @namespace, string key, object value)> handler, Action? onError = null); -} diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index 81233f3b2f..763480a33d 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -2,12 +2,12 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Tmds.DBus.Protocol; +using Tmds.DBus.SourceGenerator; namespace Avalonia.FreeDesktop { @@ -44,12 +44,12 @@ namespace Avalonia.FreeDesktop { var parentWindow = $"x11:{_handle.Handle:X}"; ObjectPath objectPath; - var chooserOptions = new Dictionary(); + var chooserOptions = new Dictionary(); var filters = ParseFilters(options.FileTypeFilter); - if (filters.Any()) + if (filters is not null) chooserOptions.Add("filters", filters); - chooserOptions.Add("multiple", options.AllowMultiple); + chooserOptions.Add("multiple", new DBusVariantItem("b", new DBusBoolItem(options.AllowMultiple))); objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); @@ -58,9 +58,8 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - tsc.TrySetException(e); - else - tsc.TrySetResult(x.results["uris"] as string[]); + return; + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task ?? Array.Empty(); @@ -71,15 +70,15 @@ namespace Avalonia.FreeDesktop { var parentWindow = $"x11:{_handle.Handle:X}"; ObjectPath objectPath; - var chooserOptions = new Dictionary(); + var chooserOptions = new Dictionary(); var filters = ParseFilters(options.FileTypeChoices); - if (filters.Any()) + if (filters is not null) chooserOptions.Add("filters", filters); if (options.SuggestedFileName is { } currentName) - chooserOptions.Add("current_name", currentName); + chooserOptions.Add("current_name", new DBusVariantItem("s", new DBusStringItem(currentName))); if (options.SuggestedStartLocation?.TryGetUri(out var currentFolder) == true) - chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString())); + chooserOptions.Add("current_folder", new DBusVariantItem("s", new DBusStringItem(currentFolder.ToString()))); objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath); @@ -87,9 +86,8 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - tsc.TrySetException(e); - else - tsc.TrySetResult(x.results["uris"] as string[]); + return; + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task; @@ -106,10 +104,10 @@ namespace Avalonia.FreeDesktop public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { var parentWindow = $"x11:{_handle.Handle:X}"; - var chooserOptions = new Dictionary + var chooserOptions = new Dictionary { - { "directory", true }, - { "multiple", options.AllowMultiple } + { "directory", new DBusVariantItem("b", new DBusBoolItem(true)) }, + { "multiple", new DBusVariantItem("b", new DBusBoolItem(options.AllowMultiple)) } }; var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); @@ -118,9 +116,8 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - tsc.TrySetException(e); - else - tsc.TrySetResult(x.results["uris"] as string[]); + return; + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task ?? Array.Empty(); @@ -131,29 +128,38 @@ namespace Avalonia.FreeDesktop .Select(static path => new BclStorageFolder(new DirectoryInfo(path))).ToList(); } - private static (string name, (uint style, string extension)[])[] ParseFilters(IReadOnlyList? fileTypes) + private static DBusVariantItem? ParseFilters(IReadOnlyList? fileTypes) { + const uint GlobStyle = 0u; + const uint MimeStyle = 1u; + // Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] if (fileTypes is null) - return Array.Empty<(string name, (uint style, string extension)[])>(); + return null; + + var any = false; + var filters = new DBusArrayItem(DBusType.Struct, new List()); - var filters = new List<(string name, (uint style, string extension)[])>(); foreach (var fileType in fileTypes) { - const uint GlobStyle = 0u; - const uint MimeStyle = 1u; - - var extensions = Enumerable.Empty<(uint, string)>(); + var extensions = new List(); + if (fileType.Patterns?.Count > 0) + extensions.AddRange( + fileType.Patterns.Select(static pattern => + new DBusStructItem(new DBusItem[] { new DBusUInt32Item(GlobStyle), new DBusStringItem(pattern) }))); + else if (fileType.MimeTypes?.Count > 0) + extensions.AddRange( + fileType.MimeTypes.Select(static mimeType => + new DBusStructItem(new DBusItem[] { new DBusUInt32Item(MimeStyle), new DBusStringItem(mimeType) }))); + else + continue; - if (fileType.Patterns is not null) - extensions = extensions.Concat(fileType.Patterns.Select(static x => (globStyle: GlobStyle, x))); - else if (fileType.MimeTypes is not null) - extensions = extensions.Concat(fileType.MimeTypes.Select(static x => (mimeStyle: MimeStyle, x))); - if (extensions.Any()) - filters.Add((fileType.Name, extensions.ToArray())); + any = true; + filters.Add(new DBusStructItem( + new DBusItem[] { new DBusStringItem(fileType.Name), new DBusArrayItem(DBusType.Struct, extensions) })); } - return filters.ToArray(); + return any ? new DBusVariantItem("a(sa(us))", filters) : null; } } } diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml new file mode 100644 index 0000000000..669997a3df --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Linux/Tmds.DBus b/src/Linux/Tmds.DBus index bfca94ab05..e86bcf1bc2 160000 --- a/src/Linux/Tmds.DBus +++ b/src/Linux/Tmds.DBus @@ -1 +1 @@ -Subproject commit bfca94ab052683c7ccef51e4a036098f539cc676 +Subproject commit e86bcf1bc2d86338ab61663a4ae48dbc0bd7e02d diff --git a/src/tools/Tmds.DBus.SourceGenerator b/src/tools/Tmds.DBus.SourceGenerator index 6007f27d04..86e0ded07f 160000 --- a/src/tools/Tmds.DBus.SourceGenerator +++ b/src/tools/Tmds.DBus.SourceGenerator @@ -1 +1 @@ -Subproject commit 6007f27d04691f7c4c35722df3dc65bf4b558f18 +Subproject commit 86e0ded07fc8622e216f93f8fc01e8c1b3ef29b1 From a4665747e3f6aa3f31784d6adddf358e1b84f8a5 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Thu, 19 Jan 2023 23:31:41 +0100 Subject: [PATCH 009/155] Don't dispose DBus connection in tray icon --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 7 ++++--- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 1 - 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 1978dfba81..6f1810d1c1 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; @@ -43,7 +44,7 @@ namespace Avalonia.FreeDesktop _xid = (uint)xid.ToInt32(); Path = GenerateDBusMenuObjPath; SetNativeMenu(new NativeMenu()); - Init(); + _ = InitializeAsync(); } public DBusMenuExporterImpl(Connection connection, string path) @@ -52,7 +53,7 @@ namespace Avalonia.FreeDesktop _appMenu = false; Path = path; SetNativeMenu(new NativeMenu()); - Init(); + _ = InitializeAsync(); } protected override Connection Connection { get; } @@ -92,7 +93,7 @@ namespace Avalonia.FreeDesktop protected override (int[] updatesNeeded, int[] idErrors) OnAboutToShowGroup(int[] ids) => (Array.Empty(), Array.Empty()); - private async void Init() + private async Task InitializeAsync() { Connection.AddMethodHandler(this); if (!_appMenu) diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index a34a9a0a84..f732cfef07 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -134,7 +134,6 @@ namespace Avalonia.FreeDesktop IsActive = false; _isDisposed = true; DestroyTrayIcon(); - _connection?.Dispose(); _serviceWatchDisposable?.Dispose(); } From 042edf132b94c32d31b67c64c7bdae88d379c7bb Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Tue, 24 Jan 2023 01:07:51 +0100 Subject: [PATCH 010/155] Remove unnecessary ItemGroup --- src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index e1e0a636ea..a77de9bb6a 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -24,8 +24,4 @@ - - - - From 4baed8b569e4f613c066e4fd67013e2a8d863c33 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Tue, 24 Jan 2023 01:12:55 +0100 Subject: [PATCH 011/155] Delete DBusMenu.cs --- src/Avalonia.FreeDesktop/DBusMenu.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/Avalonia.FreeDesktop/DBusMenu.cs diff --git a/src/Avalonia.FreeDesktop/DBusMenu.cs b/src/Avalonia.FreeDesktop/DBusMenu.cs deleted file mode 100644 index e69de29bb2..0000000000 From c978887dd6e47e089e272718a2de404872392de3 Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Tue, 24 Jan 2023 01:13:06 +0100 Subject: [PATCH 012/155] Make GetProperty static --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index 6f1810d1c1..deac7346cb 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -207,7 +207,7 @@ namespace Avalonia.FreeDesktop "type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data" }; - private DBusVariantItem? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) + private static DBusVariantItem? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) { var (it, menu) = i; From 9a26fef972a8d02328b26574927c0e338407fabb Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Tue, 24 Jan 2023 01:19:48 +0100 Subject: [PATCH 013/155] Remove NetAnalyzers.props import --- src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj index a77de9bb6a..c0f82a6e67 100644 --- a/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj +++ b/src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj @@ -6,7 +6,6 @@ - From f2050c868ac6ac4d0d134e99304b5c681f5b808d Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 1 Feb 2023 01:22:16 -0500 Subject: [PATCH 014/155] Extract theme related properties to IThemeVariantHost and move property definitions to ThemeVariantScope --- .../Controls/ResourceNodeExtensions.cs | 39 ++++++++----------- src/Avalonia.Base/StyledElement.cs | 33 ---------------- .../Styling/IGlobalThemeVariantProvider.cs | 22 ----------- .../Styling/IThemeVariantHost.cs | 26 +++++++++++++ src/Avalonia.Controls/Application.cs | 19 ++++----- src/Avalonia.Controls/Control.cs | 17 +++++++- src/Avalonia.Controls/ThemeVariantScope.cs | 17 ++++++++ src/Avalonia.Controls/TopLevel.cs | 14 +++++-- .../Diagnostics/Controls/Application.cs | 2 +- .../StaticResourceExtension.cs | 2 +- .../Xaml/ThemeDictionariesTests.cs | 4 +- 11 files changed, 99 insertions(+), 96 deletions(-) delete mode 100644 src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs create mode 100644 src/Avalonia.Base/Styling/IThemeVariantHost.cs diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs index 4b0bab0c92..8aed1545a5 100644 --- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs +++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs @@ -138,18 +138,18 @@ namespace Avalonia.Controls protected override void Initialize() { _target.ResourcesChanged += ResourcesChanged; - if (_target is StyledElement themeStyleable) + if (_target is IThemeVariantHost themeVariantHost) { - themeStyleable.PropertyChanged += PropertyChanged; + themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged; } } protected override void Deinitialize() { _target.ResourcesChanged -= ResourcesChanged; - if (_target is StyledElement themeStyleable) + if (_target is IThemeVariantHost themeVariantHost) { - themeStyleable.PropertyChanged -= PropertyChanged; + themeVariantHost.ActualThemeVariantChanged -= ActualThemeVariantChanged; } } @@ -163,18 +163,15 @@ namespace Avalonia.Controls PublishNext(GetValue()); } - private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + private void ActualThemeVariantChanged(object? sender, EventArgs e) { - if (e.Property == StyledElement.ActualThemeVariantProperty) - { - PublishNext(GetValue()); - } + PublishNext(GetValue()); } private object? GetValue() { - if (_target is not StyledElement themeStyleable - || !_target.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value)) + if (_target is not IThemeVariantHost themeVariantHost + || !_target.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value)) { value = _target.FindResource(_key) ?? AvaloniaProperty.UnsetValue; } @@ -236,9 +233,9 @@ namespace Avalonia.Controls { _owner.ResourcesChanged -= ResourcesChanged; } - if (_owner is StyledElement styleable) + if (_owner is IThemeVariantHost themeVariantHost) { - styleable.PropertyChanged += PropertyChanged; + themeVariantHost.ActualThemeVariantChanged += ActualThemeVariantChanged; } _owner = _target.Owner; @@ -247,20 +244,18 @@ namespace Avalonia.Controls { _owner.ResourcesChanged += ResourcesChanged; } - if (_owner is StyledElement styleable2) + if (_owner is IThemeVariantHost themeVariantHost2) { - styleable2.PropertyChanged += PropertyChanged; + themeVariantHost2.ActualThemeVariantChanged -= ActualThemeVariantChanged; } + PublishNext(); } - private void PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + private void ActualThemeVariantChanged(object? sender, EventArgs e) { - if (e.Property == StyledElement.ActualThemeVariantProperty) - { - PublishNext(); - } + PublishNext(); } private void ResourcesChanged(object? sender, ResourcesChangedEventArgs e) @@ -270,8 +265,8 @@ namespace Avalonia.Controls private object? GetValue() { - if (!(_target.Owner is StyledElement themeStyleable) - || !_target.Owner.TryFindResource(_key, themeStyleable.ActualThemeVariant, out var value)) + if (!(_target.Owner is IThemeVariantHost themeVariantHost) + || !_target.Owner.TryFindResource(_key, themeVariantHost.ActualThemeVariant, out var value)) { value = _target.Owner?.FindResource(_key) ?? AvaloniaProperty.UnsetValue; } diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 5bf022cd51..17d0699336 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -71,23 +71,6 @@ namespace Avalonia public static readonly StyledProperty ThemeProperty = AvaloniaProperty.Register(nameof(Theme)); - /// - /// Defines the property. - /// - public static readonly StyledProperty ActualThemeVariantProperty = - AvaloniaProperty.Register( - nameof(ThemeVariant), - inherits: true, - defaultValue: ThemeVariant.Light); - - /// - /// Defines the RequestedThemeVariant property. - /// - public static readonly StyledProperty RequestedThemeVariantProperty = - AvaloniaProperty.Register( - nameof(ThemeVariant), - defaultValue: ThemeVariant.Default); - private static readonly ControlTheme s_invalidTheme = new ControlTheme(); private int _initCount; private string? _name; @@ -274,15 +257,6 @@ namespace Avalonia set => SetValue(ThemeProperty, value); } - /// - /// Gets the UI theme that is currently used by the element, which might be different than the . - /// - /// - /// If current control is contained in the ThemeVariantScope, TopLevel or Application with non-default RequestedThemeVariant, that value will be returned. - /// Otherwise, current OS theme variant is returned. - /// - public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty); - /// /// Gets the styled element's logical children. /// @@ -647,13 +621,6 @@ namespace Avalonia if (change.Property == ThemeProperty) OnControlThemeChanged(); - else if (change.Property == RequestedThemeVariantProperty) - { - if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default) - SetValue(ActualThemeVariantProperty, themeVariant); - else - ClearValue(ActualThemeVariantProperty); - } } private protected virtual void OnControlThemeChanged() diff --git a/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs b/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs deleted file mode 100644 index 2467d99b3b..0000000000 --- a/src/Avalonia.Base/Styling/IGlobalThemeVariantProvider.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using Avalonia.Controls; -using Avalonia.Metadata; - -namespace Avalonia.Styling; - -/// -/// Interface for an application host element with a root theme variant. -/// -[Unstable] -public interface IGlobalThemeVariantProvider : IResourceHost -{ - /// - /// Gets the UI theme variant that is used by the control (and its child elements) for resource determination. - /// - ThemeVariant ActualThemeVariant { get; } - - /// - /// Raised when the theme variant is changed on the element or an ancestor of the element. - /// - event EventHandler? ActualThemeVariantChanged; -} diff --git a/src/Avalonia.Base/Styling/IThemeVariantHost.cs b/src/Avalonia.Base/Styling/IThemeVariantHost.cs new file mode 100644 index 0000000000..01583148a8 --- /dev/null +++ b/src/Avalonia.Base/Styling/IThemeVariantHost.cs @@ -0,0 +1,26 @@ +using System; +using Avalonia.Controls; +using Avalonia.Metadata; + +namespace Avalonia.Styling; + +/// +/// Interface for the host element with a theme variant. +/// +[Unstable] +public interface IThemeVariantHost : IResourceHost +{ + /// + /// Gets the UI theme that is currently used by the element, which might be different than the RequestedThemeVariantProperty. + /// + /// + /// If current control is contained in the ThemeVariantScope, TopLevel or Application with non-default RequestedThemeVariant, that value will be returned. + /// Otherwise, current OS theme variant is returned. + /// + ThemeVariant ActualThemeVariant { get; } + + /// + /// Raised when the theme variant is changed on the element or an ancestor of the element. + /// + event EventHandler? ActualThemeVariantChanged; +} diff --git a/src/Avalonia.Controls/Application.cs b/src/Avalonia.Controls/Application.cs index 6d3ba3cf8a..8456a9a3a9 100644 --- a/src/Avalonia.Controls/Application.cs +++ b/src/Avalonia.Controls/Application.cs @@ -29,7 +29,7 @@ namespace Avalonia /// method. /// - Tracks the lifetime of the application. /// - public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IGlobalThemeVariantProvider, IApplicationPlatformEvents + public class Application : AvaloniaObject, IDataContextProvider, IGlobalDataTemplates, IGlobalStyles, IThemeVariantHost, IApplicationPlatformEvents { /// /// The application-global data templates. @@ -50,13 +50,13 @@ namespace Avalonia public static readonly StyledProperty DataContextProperty = StyledElement.DataContextProperty.AddOwner(); - /// + /// public static readonly StyledProperty ActualThemeVariantProperty = - StyledElement.ActualThemeVariantProperty.AddOwner(); + ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); - /// + /// public static readonly StyledProperty RequestedThemeVariantProperty = - StyledElement.RequestedThemeVariantProperty.AddOwner(); + ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); /// public event EventHandler? ResourcesChanged; @@ -95,11 +95,8 @@ namespace Avalonia set => SetValue(RequestedThemeVariantProperty, value); } - /// - public ThemeVariant ActualThemeVariant - { - get => GetValue(ActualThemeVariantProperty); - } + /// + public ThemeVariant ActualThemeVariant => GetValue(ActualThemeVariantProperty); /// /// Gets the current instance of the class. @@ -256,7 +253,7 @@ namespace Avalonia .Bind().ToTransient() .Bind().ToConstant(this) .Bind().ToConstant(this) - .Bind().ToConstant(this) + .Bind().ToConstant(this) .Bind().ToConstant(FocusManager) .Bind().ToConstant(InputManager) .Bind().ToTransient() diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index ed24c3c7c2..bfef190dfd 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -26,7 +26,7 @@ namespace Avalonia.Controls /// - A property to allow user-defined data to be attached to the control. /// - and other context menu related members. /// - public class Control : InputElement, IDataTemplateHost, INamed, IVisualBrushInitialize, ISetterValue + public class Control : InputElement, IDataTemplateHost, INamed, IVisualBrushInitialize, ISetterValue, IThemeVariantHost { /// /// Defines the property. @@ -163,6 +163,10 @@ namespace Avalonia.Controls get => GetValue(TagProperty); set => SetValue(TagProperty, value); } + + public ThemeVariant ActualThemeVariant => GetValue(ThemeVariantScope.ActualThemeVariantProperty); + + public event EventHandler? ActualThemeVariantChanged; /// /// Occurs when the user has completed a context input gesture, such as a right-click. @@ -530,6 +534,17 @@ namespace Avalonia.Controls RaiseEvent(sizeChangedEventArgs); } } + else if (change.Property == ThemeVariantScope.RequestedThemeVariantProperty) + { + if (change.GetNewValue() is {} themeVariant && themeVariant != ThemeVariant.Default) + SetValue(ThemeVariantScope.ActualThemeVariantProperty, themeVariant); + else + ClearValue(ThemeVariantScope.ActualThemeVariantProperty); + } + else if (change.Property == ThemeVariantScope.ActualThemeVariantProperty) + { + ActualThemeVariantChanged?.Invoke(this, EventArgs.Empty); + } } } } diff --git a/src/Avalonia.Controls/ThemeVariantScope.cs b/src/Avalonia.Controls/ThemeVariantScope.cs index b9724251c7..2aac1d3e2f 100644 --- a/src/Avalonia.Controls/ThemeVariantScope.cs +++ b/src/Avalonia.Controls/ThemeVariantScope.cs @@ -7,6 +7,23 @@ namespace Avalonia.Controls /// public class ThemeVariantScope : Decorator { + /// + /// Defines the property. + /// + public static readonly StyledProperty ActualThemeVariantProperty = + AvaloniaProperty.Register( + nameof(ThemeVariant), + inherits: true, + defaultValue: ThemeVariant.Light); + + /// + /// Defines the property. + /// + public static readonly StyledProperty RequestedThemeVariantProperty = + AvaloniaProperty.Register( + nameof(ThemeVariant), + defaultValue: ThemeVariant.Default); + /// /// Gets or sets the UI theme variant that is used by the control (and its child elements) for resource determination. /// The UI theme you specify with ThemeVariant can override the app-level ThemeVariant. diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 676fa1519a..f9f7cfc4c6 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -79,6 +79,14 @@ namespace Avalonia.Controls public static readonly StyledProperty TransparencyBackgroundFallbackProperty = AvaloniaProperty.Register(nameof(TransparencyBackgroundFallback), Brushes.White); + /// + public static readonly StyledProperty ActualThemeVariantProperty = + ThemeVariantScope.ActualThemeVariantProperty.AddOwner(); + + /// + public static readonly StyledProperty RequestedThemeVariantProperty = + ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); + /// /// Defines the event. /// @@ -96,7 +104,7 @@ namespace Avalonia.Controls private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; private readonly IPlatformRenderInterface? _renderInterface; private readonly IGlobalStyles? _globalStyles; - private readonly IGlobalThemeVariantProvider? _applicationThemeHost; + private readonly IThemeVariantHost? _applicationThemeHost; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; private readonly IDisposable? _pointerOverPreProcessorSubscription; private readonly IDisposable? _backGestureSubscription; @@ -153,7 +161,7 @@ namespace Avalonia.Controls _keyboardNavigationHandler = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); _globalStyles = TryGetService(dependencyResolver); - _applicationThemeHost = TryGetService(dependencyResolver); + _applicationThemeHost = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); @@ -633,7 +641,7 @@ namespace Avalonia.Controls private void GlobalActualThemeVariantChanged(object? sender, EventArgs e) { - SetValue(ActualThemeVariantProperty, ((IGlobalThemeVariantProvider)sender!).ActualThemeVariant, BindingPriority.Template); + SetValue(ActualThemeVariantProperty, ((IThemeVariantHost)sender!).ActualThemeVariant, BindingPriority.Template); } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index 7426c4e2ed..e21983700d 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.Controls public event EventHandler? Closed; public static readonly StyledProperty RequestedThemeVariantProperty = - StyledElement.RequestedThemeVariantProperty.AddOwner(); + ThemeVariantScope.RequestedThemeVariantProperty.AddOwner(); public Application(Avalonia.Application application) { diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs index cdd344becc..bef32766dc 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs @@ -34,7 +34,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions var stack = serviceProvider.GetService(); var provideTarget = serviceProvider.GetService(); - var themeVariant = (provideTarget.TargetObject as StyledElement)?.ActualThemeVariant; + var themeVariant = (provideTarget.TargetObject as IThemeVariantHost)?.ActualThemeVariant; var targetType = provideTarget.TargetProperty switch { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs index c5b62cdff2..20d76a3cc7 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ThemeDictionariesTests.cs @@ -185,9 +185,9 @@ public class ThemeDictionariesTests : XamlTestBase { using (AvaloniaLocator.EnterScope()) { - var applicationThemeHost = new Mock(); + var applicationThemeHost = new Mock(); applicationThemeHost.SetupGet(h => h.ActualThemeVariant).Returns(ThemeVariant.Dark); - AvaloniaLocator.CurrentMutable.Bind().ToConstant(applicationThemeHost.Object); + AvaloniaLocator.CurrentMutable.Bind().ToConstant(applicationThemeHost.Object); var themeVariantScope = (ThemeVariantScope)AvaloniaRuntimeXamlLoader.Load(@" Date: Wed, 1 Feb 2023 01:39:11 -0500 Subject: [PATCH 015/155] Fix DataGrid theming --- .../Themes/Fluent.xaml | 71 ++++++++++++------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index dd8575c989..2197b0fe59 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -1,6 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0.6 0.8 @@ -9,19 +51,6 @@ M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z M109 486 19 576 1024 1581 2029 576 1939 486 1024 1401z - - - - - - - - - - - @@ -29,23 +58,10 @@ - + - - - - - - - - - @@ -565,5 +581,6 @@ + From 0c20abec120cd9a0039342c88737c4b0085d6e13 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 1 Feb 2023 02:27:27 -0500 Subject: [PATCH 016/155] Update src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml --- src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml index 2197b0fe59..ca516c8918 100644 --- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml +++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml @@ -22,7 +22,7 @@ - + From f2c3805d6a0086ce27ac9ecb55680def9f31b9ea Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Feb 2023 18:08:13 +0100 Subject: [PATCH 017/155] Update ncrunch config. --- .ncrunch/Avalonia.UnitTests.v3.ncrunchproject | 5 +++++ .ncrunch/GpuInterop.v3.ncrunchproject | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .ncrunch/Avalonia.UnitTests.v3.ncrunchproject create mode 100644 .ncrunch/GpuInterop.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject new file mode 100644 index 0000000000..cff5044edf --- /dev/null +++ b/.ncrunch/Avalonia.UnitTests.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + False + + \ No newline at end of file diff --git a/.ncrunch/GpuInterop.v3.ncrunchproject b/.ncrunch/GpuInterop.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/GpuInterop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From 1e926dac72b4c666d4c213cd72693d4cbf551e27 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 1 Feb 2023 18:19:40 +0100 Subject: [PATCH 018/155] Added failing tests for #10110. --- .../AvaloniaObjectTests_Binding.cs | 18 +++++++++++++ .../Xaml/ResourceDictionaryTests.cs | 25 ++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index 030d6ba215..f0386615c5 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -1285,6 +1285,24 @@ namespace Avalonia.Base.UnitTests subscription.Dispose(); } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Binding_Producing_UnsetValue_Does_Not_Cause_Unsubscribe(BindingPriority priority) + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.FooProperty, source, priority); + + source.OnNext("foo"); + Assert.Equal("foo", target.GetValue(Class1.FooProperty)); + source.OnNext(BindingValue.Unset); + Assert.Equal("foodefault", target.GetValue(Class1.FooProperty)); + source.OnNext("bar"); + Assert.Equal("bar", target.GetValue(Class1.FooProperty)); + } + [Fact] public void Produces_Correct_Values_And_Base_Values_With_Multiple_Animation_Bindings() { diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs index 6cab83751f..bea6bc4dc8 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs @@ -307,7 +307,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.True(buttonResources.ContainsDeferredKey("Red")); } } - + + [Fact] + public void Dynamically_Changing_Referenced_Resources_Works_With_DynamicResource() + { + var xaml = @" + + + Red + + +"; + + var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml); + + Assert.Equal(Colors.Red, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + + userControl.Resources.Remove("color"); + Assert.Equal(default, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + + userControl.Resources.Add("color", Colors.Blue); + Assert.Equal(Colors.Blue, ((ISolidColorBrush)userControl.FindResource("brush")!).Color); + } + private IDisposable StyledWindow(params (string, string)[] assets) { var services = TestServices.StyledWindow.With( From 7b00ef69892358650c10e0f27fb0d5dd1477e3f5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 12:00:40 +0100 Subject: [PATCH 019/155] Make untyped bindings produce default value. This changes the behavior of bindings slightly, in that previously a binding which produced `UnsetValue` (or a binding error) caused the value of any lower priority value of value to take effect. After this change, the binding reverts to the property's default value. This behavior more closely matches WPF and fixes #10110. --- src/Avalonia.Base/AvaloniaObject.cs | 4 +- .../PropertyStore/BindingEntryBase.cs | 47 +++++++++---------- .../LocalValueBindingObserver.cs | 42 ++++++++++++----- .../LocalValueUntypedBindingObserver.cs | 25 +++++++--- .../SourceUntypedBindingEntry.cs | 2 + .../PropertyStore/TypedBindingEntry.cs | 2 + .../PropertyStore/UntypedBindingEntry.cs | 5 ++ .../AvaloniaObjectTests_Binding.cs | 18 ++++++- .../AvaloniaObjectTests_Validation.cs | 4 +- 9 files changed, 99 insertions(+), 50 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index 1946d4ba5c..db6dbe98e0 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -270,8 +270,8 @@ namespace Avalonia /// The property. /// True if the property is set, otherwise false. /// - /// Checks whether a value is assigned to the property, or that there is a binding to the - /// property that is producing a value other than . + /// Returns true if is a styled property which has a value + /// assigned to it or a binding targeting it; otherwise false. /// public bool IsSet(AvaloniaProperty property) { diff --git a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs index 11dc80ef8f..e1ff0970c2 100644 --- a/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs +++ b/src/Avalonia.Base/PropertyStore/BindingEntryBase.cs @@ -16,6 +16,8 @@ namespace Avalonia.PropertyStore private IDisposable? _subscription; private bool _hasValue; private TValue? _value; + private TValue? _defaultValue; + private bool _isDefaultValueInitialized; protected BindingEntryBase( ValueFrame frame, @@ -89,6 +91,7 @@ namespace Avalonia.PropertyStore protected abstract BindingValue ConvertAndValidate(TSource value); protected abstract BindingValue ConvertAndValidate(BindingValue value); + protected abstract TValue GetDefaultValue(Type ownerType); protected virtual void Start(bool produceValue) { @@ -104,17 +107,6 @@ namespace Avalonia.PropertyStore }; } - private void ClearValue() - { - if (_hasValue) - { - _hasValue = false; - _value = default; - if (_subscription is not null) - Frame.Owner?.OnBindingValueCleared(Property, Frame.Priority); - } - } - private void SetValue(BindingValue value) { static void Execute(BindingEntryBase instance, BindingValue value) @@ -124,24 +116,20 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(instance.Frame.Owner.Owner, instance.Property, value); - if (value.HasValue) - { - if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, value.Value)) - { - instance._value = value.Value; - instance._hasValue = true; - if (instance._subscription is not null && instance._subscription != s_creatingQuiet) - instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); - } - } - else if (value.Type != BindingValueType.DoNothing) + var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); + + if (!instance._hasValue || !EqualityComparer.Default.Equals(instance._value, effectiveValue)) { - instance.ClearValue(); + instance._value = effectiveValue; + instance._hasValue = true; if (instance._subscription is not null && instance._subscription != s_creatingQuiet) - instance.Frame.Owner?.OnBindingValueCleared(instance.Property, instance.Frame.Priority); + instance.Frame.Owner?.OnBindingValueChanged(instance, instance.Frame.Priority); } } + if (value.Type == BindingValueType.DoNothing) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -161,5 +149,16 @@ namespace Avalonia.PropertyStore _subscription = null; Frame.OnBindingCompleted(this); } + + private TValue GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = GetDefaultValue(Frame.Owner!.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index f89cb029b6..0771ade647 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -10,6 +10,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueBindingObserver(ValueStore owner, StyledProperty property) { @@ -41,26 +43,28 @@ namespace Avalonia.PropertyStore public void OnNext(T value) { - static void Execute(ValueStore owner, StyledProperty property, T value) + static void Execute(LocalValueBindingObserver instance, T value) { - if (property.ValidateValue?.Invoke(value) != false) - owner.SetValue(property, value, BindingPriority.LocalValue); - else - owner.ClearLocalValue(property); + var owner = instance._owner; + var property = instance.Property; + + if (property.ValidateValue?.Invoke(value) == false) + value = instance.GetCachedDefaultValue(); + + owner.SetValue(property, value, BindingPriority.LocalValue); } if (Dispatcher.UIThread.CheckAccess()) { - Execute(_owner, Property, value); + Execute(this, value); } else { // To avoid allocating closure in the outer scope we need to capture variables // locally. This allows us to skip most of the allocations when on UI thread. - var instance = _owner; - var property = Property; + var instance = this; var newValue = value; - Dispatcher.UIThread.Post(() => Execute(instance, property, newValue)); + Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } @@ -73,12 +77,13 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(owner.Owner, property, value); - if (value.HasValue) - owner.SetValue(property, value.Value, BindingPriority.LocalValue); - else if (value.Type != BindingValueType.DataValidationError) - owner.ClearLocalValue(property); + var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); + owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); } + if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -92,5 +97,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs index 2d157b2519..46e6ed810a 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueUntypedBindingObserver.cs @@ -1,5 +1,4 @@ using System; -using System.Security.Cryptography; using Avalonia.Data; using Avalonia.Threading; @@ -10,6 +9,8 @@ namespace Avalonia.PropertyStore { private readonly ValueStore _owner; private IDisposable? _subscription; + private T? _defaultValue; + private bool _isDefaultValueInitialized; public LocalValueUntypedBindingObserver(ValueStore owner, StyledProperty property) { @@ -49,11 +50,7 @@ namespace Avalonia.PropertyStore if (value == AvaloniaProperty.UnsetValue) { - owner.ClearLocalValue(property); - } - else if (value == BindingOperations.DoNothing) - { - // Do nothing! + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); } else if (UntypedValueUtils.TryConvertAndValidate(property, value, out var typedValue)) { @@ -61,11 +58,14 @@ namespace Avalonia.PropertyStore } else { - owner.ClearLocalValue(property); + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); LoggingUtils.LogInvalidValue(owner.Owner, property, typeof(T), value); } } + if (value == BindingOperations.DoNothing) + return; + if (Dispatcher.UIThread.CheckAccess()) { Execute(this, value); @@ -79,5 +79,16 @@ namespace Avalonia.PropertyStore Dispatcher.UIThread.Post(() => Execute(instance, newValue)); } } + + private T GetCachedDefaultValue() + { + if (!_isDefaultValueInitialized) + { + _defaultValue = Property.GetDefaultValue(_owner.Owner.GetType()); + _isDefaultValueInitialized = true; + } + + return _defaultValue!; + } } } diff --git a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs index b56d0d4529..b82714817b 100644 --- a/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/SourceUntypedBindingEntry.cs @@ -31,5 +31,7 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override TTarget GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs index 697725c87b..550f5c0001 100644 --- a/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/TypedBindingEntry.cs @@ -48,5 +48,7 @@ namespace Avalonia.PropertyStore return value; } + + protected override T GetDefaultValue(Type ownerType) => Property.GetDefaultValue(ownerType); } } diff --git a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs index f8becb2e06..a77d7fddb6 100644 --- a/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs +++ b/src/Avalonia.Base/PropertyStore/UntypedBindingEntry.cs @@ -29,5 +29,10 @@ namespace Avalonia.PropertyStore { throw new NotSupportedException(); } + + protected override object? GetDefaultValue(Type ownerType) + { + return ((IStyledPropertyMetadata)Property.GetMetadata(ownerType)).DefaultValue; + } } } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs index f0386615c5..baaed5104d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs @@ -372,6 +372,20 @@ namespace Avalonia.Base.UnitTests Assert.Null(target.GetValue(property)); } + [Fact] + public void LocalValue_Bind_Generic_To_ValueType_Accepts_UnsetValue() + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.QuxProperty, source); + source.OnNext(6.7); + source.OnNext(BindingValue.Unset); + + Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); + } + [Fact] public void LocalValue_Bind_NonGeneric_To_ValueType_Accepts_UnsetValue() { @@ -383,7 +397,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] @@ -397,7 +411,7 @@ namespace Avalonia.Base.UnitTests source.OnNext(AvaloniaProperty.UnsetValue); Assert.Equal(5.6, target.GetValue(Class1.QuxProperty)); - Assert.False(target.IsSet(Class1.QuxProperty)); + Assert.True(target.IsSet(Class1.QuxProperty)); } [Fact] diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index e8175cf477..3fc950573d 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -66,7 +66,7 @@ namespace Avalonia.Base.UnitTests } [Fact] - public void Reverts_To_Lower_Priority_If_Style_Binding_Fails_Validation() + public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_2() { var target = new Class1(); var source = new Subject(); @@ -75,7 +75,7 @@ namespace Avalonia.Base.UnitTests target.Bind(Class1.FooProperty, source, BindingPriority.StyleTrigger); source.OnNext(150); - Assert.Equal(10, target.GetValue(Class1.FooProperty)); + Assert.Equal(11, target.GetValue(Class1.FooProperty)); } [Fact] From bca57ea0e9c65c5b17422303261b1a7a058d842b Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 16:27:48 +0100 Subject: [PATCH 020/155] Added additional failing test. --- .../AvaloniaObjectTests_Validation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index 3fc950573d..513aeb65ab 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -78,6 +78,20 @@ namespace Avalonia.Base.UnitTests Assert.Equal(11, target.GetValue(Class1.FooProperty)); } + [Theory] + [InlineData(BindingPriority.LocalValue)] + [InlineData(BindingPriority.Style)] + public void Reverts_To_DefaultValue_If_Style_Binding_Fails_Validation_3(BindingPriority priority) + { + var target = new Class1(); + var source = new Subject>(); + + target.Bind(Class1.FooProperty, source, priority); + source.OnNext(150); + + Assert.Equal(11, target.GetValue(Class1.FooProperty)); + } + [Fact] public void Reverts_To_DefaultValue_Even_In_Presence_Of_Other_Bindings() { From b2c1d80f5cfdf3c0dccbed07cb908b69f7db3e2d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 2 Feb 2023 16:32:02 +0100 Subject: [PATCH 021/155] Fix validation of local value bindings. Local value bindings with a type of `BindingValue` were not being validated. --- .../PropertyStore/LocalValueBindingObserver.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs index 0771ade647..5908d9e535 100644 --- a/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs +++ b/src/Avalonia.Base/PropertyStore/LocalValueBindingObserver.cs @@ -77,8 +77,17 @@ namespace Avalonia.PropertyStore LoggingUtils.LogIfNecessary(owner.Owner, property, value); - var effectiveValue = value.HasValue ? value.Value : instance.GetCachedDefaultValue(); - owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); + if (value.HasValue) + { + var effectiveValue = value.Value; + if (property.ValidateValue?.Invoke(effectiveValue) == false) + effectiveValue = instance.GetCachedDefaultValue(); + owner.SetValue(property, effectiveValue, BindingPriority.LocalValue); + } + else + { + owner.SetValue(property, instance.GetCachedDefaultValue(), BindingPriority.LocalValue); + } } if (value.Type is BindingValueType.DoNothing or BindingValueType.DataValidationError) From 3913fb333eadab53f070e4def78f59e52917a80a Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:50:31 -0500 Subject: [PATCH 022/155] Implement SelectedValue --- .../Primitives/SelectingItemsControl.cs | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 5210362505..2fac60c8d8 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Xml.Linq; using Avalonia.Controls.Generators; using Avalonia.Controls.Selection; +using Avalonia.Controls.Utils; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; @@ -66,6 +67,19 @@ namespace Avalonia.Controls.Primitives (o, v) => o.SelectedItem = v, defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true); + /// + /// Defines the property + /// + public static readonly StyledProperty SelectedValueProperty = + AvaloniaProperty.Register(nameof(SelectedValue), + defaultBindingMode: BindingMode.TwoWay); + + /// + /// Defines the property + /// + public static readonly StyledProperty SelectedValueBindingProperty = + AvaloniaProperty.Register(nameof(SelectedValueBinding)); + /// /// Defines the property. /// @@ -129,6 +143,8 @@ namespace Avalonia.Controls.Primitives private bool _ignoreContainerSelectionChanged; private UpdateState? _updateState; private bool _hasScrolledToSelectedItem; + private BindingHelper? _bindingHelper; + private bool _isSelectionChangeActive; /// /// Initializes static members of the class. @@ -209,6 +225,19 @@ namespace Avalonia.Controls.Primitives } } + [AssignBinding] + public IBinding? SelectedValueBinding + { + get => GetValue(SelectedValueBindingProperty); + set => SetValue(SelectedValueBindingProperty, value); + } + + public object? SelectedValue + { + get => GetValue(SelectedValueProperty); + set => SetValue(SelectedValueProperty, value); + } + /// /// Gets or sets the selected items. /// @@ -609,6 +638,60 @@ namespace Avalonia.Controls.Primitives { WrapFocus = WrapSelection; } + else if (change.Property == SelectedValueProperty) + { + if (_isSelectionChangeActive) + return; + + if (_updateState is not null) + { + _updateState.SelectedValue = change.NewValue; + return; + } + + SelectItemWithValue(change.NewValue); + } + else if (change.Property == SelectedValueBindingProperty) + { + var idx = SelectedIndex; + + // If no selection is active, don't do anything as SelectedValue is already null + if (idx == -1) + { + return; + } + + var value = change.GetNewValue(); + if (value is null) + { + // Clearing SelectedValueBinding makes the SelectedValue the item itself + SelectedValue = SelectedItem; + return; + } + + var selectedItem = SelectedItem; + + try + { + _isSelectionChangeActive = true; + + if (_bindingHelper is null) + { + _bindingHelper = new BindingHelper(value); + } + else + { + _bindingHelper.UpdateBinding(value); + } + + // Re-evaluate SelectedValue with the new binding + SelectedValue = _bindingHelper.Evaluate(selectedItem); + } + finally + { + _isSelectionChangeActive = false; + } + } } /// @@ -815,6 +898,10 @@ namespace Avalonia.Controls.Primitives new BindingValue(SelectedItems)); _oldSelectedItems = SelectedItems; } + else if (e.PropertyName == nameof(ISelectionModel.Source)) + { + ClearValue(SelectedValueProperty); + } } /// @@ -845,6 +932,11 @@ namespace Avalonia.Controls.Primitives Mark(i, false); } + if (!_isSelectionChangeActive) + { + UpdateSelectedValueFromItem(); + } + var route = BuildEventRoute(SelectionChangedEvent); if (route.HasHandlers) @@ -871,6 +963,109 @@ namespace Avalonia.Controls.Primitives } } + private void SelectItemWithValue(object? value) + { + if (ItemCount == 0 || _isSelectionChangeActive) + return; + + try + { + _isSelectionChangeActive = true; + var si = FindItemWithValue(value); + if (si != AvaloniaProperty.UnsetValue) + { + SelectedItem = si; + } + else + { + SelectedItem = null; + } + } + finally + { + _isSelectionChangeActive = false; + } + } + + private object FindItemWithValue(object? value) + { + if (ItemCount == 0 || value is null) + { + return AvaloniaProperty.UnsetValue; + } + + var items = Items; + var binding = SelectedValueBinding; + + if (binding is null) + { + // No SelectedValueBinding set, SelectedValue is the item itself + // Still verify the value passed in is in the Items list + var index = items!.IndexOf(value); + + if (index >= 0) + { + return value; + } + else + { + return AvaloniaProperty.UnsetValue; + } + } + + _bindingHelper ??= new BindingHelper(binding); + + // Matching UWP behavior, if duplicates are present, return the first item matching + // the SelectedValue provided + foreach (var item in items!) + { + var itemValue = _bindingHelper.Evaluate(item); + + if (itemValue.Equals(value)) + { + return item; + } + } + + return AvaloniaProperty.UnsetValue; + } + + private void UpdateSelectedValueFromItem() + { + if (_isSelectionChangeActive) + return; + + var binding = SelectedValueBinding; + var item = SelectedItem; + + if (binding is null || item is null) + { + // No SelectedValueBinding, SelectedValue is Item itself + try + { + _isSelectionChangeActive = true; + SelectedValue = item; + } + finally + { + _isSelectionChangeActive = false; + } + return; + } + + _bindingHelper ??= new BindingHelper(binding); + + try + { + _isSelectionChangeActive = true; + SelectedValue = _bindingHelper.Evaluate(item); + } + finally + { + _isSelectionChangeActive = false; + } + } + private void AutoScrollToSelectedItemIfNecessary() { if (AutoScrollToSelectedItem && @@ -1037,6 +1232,13 @@ namespace Avalonia.Controls.Primitives Selection.Clear(); } + if (state.SelectedValue.HasValue) + { + var item = FindItemWithValue(state.SelectedValue.Value); + if (item != AvaloniaProperty.UnsetValue) + state.SelectedItem = item; + } + if (state.SelectedIndex.HasValue) { SelectedIndex = state.SelectedIndex.Value; @@ -1098,6 +1300,7 @@ namespace Avalonia.Controls.Primitives { private Optional _selectedIndex; private Optional _selectedItem; + private Optional _selectedValue; public int UpdateCount { get; set; } public Optional Selection { get; set; } @@ -1122,6 +1325,54 @@ namespace Avalonia.Controls.Primitives _selectedIndex = default; } } + + public Optional SelectedValue + { + get => _selectedValue; + set + { + _selectedValue = value; + } + } + } + + /// + /// Helper class for evaluating a binding from an Item and IBinding instance + /// + private class BindingHelper : StyledElement + { + public BindingHelper(IBinding binding) + { + UpdateBinding(binding); + } + + public static readonly StyledProperty ValueProperty = + AvaloniaProperty.Register("Value"); + + public object Evaluate(object? dataContext) + { + dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext)); + + // Only update the DataContext if necessary + if (!dataContext.Equals(DataContext)) + DataContext = dataContext; + + return GetValue(ValueProperty); + } + + public void UpdateBinding(IBinding binding) + { + _lastBinding = binding; + var ib = binding.Initiate(this, ValueProperty); + if (ib is null) + { + throw new InvalidOperationException("Unable to create binding"); + } + + BindingOperations.Apply(this, ValueProperty, ib, null); + } + + private IBinding? _lastBinding; } } } From b9c666e1935182c657436c9e8cd909a262e2cca8 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 17:50:38 -0500 Subject: [PATCH 023/155] Add Tests --- ...electingItemsControlTests_SelectedValue.cs | 330 ++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs new file mode 100644 index 0000000000..df81b1faae --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_SelectedValue.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Avalonia.Controls.Presenters; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Data; +using Avalonia.Styling; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Primitives +{ + public class SelectingItemsControlTests_SelectedValue + { + [Fact] + public void Setting_SelectedItem_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedItem = items[0]; + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedIndex_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedIndex = 0; + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedItems_Sets_SelectedValue() + { + var items = TestClass.GetItems(); + var sic = new ListBox + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedItems = new List + { + items[1], + items[3], + items[4] + }; + + // When interacting, SelectedItem is the first item in the SelectedItems collection + // But when set here, it's the last + Assert.Equal(items[4].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_Sets_SelectedIndex() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + Prepare(sic); + + sic.SelectedValue = items[1].Name; + + Assert.Equal(1, sic.SelectedIndex); + } + } + + [Fact] + public void Setting_SelectedValue_Sets_SelectedItem() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + Prepare(sic); + + sic.SelectedValue = "Item2"; + + Assert.Equal(items[1], sic.SelectedItem); + } + } + + [Fact] + public void Changing_SelectedValueBinding_Updates_SelectedValue() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + SelectedValueBinding = new Binding("Name"), + Template = Template() + }; + + sic.SelectedValue = "Item2"; + + sic.SelectedValueBinding = new Binding("AltProperty"); + + // Ensure SelectedItem didn't change + Assert.Equal(items[1], sic.SelectedItem); + + + Assert.Equal("Alt2", sic.SelectedValue); + } + } + + [Fact] + public void SelectedValue_With_Null_SelectedValueBinding_Is_Item() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template() + }; + + sic.SelectedIndex = 0; + + Assert.Equal(items[0], sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_Before_Initialize_Should_Retain_Selection() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + sic.BeginInit(); + sic.EndInit(); + + Assert.Equal(items[1].Name, sic.SelectedValue); + } + + [Fact] + public void Setting_SelectedValue_During_Initialize_Should_Take_Priority_Over_Previous_Value() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + sic.BeginInit(); + sic.SelectedValue = "Item1"; + sic.EndInit(); + + Assert.Equal(items[0].Name, sic.SelectedValue); + } + + [Fact] + public void Changing_Items_Should_Clear_SelectedValue() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + Prepare(sic); + + sic.Items = new List + { + new TestClass("NewItem", string.Empty) + }; + + Assert.Equal(null, sic.SelectedValue); + } + } + + [Fact] + public void Setting_SelectedValue_Should_Raise_SelectionChanged_Event() + { + // Unlike SelectedIndex/SelectedItem tests, we need the ItemsControl to + // initialize so that SelectedValue can actually be looked up + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + }; + + Prepare(sic); + + var called = false; + sic.SelectionChanged += (s, e) => + { + Assert.Same(items[1], e.AddedItems.Cast().Single()); + Assert.Empty(e.RemovedItems); + called = true; + }; + + sic.SelectedValue = "Item2"; + Assert.True(called); + } + } + + [Fact] + public void Clearing_SelectedValue_Should_Raise_SelectionChanged_Event() + { + var items = TestClass.GetItems(); + var sic = new SelectingItemsControl + { + Items = items, + Template = Template(), + SelectedValueBinding = new Binding("Name"), + SelectedValue = "Item2" + }; + + var called = false; + sic.SelectionChanged += (s, e) => + { + Assert.Same(items[1], e.RemovedItems.Cast().Single()); + Assert.Empty(e.AddedItems); + called = true; + }; + + sic.SelectedValue = null; + Assert.True(called); + } + + private static FuncControlTemplate Template() + { + return new FuncControlTemplate((control, scope) => + new ItemsPresenter + { + Name = "itemsPresenter", + [~ItemsPresenter.ItemsPanelProperty] = control[~ItemsControl.ItemsPanelProperty], + }.RegisterInNameScope(scope)); + } + + private static void Prepare(SelectingItemsControl target) + { + var root = new TestRoot + { + Child = target, + Width = 100, + Height = 100, + Styles = + { + new Style(x => x.Is()) + { + Setters = + { + new Setter(ListBox.TemplateProperty, Template()), + }, + }, + }, + }; + + root.LayoutManager.ExecuteInitialLayoutPass(); + } + } + + internal class TestClass + { + public TestClass(string name, string alt) + { + Name = name; + AltProperty = alt; + } + + public string Name { get; set; } + + public string AltProperty { get; set; } + + public static List GetItems() + { + return new List + { + new TestClass("Item1", "Alt1"), + new TestClass("Item2", "Alt2"), + new TestClass("Item3", "Alt3"), + new TestClass("Item4", "Alt4"), + new TestClass("Item5", "Alt5"), + }; + } + } +} + + From a8cb64160957920b85c9c865753c42f829a5b294 Mon Sep 17 00:00:00 2001 From: amwx <40413319+amwx@users.noreply.github.com> Date: Thu, 2 Feb 2023 18:02:32 -0500 Subject: [PATCH 024/155] Missing xml docs --- src/Avalonia.Controls/Primitives/SelectingItemsControl.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 2fac60c8d8..67ad1963ff 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -225,6 +225,10 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Gets the instance used to obtain the + /// property + /// [AssignBinding] public IBinding? SelectedValueBinding { @@ -232,6 +236,10 @@ namespace Avalonia.Controls.Primitives set => SetValue(SelectedValueBindingProperty, value); } + /// + /// Gets or sets the value of the selected item, obtained using + /// + /// public object? SelectedValue { get => GetValue(SelectedValueProperty); From 467ef300bf559123f3dc355185ad78efdf0675ef Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 3 Feb 2023 10:43:26 +0100 Subject: [PATCH 025/155] Remove unneeded method. --- src/Avalonia.Base/PropertyStore/ValueStore.cs | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/Avalonia.Base/PropertyStore/ValueStore.cs b/src/Avalonia.Base/PropertyStore/ValueStore.cs index f36a96992b..a758360545 100644 --- a/src/Avalonia.Base/PropertyStore/ValueStore.cs +++ b/src/Avalonia.Base/PropertyStore/ValueStore.cs @@ -380,23 +380,6 @@ namespace Avalonia.PropertyStore } } - /// - /// Called by non-LocalValue binding entries to re-evaluate the effective value when the - /// binding produces an unset value. - /// - /// The bound property. - /// The priority of binding which produced a new value. - public void OnBindingValueCleared(AvaloniaProperty property, BindingPriority priority) - { - Debug.Assert(priority != BindingPriority.LocalValue); - - if (TryGetEffectiveValue(property, out var existing)) - { - if (priority <= existing.Priority) - ReevaluateEffectiveValue(property, existing); - } - } - /// /// Called by a when its /// state changes. From 571b7746eac6cc694aa586ec83d48ad5c9f4b9ac Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 22 Jan 2023 11:46:28 +0100 Subject: [PATCH 026/155] Added failing integration test. --- samples/IntegrationTestApp/MainWindow.axaml | 1 + .../IntegrationTestApp/MainWindow.axaml.cs | 2 ++ .../WindowTests_MacOS.cs | 24 ++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index b116e4c789..7b1f0eddce 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -139,6 +139,7 @@ Maximized FullScreen + Can Resize diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 3cd5350cce..087f25666b 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -66,11 +66,13 @@ namespace IntegrationTestApp var locationComboBox = this.GetControl("ShowWindowLocation"); var stateComboBox = this.GetControl("ShowWindowState"); var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; + var canResizeCheckBox = this.GetControl("ShowWindowCanResize"); var owner = (Window)this.GetVisualRoot()!; var window = new ShowWindowTest { WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex, + CanResize = canResizeCheckBox.IsChecked.Value, }; if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index d9817ecdd1..06180be74a 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -323,11 +323,30 @@ namespace Avalonia.IntegrationTests.Appium secondaryWindow.GetChromeButtons().close.Click(); } - private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) + [PlatformTheory(TestPlatforms.MacOS)] + [InlineData(ShowWindowMode.NonOwned)] + [InlineData(ShowWindowMode.Owned)] + [InlineData(ShowWindowMode.Modal)] + public void Window_Has_Disabled_Zoom_Button_When_CanResize_Is_False(ShowWindowMode mode) + { + using (OpenWindow(null, mode, WindowStartupLocation.Manual, canResize: false)) + { + var secondaryWindow = GetWindow("SecondaryWindow"); + var (_, _, zoomButton) = secondaryWindow.GetChromeButtons(); + Assert.False(zoomButton.Enabled); + } + } + + private IDisposable OpenWindow( + PixelSize? size, + ShowWindowMode mode, + WindowStartupLocation location, + bool canResize = true) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize"); var showButton = _session.FindElementByAccessibilityId("ShowWindow"); if (size.HasValue) @@ -338,6 +357,9 @@ namespace Avalonia.IntegrationTests.Appium locationComboBox.Click(); _session.FindElementByName(location.ToString()).SendClick(); + + if (canResizeCheckBox.GetIsChecked() != canResize) + canResizeCheckBox.Click(); return showButton.OpenWindowWithClick(); } From 2951b80c39601a8368cc902f76ef4f01bc9307c5 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Feb 2023 21:26:19 +0100 Subject: [PATCH 027/155] Add additional failing integration tests. For problems introduced in #10153. --- .../WindowTests.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 7bb991aae6..23381c2e58 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -30,9 +30,9 @@ namespace Avalonia.IntegrationTests.Appium [Theory] [MemberData(nameof(StartupLocationData))] - public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location) + public void StartupLocation(Size? size, ShowWindowMode mode, WindowStartupLocation location, bool canResize) { - using var window = OpenWindow(size, mode, location); + using var window = OpenWindow(size, mode, location, canResize: canResize); var info = GetWindowInfo(); if (size.HasValue) @@ -230,10 +230,10 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal(new Rgba32(255, 0, 0), centerColor); } - public static TheoryData StartupLocationData() + public static TheoryData StartupLocationData() { var sizes = new Size?[] { null, new Size(400, 300) }; - var data = new TheoryData(); + var data = new TheoryData(); foreach (var size in sizes) { @@ -243,7 +243,8 @@ namespace Avalonia.IntegrationTests.Appium { if (!(location == WindowStartupLocation.CenterOwner && mode == ShowWindowMode.NonOwned)) { - data.Add(size, mode, location); + data.Add(size, mode, location, true); + data.Add(size, mode, location, false); } } } @@ -311,14 +312,16 @@ namespace Avalonia.IntegrationTests.Appium Size? size, ShowWindowMode mode, WindowStartupLocation location = WindowStartupLocation.Manual, - WindowState state = Controls.WindowState.Normal) + WindowState state = Controls.WindowState.Normal, + bool canResize = true) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); var stateComboBox = _session.FindElementByAccessibilityId("ShowWindowState"); + var canResizeCheckBox = _session.FindElementByAccessibilityId("ShowWindowCanResize"); var showButton = _session.FindElementByAccessibilityId("ShowWindow"); - + if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); @@ -331,6 +334,9 @@ namespace Avalonia.IntegrationTests.Appium stateComboBox.Click(); _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + if (canResizeCheckBox.GetIsChecked() != canResize) + canResizeCheckBox.Click(); + return showButton.OpenWindowWithClick(); } From f41749170277d8e79c7a5e16af1a1db083705008 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 4 Feb 2023 22:44:24 +0100 Subject: [PATCH 028/155] Speed up integration tests. Don't change combo boxes that are already set to the correct value. --- .../WindowTests.cs | 21 +++++++++++++------ .../WindowTests_MacOS.cs | 14 +++++++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 23381c2e58..f3861ca267 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -325,14 +325,23 @@ namespace Avalonia.IntegrationTests.Appium if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); + if (modeComboBox.GetComboBoxValue() != mode.ToString()) + { + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + } - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); + if (locationComboBox.GetComboBoxValue() != location.ToString()) + { + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + } - stateComboBox.Click(); - _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + if (stateComboBox.GetComboBoxValue() != state.ToString()) + { + stateComboBox.Click(); + _session.FindElementByAccessibilityId($"ShowWindowState{state}").SendClick(); + } if (canResizeCheckBox.GetIsChecked() != canResize) canResizeCheckBox.Click(); diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 06180be74a..0839cbf183 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -352,11 +352,17 @@ namespace Avalonia.IntegrationTests.Appium if (size.HasValue) sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); + if (modeComboBox.GetComboBoxValue() != mode.ToString()) + { + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + } - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); + if (locationComboBox.GetComboBoxValue() != location.ToString()) + { + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + } if (canResizeCheckBox.GetIsChecked() != canResize) canResizeCheckBox.Click(); From dff80c8ded5cd28bb6b0f3535aee27f6394c1f4e Mon Sep 17 00:00:00 2001 From: affederaffe <68356204+affederaffe@users.noreply.github.com> Date: Sun, 5 Feb 2023 12:55:46 +0100 Subject: [PATCH 029/155] Remove ListServicesAsync usage - Uses Reflection underneath - It's faster to just fail than receiving all available services first --- src/Avalonia.FreeDesktop/DBusMenuExporter.cs | 25 ++++++++++++-------- src/Avalonia.FreeDesktop/DBusSystemDialog.cs | 24 ++++++++++++------- src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs | 24 +++++++++++-------- src/tools/Tmds.DBus.SourceGenerator | 2 +- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index deac7346cb..d86a6bf3a4 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -98,16 +98,21 @@ namespace Avalonia.FreeDesktop Connection.AddMethodHandler(this); if (!_appMenu) return; - var services = await Connection.ListServicesAsync(); - if (!services.Contains("com.canonical.AppMenu.Registrar")) - return; + _registrar = new ComCanonicalAppMenuRegistrar(Connection, "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar"); - if (!_disposed) - await _registrar.RegisterWindowAsync(_xid, Path); - // It's not really important if this code succeeds, - // and it's not important to know if it succeeds - // since even if we register the window it's not guaranteed that - // menu will be actually exported + try + { + if (!_disposed) + await _registrar.RegisterWindowAsync(_xid, Path); + } + catch + { + // It's not really important if this code succeeds, + // and it's not important to know if it succeeds + // since even if we register the window it's not guaranteed that + // menu will be actually exported + _registrar = null; + } } public void Dispose() @@ -286,7 +291,7 @@ namespace Avalonia.FreeDesktop return null; } - private Dictionary GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) + private static Dictionary GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) { if (names.Length == 0) names = s_allProperties; diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index 1c965a077e..fa36f7d1ab 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -17,20 +17,28 @@ namespace Avalonia.FreeDesktop { if (DBusHelper.Connection is null) return null; - var services = await DBusHelper.Connection.ListServicesAsync(); - return services.Contains("org.freedesktop.portal.Desktop", StringComparer.Ordinal) - ? new DBusSystemDialog(DBusHelper.Connection, handle) - : null; + + var dbusFileChooser = new OrgFreedesktopPortalFileChooser(DBusHelper.Connection, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + try + { + await dbusFileChooser.GetVersionAsync(); + } + catch + { + return null; + } + + return new DBusSystemDialog(DBusHelper.Connection, handle, dbusFileChooser); } private readonly Connection _connection; private readonly OrgFreedesktopPortalFileChooser _fileChooser; private readonly IPlatformHandle _handle; - private DBusSystemDialog(Connection connection, IPlatformHandle handle) + private DBusSystemDialog(Connection connection, IPlatformHandle handle, OrgFreedesktopPortalFileChooser fileChooser) { _connection = connection; - _fileChooser = new OrgFreedesktopPortalFileChooser(connection, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + _fileChooser = fileChooser; _handle = handle; } @@ -137,7 +145,6 @@ namespace Avalonia.FreeDesktop if (fileTypes is null) return null; - var any = false; var filters = new DBusArrayItem(DBusType.Struct, new List()); foreach (var fileType in fileTypes) @@ -154,7 +161,6 @@ namespace Avalonia.FreeDesktop else continue; - any = true; filters.Add(new DBusStructItem( new DBusItem[] { @@ -163,7 +169,7 @@ namespace Avalonia.FreeDesktop })); } - return any ? new DBusVariantItem("a(sa(us))", filters) : null; + return filters.Count > 0 ? new DBusVariantItem("a(sa(us))", filters) : null; } } } diff --git a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs index f732cfef07..521fe68e90 100644 --- a/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs +++ b/src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs @@ -1,6 +1,5 @@ using System; using System.Diagnostics; -using System.Linq; using Avalonia.Controls.Platform; using Avalonia.Logging; using Avalonia.Platform; @@ -66,13 +65,18 @@ namespace Avalonia.FreeDesktop private async void WatchAsync() { - var services = await _connection!.ListServicesAsync(); - if (!services.Contains("org.kde.StatusNotifierWatcher", StringComparer.Ordinal)) - return; - - _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item2) ); - var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher"); - OnNameChange(nameOwner); + try + { + _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((_, x) => OnNameChange(x.Item2)); + var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher"); + OnNameChange(nameOwner); + } + catch + { + _serviceWatchDisposable = null; + Logger.TryGet(LogEventLevel.Error, "DBUS") + ?.Log(this, "Interface 'org.kde.StatusNotifierWatcher' is unavailable."); + } } private void OnNameChange(string? newOwner) @@ -99,7 +103,7 @@ namespace Avalonia.FreeDesktop private async void CreateTrayIcon() { - if (_connection is null || !_serviceConnected || _isDisposed) + if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierWatcher is null) return; #if NET5_0_OR_GREATER @@ -114,7 +118,7 @@ namespace Avalonia.FreeDesktop _connection.AddMethodHandler(_statusNotifierItemDbusObj); await _dBus!.RequestNameAsync(_sysTrayServiceName, 0); - await _statusNotifierWatcher!.RegisterStatusNotifierItemAsync(_sysTrayServiceName); + await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); _statusNotifierItemDbusObj.SetIcon(_icon); diff --git a/src/tools/Tmds.DBus.SourceGenerator b/src/tools/Tmds.DBus.SourceGenerator index 86e0ded07f..309159609e 160000 --- a/src/tools/Tmds.DBus.SourceGenerator +++ b/src/tools/Tmds.DBus.SourceGenerator @@ -1 +1 @@ -Subproject commit 86e0ded07fc8622e216f93f8fc01e8c1b3ef29b1 +Subproject commit 309159609e09f3f0ae77f0abd761360049761700 From e7a6d6fbc690b96af757199c98ff1f324f3d75ec Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 12:00:48 +0100 Subject: [PATCH 030/155] Implement windowWillUseStandardFrame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seems that `NSWindow.isZoomed` returns the wrong result unless you implement this? ¯\_(ツ)_/¯ --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index d3b7b4ede6..784072221d 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -223,6 +223,19 @@ } } +// From chromium: +// +// > The delegate or the window class should implement this method so that +// > -[NSWindow isZoomed] can be then determined by whether or not the current +// > window frame is equal to the zoomed frame. +// +// If we don't implement this, then isZoomed always returns true for a non- +// resizable window ¯\_(ツ)_/¯ +- (NSRect)windowWillUseStandardFrame:(NSWindow*)window + defaultFrame:(NSRect)newFrame { + return newFrame; +} + -(BOOL)canBecomeKeyWindow { if(_canBecomeKeyWindow) From 044a499db331b0d13e33d6427643bc1868470d30 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 14:27:09 +0100 Subject: [PATCH 031/155] Added additional failing integration tests. --- tests/Avalonia.IntegrationTests.Appium/WindowTests.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index f3861ca267..ec24caa18c 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -63,9 +63,9 @@ namespace Avalonia.IntegrationTests.Appium [Theory] [MemberData(nameof(WindowStateData))] - public void WindowState(Size? size, ShowWindowMode mode, WindowState state) + public void WindowState(Size? size, ShowWindowMode mode, WindowState state, bool canResize) { - using var window = OpenWindow(size, mode, state: state); + using var window = OpenWindow(size, mode, state: state, canResize: canResize); try { @@ -253,10 +253,10 @@ namespace Avalonia.IntegrationTests.Appium return data; } - public static TheoryData WindowStateData() + public static TheoryData WindowStateData() { var sizes = new Size?[] { null, new Size(400, 300) }; - var data = new TheoryData(); + var data = new TheoryData(); foreach (var size in sizes) { @@ -274,7 +274,8 @@ namespace Avalonia.IntegrationTests.Appium mode != ShowWindowMode.NonOwned) continue; - data.Add(size, mode, state); + data.Add(size, mode, state, true); + data.Add(size, mode, state, false); } } } From 38aaadf92db4e4957a3ccab2365937c215e3c89a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 15:16:59 +0100 Subject: [PATCH 032/155] Use custom zoom logic when !_canResize. `[NSWindow setIsZoomed]` requires that the window is resizable by the user in order to work; when `canResize == false` this is not that case. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ce82f7d83f..af4f92524b 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -225,16 +225,12 @@ bool WindowImpl::IsZoomed() { } void WindowImpl::DoZoom() { - switch (_decorations) { - case SystemDecorationsNone: - case SystemDecorationsBorderOnly: - [Window setFrame:[Window screen].visibleFrame display:true]; - break; - - - case SystemDecorationsFull: - [Window performZoom:Window]; - break; + if (_decorations == SystemDecorationsNone || + _decorations == SystemDecorationsBorderOnly || + _canResize == false) { + [Window setFrame:[Window screen].visibleFrame display:true]; + } else { + [Window performZoom:Window]; } } From 5346344d12fa314c7317f90f03c8e798ee4ef6cc Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 17:32:55 +0100 Subject: [PATCH 033/155] Don't call virtual method from ctor. And remove unneeded checks for already-existing `Window` (this method is always called from ctor). --- .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 038e9a048c..4d3768a4a8 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -580,21 +580,12 @@ void WindowBaseImpl::CleanNSWindow() { } } -void WindowBaseImpl::CreateNSWindow(bool isDialog) { - if (isDialog) { - if (![Window isKindOfClass:[AvnPanel class]]) { - CleanNSWindow(); - - Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; - - [Window setHidesOnDeactivate:false]; - } +void WindowBaseImpl::CreateNSWindow(bool usePanel) { + if (usePanel) { + Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless]; + [Window setHidesOnDeactivate:false]; } else { - if (![Window isKindOfClass:[AvnWindow class]]) { - CleanNSWindow(); - - Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; - } + Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:NSWindowStyleMaskBorderless]; } } From b609ba58a8e445ff68ddbcd64d9ab3d9d4f6cb6c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 17:43:02 +0100 Subject: [PATCH 034/155] Disable zoom button when CanResize == false. To do this more easily, merged `HideOrShowTrafficLights` into a virtual `UpdateStyle`. They were always called together, and really _must_ be called together; this enforces that. --- .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 3 +- native/Avalonia.Native/src/OSX/WindowImpl.h | 3 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 37 +++++++++---------- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 4c2758f6c6..afa3a2956f 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -106,8 +106,7 @@ BEGIN_INTERFACE_MAP() protected: virtual NSWindowStyleMask GetStyle(); - - void UpdateStyle(); + virtual void UpdateStyle(); private: void CreateNSWindow (bool isDialog); diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 3861aaf170..e7e9b7f1d8 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -41,8 +41,6 @@ BEGIN_INTERFACE_MAP() WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl); - void HideOrShowTrafficLights (); - virtual HRESULT Show (bool activate, bool isDialog) override; virtual HRESULT SetEnabled (bool enable) override; @@ -101,6 +99,7 @@ BEGIN_INTERFACE_MAP() protected: virtual NSWindowStyleMask GetStyle() override; + void UpdateStyle () override; private: void OnInitialiseNSWindow(); diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index af4f92524b..ae3e1eaf1d 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -30,19 +30,6 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase OnInitialiseNSWindow(); } -void WindowImpl::HideOrShowTrafficLights() { - if (Window == nil) { - return; - } - - bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); - bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; - - [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; -} - void WindowImpl::OnInitialiseNSWindow(){ [GetWindowProtocol() setCanBecomeKeyWindow:true]; @@ -67,8 +54,6 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { WindowBaseImpl::Show(activate, isDialog); GetWindowState(&_actualWindowState); - HideOrShowTrafficLights(); - return SetWindowState(_lastWindowState); } } @@ -257,8 +242,6 @@ HRESULT WindowImpl::SetDecorations(SystemDecorations value) { UpdateStyle(); - HideOrShowTrafficLights(); - switch (_decorations) { case SystemDecorationsNone: [Window setHasShadow:NO]; @@ -415,9 +398,6 @@ HRESULT WindowImpl::SetExtendClientArea(bool enable) { } [GetWindowProtocol() setIsExtended:enable]; - - HideOrShowTrafficLights(); - UpdateStyle(); } @@ -608,3 +588,20 @@ NSWindowStyleMask WindowImpl::GetStyle() { } return s; } + +void WindowImpl::UpdateStyle() { + WindowBaseImpl::UpdateStyle(); + + if (Window == nil) { + return; + } + + bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); + bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; + + [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; + [[Window standardWindowButton:NSWindowZoomButton] setEnabled:_canResize]; +} + From d40041f02dd8ae5e53074e3b4c3e47b420ff9457 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 6 Feb 2023 19:13:59 +0100 Subject: [PATCH 035/155] GetStyle => abstract CalculateStyleMask. - Make naming more clear - it's not getting the style mask, it's calculating what it should be in order to update the mask - Make it abstract to prevent it being called from the ctor in future --- native/Avalonia.Native/src/OSX/PopupImpl.mm | 2 +- native/Avalonia.Native/src/OSX/WindowBaseImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 10 +--------- native/Avalonia.Native/src/OSX/WindowImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm index 9820a9f052..972d03d08c 100644 --- a/native/Avalonia.Native/src/OSX/PopupImpl.mm +++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm @@ -29,7 +29,7 @@ private: [Window setLevel:NSPopUpMenuWindowLevel]; } protected: - virtual NSWindowStyleMask GetStyle() override + virtual NSWindowStyleMask CalculateStyleMask() override { return NSWindowStyleMaskBorderless; } diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index afa3a2956f..93decef136 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -105,7 +105,7 @@ BEGIN_INTERFACE_MAP() virtual void BringToFront (); protected: - virtual NSWindowStyleMask GetStyle(); + virtual NSWindowStyleMask CalculateStyleMask() = 0; virtual void UpdateStyle(); private: diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 4d3768a4a8..59102e15a6 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -35,18 +35,14 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, lastSize = NSSize { 100, 100 }; lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMinSize = NSSize { 0, 0 }; - lastMenu = nullptr; CreateNSWindow(usePanel); [Window setContentView:StandardContainer]; - [Window setStyleMask:NSWindowStyleMaskBorderless]; [Window setBackingType:NSBackingStoreBuffered]; - [Window setContentMinSize:lastMinSize]; [Window setContentMaxSize:lastMaxSize]; - [Window setOpaque:false]; } @@ -564,12 +560,8 @@ bool WindowBaseImpl::IsModal() { return false; } -NSWindowStyleMask WindowBaseImpl::GetStyle() { - return NSWindowStyleMaskBorderless; -} - void WindowBaseImpl::UpdateStyle() { - [Window setStyleMask:GetStyle()]; + [Window setStyleMask:CalculateStyleMask()]; } void WindowBaseImpl::CleanNSWindow() { diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index e7e9b7f1d8..9c684c77c4 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -98,7 +98,7 @@ BEGIN_INTERFACE_MAP() bool CanBecomeKeyWindow (); protected: - virtual NSWindowStyleMask GetStyle() override; + virtual NSWindowStyleMask CalculateStyleMask() override; void UpdateStyle () override; private: diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ae3e1eaf1d..afd9c1a5ea 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -553,7 +553,7 @@ bool WindowImpl::IsOwned() { return _parent != nullptr; } -NSWindowStyleMask WindowImpl::GetStyle() { +NSWindowStyleMask WindowImpl::CalculateStyleMask() { unsigned long s = NSWindowStyleMaskBorderless; if(_actualWindowState == FullScreen) From 959b09c2434a285cead448fa8f22a6d282afb90d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 00:18:15 +0100 Subject: [PATCH 036/155] Skip flaky test for now. --- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 0839cbf183..1933d10919 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -151,7 +151,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Normal", windowState.Text); } - [PlatformFact(TestPlatforms.MacOS)] + [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked() { // Issue #9565 From 6a1910172c2a060f7ef67d875af03064db768d5f Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 10:26:45 +0100 Subject: [PATCH 037/155] Move traffic lights logic into one place. --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 4 ---- native/Avalonia.Native/src/OSX/WindowImpl.mm | 15 ++++++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 784072221d..23abf1d53f 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -274,10 +274,6 @@ -(void) setEnabled:(bool)enable { _isEnabled = enable; - - [[self standardWindowButton:NSWindowCloseButton] setEnabled:enable]; - [[self standardWindowButton:NSWindowMiniaturizeButton] setEnabled:enable]; - [[self standardWindowButton:NSWindowZoomButton] setEnabled:enable]; } -(void)becomeKeyWindow diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index afd9c1a5ea..e4bbe24cb8 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -599,9 +599,14 @@ void WindowImpl::UpdateStyle() { bool wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome); bool hasTrafficLights = _isClientAreaExtended ? wantsChrome : _decorations == SystemDecorationsFull; - [[Window standardWindowButton:NSWindowCloseButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowMiniaturizeButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setHidden:!hasTrafficLights]; - [[Window standardWindowButton:NSWindowZoomButton] setEnabled:_canResize]; -} + NSButton* closeButton = [Window standardWindowButton:NSWindowCloseButton]; + NSButton* miniaturizeButton = [Window standardWindowButton:NSWindowMiniaturizeButton]; + NSButton* zoomButton = [Window standardWindowButton:NSWindowZoomButton]; + [closeButton setHidden:!hasTrafficLights]; + [closeButton setEnabled:_isEnabled]; + [miniaturizeButton setHidden:!hasTrafficLights]; + [miniaturizeButton setEnabled:_isEnabled]; + [zoomButton setHidden:!hasTrafficLights]; + [zoomButton setEnabled:_isEnabled && _canResize]; +} From a705f546bba1f3fb973aae2cdeb9ed8d08cf4f16 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 11:46:18 +0100 Subject: [PATCH 038/155] Enable resizing during fullscreen transition. macOS seems to tie resizing of the NSView inside the window to the resizable style mask of the window somehow. If we programmatically transition a non-resizable window to fullscreen, the inner NSView's size isn't changed, so we need to make the window resizable during the fullscreen transition. Makes the final two failing `WindowState` integration tests pass. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index e4bbe24cb8..47e83f8d56 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -146,11 +146,13 @@ bool WindowImpl::CanBecomeKeyWindow() void WindowImpl::StartStateTransition() { _transitioningWindowState = true; + UpdateStyle(); } void WindowImpl::EndStateTransition() { _transitioningWindowState = false; - + UpdateStyle(); + // Ensure correct order of child windows after fullscreen transition. BringToFront(); } @@ -573,7 +575,7 @@ NSWindowStyleMask WindowImpl::CalculateStyleMask() { case SystemDecorationsFull: s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable; - if (_canResize && _isEnabled) { + if ((_canResize && _isEnabled) || _transitioningWindowState) { s = s | NSWindowStyleMaskResizable; } break; From 50a368eaa15e7a5756eeda2ec2fdf69d43e9f6da Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 13:05:05 +0100 Subject: [PATCH 039/155] Skip flaky test for now. --- tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 1933d10919..47a471fafd 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -56,7 +56,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(TestPlatforms.MacOS)] + [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { var mainWindow = GetWindow("MainWindow"); From 784c380c604b6fd6a139abd7233aaf668661bfd0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 7 Feb 2023 16:10:31 +0100 Subject: [PATCH 040/155] Don't change z-order of window exiting fullscreen. When a window exits fullscreen, its child windows need to be ordered, but we shouldn't touch the z-order of the window itself as this sometimes seemed to result in the parent window being shown over the child windows. Fixes flaky integration tests (hopefully). --- native/Avalonia.Native/src/OSX/WindowImpl.h | 1 + native/Avalonia.Native/src/OSX/WindowImpl.mm | 21 ++++++++++++------- .../WindowTests_MacOS.cs | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 9c684c77c4..29bb659039 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -102,6 +102,7 @@ protected: void UpdateStyle () override; private: + void ZOrderChildWindows(); void OnInitialiseNSWindow(); NSString *_lastTitle; }; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 47e83f8d56..4510d135dc 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -119,14 +119,19 @@ void WindowImpl::BringToFront() } [Window invalidateShadow]; + ZOrderChildWindows(); + } +} + +void WindowImpl::ZOrderChildWindows() +{ + for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) + { + auto window = (*iterator)->Window; - for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) - { - auto window = (*iterator)->Window; - - // #9565: Only bring window to front if it's on the currently active space - if ([window isOnActiveSpace]) - (*iterator)->BringToFront(); + // #9565: Only bring window to front if it's on the currently active space + if ([window isOnActiveSpace]) { + (*iterator)->BringToFront(); } } } @@ -154,7 +159,7 @@ void WindowImpl::EndStateTransition() { UpdateStyle(); // Ensure correct order of child windows after fullscreen transition. - BringToFront(); + ZOrderChildWindows(); } SystemDecorations WindowImpl::Decorations() { diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs index 47a471fafd..0839cbf183 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -56,7 +56,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] + [PlatformFact(TestPlatforms.MacOS)] public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { var mainWindow = GetWindow("MainWindow"); @@ -151,7 +151,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Normal", windowState.Text); } - [PlatformFact(TestPlatforms.MacOS, Skip = "Flaky test, skip for now")] + [PlatformFact(TestPlatforms.MacOS)] public void Does_Not_Switch_Space_From_FullScreen_To_Main_Desktop_When_FullScreen_Window_Clicked() { // Issue #9565 From e1138f2cb6a393802b235a073d28e85a64690ffe Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Tue, 7 Feb 2023 16:53:49 +0100 Subject: [PATCH 041/155] Nullability fixes for Avalonia.Controls --- .../Input/Platform/IClipboard.cs | 6 +- src/Avalonia.Base/Media/Color.cs | 2 +- .../Platform/Internal/AssemblyDescriptor.cs | 5 +- .../ClassicDesktopStyleApplicationLifetime.cs | 28 +- ...IClassicDesktopStyleApplicationLifetime.cs | 5 +- .../AutoCompleteBox/AutoCompleteBox.cs | 2 +- .../Automation/AutomationProperties.cs | 282 +++++------------- src/Avalonia.Controls/Calendar/Calendar.cs | 18 +- .../CalendarBlackoutDatesCollection.cs | 14 +- .../Calendar/CalendarItem.cs | 112 +++---- .../Calendar/DateTimeHelper.cs | 33 +- .../CalendarDatePicker/CalendarDatePicker.cs | 20 +- src/Avalonia.Controls/Chrome/TitleBar.cs | 40 +-- .../MenuScrollingVisibilityConverter.cs | 1 - src/Avalonia.Controls/DefinitionBase.cs | 90 +++--- src/Avalonia.Controls/DockPanel.cs | 9 +- src/Avalonia.Controls/Documents/Inline.cs | 9 +- src/Avalonia.Controls/Documents/Span.cs | 14 +- src/Avalonia.Controls/Grid.cs | 230 +++++++------- src/Avalonia.Controls/Image.cs | 7 +- src/Avalonia.Controls/ItemsControl.cs | 12 +- .../LayoutTransformControl.cs | 20 +- src/Avalonia.Controls/MaskedTextBox.cs | 12 +- src/Avalonia.Controls/NativeControlHost.cs | 22 +- src/Avalonia.Controls/NativeMenu.Export.cs | 20 +- .../Primitives/AdornerLayer.cs | 12 +- src/Avalonia.Controls/Primitives/Popup.cs | 12 +- .../Primitives/SelectingItemsControl.cs | 31 +- .../Primitives/TemplatedControl.cs | 7 +- .../Primitives/TextSearch.cs | 8 +- src/Avalonia.Controls/Primitives/Track.cs | 19 +- .../Primitives/VisualLayerManager.cs | 16 +- src/Avalonia.Controls/RelativePanel.cs | 7 +- .../Remote/Server/RemoteServerTopLevelImpl.cs | 44 ++- src/Avalonia.Controls/Slider.cs | 13 +- src/Avalonia.Controls/StackPanel.cs | 9 +- src/Avalonia.Controls/TickBar.cs | 18 +- src/Avalonia.Controls/TrayIcon.cs | 9 +- src/Avalonia.Controls/TreeView.cs | 16 +- src/Avalonia.Controls/Viewbox.cs | 36 +-- src/Avalonia.Controls/WrapPanel.cs | 107 +++---- src/Browser/Avalonia.Browser/ClipboardImpl.cs | 10 +- 42 files changed, 580 insertions(+), 807 deletions(-) diff --git a/src/Avalonia.Base/Input/Platform/IClipboard.cs b/src/Avalonia.Base/Input/Platform/IClipboard.cs index bf2a5a8602..3de352fc4f 100644 --- a/src/Avalonia.Base/Input/Platform/IClipboard.cs +++ b/src/Avalonia.Base/Input/Platform/IClipboard.cs @@ -6,9 +6,9 @@ namespace Avalonia.Input.Platform [NotClientImplementable] public interface IClipboard { - Task GetTextAsync(); + Task GetTextAsync(); - Task SetTextAsync(string text); + Task SetTextAsync(string? text); Task ClearAsync(); @@ -16,6 +16,6 @@ namespace Avalonia.Input.Platform Task GetFormatsAsync(); - Task GetDataAsync(string format); + Task GetDataAsync(string format); } } diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index ab89177295..74e70b2a14 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -331,7 +331,7 @@ namespace Avalonia.Media /// /// Parses the given string representing a CSS color value into a new . /// - private static bool TryParseCssFormat(string s, out Color color) + private static bool TryParseCssFormat(string? s, out Color color) { bool prefixMatched = false; diff --git a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs index d1a803fefb..6a577c204c 100644 --- a/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs +++ b/src/Avalonia.Base/Platform/Internal/AssemblyDescriptor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -18,7 +19,7 @@ internal class AssemblyDescriptor : IAssemblyDescriptor { public AssemblyDescriptor(Assembly assembly) { - Assembly = assembly; + Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); Resources = assembly.GetManifestResourceNames() .ToDictionary(n => n, n => (IAssetDescriptor)new AssemblyResourceDescriptor(assembly, n)); Name = assembly.GetName().Name; diff --git a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs index fde401fb01..ada0b94124 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/ClassicDesktopStyleApplicationLifetime.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -17,32 +16,34 @@ namespace Avalonia.Controls.ApplicationLifetimes private int _exitCode; private CancellationTokenSource? _cts; private bool _isShuttingDown; - private HashSet _windows = new HashSet(); + private readonly HashSet _windows = new(); + + private static ClassicDesktopStyleApplicationLifetime? s_activeLifetime; - private static ClassicDesktopStyleApplicationLifetime? _activeLifetime; static ClassicDesktopStyleApplicationLifetime() { Window.WindowOpenedEvent.AddClassHandler(typeof(Window), OnWindowOpened); - Window.WindowClosedEvent.AddClassHandler(typeof(Window), WindowClosedEvent); + Window.WindowClosedEvent.AddClassHandler(typeof(Window), OnWindowClosed); } - private static void WindowClosedEvent(object? sender, RoutedEventArgs e) + private static void OnWindowClosed(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Remove((Window)sender!); - _activeLifetime?.HandleWindowClosed((Window)sender!); + var window = (Window)sender!; + s_activeLifetime?._windows.Remove(window); + s_activeLifetime?.HandleWindowClosed(window); } private static void OnWindowOpened(object? sender, RoutedEventArgs e) { - _activeLifetime?._windows.Add((Window)sender!); + s_activeLifetime?._windows.Add((Window)sender!); } public ClassicDesktopStyleApplicationLifetime() { - if (_activeLifetime != null) + if (s_activeLifetime != null) throw new InvalidOperationException( "Can not have multiple active ClassicDesktopStyleApplicationLifetime instances and the previously created one was not disposed"); - _activeLifetime = this; + s_activeLifetime = this; } /// @@ -65,9 +66,10 @@ namespace Avalonia.Controls.ApplicationLifetimes /// public Window? MainWindow { get; set; } + /// public IReadOnlyList Windows => _windows.ToArray(); - private void HandleWindowClosed(Window window) + private void HandleWindowClosed(Window? window) { if (window == null) return; @@ -130,8 +132,8 @@ namespace Avalonia.Controls.ApplicationLifetimes public void Dispose() { - if (_activeLifetime == this) - _activeLifetime = null; + if (s_activeLifetime == this) + s_activeLifetime = null; } private bool DoShutdown( diff --git a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs index 22b5f8236d..b9a372f935 100644 --- a/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs +++ b/src/Avalonia.Controls/ApplicationLifetimes/IClassicDesktopStyleApplicationLifetime.cs @@ -40,7 +40,10 @@ namespace Avalonia.Controls.ApplicationLifetimes /// The main window. /// Window? MainWindow { get; set; } - + + /// + /// Gets the list of all open windows in the application. + /// IReadOnlyList Windows { get; } /// diff --git a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs index 55649660f7..9a949e31d4 100644 --- a/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs +++ b/src/Avalonia.Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -1711,7 +1711,7 @@ namespace Avalonia.Controls /// The predicate to use for the partial or /// exact match. /// Returns the object or null. - private object? TryGetMatch(string? searchText, AvaloniaList view, AutoCompleteFilterPredicate? predicate) + private object? TryGetMatch(string? searchText, AvaloniaList? view, AutoCompleteFilterPredicate? predicate) { if (predicate is null) return null; diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs index 35f94722ce..3ea9c170ff 100644 --- a/src/Avalonia.Controls/Automation/AutomationProperties.cs +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -38,8 +38,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.AcceleratorKey attached property. /// - public static readonly AttachedProperty AcceleratorKeyProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AcceleratorKeyProperty = + AvaloniaProperty.RegisterAttached( "AcceleratorKey", typeof(AutomationProperties)); @@ -54,16 +54,16 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.AccessKey attached property /// - public static readonly AttachedProperty AccessKeyProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AccessKeyProperty = + AvaloniaProperty.RegisterAttached( "AccessKey", typeof(AutomationProperties)); /// /// Defines the AutomationProperties.AutomationId attached property. /// - public static readonly AttachedProperty AutomationIdProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty AutomationIdProperty = + AvaloniaProperty.RegisterAttached( "AutomationId", typeof(AutomationProperties)); @@ -78,8 +78,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.HelpText attached property. /// - public static readonly AttachedProperty HelpTextProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty HelpTextProperty = + AvaloniaProperty.RegisterAttached( "HelpText", typeof(AutomationProperties)); @@ -122,16 +122,16 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.ItemStatus attached property. /// - public static readonly AttachedProperty ItemStatusProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty ItemStatusProperty = + AvaloniaProperty.RegisterAttached( "ItemStatus", typeof(AutomationProperties)); /// /// Defines the AutomationProperties.ItemType attached property. /// - public static readonly AttachedProperty ItemTypeProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty ItemTypeProperty = + AvaloniaProperty.RegisterAttached( "ItemType", typeof(AutomationProperties)); @@ -155,8 +155,8 @@ namespace Avalonia.Automation /// /// Defines the AutomationProperties.Name attached attached property. /// - public static readonly AttachedProperty NameProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty NameProperty = + AvaloniaProperty.RegisterAttached( "Name", typeof(AutomationProperties)); @@ -193,25 +193,17 @@ namespace Avalonia.Automation /// public static void SetAcceleratorKey(StyledElement element, string value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AcceleratorKeyProperty, value); } /// /// Helper for reading AcceleratorKey property from a StyledElement. /// - public static string GetAcceleratorKey(StyledElement element) + public static string? GetAcceleratorKey(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(AcceleratorKeyProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(AcceleratorKeyProperty); } /// @@ -219,11 +211,7 @@ namespace Avalonia.Automation /// public static void SetAccessibilityView(StyledElement element, AccessibilityView value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AccessibilityViewProperty, value); } @@ -232,11 +220,7 @@ namespace Avalonia.Automation /// public static AccessibilityView GetAccessibilityView(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(AccessibilityViewProperty); } @@ -245,50 +229,34 @@ namespace Avalonia.Automation /// public static void SetAccessKey(StyledElement element, string value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AccessKeyProperty, value); } /// /// Helper for reading AccessKey property from a StyledElement. /// - public static string GetAccessKey(StyledElement element) + public static string? GetAccessKey(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(AccessKeyProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(AccessKeyProperty); } /// /// Helper for setting AutomationId property on a StyledElement. /// - public static void SetAutomationId(StyledElement element, string value) + public static void SetAutomationId(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(AutomationIdProperty, value); } /// /// Helper for reading AutomationId property from a StyledElement. /// - public static string GetAutomationId(StyledElement element) + public static string? GetAutomationId(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(AutomationIdProperty); } @@ -297,11 +265,7 @@ namespace Avalonia.Automation /// public static void SetControlTypeOverride(StyledElement element, AutomationControlType? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ControlTypeOverrideProperty, value); } @@ -310,38 +274,26 @@ namespace Avalonia.Automation /// public static AutomationControlType? GetControlTypeOverride(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(ControlTypeOverrideProperty); } /// /// Helper for setting HelpText property on a StyledElement. /// - public static void SetHelpText(StyledElement element, string value) + public static void SetHelpText(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(HelpTextProperty, value); } /// /// Helper for reading HelpText property from a StyledElement. /// - public static string GetHelpText(StyledElement element) + public static string? GetHelpText(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(HelpTextProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(HelpTextProperty); } /// @@ -349,11 +301,7 @@ namespace Avalonia.Automation /// public static void SetIsColumnHeader(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsColumnHeaderProperty, value); } @@ -362,12 +310,8 @@ namespace Avalonia.Automation /// public static bool GetIsColumnHeader(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsColumnHeaderProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsColumnHeaderProperty); } /// @@ -375,11 +319,7 @@ namespace Avalonia.Automation /// public static void SetIsRequiredForForm(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsRequiredForFormProperty, value); } @@ -388,12 +328,8 @@ namespace Avalonia.Automation /// public static bool GetIsRequiredForForm(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsRequiredForFormProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsRequiredForFormProperty); } /// @@ -401,12 +337,8 @@ namespace Avalonia.Automation /// public static bool GetIsRowHeader(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((bool)element.GetValue(IsRowHeaderProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsRowHeaderProperty); } /// @@ -414,11 +346,7 @@ namespace Avalonia.Automation /// public static void SetIsRowHeader(StyledElement element, bool value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsRowHeaderProperty, value); } @@ -427,11 +355,7 @@ namespace Avalonia.Automation /// public static void SetIsOffscreenBehavior(StyledElement element, IsOffscreenBehavior value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(IsOffscreenBehaviorProperty, value); } @@ -440,64 +364,44 @@ namespace Avalonia.Automation /// public static IsOffscreenBehavior GetIsOffscreenBehavior(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((IsOffscreenBehavior)element.GetValue(IsOffscreenBehaviorProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(IsOffscreenBehaviorProperty); } /// /// Helper for setting ItemStatus property on a StyledElement. /// - public static void SetItemStatus(StyledElement element, string value) + public static void SetItemStatus(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ItemStatusProperty, value); } /// /// Helper for reading ItemStatus property from a StyledElement. /// - public static string GetItemStatus(StyledElement element) + public static string? GetItemStatus(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(ItemStatusProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(ItemStatusProperty); } /// /// Helper for setting ItemType property on a StyledElement. /// - public static void SetItemType(StyledElement element, string value) + public static void SetItemType(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(ItemTypeProperty, value); } /// /// Helper for reading ItemType property from a StyledElement. /// - public static string GetItemType(StyledElement element) + public static string? GetItemType(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(ItemTypeProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(ItemTypeProperty); } /// @@ -505,11 +409,7 @@ namespace Avalonia.Automation /// public static void SetLabeledBy(StyledElement element, Control value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(LabeledByProperty, value); } @@ -518,11 +418,7 @@ namespace Avalonia.Automation /// public static Control GetLabeledBy(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); return element.GetValue(LabeledByProperty); } @@ -531,11 +427,7 @@ namespace Avalonia.Automation /// public static void SetLiveSetting(StyledElement element, AutomationLiveSetting value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(LiveSettingProperty, value); } @@ -544,38 +436,26 @@ namespace Avalonia.Automation /// public static AutomationLiveSetting GetLiveSetting(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((AutomationLiveSetting)element.GetValue(LiveSettingProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(LiveSettingProperty); } /// /// Helper for setting Name property on a StyledElement. /// - public static void SetName(StyledElement element, string value) + public static void SetName(StyledElement element, string? value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(NameProperty, value); } /// /// Helper for reading Name property from a StyledElement. /// - public static string GetName(StyledElement element) + public static string? GetName(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((string)element.GetValue(NameProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(NameProperty); } /// @@ -583,11 +463,7 @@ namespace Avalonia.Automation /// public static void SetPositionInSet(StyledElement element, int value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(PositionInSetProperty, value); } @@ -596,12 +472,8 @@ namespace Avalonia.Automation /// public static int GetPositionInSet(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((int)element.GetValue(PositionInSetProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(PositionInSetProperty); } /// @@ -609,11 +481,7 @@ namespace Avalonia.Automation /// public static void SetSizeOfSet(StyledElement element, int value) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - + _ = element ?? throw new ArgumentNullException(nameof(element)); element.SetValue(SizeOfSetProperty, value); } @@ -622,12 +490,8 @@ namespace Avalonia.Automation /// public static int GetSizeOfSet(StyledElement element) { - if (element == null) - { - throw new ArgumentNullException(nameof(element)); - } - - return ((int)element.GetValue(SizeOfSetProperty)); + _ = element ?? throw new ArgumentNullException(nameof(element)); + return element.GetValue(SizeOfSetProperty); } } } diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs index 9c88bae5f6..3300292857 100644 --- a/src/Avalonia.Controls/Calendar/Calendar.cs +++ b/src/Avalonia.Controls/Calendar/Calendar.cs @@ -237,11 +237,11 @@ namespace Avalonia.Controls private DateTime _selectedYear; private DateTime _displayDate = DateTime.Today; - private DateTime? _displayDateStart = null; - private DateTime? _displayDateEnd = null; + private DateTime? _displayDateStart; + private DateTime? _displayDateEnd; private bool _isShiftPressed; - private bool _displayDateIsChanging = false; + private bool _displayDateIsChanging; internal CalendarDayButton? FocusButton { get; set; } internal CalendarButton? FocusCalendarButton { get; set; } @@ -291,7 +291,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "Invalid DayOfWeek"); + throw new ArgumentOutOfRangeException(nameof(e), "Invalid DayOfWeek"); } } @@ -346,10 +346,10 @@ namespace Avalonia.Controls } } - public static readonly StyledProperty HeaderBackgroundProperty = - AvaloniaProperty.Register(nameof(HeaderBackground)); + public static readonly StyledProperty HeaderBackgroundProperty = + AvaloniaProperty.Register(nameof(HeaderBackground)); - public IBrush HeaderBackground + public IBrush? HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } set { SetValue(HeaderBackgroundProperty, value); } @@ -478,7 +478,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "Invalid SelectionMode"); + throw new ArgumentOutOfRangeException(nameof(e), "Invalid SelectionMode"); } } @@ -574,7 +574,7 @@ namespace Avalonia.Controls } else { - throw new ArgumentOutOfRangeException("d", "SelectedDate value is not valid."); + throw new ArgumentOutOfRangeException(nameof(e), "SelectedDate value is not valid."); } } else diff --git a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs index fe8b616e02..8fb9b66f3d 100644 --- a/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs +++ b/src/Avalonia.Controls/Calendar/CalendarBlackoutDatesCollection.cs @@ -15,7 +15,7 @@ namespace Avalonia.Controls.Primitives /// /// The Calendar whose dates this object represents. /// - private Calendar _owner; + private readonly Calendar _owner; /// /// Initializes a new instance of the @@ -79,13 +79,13 @@ namespace Avalonia.Controls.Primitives if (DateTime.Compare(end, start) > -1) { - rangeStart = DateTimeHelper.DiscardTime(start).Value; - rangeEnd = DateTimeHelper.DiscardTime(end).Value; + rangeStart = DateTimeHelper.DiscardTime(start); + rangeEnd = DateTimeHelper.DiscardTime(end); } else { - rangeStart = DateTimeHelper.DiscardTime(end).Value; - rangeEnd = DateTimeHelper.DiscardTime(start).Value; + rangeStart = DateTimeHelper.DiscardTime(end); + rangeEnd = DateTimeHelper.DiscardTime(start); } int count = Count; @@ -144,7 +144,7 @@ namespace Avalonia.Controls.Primitives if (!IsValid(item)) { - throw new ArgumentOutOfRangeException("Value is not valid."); + throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid."); } base.InsertItem(index, item); @@ -186,7 +186,7 @@ namespace Avalonia.Controls.Primitives if (!IsValid(item)) { - throw new ArgumentOutOfRangeException("Value is not valid."); + throw new ArgumentOutOfRangeException(nameof(item), "Value is not valid."); } base.SetItem(index, item); diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 032f452111..3d436b4485 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -44,30 +44,30 @@ namespace Avalonia.Controls.Primitives private ITemplate? _dayTitleTemplate; private DateTime _currentMonth; - private bool _isMouseLeftButtonDown = false; - private bool _isMouseLeftButtonDownYearView = false; - private bool _isControlPressed = false; + private bool _isMouseLeftButtonDown; + private bool _isMouseLeftButtonDownYearView; + private bool _isControlPressed; - private System.Globalization.Calendar _calendar = new System.Globalization.GregorianCalendar(); - - private PointerPressedEventArgs? _downEventArg; - private PointerPressedEventArgs? _downEventArgYearView; + private readonly System.Globalization.Calendar _calendar = new GregorianCalendar(); internal Calendar? Owner { get; set; } internal CalendarDayButton? CurrentButton { get; set; } - public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); - public IBrush HeaderBackground + public static readonly StyledProperty HeaderBackgroundProperty = Calendar.HeaderBackgroundProperty.AddOwner(); + + public IBrush? HeaderBackground { get { return GetValue(HeaderBackgroundProperty); } set { SetValue(HeaderBackgroundProperty, value); } } + public static readonly DirectProperty?> DayTitleTemplateProperty = AvaloniaProperty.RegisterDirect?>( nameof(DayTitleTemplate), o => o.DayTitleTemplate, (o,v) => o.DayTitleTemplate = v, defaultBindingMode: BindingMode.OneTime); + public ITemplate? DayTitleTemplate { get { return _dayTitleTemplate; } @@ -178,7 +178,7 @@ namespace Avalonia.Controls.Primitives { if (_dayTitleTemplate != null) { - var cell = (Control) _dayTitleTemplate.Build(); + var cell = _dayTitleTemplate.Build(); cell.DataContext = string.Empty; cell.SetValue(Grid.RowProperty, 0); cell.SetValue(Grid.ColumnProperty, i); @@ -308,16 +308,13 @@ namespace Avalonia.Controls.Primitives for (int childIndex = 0; childIndex < Calendar.ColumnsPerMonth; childIndex++) { var daytitle = MonthView!.Children[childIndex]; - if (daytitle != null) + if (Owner != null) { - if (Owner != null) - { - daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek]; - } - else - { - daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek]; - } + daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)Owner.FirstDayOfWeek) % NumberOfDaysPerWeek]; + } + else + { + daytitle.DataContext = DateTimeHelper.GetCurrentDateFormat().ShortestDayNames[(childIndex + (int)DateTimeHelper.GetCurrentDateFormat().FirstDayOfWeek) % NumberOfDaysPerWeek]; } } } @@ -527,7 +524,7 @@ namespace Avalonia.Controls.Primitives childButton.Content = dateToAdd.Day.ToString(DateTimeHelper.GetCurrentDateFormat()); childButton.DataContext = dateToAdd; - if (DateTime.Compare((DateTime)DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0) + if (DateTime.Compare(DateTimeHelper.DiscardTime(DateTime.MaxValue), dateToAdd) > 0) { // Since we are sure DisplayDate is not equal to // DateTime.MaxValue, it is safe to use AddDays @@ -587,7 +584,7 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - _currentMonth = (DateTime)Owner.SelectedMonth; + _currentMonth = Owner.SelectedMonth; } else { @@ -676,7 +673,7 @@ namespace Avalonia.Controls.Primitives if (Owner != null) { selectedYear = Owner.SelectedYear; - _currentMonth = (DateTime)Owner.SelectedMonth; + _currentMonth = Owner.SelectedMonth; } else { @@ -696,9 +693,9 @@ namespace Avalonia.Controls.Primitives SetYearButtons(decade, decadeEnd); } } - internal void UpdateYearViewSelection(CalendarButton calendarButton) + internal void UpdateYearViewSelection(CalendarButton? calendarButton) { - if (Owner != null && calendarButton != null && calendarButton.DataContext != null) + if (Owner != null && calendarButton?.DataContext is DateTime selectedDate) { Owner.FocusCalendarButton!.IsCalendarButtonFocused = false; Owner.FocusCalendarButton = calendarButton; @@ -706,11 +703,11 @@ namespace Avalonia.Controls.Primitives if (Owner.DisplayMode == CalendarMode.Year) { - Owner.SelectedMonth = (DateTime)calendarButton.DataContext; + Owner.SelectedMonth = selectedDate; } else { - Owner.SelectedYear = (DateTime)calendarButton.DataContext; + Owner.SelectedYear = selectedDate; } } } @@ -719,7 +716,7 @@ namespace Avalonia.Controls.Primitives { int year; int count = -1; - foreach (object child in YearView!.Children) + foreach (var child in YearView!.Children) { CalendarButton childButton = (CalendarButton)child; year = decade + count; @@ -859,7 +856,8 @@ namespace Avalonia.Controls.Primitives { if (Owner != null) { - if (_isMouseLeftButtonDown && sender is CalendarDayButton b && b.IsEnabled && !b.IsBlackout) + if (_isMouseLeftButtonDown + && sender is CalendarDayButton { IsEnabled: true, IsBlackout: false, DataContext: DateTime selectedDate } b) { // Update the states of all buttons to be selected starting // from HoverStart to b @@ -867,7 +865,6 @@ namespace Avalonia.Controls.Primitives { case CalendarSelectionMode.SingleDate: { - DateTime selectedDate = (DateTime)b.DataContext!; Owner.CalendarDatePickerDisplayDateFlag = true; if (Owner.SelectedDates.Count == 0) { @@ -882,10 +879,9 @@ namespace Avalonia.Controls.Primitives case CalendarSelectionMode.SingleRange: case CalendarSelectionMode.MultipleRange: { - Debug.Assert(b.DataContext != null, "The DataContext should not be null!"); Owner.UnHighlightDays(); Owner.HoverEndIndex = b.Index; - Owner.HoverEnd = (DateTime?)b.DataContext; + Owner.HoverEnd = selectedDate; // Update the States of the buttons Owner.HighlightDays(); return; @@ -904,22 +900,14 @@ namespace Avalonia.Controls.Primitives Owner.Focus(); } - bool ctrl, shift; - CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out ctrl, out shift); - CalendarDayButton b = (CalendarDayButton)sender!; + CalendarExtensions.GetMetaKeyState(e.KeyModifiers, out var ctrl, out var shift); - if (b != null) + if (sender is CalendarDayButton b) { _isControlPressed = ctrl; - if (b.IsEnabled && !b.IsBlackout) + if (b.IsEnabled && !b.IsBlackout && b.DataContext is DateTime selectedDate) { - DateTime selectedDate = (DateTime)b.DataContext!; _isMouseLeftButtonDown = true; - // null check is added for unit tests - if (e != null) - { - _downEventArg = e; - } switch (Owner.SelectionMode) { @@ -1010,12 +998,12 @@ namespace Avalonia.Controls.Primitives } } } - private void AddSelection(CalendarDayButton b) + private void AddSelection(CalendarDayButton b, DateTime selectedDate) { if (Owner != null) { Owner.HoverEndIndex = b.Index; - Owner.HoverEnd = (DateTime)b.DataContext!; + Owner.HoverEnd = selectedDate; if (Owner.HoverEnd != null && Owner.HoverStart != null) { @@ -1025,7 +1013,7 @@ namespace Avalonia.Controls.Primitives // SelectionMode Owner.IsMouseSelection = true; Owner.SelectedDates.AddRange(Owner.HoverStart.Value, Owner.HoverEnd.Value); - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); } } } @@ -1039,11 +1027,11 @@ namespace Avalonia.Controls.Primitives Owner.OnDayButtonMouseUp(e); } _isMouseLeftButtonDown = false; - if (b != null && b.DataContext != null) + if (b != null && b.DataContext is DateTime selectedDate) { if (Owner.SelectionMode == CalendarSelectionMode.None || Owner.SelectionMode == CalendarSelectionMode.SingleDate) { - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); return; } if (Owner.HoverStart.HasValue) @@ -1058,14 +1046,14 @@ namespace Avalonia.Controls.Primitives Owner.RemovedItems.Add(item); } Owner.SelectedDates.ClearInternal(); - AddSelection(b); + AddSelection(b, selectedDate); return; } case CalendarSelectionMode.MultipleRange: { // add the selection (either single day or // SingleRange day) - AddSelection(b); + AddSelection(b, selectedDate); return; } } @@ -1076,7 +1064,7 @@ namespace Avalonia.Controls.Primitives // be able to switch months if (b.IsInactive && b.IsBlackout) { - Owner.OnDayClick((DateTime)b.DataContext); + Owner.OnDayClick(selectedDate); } } } @@ -1095,9 +1083,9 @@ namespace Avalonia.Controls.Primitives Owner.HoverStart = null; _isMouseLeftButtonDown = false; b.IsSelected = false; - if (b.DataContext != null) + if (b.DataContext is DateTime selectedDate) { - Owner.SelectedDates.Remove((DateTime)b.DataContext); + Owner.SelectedDates.Remove(selectedDate); } } } @@ -1107,35 +1095,26 @@ namespace Avalonia.Controls.Primitives private void Month_CalendarButtonMouseDown(object? sender, PointerPressedEventArgs e) { - CalendarButton b = (CalendarButton)sender!; - _isMouseLeftButtonDownYearView = true; - if (e != null) - { - _downEventArgYearView = e; - } - - UpdateYearViewSelection(b); + UpdateYearViewSelection(sender as CalendarButton); } internal void Month_CalendarButtonMouseUp(object? sender, PointerReleasedEventArgs e) { _isMouseLeftButtonDownYearView = false; - if (Owner != null) + if (Owner != null && (sender as CalendarButton)?.DataContext is DateTime newMonth) { - DateTime newmonth = (DateTime)((CalendarButton)sender!).DataContext!; - if (Owner.DisplayMode == CalendarMode.Year) { - Owner.DisplayDate = newmonth; + Owner.DisplayDate = newMonth; Owner.DisplayMode = CalendarMode.Month; } else { Debug.Assert(Owner.DisplayMode == CalendarMode.Decade, "The owning Calendar should be in decade mode!"); - Owner.SelectedMonth = newmonth; + Owner.SelectedMonth = newMonth; Owner.DisplayMode = CalendarMode.Year; } } @@ -1145,8 +1124,7 @@ namespace Avalonia.Controls.Primitives { if (_isMouseLeftButtonDownYearView) { - CalendarButton b = (CalendarButton)sender!; - UpdateYearViewSelection(b); + UpdateYearViewSelection(sender as CalendarButton); } } diff --git a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs index bfff03a926..570f05cfe8 100644 --- a/src/Avalonia.Controls/Calendar/DateTimeHelper.cs +++ b/src/Avalonia.Controls/Calendar/DateTimeHelper.cs @@ -53,7 +53,7 @@ namespace Avalonia.Controls public static int CompareDays(DateTime dt1, DateTime dt2) { - return DateTime.Compare(DiscardTime(dt1).Value, DiscardTime(dt2).Value); + return DateTime.Compare(DiscardTime(dt1), DiscardTime(dt2)); } public static int CompareYearMonth(DateTime dt1, DateTime dt2) @@ -71,14 +71,9 @@ namespace Avalonia.Controls return new DateTime(d.Year, d.Month, 1, 0, 0, 0); } - [return: NotNullIfNotNull("d")] - public static DateTime? DiscardTime(DateTime? d) + public static DateTime DiscardTime(DateTime d) { - if (d == null) - { - return null; - } - return d.Value.Date; + return d.Date; } public static int EndOfDecade(DateTime date) @@ -127,28 +122,14 @@ namespace Avalonia.Controls public static string ToYearMonthPatternString(DateTime date) { - string result = string.Empty; - DateTimeFormatInfo format = GetCurrentDateFormat(); - - if (format != null) - { - result = date.ToString(format.YearMonthPattern, format); - } - - return result; + var format = GetCurrentDateFormat(); + return date.ToString(format.YearMonthPattern, format); } public static string ToYearString(DateTime date) { - string result = string.Empty; - DateTimeFormatInfo format = GetCurrentDateFormat(); - - if (format != null) - { - result = date.Year.ToString(format); - } - - return result; + var format = GetCurrentDateFormat(); + return date.Year.ToString(format); } } } diff --git a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs index b17648f5bb..869bdeabea 100644 --- a/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs +++ b/src/Avalonia.Controls/CalendarDatePicker/CalendarDatePicker.cs @@ -51,11 +51,11 @@ namespace Avalonia.Controls private bool _isDropDownOpen; private DateTime? _selectedDate; private string? _text; - private bool _suspendTextChangeHandler = false; - private bool _isPopupClosing = false; - private bool _ignoreButtonClick = false; - private bool _isFlyoutOpen = false; - private bool _isPressed = false; + private bool _suspendTextChangeHandler; + private bool _isPopupClosing; + private bool _ignoreButtonClick; + private bool _isFlyoutOpen; + private bool _isPressed; /// /// Occurs when the drop-down @@ -185,7 +185,7 @@ namespace Avalonia.Controls { _textBox.KeyDown += TextBox_KeyDown; _textBox.GotFocus += TextBox_GotFocus; - _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(txt => TextBox_TextChanged()); + _textBoxTextChangedSubscription = _textBox.GetObservable(TextBox.TextProperty).Subscribe(_ => TextBox_TextChanged()); if(SelectedDate.HasValue) { @@ -292,7 +292,7 @@ namespace Avalonia.Controls // Text else if (change.Property == TextProperty) { - var (oldValue, newValue) = change.GetOldAndNewValue(); + var (_, newValue) = change.GetOldAndNewValue(); if (!_suspendTextChangeHandler) { @@ -595,9 +595,9 @@ namespace Avalonia.Controls private void Calendar_KeyDown(object? sender, KeyEventArgs e) { - Calendar? c = sender as Calendar ?? throw new ArgumentException("Sender must be Calendar.", nameof(sender)); - - if (!e.Handled && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape) && c.DisplayMode == CalendarMode.Month) + if (!e.Handled + && sender is Calendar { DisplayMode: CalendarMode.Month } + && (e.Key == Key.Enter || e.Key == Key.Space || e.Key == Key.Escape)) { Focus(); IsDropDownOpen = false; diff --git a/src/Avalonia.Controls/Chrome/TitleBar.cs b/src/Avalonia.Controls/Chrome/TitleBar.cs index 47b0bb6e2d..368c9d4c2f 100644 --- a/src/Avalonia.Controls/Chrome/TitleBar.cs +++ b/src/Avalonia.Controls/Chrome/TitleBar.cs @@ -17,28 +17,26 @@ namespace Avalonia.Controls.Chrome private void UpdateSize(Window window) { - if (window != null) + Margin = new Thickness( + window.OffScreenMargin.Left, + window.OffScreenMargin.Top, + window.OffScreenMargin.Right, + window.OffScreenMargin.Bottom); + + if (window.WindowState != WindowState.FullScreen) { - Margin = new Thickness( - window.OffScreenMargin.Left, - window.OffScreenMargin.Top, - window.OffScreenMargin.Right, - window.OffScreenMargin.Bottom); + Height = window.WindowDecorationMargin.Top; - if (window.WindowState != WindowState.FullScreen) + if (_captionButtons != null) { - Height = window.WindowDecorationMargin.Top; - - if (_captionButtons != null) - { - _captionButtons.Height = Height; - } + _captionButtons.Height = Height; } - - IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } + + IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -55,6 +53,7 @@ namespace Avalonia.Controls.Chrome } } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); @@ -64,13 +63,13 @@ namespace Avalonia.Controls.Chrome _disposables = new CompositeDisposable(6) { window.GetObservable(Window.WindowDecorationMarginProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.OffScreenMarginProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.ExtendClientAreaChromeHintsProperty) - .Subscribe(x => UpdateSize(window)), + .Subscribe(_ => UpdateSize(window)), window.GetObservable(Window.WindowStateProperty) .Subscribe(x => { @@ -80,11 +79,12 @@ namespace Avalonia.Controls.Chrome PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); }), window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty) - .Subscribe(x => UpdateSize(window)) + .Subscribe(_ => UpdateSize(window)) }; } } + /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { base.OnDetachedFromVisualTree(e); diff --git a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs index 9d859a753a..18d668e9a4 100644 --- a/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs +++ b/src/Avalonia.Controls/Converters/MenuScrollingVisibilityConverter.cs @@ -14,7 +14,6 @@ namespace Avalonia.Controls.Converters public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) { if (parameter == null || - values == null || values.Count != 4 || !(values[0] is ScrollBarVisibility visibility) || !(values[1] is double offset) || diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 5c35a09f1c..eb587fb157 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -21,9 +21,9 @@ namespace Avalonia.Controls /// /// SharedSizeGroup property. /// - public string SharedSizeGroup + public string? SharedSizeGroup { - get { return (string)GetValue(SharedSizeGroupProperty); } + get { return GetValue(SharedSizeGroupProperty); } set { SetValue(SharedSizeGroupProperty, value); } } @@ -32,20 +32,15 @@ namespace Avalonia.Controls /// internal void OnEnterParentTree() { - this.InheritanceParent = Parent; + InheritanceParent = Parent; if (_sharedState == null) { // start with getting SharedSizeGroup value. // this property is NOT inherited which should result in better overall perf. - string sharedSizeGroupId = SharedSizeGroup; - if (sharedSizeGroupId != null) + if (SharedSizeGroup is { } sharedSizeGroupId && PrivateSharedSizeScope is { } privateSharedSizeScope) { - SharedSizeScope? privateSharedSizeScope = PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - _sharedState.AddMember(this); - } + _sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + _sharedState.AddMember(this); } } @@ -321,13 +316,12 @@ namespace Avalonia.Controls return ((_flags & flags) == flags); } - private static void OnSharedSizeGroupPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + private static void OnSharedSizeGroupPropertyChanged(DefinitionBase definition, + AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase)d; - if (definition.Parent != null) { - string sharedSizeGroupId = (string)e.NewValue!; + string? sharedSizeGroupId = e.NewValue.Value; if (definition._sharedState != null) { @@ -337,16 +331,14 @@ namespace Avalonia.Controls definition._sharedState = null; } - if ((definition._sharedState == null) && (sharedSizeGroupId != null)) + if (definition._sharedState == null + && sharedSizeGroupId != null + && definition.PrivateSharedSizeScope is { } privateSharedSizeScope) { - SharedSizeScope? privateSharedSizeScope = definition.PrivateSharedSizeScope; - if (privateSharedSizeScope != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); - definition._sharedState.AddMember(definition); - } + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroupId); + definition._sharedState.AddMember(definition); } } } @@ -357,17 +349,15 @@ namespace Avalonia.Controls /// b) contains only letters, digits and underscore ('_'). /// c) does not start with a digit. /// - private static bool SharedSizeGroupPropertyValueValid(string value) + private static bool SharedSizeGroupPropertyValueValid(string? id) { // null is default value - if (value == null) + if (id == null) { return true; } - string id = (string)value; - - if (!string.IsNullOrEmpty(id)) + if (id.Length > 0) { int i = -1; while (++i < id.Length) @@ -397,14 +387,11 @@ namespace Avalonia.Controls /// existing scope just left. In both cases if the DefinitionBase object is already registered /// in SharedSizeState, it should un-register and register itself in a new one. /// - private static void OnPrivateSharedSizeScopePropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) + private static void OnPrivateSharedSizeScopePropertyChanged(DefinitionBase definition, + AvaloniaPropertyChangedEventArgs e) { - DefinitionBase definition = (DefinitionBase)d; - if (definition.Parent != null) { - SharedSizeScope privateSharedSizeScope = (SharedSizeScope)e.NewValue!; - if (definition._sharedState != null) { // if definition is already registered And shared size scope is changing, @@ -413,16 +400,14 @@ namespace Avalonia.Controls definition._sharedState = null; } - if ((definition._sharedState == null) && (privateSharedSizeScope != null)) + if (definition._sharedState == null + && e.NewValue.Value is { } privateSharedSizeScope + && definition.SharedSizeGroup is { } sharedSizeGroup) { - string sharedSizeGroup = definition.SharedSizeGroup; - if (sharedSizeGroup != null) - { - // if definition is not registered and both: shared size group id AND private shared scope - // are available, then register definition. - definition._sharedState = privateSharedSizeScope.EnsureSharedState(definition.SharedSizeGroup); - definition._sharedState.AddMember(definition); - } + // if definition is not registered and both: shared size group id AND private shared scope + // are available, then register definition. + definition._sharedState = privateSharedSizeScope.EnsureSharedState(sharedSizeGroup); + definition._sharedState.AddMember(definition); } } } @@ -432,7 +417,7 @@ namespace Avalonia.Controls /// private SharedSizeScope? PrivateSharedSizeScope { - get { return (SharedSizeScope?)GetValue(PrivateSharedSizeScopeProperty); } + get { return GetValue(PrivateSharedSizeScopeProperty); } } /// @@ -465,7 +450,7 @@ namespace Avalonia.Controls private SharedSizeState? _sharedState; // reference to shared state object this instance is registered with - [System.Flags] + [Flags] private enum Flags : byte { // @@ -520,11 +505,10 @@ namespace Avalonia.Controls /// internal SharedSizeState(SharedSizeScope sharedSizeScope, string sharedSizeGroupId) { - Debug.Assert(sharedSizeScope != null && sharedSizeGroupId != null); _sharedSizeScope = sharedSizeScope; _sharedSizeGroupId = sharedSizeGroupId; _registry = new List(); - _layoutUpdated = new EventHandler(OnLayoutUpdated); + _layoutUpdated = OnLayoutUpdated; _broadcastInvalidation = true; } @@ -568,7 +552,7 @@ namespace Avalonia.Controls { for (int i = 0, count = _registry.Count; i < count; ++i) { - Grid parentGrid = (Grid)(_registry[i].Parent!); + Grid parentGrid = _registry[i].Parent!; parentGrid.Invalidate(); } _broadcastInvalidation = false; @@ -703,7 +687,7 @@ namespace Avalonia.Controls // measure is invalid - it used the old shared size, // which is larger than d's (possibly changed) minSize measureIsValid = (definitionBase.LayoutWasUpdated && - MathUtilities.GreaterThanOrClose(definitionBase._minSize, this.MinSize)); + MathUtilities.GreaterThanOrClose(definitionBase._minSize, MinSize)); } if(!measureIsValid) @@ -786,8 +770,8 @@ namespace Avalonia.Controls /// /// /// - public static readonly AttachedProperty SharedSizeGroupProperty = - AvaloniaProperty.RegisterAttached( + public static readonly AttachedProperty SharedSizeGroupProperty = + AvaloniaProperty.RegisterAttached( "SharedSizeGroup", validate: SharedSizeGroupPropertyValueValid); @@ -796,8 +780,8 @@ namespace Avalonia.Controls /// static DefinitionBase() { - SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); - PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); + SharedSizeGroupProperty.Changed.AddClassHandler(OnSharedSizeGroupPropertyChanged); + PrivateSharedSizeScopeProperty.Changed.AddClassHandler(OnPrivateSharedSizeScopePropertyChanged); } /// diff --git a/src/Avalonia.Controls/DockPanel.cs b/src/Avalonia.Controls/DockPanel.cs index 3e3ed509b5..1a0cf1644a 100644 --- a/src/Avalonia.Controls/DockPanel.cs +++ b/src/Avalonia.Controls/DockPanel.cs @@ -101,9 +101,6 @@ namespace Avalonia.Controls Size childConstraint; // Contains the suggested input constraint for this child. Size childDesiredSize; // Contains the return size from child measure. - if (child == null) - { continue; } - // Child constraint is the remaining size; this is total size minus size consumed by previous children. childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth), Math.Max(0.0, constraint.Height - accumulatedHeight)); @@ -122,7 +119,7 @@ namespace Avalonia.Controls // will deal with computing our minimum size (parentSize) due to that accumulation. // Therefore, we only need to compute our minimum size (parentSize) in dimensions that this child does // not accumulate: Width for Top/Bottom, Height for Left/Right. - switch (DockPanel.GetDock((Control)child)) + switch (GetDock(child)) { case Dock.Left: case Dock.Right: @@ -164,8 +161,6 @@ namespace Avalonia.Controls for (int i = 0; i < totalChildrenCount; ++i) { var child = children[i]; - if (child == null) - { continue; } Size childDesiredSize = child.DesiredSize; Rect rcChild = new Rect( @@ -176,7 +171,7 @@ namespace Avalonia.Controls if (i < nonFillChildrenCount) { - switch (DockPanel.GetDock((Control)child)) + switch (GetDock(child)) { case Dock.Left: accumulatedLeft += childDesiredSize.Width; diff --git a/src/Avalonia.Controls/Documents/Inline.cs b/src/Avalonia.Controls/Documents/Inline.cs index 47581e87f1..23b806583e 100644 --- a/src/Avalonia.Controls/Documents/Inline.cs +++ b/src/Avalonia.Controls/Documents/Inline.cs @@ -13,8 +13,8 @@ namespace Avalonia.Controls.Documents /// /// AvaloniaProperty for property. /// - public static readonly StyledProperty TextDecorationsProperty = - AvaloniaProperty.Register( + public static readonly StyledProperty TextDecorationsProperty = + AvaloniaProperty.Register( nameof(TextDecorations)); /// @@ -28,7 +28,7 @@ namespace Avalonia.Controls.Documents /// /// The TextDecorations property specifies decorations that are added to the text of an element. /// - public TextDecorationCollection TextDecorations + public TextDecorationCollection? TextDecorations { get { return GetValue(TextDecorationsProperty); } set { SetValue(TextDecorationsProperty, value); } @@ -83,7 +83,8 @@ namespace Avalonia.Controls.Documents return new GenericTextRunProperties(new Typeface(FontFamily, fontStyle, fontWeight), FontSize, textDecorations, Foreground, background, BaselineAlignment); } - + + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index a7a702ceae..d3565cbdd5 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text; using Avalonia.Media.TextFormatting; @@ -51,6 +52,7 @@ namespace Avalonia.Controls.Documents } } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -68,26 +70,26 @@ namespace Avalonia.Controls.Documents { base.OnInlineHostChanged(oldValue, newValue); - if (Inlines is not null) - { - Inlines.InlineHost = newValue; - } + Inlines.InlineHost = newValue; } private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) { + void OnInlinesInvalidated(object? sender, EventArgs e) + => InlineHost?.Invalidate(); + if (oldValue is not null) { oldValue.LogicalChildren = null; oldValue.InlineHost = null; - oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate(); + oldValue.Invalidated -= OnInlinesInvalidated; } if (newValue is not null) { newValue.LogicalChildren = LogicalChildren; newValue.InlineHost = InlineHost; - newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); + newValue.Invalidated += OnInlinesInvalidated; } } } diff --git a/src/Avalonia.Controls/Grid.cs b/src/Avalonia.Controls/Grid.cs index 7737fdac2e..2501440ff2 100644 --- a/src/Avalonia.Controls/Grid.cs +++ b/src/Avalonia.Controls/Grid.cs @@ -164,20 +164,21 @@ namespace Avalonia.Controls /// /// Returns a ColumnDefinitions of column definitions. /// + [MemberNotNull(nameof(_extData))] public ColumnDefinitions ColumnDefinitions { get { - if (_data == null) { _data = new ExtendedData(); } - if (_data.ColumnDefinitions == null) { _data.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } + if (_extData == null) { _extData = new ExtendedData(); } + if (_extData.ColumnDefinitions == null) { _extData.ColumnDefinitions = new ColumnDefinitions() { Parent = this }; } - return (_data.ColumnDefinitions); + return (_extData.ColumnDefinitions); } set { - if (_data == null) { _data = new ExtendedData(); } - _data.ColumnDefinitions = value; - _data.ColumnDefinitions.Parent = this; + if (_extData == null) { _extData = new ExtendedData(); } + _extData.ColumnDefinitions = value; + _extData.ColumnDefinitions.Parent = this; InvalidateMeasure(); } } @@ -185,20 +186,21 @@ namespace Avalonia.Controls /// /// Returns a RowDefinitions of row definitions. /// + [MemberNotNull(nameof(_extData))] public RowDefinitions RowDefinitions { get { - if (_data == null) { _data = new ExtendedData(); } - if (_data.RowDefinitions == null) { _data.RowDefinitions = new RowDefinitions() { Parent = this }; } + if (_extData == null) { _extData = new ExtendedData(); } + if (_extData.RowDefinitions == null) { _extData.RowDefinitions = new RowDefinitions() { Parent = this }; } - return (_data.RowDefinitions); + return (_extData.RowDefinitions); } set { - if (_data == null) { _data = new ExtendedData(); } - _data.RowDefinitions = value; - _data.RowDefinitions.Parent = this; + if (_extData == null) { _extData = new ExtendedData(); } + _extData.RowDefinitions = value; + _extData.RowDefinitions.Parent = this; InvalidateMeasure(); } } @@ -211,7 +213,7 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size constraint) { Size gridDesiredSize; - ExtendedData extData = ExtData; + var extData = _extData; try { @@ -221,17 +223,14 @@ namespace Avalonia.Controls if (extData == null) { gridDesiredSize = new Size(); - var children = this.Children; + var children = Children; for (int i = 0, count = children.Count; i < count; ++i) { var child = children[i]; - if (child != null) - { - child.Measure(constraint); - gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), - Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); - } + child.Measure(constraint); + gridDesiredSize = new Size(Math.Max(gridDesiredSize.Width, child.DesiredSize.Width), + Math.Max(gridDesiredSize.Height, child.DesiredSize.Height)); } } else @@ -512,17 +511,14 @@ namespace Avalonia.Controls { ArrangeOverrideInProgress = true; - if (_data == null) + if (_extData is null) { - var children = this.Children; + var children = Children; for (int i = 0, count = children.Count; i < count; ++i) { var child = children[i]; - if (child != null) - { - child.Arrange(new Rect(arrangeSize)); - } + child.Arrange(new Rect(arrangeSize)); } } else @@ -532,15 +528,11 @@ namespace Avalonia.Controls SetFinalSize(DefinitionsU, arrangeSize.Width, true); SetFinalSize(DefinitionsV, arrangeSize.Height, false); - var children = this.Children; + var children = Children; for (int currentCell = 0; currentCell < PrivateCells.Length; ++currentCell) { var cell = children[currentCell]; - if (cell == null) - { - continue; - } int columnIndex = PrivateCells[currentCell].ColumnIndex; int rowIndex = PrivateCells[currentCell].RowIndex; @@ -599,7 +591,7 @@ namespace Avalonia.Controls { double value = 0.0; - Debug.Assert(_data != null); + Debug.Assert(_extData != null); // actual value calculations require structure to be up-to-date if (!ColumnDefinitionsDirty) @@ -621,7 +613,7 @@ namespace Avalonia.Controls { double value = 0.0; - Debug.Assert(_data != null); + Debug.Assert(_extData != null); // actual value calculations require structure to be up-to-date if (!RowDefinitionsDirty) @@ -654,18 +646,20 @@ namespace Avalonia.Controls /// /// Convenience accessor to ValidDefinitionsUStructure bit flag. /// + [MemberNotNull(nameof(_extData))] internal bool ColumnDefinitionsDirty { - get => ColumnDefinitions?.IsDirty ?? false; + get => ColumnDefinitions.IsDirty; set => ColumnDefinitions.IsDirty = value; } /// /// Convenience accessor to ValidDefinitionsVStructure bit flag. /// + [MemberNotNull(nameof(_extData))] internal bool RowDefinitionsDirty { - get => RowDefinitions?.IsDirty ?? false; + get => RowDefinitions.IsDirty; set => RowDefinitions.IsDirty = value; } @@ -686,8 +680,10 @@ namespace Avalonia.Controls /// private void ValidateCellsCore() { - var children = this.Children; - ExtendedData extData = ExtData; + Debug.Assert(_extData is not null); + + var children = Children; + var extData = _extData!; extData.CellCachesCollection = new CellCache[children.Count]; extData.CellGroup1 = int.MaxValue; @@ -702,10 +698,6 @@ namespace Avalonia.Controls for (int i = PrivateCells.Length - 1; i >= 0; --i) { var child = children[i]; - if (child == null) - { - continue; - } CellCache cell = new CellCache(); @@ -713,19 +705,19 @@ namespace Avalonia.Controls // Read indices from the corresponding properties: // clamp to value < number_of_columns // column >= 0 is guaranteed by property value validation callback - cell.ColumnIndex = Math.Min(GetColumn((Control)child), DefinitionsU.Count - 1); + cell.ColumnIndex = Math.Min(GetColumn(child), DefinitionsU.Count - 1); // clamp to value < number_of_rows // row >= 0 is guaranteed by property value validation callback - cell.RowIndex = Math.Min(GetRow((Control)child), DefinitionsV.Count - 1); + cell.RowIndex = Math.Min(GetRow(child), DefinitionsV.Count - 1); // Read span properties: // clamp to not exceed beyond right side of the grid // column_span > 0 is guaranteed by property value validation callback - cell.ColumnSpan = Math.Min(GetColumnSpan((Control)child), DefinitionsU.Count - cell.ColumnIndex); + cell.ColumnSpan = Math.Min(GetColumnSpan(child), DefinitionsU.Count - cell.ColumnIndex); // clamp to not exceed beyond bottom side of the grid // row_span > 0 is guaranteed by property value validation callback - cell.RowSpan = Math.Min(GetRowSpan((Control)child), DefinitionsV.Count - cell.RowIndex); + cell.RowSpan = Math.Min(GetRowSpan(child), DefinitionsV.Count - cell.RowIndex); Debug.Assert(0 <= cell.ColumnIndex && cell.ColumnIndex < DefinitionsU.Count); Debug.Assert(0 <= cell.RowIndex && cell.RowIndex < DefinitionsV.Count); @@ -792,7 +784,7 @@ namespace Avalonia.Controls { if (ColumnDefinitionsDirty) { - ExtendedData extData = ExtData; + var extData = _extData; if (extData.ColumnDefinitions == null) { @@ -818,7 +810,7 @@ namespace Avalonia.Controls ColumnDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsU != null && ExtData.DefinitionsU.Count > 0); + Debug.Assert(_extData is { DefinitionsU.Count: > 0 }); } /// @@ -833,7 +825,7 @@ namespace Avalonia.Controls { if (RowDefinitionsDirty) { - ExtendedData extData = ExtData; + var extData = _extData; if (extData.RowDefinitions == null) { @@ -859,7 +851,7 @@ namespace Avalonia.Controls RowDefinitionsDirty = false; } - Debug.Assert(ExtData.DefinitionsV != null && ExtData.DefinitionsV.Count > 0); + Debug.Assert(_extData is { DefinitionsV.Count: > 0 }); } /// @@ -965,8 +957,7 @@ namespace Avalonia.Controls bool ignoreDesiredSizeU, bool forceInfinityV) { - bool unusedHasDesiredSizeUChanged; - MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out unusedHasDesiredSizeUChanged); + MeasureCellsGroup(cellsHead, referenceSize, ignoreDesiredSizeU, forceInfinityV, out _); } /// @@ -994,7 +985,7 @@ namespace Avalonia.Controls return; } - var children = this.Children; + var children = Children; Hashtable? spanStore = null; bool ignoreDesiredSizeV = forceInfinityV; @@ -1101,8 +1092,6 @@ namespace Avalonia.Controls int cell, bool forceInfinityV) { - - double cellMeasureWidth; double cellMeasureHeight; @@ -1144,15 +1133,9 @@ namespace Avalonia.Controls } - var child = this.Children[cell]; - if (child != null) - { - Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); - child.Measure(childConstraint); - } - - - + var child = Children[cell]; + Size childConstraint = new Size(cellMeasureWidth, cellMeasureHeight); + child.Measure(childConstraint); } /// @@ -1230,7 +1213,7 @@ namespace Avalonia.Controls // avoid processing when asked to distribute "0" if (!MathUtilities.IsZero(requestedSize)) { - DefinitionBase[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting + DefinitionBase?[] tempDefinitions = TempDefinitions; // temp array used to remember definitions for sorting int end = start + count; int autoDefinitionsCount = 0; double rangeMinSize = 0; @@ -1288,20 +1271,24 @@ namespace Avalonia.Controls Array.Sort(tempDefinitions, 0, count, s_spanPreferredDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize; i < autoDefinitionsCount; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(tempDefinition.UserSize.IsAuto); // adjust sizeToDistribute value by subtracting auto definition min size - sizeToDistribute -= (tempDefinitions[i].MinSize); + sizeToDistribute -= (tempDefinition.MinSize); } for (; i < count; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(!tempDefinition.UserSize.IsAuto); - double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinitions[i].PreferredSize); - if (newMinSize > tempDefinitions[i].MinSize) { tempDefinitions[i].UpdateMinSize(newMinSize); } + double newMinSize = Math.Min(sizeToDistribute / (count - i), tempDefinition.PreferredSize); + if (newMinSize > tempDefinition.MinSize) { tempDefinition.UpdateMinSize(newMinSize); } sizeToDistribute -= newMinSize; } @@ -1325,24 +1312,28 @@ namespace Avalonia.Controls Array.Sort(tempDefinitions, 0, count, s_spanMaxDistributionOrderComparer); for (i = 0, sizeToDistribute = requestedSize - rangePreferredSize; i < count - autoDefinitionsCount; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: no auto definitions allowed in this loop - Debug.Assert(!tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(!tempDefinition.UserSize.IsAuto); - double preferredSize = tempDefinitions[i].PreferredSize; + double preferredSize = tempDefinition.PreferredSize; double newMinSize = preferredSize + sizeToDistribute / (count - autoDefinitionsCount - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache)); + sizeToDistribute -= (tempDefinition.MinSize - preferredSize); } for (; i < count; ++i) { + var tempDefinition = tempDefinitions[i]!; + // sanity check: only auto definitions allowed in this loop - Debug.Assert(tempDefinitions[i].UserSize.IsAuto); + Debug.Assert(tempDefinition.UserSize.IsAuto); - double preferredSize = tempDefinitions[i].MinSize; + double preferredSize = tempDefinition.MinSize; double newMinSize = preferredSize + sizeToDistribute / (count - i); - tempDefinitions[i].UpdateMinSize(Math.Min(newMinSize, tempDefinitions[i].SizeCache)); - sizeToDistribute -= (tempDefinitions[i].MinSize - preferredSize); + tempDefinition.UpdateMinSize(Math.Min(newMinSize, tempDefinition.SizeCache)); + sizeToDistribute -= (tempDefinition.MinSize - preferredSize); } // sanity check: requested size must all be distributed @@ -1376,8 +1367,10 @@ namespace Avalonia.Controls for (int i = 0; i < count; ++i) { - double deltaSize = (maxMaxSize - tempDefinitions[i].SizeCache) * sizeToDistribute / totalRemainingSize; - tempDefinitions[i].UpdateMinSize(tempDefinitions[i].SizeCache + deltaSize); + var tempDefinition = tempDefinitions[i]!; + + double deltaSize = (maxMaxSize - tempDefinition.SizeCache) * sizeToDistribute / totalRemainingSize; + tempDefinition.UpdateMinSize(tempDefinition.SizeCache + deltaSize); } } else @@ -1388,7 +1381,7 @@ namespace Avalonia.Controls // for (int i = 0; i < count; ++i) { - tempDefinitions[i].UpdateMinSize(equalSize); + tempDefinitions[i]!.UpdateMinSize(equalSize); } } } @@ -1429,7 +1422,7 @@ namespace Avalonia.Controls double availableSize) { int defCount = definitions.Count; - DefinitionBase[] tempDefinitions = TempDefinitions; + DefinitionBase?[] tempDefinitions = TempDefinitions; int minCount = 0, maxCount = 0; double takenSize = 0; double totalStarWeight = 0.0; @@ -1560,8 +1553,8 @@ namespace Avalonia.Controls remainingStarWeight = totalStarWeight - takenStarWeight; } - double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1].MeasureSize : Double.PositiveInfinity; - double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1].SizeCache : -1.0; + double minRatio = (minCount > 0) ? tempDefinitions[minCount - 1]!.MeasureSize : Double.PositiveInfinity; + double maxRatio = (maxCount > 0) ? tempDefinitions[defCount + maxCount - 1]!.SizeCache : -1.0; // choose the def with larger ratio to the current proportion ("max discrepancy") double proportion = remainingStarWeight / remainingAvailableSize; @@ -1579,13 +1572,13 @@ namespace Avalonia.Controls double resolvedSize; if (chooseMin == true) { - resolvedDef = tempDefinitions[minCount - 1]; + resolvedDef = tempDefinitions[minCount - 1]!; resolvedSize = resolvedDef.MinSize; --minCount; } else { - resolvedDef = tempDefinitions[defCount + maxCount - 1]; + resolvedDef = tempDefinitions[defCount + maxCount - 1]!; resolvedSize = Math.Max(resolvedDef.MinSize, resolvedDef.UserMaxSize); --maxCount; } @@ -1603,12 +1596,12 @@ namespace Avalonia.Controls // advance to the next candidate defs, removing ones that have been resolved. // Both counts are advanced, as a def might appear in both lists. - while (minCount > 0 && tempDefinitions[minCount - 1].MeasureSize < 0.0) + while (minCount > 0 && tempDefinitions[minCount - 1]!.MeasureSize < 0.0) { --minCount; tempDefinitions[minCount] = null!; } - while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1].MeasureSize < 0.0) + while (maxCount > 0 && tempDefinitions[defCount + maxCount - 1]!.MeasureSize < 0.0) { --maxCount; tempDefinitions[defCount + maxCount] = null!; @@ -1637,8 +1630,7 @@ namespace Avalonia.Controls // resolved as 'min'. Their allocation can be increased to make up the gap. for (int i = minCount; i < minCountPhase2; ++i) { - DefinitionBase def = tempDefinitions[i]; - if (def != null) + if (tempDefinitions[i] is { } def) { def.MeasureSize = 1.0; // mark as 'not yet resolved' ++starCount; @@ -1653,8 +1645,7 @@ namespace Avalonia.Controls // resolved as 'max'. Their allocation can be decreased to make up the gap. for (int i = maxCount; i < maxCountPhase2; ++i) { - DefinitionBase def = tempDefinitions[defCount + i]; - if (def != null) + if (tempDefinitions[defCount + i] is { } def) { def.MeasureSize = 1.0; // mark as 'not yet resolved' ++starCount; @@ -1695,7 +1686,7 @@ namespace Avalonia.Controls totalStarWeight = 0.0; for (int i = 0; i < starCount; ++i) { - DefinitionBase def = tempDefinitions[i]; + DefinitionBase def = tempDefinitions[i]!; totalStarWeight += def.MeasureSize; def.SizeCache = totalStarWeight; } @@ -1703,7 +1694,7 @@ namespace Avalonia.Controls // resolve the defs, in decreasing order of weight for (int i = starCount - 1; i >= 0; --i) { - DefinitionBase def = tempDefinitions[i]; + DefinitionBase def = tempDefinitions[i]!; double resolvedSize = (def.MeasureSize > 0.0) ? Math.Max(availableSize - takenSize, 0.0) * (def.MeasureSize / def.SizeCache) : 0.0; // min and max should have no effect by now, but just in case... @@ -2095,7 +2086,7 @@ namespace Avalonia.Controls { // DpiScale dpiScale = GetDpi(); // double dpi = columns ? dpiScale.DpiScaleX : dpiScale.DpiScaleY; - var dpi = (VisualRoot as Layout.ILayoutRoot)?.LayoutScaling ?? 1.0; + var dpi = (VisualRoot as ILayoutRoot)?.LayoutScaling ?? 1.0; double[] roundingErrors = RoundingErrors; double roundedTakenSize = 0.0; @@ -2302,8 +2293,7 @@ namespace Avalonia.Controls /// private void SetValid() { - ExtendedData extData = ExtData; - if (extData != null) + if (_extData is { } extData) { // for (int i = 0; i < PrivateColumnCount; ++i) DefinitionsU[i].SetValid (); // for (int i = 0; i < PrivateRowCount; ++i) DefinitionsV[i].SetValid (); @@ -2330,12 +2320,12 @@ namespace Avalonia.Controls if (ShowGridLines && (_gridLinesRenderer == null)) { _gridLinesRenderer = new GridLinesRenderer(); - this.VisualChildren.Add(_gridLinesRenderer); + VisualChildren.Add(_gridLinesRenderer); } if ((!ShowGridLines) && (_gridLinesRenderer != null)) { - this.VisualChildren.Add(_gridLinesRenderer); + VisualChildren.Add(_gridLinesRenderer); _gridLinesRenderer = null; } @@ -2364,7 +2354,7 @@ namespace Avalonia.Controls { Grid grid = (Grid)d; - if (grid.ExtData != null // trivial grid is 1 by 1. there is no grid lines anyway + if (grid._extData != null // trivial grid is 1 by 1. there is no grid lines anyway && grid.ListenToNotifications) { grid.InvalidateVisual(); @@ -2375,13 +2365,11 @@ namespace Avalonia.Controls private static void OnCellAttachedPropertyChanged(AvaloniaObject d, AvaloniaPropertyChangedEventArgs e) { - Visual? child = d as Visual; - - if (child != null) + if (d is Visual child) { Grid? grid = child.GetVisualParent(); if (grid != null - && grid.ExtData != null + && grid._extData != null && grid.ListenToNotifications) { grid.CellsStructureDirty = true; @@ -2427,7 +2415,7 @@ namespace Avalonia.Controls /// private IReadOnlyList DefinitionsU { - get { return (ExtData.DefinitionsU!); } + get { return _extData!.DefinitionsU!; } } /// @@ -2435,17 +2423,19 @@ namespace Avalonia.Controls /// private IReadOnlyList DefinitionsV { - get { return (ExtData.DefinitionsV!); } + get { return _extData!.DefinitionsV!; } } /// /// Helper accessor to layout time array of definitions. /// - private DefinitionBase[] TempDefinitions + private DefinitionBase?[] TempDefinitions { get { - ExtendedData extData = ExtData; + Debug.Assert(_extData is not null); + + var extData = _extData!; int requiredLength = Math.Max(DefinitionsU.Count, DefinitionsV.Count) * 2; if (extData.TempDefinitions == null @@ -2516,7 +2506,7 @@ namespace Avalonia.Controls /// private CellCache[] PrivateCells { - get { return (ExtData.CellCachesCollection!); } + get { return _extData!.CellCachesCollection!; } } /// @@ -2582,18 +2572,10 @@ namespace Avalonia.Controls set { SetFlags(value, Flags.HasGroup3CellsInAutoRows); } } - /// - /// Returns reference to extended data bag. - /// - private ExtendedData ExtData - { - get { return (_data!); } - } - /// /// Returns *-weight, adjusted for scale computed during Phase 1 /// - static double StarWeight(DefinitionBase def, double scale) + private static double StarWeight(DefinitionBase def, double scale) { if (scale < 0.0) { @@ -2609,17 +2591,17 @@ namespace Avalonia.Controls } // Extended data instantiated on demand, for non-trivial case handling only - private ExtendedData? _data; + private ExtendedData? _extData; // Grid validity / property caches dirtiness flags private Flags _flags; private GridLinesRenderer? _gridLinesRenderer; // Keeps track of definition indices. - int[]? _definitionIndices; + private int[]? _definitionIndices; // Stores unrounded values and rounding errors during layout rounding. - double[]? _roundingErrors; + private double[]? _roundingErrors; // 5 is an arbitrary constant chosen to end the measure loop private const int c_layoutLoopMaxCount = 5; @@ -2645,14 +2627,14 @@ namespace Avalonia.Controls internal int CellGroup2; // index of the first cell in second cell group internal int CellGroup3; // index of the first cell in third cell group internal int CellGroup4; // index of the first cell in forth cell group - internal DefinitionBase[]? TempDefinitions; // temporary array used during layout for various purposes + internal DefinitionBase?[]? TempDefinitions; // temporary array used during layout for various purposes // TempDefinitions.Length == Max(definitionsU.Length, definitionsV.Length) } /// /// Grid validity / property caches dirtiness flags /// - [System.Flags] + [Flags] private enum Flags { // @@ -2768,7 +2750,7 @@ namespace Avalonia.Controls /// /// LayoutTimeSizeType is used internally and reflects layout-time size type. /// - [System.Flags] + [Flags] internal enum LayoutTimeSizeType : byte { None = 0x00, @@ -3317,7 +3299,7 @@ namespace Avalonia.Controls internal void UpdateRenderBounds(Size arrangeSize) { _lastArrangeSize = arrangeSize; - this.InvalidateVisual(); + InvalidateVisual(); } private static Size _lastArrangeSize; diff --git a/src/Avalonia.Controls/Image.cs b/src/Avalonia.Controls/Image.cs index 7408bff902..2cf0fc3ec4 100644 --- a/src/Avalonia.Controls/Image.cs +++ b/src/Avalonia.Controls/Image.cs @@ -14,8 +14,8 @@ namespace Avalonia.Controls /// /// Defines the property. /// - public static readonly StyledProperty SourceProperty = - AvaloniaProperty.Register(nameof(Source)); + public static readonly StyledProperty SourceProperty = + AvaloniaProperty.Register(nameof(Source)); /// /// Defines the property. @@ -42,7 +42,7 @@ namespace Avalonia.Controls /// Gets or sets the image that will be displayed. /// [Content] - public IImage Source + public IImage? Source { get { return GetValue(SourceProperty); } set { SetValue(SourceProperty, value); } @@ -66,6 +66,7 @@ namespace Avalonia.Controls set { SetValue(StretchDirectionProperty, value); } } + /// protected override bool BypassFlowDirectionPolicies => true; /// diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 59b5bf48a5..42674cb481 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -2,7 +2,6 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; -using System.Diagnostics.CodeAnalysis; using Avalonia.Automation.Peers; using Avalonia.Collections; using Avalonia.Controls.Generators; @@ -17,7 +16,6 @@ using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Metadata; using Avalonia.Styling; -using Avalonia.VisualTree; namespace Avalonia.Controls { @@ -106,7 +104,6 @@ namespace Avalonia.Controls private Tuple? _containerBeingPrepared; private ScrollViewer? _scrollViewer; private ItemsPresenter? _itemsPresenter; - private IScrollSnapPointsInfo? _scrolSnapPointInfo; /// /// Initializes a new instance of the class. @@ -221,6 +218,7 @@ namespace Avalonia.Controls } + /// public event EventHandler HorizontalSnapPointsChanged { add @@ -240,6 +238,7 @@ namespace Avalonia.Controls } } + /// public event EventHandler VerticalSnapPointsChanged { add @@ -424,13 +423,12 @@ namespace Avalonia.Controls /// true if the item is (or is eligible to be) its own container; otherwise, false. protected internal virtual bool IsItemItsOwnContainerOverride(Control item) => true; + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); _scrollViewer = e.NameScope.Find("PART_ScrollViewer"); _itemsPresenter = e.NameScope.Find("PART_ItemsPresenter"); - - _scrolSnapPointInfo = _itemsPresenter as IScrollSnapPointsInfo; } /// @@ -477,11 +475,13 @@ namespace Avalonia.Controls base.OnKeyDown(e); } + /// protected override AutomationPeer OnCreateAutomationPeer() { return new ItemsControlAutomationPeer(this); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -748,11 +748,13 @@ namespace Avalonia.Controls return true; } + /// public IReadOnlyList GetIrregularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment) { return _itemsPresenter?.GetIrregularSnapPoints(orientation, snapPointsAlignment) ?? new List(); } + /// public double GetRegularSnapPoints(Orientation orientation, SnapPointsAlignment snapPointsAlignment, out double offset) { offset = 0; diff --git a/src/Avalonia.Controls/LayoutTransformControl.cs b/src/Avalonia.Controls/LayoutTransformControl.cs index ce254684b7..387dc27562 100644 --- a/src/Avalonia.Controls/LayoutTransformControl.cs +++ b/src/Avalonia.Controls/LayoutTransformControl.cs @@ -28,7 +28,7 @@ namespace Avalonia.Controls .AddClassHandler((x, e) => x.OnLayoutTransformChanged(e)); ChildProperty.Changed - .AddClassHandler((x, e) => x.OnChildChanged(e)); + .AddClassHandler((x, _) => x.OnChildChanged()); UseRenderTransformProperty.Changed .AddClassHandler((x, e) => x.OnUseRenderTransformPropertyChanged(e)); @@ -146,7 +146,7 @@ namespace Avalonia.Controls return transformedDesiredSize; } - IDisposable? _renderTransformChangedEvent; + private IDisposable? _renderTransformChangedEvent; private void OnUseRenderTransformPropertyChanged(AvaloniaPropertyChangedEventArgs e) { @@ -167,8 +167,7 @@ namespace Avalonia.Controls .Subscribe( (x) => { - var target2 = x.Sender as LayoutTransformControl; - if (target2 != null) + if (x.Sender is LayoutTransformControl target2) { target2.LayoutTransform = target2.RenderTransform; } @@ -182,7 +181,7 @@ namespace Avalonia.Controls } } - private void OnChildChanged(AvaloniaPropertyChangedEventArgs e) + private void OnChildChanged() { if (null != TransformRoot) { @@ -206,18 +205,18 @@ namespace Avalonia.Controls /// /// Actual DesiredSize of Child element (the value it returned from its MeasureOverride method). /// - private Size _childActualSize = default; + private Size _childActualSize; /// /// RenderTransform/MatrixTransform applied to TransformRoot. /// - private MatrixTransform _matrixTransform = new MatrixTransform(); + private readonly MatrixTransform _matrixTransform = new(); /// /// Transformation matrix corresponding to _matrixTransform. /// private Matrix _transformation; - private IDisposable? _transformChangedEvent = null; + private IDisposable? _transformChangedEvent; /// /// Returns true if Size a is smaller than Size b in either dimension. @@ -263,10 +262,7 @@ namespace Avalonia.Controls // Get the transform matrix and apply it _transformation = RoundMatrix(LayoutTransform.Value, DecimalsAfterRound); - if (null != _matrixTransform) - { - _matrixTransform.Matrix = _transformation; - } + _matrixTransform.Matrix = _transformation; // New transform means re-layout is necessary InvalidateMeasure(); diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index 080326606e..5a3eb47ce4 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -178,12 +178,11 @@ namespace Avalonia.Controls } } - - } Type IStyleable.StyleKey => typeof(TextBox); + /// protected override void OnGotFocus(GotFocusEventArgs e) { if (HidePromptOnLeave == true && MaskProvider != null) @@ -193,6 +192,7 @@ namespace Avalonia.Controls base.OnGotFocus(e); } + /// protected override async void OnKeyDown(KeyEventArgs e) { if (MaskProvider == null) @@ -271,15 +271,17 @@ namespace Avalonia.Controls } } + /// protected override void OnLostFocus(RoutedEventArgs e) { - if (HidePromptOnLeave == true && MaskProvider != null) + if (HidePromptOnLeave && MaskProvider != null) { Text = MaskProvider.ToString(!HidePromptOnLeave, true); } base.OnLostFocus(e); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { void UpdateMaskProvider() @@ -357,6 +359,8 @@ namespace Avalonia.Controls } base.OnPropertyChanged(change); } + + /// protected override void OnTextInput(TextInputEventArgs e) { _ignoreTextChanges = true; @@ -423,7 +427,7 @@ namespace Avalonia.Controls return startPosition; } - private void RefreshText(MaskedTextProvider provider, int position) + private void RefreshText(MaskedTextProvider? provider, int position) { if (provider != null) { diff --git a/src/Avalonia.Controls/NativeControlHost.cs b/src/Avalonia.Controls/NativeControlHost.cs index 6b9e378d3d..a94a1ee983 100644 --- a/src/Avalonia.Controls/NativeControlHost.cs +++ b/src/Avalonia.Controls/NativeControlHost.cs @@ -16,19 +16,17 @@ namespace Avalonia.Controls private IPlatformHandle? _nativeControlHandle; private bool _queuedForDestruction; private bool _queuedForMoveResize; - private readonly List _propertyChangedSubscriptions = new List(); + private readonly List _propertyChangedSubscriptions = new(); + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = e.Root as TopLevel; var visual = (Visual)this; while (visual != null) { - if (visual is Visual v) - { - v.PropertyChanged += PropertyChangedHandler; - _propertyChangedSubscriptions.Add(v); - } + visual.PropertyChanged += PropertyChangedHandler; + _propertyChangedSubscriptions.Add(visual); visual = visual.GetVisualParent(); } @@ -42,15 +40,13 @@ namespace Avalonia.Controls EnqueueForMoveResize(); } + /// protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) { _currentRoot = null; - if (_propertyChangedSubscriptions != null) - { - foreach (var v in _propertyChangedSubscriptions) - v.PropertyChanged -= PropertyChangedHandler; - _propertyChangedSubscriptions.Clear(); - } + foreach (var v in _propertyChangedSubscriptions) + v.PropertyChanged -= PropertyChangedHandler; + _propertyChangedSubscriptions.Clear(); UpdateHost(); } @@ -128,7 +124,7 @@ namespace Avalonia.Controls return new Rect(position.Value, bounds.Size); } - void EnqueueForMoveResize() + private void EnqueueForMoveResize() { if(_queuedForMoveResize) return; diff --git a/src/Avalonia.Controls/NativeMenu.Export.cs b/src/Avalonia.Controls/NativeMenu.Export.cs index 9c1fb93a48..ab64416a2c 100644 --- a/src/Avalonia.Controls/NativeMenu.Export.cs +++ b/src/Avalonia.Controls/NativeMenu.Export.cs @@ -12,10 +12,10 @@ namespace Avalonia.Controls public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty); - private static readonly AttachedProperty s_nativeMenuInfoProperty = - AvaloniaProperty.RegisterAttached("___NativeMenuInfo"); - - class NativeMenuInfo + private static readonly AttachedProperty s_nativeMenuInfoProperty = + AvaloniaProperty.RegisterAttached("___NativeMenuInfo"); + + private sealed class NativeMenuInfo { public bool ChangingIsExported { get; set; } public ITopLevelNativeMenuExporter? Exporter { get; } @@ -33,7 +33,7 @@ namespace Avalonia.Controls } } - static NativeMenuInfo GetInfo(TopLevel target) + private static NativeMenuInfo GetInfo(TopLevel target) { var rv = target.GetValue(s_nativeMenuInfoProperty); if (rv == null) @@ -45,18 +45,18 @@ namespace Avalonia.Controls return rv; } - static void SetIsNativeMenuExported(TopLevel tl, bool value) + private static void SetIsNativeMenuExported(TopLevel tl, bool value) { GetInfo(tl).ChangingIsExported = true; tl.SetValue(IsNativeMenuExportedProperty, value); } - public static readonly AttachedProperty MenuProperty - = AvaloniaProperty.RegisterAttached("Menu"); + public static readonly AttachedProperty MenuProperty + = AvaloniaProperty.RegisterAttached("Menu"); - public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu); + public static void SetMenu(AvaloniaObject o, NativeMenu? menu) => o.SetValue(MenuProperty, menu); - public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); + public static NativeMenu? GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); static NativeMenu() { diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 3464857131..79719912ea 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -34,8 +34,8 @@ namespace Avalonia.Controls.Primitives public static readonly AttachedProperty AdornerProperty = AvaloniaProperty.RegisterAttached("Adorner"); - private static readonly AttachedProperty s_adornedElementInfoProperty = - AvaloniaProperty.RegisterAttached("AdornedElementInfo"); + private static readonly AttachedProperty s_adornedElementInfoProperty = + AvaloniaProperty.RegisterAttached("AdornedElementInfo"); private static readonly AttachedProperty s_savedAdornerLayerProperty = AvaloniaProperty.RegisterAttached("SavedAdornerLayer"); @@ -159,8 +159,8 @@ namespace Avalonia.Controls.Primitives return; } - AdornerLayer.SetAdornedElement(adorner, visual); - AdornerLayer.SetIsClipEnabled(adorner, false); + SetAdornedElement(adorner, visual); + SetIsClipEnabled(adorner, false); ((ISetLogicalParent) adorner).SetParent(visual); layer.Children.Add(adorner); @@ -177,6 +177,7 @@ namespace Avalonia.Controls.Primitives ((ISetLogicalParent) adorner).SetParent(null); } + /// protected override Size MeasureOverride(Size availableSize) { foreach (var child in Children) @@ -199,6 +200,7 @@ namespace Avalonia.Controls.Primitives return default; } + /// protected override Size ArrangeOverride(Size finalSize) { foreach (var child in Children) @@ -217,7 +219,7 @@ namespace Avalonia.Controls.Primitives } else { - ArrangeChild((Control) child, finalSize); + ArrangeChild(child, finalSize); } } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 3b68cd2ae8..d6cd71aedc 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -120,7 +120,7 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty TopmostProperty = AvaloniaProperty.Register(nameof(Topmost)); - private bool _isOpenRequested = false; + private bool _isOpenRequested; private bool _isOpen; private bool _ignoreIsOpenChanged; private PopupOpenState? _openState; @@ -377,9 +377,9 @@ namespace Avalonia.Controls.Primitives popupHost.SetChild(Child); ((ISetLogicalParent)popupHost).SetParent(this); - if (InheritsTransform && placementTarget is Control c) + if (InheritsTransform) { - TransformTrackingHelper.Track(c, PlacementTargetTransformChanged) + TransformTrackingHelper.Track(placementTarget, PlacementTargetTransformChanged) .DisposeWith(handlerCleanup); } else @@ -518,6 +518,7 @@ namespace Avalonia.Controls.Primitives Close(); } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -579,7 +580,7 @@ namespace Avalonia.Controls.Primitives var scaleX = 1.0; var scaleY = 1.0; - if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is Matrix m) + if (InheritsTransform && placementTarget.TransformToVisual(topLevel) is { } m) { scaleX = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); scaleY = Math.Sqrt(m.M11 * m.M11 + m.M12 * m.M12); @@ -623,6 +624,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override AutomationPeer OnCreateAutomationPeer() { return new PopupAutomationPeer(this); @@ -850,7 +852,7 @@ namespace Avalonia.Controls.Primitives var popupHost = _openState.PopupHost; - return popupHost != null && ((Visual)popupHost).IsVisualAncestorOf(visual); + return ((Visual)popupHost).IsVisualAncestorOf(visual); } public bool IsPointerOverPopup => ((IInputElement?)_openState?.PopupHost)?.IsPointerOver ?? false; diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs index 5210362505..8d79d62dc4 100644 --- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs +++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs @@ -3,16 +3,14 @@ using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Xml.Linq; -using Avalonia.Controls.Generators; using Avalonia.Controls.Selection; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Threading; -using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { @@ -255,6 +253,7 @@ namespace Avalonia.Controls.Primitives /// /// Gets or sets the model that holds the current selection. /// + [AllowNull] protected ISelectionModel Selection { get @@ -399,6 +398,7 @@ namespace Avalonia.Controls.Primitives return null; } + /// protected override void ItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { base.ItemsCollectionChanged(sender!, e); @@ -409,12 +409,14 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); AutoScrollToSelectedItemIfNecessary(); } + /// protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -431,6 +433,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected internal override void PrepareContainerForItemOverride(Control element, object? item, int index) { base.PrepareContainerForItemOverride(element, item, index); @@ -447,12 +450,14 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void ContainerIndexChangedOverride(Control container, int oldIndex, int newIndex) { base.ContainerIndexChangedOverride(container, oldIndex, newIndex); MarkContainerSelected(container, Selection.IsSelected(newIndex)); } + /// protected internal override void ClearContainerForItemOverride(Control element) { base.ClearContainerForItemOverride(element); @@ -463,7 +468,7 @@ namespace Avalonia.Controls.Primitives KeyboardNavigation.SetTabOnceActiveElement(panel, null); } - if (element is ISelectable selectable) + if (element is ISelectable) MarkContainerSelected(element, false); } @@ -498,7 +503,8 @@ namespace Avalonia.Controls.Primitives DataValidationErrors.SetError(this, error); } } - + + /// protected override void OnInitialized() { base.OnInitialized(); @@ -509,6 +515,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnTextInput(TextInputEventArgs e) { if (!e.Handled) @@ -551,6 +558,7 @@ namespace Avalonia.Controls.Primitives base.OnTextInput(e); } + /// protected override void OnKeyDown(KeyEventArgs e) { base.OnKeyDown(e); @@ -582,6 +590,7 @@ namespace Avalonia.Controls.Primitives } } + /// protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); @@ -592,7 +601,7 @@ namespace Avalonia.Controls.Primitives } if (change.Property == ItemsProperty && _updateState is null && _selection is object) { - var newValue = change.GetNewValue(); + var newValue = change.GetNewValue(); _selection.Source = newValue; if (newValue is null) @@ -695,7 +704,7 @@ namespace Avalonia.Controls.Primitives { if (multi) { - if (Selection.IsSelected(index) == true) + if (Selection.IsSelected(index)) { Selection.Deselect(index); } @@ -716,12 +725,10 @@ namespace Avalonia.Controls.Primitives Selection.Select(index); } - if (Presenter?.Panel != null) + if (Presenter?.Panel is { } panel) { var container = ContainerFromIndex(index); - KeyboardNavigation.SetTabOnceActiveElement( - (InputElement)Presenter.Panel, - container); + KeyboardNavigation.SetTabOnceActiveElement(panel, container); } } @@ -940,7 +947,7 @@ namespace Avalonia.Controls.Primitives private void UpdateContainerSelection() { - if (Presenter?.Panel is Panel panel) + if (Presenter?.Panel is { } panel) { foreach (var container in panel.Children) { diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs index 9a684c4534..d8874832bd 100644 --- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs +++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs @@ -290,12 +290,6 @@ namespace Avalonia.Controls.Primitives ApplyTemplatedParent(child, this); ((ISetLogicalParent)child).SetParent(this); VisualChildren.Add(child); - - // Existing code kinda expect to see a NameScope even if it's empty - if (nameScope == null) - { - nameScope = new NameScope(); - } var e = new TemplateAppliedEventArgs(nameScope); OnApplyTemplate(e); @@ -320,6 +314,7 @@ namespace Avalonia.Controls.Primitives return this; } + /// protected sealed override void NotifyChildResourcesChanged(ResourcesChangedEventArgs e) { var count = VisualChildren.Count; diff --git a/src/Avalonia.Controls/Primitives/TextSearch.cs b/src/Avalonia.Controls/Primitives/TextSearch.cs index 949532cb16..962fba361e 100644 --- a/src/Avalonia.Controls/Primitives/TextSearch.cs +++ b/src/Avalonia.Controls/Primitives/TextSearch.cs @@ -11,15 +11,15 @@ namespace Avalonia.Controls.Primitives /// Defines the Text attached property. /// This text will be considered during text search in (such as ) /// - public static readonly AttachedProperty TextProperty - = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch)); + public static readonly AttachedProperty TextProperty + = AvaloniaProperty.RegisterAttached("Text", typeof(TextSearch)); /// /// Sets the for a control. /// /// The control /// The search text to set - public static void SetText(Control control, string text) + public static void SetText(Control control, string? text) { control.SetValue(TextProperty, text); } @@ -29,7 +29,7 @@ namespace Avalonia.Controls.Primitives /// /// The control /// The property value - public static string GetText(Control control) + public static string? GetText(Control control) { return control.GetValue(TextProperty); } diff --git a/src/Avalonia.Controls/Primitives/Track.cs b/src/Avalonia.Controls/Primitives/Track.cs index 14ec7a2849..9e8d1478fa 100644 --- a/src/Avalonia.Controls/Primitives/Track.cs +++ b/src/Avalonia.Controls/Primitives/Track.cs @@ -5,7 +5,6 @@ using System; using Avalonia.Controls.Metadata; -using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Metadata; @@ -31,14 +30,14 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty OrientationProperty = ScrollBar.OrientationProperty.AddOwner(); - public static readonly StyledProperty ThumbProperty = - AvaloniaProperty.Register(nameof(Thumb)); + public static readonly StyledProperty ThumbProperty = + AvaloniaProperty.Register(nameof(Thumb)); - public static readonly StyledProperty