From 92d6f319535528e241a9e47c02f28b60933914e5 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Mon, 31 Aug 2026 18:52:12 +0200 Subject: [PATCH 1/2] lib: Introduce a KeybindingHelper --- lib/KeybindingHelper.vala | 158 ++++++++++++++++++++++++++++++++++++++ lib/meson.build | 1 + 2 files changed, 159 insertions(+) create mode 100644 lib/KeybindingHelper.vala diff --git a/lib/KeybindingHelper.vala b/lib/KeybindingHelper.vala new file mode 100644 index 00000000..c65ceaf8 --- /dev/null +++ b/lib/KeybindingHelper.vala @@ -0,0 +1,158 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Wingpanel.KeybindingHelper : Object { + private static Once instance; + + private ShellKeyGrabber? shell_key_grabber = null; + + private Gee.ArrayList keybindings; + + public static unowned KeybindingHelper get_default () { + return instance.once (() => new KeybindingHelper ()); + } + + construct { + keybindings = new Gee.ArrayList (); + + try { + shell_key_grabber = Bus.get_proxy_sync (SESSION, "org.gnome.Shell", "/org/gnome/Shell"); + } catch (Error e) { + warning ("Failed to get key grabber proxy: %s", e.message); + } + } + + public void add_keybinding (string name, Settings settings, KeybindingAction action) requires (shell_key_grabber != null) { + var binding = new Keybinding (name, settings, action, shell_key_grabber); + keybindings.add (binding); + } + + public void show_osd (HashTable parameters) requires (shell_key_grabber != null) { + try { + shell_key_grabber.show_osd (parameters); + } catch (Error e) { + warning ("Failed to show OSD: %s", e.message); + } + } + + public class NamedAction : Object, KeybindingAction { + public Gtk.Widget widget { get; construct; } + public string name { get; construct; } + public Variant? target { get; construct; } + + public NamedAction (Gtk.Widget widget, string name, Variant? target = null) { + Object (widget: widget, name: name, target: target); + } + + internal void activate () { + widget.activate_action_variant (name, target); + } + } + + public class SignalAction : Object, KeybindingAction { + public signal void activated (); + + internal void activate () { + activated (); + } + } + + public interface KeybindingAction : Object { + internal abstract void activate (); + } + + private class Keybinding : Object { + public string name { get; construct; } + public Settings settings { get; construct; } + public KeybindingAction action { get; construct; } + public ShellKeyGrabber shell_key_grabber { get; construct; } + + private uint[] action_ids = {}; + + public Keybinding (string name, Settings settings, KeybindingAction action, ShellKeyGrabber shell_key_grabber) { + Object (name: name, settings: settings, action: action, shell_key_grabber: shell_key_grabber); + } + + construct { + shell_key_grabber.accelerator_activated.connect (on_accelerator_activated); + + settings.changed.connect (on_settings_changed); + grab (); + } + + private void on_accelerator_activated (uint action_id, HashTable parameters) { + if (action_id in action_ids) { + action.activate (); + } + } + + private void on_settings_changed (string key) { + if (key == name) { + grab (); + } + } + + private void grab () { + if (action_ids.length > 0) { + ungrab (); + } + + action_ids = {}; + + var accelerators = settings.get_strv (name); + foreach (var accelerator in accelerators) { + try { + action_ids += shell_key_grabber.grab_accelerator (accelerator, NONE, NONE); + } catch (Error e) { + warning ("Failed to grab keybinding '%s' for accelerator '%s': %s", name, accelerator, e.message); + } + } + } + + private void ungrab () { + try { + shell_key_grabber.ungrab_accelerators (action_ids); + } catch (Error e) { + warning ("Failed to ungrab keybinding '%s': %s", name, e.message); + } + } + } + + [Flags] + private enum ActionMode { + NONE = 0, + NORMAL = 1 << 0, + OVERVIEW = 1 << 1, + LOCK_SCREEN = 1 << 2, + UNLOCK_SCREEN = 1 << 3, + LOGIN_SCREEN = 1 << 4, + SYSTEM_MODAL = 1 << 5, + LOOKING_GLASS = 1 << 6, + POPUP = 1 << 7, + } + + [Flags] + private enum KeyBindingFlags { + NONE = 0, + PER_WINDOW = 1 << 0, + BUILTIN = 1 << 1, + IS_REVERSED = 1 << 2, + NON_MASKABLE = 1 << 3, + IGNORE_AUTOREPEAT = 1 << 4, + } + + [DBus (name = "org.gnome.Shell")] + private interface ShellKeyGrabber : GLib.Object { + public signal void accelerator_activated (uint action, GLib.HashTable parameters_dict); + + public abstract uint grab_accelerator (string accelerator, ActionMode mode_flags, KeyBindingFlags grab_flags) throws GLib.DBusError, GLib.IOError; + public abstract bool ungrab_accelerators (uint[] actions) throws GLib.DBusError, GLib.IOError; + + [DBus (name = "ShowOSD")] + public abstract void show_osd (GLib.HashTable parameters_dict) throws GLib.DBusError, GLib.IOError; + } +} diff --git a/lib/meson.build b/lib/meson.build index 32861ba6..5efc9371 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -24,6 +24,7 @@ libwingpanel_lib = library( 'wingpanel-9', 'Indicator.vala', 'IndicatorManager.vala', + 'KeybindingHelper.vala', 'PopoverMenuItem.vala', config_header, dependencies: [libwingpanel_deps, config_vapi], From c7d8bfc42f3aa708ec18ec6c01f6abd95994fe25 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Mon, 31 Aug 2026 18:52:46 +0200 Subject: [PATCH 2/2] Application: Use KeybindingHelper --- src/Application.vala | 10 ++- src/DBus/ShellKeyGrabber.vala | 129 ---------------------------------- src/meson.build | 1 - 3 files changed, 9 insertions(+), 131 deletions(-) delete mode 100644 src/DBus/ShellKeyGrabber.vala diff --git a/src/Application.vala b/src/Application.vala index 486b81b2..11f8dc3b 100644 --- a/src/Application.vala +++ b/src/Application.vala @@ -69,7 +69,6 @@ public class Wingpanel.Application : Gtk.Application { base.startup (); Granite.init (); - ShellKeyGrabber.init (); panel_window = new PanelWindow (this); panel_window.present (); @@ -103,6 +102,15 @@ public class Wingpanel.Application : Gtk.Application { add_action (open_indicator_action); add_action (close_indicator_action); add_action (toggle_indicator_action); + + var keybinding_settings = new Settings ("io.elementary.panel.keybindings"); + var helper = KeybindingHelper.get_default (); + + var open_notifications_action = new KeybindingHelper.NamedAction ( + panel_window, "app." + TOGGLE_INDICATOR_ACTION_NAME, "messages" + ); + + helper.add_keybinding ("open-menu-notifications", keybinding_settings, open_notifications_action); } protected override void activate () { diff --git a/src/DBus/ShellKeyGrabber.vala b/src/DBus/ShellKeyGrabber.vala deleted file mode 100644 index 96af7011..00000000 --- a/src/DBus/ShellKeyGrabber.vala +++ /dev/null @@ -1,129 +0,0 @@ -/* - * SPDX-License-Identifier: GPL-3.0 - * SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io) - */ - -/** - * ActionMode: - * @NONE: block action - * @NORMAL: allow action when in window mode, e.g. when the focus is in an application window - * @OVERVIEW: allow action while the overview is active - * @LOCK_SCREEN: allow action when the screen is locked, e.g. when the screen shield is shown - * @UNLOCK_SCREEN: allow action in the unlock dialog - * @LOGIN_SCREEN: allow action in the login screen - * @SYSTEM_MODAL: allow action when a system modal dialog (e.g. authentification or session dialogs) is open - * @LOOKING_GLASS: allow action in looking glass - * @POPUP: allow action while a shell menu is open - */ - -[Flags] -public enum ActionMode { - NONE = 0, - NORMAL = 1 << 0, - OVERVIEW = 1 << 1, - LOCK_SCREEN = 1 << 2, - UNLOCK_SCREEN = 1 << 3, - LOGIN_SCREEN = 1 << 4, - SYSTEM_MODAL = 1 << 5, - LOOKING_GLASS = 1 << 6, - POPUP = 1 << 7, -} - -[Flags] -public enum Meta.KeyBindingFlags { - NONE = 0, - PER_WINDOW = 1 << 0, - BUILTIN = 1 << 1, - IS_REVERSED = 1 << 2, - NON_MASKABLE = 1 << 3, - IGNORE_AUTOREPEAT = 1 << 4, -} - -public struct Accelerator { - public string name; - public ActionMode mode_flags; - public Meta.KeyBindingFlags grab_flags; -} - -[DBus (name = "org.gnome.Shell")] -public interface Wingpanel.ShellKeyGrabber : GLib.Object { - public signal void accelerator_activated (uint action, GLib.HashTable parameters_dict); - - public abstract uint grab_accelerator (string accelerator, ActionMode mode_flags, Meta.KeyBindingFlags grab_flags) throws GLib.DBusError, GLib.IOError; - public abstract uint[] grab_accelerators (Accelerator[] accelerators) throws GLib.DBusError, GLib.IOError; - public abstract bool ungrab_accelerator (uint action) throws GLib.DBusError, GLib.IOError; - public abstract bool ungrab_accelerators (uint[] actions) throws GLib.DBusError, GLib.IOError; - - [DBus (name = "ShowOSD")] - public abstract void show_osd (GLib.HashTable parameters_dict) throws GLib.DBusError, GLib.IOError; - - private static Settings settings; - private static ShellKeyGrabber? instance; - - private static HashTable saved_action_ids; - - public static void init () { - settings = new Settings ("io.elementary.panel.keybindings"); - saved_action_ids = new HashTable (null, null); - - settings.changed.connect (() => { - ungrab_keybindings (); - setup_grabs (); - }); - - Bus.watch_name (BusType.SESSION, "org.gnome.Shell", BusNameWatcherFlags.NONE, () => on_watch.begin (), () => instance = null); - } - - private static async void on_watch () { - try { - instance = yield Bus.get_proxy (SESSION, "org.gnome.Shell", "/org/gnome/Shell"); - setup_grabs (); - } catch (Error e) { - warning ("Failed to connect to bus for keyboard shortcut grabs: %s", e.message); - } - } - - private static void setup_grabs () requires (instance != null) { - // TODO: We should make an API for this: https://github.com/elementary/wingpanel/issues/700 - var keybindings = settings.get_strv ("open-menu-notifications"); - Accelerator[] accelerators = {}; - for (int j = 0; j < keybindings.length; j++) { - accelerators += Accelerator () { - name = keybindings[j], - mode_flags = ActionMode.NONE, - grab_flags = Meta.KeyBindingFlags.NONE - }; - - try { - foreach (var id in instance.grab_accelerators (accelerators)) { - saved_action_ids[id] = "messages"; - } - } catch (Error e) { - critical ("Couldn't grab accelerators: %s", e.message); - } - } - - instance.accelerator_activated.connect (on_accelerator_activated); - } - - private static void on_accelerator_activated (uint action, GLib.HashTable parameters_dict) { - if (!(action in saved_action_ids)) { - return; - } - - GLib.Application.get_default ().activate_action ( - Wingpanel.Application.TOGGLE_INDICATOR_ACTION_NAME, - saved_action_ids[action] - ); - } - - private static void ungrab_keybindings () requires (instance != null) { - var actions = saved_action_ids.get_keys_as_array (); - - try { - instance.ungrab_accelerators (actions); - } catch (Error e) { - critical ("Couldn't ungrab accelerators: %s", e.message); - } - } -} diff --git a/src/meson.build b/src/meson.build index ccbeb1c6..876f50d4 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,7 +1,6 @@ wingpanel_files = files( 'PanelWindow.vala', 'Application.vala', - 'DBus/ShellKeyGrabber.vala', 'Services/BackgroundManager.vala', 'Services/DisplayConfig.vala', 'Services/PopoverManager.vala',