From 3a81898095ce1dfaa794bba05c53d7050982dd00 Mon Sep 17 00:00:00 2001 From: Leonhard <106322251+leolost2605@users.noreply.github.com> Date: Fri, 5 Jul 2024 20:14:35 +0200 Subject: [PATCH] Daemon: Port to GTK4 (#1964) --- {daemon => daemon-gtk3}/BackgroundMenu.vala | 0 daemon-gtk3/DBus.vala | 133 ++++++++++++++ daemon-gtk3/Main.vala | 49 +++++ daemon-gtk3/MonitorLabel.vala | 62 +++++++ daemon-gtk3/Window.vala | 50 +++++ daemon-gtk3/WindowMenu.vala | 194 ++++++++++++++++++++ daemon-gtk3/meson.build | 19 ++ daemon/DBus.vala | 148 +++++++++++---- daemon/Main.vala | 7 +- daemon/MonitorLabel.vala | 25 +-- daemon/Window.vala | 36 ++-- daemon/WindowMenu.vala | 148 +++++++++------ daemon/meson.build | 4 +- data/gala-daemon.css | 2 +- meson.build | 7 +- src/DaemonManager.vala | 3 +- 16 files changed, 740 insertions(+), 147 deletions(-) rename {daemon => daemon-gtk3}/BackgroundMenu.vala (100%) create mode 100644 daemon-gtk3/DBus.vala create mode 100644 daemon-gtk3/Main.vala create mode 100644 daemon-gtk3/MonitorLabel.vala create mode 100644 daemon-gtk3/Window.vala create mode 100644 daemon-gtk3/WindowMenu.vala create mode 100644 daemon-gtk3/meson.build diff --git a/daemon/BackgroundMenu.vala b/daemon-gtk3/BackgroundMenu.vala similarity index 100% rename from daemon/BackgroundMenu.vala rename to daemon-gtk3/BackgroundMenu.vala diff --git a/daemon-gtk3/DBus.vala b/daemon-gtk3/DBus.vala new file mode 100644 index 00000000..41a2e0c3 --- /dev/null +++ b/daemon-gtk3/DBus.vala @@ -0,0 +1,133 @@ +/* + * Copyright 2024 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; + public string background_color; + public string text_color; + public int x; + public int y; +} + +[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 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 { + if (window_menu == null) { + window_menu = new WindowMenu (); + window_menu.perform_action.connect (perform_action); + } + + 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 (); + } + + show_menu (background_menu, display_width, display_height, x, y, false); + } + + 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 (); + + menu.attach_to_widget (window.content, null); + + Gdk.Rectangle rect = { + x, + y, + 0, + 0 + }; + + menu.show_all (); + menu.popup_at_rect (window.get_window (), rect, NORTH, NORTH_WEST); + + menu.deactivate.connect (window.close); + + if (ignore_first_release) { + bool first = true; + menu.button_release_event.connect (() => { + if (first) { + first = false; + return Gdk.EVENT_STOP; + } + + return Gdk.EVENT_PROPAGATE; + }); + } + } + + public void show_monitor_labels (MonitorLabelInfo[] label_infos) throws GLib.DBusError, GLib.IOError { + hide_monitor_labels (); + + monitor_labels = new List (); + foreach (var info in label_infos) { + var label = new MonitorLabel (info); + monitor_labels.append (label); + label.present (); + } + } + + public void hide_monitor_labels () throws GLib.DBusError, GLib.IOError { + foreach (var monitor_label in monitor_labels) { + monitor_label.close (); + } + } +} diff --git a/daemon-gtk3/Main.vala b/daemon-gtk3/Main.vala new file mode 100644 index 00000000..d2ffaaac --- /dev/null +++ b/daemon-gtk3/Main.vala @@ -0,0 +1,49 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +public class Gala.Daemon.Application : Gtk.Application { + public Application () { + Object (application_id: "org.pantheon.gala.daemon"); + } + + public override void startup () { + base.startup (); + + 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; + }); + + 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 override void activate () { + hold (); + } + + public override bool dbus_register (DBusConnection connection, string object_path) throws Error { + base.dbus_register (connection, object_path); + + connection.register_object (object_path, new DBus ()); + + 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-gtk3/MonitorLabel.vala b/daemon-gtk3/MonitorLabel.vala new file mode 100644 index 00000000..0ece5723 --- /dev/null +++ b/daemon-gtk3/MonitorLabel.vala @@ -0,0 +1,62 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-3.0-or-later + */ + + public class Gala.Daemon.MonitorLabel : Hdy.Window { + private const int SPACING = 12; + private const string COLORED_STYLE_CSS = """ + .%s { + background-color: alpha(%s, 0.8); + color: %s; + } + """; + + public MonitorLabelInfo info { get; construct; } + + public MonitorLabel (MonitorLabelInfo info) { + Object (info: info); + } + + construct { + child = new Gtk.Label (info.label); + + title = "LABEL-%i".printf (info.monitor); + + input_shape_combine_region (null); + accept_focus = false; + decorated = false; + resizable = false; + deletable = false; + can_focus = false; + skip_taskbar_hint = true; + skip_pager_hint = true; + type_hint = Gdk.WindowTypeHint.TOOLTIP; + set_keep_above (true); + + stick (); + + var scale_factor = get_style_context ().get_scale (); + move ( + (int) (info.x / scale_factor) + SPACING, + (int) (info.y / scale_factor) + SPACING + ); + + var provider = new Gtk.CssProvider (); + try { + provider.load_from_data (COLORED_STYLE_CSS.printf (title, info.background_color, info.text_color)); + get_style_context ().add_class (title); + get_style_context ().add_class ("monitor-label"); + + Gtk.StyleContext.add_provider_for_screen ( + Gdk.Screen.get_default (), + provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + ); + } catch (Error e) { + warning ("Failed to load CSS: %s", e.message); + } + + show_all (); + } +} diff --git a/daemon-gtk3/Window.vala b/daemon-gtk3/Window.vala new file mode 100644 index 00000000..3e864139 --- /dev/null +++ b/daemon-gtk3/Window.vala @@ -0,0 +1,50 @@ +/* + * 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 { + 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); + + title = "MODAL"; + 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-gtk3/WindowMenu.vala b/daemon-gtk3/WindowMenu.vala new file mode 100644 index 00000000..1aa6a700 --- /dev/null +++ b/daemon-gtk3/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-gtk3/meson.build b/daemon-gtk3/meson.build new file mode 100644 index 00000000..c4a53cce --- /dev/null +++ b/daemon-gtk3/meson.build @@ -0,0 +1,19 @@ +gala_daemon_sources = files( + 'Main.vala', + 'DBus.vala', + 'MonitorLabel.vala', + 'Window.vala', + 'WindowMenu.vala', + 'BackgroundMenu.vala', +) + +granite_dep_old = dependency('granite') +hdy_dep = dependency('libhandy-1') + +gala_daemon_bin = executable( + 'gala-daemon-gtk3', + gala_daemon_sources, + dependencies: [gala_dep, gala_base_dep, granite_dep_old, hdy_dep], + include_directories: config_inc_dir, + install: true, +) diff --git a/daemon/DBus.vala b/daemon/DBus.vala index 41a2e0c3..75f996f7 100644 --- a/daemon/DBus.vala +++ b/daemon/DBus.vala @@ -3,6 +3,42 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ +public enum Gala.ActionType { + NONE = 0, + SHOW_WORKSPACE_VIEW, + MAXIMIZE_CURRENT, + HIDE_CURRENT, + OPEN_LAUNCHER, + CUSTOM_COMMAND, + WINDOW_OVERVIEW, + WINDOW_OVERVIEW_ALL, + SWITCH_TO_WORKSPACE_PREVIOUS, + SWITCH_TO_WORKSPACE_NEXT, + SWITCH_TO_WORKSPACE_LAST, + START_MOVE_CURRENT, + START_RESIZE_CURRENT, + TOGGLE_ALWAYS_ON_TOP_CURRENT, + TOGGLE_ALWAYS_ON_VISIBLE_WORKSPACE_CURRENT, + MOVE_CURRENT_WORKSPACE_LEFT, + MOVE_CURRENT_WORKSPACE_RIGHT, + CLOSE_CURRENT, + SCREENSHOT_CURRENT +} + +[Flags] +public enum Gala.WindowFlags { + NONE = 0, + CAN_HIDE, + CAN_MAXIMIZE, + IS_MAXIMIZED, + ALLOWS_MOVE, + ALLOWS_RESIZE, + ALWAYS_ON_TOP, + ON_ALL_WORKSPACES, + CAN_CLOSE, + IS_TILED +} + [DBus (name = "org.pantheon.gala")] public interface Gala.WMDBus : GLib.Object { public abstract void perform_action (Gala.ActionType type) throws DBusError, IOError; @@ -25,15 +61,68 @@ public class Gala.Daemon.DBus : 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 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 BackgroundMenu? background_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 (); + background_menu_top_section.append ( + _("Change Wallpaper…"), + Action.print_detailed_name (BG_MENU_ACTION_PREFIX + "launch-uri", "settings://desktop/appearance/wallpaper") + ); + background_menu_top_section.append ( + _("Display Settings…"), + Action.print_detailed_name (BG_MENU_ACTION_PREFIX + "launch-uri", "settings://display") + ); + + var background_menu_bottom_section = new Menu (); + background_menu_bottom_section.append ( + _("System Settings…"), + Action.print_detailed_name (BG_MENU_ACTION_PREFIX + "launch-uri", "settings://") + ); + + var background_menu_model = new Menu (); + background_menu_model.append_section (null, background_menu_top_section); + background_menu_model.append_section (null, background_menu_bottom_section); + + background_menu = new Gtk.PopoverMenu.from_model (background_menu_model) { + halign = START, + position = BOTTOM, + autohide = false, + has_arrow = false + }; + background_menu.set_parent (window.child); + background_menu.closed.connect (window.close); + + 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); + + 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; + }); + }); } private void on_gala_get (GLib.Object? obj, GLib.AsyncResult? res) { @@ -65,53 +154,32 @@ public class Gala.Daemon.DBus : GLib.Object { } 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); - show_menu (window_menu, display_width, display_height, x, y, true); + show_menu (window_menu, display_width, display_height, x, y); } 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 (); - } - - show_menu (background_menu, display_width, display_height, x, y, false); + show_menu (background_menu, display_width, display_height, x, y); } - 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); + private void show_menu (Gtk.Popover menu, int display_width, int display_height, int x, int y) { + window.default_width = display_width; + window.default_height = display_height; window.present (); - menu.attach_to_widget (window.content, null); - Gdk.Rectangle rect = { x, y, 0, 0 }; + menu.pointing_to = rect; - menu.show_all (); - menu.popup_at_rect (window.get_window (), rect, NORTH, NORTH_WEST); - - menu.deactivate.connect (window.close); - - if (ignore_first_release) { - bool first = true; - menu.button_release_event.connect (() => { - if (first) { - first = false; - return Gdk.EVENT_STOP; - } - - return Gdk.EVENT_PROPAGATE; - }); - } + Idle.add (() => { + menu.popup (); + return Source.REMOVE; + }); } public void show_monitor_labels (MonitorLabelInfo[] label_infos) throws GLib.DBusError, GLib.IOError { @@ -130,4 +198,20 @@ public class Gala.Daemon.DBus : GLib.Object { monitor_label.close (); } } + + private static 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 d2ffaaac..bab80909 100644 --- a/daemon/Main.vala +++ b/daemon/Main.vala @@ -21,8 +21,8 @@ public class Gala.Daemon.Application : Gtk.Application { }); 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); + app_provider.load_from_resource ("/io/elementary/desktop/gala-daemon/gala-daemon.css"); + Gtk.StyleContext.add_provider_for_display (Gdk.Display.get_default (), app_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); } public override void activate () { @@ -32,6 +32,9 @@ public class Gala.Daemon.Application : Gtk.Application { public override bool dbus_register (DBusConnection connection, string object_path) throws Error { base.dbus_register (connection, object_path); + // We are using gtk in the DBus () constructor so we need to init it early + Gtk.init (); + connection.register_object (object_path, new DBus ()); return true; diff --git a/daemon/MonitorLabel.vala b/daemon/MonitorLabel.vala index 0ece5723..c3a19f4b 100644 --- a/daemon/MonitorLabel.vala +++ b/daemon/MonitorLabel.vala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: LGPL-3.0-or-later */ - public class Gala.Daemon.MonitorLabel : Hdy.Window { + public class Gala.Daemon.MonitorLabel : Gtk.Window { private const int SPACING = 12; private const string COLORED_STYLE_CSS = """ .%s { @@ -23,33 +23,20 @@ title = "LABEL-%i".printf (info.monitor); - input_shape_combine_region (null); - accept_focus = false; decorated = false; resizable = false; deletable = false; can_focus = false; - skip_taskbar_hint = true; - skip_pager_hint = true; - type_hint = Gdk.WindowTypeHint.TOOLTIP; - set_keep_above (true); - - stick (); - - var scale_factor = get_style_context ().get_scale (); - move ( - (int) (info.x / scale_factor) + SPACING, - (int) (info.y / scale_factor) + SPACING - ); + titlebar = new Gtk.Grid (); var provider = new Gtk.CssProvider (); try { - provider.load_from_data (COLORED_STYLE_CSS.printf (title, info.background_color, info.text_color)); + provider.load_from_string (COLORED_STYLE_CSS.printf (title, info.background_color, info.text_color)); get_style_context ().add_class (title); get_style_context ().add_class ("monitor-label"); - Gtk.StyleContext.add_provider_for_screen ( - Gdk.Screen.get_default (), + Gtk.StyleContext.add_provider_for_display ( + Gdk.Display.get_default (), provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION ); @@ -57,6 +44,6 @@ warning ("Failed to load CSS: %s", e.message); } - show_all (); + present (); } } diff --git a/daemon/Window.vala b/daemon/Window.vala index 3e864139..d6d0881b 100644 --- a/daemon/Window.vala +++ b/daemon/Window.vala @@ -6,15 +6,6 @@ */ public class Gala.Daemon.Window : Gtk.Window { - 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"); } @@ -24,27 +15,22 @@ public class Gala.Daemon.Window : Gtk.Window { 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); - title = "MODAL"; - child = content = new Gtk.Box (HORIZONTAL, 0) { + hide_on_close = true; + child = new Gtk.Box (HORIZONTAL, 0) { hexpand = true, vexpand = true }; - set_visual (get_screen ().get_rgba_visual ()); + var controller = new Gtk.GestureClick (); + child.add_controller (controller); + controller.released.connect (close); + } - show_all (); - move (0, 0); - - button_press_event.connect (() => { - close (); - return Gdk.EVENT_STOP; - }); + public override void snapshot (Gtk.Snapshot snapshot) { + base.snapshot (snapshot); + // We need to append something here otherwise GTK thinks the snapshot is empty and therefore doesn't + // render anything and therefore doesn't present a window which is needed for our popovers + snapshot.append_color ({0, 0, 0, 0}, {{0, 0}, {0, 0}}); } } diff --git a/daemon/WindowMenu.vala b/daemon/WindowMenu.vala index 1aa6a700..9980bf26 100644 --- a/daemon/WindowMenu.vala +++ b/daemon/WindowMenu.vala @@ -3,11 +3,13 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ -public class Gala.Daemon.WindowMenu : Gtk.Menu { +public class Gala.Daemon.WindowMenu : Gtk.Popover { 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); + public signal void perform_action (Gala.ActionType type) { + popdown (); + } private Granite.AccelLabel always_on_top_accellabel; private Granite.AccelLabel close_accellabel; @@ -18,16 +20,16 @@ public class Gala.Daemon.WindowMenu : Gtk.Menu { 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 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; @@ -35,94 +37,121 @@ public class Gala.Daemon.WindowMenu : Gtk.Menu { construct { minimize_accellabel = new Granite.AccelLabel (_("Hide")); - minimize = new Gtk.MenuItem (); - minimize.add (minimize_accellabel); - minimize.activate.connect (() => { + 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.MenuItem (); - maximize.activate.connect (() => { + 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.MenuItem (); - move.add (move_accellabel); - move.activate.connect (() => { + 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.MenuItem (); - resize.add (resize_accellabel); - resize.activate.connect (() => { + 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.CheckMenuItem (); - always_on_top.add (always_on_top_accellabel); - always_on_top_sid = always_on_top.activate.connect (() => { + 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.CheckMenuItem (); - on_visible_workspace.add (on_visible_workspace_accellabel); - on_visible_workspace_sid = on_visible_workspace.activate.connect (() => { + 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.MenuItem (); - move_left.add (move_left_accellabel); - move_left.activate.connect (() => { + 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.MenuItem (); - move_right.add (move_right_accellabel); - move_right.activate.connect (() => { + 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.MenuItem (); - screenshot.add (screenshot_accellabel); - screenshot.activate.connect (() => { + 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.MenuItem (); - close.add (close_accellabel); - close.activate.connect (() => { + close = new Gtk.Button () { + child = close_accellabel + }; + close.add_css_class (Granite.STYLE_CLASS_MENUITEM); + close.clicked.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); + 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) { @@ -141,15 +170,12 @@ public class Gala.Daemon.WindowMenu : Gtk.Menu { } maximize.get_child ().destroy (); - maximize.add ( - new Granite.AccelLabel ( - maximize_label, - keybind_settings.get_strv ("toggle-maximized")[0] - ) + 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]; @@ -160,7 +186,7 @@ public class Gala.Daemon.WindowMenu : Gtk.Menu { resize_accellabel.accel_string = keybind_settings.get_strv ("begin-resize")[0]; } - // Setting active causes signal fires on activate so + // 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); diff --git a/daemon/meson.build b/daemon/meson.build index b3edc81d..595d1dfa 100644 --- a/daemon/meson.build +++ b/daemon/meson.build @@ -4,13 +4,13 @@ gala_daemon_sources = files( 'MonitorLabel.vala', 'Window.vala', 'WindowMenu.vala', - 'BackgroundMenu.vala', ) gala_daemon_bin = executable( 'gala-daemon', gala_daemon_sources, - dependencies: [gala_dep, gala_base_dep, granite_dep, hdy_dep], + gala_resources, + dependencies: [config_dep, granite_dep, gtk4_dep], include_directories: config_inc_dir, install: true, ) diff --git a/data/gala-daemon.css b/data/gala-daemon.css index c0b49e94..43a0f9eb 100644 --- a/data/gala-daemon.css +++ b/data/gala-daemon.css @@ -4,7 +4,7 @@ */ daemon-window { - background-color: transparent; + background: transparent; } .monitor-label { diff --git a/meson.build b/meson.build index fa57aac5..b8c4e7d5 100644 --- a/meson.build +++ b/meson.build @@ -71,8 +71,6 @@ add_project_arguments([ '-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_44', '-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_44', - '-DGDK_VERSION_MIN_REQUIRED=GDK_VERSION_3_10', - '-DGDK_VERSION_MAX_ALLOWED=GDK_VERSION_3_10', ], language: 'c', ) @@ -89,10 +87,10 @@ gio_dep = dependency('gio-2.0', version: '>= @0@'.format(glib_version_required)) gio_unix_dep = dependency('gio-unix-2.0', version: '>= @0@'.format(glib_version_required)) gmodule_dep = dependency('gmodule-2.0') gtk_dep = [dependency('gtk+-3.0', version: '>= @0@'.format(gtk_version_required)), dependency('gdk-x11-3.0')] +gtk4_dep = dependency('gtk4') gee_dep = dependency('gee-0.8') -granite_dep = dependency('granite', version: '>= 5.4.0') +granite_dep = dependency('granite-7') gnome_desktop_dep = dependency('gnome-desktop-3.0') -hdy_dep = dependency('libhandy-1') m_dep = cc.find_library('m', required: false) posix_dep = vala.find_library('posix', required: false) sqlite3_dep = dependency('sqlite3') @@ -184,6 +182,7 @@ subdir('protocol') subdir('lib') subdir('src') subdir('daemon') +subdir('daemon-gtk3') subdir('plugins/pip') subdir('plugins/template') if get_option('documentation') diff --git a/src/DaemonManager.vala b/src/DaemonManager.vala index 75215314..16609cc6 100644 --- a/src/DaemonManager.vala +++ b/src/DaemonManager.vala @@ -67,7 +67,7 @@ public class Gala.DaemonManager : GLib.Object { private async void start_x () { try { - var subprocess = new Subprocess (NONE, "gala-daemon"); + var subprocess = new Subprocess (NONE, "gala-daemon-gtk3"); yield subprocess.wait_async (); //Restart the daemon if it crashes @@ -102,6 +102,7 @@ public class Gala.DaemonManager : GLib.Object { break; case "MODAL": + daemon_client.make_dock (window); window.move_frame (false, 0, 0); window.make_above (); break;