From 1505760a935433e4acca14380f13efa28a680557 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Tue, 11 Aug 2026 23:57:08 +0200 Subject: [PATCH 1/2] Use DBusMenuModel and DBusActionGroup for the window menu --- daemon-gtk3/DBus.vala | 56 ++------ daemon-gtk3/WindowMenu.vala | 194 ---------------------------- daemon-gtk3/meson.build | 2 - daemon/DBus.vala | 70 +++------- daemon/WindowMenu.vala | 220 -------------------------------- daemon/meson.build | 2 - lib/CommonEnums.vala | 18 --- lib/meson.build | 7 +- src/Misc/DaemonManager.vala | 6 +- src/Misc/WindowMenuManager.vala | 201 +++++++++++++++++++++++++++++ src/WindowManager.vala | 57 +-------- src/meson.build | 1 + 12 files changed, 244 insertions(+), 590 deletions(-) delete mode 100644 daemon-gtk3/WindowMenu.vala delete mode 100644 daemon/WindowMenu.vala create mode 100644 src/Misc/WindowMenuManager.vala diff --git a/daemon-gtk3/DBus.vala b/daemon-gtk3/DBus.vala index b00f4148a..05714681a 100644 --- a/daemon-gtk3/DBus.vala +++ b/daemon-gtk3/DBus.vala @@ -3,11 +3,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -[DBus (name = "org.pantheon.gala")] -public interface Gala.WMDBus : GLib.Object { - public abstract void perform_action (Gala.ActionType type) throws DBusError, IOError; -} - public struct Gala.Daemon.MonitorLabelInfo { public int monitor; public string label; @@ -19,59 +14,32 @@ public struct Gala.Daemon.MonitorLabelInfo { [DBus (name = "org.pantheon.gala.daemon")] public class Gala.Daemon.DBus : GLib.Object { - private const string DBUS_NAME = "org.pantheon.gala"; - private const string DBUS_OBJECT_PATH = "/org/pantheon/gala"; - - private const string DAEMON_DBUS_NAME = "org.pantheon.gala.daemon"; - private const string DAEMON_DBUS_OBJECT_PATH = "/org/pantheon/gala/daemon"; - - private WMDBus? wm_proxy = null; - - private WindowMenu? window_menu; + private Gtk.Menu? window_menu; private BackgroundMenu? background_menu; private List monitor_labels = new List (); construct { - Bus.watch_name (BusType.SESSION, DBUS_NAME, BusNameWatcherFlags.NONE, gala_appeared, lost_gala); + load_window_menu.begin (); } - private void on_gala_get (GLib.Object? obj, GLib.AsyncResult? res) { + private async void load_window_menu () { + DBusConnection connection; try { - wm_proxy = Bus.get_proxy.end (res); + connection = yield Bus.get (SESSION, null); } catch (Error e) { - warning ("Failed to get Gala proxy: %s", e.message); + warning ("Failed to get DBus connection: %s", e.message); + return; } - } - - private void lost_gala () { - wm_proxy = null; - } - private void gala_appeared () { - if (wm_proxy == null) { - Bus.get_proxy.begin (BusType.SESSION, DBUS_NAME, DBUS_OBJECT_PATH, 0, null, on_gala_get); - } - } + var menu_model = DBusMenuModel.get (connection, "org.pantheon.gala", "/io/elementary/gala/window_menu"); + var action_group = DBusActionGroup.get (connection, "org.pantheon.gala", "/io/elementary/gala/window_menu"); - private void perform_action (Gala.ActionType type) { - if (wm_proxy != null) { - try { - wm_proxy.perform_action (type); - } catch (Error e) { - warning ("Failed to perform Gala action over DBus: %s", e.message); - } - } + window_menu = new Gtk.Menu.from_model (menu_model); + window_menu.insert_action_group ("window-menu", action_group); } - public void show_window_menu (Gala.WindowFlags flags, int display_width, int display_height, int x, int y) throws DBusError, IOError { - if (window_menu == null) { - window_menu = new WindowMenu (); - window_menu.perform_action.connect (perform_action); - } - - window_menu.update (flags); - + public void show_window_menu (int display_width, int display_height, int x, int y) throws DBusError, IOError { show_menu (window_menu, display_width, display_height, x, y, true); } diff --git a/daemon-gtk3/WindowMenu.vala b/daemon-gtk3/WindowMenu.vala deleted file mode 100644 index d4e855471..000000000 --- a/daemon-gtk3/WindowMenu.vala +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2024 elementary, Inc. (https://elementary.io) - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -public class Gala.Daemon.WindowMenu : Gtk.Menu { - private static GLib.Settings gala_keybind_settings = new GLib.Settings ("io.elementary.desktop.wm.keybindings"); - private static GLib.Settings keybind_settings = new GLib.Settings ("org.gnome.desktop.wm.keybindings"); - - public signal void perform_action (Gala.ActionType type); - - private Granite.AccelLabel always_on_top_accellabel; - private Granite.AccelLabel close_accellabel; - private Granite.AccelLabel minimize_accellabel; - private Granite.AccelLabel move_accellabel; - private Granite.AccelLabel move_left_accellabel; - private Granite.AccelLabel move_right_accellabel; - private Granite.AccelLabel on_visible_workspace_accellabel; - private Granite.AccelLabel resize_accellabel; - private Granite.AccelLabel screenshot_accellabel; - private Gtk.MenuItem minimize; - private Gtk.MenuItem maximize; - private Gtk.MenuItem move; - private Gtk.MenuItem resize; - private Gtk.CheckMenuItem always_on_top; - private Gtk.CheckMenuItem on_visible_workspace; - private Gtk.MenuItem move_left; - private Gtk.MenuItem move_right; - private Gtk.MenuItem close; - private Gtk.MenuItem screenshot; - - private ulong always_on_top_sid = 0U; - private ulong on_visible_workspace_sid = 0U; - - construct { - minimize_accellabel = new Granite.AccelLabel (_("Hide")); - - minimize = new Gtk.MenuItem (); - minimize.add (minimize_accellabel); - minimize.activate.connect (() => { - perform_action (Gala.ActionType.HIDE_CURRENT); - }); - - maximize = new Gtk.MenuItem (); - maximize.activate.connect (() => { - perform_action (Gala.ActionType.MAXIMIZE_CURRENT); - }); - - move_accellabel = new Granite.AccelLabel (_("Move")); - - move = new Gtk.MenuItem (); - move.add (move_accellabel); - move.activate.connect (() => { - perform_action (Gala.ActionType.START_MOVE_CURRENT); - }); - - resize_accellabel = new Granite.AccelLabel (_("Resize")); - - resize = new Gtk.MenuItem (); - resize.add (resize_accellabel); - resize.activate.connect (() => { - perform_action (Gala.ActionType.START_RESIZE_CURRENT); - }); - - always_on_top_accellabel = new Granite.AccelLabel (_("Always on Top")); - - always_on_top = new Gtk.CheckMenuItem (); - always_on_top.add (always_on_top_accellabel); - always_on_top_sid = always_on_top.activate.connect (() => { - perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_TOP_CURRENT); - }); - - on_visible_workspace_accellabel = new Granite.AccelLabel (_("Always on Visible Workspace")); - - on_visible_workspace = new Gtk.CheckMenuItem (); - on_visible_workspace.add (on_visible_workspace_accellabel); - on_visible_workspace_sid = on_visible_workspace.activate.connect (() => { - perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT); - }); - - move_left_accellabel = new Granite.AccelLabel (_("Move to Workspace Left")); - - move_left = new Gtk.MenuItem (); - move_left.add (move_left_accellabel); - move_left.activate.connect (() => { - perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_LEFT); - }); - - move_right_accellabel = new Granite.AccelLabel (_("Move to Workspace Right")); - - move_right = new Gtk.MenuItem (); - move_right.add (move_right_accellabel); - move_right.activate.connect (() => { - perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_RIGHT); - }); - - screenshot_accellabel = new Granite.AccelLabel (_("Take Screenshot")); - - screenshot = new Gtk.MenuItem (); - screenshot.add (screenshot_accellabel); - screenshot.activate.connect (() => { - perform_action (Gala.ActionType.SCREENSHOT_CURRENT); - }); - - close_accellabel = new Granite.AccelLabel (_("Close")); - - close = new Gtk.MenuItem (); - close.add (close_accellabel); - close.activate.connect (() => { - perform_action (Gala.ActionType.CLOSE_CURRENT); - }); - - append (screenshot); - append (new Gtk.SeparatorMenuItem ()); - append (always_on_top); - append (on_visible_workspace); - append (move_left); - append (move_right); - append (new Gtk.SeparatorMenuItem ()); - append (move); - append (resize); - append (maximize); - append (new Gtk.SeparatorMenuItem ()); - append (minimize); - append (close); - } - - public void update (Gala.WindowFlags flags) { - minimize.visible = Gala.WindowFlags.CAN_HIDE in flags; - if (minimize.visible) { - minimize_accellabel.accel_string = keybind_settings.get_strv ("minimize")[0]; - } - - maximize.visible = Gala.WindowFlags.CAN_MAXIMIZE in flags; - if (maximize.visible) { - unowned string maximize_label; - if (Gala.WindowFlags.IS_MAXIMIZED in flags) { - maximize_label = (Gala.WindowFlags.IS_TILED in flags) ? _("Untile") : _("Unmaximize"); - } else { - maximize_label = _("Maximize"); - } - - maximize.get_child ().destroy (); - maximize.add ( - new Granite.AccelLabel ( - maximize_label, - keybind_settings.get_strv ("toggle-maximized")[0] - ) - ); - } - - - move.visible = Gala.WindowFlags.ALLOWS_MOVE in flags; - if (move.visible) { - move_accellabel.accel_string = keybind_settings.get_strv ("begin-move")[0]; - } - - resize.visible = Gala.WindowFlags.ALLOWS_RESIZE in flags; - if (resize.visible) { - resize_accellabel.accel_string = keybind_settings.get_strv ("begin-resize")[0]; - } - - // Setting active causes signal fires on activate so - // we temporarily block those signals from emissions - SignalHandler.block (always_on_top, always_on_top_sid); - SignalHandler.block (on_visible_workspace, on_visible_workspace_sid); - - always_on_top.active = Gala.WindowFlags.ALWAYS_ON_TOP in flags; - always_on_top_accellabel.accel_string = keybind_settings.get_strv ("always-on-top")[0]; - - on_visible_workspace.active = Gala.WindowFlags.ON_ALL_WORKSPACES in flags; - on_visible_workspace_accellabel.accel_string = keybind_settings.get_strv ("toggle-on-all-workspaces")[0]; - - SignalHandler.unblock (always_on_top, always_on_top_sid); - SignalHandler.unblock (on_visible_workspace, on_visible_workspace_sid); - - move_right.sensitive = !on_visible_workspace.active && Gala.WindowFlags.ALLOWS_MOVE_RIGHT in flags; - if (move_right.sensitive) { - move_right_accellabel.accel_string = keybind_settings.get_strv ("move-to-workspace-right")[0]; - } - - move_left.sensitive = !on_visible_workspace.active && Gala.WindowFlags.ALLOWS_MOVE_LEFT in flags; - if (move_left.sensitive) { - move_left_accellabel.accel_string = keybind_settings.get_strv ("move-to-workspace-left")[0]; - } - - screenshot_accellabel.accel_string = gala_keybind_settings.get_strv ("window-screenshot")[0]; - - close.visible = Gala.WindowFlags.CAN_CLOSE in flags; - if (close.visible) { - close_accellabel.accel_string = keybind_settings.get_strv ("close")[0]; - } - } -} diff --git a/daemon-gtk3/meson.build b/daemon-gtk3/meson.build index 28c8821bf..cd249db08 100644 --- a/daemon-gtk3/meson.build +++ b/daemon-gtk3/meson.build @@ -3,7 +3,6 @@ gala_daemon_sources = files( 'DBus.vala', 'MonitorLabel.vala', 'Window.vala', - 'WindowMenu.vala', 'BackgroundMenu.vala', ) @@ -13,7 +12,6 @@ hdy_dep = dependency('libhandy-1') executable( 'gala-daemon-gtk3', gala_daemon_sources, - gala_common_enums, config_header, gala_resources, dependencies: [granite6_dep, hdy_dep], diff --git a/daemon/DBus.vala b/daemon/DBus.vala index 1dedf953c..9b3fc868c 100644 --- a/daemon/DBus.vala +++ b/daemon/DBus.vala @@ -3,11 +3,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -[DBus (name = "org.pantheon.gala")] -public interface Gala.WMDBus : GLib.Object { - public abstract void perform_action (Gala.ActionType type) throws DBusError, IOError; -} - public struct Gala.Daemon.MonitorLabelInfo { public int monitor; public string label; @@ -19,26 +14,16 @@ public struct Gala.Daemon.MonitorLabelInfo { [DBus (name = "org.pantheon.gala.daemon")] public class Gala.Daemon.DBus : GLib.Object { - private const string DBUS_NAME = "org.pantheon.gala"; - private const string DBUS_OBJECT_PATH = "/org/pantheon/gala"; - - private const string DAEMON_DBUS_NAME = "org.pantheon.gala.daemon"; - private const string DAEMON_DBUS_OBJECT_PATH = "/org/pantheon/gala/daemon"; - private const string BG_MENU_ACTION_GROUP_PREFIX = "background-menu"; private const string BG_MENU_ACTION_PREFIX = BG_MENU_ACTION_GROUP_PREFIX + "."; - private WMDBus? wm_proxy = null; - private Window window; - private WindowMenu? window_menu; + private Gtk.PopoverMenu? window_menu; private Gtk.PopoverMenu background_menu; private List monitor_labels = new List (); construct { - Bus.watch_name (BusType.SESSION, DBUS_NAME, BusNameWatcherFlags.NONE, gala_appeared, lost_gala); - window = new Window (); var background_menu_top_section = new Menu (); @@ -78,48 +63,35 @@ public class Gala.Daemon.DBus : GLib.Object { background_menu.insert_action_group (BG_MENU_ACTION_GROUP_PREFIX, action_group); - window_menu = new WindowMenu (); - window_menu.set_parent (window.child); - window_menu.closed.connect (window.close); - window_menu.perform_action.connect ((type) => { - Idle.add (() => { - perform_action (type); - return Source.REMOVE; - }); - }); + load_window_menu.begin (); } - private void on_gala_get (GLib.Object? obj, GLib.AsyncResult? res) { + private async void load_window_menu () { + DBusConnection connection; try { - wm_proxy = Bus.get_proxy.end (res); + connection = yield Bus.get (SESSION, null); } catch (Error e) { - warning ("Failed to get Gala proxy: %s", e.message); + warning ("Failed to get DBus connection: %s", e.message); + return; } - } - - private void lost_gala () { - wm_proxy = null; - } - private void gala_appeared () { - if (wm_proxy == null) { - Bus.get_proxy.begin (BusType.SESSION, DBUS_NAME, DBUS_OBJECT_PATH, 0, null, on_gala_get); - } - } + var menu_model = DBusMenuModel.get (connection, "io.elementary.gala", "/io/elementary/gala/WindowMenu"); + var action_group = DBusActionGroup.get (connection, "io.elementary.gala", "/io/elementary/gala/WindowMenu"); - private void perform_action (Gala.ActionType type) { - if (wm_proxy != null) { - try { - wm_proxy.perform_action (type); - } catch (Error e) { - warning ("Failed to perform Gala action over DBus: %s", e.message); - } - } + window_menu = new Gtk.PopoverMenu.from_model (menu_model) { + halign = START, + position = BOTTOM, + autohide = false, + has_arrow = false, + }; + window_menu.insert_action_group ("window-menu", action_group); + window_menu.set_parent (window.child); + window_menu.closed.connect (window.close); } - public void show_window_menu (Gala.WindowFlags flags, int display_width, int display_height, int x, int y) throws DBusError, IOError { - window_menu.update (flags); - + public void show_window_menu ( + int display_width, int display_height, int x, int y + ) throws DBusError, IOError requires (window_menu != null) { show_menu (window_menu, display_width, display_height, x, y); } diff --git a/daemon/WindowMenu.vala b/daemon/WindowMenu.vala deleted file mode 100644 index 4a148405e..000000000 --- a/daemon/WindowMenu.vala +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright 2024 elementary, Inc. (https://elementary.io) - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -public class Gala.Daemon.WindowMenu : Gtk.Popover { - private static GLib.Settings gala_keybind_settings = new GLib.Settings ("io.elementary.desktop.wm.keybindings"); - private static GLib.Settings keybind_settings = new GLib.Settings ("org.gnome.desktop.wm.keybindings"); - - public signal void perform_action (Gala.ActionType type) { - popdown (); - } - - private Granite.AccelLabel always_on_top_accellabel; - private Granite.AccelLabel close_accellabel; - private Granite.AccelLabel minimize_accellabel; - private Granite.AccelLabel move_accellabel; - private Granite.AccelLabel move_left_accellabel; - private Granite.AccelLabel move_right_accellabel; - private Granite.AccelLabel on_visible_workspace_accellabel; - private Granite.AccelLabel resize_accellabel; - private Granite.AccelLabel screenshot_accellabel; - private Gtk.Button minimize; - private Gtk.Button maximize; - private Gtk.Button move; - private Gtk.Button resize; - private Gtk.CheckButton always_on_top; - private Gtk.CheckButton on_visible_workspace; - private Gtk.Button move_left; - private Gtk.Button move_right; - private Gtk.Button close; - private Gtk.Button screenshot; - - private ulong always_on_top_sid = 0U; - private ulong on_visible_workspace_sid = 0U; - - construct { - minimize_accellabel = new Granite.AccelLabel (_("Hide")); - - minimize = new Gtk.Button () { - child = minimize_accellabel - }; - minimize.add_css_class (Granite.STYLE_CLASS_MENUITEM); - minimize.clicked.connect (() => { - perform_action (Gala.ActionType.HIDE_CURRENT); - }); - - maximize = new Gtk.Button (); - maximize.add_css_class (Granite.STYLE_CLASS_MENUITEM); - maximize.clicked.connect (() => { - perform_action (Gala.ActionType.MAXIMIZE_CURRENT); - }); - - move_accellabel = new Granite.AccelLabel (_("Move")); - - move = new Gtk.Button () { - child = move_accellabel - }; - move.add_css_class (Granite.STYLE_CLASS_MENUITEM); - move.clicked.connect (() => { - perform_action (Gala.ActionType.START_MOVE_CURRENT); - }); - - resize_accellabel = new Granite.AccelLabel (_("Resize")); - - resize = new Gtk.Button () { - child = resize_accellabel - }; - resize.add_css_class (Granite.STYLE_CLASS_MENUITEM); - resize.clicked.connect (() => { - perform_action (Gala.ActionType.START_RESIZE_CURRENT); - }); - - always_on_top_accellabel = new Granite.AccelLabel (_("Always on Top")); - - always_on_top = new Gtk.CheckButton () { - child = always_on_top_accellabel - }; - always_on_top.add_css_class (Granite.STYLE_CLASS_MENUITEM); - always_on_top_sid = always_on_top.toggled.connect (() => { - perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_TOP_CURRENT); - }); - - on_visible_workspace_accellabel = new Granite.AccelLabel (_("Always on Visible Workspace")); - - on_visible_workspace = new Gtk.CheckButton () { - child = on_visible_workspace_accellabel - }; - on_visible_workspace.add_css_class (Granite.STYLE_CLASS_MENUITEM); - on_visible_workspace_sid = on_visible_workspace.toggled.connect (() => { - perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT); - }); - - move_left_accellabel = new Granite.AccelLabel (_("Move to Workspace Left")); - - move_left = new Gtk.Button () { - child = move_left_accellabel - }; - move_left.add_css_class (Granite.STYLE_CLASS_MENUITEM); - move_left.clicked.connect (() => { - perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_LEFT); - }); - - move_right_accellabel = new Granite.AccelLabel (_("Move to Workspace Right")); - - move_right = new Gtk.Button () { - child = move_right_accellabel - }; - move_right.add_css_class (Granite.STYLE_CLASS_MENUITEM); - move_right.clicked.connect (() => { - perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_RIGHT); - }); - - screenshot_accellabel = new Granite.AccelLabel (_("Take Screenshot")); - - screenshot = new Gtk.Button () { - child = screenshot_accellabel - }; - screenshot.add_css_class (Granite.STYLE_CLASS_MENUITEM); - screenshot.clicked.connect (() => { - perform_action (Gala.ActionType.SCREENSHOT_CURRENT); - }); - - close_accellabel = new Granite.AccelLabel (_("Close")); - - close = new Gtk.Button () { - child = close_accellabel - }; - close.add_css_class (Granite.STYLE_CLASS_MENUITEM); - close.clicked.connect (() => { - perform_action (Gala.ActionType.CLOSE_CURRENT); - }); - - var box = new Gtk.Box (VERTICAL, 0); - box.append (screenshot); - box.append (new Gtk.Separator (HORIZONTAL)); - box.append (always_on_top); - box.append (on_visible_workspace); - box.append (move_left); - box.append (move_right); - box.append (new Gtk.Separator (HORIZONTAL)); - box.append (move); - box.append (resize); - box.append (maximize); - box.append (new Gtk.Separator (HORIZONTAL)); - box.append (minimize); - box.append (close); - - child = box; - halign = START; - position = BOTTOM; - autohide = false; - has_arrow = false; - add_css_class (Granite.STYLE_CLASS_MENU); - } - - public void update (Gala.WindowFlags flags) { - minimize.visible = Gala.WindowFlags.CAN_HIDE in flags; - if (minimize.visible) { - minimize_accellabel.accel_string = keybind_settings.get_strv ("minimize")[0]; - } - - maximize.visible = Gala.WindowFlags.CAN_MAXIMIZE in flags; - if (maximize.visible) { - unowned string maximize_label; - if (Gala.WindowFlags.IS_MAXIMIZED in flags) { - maximize_label = (Gala.WindowFlags.IS_TILED in flags) ? _("Untile") : _("Unmaximize"); - } else { - maximize_label = _("Maximize"); - } - - maximize.get_child ().destroy (); - maximize.child = new Granite.AccelLabel ( - maximize_label, - keybind_settings.get_strv ("toggle-maximized")[0] - ); - } - - move.visible = Gala.WindowFlags.ALLOWS_MOVE in flags; - if (move.visible) { - move_accellabel.accel_string = keybind_settings.get_strv ("begin-move")[0]; - } - - resize.visible = Gala.WindowFlags.ALLOWS_RESIZE in flags; - if (resize.visible) { - resize_accellabel.accel_string = keybind_settings.get_strv ("begin-resize")[0]; - } - - // Setting active causes signal fires on clicked so - // we temporarily block those signals from emissions - SignalHandler.block (always_on_top, always_on_top_sid); - SignalHandler.block (on_visible_workspace, on_visible_workspace_sid); - - always_on_top.active = Gala.WindowFlags.ALWAYS_ON_TOP in flags; - always_on_top_accellabel.accel_string = keybind_settings.get_strv ("always-on-top")[0]; - - on_visible_workspace.active = Gala.WindowFlags.ON_ALL_WORKSPACES in flags; - on_visible_workspace_accellabel.accel_string = keybind_settings.get_strv ("toggle-on-all-workspaces")[0]; - - SignalHandler.unblock (always_on_top, always_on_top_sid); - SignalHandler.unblock (on_visible_workspace, on_visible_workspace_sid); - - move_right.sensitive = !on_visible_workspace.active && Gala.WindowFlags.ALLOWS_MOVE_RIGHT in flags; - if (move_right.sensitive) { - move_right_accellabel.accel_string = keybind_settings.get_strv ("move-to-workspace-right")[0]; - } - - move_left.sensitive = !on_visible_workspace.active && Gala.WindowFlags.ALLOWS_MOVE_LEFT in flags; - if (move_left.sensitive) { - move_left_accellabel.accel_string = keybind_settings.get_strv ("move-to-workspace-left")[0]; - } - - screenshot_accellabel.accel_string = gala_keybind_settings.get_strv ("window-screenshot")[0]; - - close.visible = Gala.WindowFlags.CAN_CLOSE in flags; - if (close.visible) { - close_accellabel.accel_string = keybind_settings.get_strv ("close")[0]; - } - } -} diff --git a/daemon/meson.build b/daemon/meson.build index 8b66b1c39..fd3633d6b 100644 --- a/daemon/meson.build +++ b/daemon/meson.build @@ -4,7 +4,6 @@ gala_daemon_sources = files( 'DBus.vala', 'MonitorLabel.vala', 'Window.vala', - 'WindowMenu.vala', 'IBus' / 'Candidate.vala', 'IBus' / 'CandidateArea.vala', 'IBus' / 'CandidateBox.vala', @@ -32,7 +31,6 @@ json_dep = dependency('json-glib-1.0') executable( 'gala-daemon', gala_daemon_sources, - gala_common_enums, config_header, gala_resources, osk_resources, diff --git a/lib/CommonEnums.vala b/lib/CommonEnums.vala index 74cab00f8..242ff61c6 100644 --- a/lib/CommonEnums.vala +++ b/lib/CommonEnums.vala @@ -2,8 +2,6 @@ * SPDX-License-Identifier: GPL-3.0-or-later * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) * 2014 Tom Beckmann - * - * Note: These enums are shared with the daemon */ namespace Gala { @@ -28,20 +26,4 @@ namespace Gala { CLOSE_CURRENT, SCREENSHOT_CURRENT } - - [Flags] - public enum WindowFlags { - NONE = 0, - CAN_HIDE, - CAN_MAXIMIZE, - IS_MAXIMIZED, - ALLOWS_MOVE, - ALLOWS_RESIZE, - ALWAYS_ON_TOP, - ON_ALL_WORKSPACES, - CAN_CLOSE, - IS_TILED, - ALLOWS_MOVE_LEFT, - ALLOWS_MOVE_RIGHT - } } diff --git a/lib/meson.build b/lib/meson.build index 538f2af23..137badfdb 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -1,9 +1,6 @@ -gala_common_enums = files( - 'CommonEnums.vala', -) - gala_lib_sources = files( 'BackgroundManager.vala', + 'CommonEnums.vala', 'Constants.vala', 'DragDropAction.vala', 'Plugin.vala', @@ -49,7 +46,7 @@ gala_lib_sources = files( 'Widgets/Text.vala', 'Widgets/Widget.vala', 'Widgets/WindowIcon.vala' -) + gala_common_enums +) gala_resources = gnome.compile_resources( 'gala-resources', diff --git a/src/Misc/DaemonManager.vala b/src/Misc/DaemonManager.vala index e30621b1a..ff22aec81 100644 --- a/src/Misc/DaemonManager.vala +++ b/src/Misc/DaemonManager.vala @@ -12,7 +12,7 @@ public class Gala.DaemonManager : GLib.Object { [DBus (name = "org.pantheon.gala.daemon")] public interface Daemon: GLib.Object { - public abstract async void show_window_menu (WindowFlags flags, int width, int height, int x, int y) throws Error; + public abstract async void show_window_menu (int width, int height, int x, int y) throws Error; public abstract async void show_desktop_menu (int display_width, int display_height, int x, int y) throws Error; } @@ -110,7 +110,7 @@ public class Gala.DaemonManager : GLib.Object { } } - public async void show_window_menu (WindowFlags flags, int x, int y) { + public async void show_window_menu (int x, int y) { if (daemon_proxy == null) { return; } @@ -119,7 +119,7 @@ public class Gala.DaemonManager : GLib.Object { display.get_size (out width, out height); try { - yield daemon_proxy.show_window_menu (flags, width, height, x, y); + yield daemon_proxy.show_window_menu (width, height, x, y); } catch (Error e) { warning ("Error invoking MenuManager: %s", e.message); } diff --git a/src/Misc/WindowMenuManager.vala b/src/Misc/WindowMenuManager.vala new file mode 100644 index 000000000..8427d016e --- /dev/null +++ b/src/Misc/WindowMenuManager.vala @@ -0,0 +1,201 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.WindowMenuManager : Object { + /* This is the name of the action group the daemon uses */ + private const string ACTION_PREFIX = "window-menu."; + + private const string ACTION_SCREENSHOT = "screenshot"; + private const string ACTION_ALWAYS_ON_TOP = "always-on-top"; + private const string ACTION_ALWAYS_ON_VISIBLE_WORKSPACE = "always-on-visible-workspace"; + private const string ACTION_MOVE_LEFT = "move-left"; + private const string ACTION_MOVE_RIGHT = "move-right"; + private const string ACTION_MOVE = "move"; + private const string ACTION_RESIZE = "resize"; + private const string ACTION_MAXIMIZE = "maximize"; + private const string ACTION_HIDE = "hide"; + private const string ACTION_CLOSE = "close"; + + private const ActionEntry[] ACTION_ENTRIES = { + { ACTION_SCREENSHOT, on_screenshot }, + { ACTION_ALWAYS_ON_TOP, on_always_on_top, null, "false" }, + { ACTION_ALWAYS_ON_VISIBLE_WORKSPACE, on_always_on_visible_workspace, null, "false" }, + { ACTION_MOVE_LEFT, on_move_left }, + { ACTION_MOVE_RIGHT, on_move_right }, + { ACTION_MOVE, on_move }, + { ACTION_RESIZE, on_resize }, + { ACTION_MAXIMIZE, on_maximize }, + { ACTION_HIDE, on_hide }, + { ACTION_CLOSE, on_close } + }; + + public WindowManager wm { private get; construct; } + public DaemonManager daemon_manager { private get; construct; } + + private static GLib.Settings gala_keybind_settings = new GLib.Settings ("io.elementary.desktop.wm.keybindings"); + private static GLib.Settings keybind_settings = new GLib.Settings ("org.gnome.desktop.wm.keybindings"); + + private SimpleActionGroup action_group; + private Menu menu; + + private Meta.Window? current_window; + + public WindowMenuManager (WindowManager wm, DaemonManager daemon_manager) { + Object (wm: wm, daemon_manager: daemon_manager); + } + + construct { + action_group = new SimpleActionGroup (); + action_group.add_action_entries (ACTION_ENTRIES, this); + + menu = new Menu (); + + Bus.own_name (SESSION, "io.elementary.gala", NONE, null, on_name_acquired, null); + } + + private void on_name_acquired (DBusConnection connection, string name) { + try { + connection.export_action_group ("/io/elementary/gala/WindowMenu", action_group); + connection.export_menu_model ("/io/elementary/gala/WindowMenu", menu); + } catch (Error e) { + warning ("Failed to export window menu model and action group: %s", e.message); + } + } + + public void show_window_menu (Meta.Window window, int x, int y) { + current_window = window; + + action_group.change_action_state (ACTION_ALWAYS_ON_TOP, window.above); + action_group.change_action_state (ACTION_ALWAYS_ON_VISIBLE_WORKSPACE, window.on_all_workspaces); + + var window_ws = window.get_workspace (); + var window_ws_index = window_ws.index (); + var n_ws = window.display.get_workspace_manager ().n_workspaces; + var can_move_left = window_ws_index != 0; + var can_move_right = window_ws_index != n_ws - 2 && Utils.get_n_windows (window_ws) != 1; + ((SimpleAction) action_group.lookup_action (ACTION_MOVE_LEFT)).set_enabled (can_move_left); + ((SimpleAction) action_group.lookup_action (ACTION_MOVE_RIGHT)).set_enabled (can_move_right); + + menu.remove_all (); + + var screenshot_item = new MenuItem (_("Take Screenshot"), ACTION_PREFIX + ACTION_SCREENSHOT); + set_accel_attribute (screenshot_item, gala_keybind_settings, "screenshot"); + + var screenshot_section = new Menu (); + screenshot_section.append_item (screenshot_item); + + var always_on_top_item = new MenuItem (_("Always on Top"), ACTION_PREFIX + ACTION_ALWAYS_ON_TOP); + set_accel_attribute (always_on_top_item, keybind_settings, "always-on-top"); + var always_on_visible_workspace_item = new MenuItem (_("Always on Visible Workspace"), ACTION_PREFIX + ACTION_ALWAYS_ON_VISIBLE_WORKSPACE); + set_accel_attribute (always_on_visible_workspace_item, keybind_settings, "toggle-on-all-workspaces"); + var move_left_item = new MenuItem (_("Move to Workspace Left"), ACTION_PREFIX + ACTION_MOVE_LEFT); + set_accel_attribute (move_left_item, keybind_settings, "move-to-workspace-left"); + var move_right_item = new MenuItem (_("Move to Workspace Right"), ACTION_PREFIX + ACTION_MOVE_RIGHT); + set_accel_attribute (move_right_item, keybind_settings, "move-to-workspace-right"); + + var workspace_section = new Menu (); + workspace_section.append_item (always_on_top_item); + workspace_section.append_item (always_on_visible_workspace_item); + workspace_section.append_item (move_left_item); + workspace_section.append_item (move_right_item); + + var move_resize_section = new Menu (); + + if (window.allows_move ()) { + var move_item = new MenuItem (_("Move"), ACTION_PREFIX + ACTION_MOVE); + set_accel_attribute (move_item, keybind_settings, "begin-move"); + move_resize_section.append_item (move_item); + } + + if (window.allows_resize ()) { + var resize_item = new MenuItem (_("Resize"), ACTION_PREFIX + ACTION_RESIZE); + set_accel_attribute (resize_item, keybind_settings, "begin-resize"); + move_resize_section.append_item (resize_item); + } + + if (window.can_maximize ()) { + var maximize_label = _("Maximize"); + + if (window.maximized_vertically || window.maximized_horizontally) { + var is_tiled = window.maximized_vertically && !window.maximized_horizontally; + maximize_label = is_tiled ? _("Untile") : _("Unmaximize"); + } + + var maximize_item = new MenuItem (maximize_label, ACTION_PREFIX + ACTION_MAXIMIZE); + set_accel_attribute (maximize_item, keybind_settings, "toggle-maximized"); + move_resize_section.append_item (maximize_item); + } + + var close_section = new Menu (); + + if (window.can_minimize ()) { + var minimize_item = new MenuItem (_("Minimize"), ACTION_PREFIX + ACTION_HIDE); + set_accel_attribute (minimize_item, keybind_settings, "minimize"); + close_section.append_item (minimize_item); + } + + if (window.can_close ()) { + var close_item = new MenuItem (_("Close"), ACTION_PREFIX + ACTION_CLOSE); + set_accel_attribute (close_item, keybind_settings, "close"); + close_section.append_item (close_item); + } + + menu.append_section (null, screenshot_section); + menu.append_section (null, workspace_section); + menu.append_section (null, move_resize_section); + menu.append_section (null, close_section); + + daemon_manager.show_window_menu.begin (x, y); + } + + private static void set_accel_attribute (MenuItem item, Settings settings, string key) { + var accels = settings.get_strv (key); + if (accels.length > 0) { + item.set_attribute ("accel", "s", accels[0]); + } + } + + private void on_screenshot (SimpleAction action, Variant? parameters) { + wm.perform_action (SCREENSHOT_CURRENT); + } + + private void on_always_on_top (SimpleAction action, Variant? parameters) { + wm.perform_action (TOGGLE_ALWAYS_ON_TOP_CURRENT); + } + + private void on_always_on_visible_workspace (SimpleAction action, Variant? parameters) { + wm.perform_action (TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT); + } + + private void on_move_left (SimpleAction action, Variant? parameters) { + wm.perform_action (MOVE_CURRENT_WORKSPACE_LEFT); + } + + private void on_move_right (SimpleAction action, Variant? parameters) { + wm.perform_action (MOVE_CURRENT_WORKSPACE_RIGHT); + } + + private void on_move (SimpleAction action, Variant? parameters) { + wm.perform_action (START_MOVE_CURRENT); + } + + private void on_resize (SimpleAction action, Variant? parameters) { + wm.perform_action (START_RESIZE_CURRENT); + } + + private void on_maximize (SimpleAction action, Variant? parameters) { + wm.perform_action (MAXIMIZE_CURRENT); + } + + private void on_hide (SimpleAction action, Variant? parameters) { + wm.perform_action (HIDE_CURRENT); + } + + private void on_close (SimpleAction action, Variant? parameters) { + wm.perform_action (CLOSE_CURRENT); + } +} diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 9d5fd2105..ed455cba1 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -79,6 +79,8 @@ namespace Gala { private DaemonManager daemon_manager; + private WindowMenuManager window_menu_manager; + private NotificationStack notification_stack; private LockScreenManager lock_screen_manager; @@ -118,6 +120,7 @@ namespace Gala { ShellClientsManager.init (this, input_method); BlurManager.init (this); daemon_manager = new DaemonManager (get_display ()); + window_menu_manager = new WindowMenuManager (this, daemon_manager); show_stage (); @@ -774,59 +777,7 @@ namespace Gala { return; } - WindowFlags flags = WindowFlags.NONE; - if (window.can_minimize ()) - flags |= WindowFlags.CAN_HIDE; - - if (window.can_maximize ()) - flags |= WindowFlags.CAN_MAXIMIZE; - -#if HAS_MUTTER49 - if (window.is_maximized ()) - flags |= WindowFlags.IS_MAXIMIZED; - - if (window.maximized_vertically && !window.maximized_horizontally) - flags |= WindowFlags.IS_TILED; -#else - var maximize_flags = window.get_maximized (); - if (maximize_flags > 0) { - flags |= WindowFlags.IS_MAXIMIZED; - - if (Meta.MaximizeFlags.VERTICAL in maximize_flags && !(Meta.MaximizeFlags.HORIZONTAL in maximize_flags)) { - flags |= WindowFlags.IS_TILED; - } - } -#endif - - if (window.allows_move ()) - flags |= WindowFlags.ALLOWS_MOVE; - - if (window.allows_resize ()) - flags |= WindowFlags.ALLOWS_RESIZE; - - if (window.is_above ()) - flags |= WindowFlags.ALWAYS_ON_TOP; - - if (window.on_all_workspaces) - flags |= WindowFlags.ON_ALL_WORKSPACES; - - if (window.can_close ()) - flags |= WindowFlags.CAN_CLOSE; - - unowned var workspace = window.get_workspace (); - if (workspace != null) { - unowned var manager = window.display.get_workspace_manager (); - var workspace_index = workspace.workspace_index; - if (workspace_index != 0) { - flags |= WindowFlags.ALLOWS_MOVE_LEFT; - } - - if (workspace_index != manager.n_workspaces - 2 || Utils.get_n_windows (workspace) != 1) { - flags |= WindowFlags.ALLOWS_MOVE_RIGHT; - } - } - - daemon_manager.show_window_menu.begin (flags, x, y); + window_menu_manager.show_window_menu (window, x, y); } public override void show_tile_preview (Meta.Window window, Mtk.Rectangle tile_rect, int tile_monitor_number) { diff --git a/src/meson.build b/src/meson.build index 19653a5d3..6b941f249 100644 --- a/src/meson.build +++ b/src/meson.build @@ -40,6 +40,7 @@ gala_bin_sources = files( 'Misc/WindowAttentionTracker.vala', 'Misc/WindowDragProvider.vala', 'Misc/WindowListener.vala', + 'Misc/WindowMenuManager.vala', 'Misc/WindowMover.vala', 'Misc/WindowStateSaver.vala', 'Misc/WindowTracker.vala', From 20035ad0ebad2794775005acd2dca8e23d16583f Mon Sep 17 00:00:00 2001 From: Leonhard Date: Wed, 12 Aug 2026 17:22:36 +0200 Subject: [PATCH 2/2] Update POTFILES --- po/POTFILES | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/po/POTFILES b/po/POTFILES index 349c497bb..584195233 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -9,14 +9,12 @@ daemon/DisplayConfig.vala daemon/Main.vala daemon/MonitorLabel.vala daemon/Window.vala -daemon/WindowMenu.vala daemon-gtk3/BackgroundMenu.vala daemon-gtk3/DBus.vala daemon-gtk3/Main.vala daemon-gtk3/MonitorLabel.vala daemon-gtk3/Window.vala -daemon-gtk3/WindowMenu.vala data/gala-multitaskingview.desktop.in data/gala-other.desktop.in @@ -111,6 +109,7 @@ src/Misc/SessionManager.vala src/Misc/WindowAttentionTracker.vala src/Misc/WindowDragProvider.vala src/Misc/WindowListener.vala +src/Misc/WindowMenuManager.vala src/Misc/WindowMover.vala src/Misc/WindowStateSaver.vala src/Misc/WindowTracker.vala