Browse Source

Use Tmds.DBus.Protocol

pull/9810/head
affederaffe 3 years ago
parent
commit
d7904b34cd
  1. 3
      .gitmodules
  2. 7
      Avalonia.Desktop.slnf
  3. 7
      Avalonia.sln
  4. 1
      NuGet.Config
  5. 176
      src/Avalonia.FreeDesktop/AppMenuRegistrar.DBus.cs
  6. 3
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  7. 643
      src/Avalonia.FreeDesktop/DBus.DBus.cs
  8. 32
      src/Avalonia.FreeDesktop/DBusFileChooser.cs
  9. 54
      src/Avalonia.FreeDesktop/DBusHelper.cs
  10. 58
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  11. 627
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/Fcitx.DBus.cs
  12. 69
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs
  13. 32
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs
  14. 47
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  15. 513
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBus.DBus.cs
  16. 52
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs
  17. 2
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs
  18. 44
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  19. 25
      src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs
  20. 463
      src/Avalonia.FreeDesktop/DBusMenu.DBus.cs
  21. 56
      src/Avalonia.FreeDesktop/DBusMenu.cs
  22. 432
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  23. 16
      src/Avalonia.FreeDesktop/DBusRequest.cs
  24. 107
      src/Avalonia.FreeDesktop/DBusSystemDialog.cs
  25. 415
      src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs
  26. 937
      src/Avalonia.FreeDesktop/FreeDesktopPortalDesktop.DBus.cs
  27. 351
      src/Avalonia.FreeDesktop/StatusNotifierWatcher.DBus.cs
  28. 6
      src/Avalonia.X11/X11Window.cs
  29. 1
      src/Linux/Tmds.DBus

3
.gitmodules

@ -7,3 +7,6 @@
[submodule "nukebuild/il-repack"] [submodule "nukebuild/il-repack"]
path = nukebuild/il-repack path = nukebuild/il-repack
url = https://github.com/Gillibald/il-repack url = https://github.com/Gillibald/il-repack
[submodule "Tmds.DBus"]
path = src/Linux/Tmds.DBus
url = https://github.com/affederaffe/Tmds.DBus

7
Avalonia.Desktop.slnf

@ -7,9 +7,9 @@
"samples\\ControlCatalog\\ControlCatalog.csproj", "samples\\ControlCatalog\\ControlCatalog.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj", "samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj", "samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"samples\\SampleControls\\ControlSamples.csproj", "samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj", "samples\\Sandbox\\Sandbox.csproj",
"samples\\ReactiveUIDemo\\ReactiveUIDemo.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj", "src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj", "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj", "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
@ -31,15 +31,16 @@
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj", "src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj", "src\\Avalonia.X11\\Avalonia.X11.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.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.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj", "src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
"src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj", "src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj",
"src\\Skia\\Avalonia.Skia\\Avalonia.Skia.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.Direct2D1\\Avalonia.Direct2D1.csproj",
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj", "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.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.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
"tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj", "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
"tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj", "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",

7
Avalonia.sln

@ -231,6 +231,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlCatalog.Browser.Blaz
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUIDemo", "samples\ReactiveUIDemo\ReactiveUIDemo.csproj", "{75C47156-C5D8-44BC-A5A7-E8657C2248D6}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{75C47156-C5D8-44BC-A5A7-E8657C2248D6}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -606,6 +612,7 @@ Global
{15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098} {15B93A4C-1B46-43F6-B534-7B25B6E99932} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{90B08091-9BBD-4362-B712-E9F2CC62B218} = {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} {75C47156-C5D8-44BC-A5A7-E8657C2248D6} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{29E25263-3CC3-4D55-A042-00BA136867D4} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

1
NuGet.Config

@ -6,5 +6,6 @@
<add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" /> <add key="api.nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="dotnet-eng" value="https://nuget.avaloniaui.net/repository/avalonia-devdeps/index.json" protocolVersion="3" /> <add key="dotnet-eng" value="https://nuget.avaloniaui.net/repository/avalonia-devdeps/index.json" protocolVersion="3" />
<add key="skiasharp" value="https://aka.ms/skiasharp-eap/index.json" /> <add key="skiasharp" value="https://aka.ms/skiasharp-eap/index.json" />
<add key="tmds-dbus" value="https://www.myget.org/F/tmds/api/v3/index.json" />
</packageSources> </packageSources>
</configuration> </configuration>

176
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<IDisposable> WatchPropertiesChangedAsync<TProperties>(string @interface,
MessageValueReader<PropertyChanges<TProperties>> reader, Action<Exception?, PropertyChanges<TProperties>> 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<TProperties> changes, object? rs, object? hs) =>
((Action<Exception?, PropertyChanges<TProperties>>)hs!).Invoke(ex, changes),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync<TArg>(string sender, string @interface, ObjectPath path, string signal,
MessageValueReader<TArg> reader, Action<Exception?, TArg> 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<Exception?, TArg>)hs!).Invoke(ex, arg),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action<Exception?> handler,
bool emitOnCapturedContext)
{
var rule = new MatchRule
{
Type = MessageType.Signal,
Sender = sender,
Path = path,
Member = signal,
Interface = @interface
};
return Connection.AddMatchAsync<object>(rule, (Message message, object? state) => null!,
(Exception? ex, object v, object? rs, object? hs) => ((Action<Exception?>)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);
}
}
}

3
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -12,7 +12,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" /> <ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" /> <ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<PackageReference Include="Tmds.DBus" Version="0.9.0" /> <ProjectReference Include="..\Linux\Tmds.DBus\src\Tmds.DBus.Protocol\Tmds.DBus.Protocol.csproj" />
<!--<PackageReference Include="Tmds.DBus.Protocol" Version="0.11.1-151-9f126c13809fceab8388328aff84b0d8e89b2aae" />-->
</ItemGroup> </ItemGroup>
<ItemGroup Label="InternalsVisibleTo"> <ItemGroup Label="InternalsVisibleTo">
<InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" /> <InternalsVisibleTo Include="Avalonia.X11, PublicKey=$(AvaloniaPublicKey)" />

643
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<string> 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<uint> 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<uint> 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<uint> 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<string, string> 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<bool> 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<string[]> 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<string[]> 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<string> 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<string[]> 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<uint> 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<uint> 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<byte[]> 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<byte[]> 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<string> 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<Dictionary<string, object>> 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<IDisposable> WatchNameOwnerChangedAsync(Action<Exception?, (string A0, string A1, string A2)> handler, bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "NameOwnerChanged", static (m, s) =>
ReadMessage_sss(m, (DBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchNameLostAsync(Action<Exception?, string> handler, bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "NameLost", static (m, s) =>
ReadMessage_s(m, (DBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchNameAcquiredAsync(Action<Exception?, string> 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<string[]> GetFeaturesAsync() =>
Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Features"), static (m, s) =>
ReadMessage_v_as(m, (DBusObject)s!), this);
public Task<string[]> GetInterfacesAsync() =>
Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Interfaces"), static (m, s) =>
ReadMessage_v_as(m, (DBusObject)s!), this);
public Task<DBusProperties> 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<IDisposable> WatchPropertiesChangedAsync(Action<Exception?, PropertyChanges<DBusProperties>> handler, bool emitOnCapturedContext = true)
{
return base.WatchPropertiesChangedAsync(Interface, static (m, s) =>
ReadMessage(m, (DBusObject)s!), handler, emitOnCapturedContext);
static PropertyChanges<DBusProperties> ReadMessage(Message message, DBusObject _)
{
var reader = message.GetBodyReader();
reader.ReadString(); // interface
List<string> changed = new();
return new PropertyChanges<DBusProperties>(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader));
}
static string[] ReadInvalidated(ref Reader reader)
{
List<string>? invalidated = null;
var headersEnd = reader.ReadArrayStart(DBusType.String);
while (reader.HasNext(headersEnd))
{
invalidated ??= new List<string>();
var property = reader.ReadString();
switch (property)
{
case "Features":
invalidated.Add("Features");
break;
case "Interfaces":
invalidated.Add("Interfaces");
break;
}
}
return invalidated?.ToArray() ?? Array.Empty<string>();
}
}
private static DBusProperties ReadProperties(ref Reader reader, List<string>? 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<string>();
changedList?.Add("Features");
break;
case "Interfaces":
reader.ReadSignature("as");
props.Interfaces = reader.ReadArray<string>();
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<IDisposable> WatchPropertiesChangedAsync<TProperties>(string @interface,
MessageValueReader<PropertyChanges<TProperties>> reader, Action<Exception?, PropertyChanges<TProperties>> 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<Exception?, PropertyChanges<TProperties>>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync<TArg>(string sender, string @interface, ObjectPath path, string signal,
MessageValueReader<TArg> reader, Action<Exception?, TArg> 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<Exception?, TArg>)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action<Exception?> handler,
bool emitOnCapturedContext)
{
var rule = new MatchRule
{
Type = MessageType.Signal,
Sender = sender,
Path = path,
Member = signal,
Interface = @interface
};
return Connection.AddMatchAsync<object>(rule, static (_, _) => null!, static (ex, _, _, hs) =>
((Action<Exception?>)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<string>();
}
protected static byte[] ReadMessage_ay(Message message, DBusObject _)
{
var reader = message.GetBodyReader();
return reader.ReadArray<byte>();
}
protected static Dictionary<string, object> ReadMessage_aesv(Message message, DBusObject _)
{
var reader = message.GetBodyReader();
return reader.ReadDictionary<string, object>();
}
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<string>();
}
}
}

32
src/Avalonia.FreeDesktop/DBusFileChooser.cs

@ -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<ObjectPath> OpenFileAsync(string ParentWindow, string Title, IDictionary<string, object> Options);
Task<ObjectPath> SaveFileAsync(string ParentWindow, string Title, IDictionary<string, object> Options);
Task<ObjectPath> SaveFilesAsync(string ParentWindow, string Title, IDictionary<string, object> Options);
Task<T> GetAsync<T>(string prop);
Task<FileChooserProperties> GetAllAsync();
Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
[Dictionary]
internal class FileChooserProperties
{
public uint Version { get; set; }
}
internal static class FileChooserExtensions
{
public static Task<uint> GetVersionAsync(this IFileChooser o) => o.GetAsync<uint>("version");
}
}

54
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -1,51 +1,12 @@
using System; using System;
using System.Threading; using System.Threading;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Threading; using Tmds.DBus.Protocol;
using Tmds.DBus;
namespace Avalonia.FreeDesktop namespace Avalonia.FreeDesktop
{ {
public static class DBusHelper public static class DBusHelper
{ {
/// <summary>
/// This class uses synchronous execution at DBus connection establishment stage
/// then switches to using AvaloniaSynchronizationContext
/// </summary>
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? Connection { get; private set; }
public static Connection? TryInitialize(string? dbusAddress = null) public static Connection? TryInitialize(string? dbusAddress = null)
@ -56,19 +17,14 @@ namespace Avalonia.FreeDesktop
var oldContext = SynchronizationContext.Current; var oldContext = SynchronizationContext.Current;
try try
{ {
var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session!)
var dbusContext = new DBusSyncContext();
SynchronizationContext.SetSynchronizationContext(dbusContext);
var conn = new Connection(new ClientConnectionOptions(dbusAddress ?? Address.Session)
{ {
AutoConnect = false, AutoConnect = false
SynchronizationContext = dbusContext
}); });
// Connect synchronously // Connect synchronously
conn.ConnectAsync().Wait(); conn.ConnectAsync().GetAwaiter().GetResult();
// Initialize a brand new sync-context
dbusContext.Initialized();
Connection = conn; Connection = conn;
} }
catch (Exception e) catch (Exception e)

58
src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs

@ -5,7 +5,7 @@ using System.Threading.Tasks;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Avalonia.Logging; using Avalonia.Logging;
using Tmds.DBus; using Tmds.DBus.Protocol;
namespace Avalonia.FreeDesktop.DBusIme namespace Avalonia.FreeDesktop.DBusIme
{ {
@ -46,7 +46,7 @@ namespace Avalonia.FreeDesktop.DBusIme
public DBusTextInputMethodBase(Connection connection, params string[] knownNames) public DBusTextInputMethodBase(Connection connection, params string[] knownNames)
{ {
_queue = new DBusCallQueue(QueueOnError); _queue = new DBusCallQueue(QueueOnErrorAsync);
Connection = connection; Connection = connection;
_knownNames = knownNames; _knownNames = knownNames;
Watch(); Watch();
@ -54,12 +54,17 @@ namespace Avalonia.FreeDesktop.DBusIme
public ITextInputMethodClient Client => _client; 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) 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<bool> Connect(string name); protected abstract Task<bool> Connect(string name);
@ -67,9 +72,9 @@ namespace Avalonia.FreeDesktop.DBusIme
protected string GetAppName() => protected string GetAppName() =>
Application.Current?.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia"; 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); _onlineNamesQueue.Enqueue(args.ServiceName);
if (!_connecting) if (!_connecting)
@ -89,10 +94,10 @@ namespace Avalonia.FreeDesktop.DBusIme
return; return;
} }
} }
catch (Exception e) catch (Exception ex)
{ {
Logger.TryGet(LogEventLevel.Error, "IME") 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 // IME has crashed
if (args.NewOwner == null && args.ServiceName == _currentName) if (args.NewOwner is null && args.ServiceName == _currentName)
{ {
_currentName = null; _currentName = null;
foreach (var s in _disposables) foreach (var s in _disposables)
@ -120,7 +125,7 @@ namespace Avalonia.FreeDesktop.DBusIme
} }
} }
protected virtual Task Disconnect() protected virtual Task DisconnectAsync()
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -136,13 +141,13 @@ namespace Avalonia.FreeDesktop.DBusIme
_imeActive = null; _imeActive = null;
} }
async Task QueueOnError(Exception e) private async Task QueueOnErrorAsync(Exception e)
{ {
Logger.TryGet(LogEventLevel.Error, "IME") Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Error:\n" + e); ?.Log(this, "Error:\n" + e);
try try
{ {
await Disconnect(); await DisconnectAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -160,20 +165,13 @@ namespace Avalonia.FreeDesktop.DBusIme
if(d is { }) if(d is { })
_disposables.Add(d); _disposables.Add(d);
} }
public void Dispose() public void Dispose()
{ {
foreach(var d in _disposables) foreach(var d in _disposables)
d.Dispose(); d.Dispose();
_disposables.Clear(); _disposables.Clear();
try DisconnectAsync();
{
Disconnect().ContinueWith(_ => { });
}
catch
{
// fire and forget
}
_currentName = null; _currentName = null;
} }
@ -182,13 +180,13 @@ namespace Avalonia.FreeDesktop.DBusIme
protected abstract Task ResetContextCore(); protected abstract Task ResetContextCore();
protected abstract Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode); protected abstract Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode);
void UpdateActive() private void UpdateActive()
{ {
_queue.Enqueue(async () => _queue.Enqueue(async () =>
{ {
if(!IsConnected) if(!IsConnected)
return; return;
var active = _windowActive && _controlActive; var active = _windowActive && _controlActive;
if (active != _imeActive) if (active != _imeActive)
{ {
@ -204,7 +202,7 @@ namespace Avalonia.FreeDesktop.DBusIme
_windowActive = active; _windowActive = active;
UpdateActive(); UpdateActive();
} }
void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client) void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
{ {
_client = client; _client = client;
@ -227,7 +225,7 @@ namespace Avalonia.FreeDesktop.DBusIme
// Error, disconnect // Error, disconnect
catch (Exception e) catch (Exception e)
{ {
await QueueOnError(e); await QueueOnErrorAsync(e);
return false; return false;
} }
} }
@ -240,7 +238,7 @@ namespace Avalonia.FreeDesktop.DBusIme
} }
protected void FireCommit(string s) => _onCommit?.Invoke(s); protected void FireCommit(string s) => _onCommit?.Invoke(s);
private Action<X11InputMethodForwardedKey>? _onForward; private Action<X11InputMethodForwardedKey>? _onForward;
event Action<X11InputMethodForwardedKey> IX11InputMethodControl.ForwardKey event Action<X11InputMethodForwardedKey> IX11InputMethodControl.ForwardKey
{ {
@ -249,8 +247,8 @@ namespace Avalonia.FreeDesktop.DBusIme
} }
protected void FireForward(X11InputMethodForwardedKey k) => _onForward?.Invoke(k); protected void FireForward(X11InputMethodForwardedKey k) => _onForward?.Invoke(k);
void UpdateCursorRect() private void UpdateCursorRect()
{ {
_queue.Enqueue(async () => _queue.Enqueue(async () =>
{ {
@ -265,7 +263,7 @@ namespace Avalonia.FreeDesktop.DBusIme
} }
}); });
} }
void IX11InputMethodControl.UpdateWindowInfo(PixelPoint position, double scaling) void IX11InputMethodControl.UpdateWindowInfo(PixelPoint position, double scaling)
{ {
_windowPosition = position; _windowPosition = position;

627
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<int> 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<IDisposable> WatchCommitStringAsync(Action<Exception?, string> handler, bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "CommitString", static (m, s) =>
ReadMessage_s(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchCurrentIMAsync(Action<Exception?, (string Name, string UniqueName, string LangCode)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "CurrentIM", static (m, s) =>
ReadMessage_sss(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchUpdateFormattedPreeditAsync(Action<Exception?, ((string, int)[] Str, int Cursorpos)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "UpdateFormattedPreedit", static (m, s) =>
ReadMessage_arsizi(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchForwardKeyAsync(Action<Exception?, (uint Keyval, uint State, int Type)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKey", static (m, s) =>
ReadMessage_uui(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchDeleteSurroundingTextAsync(Action<Exception?, (int Offset, uint Nchar)> 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<bool> 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<IDisposable> WatchCommitStringAsync(Action<Exception?, string> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "CommitString", static (m, s) =>
ReadMessage_s(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchCurrentIMAsync(Action<Exception?, (string Name, string UniqueName, string LangCode)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "CurrentIM", static (m, s) =>
ReadMessage_sss(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchUpdateFormattedPreeditAsync(Action<Exception?, ((string, int)[] Str, int Cursorpos)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "UpdateFormattedPreedit", static (m, s) =>
ReadMessage_arsizi(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchForwardKeyAsync(Action<Exception?, (uint Keyval, uint State, bool Type)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKey", static (m, s) =>
ReadMessage_uub(m, (FcitxObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchDeleteSurroundingTextAsync(Action<Exception?, (int Offset, uint Nchar)> 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<IDisposable> WatchPropertiesChangedAsync<TProperties>(string @interface,
MessageValueReader<PropertyChanges<TProperties>> reader, Action<Exception?, PropertyChanges<TProperties>> 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<Exception?, PropertyChanges<TProperties>>)hs!).Invoke(ex, changes),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync<TArg>(string sender, string @interface, ObjectPath path, string signal,
MessageValueReader<TArg> reader, Action<Exception?, TArg> 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<Exception?, TArg>)hs!).Invoke(ex, arg), this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action<Exception?> handler,
bool emitOnCapturedContext)
{
var rule = new MatchRule
{
Type = MessageType.Signal,
Sender = sender,
Path = path,
Member = signal,
Interface = @interface
};
return Connection.AddMatchAsync<object>(rule, static (_, _) =>
null!, static (ex, _, _, hs) =>
((Action<Exception?>)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<byte>();
return (arg0, arg1);
}
}
}

69
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs

@ -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<int> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time);
Task<IDisposable> WatchEnableIMAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCloseIMAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable?> WatchCommitStringAsync(Action<string> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable?> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception>? 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<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time);
Task<IDisposable?> WatchCommitStringAsync(Action<string> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception>? onError = null);
Task<IDisposable?> WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception>? onError = null);
}
[DBusInterface("org.fcitx.Fcitx.InputMethod1")]
interface IFcitxInputMethod1 : IDBusObject
{
Task<(ObjectPath path, byte[] data)> CreateInputContextAsync((string, string)[] arg0);
}
}

32
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs

@ -5,15 +5,15 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{ {
internal class FcitxICWrapper internal class FcitxICWrapper
{ {
private readonly IFcitxInputContext1? _modern; private readonly InputContext1? _modern;
private readonly IFcitxInputContext? _old; private readonly InputContext? _old;
public FcitxICWrapper(IFcitxInputContext old) public FcitxICWrapper(InputContext old)
{ {
_old = old; _old = old;
} }
public FcitxICWrapper(IFcitxInputContext1 modern) public FcitxICWrapper(InputContext1 modern)
{ {
_modern = modern; _modern = modern;
} }
@ -21,32 +21,30 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
public Task FocusInAsync() => _old?.FocusInAsync() ?? _modern?.FocusInAsync() ?? Task.CompletedTask; public Task FocusInAsync() => _old?.FocusInAsync() ?? _modern?.FocusInAsync() ?? Task.CompletedTask;
public Task FocusOutAsync() => _old?.FocusOutAsync() ?? _modern?.FocusOutAsync() ?? Task.CompletedTask; public Task FocusOutAsync() => _old?.FocusOutAsync() ?? _modern?.FocusOutAsync() ?? Task.CompletedTask;
public Task ResetAsync() => _old?.ResetAsync() ?? _modern?.ResetAsync() ?? Task.CompletedTask; public Task ResetAsync() => _old?.ResetAsync() ?? _modern?.ResetAsync() ?? Task.CompletedTask;
public Task SetCursorRectAsync(int x, int y, int w, int h) => 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; _old?.SetCursorRectAsync(x, y, w, h) ?? _modern?.SetCursorRectAsync(x, y, w, h) ?? Task.CompletedTask;
public Task DestroyICAsync() => _old?.DestroyICAsync() ?? _modern?.DestroyICAsync() ?? Task.CompletedTask; public Task DestroyICAsync() => _old?.DestroyICAsync() ?? _modern?.DestroyICAsync() ?? Task.CompletedTask;
public async Task<bool> ProcessKeyEventAsync(uint keyVal, uint keyCode, uint state, int type, uint time) public async Task<bool> 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 _old.ProcessKeyEventAsync(keyVal, keyCode, state, type, time) != 0;
return await (_modern?.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time) ?? Task.FromResult(false)); return await (_modern?.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time) ?? Task.FromResult(false));
} }
public Task<IDisposable?> WatchCommitStringAsync(Action<string> handler) => public ValueTask<IDisposable?> WatchCommitStringAsync(Action<Exception?, string> handler) =>
_old?.WatchCommitStringAsync(handler) _old?.WatchCommitStringAsync(handler)
?? _modern?.WatchCommitStringAsync(handler) ?? _modern?.WatchCommitStringAsync(handler)
?? Task.FromResult(default(IDisposable?)); ?? new ValueTask<IDisposable?>(default(IDisposable?));
public Task<IDisposable?> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler) public ValueTask<IDisposable?> WatchForwardKeyAsync(Action<Exception?, (uint keyval, uint state, int type)> handler) =>
{ _old?.WatchForwardKeyAsync(handler)
return _old?.WatchForwardKeyAsync(handler) ?? _modern?.WatchForwardKeyAsync((e, ev) => handler.Invoke(e, (ev.Keyval, ev.State, ev.Type ? 1 : 0)))
?? _modern?.WatchForwardKeyAsync(ev => ?? new ValueTask<IDisposable?>(default(IDisposable?));
handler((ev.keyval, ev.state, ev.type ? 1 : 0)))
?? Task.FromResult(default(IDisposable?));
}
public Task SetCapacityAsync(uint flags) => public Task SetCapacityAsync(uint flags) =>
_old?.SetCapacityAsync(flags) ?? _modern?.SetCapabilityAsync(flags) ?? Task.CompletedTask; _old?.SetCapacityAsync(flags) ?? _modern?.SetCapabilityAsync(flags) ?? Task.CompletedTask;

47
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs

@ -1,12 +1,10 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Tmds.DBus; using Tmds.DBus.Protocol;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{ {
@ -15,32 +13,25 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
private FcitxICWrapper? _context; private FcitxICWrapper? _context;
private FcitxCapabilityFlags? _lastReportedFlags; private FcitxCapabilityFlags? _lastReportedFlags;
public FcitxX11TextInputMethod(Connection connection) : base(connection, public FcitxX11TextInputMethod(Connection connection) : base(connection, "org.fcitx.Fcitx", "org.freedesktop.portal.Fcitx") { }
"org.fcitx.Fcitx",
"org.freedesktop.portal.Fcitx"
)
{
}
protected override async Task<bool> Connect(string name) protected override async Task<bool> Connect(string name)
{ {
var service = new FcitxService(Connection, name);
if (name == "org.fcitx.Fcitx") if (name == "org.fcitx.Fcitx")
{ {
var method = Connection.CreateProxy<IFcitxInputMethod>(name, "/inputmethod"); var method = service.CreateInputMethod("/inputmethod");
var resp = await method.CreateICv3Async(GetAppName(), var resp = await method.CreateICv3Async(GetAppName(),
Process.GetCurrentProcess().Id); Process.GetCurrentProcess().Id);
var proxy = Connection.CreateProxy<IFcitxInputContext>(name, var proxy = service.CreateInputContext($"/inputcontext_{resp.Icid}");
"/inputcontext_" + resp.icid);
_context = new FcitxICWrapper(proxy); _context = new FcitxICWrapper(proxy);
} }
else else
{ {
var method = Connection.CreateProxy<IFcitxInputMethod1>(name, "/inputmethod"); var method = service.CreateInputMethod1("/inputmethod");
var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) }); var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) });
var proxy = Connection.CreateProxy<IFcitxInputContext1>(name, resp.path); var proxy = service.CreateInputContext1(resp.A0);
_context = new FcitxICWrapper(proxy); _context = new FcitxICWrapper(proxy);
} }
@ -49,7 +40,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
return true; return true;
} }
protected override Task Disconnect() => _context?.DestroyICAsync() ?? Task.CompletedTask; protected override Task DisconnectAsync() => _context?.DestroyICAsync() ?? Task.CompletedTask;
protected override void OnDisconnected() => _context = null; protected override void OnDisconnected() => _context = null;
@ -64,14 +55,12 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
Math.Max(1, cursorRect.Height)) Math.Max(1, cursorRect.Height))
?? Task.CompletedTask; ?? Task.CompletedTask;
protected override Task SetActiveCore(bool active)=> (active protected override Task SetActiveCore(bool active)=> (active
? _context?.FocusInAsync() ? _context?.FocusInAsync()
: _context?.FocusOutAsync()) : _context?.FocusOutAsync())
?? Task.CompletedTask; ?? Task.CompletedTask;
protected override Task ResetContextCore() => _context?.ResetAsync() protected override Task ResetContextCore() => _context?.ResetAsync() ?? Task.CompletedTask;
?? Task.CompletedTask;
protected override async Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode) protected override async Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{ {
@ -88,17 +77,13 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
var type = args.Type == RawKeyEventType.KeyDown ? var type = args.Type == RawKeyEventType.KeyDown ?
FcitxKeyEventType.FCITX_PRESS_KEY : FcitxKeyEventType.FCITX_PRESS_KEY :
FcitxKeyEventType.FCITX_RELEASE_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, return await _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state, (int)type,
(uint)args.Timestamp).ConfigureAwait(false); (uint)args.Timestamp).ConfigureAwait(false);
}
else return false;
{
return false;
}
} }
public override void SetOptions(TextInputOptions options) => public override void SetOptions(TextInputOptions options) =>
Enqueue(async () => 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; var state = (FcitxKeyState)ev.state;
KeyModifiers mods = default; 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);
} }
} }

513
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<ObjectPath> 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<bool> 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<object> 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<IDisposable> WatchCommitTextAsync(Action<Exception?, object> handler, bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "CommitText", static (m, s) =>
ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchForwardKeyEventAsync(Action<Exception?, (uint Keyval, uint Keycode, uint State)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "ForwardKeyEvent", static (m, s) =>
ReadMessage_uuu(m, (IBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchUpdatePreeditTextAsync(Action<Exception?, (object Text, uint CursorPos, bool Visible)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "UpdatePreeditText", static (m, s) =>
ReadMessage_vub(m, (IBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchUpdatePreeditTextWithModeAsync(
Action<Exception?, (object Text, uint CursorPos, bool Visible, uint Mode)> handler, bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "UpdatePreeditTextWithMode", static (m, s) =>
ReadMessage_vubu(m, (IBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchShowPreeditTextAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "ShowPreeditText", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchHidePreeditTextAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "HidePreeditText", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<Exception?, (object Text, bool Visible)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "UpdateAuxiliaryText", static (m, s) =>
ReadMessage_vb(m, (IBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchShowAuxiliaryTextAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "ShowAuxiliaryText", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchHideAuxiliaryTextAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "HideAuxiliaryText", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchUpdateLookupTableAsync(Action<Exception?, (object Table, bool Visible)> handler,
bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "UpdateLookupTable", static (m, s) =>
ReadMessage_vb(m, (IBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchShowLookupTableAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "ShowLookupTable", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchHideLookupTableAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "HideLookupTable", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchPageUpLookupTableAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "PageUpLookupTable", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchPageDownLookupTableAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "PageDownLookupTable", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchCursorUpLookupTableAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "CursorUpLookupTable", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchCursorDownLookupTableAsync(Action<Exception?> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "CursorDownLookupTable", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchRegisterPropertiesAsync(Action<Exception?, object> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "RegisterProperties", static (m, s) => ReadMessage_v(m, (IBusObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchUpdatePropertyAsync(Action<Exception?, object> 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<IDisposable> WatchPropertiesChangedAsync<TProperties>(string @interface,
MessageValueReader<PropertyChanges<TProperties>> reader, Action<Exception?, PropertyChanges<TProperties>> 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<Exception?, PropertyChanges<TProperties>>)hs!).Invoke(ex, changes),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync<TArg>(string sender, string @interface, ObjectPath path, string signal,
MessageValueReader<TArg> reader, Action<Exception?, TArg> 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<Exception?, TArg>)hs!).Invoke(ex, arg),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action<Exception?> handler,
bool emitOnCapturedContext)
{
var rule = new MatchRule
{
Type = MessageType.Signal,
Sender = sender,
Path = path,
Member = signal,
Interface = @interface
};
return Connection.AddMatchAsync<object>(rule, static (_, _) => null!, static (ex, _, _, hs) => ((Action<Exception?>)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);
}
}
}

52
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs

@ -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<bool> 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<object> GetEngineAsync();
Task DestroyAsync();
Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos);
Task<IDisposable> WatchCommitTextAsync(Action<object> cb, Action<Exception>? onError = null);
Task<IDisposable> WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchRequireSurroundingTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchShowPreeditTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchHidePreeditTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchShowAuxiliaryTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchHideAuxiliaryTextAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchShowLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchHideLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchPageUpLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchPageDownLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCursorUpLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchCursorDownLookupTableAsync(Action handler, Action<Exception>? onError = null);
Task<IDisposable> WatchRegisterPropertiesAsync(Action<object> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchUpdatePropertyAsync(Action<object> handler, Action<Exception>? onError = null);
}
[DBusInterface("org.freedesktop.IBus.Portal")]
interface IIBusPortal : IDBusObject
{
Task<ObjectPath> CreateInputContextAsync(string Name);
}
}

2
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs

@ -18,7 +18,7 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
Button3Mask = 1 << 10, Button3Mask = 1 << 10,
Button4Mask = 1 << 11, Button4Mask = 1 << 11,
Button5Mask = 1 << 12, Button5Mask = 1 << 12,
HandledMask = 1 << 24, HandledMask = 1 << 24,
ForwardMask = 1 << 25, ForwardMask = 1 << 25,
IgnoredMask = ForwardMask, IgnoredMask = ForwardMask,

44
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs

@ -1,35 +1,32 @@
using System.Collections.Generic; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Raw; using Avalonia.Input.Raw;
using Avalonia.Input.TextInput; using Avalonia.Input.TextInput;
using Tmds.DBus; using Tmds.DBus.Protocol;
namespace Avalonia.FreeDesktop.DBusIme.IBus namespace Avalonia.FreeDesktop.DBusIme.IBus
{ {
internal class IBusX11TextInputMethod : DBusTextInputMethodBase internal class IBusX11TextInputMethod : DBusTextInputMethodBase
{ {
private IIBusInputContext? _context; private Service? _service;
private InputContext? _context;
public IBusX11TextInputMethod(Connection connection) : base(connection, public IBusX11TextInputMethod(Connection connection) : base(connection, "org.freedesktop.portal.IBus") { }
"org.freedesktop.portal.IBus")
{
}
protected override async Task<bool> Connect(string name) protected override async Task<bool> Connect(string name)
{ {
var path = var service = new IBusService(Connection, name);
await Connection.CreateProxy<IIBusPortal>(name, "/org/freedesktop/IBus") var path = await service.CreatePortal("/org/freedesktop/IBus").CreateInputContextAsync(GetAppName());
.CreateInputContextAsync(GetAppName()); _context = service.CreateInputContext(path);
_service = service.CreateService(path);
_context = Connection.CreateProxy<IIBusInputContext>(name, path);
AddDisposable(await _context.WatchCommitTextAsync(OnCommitText)); AddDisposable(await _context.WatchCommitTextAsync(OnCommitText));
AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey)); AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey));
Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus)); Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus));
return true; 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; var state = (IBusModifierMask)k.state;
KeyModifiers mods = default; KeyModifiers mods = default;
@ -49,8 +46,7 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
}); });
} }
private void OnCommitText(Exception? e, object wtf)
private void OnCommitText(object wtf)
{ {
// Hello darkness, my old friend // Hello darkness, my old friend
if (wtf.GetType().GetField("Item3") is { } prop) if (wtf.GetType().GetField("Item3") is { } prop)
@ -61,16 +57,16 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
} }
} }
protected override Task Disconnect() => _context?.DestroyAsync() protected override Task DisconnectAsync() => _service?.DestroyAsync() ?? Task.CompletedTask;
?? Task.CompletedTask;
protected override void OnDisconnected() protected override void OnDisconnected()
{ {
_service = null;
_context = null; _context = null;
base.OnDisconnected(); base.OnDisconnected();
} }
protected override Task SetCursorRectCore(PixelRect rect) protected override Task SetCursorRectCore(PixelRect rect)
=> _context?.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height) => _context?.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height)
?? Task.CompletedTask; ?? Task.CompletedTask;
@ -96,20 +92,12 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
if (args.Type == RawKeyEventType.KeyUp) if (args.Type == RawKeyEventType.KeyUp)
state |= IBusModifierMask.ReleaseMask; state |= IBusModifierMask.ReleaseMask;
if(_context is { }) return _context is not null ? _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state) : Task.FromResult(false);
{
return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state);
}
else
{
return Task.FromResult(false);
}
} }
public override void SetOptions(TextInputOptions options) public override void SetOptions(TextInputOptions options)
{ {
// No-op, because ibus // No-op, because ibus
} }
} }
} }

25
src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs

@ -2,44 +2,43 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.FreeDesktop.DBusIme.Fcitx; using Avalonia.FreeDesktop.DBusIme.Fcitx;
using Avalonia.FreeDesktop.DBusIme.IBus; using Avalonia.FreeDesktop.DBusIme.IBus;
using Tmds.DBus; using Tmds.DBus.Protocol;
namespace Avalonia.FreeDesktop.DBusIme namespace Avalonia.FreeDesktop.DBusIme
{ {
public class X11DBusImeHelper public class X11DBusImeHelper
{ {
private static readonly Dictionary<string, Func<Connection, IX11InputMethodFactory>> KnownMethods = private static readonly Dictionary<string, Func<Connection, IX11InputMethodFactory>> KnownMethods = new()
new Dictionary<string, Func<Connection, IX11InputMethodFactory>>
{ {
["fcitx"] = conn => ["fcitx"] = static conn =>
new DBusInputMethodFactory<FcitxX11TextInputMethod>(_ => new FcitxX11TextInputMethod(conn)), new DBusInputMethodFactory<FcitxX11TextInputMethod>(_ => new FcitxX11TextInputMethod(conn)),
["ibus"] = conn => ["ibus"] = static conn =>
new DBusInputMethodFactory<IBusX11TextInputMethod>(_ => new IBusX11TextInputMethod(conn)) new DBusInputMethodFactory<IBusX11TextInputMethod>(_ => new IBusX11TextInputMethod(conn))
}; };
static Func<Connection, IX11InputMethodFactory>? DetectInputMethod() private static Func<Connection, IX11InputMethodFactory>? DetectInputMethod()
{ {
foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" }) foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" })
{ {
var value = Environment.GetEnvironmentVariable(name); var value = Environment.GetEnvironmentVariable(name);
if (value == "none") if (value == "none")
return null; 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 factory;
} }
return null; return null;
} }
public static bool DetectAndRegister() public static bool DetectAndRegister()
{ {
var factory = DetectInputMethod(); var factory = DetectInputMethod();
if (factory != null) if (factory is not null)
{ {
var conn = DBusHelper.TryInitialize(); var conn = DBusHelper.TryInitialize();
if (conn != null) if (conn is not null)
{ {
AvaloniaLocator.CurrentMutable.Bind<IX11InputMethodFactory>().ToConstant(factory(conn)); AvaloniaLocator.CurrentMutable.Bind<IX11InputMethodFactory>().ToConstant(factory(conn));
return true; return true;

463
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<string, object>, 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<string, object>)[]> 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<object> 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<int[]> 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<bool> 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<IDisposable> WatchItemsPropertiesUpdatedAsync(Action<Exception?, ((int, Dictionary<string, object>)[] 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<IDisposable> WatchLayoutUpdatedAsync(Action<Exception?, (uint Revision, int Parent)> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "LayoutUpdated", (m, s) => ReadMessage_ui(m, (DBusMenuObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchItemActivationRequestedAsync(Action<Exception?, (int Id, uint Timestamp)> 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<uint> GetVersionAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Version"), (m, s) => ReadMessage_v_u(m, (DBusMenuObject)s!), this);
public Task<string> GetTextDirectionAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "TextDirection"), (m, s) => ReadMessage_v_s(m, (DBusMenuObject)s!), this);
public Task<string> GetStatusAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "Status"), (m, s) => ReadMessage_v_s(m, (DBusMenuObject)s!), this);
public Task<string[]> GetIconThemePathAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IconThemePath"), (m, s) => ReadMessage_v_as(m, (DBusMenuObject)s!), this);
public Task<DBusMenuProperties> 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<IDisposable> WatchPropertiesChangedAsync(Action<Exception?, PropertyChanges<DBusMenuProperties>> handler, bool emitOnCapturedContext = true)
{
return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DBusMenuObject)s!), handler, emitOnCapturedContext);
static PropertyChanges<DBusMenuProperties> ReadMessage(Message message, DBusMenuObject _)
{
var reader = message.GetBodyReader();
reader.ReadString(); // interface
List<string> changed = new(), invalidated = new();
return new PropertyChanges<DBusMenuProperties>(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader));
}
static string[] ReadInvalidated(ref Reader reader)
{
List<string>? 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<string>();
}
}
private static DBusMenuProperties ReadProperties(ref Reader reader, List<string>? 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<string>();
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<IDisposable> WatchPropertiesChangedAsync<TProperties>(string @interface, MessageValueReader<PropertyChanges<TProperties>> reader, Action<Exception?, PropertyChanges<TProperties>> 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<Exception?, PropertyChanges<TProperties>>)hs!).Invoke(ex, changes),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync<TArg>(string sender, string @interface, ObjectPath path, string signal, MessageValueReader<TArg> reader, Action<Exception?, TArg> 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<Exception?, TArg>)hs!).Invoke(ex, arg),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action<Exception?> handler, bool emitOnCapturedContext)
{
var rule = new MatchRule
{
Type = MessageType.Signal,
Sender = sender,
Path = path,
Member = signal,
Interface = @interface
};
return Connection.AddMatchAsync<object>(rule, (message, state) => null!,
(ex, v, rs, hs) => ((Action<Exception?>)hs!).Invoke(ex), this, handler, emitOnCapturedContext);
}
protected static (uint, (int, Dictionary<string, object>, object[])) ReadMessage_uriaesvavz(Message message, DBusMenuObject _)
{
var reader = message.GetBodyReader();
var arg0 = reader.ReadUInt32();
var arg1 = reader.ReadStruct<int, Dictionary<string, object>, object[]>();
return (arg0, arg1);
}
protected static (int, Dictionary<string, object>)[] ReadMessage_ariaesvz(Message message, DBusMenuObject _)
{
var reader = message.GetBodyReader();
return reader.ReadArray<(int, Dictionary<string, object>)>();
}
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<int>();
}
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<int>();
var arg1 = reader.ReadArray<int>();
return (arg0, arg1);
}
protected static ((int, Dictionary<string, object>)[], (int, string[])[]) ReadMessage_ariaesvzariasz(Message message, DBusMenuObject _)
{
var reader = message.GetBodyReader();
var arg0 = reader.ReadArray<(int, Dictionary<string, object>)>();
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<string>();
}
}
}

56
src/Avalonia.FreeDesktop/DBusMenu.cs

@ -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<object> GetAsync(string prop);
Task<DBusMenuProperties> GetAllAsync();
Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
[DBusInterface("com.canonical.dbusmenu")]
interface IDBusMenu : IFreeDesktopDBusProperties
{
Task<(uint revision, (int, KeyValuePair<string, object>[], object[]) layout)> GetLayoutAsync(int ParentId, int RecursionDepth, string[] PropertyNames);
Task<(int, KeyValuePair<string, object>[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames);
Task<object> GetPropertyAsync(int Id, string Name);
Task EventAsync(int Id, string EventId, object Data, uint Timestamp);
Task<int[]> EventGroupAsync((int id, string eventId, object data, uint timestamp)[] events);
Task<bool> AboutToShowAsync(int Id);
Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids);
Task<IDisposable> WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception>? 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<IDisposable> WatchWindowRegisteredAsync(Action<(uint windowId, string service, ObjectPath menuObjectPath)> handler, Action<Exception>? onError = null);
Task<IDisposable> WatchWindowUnregisteredAsync(Action<uint> handler, Action<Exception>? onError = null);
}
}

432
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -2,98 +2,81 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.IO; using System.IO;
using System.Reactive.Disposables; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop.DBusMenu;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Threading; using Avalonia.Threading;
using Tmds.DBus; using Tmds.DBus.Protocol;
#pragma warning disable 1998 #pragma warning disable 1998
namespace Avalonia.FreeDesktop namespace Avalonia.FreeDesktop
{ {
public class DBusMenuExporter public class DBusMenuExporter
{ {
public static ITopLevelNativeMenuExporter? TryCreateTopLevelNativeMenu(IntPtr xid) public static ITopLevelNativeMenuExporter? TryCreateTopLevelNativeMenu(IntPtr xid) =>
{ DBusHelper.Connection is null ? null : new DBusMenuExporterImpl(DBusHelper.Connection, xid);
if (DBusHelper.Connection == null)
return null;
return new DBusMenuExporterImpl(DBusHelper.Connection, xid); public static INativeMenuExporter TryCreateDetachedNativeMenu(string path, Connection currentConnection) =>
} new DBusMenuExporterImpl(currentConnection, path);
public static INativeMenuExporter TryCreateDetachedNativeMenu(ObjectPath path, Connection currentConection)
{
return new DBusMenuExporterImpl(currentConection, path);
}
public static ObjectPath GenerateDBusMenuObjPath => "/net/avaloniaui/dbusmenu/" public static string GenerateDBusMenuObjPath => $"/net/avaloniaui/dbusmenu/{Guid.NewGuid():N}";
+ Guid.NewGuid().ToString("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<int, NativeMenuItemBase> _idsToItems = new();
private readonly Dictionary<NativeMenuItemBase, int> _itemsToIds = new();
private readonly HashSet<NativeMenu> _menus = new();
private readonly uint _xid; private readonly uint _xid;
private IRegistrar? _registrar; private readonly bool _appMenu = true;
private Registrar? _registrar;
private NativeMenu? _menu;
private bool _disposed; private bool _disposed;
private uint _revision = 1; private uint _revision = 1;
private NativeMenu? _menu;
private readonly Dictionary<int, NativeMenuItemBase> _idsToItems = new Dictionary<int, NativeMenuItemBase>();
private readonly Dictionary<NativeMenuItemBase, int> _itemsToIds = new Dictionary<NativeMenuItemBase, int>();
private readonly HashSet<NativeMenu> _menus = new HashSet<NativeMenu>();
private bool _resetQueued; private bool _resetQueued;
private int _nextId = 1; private int _nextId = 1;
private bool _appMenu = true;
public DBusMenuExporterImpl(Connection connection, IntPtr xid)
public DBusMenuExporterImpl(Connection dbus, IntPtr xid)
{ {
_dbus = dbus; _connection = connection;
_xid = (uint)xid.ToInt32(); _xid = (uint)xid.ToInt32();
ObjectPath = GenerateDBusMenuObjPath; Path = GenerateDBusMenuObjPath;
SetNativeMenu(new NativeMenu()); SetNativeMenu(new NativeMenu());
Init(); Init();
} }
public DBusMenuExporterImpl(Connection dbus, ObjectPath path) public DBusMenuExporterImpl(Connection connection, string path)
{ {
_dbus = dbus; _connection = connection;
_appMenu = false; _appMenu = false;
ObjectPath = path; Path = path;
SetNativeMenu(new NativeMenu()); SetNativeMenu(new NativeMenu());
Init(); Init();
} }
async void Init()
{
try
{
if (_appMenu)
{
await _dbus.RegisterObjectAsync(this);
_registrar = DBusHelper.Connection?.CreateProxy<IRegistrar>(
"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, public string Path { get; }
// and it's not important to know if it succeeds
// since even if we register the window it's not guaranteed that private async void Init()
// menu will be actually exported {
} _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() public void Dispose()
@ -101,7 +84,7 @@ namespace Avalonia.FreeDesktop
if (_disposed) if (_disposed)
return; return;
_disposed = true; _disposed = true;
_dbus.UnregisterObject(this);
// Fire and forget // Fire and forget
_registrar?.UnregisterWindowAsync(_xid); _registrar?.UnregisterWindowAsync(_xid);
} }
@ -113,17 +96,17 @@ namespace Avalonia.FreeDesktop
public void SetNativeMenu(NativeMenu? menu) public void SetNativeMenu(NativeMenu? menu)
{ {
if (menu == null) if (menu is null)
menu = new NativeMenu(); menu = new NativeMenu();
if (_menu != null) if (_menu is not null)
((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged; ((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged;
_menu = menu; _menu = menu;
((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged; ((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged;
DoLayoutReset(); DoLayoutReset();
} }
/* /*
This is basic initial implementation, so we don't actually track anything and This is basic initial implementation, so we don't actually track anything and
just reset the whole layout on *ANY* change 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, 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... but that's the fastest way to get things working, so...
*/ */
void DoLayoutReset() private void DoLayoutReset()
{ {
_resetQueued = false; _resetQueued = false;
foreach (var i in _idsToItems.Values) foreach (var i in _idsToItems.Values)
i.PropertyChanged -= OnItemPropertyChanged; i.PropertyChanged -= OnItemPropertyChanged;
foreach(var menu in _menus) foreach(var menu in _menus)
((INotifyCollectionChanged)menu.Items).CollectionChanged -= OnMenuItemsChanged; ((INotifyCollectionChanged)menu.Items).CollectionChanged -= OnMenuItemsChanged;
@ -142,10 +125,10 @@ namespace Avalonia.FreeDesktop
_idsToItems.Clear(); _idsToItems.Clear();
_itemsToIds.Clear(); _itemsToIds.Clear();
_revision++; _revision++;
LayoutUpdated?.Invoke((_revision, 0)); EmitUIntIntSignal("LayoutUpdated", _revision, 0);
} }
void QueueReset() private void QueueReset()
{ {
if(_resetQueued) if(_resetQueued)
return; return;
@ -163,10 +146,10 @@ namespace Avalonia.FreeDesktop
private void EnsureSubscribed(NativeMenu? menu) private void EnsureSubscribed(NativeMenu? menu)
{ {
if(menu!=null && _menus.Add(menu)) if (menu is not null && _menus.Add(menu))
((INotifyCollectionChanged)menu.Items).CollectionChanged += OnMenuItemsChanged; ((INotifyCollectionChanged)menu.Items).CollectionChanged += OnMenuItemsChanged;
} }
private int GetId(NativeMenuItemBase item) private int GetId(NativeMenuItemBase item)
{ {
if (_itemsToIds.TryGetValue(item, out var id)) if (_itemsToIds.TryGetValue(item, out var id))
@ -190,33 +173,11 @@ namespace Avalonia.FreeDesktop
QueueReset(); QueueReset();
} }
public ObjectPath ObjectPath { get; } private static readonly string[] AllProperties = {
async Task<object> IFreeDesktopDBusProperties.GetAsync(string prop)
{
if (prop == "Version")
return 2;
if (prop == "Status")
return "normal";
return 0;
}
async Task<DBusMenuProperties> IFreeDesktopDBusProperties.GetAllAsync()
{
return new DBusMenuProperties
{
Version = 2,
Status = "normal",
};
}
private static string[] AllProperties = new[]
{
"type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data" "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; var (it, menu) = i;
@ -228,24 +189,20 @@ namespace Avalonia.FreeDesktop
else if (it is NativeMenuItem item) else if (it is NativeMenuItem item)
{ {
if (name == "type") if (name == "type")
{
return null; return null;
}
if (name == "label") if (name == "label")
return item?.Header ?? "<null>"; return item.Header ?? "<null>";
if (name == "enabled") if (name == "enabled")
{ {
if (item == null) if (item.Menu is not null && item.Menu.Items.Count == 0)
return null;
if (item.Menu != null && item.Menu.Items.Count == 0)
return false; return false;
if (item.IsEnabled == false) if (!item.IsEnabled)
return false; return false;
return null; return null;
} }
if (name == "shortcut") if (name == "shortcut")
{ {
if (item?.Gesture == null) if (item.Gesture is null)
return null; return null;
if (item.Gesture.KeyModifiers == 0) if (item.Gesture.KeyModifiers == 0)
return null; return null;
@ -271,19 +228,16 @@ namespace Avalonia.FreeDesktop
return "radio"; return "radio";
} }
if (name == "toggle-state") if (name == "toggle-state" && item.ToggleType != NativeMenuItemToggleType.None)
{ return item.IsChecked ? 1 : 0;
if (item.ToggleType != NativeMenuItemToggleType.None)
return item.IsChecked ? 1 : 0;
}
if (name == "icon-data") if (name == "icon-data")
{ {
if (item.Icon != null) if (item.Icon is not null)
{ {
var loader = AvaloniaLocator.Current.GetService<IPlatformIconLoader>(); var loader = AvaloniaLocator.Current.GetService<IPlatformIconLoader>();
if (loader != null) if (loader is not null)
{ {
var icon = loader.LoadIcon(item.Icon.PlatformImpl.Item); var icon = loader.LoadIcon(item.Icon.PlatformImpl.Item);
@ -293,155 +247,199 @@ namespace Avalonia.FreeDesktop
} }
} }
} }
if (name == "children-display") if (name == "children-display")
return menu != null ? "submenu" : null; return menu is not null ? "submenu" : null;
} }
return null; return null;
} }
private List<KeyValuePair<string, object>> _reusablePropertyList = new List<KeyValuePair<string, object>>(); private Dictionary<string, object> GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names)
KeyValuePair<string, object>[] GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names)
{ {
if (names?.Length > 0 != true) if (names.Length == 0)
names = AllProperties; names = AllProperties;
_reusablePropertyList.Clear(); var properties = new Dictionary<string, object>();
foreach (var n in names) foreach (var n in names)
{ {
var v = GetProperty(i, n); var v = GetProperty(i, n);
if (v != null) if (v is not null)
_reusablePropertyList.Add(new KeyValuePair<string, object>(n, v)); properties.Add(n, v);
} }
return _reusablePropertyList.ToArray(); return properties;
} }
private (int, Dictionary<string, object>, object[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames)
public Task SetAsync(string prop, object val) => Task.CompletedTask;
public Task<(uint revision, (int, KeyValuePair<string, object>[], object[]) layout)> GetLayoutAsync(
int ParentId, int RecursionDepth, string[] PropertyNames)
{ {
var menu = GetMenu(ParentId); var id = item is null ? 0 : GetId(item);
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<string, object>[], object[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames)
{
var id = item == null ? 0 : GetId(item);
var props = GetProperties((item, menu), propertyNames); var props = GetProperties((item, menu), propertyNames);
var children = (depth == 0 || menu == null) ? Array.Empty<object>() : new object[menu.Items.Count]; var children = depth == 0 || menu is null ? Array.Empty<object>() : new object[menu.Items.Count];
if(menu != null) if (menu is not null)
{
for (var c = 0; c < children.Length; c++) for (var c = 0; c < children.Length; c++)
{ {
var ch = menu.Items[c]; var ch = menu.Items[c];
children[c] = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames); children[c] = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames);
} }
return (id, props, children);
}
public Task<(int, KeyValuePair<string, object>[])[]> GetGroupPropertiesAsync(int[] Ids, string[] PropertyNames)
{
var arr = new (int, KeyValuePair<string, object>[])[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); return (id, props, children);
}
public async Task<object> GetPropertyAsync(int Id, string Name)
{
return GetProperty(GetMenu(Id), Name) ?? 0;
} }
private void HandleEvent(int id, string eventId, object data, uint timestamp)
public void HandleEvent(int id, string eventId, object data, uint timestamp)
{ {
if (eventId == "clicked") if (eventId == "clicked")
{ {
var item = GetMenu(id).item; var item = GetMenu(id).item;
if (item is NativeMenuItem { IsEnabled: true } and INativeMenuItemExporterEventsImplBridge bridge)
if (item is NativeMenuItem menuItem && item is INativeMenuItemExporterEventsImplBridge bridge) bridge.RaiseClicked();
{
if (menuItem?.IsEnabled == true)
bridge?.RaiseClicked();
}
} }
} }
public Task EventAsync(int Id, string EventId, object Data, uint Timestamp)
{
HandleEvent(Id, EventId, Data, Timestamp);
return Task.CompletedTask;
}
public Task<int[]> EventGroupAsync((int id, string eventId, object data, uint timestamp)[] Events) public ValueTask HandleMethodAsync(MethodContext context)
{ {
foreach (var e in Events) switch (context.Request.InterfaceAsString)
HandleEvent(e.id, e.eventId, e.data, e.timestamp); {
return Task.FromResult(Array.Empty<int>()); case "com.canonical.dbusmenu":
} switch (context.Request.MemberAsString, context.Request.SignatureAsString)
{
public async Task<bool> AboutToShowAsync(int Id) case ("GetLayout", "iias"):
{ {
return false; using var writer = context.CreateReplyWriter("u(ia{sv}av)");
} var reader = context.Request.GetBodyReader();
var parentId = reader.ReadInt32();
public async Task<(int[] updatesNeeded, int[] idErrors)> AboutToShowGroupAsync(int[] Ids) var recursionDepth = reader.ReadInt32();
{ var propertyNames = reader.ReadArray<string>();
return (Array.Empty<int>(), Array.Empty<int>()); 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<int>();
var propertyNames = reader.ReadArray<string>();
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<int>());
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<int>(), Array.Empty<int>()));
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<string, object>)[] updatedProps, (int, string[])[] removedProps)> break;
ItemsPropertiesUpdated { add { } remove { } } }
private event Action<(uint revision, int parent)>? LayoutUpdated;
private event Action<(int id, uint timestamp)> ItemActivationRequested { add { } remove { } }
private event Action<PropertyChanges> PropertiesChanged { add { } remove { } }
async Task<IDisposable> IDBusMenu.WatchItemsPropertiesUpdatedAsync(Action<((int, IDictionary<string, object>)[] updatedProps, (int, string[])[] removedProps)> handler, Action<Exception>? onError) return default;
{
ItemsPropertiesUpdated += handler;
return Disposable.Create(() => ItemsPropertiesUpdated -= handler);
}
async Task<IDisposable> IDBusMenu.WatchLayoutUpdatedAsync(Action<(uint revision, int parent)> handler, Action<Exception>? onError)
{
LayoutUpdated += handler;
return Disposable.Create(() => LayoutUpdated -= handler);
} }
async Task<IDisposable> IDBusMenu.WatchItemActivationRequestedAsync(Action<(int id, uint timestamp)> handler, Action<Exception>? onError) public bool RunMethodHandlerSynchronously(Message message) => true;
{
ItemActivationRequested+= handler;
return Disposable.Create(() => ItemActivationRequested -= handler);
}
async Task<IDisposable> IFreeDesktopDBusProperties.WatchPropertiesAsync(Action<PropertyChanges> handler) private void EmitUIntIntSignal(string member, uint arg0, int arg1)
{ {
PropertiesChanged += handler; using var writer = _connection.GetMessageWriter();
return Disposable.Create(() => PropertiesChanged -= handler); writer.WriteSignalHeader(null, Path, "com.canonical.dbusmenu", member, "ui");
writer.WriteUInt32(arg0);
writer.WriteInt32(arg1);
_connection.TrySendMessage(writer.CreateMessage());
} }
#endregion
} }
} }
} }

16
src/Avalonia.FreeDesktop/DBusRequest.cs

@ -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<IDisposable> WatchResponseAsync(Action<(uint response, IDictionary<string, object> results)> handler, Action<Exception>? onError = null);
}
}

107
src/Avalonia.FreeDesktop/DBusSystemDialog.cs

@ -4,45 +4,33 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Logging;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using Avalonia.Platform.Storage.FileIO; using Avalonia.Platform.Storage.FileIO;
using Tmds.DBus.Protocol;
using Tmds.DBus;
namespace Avalonia.FreeDesktop namespace Avalonia.FreeDesktop
{ {
internal class DBusSystemDialog : BclStorageProvider internal class DBusSystemDialog : BclStorageProvider
{ {
private static readonly Lazy<IFileChooser?> s_fileChooser = new(() => DBusHelper.Connection? internal static async Task<IStorageProvider?> TryCreateAsync(IPlatformHandle handle)
.CreateProxy<IFileChooser>("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"));
internal static async Task<IStorageProvider?> TryCreate(IPlatformHandle handle)
{ {
if (handle.HandleDescriptor == "XID" && s_fileChooser.Value is { } fileChooser) if (DBusHelper.Connection is null)
{ return null;
try var services = await DBusHelper.Connection.ListServicesAsync();
{ if (!services.Contains("org.freedesktop.portal.Desktop", StringComparer.Ordinal))
await fileChooser.GetVersionAsync(); return null;
return new DBusSystemDialog(fileChooser, handle); return new DBusSystemDialog(new DesktopService(DBusHelper.Connection, "org.freedesktop.portal.Desktop"), 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;
} }
private readonly IFileChooser _fileChooser; private readonly DesktopService _desktopService;
private readonly FileChooser _fileChooser;
private readonly IPlatformHandle _handle; 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; _handle = handle;
} }
@ -59,20 +47,24 @@ namespace Avalonia.FreeDesktop
var chooserOptions = new Dictionary<string, object>(); var chooserOptions = new Dictionary<string, object>();
var filters = ParseFilters(options.FileTypeFilter); var filters = ParseFilters(options.FileTypeFilter);
if (filters.Any()) if (filters.Any())
{
chooserOptions.Add("filters", filters); chooserOptions.Add("filters", filters);
}
chooserOptions.Add("multiple", options.AllowMultiple); chooserOptions.Add("multiple", options.AllowMultiple);
objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath); var request = _desktopService.CreateRequest(objectPath);
var tsc = new TaskCompletionSource<string[]?>(); var tsc = new TaskCompletionSource<string[]?>();
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); using var disposable = await request.WatchResponseAsync((e, x) =>
var uris = await tsc.Task ?? Array.Empty<string>(); {
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<string>();
return uris.Select(static path => new BclStorageFile(new FileInfo(new Uri(path).LocalPath))).ToList();
} }
public override async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options) public override async Task<IStorageFile?> SaveFilePickerAsync(FilePickerSaveOptions options)
@ -82,33 +74,33 @@ namespace Avalonia.FreeDesktop
var chooserOptions = new Dictionary<string, object>(); var chooserOptions = new Dictionary<string, object>();
var filters = ParseFilters(options.FileTypeChoices); var filters = ParseFilters(options.FileTypeChoices);
if (filters.Any()) if (filters.Any())
{
chooserOptions.Add("filters", filters); chooserOptions.Add("filters", filters);
}
if (options.SuggestedFileName is { } currentName) if (options.SuggestedFileName is { } currentName)
chooserOptions.Add("current_name", currentName); chooserOptions.Add("current_name", currentName);
if (options.SuggestedStartLocation?.TryGetUri(out var currentFolder) == true) if (options.SuggestedStartLocation?.TryGetUri(out var currentFolder) == true)
chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString())); chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString()));
objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath); objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
var request = _desktopService.CreateRequest(objectPath);
var tsc = new TaskCompletionSource<string[]?>(); var tsc = new TaskCompletionSource<string[]?>();
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 uris = await tsc.Task;
var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).LocalPath : null; var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).LocalPath : null;
if (path is null) if (path is null)
{
return 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<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options) public override async Task<IReadOnlyList<IStorageFolder>> OpenFolderPickerAsync(FolderPickerOpenOptions options)
@ -119,27 +111,31 @@ namespace Avalonia.FreeDesktop
{ "directory", true }, { "directory", true },
{ "multiple", options.AllowMultiple } { "multiple", options.AllowMultiple }
}; };
var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions);
var request = DBusHelper.Connection!.CreateProxy<IRequest>("org.freedesktop.portal.Request", objectPath); var request = _desktopService.CreateRequest(objectPath);
var tsc = new TaskCompletionSource<string[]?>(); var tsc = new TaskCompletionSource<string[]?>();
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException); using var disposable = await request.WatchResponseAsync((e, x) =>
var uris = await tsc.Task ?? Array.Empty<string>(); {
if (e is not null)
tsc.TrySetException(e);
else
tsc.TrySetResult(x.results["uris"] as string[]);
});
var uris = await tsc.Task ?? Array.Empty<string>();
return uris 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. // WSL2 freedesktop allows to select files as well in directory picker, filter it out.
.Where(Directory.Exists) .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<FilePickerFileType>? fileTypes) private static (string name, (uint style, string extension)[])[] ParseFilters(IReadOnlyList<FilePickerFileType>? fileTypes)
{ {
// Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] // Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])]
if (fileTypes is null) if (fileTypes is null)
{
return Array.Empty<(string name, (uint style, string extension)[])>(); return Array.Empty<(string name, (uint style, string extension)[])>();
}
var filters = new List<(string name, (uint style, string extension)[])>(); var filters = new List<(string name, (uint style, string extension)[])>();
foreach (var fileType in fileTypes) foreach (var fileType in fileTypes)
@ -150,18 +146,11 @@ namespace Avalonia.FreeDesktop
var extensions = Enumerable.Empty<(uint, string)>(); var extensions = Enumerable.Empty<(uint, string)>();
if (fileType.Patterns is { } patterns) if (fileType.Patterns is { } patterns)
{
extensions = extensions.Concat(patterns.Select(static x => (globStyle, x))); extensions = extensions.Concat(patterns.Select(static x => (globStyle, x)));
}
else if (fileType.MimeTypes is { } mimeTypes) else if (fileType.MimeTypes is { } mimeTypes)
{
extensions = extensions.Concat(mimeTypes.Select(static x => (mimeStyle, x))); extensions = extensions.Concat(mimeTypes.Select(static x => (mimeStyle, x)));
}
if (extensions.Any()) if (extensions.Any())
{
filters.Add((fileType.Name, extensions.ToArray())); filters.Add((fileType.Name, extensions.ToArray()));
}
} }
return filters.ToArray(); return filters.ToArray();

415
src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs

@ -1,16 +1,12 @@
#nullable enable using System;
using System.Collections.Generic;
using System;
using System.Diagnostics; using System.Diagnostics;
using System.Reactive.Disposables; using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Platform; using Avalonia.Platform;
using Tmds.DBus; using Tmds.DBus.Protocol;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop namespace Avalonia.FreeDesktop
{ {
@ -20,11 +16,12 @@ namespace Avalonia.FreeDesktop
private readonly ObjectPath _dbusMenuPath; private readonly ObjectPath _dbusMenuPath;
private readonly Connection? _connection; private readonly Connection? _connection;
private IDisposable? _serviceWatchDisposable; private readonly DBus? _dBus;
private IDisposable? _serviceWatchDisposable;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj; private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private IStatusNotifierWatcher? _statusNotifierWatcher; private StatusNotifierWatcher? _statusNotifierWatcher;
private DbusPixmap _icon; private (int, int, byte[]) _icon;
private string? _sysTrayServiceName; private string? _sysTrayServiceName;
private string? _tooltipText; private string? _tooltipText;
@ -51,6 +48,7 @@ namespace Avalonia.FreeDesktop
IsActive = true; IsActive = true;
_dBus = new DBusService(_connection, "org.freedesktop.DBus").CreateDBus("/org/freedesktop/DBus");
_dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; _dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath;
MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection); MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection);
@ -60,47 +58,31 @@ namespace Avalonia.FreeDesktop
private void InitializeSNWService() private void InitializeSNWService()
{ {
if (_connection is null || _isDisposed) return; if (_connection is null || _isDisposed)
try
{
_statusNotifierWatcher = _connection.CreateProxy<IStatusNotifierWatcher>(
"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.");
return; return;
}
_statusNotifierWatcher = new StatusNotifierWatcherService(_connection, "org.kde.StatusNotifierWatcher")
.CreateStatusNotifierWatcher("/StatusNotifierWatcher");
_serviceConnected = true; _serviceConnected = true;
} }
private async void WatchAsync() private async void WatchAsync()
{ {
try var services = await _connection!.ListServicesAsync();
{ if (!services.Contains("org.kde.StatusNotifierWatcher", StringComparer.Ordinal))
_serviceWatchDisposable = return;
await _connection?.ResolveServiceOwnerAsync("org.kde.StatusNotifierWatcher", OnNameChange)!;
} _serviceWatchDisposable = await _dBus!.WatchNameOwnerChangedAsync((e, x) => { OnNameChange(x.A2); });
catch (Exception e) var nameOwner = await _dBus.GetNameOwnerAsync("org.kde.StatusNotifierWatcher");
{ OnNameChange(nameOwner);
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this,
$"Unable to hook watcher method on org.kde.StatusNotifierWatcher: {e}");
}
} }
private void OnNameChange(ServiceOwnerChangedEventArgs obj) private void OnNameChange(string? newOwner)
{ {
if (_isDisposed) if (_isDisposed)
return; return;
if (!_serviceConnected & obj.NewOwner != null) if (!_serviceConnected & newOwner is not null)
{ {
_serviceConnected = true; _serviceConnected = true;
InitializeSNWService(); InitializeSNWService();
@ -108,55 +90,45 @@ namespace Avalonia.FreeDesktop
DestroyTrayIcon(); DestroyTrayIcon();
if (_isVisible) if (_isVisible)
{
CreateTrayIcon(); CreateTrayIcon();
}
} }
else if (_serviceConnected & obj.NewOwner is null) else if (_serviceConnected & newOwner is null)
{ {
DestroyTrayIcon(); DestroyTrayIcon();
_serviceConnected = false; _serviceConnected = false;
} }
} }
private void CreateTrayIcon() private async void CreateTrayIcon()
{ {
if (_connection is null || !_serviceConnected || _isDisposed) if (_connection is null || !_serviceConnected || _isDisposed)
return; return;
#if NET5_0_OR_GREATER
var pid = Environment.ProcessId;
#else
var pid = Process.GetCurrentProcess().Id; var pid = Process.GetCurrentProcess().Id;
#endif
var tid = s_trayIconInstanceId++; var tid = s_trayIconInstanceId++;
_sysTrayServiceName = FormattableString.Invariant($"org.kde.StatusNotifierItem-{pid}-{tid}"); _sysTrayServiceName = FormattableString.Invariant($"org.kde.StatusNotifierItem-{pid}-{tid}");
_statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath); _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_connection, _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}.");
_serviceConnected = false; _connection.AddMethodHandler(_statusNotifierItemDbusObj);
} await _dBus!.RequestNameAsync(_sysTrayServiceName, 0);
await _statusNotifierWatcher!.RegisterStatusNotifierItemAsync(_sysTrayServiceName);
_statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText);
_statusNotifierItemDbusObj.SetIcon(_icon); _statusNotifierItemDbusObj.SetIcon(_icon);
_statusNotifierItemDbusObj.ActivationDelegate += OnClicked; _statusNotifierItemDbusObj.ActivationDelegate += OnClicked;
} }
private void DestroyTrayIcon() 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; return;
_connection.UnregisterObject(_statusNotifierItemDbusObj); _dBus.ReleaseNameAsync(_sysTrayServiceName);
_connection.UnregisterServiceAsync(_sysTrayServiceName);
} }
public void Dispose() public void Dispose()
@ -175,13 +147,14 @@ namespace Avalonia.FreeDesktop
if (icon is null) if (icon is null)
{ {
_statusNotifierItemDbusObj?.SetIcon(DbusPixmap.EmptyPixmap); _statusNotifierItemDbusObj?.SetIcon((1, 1, new byte[] { 255, 0, 0, 0 }));
return; return;
} }
var x11iconData = IconConverterDelegate(icon); var x11iconData = IconConverterDelegate(icon);
if (x11iconData.Length == 0) return; if (x11iconData.Length == 0)
return;
var w = (int)x11iconData[0]; var w = (int)x11iconData[0];
var h = (int)x11iconData[1]; var h = (int)x11iconData[1];
@ -199,7 +172,7 @@ namespace Avalonia.FreeDesktop
pixByteArray[pixByteArrayCounter++] = (byte)(rawPixel & 0xFF); pixByteArray[pixByteArrayCounter++] = (byte)(rawPixel & 0xFF);
} }
_icon = new DbusPixmap(w, h, pixByteArray); _icon = (w, h, pixByteArray);
_statusNotifierItemDbusObj?.SetIcon(_icon); _statusNotifierItemDbusObj?.SetIcon(_icon);
} }
@ -237,111 +210,38 @@ namespace Avalonia.FreeDesktop
/// <remarks> /// <remarks>
/// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html /// Useful guide: https://web.archive.org/web/20210818173850/https://www.notmart.org/misc/statusnotifieritem/statusnotifieritem.html
/// </remarks> /// </remarks>
internal class StatusNotifierItemDbusObj : IStatusNotifierItem internal class StatusNotifierItemDbusObj : IMethodHandler
{ {
private readonly Connection _connection;
private readonly StatusNotifierItemProperties _backingProperties; 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<string>? 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 _backingProperties = new StatusNotifierItemProperties
{ {
Menu = dbusmenuPath, // Needs a dbus menu somehow Menu = dbusMenuPath, // Needs a dbus menu somehow
ToolTip = new ToolTip("") ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), string.Empty, string.Empty)
}; };
InvalidateAll(); InvalidateAll();
} }
public Task ContextMenuAsync(int x, int y) => Task.CompletedTask; public string Path => "/StatusNotifierItem";
public Task ActivateAsync(int x, int y)
{
ActivationDelegate?.Invoke();
return Task.CompletedTask;
}
public Task SecondaryActivateAsync(int x, int y) => Task.CompletedTask; public event Action? ActivationDelegate;
public Task ScrollAsync(int delta, string orientation) => Task.CompletedTask;
public void InvalidateAll() public void InvalidateAll()
{ {
OnTitleChanged?.Invoke(); EmitVoidSignal("NewTitle");
OnIconChanged?.Invoke(); EmitVoidSignal("NewIcon");
OnOverlayIconChanged?.Invoke(); EmitVoidSignal("NewAttentionIcon");
OnAttentionIconChanged?.Invoke(); EmitVoidSignal("NewOverlayIcon");
OnTooltipChanged?.Invoke(); EmitVoidSignal("NewToolTip");
} EmitStringSignal("NewStatus", _backingProperties.Status ?? string.Empty);
public Task<IDisposable> WatchNewTitleAsync(Action handler, Action<Exception> onError)
{
OnTitleChanged += handler;
return Task.FromResult(Disposable.Create(() => OnTitleChanged -= handler));
}
public Task<IDisposable> WatchNewIconAsync(Action handler, Action<Exception> onError)
{
OnIconChanged += handler;
return Task.FromResult(Disposable.Create(() => OnIconChanged -= handler));
}
public Task<IDisposable> WatchNewAttentionIconAsync(Action handler, Action<Exception> onError)
{
OnAttentionIconChanged += handler;
return Task.FromResult(Disposable.Create(() => OnAttentionIconChanged -= handler));
} }
public Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError) public void SetIcon((int, int, byte[]) dbusPixmap)
{
OnOverlayIconChanged += handler;
return Task.FromResult(Disposable.Create(() => OnOverlayIconChanged -= handler));
}
public Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError)
{
OnTooltipChanged += handler;
return Task.FromResult(Disposable.Create(() => OnTooltipChanged -= handler));
}
public Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError)
{
NewStatusAsync += handler;
return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler));
}
public Task<object?> GetAsync(string prop)
{
return Task.FromResult<object?>(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<StatusNotifierItemProperties> GetAllAsync() => Task.FromResult(_backingProperties);
public Task SetAsync(string prop, object val) => Task.CompletedTask;
public Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler) =>
Task.FromResult(Disposable.Empty);
public void SetIcon(DbusPixmap dbusPixmap)
{ {
_backingProperties.IconPixmap = new[] { dbusPixmap }; _backingProperties.IconPixmap = new[] { dbusPixmap };
InvalidateAll(); InvalidateAll();
@ -356,98 +256,153 @@ namespace Avalonia.FreeDesktop
_backingProperties.Category = "ApplicationStatus"; _backingProperties.Category = "ApplicationStatus";
_backingProperties.Status = text; _backingProperties.Status = text;
_backingProperties.Title = text; _backingProperties.Title = text;
_backingProperties.ToolTip = new ToolTip(text); _backingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), text, string.Empty);
InvalidateAll(); InvalidateAll();
} }
}
[DBusInterface("org.kde.StatusNotifierWatcher")]
internal interface IStatusNotifierWatcher : IDBusObject
{
Task RegisterStatusNotifierItemAsync(string Service);
Task RegisterStatusNotifierHostAsync(string Service);
}
[DBusInterface("org.kde.StatusNotifierItem")] public bool RunMethodHandlerSynchronously(Message message) => false;
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<IDisposable> WatchNewTitleAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewAttentionIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError);
Task<object?> GetAsync(string prop);
Task<StatusNotifierItemProperties> GetAllAsync();
Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> 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 string? Status; public ValueTask HandleMethodAsync(MethodContext context)
{
public ObjectPath Menu; switch (context.Request.InterfaceAsString)
{
public DbusPixmap[]? IconPixmap; 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<string, object>
{
{ "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 break;
{ }
public readonly string First;
public readonly DbusPixmap[] Second;
public readonly string Third;
public readonly string Fourth;
private static readonly DbusPixmap[] s_blank = return default;
{ }
new DbusPixmap(0, 0, Array.Empty<byte>()), new DbusPixmap(0, 0, Array.Empty<byte>())
};
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; using var writer = _connection.GetMessageWriter();
Second = second; writer.WriteSignalHeader(null, Path, "org.kde.StatusNotifierItem", member, "s");
Third = third; writer.WriteString(value);
Fourth = fourth; _connection.TrySendMessage(writer.CreateMessage());
} }
}
internal readonly struct DbusPixmap private record StatusNotifierItemProperties
{
public readonly int Width;
public readonly int Height;
public readonly byte[] Data;
public DbusPixmap(int width, int height, byte[] data)
{ {
Width = width; public string? Category { get; set; }
Height = height; public string? Id { get; set; }
Data = data; 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 });
} }
} }

937
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<string, object> 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<IDisposable> WatchActionInvokedAsync(Action<Exception?, (string Id, string Action, object[] Parameter)> 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<uint> GetVersionAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"),
(m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this);
public Task<NotificationProperties> 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<IDisposable> WatchPropertiesChangedAsync(Action<Exception?, PropertyChanges<NotificationProperties>> handler,
bool emitOnCapturedContext = true)
{
return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler,
emitOnCapturedContext);
static PropertyChanges<NotificationProperties> ReadMessage(Message message, DesktopObject _)
{
var reader = message.GetBodyReader();
reader.ReadString(); // interface
List<string> changed = new();
return new PropertyChanges<NotificationProperties>(ReadProperties(ref reader, changed), changed.ToArray(),
ReadInvalidated(ref reader));
}
static string[] ReadInvalidated(ref Reader reader)
{
List<string>? 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<string>();
}
}
private static NotificationProperties ReadProperties(ref Reader reader, List<string>? 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<ObjectPath> OpenUriAsync(string parentWindow, string uri, Dictionary<string, object> 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<ObjectPath> OpenFileAsync(string parentWindow, SafeHandle fd, Dictionary<string, object> 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<ObjectPath> OpenDirectoryAsync(string parentWindow, SafeHandle fd, Dictionary<string, object> 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<uint> GetVersionAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"),
(m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this);
public Task<OpenURIProperties> 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<IDisposable> WatchPropertiesChangedAsync(Action<Exception?, PropertyChanges<OpenURIProperties>> handler,
bool emitOnCapturedContext = true)
{
return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler,
emitOnCapturedContext);
static PropertyChanges<OpenURIProperties> ReadMessage(Message message, DesktopObject _)
{
var reader = message.GetBodyReader();
reader.ReadString(); // interface
List<string> changed = new();
return new PropertyChanges<OpenURIProperties>(ReadProperties(ref reader, changed), changed.ToArray(), ReadInvalidated(ref reader));
}
static string[] ReadInvalidated(ref Reader reader)
{
List<string>? 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<string>();
}
}
private static OpenURIProperties ReadProperties(ref Reader reader, List<string>? 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<string, object> 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<ObjectPath> PrepareInstallAsync(string parentWindow, string name, object iconV, Dictionary<string, object> 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<string> RequestInstallTokenAsync(string name, object iconV, Dictionary<string, object> 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<string, object> 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<string> 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<string, object> 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<uint> GetSupportedLauncherTypesAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "SupportedLauncherTypes"),
(m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this);
public Task<uint> GetVersionAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"),
(m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this);
public Task<DynamicLauncherProperties> 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<IDisposable> WatchPropertiesChangedAsync(Action<Exception?, PropertyChanges<DynamicLauncherProperties>> handler,
bool emitOnCapturedContext = true)
{
return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler,
emitOnCapturedContext);
static PropertyChanges<DynamicLauncherProperties> ReadMessage(Message message, DesktopObject _)
{
var reader = message.GetBodyReader();
reader.ReadString(); // interface
List<string> changed = new();
return new PropertyChanges<DynamicLauncherProperties>(ReadProperties(ref reader, changed), changed.ToArray(),
ReadInvalidated(ref reader));
}
static string[] ReadInvalidated(ref Reader reader)
{
List<string>? 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<string>();
}
}
private static DynamicLauncherProperties ReadProperties(ref Reader reader, List<string>? 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<ObjectPath> OpenFileAsync(string parentWindow, string title, Dictionary<string, object> 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<ObjectPath> SaveFileAsync(string parentWindow, string title, Dictionary<string, object> 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<ObjectPath> SaveFilesAsync(string parentWindow, string title, Dictionary<string, object> 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<uint> GetVersionAsync()
=> Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "version"),
(m, s) => ReadMessage_v_u(m, (DesktopObject)s!), this);
public Task<FileChooserProperties> 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<IDisposable> WatchPropertiesChangedAsync(Action<Exception?, PropertyChanges<FileChooserProperties>> handler,
bool emitOnCapturedContext = true)
{
return base.WatchPropertiesChangedAsync(Interface, (m, s) => ReadMessage(m, (DesktopObject)s!), handler,
emitOnCapturedContext);
static PropertyChanges<FileChooserProperties> ReadMessage(Message message, DesktopObject _)
{
var reader = message.GetBodyReader();
reader.ReadString(); // interface
List<string> changed = new();
return new PropertyChanges<FileChooserProperties>(ReadProperties(ref reader, changed), changed.ToArray(),
ReadInvalidated(ref reader));
}
static string[] ReadInvalidated(ref Reader reader)
{
List<string>? 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<string>();
}
}
private static FileChooserProperties ReadProperties(ref Reader reader, List<string>? 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<IDisposable> WatchResponseAsync(Action<Exception?, (uint response, IDictionary<string, object> 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<IDisposable> WatchPropertiesChangedAsync<TProperties>(string @interface,
MessageValueReader<PropertyChanges<TProperties>> reader, Action<Exception?, PropertyChanges<TProperties>> 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<Exception?, PropertyChanges<TProperties>>)hs!).Invoke(ex, changes),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync<TArg>(string sender, string @interface, ObjectPath path, string signal,
MessageValueReader<TArg> reader, Action<Exception?, TArg> 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<Exception?, TArg>)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<object>();
return (arg0, arg1, arg2);
}
protected static (uint, Dictionary<string, object>) ReadMessage_uaesv(Message message, DesktopObject _)
{
var reader = message.GetBodyReader();
var arg0 = reader.ReadUInt32();
var arg1 = reader.ReadDictionary<string, object>();
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<TProperties>
{
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;
}
}

351
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<IDisposable> WatchStatusNotifierItemRegisteredAsync(Action<Exception?, string> handler, bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierItemRegistered", static (m, s) =>
ReadMessage_s(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchStatusNotifierItemUnregisteredAsync(Action<Exception?, string> handler, bool emitOnCapturedContext = true)
=> WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierItemUnregistered", static (m, s)
=> ReadMessage_s(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchStatusNotifierHostRegisteredAsync(Action<Exception?> handler, bool emitOnCapturedContext = true) =>
WatchSignalAsync(Service.Destination, Interface, Path, "StatusNotifierHostRegistered", handler, emitOnCapturedContext);
public ValueTask<IDisposable> WatchStatusNotifierHostUnregisteredAsync(Action<Exception?> 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<string[]> GetRegisteredStatusNotifierItemsAsync() =>
Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "RegisteredStatusNotifierItems"), static (m, s)
=> ReadMessage_v_as(m, (StatusNotifierWatcherObject)s!), this);
public Task<bool> GetIsStatusNotifierHostRegisteredAsync() =>
Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "IsStatusNotifierHostRegistered"), static (m, s) =>
ReadMessage_v_b(m, (StatusNotifierWatcherObject)s!), this);
public Task<int> GetProtocolVersionAsync() =>
Connection.CallMethodAsync(CreateGetPropertyMessage(Interface, "ProtocolVersion"), static (m, s)
=> ReadMessage_v_i(m, (StatusNotifierWatcherObject)s!), this);
public Task<StatusNotifierWatcherProperties> 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<IDisposable> WatchPropertiesChangedAsync(Action<Exception?, PropertyChanges<StatusNotifierWatcherProperties>> handler, bool emitOnCapturedContext = true)
{
return base.WatchPropertiesChangedAsync(Interface, static (m, s) => ReadMessage(m, (StatusNotifierWatcherObject)s!), handler, emitOnCapturedContext);
static PropertyChanges<StatusNotifierWatcherProperties> ReadMessage(Message message, StatusNotifierWatcherObject _)
{
var reader = message.GetBodyReader();
reader.ReadString(); // interface
List<string> changed = new();
return new PropertyChanges<StatusNotifierWatcherProperties>(ReadProperties(ref reader, changed), changed.ToArray(),
ReadInvalidated(ref reader));
}
static string[] ReadInvalidated(ref Reader reader)
{
List<string>? invalidated = null;
var headersEnd = reader.ReadArrayStart(DBusType.String);
while (reader.HasNext(headersEnd))
{
invalidated ??= new List<string>();
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<string>();
}
}
private static StatusNotifierWatcherProperties ReadProperties(ref Reader reader, List<string>? 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<string>();
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<IDisposable> WatchPropertiesChangedAsync<TProperties>(string @interface, MessageValueReader<PropertyChanges<TProperties>> reader, Action<Exception?, PropertyChanges<TProperties>> 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<Exception?, PropertyChanges<TProperties>>)hs!).Invoke(ex, changes), this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync<TArg>(string sender, string @interface, ObjectPath path, string signal, MessageValueReader<TArg> reader, Action<Exception?, TArg> 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<Exception?, TArg>)hs!).Invoke(ex, arg),
this, handler, emitOnCapturedContext);
}
public ValueTask<IDisposable> WatchSignalAsync(string sender, string @interface, ObjectPath path, string signal, Action<Exception?> 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<Exception?>)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<string>();
}
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();
}
}
}

6
src/Avalonia.X11/X11Window.cs

@ -212,10 +212,10 @@ namespace Avalonia.X11
_x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1); _x11.Atoms.XA_CARDINAL, 32, PropertyMode.Replace, ref _xSyncCounter, 1);
} }
StorageProvider = new CompositeStorageProvider(new Func<Task<IStorageProvider>>[] StorageProvider = new CompositeStorageProvider(new[]
{ {
() => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreate(Handle) : Task.FromResult<IStorageProvider>(null), () => _platform.Options.UseDBusFilePicker ? DBusSystemDialog.TryCreateAsync(Handle) : Task.FromResult<IStorageProvider>(null),
() => GtkSystemDialog.TryCreate(this), () => GtkSystemDialog.TryCreate(this)
}); });
} }

1
src/Linux/Tmds.DBus

@ -0,0 +1 @@
Subproject commit bfca94ab052683c7ccef51e4a036098f539cc676
Loading…
Cancel
Save