diff --git a/daemon-gtk3/DBus.vala b/daemon-gtk3/DBus.vala index b00f4148a..925626f44 100644 --- a/daemon-gtk3/DBus.vala +++ b/daemon-gtk3/DBus.vala @@ -1,13 +1,8 @@ /* - * Copyright 2024 elementary, Inc. (https://elementary.io) + * Copyright 2024-2025 elementary, Inc. (https://elementary.io) * 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,58 +14,23 @@ 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; + public signal void window_menu_action_invoked (int action); private WindowMenu? 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); - } - - private void on_gala_get (GLib.Object? obj, GLib.AsyncResult? res) { - try { - wm_proxy = Bus.get_proxy.end (res); - } catch (Error e) { - warning ("Failed to get Gala proxy: %s", e.message); - } - } - - 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); - } - } - - 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); - } - } - } - - public void show_window_menu (Gala.WindowFlags flags, int display_width, int display_height, int x, int y) throws DBusError, IOError { + public void show_window_menu (int display_width, int display_height, int x, int y, DaemonWindowMenuItem[] items) throws DBusError, IOError { if (window_menu == null) { window_menu = new WindowMenu (); - window_menu.perform_action.connect (perform_action); + window_menu.action_invoked.connect ((action) => window_menu_action_invoked (action)); } - window_menu.update (flags); + window_menu.update (items); show_menu (window_menu, display_width, display_height, x, y, true); } diff --git a/daemon-gtk3/WindowMenu.vala b/daemon-gtk3/WindowMenu.vala index d4e855471..267dbcc01 100644 --- a/daemon-gtk3/WindowMenu.vala +++ b/daemon-gtk3/WindowMenu.vala @@ -1,194 +1,45 @@ /* - * Copyright 2024 elementary, Inc. (https://elementary.io) + * Copyright 2024-2025 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 action_invoked (int action); - 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")); + public void update (DaemonWindowMenuItem[] items) { + foreach (unowned var child in get_children ()) { + remove (child); + } - move_right = new Gtk.MenuItem (); - move_right.add (move_right_accellabel); - move_right.activate.connect (() => { - perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_RIGHT); - }); + for (var i = 0; i < items.length; i++) { + var item = items[i]; - screenshot_accellabel = new Granite.AccelLabel (_("Take Screenshot")); + var accel_label = new Granite.AccelLabel (item.display_name, item.keybinding); - screenshot = new Gtk.MenuItem (); - screenshot.add (screenshot_accellabel); - screenshot.activate.connect (() => { - perform_action (Gala.ActionType.SCREENSHOT_CURRENT); - }); + if (item.type == BUTTON) { + var button = new Gtk.MenuItem () { + child = accel_label, + sensitive = item.sensitive + }; - close_accellabel = new Granite.AccelLabel (_("Close")); + var i_copy = i; + button.activate.connect (() => action_invoked (i_copy)); - close = new Gtk.MenuItem (); - close.add (close_accellabel); - close.activate.connect (() => { - perform_action (Gala.ActionType.CLOSE_CURRENT); - }); + append (button); + } else if (item.type == TOGGLE) { + var button = new Gtk.CheckMenuItem () { + child = accel_label, + sensitive = item.sensitive, + active = item.toggle_state + }; - 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); - } + var i_copy = i; + button.activate.connect (() => action_invoked (i_copy)); - 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"); + append (button); } else { - maximize_label = _("Maximize"); + append (new Gtk.SeparatorMenuItem ()); } - - 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/DBus.vala b/daemon/DBus.vala index 1dedf953c..176392946 100644 --- a/daemon/DBus.vala +++ b/daemon/DBus.vala @@ -1,13 +1,8 @@ /* - * Copyright 2024 elementary, Inc. (https://elementary.io) + * Copyright 2024-2025 elementary, Inc. (https://elementary.io) * 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,21 @@ 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; + public signal void window_menu_action_invoked (int action); private Window window; - private WindowMenu? window_menu; + private WindowMenu 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 (); @@ -81,44 +71,17 @@ public class Gala.Daemon.DBus : GLib.Object { window_menu = new WindowMenu (); window_menu.set_parent (window.child); window_menu.closed.connect (window.close); - window_menu.perform_action.connect ((type) => { + window_menu.action_invoked.connect ((action) => { + // Using Idle here because we need to wait until focus changes from the daemon window Idle.add (() => { - perform_action (type); + window_menu_action_invoked (action); return Source.REMOVE; }); }); } - private void on_gala_get (GLib.Object? obj, GLib.AsyncResult? res) { - try { - wm_proxy = Bus.get_proxy.end (res); - } catch (Error e) { - warning ("Failed to get Gala proxy: %s", e.message); - } - } - - 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); - } - } - - 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); - } - } - } - - 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, DaemonWindowMenuItem[] items) throws DBusError, IOError { + window_menu.update (items); show_menu (window_menu, display_width, display_height, x, y); } diff --git a/daemon/WindowMenu.vala b/daemon/WindowMenu.vala index 4a148405e..3279cdf80 100644 --- a/daemon/WindowMenu.vala +++ b/daemon/WindowMenu.vala @@ -1,152 +1,14 @@ /* - * Copyright 2024 elementary, Inc. (https://elementary.io) + * Copyright 2024-2025 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) { + public signal void action_invoked (int action) { 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; @@ -154,67 +16,41 @@ public class Gala.Daemon.WindowMenu : Gtk.Popover { 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"); + public void update (DaemonWindowMenuItem[] items) { + var box = new Gtk.Box (VERTICAL, 0); + for (var i = 0; i < items.length; i++) { + var item = items[i]; + + var accel_label = new Granite.AccelLabel (item.display_name, item.keybinding); + + if (item.type == BUTTON) { + var button = new Gtk.Button () { + child = accel_label, + sensitive = item.sensitive + }; + button.add_css_class (Granite.STYLE_CLASS_MENUITEM); + + var i_copy = i; + button.clicked.connect (() => action_invoked (i_copy)); + + box.append (button); + } else if (item.type == TOGGLE) { + var button = new Gtk.CheckButton () { + child = accel_label, + sensitive = item.sensitive, + active = item.toggle_state + }; + button.add_css_class (Granite.STYLE_CLASS_MENUITEM); + + var i_copy = i; + button.toggled.connect (() => action_invoked (i_copy)); + + box.append (button); } else { - maximize_label = _("Maximize"); + box.append (new Gtk.Separator (HORIZONTAL)); } - - 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]; - } + child = box; } } diff --git a/lib/CommonEnums.vala b/lib/CommonEnums.vala index 74cab00f8..49fdfb0ab 100644 --- a/lib/CommonEnums.vala +++ b/lib/CommonEnums.vala @@ -7,6 +7,20 @@ */ namespace Gala { + public enum WindowMenuItemType { + BUTTON, + TOGGLE, + SEPARATOR + } + + public struct DaemonWindowMenuItem { + WindowMenuItemType type; + bool sensitive; + bool toggle_state; + string display_name; + string keybinding; + } + public enum ActionType { NONE = 0, SHOW_MULTITASKING_VIEW, diff --git a/lib/WindowManager.vala b/lib/WindowManager.vala index 41c03517a..d81e26048 100644 --- a/lib/WindowManager.vala +++ b/lib/WindowManager.vala @@ -1,5 +1,6 @@ // // Copyright (C) 2014 Tom Beckmann +// 2025 elementary, Inc. (https://elementary.io) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -23,6 +24,17 @@ namespace Gala { public const string TOGGLE_RECORDING_ACTION = "toggle-recording-action"; } + public delegate void WindowMenuItemCallback (Meta.Window window); + + public struct WindowMenuItem { + WindowMenuItemType type; + bool sensitive; + bool toggle_state; + string display_name; + string keybinding; + WindowMenuItemCallback callback; + } + /** * Function that should return true if the given shortcut should be blocked. */ diff --git a/po/POTFILES b/po/POTFILES index b96956f18..dfa89fba0 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -100,6 +100,7 @@ src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala src/Widgets/WindowSwitcher/WindowSwitcher.vala src/WindowListener.vala src/WindowManager.vala +src/WindowMenuManager.vala src/WindowStateSaver.vala src/WindowTracker.vala src/WorkspaceManager.vala diff --git a/src/DaemonManager.vala b/src/DaemonManager.vala index 7303efca3..f41b97b3f 100644 --- a/src/DaemonManager.vala +++ b/src/DaemonManager.vala @@ -6,16 +6,20 @@ */ public class Gala.DaemonManager : GLib.Object { - private const string DAEMON_DBUS_NAME = "org.pantheon.gala.daemon"; - private const string DAEMON_DBUS_OBJECT_PATH = "/org/pantheon/gala/daemon"; - private const int SPACING = 12; - [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 signal void window_menu_action_invoked (int action); + + public abstract async void show_window_menu (int display_width, int display_height, int x, int y, DaemonWindowMenuItem[] items) throws Error; public abstract async void show_desktop_menu (int display_width, int display_height, int x, int y) throws Error; } + private const string DAEMON_DBUS_NAME = "org.pantheon.gala.daemon"; + private const string DAEMON_DBUS_OBJECT_PATH = "/org/pantheon/gala/daemon"; + private const int SPACING = 12; + + public signal void window_menu_action_invoked (int action); + public Meta.Display display { get; construct; } private ManagedClient client; @@ -83,6 +87,7 @@ public class Gala.DaemonManager : GLib.Object { Bus.get_proxy.begin (BusType.SESSION, DAEMON_DBUS_NAME, DAEMON_DBUS_OBJECT_PATH, 0, null, (obj, res) => { try { daemon_proxy = Bus.get_proxy.end (res); + daemon_proxy.window_menu_action_invoked.connect ((action) => window_menu_action_invoked (action)); } catch (Error e) { warning ("Failed to get Menu proxy: %s", e.message); } @@ -105,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, DaemonWindowMenuItem[] items) { if (daemon_proxy == null) { return; } @@ -114,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, items); } catch (Error e) { warning ("Error invoking MenuManager: %s", e.message); } diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 3a2fd7983..296281dc3 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -1,6 +1,6 @@ // // Copyright (C) 2012-2014 Tom Beckmann, Rico Tzschichholz -// 2025 elementary, Inc. +// 2025 elementary, Inc. (https://elementary.io) // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -77,6 +77,8 @@ namespace Gala { private KeyboardManager keyboard_manager; + private WindowMenuManager window_menu_manager; + public WindowTracker? window_tracker { get; private set; } private FilterManager filter_manager; @@ -190,6 +192,7 @@ namespace Gala { WindowStateSaver.init (window_tracker); window_tracker.init (display); WindowAttentionTracker.init (display); + window_menu_manager = new WindowMenuManager (this, daemon_manager); notification_stack = new NotificationStack (display); @@ -883,70 +886,12 @@ namespace Gala { } public override void show_window_menu (Meta.Window window, Meta.WindowMenuType menu, int x, int y) { - switch (menu) { - case Meta.WindowMenuType.WM: - if (NotificationStack.is_notification (window)) { - 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); - break; - case Meta.WindowMenuType.APP: - // FIXME we don't have any sort of app menus - break; + if (menu == APP) { + // FIXME we don't have any sort of app menus + return; } + + 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/WindowMenuManager.vala b/src/WindowMenuManager.vala new file mode 100644 index 000000000..b3ad19733 --- /dev/null +++ b/src/WindowMenuManager.vala @@ -0,0 +1,187 @@ +/* + * SPDX-License-Identifier: GPL-3.0-or-later + * SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io) + */ + +public class Gala.WindowMenuManager : Object { + public WindowManager wm { private get; construct; } + public DaemonManager daemon_manager { private get; construct; } + + private static GLib.Settings gala_keybinding_settings = new GLib.Settings ("io.elementary.desktop.wm.keybindings"); + private static GLib.Settings keybinding_settings = new GLib.Settings ("org.gnome.desktop.wm.keybindings"); + + private unowned Meta.Window? last_window = null; + private WindowMenuItem[] last_items; + + public WindowMenuManager (WindowManager wm, DaemonManager daemon_manager) { + Object (wm: wm, daemon_manager: daemon_manager); + } + + construct { + daemon_manager.window_menu_action_invoked.connect (handle_action_invoked); + } + + public void show_window_menu (Meta.Window window, int x, int y) { + var items = get_items_for_window (window); + if (items.length == 0) { + return; + } + + DaemonWindowMenuItem[] daemon_items = {}; + for (var i = 0; i < items.length; i++) { + daemon_items += prepare_item_for_daemon (items[i]); + } + + daemon_manager.show_window_menu.begin (x, y, daemon_items); + } + + private WindowMenuItem[] get_items_for_window (Meta.Window window) { + if (ShellClientsManager.get_instance ().is_itself_positioned (window) || + !Utils.get_window_is_normal (window) + ) { + return {}; + } + + last_window = window; + + WindowMenuItem[] items = {}; + WindowMenuItem separator = { SEPARATOR, false, false, "", "", () => {} }; + + WindowMenuItem screenshot_item = { + BUTTON, + true, + false, + _("Take Screenshot"), + get_keybinding (gala_keybinding_settings, "window-screenshot"), + (window) => wm.perform_action (Gala.ActionType.SCREENSHOT_CURRENT) + }; + items += screenshot_item; + + items += separator; + + WindowMenuItem above_item = { + TOGGLE, + true, + window.above, + _("Always on Top"), + get_keybinding (keybinding_settings, "always-on-top"), + (window) => wm.perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_TOP_CURRENT) + }; + items += above_item; + + WindowMenuItem on_all_workspaces_item = { + TOGGLE, + true, + window.on_all_workspaces, + _("Always on Visible Workspace"), + get_keybinding (keybinding_settings, "toggle-on-all-workspaces"), + (window) => wm.perform_action (Gala.ActionType.TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT) + }; + items += on_all_workspaces_item; + + unowned var workspace = window.get_workspace (); + var workspace_index = workspace.workspace_index; + WindowMenuItem move_left_item = { + BUTTON, + workspace_index != 0, + false, + _("Move to Workspace Left"), + get_keybinding (keybinding_settings, "move-to-workspace-left"), + (window) => wm.perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_LEFT) + }; + items += move_left_item; + + unowned var manager = window.display.get_workspace_manager (); + WindowMenuItem move_right_item = { + BUTTON, + workspace_index != manager.n_workspaces - 2 || Utils.get_n_windows (workspace) != 1, + false, + _("Move to Workspace Right"), + get_keybinding (keybinding_settings, "move-to-workspace-right"), + (window) => wm.perform_action (Gala.ActionType.MOVE_CURRENT_WORKSPACE_RIGHT) + }; + items += move_right_item; + + items += separator; + + WindowMenuItem move_item = { + BUTTON, + window.allows_move (), + false, + _("Move"), + get_keybinding (keybinding_settings, "begin-move"), + (window) => wm.perform_action (Gala.ActionType.START_MOVE_CURRENT) + }; + items += move_item; + + WindowMenuItem resize_item = { + BUTTON, + window.resizeable, + false, + _("Resize"), + get_keybinding (keybinding_settings, "begin-resize"), + (window) => wm.perform_action (Gala.ActionType.START_RESIZE_CURRENT) + }; + items += resize_item; + + var maximize_string = _("Maximize"); + if (window.maximized_horizontally) { + maximize_string = _("Unmaximize"); + } else if (window.maximized_vertically) { + maximize_string = _("Untile"); + } + WindowMenuItem maximize_item = { + BUTTON, + window.can_maximize (), + false, + maximize_string, + get_keybinding (keybinding_settings, "toggle-maximized"), + (window) => wm.perform_action (Gala.ActionType.MAXIMIZE_CURRENT) + }; + items += maximize_item; + + items += separator; + + WindowMenuItem hide_item = { + BUTTON, + window.can_minimize (), + false, + _("Hide"), + get_keybinding (keybinding_settings, "minimize"), + (window) => wm.perform_action (Gala.ActionType.HIDE_CURRENT) + }; + items += hide_item; + + WindowMenuItem close_item = { + BUTTON, + window.can_close (), + false, + _("Close"), + get_keybinding (keybinding_settings, "close"), + (window) => { wm.perform_action (Gala.ActionType.CLOSE_CURRENT); } + }; + items += close_item; + + last_items = items.copy (); + + return items; + } + + private string get_keybinding (GLib.Settings settings, string key) { + var strv = settings.get_strv (key); + + if (strv.length == 0) { + return ""; + } + + return strv[0]; + } + + private DaemonWindowMenuItem prepare_item_for_daemon (WindowMenuItem item) { + return { item.type, item.sensitive, item.toggle_state, item.display_name, item.keybinding }; + } + + private void handle_action_invoked (int action) requires (action < last_items.length) { + last_items[action].callback (last_window); + } +} diff --git a/src/meson.build b/src/meson.build index 724bff590..16c7a9680 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,6 +20,7 @@ gala_bin_sources = files( 'WindowDragProvider.vala', 'WindowListener.vala', 'WindowManager.vala', + 'WindowMenuManager.vala', 'WindowStateSaver.vala', 'WindowTracker.vala', 'WorkspaceManager.vala',