From 351722c5a4fded46992b725e03dc94971c5bd31f Mon Sep 17 00:00:00 2001
From: Leonhard <106322251+leolost2605@users.noreply.github.com>
Date: Sun, 18 Feb 2024 03:03:26 +0100
Subject: [PATCH] Wayland menus (#1844)
---
daemon/BackgroundMenu.vala | 57 ++++++
daemon/Main.vala | 177 +++-------------
daemon/MenuDaemon.vala | 402 +++++++------------------------------
daemon/Window.vala | 55 +++++
daemon/WindowMenu.vala | 194 ++++++++++++++++++
daemon/meson.build | 5 +-
data/gala-daemon.css | 8 +
data/gala-daemon.desktop | 14 --
data/gala.gresource.xml | 3 +
data/meson.build | 5 -
po/POTFILES | 4 +
src/DaemonManager.vala | 133 ++++++++++++
src/WindowManager.vala | 56 +-----
src/meson.build | 1 +
14 files changed, 566 insertions(+), 548 deletions(-)
create mode 100644 daemon/BackgroundMenu.vala
create mode 100644 daemon/Window.vala
create mode 100644 daemon/WindowMenu.vala
create mode 100644 data/gala-daemon.css
delete mode 100644 data/gala-daemon.desktop
create mode 100644 src/DaemonManager.vala
diff --git a/daemon/BackgroundMenu.vala b/daemon/BackgroundMenu.vala
new file mode 100644
index 00000000..5923f330
--- /dev/null
+++ b/daemon/BackgroundMenu.vala
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2024 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+public class Gala.Daemon.BackgroundMenu : Gtk.Menu {
+ public const string ACTION_GROUP_PREFIX = "background-menu";
+ public const string ACTION_PREFIX = ACTION_GROUP_PREFIX + ".";
+
+ construct {
+ var change_wallpaper = new Gtk.MenuItem.with_label (_("Change Wallpaper…")) {
+ action_name = ACTION_PREFIX + "launch-uri",
+ action_target = new Variant.string ("settings://desktop/appearance/wallpaper")
+ };
+
+ var display_settings = new Gtk.MenuItem.with_label (_("Display Settings…")) {
+ action_name = ACTION_PREFIX + "launch-uri",
+ action_target = new Variant.string ("settings://display")
+ };
+
+
+ var system_settings = new Gtk.MenuItem.with_label (_("System Settings…")) {
+ action_name = ACTION_PREFIX + "launch-uri",
+ action_target = new Variant.string ("settings://")
+ };
+
+ append (change_wallpaper);
+ append (display_settings);
+ append (new Gtk.SeparatorMenuItem ());
+ append (system_settings);
+ show_all ();
+
+ var launch_action = new SimpleAction ("launch-uri", VariantType.STRING);
+ launch_action.activate.connect (action_launch);
+
+ var action_group = new SimpleActionGroup ();
+ action_group.add_action (launch_action);
+
+ insert_action_group (ACTION_GROUP_PREFIX, action_group);
+ }
+
+ private void action_launch (SimpleAction action, Variant? variant) {
+ try {
+ AppInfo.launch_default_for_uri (variant.get_string (), null);
+ } catch (Error e) {
+ var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (
+ _("Failed to open System Settings"),
+ _("A handler for the “settings://” URI scheme must be installed."),
+ "dialog-error",
+ Gtk.ButtonsType.CLOSE
+ );
+ message_dialog.show_error_details (e.message);
+ message_dialog.present ();
+ message_dialog.response.connect (message_dialog.destroy);
+ }
+ }
+}
diff --git a/daemon/Main.vala b/daemon/Main.vala
index a8ff12e6..4c4ff6e6 100644
--- a/daemon/Main.vala
+++ b/daemon/Main.vala
@@ -1,160 +1,45 @@
-//
-// Copyright (c) 2018 elementary LLC. (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
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-//
+/*
+ * Copyright 2024 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
-namespace Gala {
- [DBus (name = "org.gnome.SessionManager")]
- public interface SessionManager : Object {
- public abstract async ObjectPath register_client (
- string app_id,
- string client_start_id
- ) throws DBusError, IOError;
+public class Gala.Daemon.Application : Gtk.Application {
+ public Application () {
+ Object (application_id: "org.pantheon.gala.daemon");
}
- [DBus (name = "org.gnome.SessionManager.ClientPrivate")]
- public interface SessionClient : Object {
- public abstract void end_session_response (bool is_ok, string reason) throws DBusError, IOError;
+ public override void startup () {
+ base.startup ();
- public signal void stop () ;
- public signal void query_end_session (uint flags);
- public signal void end_session (uint flags);
- public signal void cancel_end_session ();
- }
+ var granite_settings = Granite.Settings.get_default ();
+ var gtk_settings = Gtk.Settings.get_default ();
- public class Daemon {
- private SessionClient? sclient = null;
-
- public Daemon () {
- register.begin ((o, res)=> {
- bool success = register.end (res);
- if (!success) {
- message ("Failed to register with Session manager");
- }
- });
-
- var granite_settings = Granite.Settings.get_default ();
- var gtk_settings = Gtk.Settings.get_default ();
+ gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK;
+ granite_settings.notify["prefers-color-scheme"].connect (() => {
gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK;
-
- granite_settings.notify["prefers-color-scheme"].connect (() => {
- gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK;
- });
-
- var menu_daemon = new MenuDaemon ();
- menu_daemon.setup_dbus ();
- }
-
- public void run () {
- Gtk.main ();
- }
-
- public static async SessionClient? register_with_session (string app_id) {
- ObjectPath? path = null;
- string? msg = null;
- string? start_id = null;
-
- SessionManager? session = null;
- SessionClient? session_client = null;
-
- start_id = Environment.get_variable ("DESKTOP_AUTOSTART_ID");
- if (start_id != null) {
- Environment.unset_variable ("DESKTOP_AUTOSTART_ID");
- } else {
- start_id = "";
- warning (
- "DESKTOP_AUTOSTART_ID not set, session registration may be broken (not running via session?)"
- );
- }
-
- try {
- session = yield Bus.get_proxy (
- BusType.SESSION,
- "org.gnome.SessionManager",
- "/org/gnome/SessionManager"
- );
- } catch (Error e) {
- warning ("Unable to connect to session manager: %s", e.message);
- return null;
- }
-
- try {
- path = yield session.register_client (app_id, start_id);
- } catch (Error e) {
- msg = e.message;
- warning ("Error registering with session manager: %s", e.message);
- return null;
- }
-
- try {
- session_client = yield Bus.get_proxy (BusType.SESSION, "org.gnome.SessionManager", path);
- } catch (Error e) {
- warning ("Unable to get private sessions client proxy: %s", e.message);
- return null;
- }
-
- return session_client;
- }
-
- private async bool register () {
- sclient = yield register_with_session ("org.pantheon.gala.daemon");
-
- sclient.query_end_session.connect (() => end_session (false));
- sclient.end_session.connect (() => end_session (false));
- sclient.stop.connect (() => end_session (true));
-
- return true;
- }
-
- private void end_session (bool quit) {
- if (quit) {
- Gtk.main_quit ();
- return;
- }
-
- try {
- sclient.end_session_response (true, "");
- } catch (Error e) {
- warning ("Unable to respond to session manager: %s", e.message);
- }
- }
+ });
}
- public static int main (string[] args) {
- GLib.Intl.setlocale (LocaleCategory.ALL, "");
- GLib.Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
- GLib.Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
- GLib.Intl.textdomain (Config.GETTEXT_PACKAGE);
+ public override void activate () {
+ hold ();
+ }
- Gtk.init (ref args);
+ public override bool dbus_register (DBusConnection connection, string object_path) throws Error {
+ base.dbus_register (connection, object_path);
- var ctx = new OptionContext ("Gala Daemon");
- ctx.set_help_enabled (true);
- ctx.add_group (Gtk.get_option_group (false));
+ connection.register_object (object_path, new MenuDaemon ());
- try {
- ctx.parse (ref args);
- } catch (Error e) {
- stderr.printf ("Error: %s\n", e.message);
- return 0;
- }
-
- var daemon = new Daemon ();
- daemon.run ();
-
- return 0;
+ return true;
}
}
+
+public static int main (string[] args) {
+ GLib.Intl.setlocale (LocaleCategory.ALL, "");
+ GLib.Intl.bindtextdomain (Config.GETTEXT_PACKAGE, Config.LOCALEDIR);
+ GLib.Intl.bind_textdomain_codeset (Config.GETTEXT_PACKAGE, "UTF-8");
+ GLib.Intl.textdomain (Config.GETTEXT_PACKAGE);
+
+ var app = new Gala.Daemon.Application ();
+ return app.run ();
+}
diff --git a/daemon/MenuDaemon.vala b/daemon/MenuDaemon.vala
index 5029aab4..298807b3 100644
--- a/daemon/MenuDaemon.vala
+++ b/daemon/MenuDaemon.vala
@@ -1,367 +1,105 @@
-//
-// Copyright 2018-2020 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
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with this program. If not, see .
-//
+/*
+ * Copyright 2024 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
-namespace Gala {
+[DBus (name = "org.pantheon.gala")]
+public interface Gala.WMDBus : GLib.Object {
+ public abstract void perform_action (Gala.ActionType type) throws DBusError, IOError;
+}
+
+[DBus (name = "org.pantheon.gala.daemon")]
+public class Gala.Daemon.MenuDaemon : 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";
- [DBus (name = "org.pantheon.gala")]
- public interface WMDBus : GLib.Object {
- public abstract void perform_action (Gala.ActionType type) throws DBusError, IOError;
+ private WMDBus? wm_proxy = null;
+
+ private WindowMenu? window_menu;
+ private BackgroundMenu? background_menu;
+
+ construct {
+ Bus.watch_name (BusType.SESSION, DBUS_NAME, BusNameWatcherFlags.NONE, gala_appeared, lost_gala);
}
- [DBus (name = "org.pantheon.gala.daemon")]
- public class MenuDaemon : Object {
- // Window Menu
- private Granite.AccelLabel always_on_top_accellabel;
- private Granite.AccelLabel close_accellabel;
- private Granite.AccelLabel hide_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.Menu? window_menu = null;
- private Gtk.MenuItem hide;
- 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;
-
- // Desktop Menu
- private Gtk.Menu? desktop_menu = null;
-
- private WMDBus? wm_proxy = null;
-
- private ulong always_on_top_sid = 0U;
- private ulong on_visible_workspace_sid = 0U;
-
- private static GLib.Settings keybind_settings;
- private static GLib.Settings gala_keybind_settings;
-
- static construct {
- keybind_settings = new GLib.Settings ("org.gnome.desktop.wm.keybindings");
- gala_keybind_settings = new GLib.Settings ("org.pantheon.desktop.gala.keybindings");
+ 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);
}
+ }
- [DBus (visible = false)]
- public void setup_dbus () {
- var flags = BusNameOwnerFlags.ALLOW_REPLACEMENT | BusNameOwnerFlags.REPLACE;
- Bus.own_name (BusType.SESSION, DAEMON_DBUS_NAME, flags, on_bus_acquired, () => {}, null);
+ private void lost_gala () {
+ wm_proxy = null;
+ }
- Bus.watch_name (BusType.SESSION, DBUS_NAME, BusNameWatcherFlags.NONE, gala_appeared, lost_gala);
+ 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 on_gala_get (GLib.Object? o, GLib.AsyncResult? res) {
+ private void perform_action (Gala.ActionType type) {
+ if (wm_proxy != null) {
try {
- wm_proxy = Bus.get_proxy.end (res);
+ wm_proxy.perform_action (type);
} catch (Error e) {
- warning ("Failed to get Gala proxy: %s", e.message);
+ warning ("Failed to perform Gala action over DBus: %s", e.message);
}
}
+ }
- private void lost_gala () {
- wm_proxy = null;
+ 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);
}
- private void gala_appeared () {
- if (wm_proxy == null) {
- Bus.get_proxy.begin (BusType.SESSION, DBUS_NAME, DBUS_OBJECT_PATH, 0, null, on_gala_get);
- }
+ window_menu.update (flags);
+
+ show_menu (window_menu, display_width, display_height, x, y, true);
+ }
+
+ public void show_desktop_menu (int display_width, int display_height, int x, int y) throws DBusError, IOError {
+ if (background_menu == null) {
+ background_menu = new BackgroundMenu ();
}
- private void on_bus_acquired (DBusConnection conn) {
- try {
- conn.register_object (DAEMON_DBUS_OBJECT_PATH, this);
- } catch (Error e) {
- stderr.printf ("Error registering MenuDaemon: %s\n", e.message);
- }
- }
+ show_menu (background_menu, display_width, display_height, x, y, false);
+ }
- 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);
- }
- }
- }
+ private void show_menu (Gtk.Menu menu, int display_width, int display_height, int x, int y, bool ignore_first_release) {
+ var window = new Window (display_width, display_height);
+ window.present ();
- private void init_window_menu () {
- hide_accellabel = new Granite.AccelLabel (_("Hide"));
+ menu.attach_to_widget (window.content, null);
- hide = new Gtk.MenuItem ();
- hide.add (hide_accellabel);
- hide.activate.connect (() => {
- perform_action (Gala.ActionType.HIDE_CURRENT);
- });
+ Gdk.Rectangle rect = {
+ x,
+ y,
+ 0,
+ 0
+ };
- maximize = new Gtk.MenuItem ();
- maximize.activate.connect (() => {
- perform_action (Gala.ActionType.MAXIMIZE_CURRENT);
- });
+ menu.show_all ();
+ menu.popup_at_rect (window.get_window (), rect, NORTH, NORTH_WEST);
- move_accellabel = new Granite.AccelLabel (_("Move"));
+ menu.deactivate.connect (window.close);
- 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);
- });
-
- window_menu = new Gtk.Menu ();
- window_menu.append (screenshot);
- window_menu.append (new Gtk.SeparatorMenuItem ());
- window_menu.append (always_on_top);
- window_menu.append (on_visible_workspace);
- window_menu.append (move_left);
- window_menu.append (move_right);
- window_menu.append (new Gtk.SeparatorMenuItem ());
- window_menu.append (move);
- window_menu.append (resize);
- window_menu.append (maximize);
- window_menu.append (new Gtk.SeparatorMenuItem ());
- window_menu.append (hide);
- window_menu.append (close);
- window_menu.show_all ();
- }
-
- public void show_window_menu (Gala.WindowFlags flags, int x, int y) throws DBusError, IOError {
- if (window_menu == null) {
- init_window_menu ();
- }
-
- hide.visible = Gala.WindowFlags.CAN_HIDE in flags;
- if (hide.visible) {
- hide_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");
+ if (ignore_first_release) {
+ bool first = true;
+ menu.button_release_event.connect (() => {
+ if (first) {
+ first = false;
+ return Gdk.EVENT_STOP;
}
- 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;
- 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;
- 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];
- }
-
- // `opened` is used as workaround for https://github.com/elementary/gala/issues/1387
- var opened = false;
- Idle.add (() => {
- window_menu.popup (null, null, (m, ref px, ref py, out push_in) => {
- var scale = m.scale_factor;
- px = x / scale;
- // Move the menu 1 pixel outside of the pointer or else it closes instantly
- // on the mouse up event
- py = (y / scale) + 1;
- push_in = true;
- opened = true;
- }, Gdk.BUTTON_SECONDARY, Gdk.CURRENT_TIME);
-
- return opened ? Source.REMOVE : Source.CONTINUE;
+ return Gdk.EVENT_PROPAGATE;
});
}
-
- public void show_desktop_menu (int x, int y) throws DBusError, IOError {
- if (desktop_menu == null) {
- var change_wallpaper = new Gtk.MenuItem.with_label (_("Change Wallpaper…"));
- change_wallpaper.activate.connect (() => {
- try {
- AppInfo.launch_default_for_uri ("settings://desktop/appearance/wallpaper", null);
- } catch (Error e) {
- var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (
- "Failed to Open Wallpaper Settings",
- "Unable to open System Settings. A handler for the `settings://` URI scheme must be installed.",
- "dialog-error",
- Gtk.ButtonsType.CLOSE
- );
- message_dialog.show_error_details (e.message);
- message_dialog.run ();
- message_dialog.destroy ();
- }
- });
-
- var display_settings = new Gtk.MenuItem.with_label (_("Display Settings…"));
- display_settings.activate.connect (() => {
- try {
- AppInfo.launch_default_for_uri ("settings://display", null);
- } catch (Error e) {
- var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (
- "Failed to Open Display Settings",
- "Unable to open System Settings. A handler for the `settings://` URI scheme must be installed.",
- "dialog-warning",
- Gtk.ButtonsType.CLOSE
- );
- message_dialog.show_error_details (e.message);
- message_dialog.run ();
- message_dialog.destroy ();
- }
- });
-
- var system_settings = new Gtk.MenuItem.with_label (_("System Settings…"));
- system_settings.activate.connect (() => {
- try {
- AppInfo.launch_default_for_uri ("settings://", null);
- } catch (Error e) {
- var message_dialog = new Granite.MessageDialog.with_image_from_icon_name (
- "Failed to Open System Settings",
- "Unable to open System Settings. A handler for the `settings://` URI scheme must be installed.",
- "dialog-warning",
- Gtk.ButtonsType.CLOSE
- );
- message_dialog.show_error_details (e.message);
- message_dialog.run ();
- message_dialog.destroy ();
- }
- });
-
- desktop_menu = new Gtk.Menu ();
- desktop_menu.append (change_wallpaper);
- desktop_menu.append (display_settings);
- desktop_menu.append (new Gtk.SeparatorMenuItem ());
- desktop_menu.append (system_settings);
- desktop_menu.show_all ();
- }
-
- desktop_menu.popup (null, null, (m, ref px, ref py, out push_in) => {
- var scale = m.scale_factor;
- px = x / scale;
- // Move the menu 1 pixel outside of the pointer or else it closes instantly
- // on the mouse up event
- py = (y / scale) + 1;
- push_in = false;
- }, Gdk.BUTTON_SECONDARY, Gdk.CURRENT_TIME);
- }
}
}
diff --git a/daemon/Window.vala b/daemon/Window.vala
new file mode 100644
index 00000000..2d2894ea
--- /dev/null
+++ b/daemon/Window.vala
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2024 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ *
+ * Authored by: Leonhard Kargl
+ */
+
+public class Gala.Daemon.Window : Gtk.Window {
+ static construct {
+ var app_provider = new Gtk.CssProvider ();
+ app_provider.load_from_resource ("io/elementary/desktop/gala-daemon/gala-daemon.css");
+ Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), app_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ public Gtk.Box content { get; construct; }
+
+ public Window (int width, int height) {
+ Object (
+ default_width: width,
+ default_height: height
+ );
+ }
+
+ class construct {
+ set_css_name ("daemon-window");
+ }
+
+ construct {
+ decorated = false;
+ resizable = false;
+ deletable = false;
+ can_focus = false;
+ input_shape_combine_region (null);
+ accept_focus = false;
+ skip_taskbar_hint = true;
+ skip_pager_hint = true;
+ type_hint = Gdk.WindowTypeHint.DOCK;
+ set_keep_above (true);
+
+ child = content = new Gtk.Box (HORIZONTAL, 0) {
+ hexpand = true,
+ vexpand = true
+ };
+
+ set_visual (get_screen ().get_rgba_visual ());
+
+ show_all ();
+ move (0, 0);
+
+ button_press_event.connect (() => {
+ close ();
+ return Gdk.EVENT_STOP;
+ });
+ }
+}
diff --git a/daemon/WindowMenu.vala b/daemon/WindowMenu.vala
new file mode 100644
index 00000000..1aa6a700
--- /dev/null
+++ b/daemon/WindowMenu.vala
@@ -0,0 +1,194 @@
+/*
+ * 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 ("org.pantheon.desktop.gala.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;
+ 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;
+ 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 00e96d41..9f95b110 100644
--- a/daemon/meson.build
+++ b/daemon/meson.build
@@ -1,6 +1,9 @@
gala_daemon_sources = files(
'Main.vala',
- 'MenuDaemon.vala'
+ 'MenuDaemon.vala',
+ 'Window.vala',
+ 'WindowMenu.vala',
+ 'BackgroundMenu.vala'
)
gala_daemon_bin = executable(
diff --git a/data/gala-daemon.css b/data/gala-daemon.css
new file mode 100644
index 00000000..8332358b
--- /dev/null
+++ b/data/gala-daemon.css
@@ -0,0 +1,8 @@
+/*
+ * Copyright 2024 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+daemon-window {
+ background-color: transparent;
+}
diff --git a/data/gala-daemon.desktop b/data/gala-daemon.desktop
deleted file mode 100644
index 58df1496..00000000
--- a/data/gala-daemon.desktop
+++ /dev/null
@@ -1,14 +0,0 @@
-[Desktop Entry]
-Type=Application
-Name=Gala Background Services
-Comment=Gala Background Services
-Exec=gala-daemon
-Terminal=false
-Categories=GNOME;GTK;System;Core;
-NoDisplay=true
-StartupNotify=true
-X-GNOME-AutoRestart=true
-X-GNOME-Autostart-Notify=true
-X-GNOME-Autostart-Phase=Desktop
-X-GNOME-HiddenUnderSystemd=true
-OnlyShowIn=Pantheon
diff --git a/data/gala.gresource.xml b/data/gala.gresource.xml
index 727942e9..2959b72a 100644
--- a/data/gala.gresource.xml
+++ b/data/gala.gresource.xml
@@ -25,4 +25,7 @@
shaders/colorblindness-correction.vert
shaders/monochrome.vert
+
+ gala-daemon.css
+
diff --git a/data/meson.build b/data/meson.build
index 1efb96f3..d4ec7849 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -4,11 +4,6 @@ install_data(
rename: 'org.pantheon.desktop.gala.gschema.xml'
)
-install_data(
- 'gala-daemon.desktop',
- install_dir: join_paths(get_option('sysconfdir'), 'xdg', 'autostart')
-)
-
i18n.merge_file(
input: 'gala.metainfo.xml.in',
output: meson.project_name() + '.metainfo.xml',
diff --git a/po/POTFILES b/po/POTFILES
index 5b356007..68b5e80f 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -1,7 +1,11 @@
src/Dialogs.vala
src/ScreenshotManager.vala
src/WindowManager.vala
+daemon/BackgroundMenu.vala
+daemon/Main.vala
daemon/MenuDaemon.vala
+daemon/Window.vala
+daemon/WindowMenu.vala
data/gala.metainfo.xml.in
data/gala-multitaskingview.desktop.in
data/gala-other.desktop.in
diff --git a/src/DaemonManager.vala b/src/DaemonManager.vala
new file mode 100644
index 00000000..6adc9738
--- /dev/null
+++ b/src/DaemonManager.vala
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2024 elementary, Inc. (https://elementary.io)
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ *
+ * Authored by: Leonhard Kargl
+ */
+
+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 abstract async void show_desktop_menu (int display_width, int display_height, int x, int y) throws Error;
+ }
+
+ public Meta.Display display { get; construct; }
+
+ private Meta.WaylandClient daemon_client;
+ private Daemon? daemon_proxy = null;
+
+ public DaemonManager (Meta.Display display) {
+ Object (display: display);
+ }
+
+ construct {
+ Bus.watch_name (BusType.SESSION, DAEMON_DBUS_NAME, BusNameWatcherFlags.NONE, daemon_appeared, lost_daemon);
+
+ if (Meta.Util.is_wayland_compositor ()) {
+ start_wayland.begin ();
+
+ display.window_created.connect ((window) => {
+ if (daemon_client.owns_window (window)) {
+ window.shown.connect (handle_daemon_window);
+ }
+ });
+ } else {
+ start_x.begin ();
+ }
+ }
+
+ private async void start_wayland () {
+ var subprocess_launcher = new GLib.SubprocessLauncher (NONE);
+ try {
+#if HAS_MUTTER44
+ daemon_client = new Meta.WaylandClient (display.get_context (), subprocess_launcher);
+#else
+ daemon_client = new Meta.WaylandClient (subprocess_launcher);
+#endif
+ string[] args = {"gala-daemon"};
+ var subprocess = daemon_client.spawnv (display, args);
+
+ yield subprocess.wait_async ();
+
+ //Restart the daemon if it crashes
+ Timeout.add_seconds (1, () => {
+ start_wayland.begin ();
+ return Source.REMOVE;
+ });
+ } catch (Error e) {
+ warning ("Failed to create dock client: %s", e.message);
+ return;
+ }
+ }
+
+ private async void start_x () {
+ try {
+ var subprocess = new Subprocess (NONE, "gala-daemon");
+ yield subprocess.wait_async ();
+
+ //Restart the daemon if it crashes
+ Timeout.add_seconds (1, () => {
+ start_x.begin ();
+ return Source.REMOVE;
+ });
+ } catch (Error e) {
+ warning ("Failed to create daemon subprocess with x: %s", e.message);
+ }
+ }
+
+ private void handle_daemon_window (Meta.Window window) {
+ window.move_frame (false, 0, 0);
+ window.make_above ();
+ }
+
+ private void lost_daemon () {
+ daemon_proxy = null;
+ }
+
+ private void daemon_appeared () {
+ if (daemon_proxy == null) {
+ 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);
+ } catch (Error e) {
+ warning ("Failed to get Menu proxy: %s", e.message);
+ }
+ });
+ }
+ }
+
+ public async void show_background_menu (int x, int y) {
+ if (daemon_proxy == null) {
+ return;
+ }
+
+ int width, height;
+ display.get_size (out width, out height);
+
+ try {
+ yield daemon_proxy.show_desktop_menu (width, height, x, y);
+ } catch (Error e) {
+ warning ("Error invoking MenuManager: %s", e.message);
+ }
+ }
+
+ public async void show_window_menu (WindowFlags flags, int x, int y) {
+ if (daemon_proxy == null) {
+ return;
+ }
+
+ int width, height;
+ display.get_size (out width, out height);
+
+ try {
+ yield daemon_proxy.show_window_menu (flags, width, height, x, y);
+ } catch (Error e) {
+ warning ("Error invoking MenuManager: %s", e.message);
+ }
+ }
+}
diff --git a/src/WindowManager.vala b/src/WindowManager.vala
index a5683efe..49720167 100644
--- a/src/WindowManager.vala
+++ b/src/WindowManager.vala
@@ -16,15 +16,6 @@
//
namespace Gala {
- private const string DAEMON_DBUS_NAME = "org.pantheon.gala.daemon";
- private const string DAEMON_DBUS_OBJECT_PATH = "/org/pantheon/gala/daemon";
-
- [DBus (name = "org.pantheon.gala.daemon")]
- public interface Daemon: GLib.Object {
- public abstract async void show_window_menu (WindowFlags flags, int x, int y) throws Error;
- public abstract async void show_desktop_menu (int x, int y) throws Error;
- }
-
public class WindowManagerGala : Meta.Plugin, WindowManager {
/**
* {@inheritDoc}
@@ -90,7 +81,7 @@ namespace Gala {
private Meta.Window? moving; //place for the window that is being moved over
- private Daemon? daemon_proxy = null;
+ private DaemonManager daemon_manager;
private NotificationStack notification_stack;
@@ -147,9 +138,10 @@ namespace Gala {
}
public override void start () {
+ daemon_manager = new DaemonManager (get_display ());
+
show_stage ();
- Bus.watch_name (BusType.SESSION, DAEMON_DBUS_NAME, BusNameWatcherFlags.NONE, daemon_appeared, lost_daemon);
AccessDialog.watch_portal ();
unowned Meta.Display display = get_display ();
@@ -158,22 +150,6 @@ namespace Gala {
});
}
- private void lost_daemon () {
- daemon_proxy = null;
- }
-
- private void daemon_appeared () {
- if (daemon_proxy == null) {
- 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);
- } catch (Error e) {
- warning ("Failed to get Menu proxy: %s", e.message);
- }
- });
- }
- }
-
private bool show_stage () {
unowned Meta.Display display = get_display ();
@@ -240,7 +216,7 @@ namespace Gala {
ui_group.add_child (window_group);
background_group = new BackgroundContainer (this);
- ((BackgroundContainer)background_group).show_background_menu.connect (on_show_background_menu);
+ ((BackgroundContainer)background_group).show_background_menu.connect (daemon_manager.show_background_menu);
window_group.add_child (background_group);
window_group.set_child_below_sibling (background_group, null);
@@ -397,20 +373,6 @@ namespace Gala {
} catch (Error e) { warning (e.message); }
}
- private void on_show_background_menu (int x, int y) {
- if (daemon_proxy == null) {
- return;
- }
-
- daemon_proxy.show_desktop_menu.begin (x, y, (obj, res) => {
- try {
- ((Daemon) obj).show_desktop_menu.end (res);
- } catch (Error e) {
- message ("Error invoking MenuManager: %s", e.message);
- }
- });
- }
-
private void on_monitors_changed () {
screen_shield.expand_to_screen_size ();
}
@@ -1048,7 +1010,7 @@ 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 (daemon_proxy == null || window.get_window_type () == Meta.WindowType.NOTIFICATION) {
+ if (window.get_window_type () == Meta.WindowType.NOTIFICATION) {
return;
}
@@ -1083,13 +1045,7 @@ namespace Gala {
if (window.can_close ())
flags |= WindowFlags.CAN_CLOSE;
- daemon_proxy.show_window_menu.begin (flags, x, y, (obj, res) => {
- try {
- ((Daemon) obj).show_window_menu.end (res);
- } catch (Error e) {
- message ("Error invoking MenuManager: %s", e.message);
- }
- });
+ daemon_manager.show_window_menu.begin (flags, x, y);
break;
case Meta.WindowMenuType.APP:
// FIXME we don't have any sort of app menus
diff --git a/src/meson.build b/src/meson.build
index c1d56ef1..31f66de3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,6 +1,7 @@
gala_bin_sources = files(
'DBus.vala',
'DBusAccelerator.vala',
+ 'DaemonManager.vala',
'DesktopIntegration.vala',
'Dialogs.vala',
'InternalUtils.vala',