public static INativeMenuExporter TryCreateDetachedNativeMenu()

in src/Avalonia.FreeDesktop/DBusMenuExporter.cs [22:344]


        public static INativeMenuExporter TryCreateDetachedNativeMenu(string path, Connection currentConnection) =>
            new DBusMenuExporterImpl(currentConnection, path);

        public static string GenerateDBusMenuObjPath => $"/net/avaloniaui/dbusmenu/{Guid.NewGuid():N}";

        private sealed class DBusMenuExporterImpl : ComCanonicalDbusmenuHandler, ITopLevelNativeMenuExporter, IDisposable
        {
            private readonly Dictionary<int, NativeMenuItemBase> _idsToItems = new();
            private readonly Dictionary<NativeMenuItemBase, int> _itemsToIds = new();
            private readonly HashSet<NativeMenu> _menus = [];
            private readonly PathHandler _pathHandler;
            private readonly uint _xid;
            private readonly bool _appMenu = true;
            private ComCanonicalAppMenuRegistrarProxy? _registrar;
            private NativeMenu? _menu;
            private bool _disposed;
            private uint _revision = 1;
            private bool _resetQueued;
            private int _nextId = 1;

            public DBusMenuExporterImpl(Connection connection, IntPtr xid)
            {
                Version = 4;
                Connection = connection;
                _xid = (uint)xid.ToInt32();
                _pathHandler = new PathHandler(GenerateDBusMenuObjPath);
                _pathHandler.Add(this);
                SetNativeMenu([]);
                _ = InitializeAsync();
            }

            public DBusMenuExporterImpl(Connection connection, string path)
            {
                Version = 4;
                Connection = connection;
                _appMenu = false;
                _pathHandler = new PathHandler(path);
                _pathHandler.Add(this);
                SetNativeMenu([]);
                _ = InitializeAsync();
            }

            public override Connection Connection { get; }

            protected override ValueTask<(uint Revision, (int, Dictionary<string, VariantValue>, VariantValue[]) Layout)> OnGetLayoutAsync(Message message, int parentId, int recursionDepth, string[] propertyNames)
            {
                var menu = GetMenu(parentId);
                var layout = GetLayout(menu.item, menu.menu, recursionDepth, propertyNames);
                if (!IsNativeMenuExported)
                {
                    IsNativeMenuExported = true;
                    OnIsNativeMenuExportedChanged?.Invoke(this, EventArgs.Empty);
                }

                return new ValueTask<(uint, (int, Dictionary<string, VariantValue>, VariantValue[]))>((_revision, layout));
            }

            protected override ValueTask<(int, Dictionary<string, VariantValue>)[]> OnGetGroupPropertiesAsync(Message message, int[] ids, string[] propertyNames)
                => new(ids.Select(id => (id, GetProperties(GetMenu(id), propertyNames))).ToArray());

            protected override ValueTask<VariantValue> OnGetPropertyAsync(Message message, int id, string name) =>
                new(GetProperty(GetMenu(id), name) ?? VariantValue.Int32(0));

            protected override ValueTask OnEventAsync(Message message, int id, string eventId, VariantValue data, uint timestamp)
            {
                HandleEvent(id, eventId);
                return new ValueTask();
            }

            protected override ValueTask<int[]> OnEventGroupAsync(Message message, (int, string, VariantValue, uint)[] events)
            {
                foreach (var e in events)
                    HandleEvent(e.Item1, e.Item2);
                return new ValueTask<int[]>([]);
            }

            protected override ValueTask<bool> OnAboutToShowAsync(Message message, int id) => new(false);

            protected override ValueTask<(int[] UpdatesNeeded, int[] IdErrors)> OnAboutToShowGroupAsync(Message message, int[] ids) =>
                new(([], []));

            private async Task InitializeAsync()
            {
                Connection.AddMethodHandler(_pathHandler);
                if (!_appMenu)
                    return;

                _registrar = new ComCanonicalAppMenuRegistrarProxy(Connection, "com.canonical.AppMenu.Registrar", "/com/canonical/AppMenu/Registrar");
                try
                {
                    if (!_disposed)
                        await _registrar.RegisterWindowAsync(_xid, _pathHandler.Path);
                }
                catch
                {
                    // It's not really important if this code succeeds,
                    // and it's not important to know if it succeeds
                    // since even if we register the window it's not guaranteed that
                    // menu will be actually exported
                    _registrar = null;
                }
            }

            public void Dispose()
            {
                if (_disposed)
                    return;
                _disposed = true;
                // Fire and forget
                _ = _registrar?.UnregisterWindowAsync(_xid);
                _pathHandler.Remove(this);
                Connection.RemoveMethodHandler(_pathHandler.Path);
            }

            public bool IsNativeMenuExported { get; private set; }

            public event EventHandler? OnIsNativeMenuExportedChanged;

            public void SetNativeMenu(NativeMenu? menu)
            {
                menu ??= [];

                if (_menu is not null)
                    ((INotifyCollectionChanged)_menu.Items).CollectionChanged -= OnMenuItemsChanged;
                _menu = menu;
                ((INotifyCollectionChanged)_menu.Items).CollectionChanged += OnMenuItemsChanged;

                DoLayoutReset();
            }

            /*
                 This is basic initial implementation, so we don't actually track anything and
                 just reset the whole layout on *ANY* change
                 
                 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...
             */
            private void DoLayoutReset()
            {
                _resetQueued = false;
                foreach (var i in _idsToItems.Values)
                    i.PropertyChanged -= OnItemPropertyChanged;
                foreach(var menu in _menus)
                    ((INotifyCollectionChanged)menu.Items).CollectionChanged -= OnMenuItemsChanged;
                _menus.Clear();
                _idsToItems.Clear();
                _itemsToIds.Clear();
                _revision++;
                EmitLayoutUpdated(_revision, 0);
            }

            private void QueueReset()
            {
                if(_resetQueued)
                    return;
                _resetQueued = true;
                Dispatcher.UIThread.Post(DoLayoutReset, DispatcherPriority.Background);
            }

            private (NativeMenuItemBase? item, NativeMenu? menu) GetMenu(int id)
            {
                if (id == 0)
                    return (null, _menu);
                _idsToItems.TryGetValue(id, out var item);
                return (item, (item as NativeMenuItem)?.Menu);
            }

            private void EnsureSubscribed(NativeMenu? menu)
            {
                if (menu is not null && _menus.Add(menu))
                    ((INotifyCollectionChanged)menu.Items).CollectionChanged += OnMenuItemsChanged;
            }

            private int GetId(NativeMenuItemBase item)
            {
                if (_itemsToIds.TryGetValue(item, out var id))
                    return id;
                id = _nextId++;
                _idsToItems[id] = item;
                _itemsToIds[item] = id;
                item.PropertyChanged += OnItemPropertyChanged;
                if (item is NativeMenuItem nmi)
                    EnsureSubscribed(nmi.Menu);
                return id;
            }

            private void OnMenuItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) => QueueReset();

            private void OnItemPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) => QueueReset();

            private static readonly string[] s_allProperties = ["type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data"];

            private static VariantValue? GetProperty((NativeMenuItemBase? item, NativeMenu? menu) i, string name)
            {
                var (it, menu) = i;

                if (it is NativeMenuItemSeparator)
                {
                    if (name == "type")
                        return VariantValue.String("separator");
                }
                else if (it is NativeMenuItem item)
                {
                    if (name == "type")
                        return null;
                    if (name == "label")
                        return VariantValue.String(item.Header ?? "<null>");
                    if (name == "enabled")
                    {
                        if (item.Menu is not null && item.Menu.Items.Count == 0)
                            return VariantValue.Bool(false);
                        if (!item.IsEnabled)
                            return VariantValue.Bool(false);
                        return null;
                    }

                    if (name == "visible")
                        return VariantValue.Bool(item.IsVisible);

                    if (name == "shortcut")
                    {
                        if (item.Gesture is null)
                            return null;
                        if (item.Gesture.KeyModifiers == 0)
                            return null;
                        var lst = new Array<string>();
                        var mod = item.Gesture;
                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Control))
                            lst.Add("Control");
                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Alt))
                            lst.Add("Alt");
                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Shift))
                            lst.Add("Shift");
                        if (mod.KeyModifiers.HasAllFlags(KeyModifiers.Meta))
                            lst.Add("Super");
                        lst.Add(item.Gesture.Key.ToString());
                        return new Array<Array<string>> { lst }.AsVariantValue();
                    }

                    if (name == "toggle-type")
                    {
                        if (item.ToggleType == NativeMenuItemToggleType.CheckBox)
                            return VariantValue.String("checkmark");
                        if (item.ToggleType == NativeMenuItemToggleType.Radio)
                            return VariantValue.String("radio");
                    }

                    if (name == "toggle-state" && item.ToggleType != NativeMenuItemToggleType.None)
                        return VariantValue.Int32(item.IsChecked ? 1 : 0);

                    if (name == "icon-data")
                    {
                        if (item.Icon is not null)
                        {
                            var loader = AvaloniaLocator.Current.GetService<IPlatformIconLoader>();

                            if (loader is not null)
                            {
                                var icon = loader.LoadIcon(item.Icon.PlatformImpl.Item);
                                using var ms = new MemoryStream();
                                icon.Save(ms);
                                return VariantValue.Array(ms.ToArray());
                            }
                        }
                    }

                    if (name == "children-display")
                    {
                        if (menu is not null)
                            return VariantValue.String("submenu");
                        return null;
                    }
                }

                return null;
            }

            private static Dictionary<string, VariantValue> GetProperties((NativeMenuItemBase? item, NativeMenu? menu) i, string[] names)
            {
                if (names.Length == 0)
                    names = s_allProperties;
                var properties = new Dictionary<string, VariantValue>();
                foreach (var n in names)
                {
                    var v = GetProperty(i, n);
                    if (v.HasValue)
                        properties.Add(n, v.Value);
                }

                return properties;
            }

            private (int, Dictionary<string, VariantValue>, VariantValue[]) 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 ? [] : new VariantValue[menu.Items.Count];
                if (menu is not null)
                {
                    for (var c = 0; c < children.Length; c++)
                    {
                        var ch = menu.Items[c];
                        var layout = GetLayout(ch, (ch as NativeMenuItem)?.Menu, depth == -1 ? -1 : depth - 1, propertyNames);
                        children[c] = VariantValue.Struct(
                            VariantValue.Int32(layout.Item1),
                            new Dict<string, VariantValue>(layout.Item2).AsVariantValue(),
                            VariantValue.ArrayOfVariant(layout.Item3));
                    }
                }

                return (id, props, children);
            }

            private void HandleEvent(int id, string eventId)
            {
                if (eventId == "clicked")
                {
                    var item = GetMenu(id).item;
                    if (item is NativeMenuItem { IsEnabled: true } and INativeMenuItemExporterEventsImplBridge bridge)
                        bridge.RaiseClicked();
                }
            }
        }