diff --git a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs index 57bb10a885..59e9ecd1cf 100644 --- a/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs +++ b/src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs @@ -4,6 +4,8 @@ using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Tmds.DBus.Protocol; +using Tmds.DBus.SourceGenerator; + namespace Avalonia.FreeDesktop.DBusIme.IBus { @@ -49,18 +51,13 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus }); } - private void OnCommitText(Exception? e, object wtf) + private void OnCommitText(Exception? e, DBusVariantItem variantItem) { if (e is not null) return; - // Hello darkness, my old friend - if (wtf.GetType().GetField("Item3") is { } prop) - { - var text = prop.GetValue(wtf) as string; - if (!string.IsNullOrEmpty(text)) - FireCommit(text!); - } + if (variantItem.Value is DBusStructItem { Count: >= 3 } structItem && structItem[2] is DBusStringItem stringItem) + FireCommit(stringItem.Value); } protected override Task DisconnectAsync() => _service?.DestroyAsync() ?? Task.CompletedTask; diff --git a/src/Avalonia.FreeDesktop/DBusInterfaces.cs b/src/Avalonia.FreeDesktop/DBusInterfaces.cs index 3bece27ab1..fb2f0ca2f3 100644 --- a/src/Avalonia.FreeDesktop/DBusInterfaces.cs +++ b/src/Avalonia.FreeDesktop/DBusInterfaces.cs @@ -9,9 +9,10 @@ namespace Avalonia.FreeDesktop [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputMethod.xml")] [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputContext1.xml")] [DBusInterface("./DBusXml/org.fcitx.Fcitx.InputMethod1.xml")] + [DBusInterface("./DBusXml/org.freedesktop.IBus.Portal.xml")] [DBusInterface("./DBusXml/org.freedesktop.portal.FileChooser.xml")] [DBusInterface("./DBusXml/org.freedesktop.portal.Request.xml")] - [DBusInterface("./DBusXml/org.freedesktop.IBus.Portal.xml")] + [DBusInterface("./DBusXml/org.freedesktop.portal.Settings.xml")] [DBusHandler("./DBusXml/DBusMenu.xml")] [DBusHandler("./DBusXml/StatusNotifierItem.xml")] internal class DBusInterfaces { } diff --git a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs index cb6def056f..1978dfba81 100644 --- a/src/Avalonia.FreeDesktop/DBusMenuExporter.cs +++ b/src/Avalonia.FreeDesktop/DBusMenuExporter.cs @@ -9,8 +9,7 @@ using Avalonia.Input; using Avalonia.Platform; using Avalonia.Threading; using Tmds.DBus.Protocol; - -#pragma warning disable 1998 +using Tmds.DBus.SourceGenerator; namespace Avalonia.FreeDesktop { @@ -60,7 +59,7 @@ namespace Avalonia.FreeDesktop public override string Path { get; } - protected override (uint revision, (int, Dictionary, object[]) layout) OnGetLayout(int parentId, int recursionDepth, string[] propertyNames) + protected override (uint revision, (int, Dictionary, DBusVariantItem[]) layout) OnGetLayout(int parentId, int recursionDepth, string[] propertyNames) { var menu = GetMenu(parentId); var layout = GetLayout(menu.item, menu.menu, recursionDepth, propertyNames); @@ -73,15 +72,15 @@ namespace Avalonia.FreeDesktop return (_revision, layout); } - protected override (int, Dictionary)[] OnGetGroupProperties(int[] ids, string[] propertyNames) => + protected override (int, Dictionary)[] OnGetGroupProperties(int[] ids, string[] propertyNames) => ids.Select(id => (id, GetProperties(GetMenu(id), propertyNames))).ToArray(); - protected override object OnGetProperty(int id, string name) => GetProperty(GetMenu(id), name) ?? 0; + protected override DBusVariantItem OnGetProperty(int id, string name) => GetProperty(GetMenu(id), name) ?? new DBusVariantItem("i", new DBusInt32Item(0)); - protected override void OnEvent(int id, string eventId, object data, uint timestamp) => + protected override void OnEvent(int id, string eventId, DBusVariantItem data, uint timestamp) => Dispatcher.UIThread.Post(() => HandleEvent(id, eventId)); - protected override int[] OnEventGroup((int, string, object, uint)[] events) + protected override int[] OnEventGroup((int, string, DBusVariantItem, uint)[] events) { foreach (var e in events) Dispatcher.UIThread.Post(() => HandleEvent(e.Item1, e.Item2)); @@ -115,14 +114,14 @@ namespace Avalonia.FreeDesktop if (_disposed) return; _disposed = true; - // Fire and forget - _registrar?.UnregisterWindowAsync(_xid); + _ = _registrar?.UnregisterWindowAsync(_xid); } public bool IsNativeMenuExported { get; private set; } + public event EventHandler? OnIsNativeMenuExportedChanged; public void SetNativeMenu(NativeMenu? menu) @@ -203,31 +202,31 @@ namespace Avalonia.FreeDesktop QueueReset(); } - private static readonly string[] AllProperties = { + private static readonly string[] s_allProperties = { "type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data" }; - private object? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) + private DBusVariantItem? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name) { var (it, menu) = i; if (it is NativeMenuItemSeparator) { if (name == "type") - return "separator"; + return new DBusVariantItem("s", new DBusStringItem("separator")); } else if (it is NativeMenuItem item) { if (name == "type") return null; if (name == "label") - return item.Header ?? ""; + return new DBusVariantItem("s", new DBusStringItem(item.Header ?? "")); if (name == "enabled") { if (item.Menu is not null && item.Menu.Items.Count == 0) - return false; + return new DBusVariantItem("b", new DBusBoolItem(false)); if (!item.IsEnabled) - return false; + return new DBusVariantItem("b", new DBusBoolItem(false)); return null; } if (name == "shortcut") @@ -236,30 +235,30 @@ namespace Avalonia.FreeDesktop return null; if (item.Gesture.KeyModifiers == 0) return null; - var lst = new List(); + var lst = new List(); var mod = item.Gesture; if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Control)) - lst.Add("Control"); + lst.Add(new DBusStringItem("Control")); if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Alt)) - lst.Add("Alt"); + lst.Add(new DBusStringItem("Alt")); if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Shift)) - lst.Add("Shift"); + lst.Add(new DBusStringItem("Shift")); if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Meta)) - lst.Add("Super"); - lst.Add(item.Gesture.Key.ToString()); - return new[] { lst.ToArray() }; + lst.Add(new DBusStringItem("Super")); + lst.Add(new DBusStringItem(item.Gesture.Key.ToString())); + return new DBusVariantItem("aas", new DBusArrayItem(DBusType.Array, new[] { new DBusArrayItem(DBusType.String, lst) })); } if (name == "toggle-type") { if (item.ToggleType == NativeMenuItemToggleType.CheckBox) - return "checkmark"; + return new DBusVariantItem("s", new DBusStringItem("checkmark")); if (item.ToggleType == NativeMenuItemToggleType.Radio) - return "radio"; + return new DBusVariantItem("s", new DBusStringItem("radio")); } if (name == "toggle-state" && item.ToggleType != NativeMenuItemToggleType.None) - return item.IsChecked ? 1 : 0; + return new DBusVariantItem("i", new DBusInt32Item(item.IsChecked ? 1 : 0)); if (name == "icon-data") { @@ -273,23 +272,24 @@ namespace Avalonia.FreeDesktop using var ms = new MemoryStream(); icon.Save(ms); - return ms.ToArray(); + return new DBusVariantItem("ay", + new DBusArrayItem(DBusType.Byte, ms.ToArray().Select(static x => new DBusByteItem(x)))); } } } if (name == "children-display") - return menu is not null ? "submenu" : null; + return menu is not null ? new DBusVariantItem("s", new DBusStringItem("submenu")) : null; } return null; } - private Dictionary GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) + private Dictionary GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names) { if (names.Length == 0) - names = AllProperties; - var properties = new Dictionary(); + names = s_allProperties; + var properties = new Dictionary(); foreach (var n in names) { var v = GetProperty(i, n); @@ -300,17 +300,23 @@ namespace Avalonia.FreeDesktop return properties; } - private (int, Dictionary, object[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames) + private (int, Dictionary, DBusVariantItem[]) GetLayout(NativeMenuItemBase? item, NativeMenu? menu, int depth, string[] propertyNames) { var id = item is null ? 0 : GetId(item); var props = GetProperties((item, menu), propertyNames); - var children = depth == 0 || menu is null ? Array.Empty() : new object[menu.Items.Count]; + var children = depth == 0 || menu is null ? Array.Empty() : new DBusVariantItem[menu.Items.Count]; if (menu is not null) { for (var c = 0; c < children.Length; c++) { var ch = menu.Items[c]; - children[c] = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames); + var layout = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames); + children[c] = new DBusVariantItem("(ia{sv}av)", new DBusStructItem(new DBusItem[] + { + new DBusInt32Item(layout.Item1), + new DBusArrayItem(DBusType.DictEntry, layout.Item2.Select(static x => new DBusDictEntryItem(new DBusStringItem(x.Key), x.Value))), + new DBusArrayItem(DBusType.Variant, layout.Item3) + })); } } diff --git a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs index 039fc7c088..a25bb68458 100644 --- a/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs +++ b/src/Avalonia.FreeDesktop/DBusPlatformSettings.cs @@ -2,44 +2,35 @@ using System.Threading.Tasks; using Avalonia.Logging; using Avalonia.Platform; +using Tmds.DBus.SourceGenerator; -namespace Avalonia.FreeDesktop; - -internal class DBusPlatformSettings : DefaultPlatformSettings +namespace Avalonia.FreeDesktop { - private readonly IDBusSettings? _settings; - private PlatformColorValues? _lastColorValues; - - public DBusPlatformSettings() + internal class DBusPlatformSettings : DefaultPlatformSettings { - _settings = DBusHelper.TryInitialize()? - .CreateProxy("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + private readonly OrgFreedesktopPortalSettings? _settings; + private PlatformColorValues? _lastColorValues; - if (_settings is not null) + public DBusPlatformSettings() { - _ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); + if (DBusHelper.Connection is null) + return; - _ = TryGetInitialValue(); + _settings = new OrgFreedesktopPortalSettings(DBusHelper.Connection, "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop"); + _ = _settings.WatchSettingChangedAsync(SettingsChangedHandler); + _ = TryGetInitialValueAsync(); } - } - - public override PlatformColorValues GetColorValues() - { - return _lastColorValues ?? base.GetColorValues(); - } - private async Task TryGetInitialValue() - { - var colorSchemeTask = _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme"); - if (colorSchemeTask.Status == TaskStatus.RanToCompletion) + public override PlatformColorValues GetColorValues() { - _lastColorValues = GetColorValuesFromSetting(colorSchemeTask.Result); + return _lastColorValues ?? base.GetColorValues(); } - else + + private async Task TryGetInitialValueAsync() { try { - var value = await colorSchemeTask; + var value = await _settings!.ReadAsync("org.freedesktop.appearance", "color-scheme"); _lastColorValues = GetColorValuesFromSetting(value); OnColorValuesChanged(_lastColorValues); } @@ -49,29 +40,31 @@ internal class DBusPlatformSettings : DefaultPlatformSettings Logger.TryGet(LogEventLevel.Error, LogArea.FreeDesktopPlatform)?.Log(this, "Unable to get setting value", ex); } } - } - - private void SettingsChangedHandler((string @namespace, string key, object value) tuple) - { - if (tuple.@namespace == "org.freedesktop.appearance" - && tuple.key == "color-scheme") + + private void SettingsChangedHandler(Exception? exception, (string @namespace, string key, DBusVariantItem value) valueTuple) { - /* - 0: No preference - 1: Prefer dark appearance - 2: Prefer light appearance - */ - _lastColorValues = GetColorValuesFromSetting(tuple.value); - OnColorValuesChanged(_lastColorValues); + if (exception is not null) + return; + + if (valueTuple is ("org.freedesktop.appearance", "color-scheme", { } value)) + { + /* + 0: No preference + 1: Prefer dark appearance + 2: Prefer light appearance + */ + _lastColorValues = GetColorValuesFromSetting(value); + OnColorValuesChanged(_lastColorValues); + } } - } - - private static PlatformColorValues GetColorValuesFromSetting(object value) - { - var isDark = value?.ToString() == "1"; - return new PlatformColorValues + + private static PlatformColorValues GetColorValuesFromSetting(DBusVariantItem value) { - ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light - }; + var isDark = ((value.Value as DBusVariantItem)!.Value as DBusUInt32Item)!.Value == 1; + return new PlatformColorValues + { + ThemeVariant = isDark ? PlatformThemeVariant.Dark : PlatformThemeVariant.Light + }; + } } } diff --git a/src/Avalonia.FreeDesktop/DBusSettings.cs b/src/Avalonia.FreeDesktop/DBusSettings.cs deleted file mode 100644 index 05911981c7..0000000000 --- a/src/Avalonia.FreeDesktop/DBusSettings.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Tmds.DBus; - -namespace Avalonia.FreeDesktop; - -[DBusInterface("org.freedesktop.portal.Settings")] -internal interface IDBusSettings : IDBusObject -{ - Task<(string @namespace, IDictionary)> ReadAllAsync(string[] namespaces); - - Task ReadAsync(string @namespace, string key); - - Task WatchSettingChangedAsync(Action<(string @namespace, string key, object value)> handler, Action? onError = null); -} diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs index 81233f3b2f..763480a33d 100644 --- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs +++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs @@ -2,12 +2,12 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading.Tasks; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Platform.Storage.FileIO; using Tmds.DBus.Protocol; +using Tmds.DBus.SourceGenerator; namespace Avalonia.FreeDesktop { @@ -44,12 +44,12 @@ namespace Avalonia.FreeDesktop { var parentWindow = $"x11:{_handle.Handle:X}"; ObjectPath objectPath; - var chooserOptions = new Dictionary(); + var chooserOptions = new Dictionary(); var filters = ParseFilters(options.FileTypeFilter); - if (filters.Any()) + if (filters is not null) chooserOptions.Add("filters", filters); - chooserOptions.Add("multiple", options.AllowMultiple); + chooserOptions.Add("multiple", new DBusVariantItem("b", new DBusBoolItem(options.AllowMultiple))); objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); @@ -58,9 +58,8 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - tsc.TrySetException(e); - else - tsc.TrySetResult(x.results["uris"] as string[]); + return; + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task ?? Array.Empty(); @@ -71,15 +70,15 @@ namespace Avalonia.FreeDesktop { var parentWindow = $"x11:{_handle.Handle:X}"; ObjectPath objectPath; - var chooserOptions = new Dictionary(); + var chooserOptions = new Dictionary(); var filters = ParseFilters(options.FileTypeChoices); - if (filters.Any()) + if (filters is not null) chooserOptions.Add("filters", filters); if (options.SuggestedFileName is { } currentName) - chooserOptions.Add("current_name", currentName); + chooserOptions.Add("current_name", new DBusVariantItem("s", new DBusStringItem(currentName))); if (options.SuggestedStartLocation?.TryGetUri(out var currentFolder) == true) - chooserOptions.Add("current_folder", Encoding.UTF8.GetBytes(currentFolder.ToString())); + chooserOptions.Add("current_folder", new DBusVariantItem("s", new DBusStringItem(currentFolder.ToString()))); objectPath = await _fileChooser.SaveFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); var request = new OrgFreedesktopPortalRequest(_connection, "org.freedesktop.portal.Desktop", objectPath); @@ -87,9 +86,8 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - tsc.TrySetException(e); - else - tsc.TrySetResult(x.results["uris"] as string[]); + return; + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task; @@ -106,10 +104,10 @@ namespace Avalonia.FreeDesktop public override async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { var parentWindow = $"x11:{_handle.Handle:X}"; - var chooserOptions = new Dictionary + var chooserOptions = new Dictionary { - { "directory", true }, - { "multiple", options.AllowMultiple } + { "directory", new DBusVariantItem("b", new DBusBoolItem(true)) }, + { "multiple", new DBusVariantItem("b", new DBusBoolItem(options.AllowMultiple)) } }; var objectPath = await _fileChooser.OpenFileAsync(parentWindow, options.Title ?? string.Empty, chooserOptions); @@ -118,9 +116,8 @@ namespace Avalonia.FreeDesktop using var disposable = await request.WatchResponseAsync((e, x) => { if (e is not null) - tsc.TrySetException(e); - else - tsc.TrySetResult(x.results["uris"] as string[]); + return; + tsc.TrySetResult((x.results["uris"].Value as DBusArrayItem)?.Select(static y => (y as DBusStringItem)!.Value).ToArray()); }); var uris = await tsc.Task ?? Array.Empty(); @@ -131,29 +128,38 @@ namespace Avalonia.FreeDesktop .Select(static path => new BclStorageFolder(new DirectoryInfo(path))).ToList(); } - private static (string name, (uint style, string extension)[])[] ParseFilters(IReadOnlyList? fileTypes) + private static DBusVariantItem? ParseFilters(IReadOnlyList? fileTypes) { + const uint GlobStyle = 0u; + const uint MimeStyle = 1u; + // Example: [('Images', [(0, '*.ico'), (1, 'image/png')]), ('Text', [(0, '*.txt')])] if (fileTypes is null) - return Array.Empty<(string name, (uint style, string extension)[])>(); + return null; + + var any = false; + var filters = new DBusArrayItem(DBusType.Struct, new List()); - var filters = new List<(string name, (uint style, string extension)[])>(); foreach (var fileType in fileTypes) { - const uint GlobStyle = 0u; - const uint MimeStyle = 1u; - - var extensions = Enumerable.Empty<(uint, string)>(); + var extensions = new List(); + if (fileType.Patterns?.Count > 0) + extensions.AddRange( + fileType.Patterns.Select(static pattern => + new DBusStructItem(new DBusItem[] { new DBusUInt32Item(GlobStyle), new DBusStringItem(pattern) }))); + else if (fileType.MimeTypes?.Count > 0) + extensions.AddRange( + fileType.MimeTypes.Select(static mimeType => + new DBusStructItem(new DBusItem[] { new DBusUInt32Item(MimeStyle), new DBusStringItem(mimeType) }))); + else + continue; - if (fileType.Patterns is not null) - extensions = extensions.Concat(fileType.Patterns.Select(static x => (globStyle: GlobStyle, x))); - else if (fileType.MimeTypes is not null) - extensions = extensions.Concat(fileType.MimeTypes.Select(static x => (mimeStyle: MimeStyle, x))); - if (extensions.Any()) - filters.Add((fileType.Name, extensions.ToArray())); + any = true; + filters.Add(new DBusStructItem( + new DBusItem[] { new DBusStringItem(fileType.Name), new DBusArrayItem(DBusType.Struct, extensions) })); } - return filters.ToArray(); + return any ? new DBusVariantItem("a(sa(us))", filters) : null; } } } diff --git a/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml new file mode 100644 index 0000000000..669997a3df --- /dev/null +++ b/src/Avalonia.FreeDesktop/DBusXml/org.freedesktop.portal.Settings.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Linux/Tmds.DBus b/src/Linux/Tmds.DBus index bfca94ab05..e86bcf1bc2 160000 --- a/src/Linux/Tmds.DBus +++ b/src/Linux/Tmds.DBus @@ -1 +1 @@ -Subproject commit bfca94ab052683c7ccef51e4a036098f539cc676 +Subproject commit e86bcf1bc2d86338ab61663a4ae48dbc0bd7e02d diff --git a/src/tools/Tmds.DBus.SourceGenerator b/src/tools/Tmds.DBus.SourceGenerator index 6007f27d04..86e0ded07f 160000 --- a/src/tools/Tmds.DBus.SourceGenerator +++ b/src/tools/Tmds.DBus.SourceGenerator @@ -1 +1 @@ -Subproject commit 6007f27d04691f7c4c35722df3dc65bf4b558f18 +Subproject commit 86e0ded07fc8622e216f93f8fc01e8c1b3ef29b1