From 338847557ac1b72ba07cc33bc03fbf41cddc201a Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 01:56:42 +0200 Subject: [PATCH 01/15] Implement window switcher prototype --- meson.build | 1 + windowSwitcher/Application.vala | 46 +++++++++ windowSwitcher/ShellKeyGrabber.vala | 129 +++++++++++++++++++++++++ windowSwitcher/WindowSwitcher.vala | 111 +++++++++++++++++++++ windowSwitcher/WindowSwitcherIcon.vala | 23 +++++ windowSwitcher/meson.build | 15 +++ 6 files changed, 325 insertions(+) create mode 100644 windowSwitcher/Application.vala create mode 100644 windowSwitcher/ShellKeyGrabber.vala create mode 100644 windowSwitcher/WindowSwitcher.vala create mode 100644 windowSwitcher/WindowSwitcherIcon.vala create mode 100644 windowSwitcher/meson.build diff --git a/meson.build b/meson.build index 3bfa153df..91b6e1e13 100644 --- a/meson.build +++ b/meson.build @@ -189,6 +189,7 @@ if get_option('documentation') subdir('docs') endif subdir('po') +subdir('windowSwitcher') vapigen = find_program('vapigen', required: false) if vapigen.found() diff --git a/windowSwitcher/Application.vala b/windowSwitcher/Application.vala new file mode 100644 index 000000000..5e85f97c8 --- /dev/null +++ b/windowSwitcher/Application.vala @@ -0,0 +1,46 @@ +/* + * Copyright 2024 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + */ + + public class Gala.WindowSwitcher.Application : Gtk.Application { + private WindowSwitcher window_switcher; + + public Application () { + Object (application_id: "io.elementary.window-switcher"); + } + + 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_display (Gdk.Display.get_default (), app_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + + Granite.init (); + hold (); + + window_switcher = new WindowSwitcher (); + } + + public override void activate () { } +} + +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.WindowSwitcher.Application (); + return app.run (); +} diff --git a/windowSwitcher/ShellKeyGrabber.vala b/windowSwitcher/ShellKeyGrabber.vala new file mode 100644 index 000000000..ecfe275b1 --- /dev/null +++ b/windowSwitcher/ShellKeyGrabber.vala @@ -0,0 +1,129 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io) + */ + +/** + * ActionMode: + * @NONE: block action + * @NORMAL: allow action when in window mode, e.g. when the focus is in an application window + * @OVERVIEW: allow action while the overview is active + * @LOCK_SCREEN: allow action when the screen is locked, e.g. when the screen shield is shown + * @UNLOCK_SCREEN: allow action in the unlock dialog + * @LOGIN_SCREEN: allow action in the login screen + * @SYSTEM_MODAL: allow action when a system modal dialog (e.g. authentification or session dialogs) is open + * @LOOKING_GLASS: allow action in looking glass + * @POPUP: allow action while a shell menu is open + */ + +[Flags] +public enum ActionMode { + NONE = 0, + NORMAL = 1 << 0, + OVERVIEW = 1 << 1, + LOCK_SCREEN = 1 << 2, + UNLOCK_SCREEN = 1 << 3, + LOGIN_SCREEN = 1 << 4, + SYSTEM_MODAL = 1 << 5, + LOOKING_GLASS = 1 << 6, + POPUP = 1 << 7, +} + +[Flags] +public enum Meta.KeyBindingFlags { + NONE = 0, + PER_WINDOW = 1 << 0, + BUILTIN = 1 << 1, + IS_REVERSED = 1 << 2, + NON_MASKABLE = 1 << 3, + IGNORE_AUTOREPEAT = 1 << 4, +} + +public struct Accelerator { + public string name; + public ActionMode mode_flags; + public Meta.KeyBindingFlags grab_flags; +} + +[DBus (name = "org.gnome.Shell")] +public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { + public abstract signal void accelerator_activated (uint action, GLib.HashTable parameters_dict); + + public abstract uint grab_accelerator (string accelerator, ActionMode mode_flags, Meta.KeyBindingFlags grab_flags) throws GLib.DBusError, GLib.IOError; + public abstract uint[] grab_accelerators (Accelerator[] accelerators) throws GLib.DBusError, GLib.IOError; + public abstract bool ungrab_accelerator (uint action) throws GLib.DBusError, GLib.IOError; + public abstract bool ungrab_accelerators (uint[] actions) throws GLib.DBusError, GLib.IOError; + + private static Settings settings; + private static ShellKeyGrabber? instance; + + private static HashTable saved_action_ids; + + private static WindowSwitcher window_switcher; + + public static void init (WindowSwitcher _window_switcher) { + window_switcher = _window_switcher; + + saved_action_ids = new HashTable (null, null); + + settings = new Settings ("org.gnome.desktop.wm.keybindings"); + + settings.changed.connect (() => { + ungrab_keybindings (); + setup_grabs (); + }); + + Bus.watch_name (BusType.SESSION, "org.gnome.Shell", BusNameWatcherFlags.NONE, () => on_watch.begin (), () => instance = null); + } + + private static async void on_watch () { + try { + instance = yield Bus.get_proxy (SESSION, "org.gnome.Shell", "/org/gnome/Shell"); + setup_grabs (); + } catch (Error e) { + warning ("Failed to connect to bus for keyboard shortcut grabs: %s", e.message); + } + } + + private static void setup_grabs () requires (instance != null) { + // var keybindings = settings.get_strv ("switch-windows"); + string[] keybindings = {"Tab"}; + Accelerator[] accelerators = {}; + for (int j = 0; j < keybindings.length; j++) { + accelerators += Accelerator () { + name = keybindings[j], + mode_flags = ActionMode.NONE, + grab_flags = Meta.KeyBindingFlags.NONE + }; + + try { + foreach (var id in instance.grab_accelerators (accelerators)) { + saved_action_ids[id] = 1; + warning ("GRABBER"); + } + } catch (Error e) { + critical ("Couldn't grab accelerators: %s", e.message); + } + } + + instance.accelerator_activated.connect (on_accelerator_activated); + } + + private static void on_accelerator_activated (uint action, GLib.HashTable parameters_dict) { + if (!(action in saved_action_ids)) { + return; + } + + window_switcher.activate_switcher (); + } + + private static void ungrab_keybindings () requires (instance != null) { + var actions = saved_action_ids.get_keys_as_array (); + + try { + instance.ungrab_accelerators (actions); + } catch (Error e) { + critical ("Couldn't ungrab accelerators: %s", e.message); + } + } +} diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala new file mode 100644 index 000000000..714fe3eb4 --- /dev/null +++ b/windowSwitcher/WindowSwitcher.vala @@ -0,0 +1,111 @@ +[DBus (name="org.pantheon.gala.DesktopIntegration")] +public interface DesktopIntegration : Object { + public struct Window { + uint64 uid; + GLib.HashTable properties; + } + + public abstract Window[] get_windows () throws IOError, DBusError; + public abstract void focus_window (uint64 uid) throws GLib.DBusError, GLib.IOError; +} + +public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.ExtendedBehavior { + private DesktopIntegration? desktop_integration; + private Gtk.FlowBox flow_box; + + construct { + flow_box = new Gtk.FlowBox () { + homogeneous = true, + selection_mode = BROWSE, + min_children_per_line = 10 + }; + + titlebar = new Gtk.Grid () { visible = false }; + child = flow_box; + + child.realize.connect (() => { + connect_to_shell (); + set_keep_above (); + make_centered (); + + var surface = get_surface (); + if (surface is Gdk.Toplevel) { + ((Gdk.Toplevel) surface).inhibit_system_shortcuts (null); + } + }); + + var key_controller = new Gtk.EventControllerKey () { + propagation_phase = CAPTURE + }; + + key_controller.key_released.connect ((val) => { + if (val == Gdk.Key.Alt_L) { + close_switcher (); + } + }); + + key_controller.key_pressed.connect ((val) => { + if (val == Gdk.Key.Tab) { + cycle (false); + } + }); + + ((Gtk.Widget) this).add_controller (key_controller); + + ShellKeyGrabber.init (this); + + try { + desktop_integration = Bus.get_proxy_sync (SESSION, "org.pantheon.gala", "/org/pantheon/gala/DesktopInterface"); + } catch (Error e) { + warning ("Failed to get the desktop integration: %s", e.message); + } + } + + public void activate_switcher () { + flow_box.remove_all (); + default_width = 1; + default_height = 1; + + try { + int i = 0; + foreach (var window in desktop_integration.get_windows ()) { + if (is_eligible_window (window)) { + var icon = new WindowSwitcherIcon (window.uid, (string) window.properties["title"], (string) window.properties["app-id"]); + flow_box.append (icon); + + if (++i == 2) { + flow_box.set_focus_child (icon); + } + } + } + } catch (Error e) { + warning ("Failed to get windows: %s", e.message); + } + + present (); + } + + public void close_switcher () { + hide (); + + var icon = (WindowSwitcherIcon) flow_box.get_focus_child (); + + try { + desktop_integration.focus_window (icon.uid); + } catch (Error e) { + warning ("Failed to focus window"); + } + } + + private void cycle (bool backwards) { + if (!(flow_box.get_focus_child ().get_next_sibling () is WindowSwitcherIcon)) { + flow_box.set_focus_child (flow_box.get_first_child ()); + } + + flow_box.child_focus (TAB_FORWARD); + } + + private bool is_eligible_window (DesktopIntegration.Window window) { + return true; + } +} diff --git a/windowSwitcher/WindowSwitcherIcon.vala b/windowSwitcher/WindowSwitcherIcon.vala new file mode 100644 index 000000000..0d3553c8d --- /dev/null +++ b/windowSwitcher/WindowSwitcherIcon.vala @@ -0,0 +1,23 @@ +public class WindowSwitcherIcon : Gtk.FlowBoxChild { + public uint64 uid { get; construct; } + public string title { get; construct; } + public string app_id { get; construct; } + + public WindowSwitcherIcon (uint64 uid, string title, string app_id) { + Object ( + uid: uid, + title: title, + app_id: app_id + ); + } + + construct { + var desktop_app_info = new DesktopAppInfo (app_id); + + var image = new Gtk.Image.from_gicon (desktop_app_info.get_icon ()) { + pixel_size = 64 + }; + + child = image; + } +} diff --git a/windowSwitcher/meson.build b/windowSwitcher/meson.build new file mode 100644 index 000000000..14c64854b --- /dev/null +++ b/windowSwitcher/meson.build @@ -0,0 +1,15 @@ +window_switcher_sources = files( + 'Application.vala', + 'ShellKeyGrabber.vala', + 'WindowSwitcher.vala', + 'WindowSwitcherIcon.vala' +) + +window_switcher_bin = executable( + 'io.elementary.window-switcher', + window_switcher_sources, + gala_resources, + dependencies: [config_dep, granite_dep, gtk4_dep, dependency('pantheon-wayland-1')], + include_directories: config_inc_dir, + install: true, +) From 46336281ffaaef514303dd80f5c88ac958a5bec0 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 16:51:12 +0200 Subject: [PATCH 02/15] Cleanup and cycling forward and backward --- windowSwitcher/Application.vala | 4 ---- windowSwitcher/ShellKeyGrabber.vala | 21 +++++++++------------ windowSwitcher/WindowSwitcher.vala | 29 ++++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/windowSwitcher/Application.vala b/windowSwitcher/Application.vala index 5e85f97c8..44f527d81 100644 --- a/windowSwitcher/Application.vala +++ b/windowSwitcher/Application.vala @@ -22,10 +22,6 @@ 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_display (Gdk.Display.get_default (), app_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); - Granite.init (); hold (); diff --git a/windowSwitcher/ShellKeyGrabber.vala b/windowSwitcher/ShellKeyGrabber.vala index ecfe275b1..03e96720f 100644 --- a/windowSwitcher/ShellKeyGrabber.vala +++ b/windowSwitcher/ShellKeyGrabber.vala @@ -57,14 +57,14 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { private static Settings settings; private static ShellKeyGrabber? instance; - private static HashTable saved_action_ids; + private static Gee.HashSet saved_action_ids; private static WindowSwitcher window_switcher; public static void init (WindowSwitcher _window_switcher) { window_switcher = _window_switcher; - saved_action_ids = new HashTable (null, null); + saved_action_ids = new Gee.HashSet (); settings = new Settings ("org.gnome.desktop.wm.keybindings"); @@ -95,15 +95,14 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { mode_flags = ActionMode.NONE, grab_flags = Meta.KeyBindingFlags.NONE }; + } - try { - foreach (var id in instance.grab_accelerators (accelerators)) { - saved_action_ids[id] = 1; - warning ("GRABBER"); - } - } catch (Error e) { - critical ("Couldn't grab accelerators: %s", e.message); + try { + foreach (var id in instance.grab_accelerators (accelerators)) { + saved_action_ids.add (id); } + } catch (Error e) { + critical ("Couldn't grab accelerators: %s", e.message); } instance.accelerator_activated.connect (on_accelerator_activated); @@ -118,10 +117,8 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { } private static void ungrab_keybindings () requires (instance != null) { - var actions = saved_action_ids.get_keys_as_array (); - try { - instance.ungrab_accelerators (actions); + instance.ungrab_accelerators (saved_action_ids.to_array ()); } catch (Error e) { critical ("Couldn't ungrab accelerators: %s", e.message); } diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index 714fe3eb4..8452d9a0e 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -44,9 +44,20 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex } }); - key_controller.key_pressed.connect ((val) => { + key_controller.key_pressed.connect ((val, code, modifier_state) => { if (val == Gdk.Key.Tab) { + cycle (SHIFT_MASK in modifier_state); + return Gdk.EVENT_STOP; + } + + if (val == Gdk.Key.Right) { cycle (false); + return Gdk.EVENT_STOP; + } + + if (val == Gdk.Key.Left) { + cycle (true); + return Gdk.EVENT_STOP; } }); @@ -98,11 +109,19 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex } private void cycle (bool backwards) { - if (!(flow_box.get_focus_child ().get_next_sibling () is WindowSwitcherIcon)) { - flow_box.set_focus_child (flow_box.get_first_child ()); - } + if (backwards) { + if (!(flow_box.get_focus_child ().get_prev_sibling () is WindowSwitcherIcon)) { + flow_box.set_focus_child (flow_box.get_last_child ()); + } + + flow_box.child_focus (TAB_BACKWARD); + } else { + if (!(flow_box.get_focus_child ().get_next_sibling () is WindowSwitcherIcon)) { + flow_box.set_focus_child (flow_box.get_first_child ()); + } - flow_box.child_focus (TAB_FORWARD); + flow_box.child_focus (TAB_FORWARD); + } } private bool is_eligible_window (DesktopIntegration.Window window) { From 318d7c0f3a6b8acb61fe95e91de9beb0f6b8a0bf Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 17:01:52 +0200 Subject: [PATCH 03/15] Add title and some stylistic improvements --- windowSwitcher/WindowSwitcher.vala | 33 ++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index 8452d9a0e..7f91fb4b8 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -11,17 +11,34 @@ public interface DesktopIntegration : Object { public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.ExtendedBehavior { private DesktopIntegration? desktop_integration; + private Gtk.FlowBox flow_box; + private Gtk.Label title_label; construct { flow_box = new Gtk.FlowBox () { homogeneous = true, selection_mode = BROWSE, - min_children_per_line = 10 + min_children_per_line = 10, + column_spacing = 3, + row_spacing = 3 + }; + + title_label = new Gtk.Label (null) { + ellipsize = END + }; + + var box = new Gtk.Box (VERTICAL, 6) { + margin_top = 12, + margin_bottom = 12, + margin_end = 12, + margin_start = 12 }; + box.append (flow_box); + box.append (title_label); titlebar = new Gtk.Grid () { visible = false }; - child = flow_box; + child = box; child.realize.connect (() => { connect_to_shell (); @@ -93,6 +110,7 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex warning ("Failed to get windows: %s", e.message); } + update_title (); present (); } @@ -122,6 +140,17 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex flow_box.child_focus (TAB_FORWARD); } + + update_title (); + } + + private void update_title () { + var focus_child = flow_box.get_focus_child (); + if (focus_child != null && focus_child is WindowSwitcherIcon) { + title_label.label = ((WindowSwitcherIcon) focus_child).title; + } else { + title_label.label = null; + } } private bool is_eligible_window (DesktopIntegration.Window window) { From 0c6aff755607fdca834c19057a355451e6b6888d Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 17:04:44 +0200 Subject: [PATCH 04/15] Make it a bit better --- windowSwitcher/WindowSwitcher.vala | 1 - windowSwitcher/WindowSwitcherIcon.vala | 6 +++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index 7f91fb4b8..faf912d21 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -19,7 +19,6 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex flow_box = new Gtk.FlowBox () { homogeneous = true, selection_mode = BROWSE, - min_children_per_line = 10, column_spacing = 3, row_spacing = 3 }; diff --git a/windowSwitcher/WindowSwitcherIcon.vala b/windowSwitcher/WindowSwitcherIcon.vala index 0d3553c8d..2a6ee6ddc 100644 --- a/windowSwitcher/WindowSwitcherIcon.vala +++ b/windowSwitcher/WindowSwitcherIcon.vala @@ -15,7 +15,11 @@ public class WindowSwitcherIcon : Gtk.FlowBoxChild { var desktop_app_info = new DesktopAppInfo (app_id); var image = new Gtk.Image.from_gicon (desktop_app_info.get_icon ()) { - pixel_size = 64 + pixel_size = 64, + margin_top = 12, + margin_bottom = 12, + margin_start = 12, + margin_end = 12 }; child = image; From d871d4375b1ddf7dccd5ee63e56d82c9f687af7b Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 17:24:29 +0200 Subject: [PATCH 05/15] Fix not keeping above on subsequent and start size calculation --- windowSwitcher/WindowSwitcher.vala | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index faf912d21..d50fbd174 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -39,8 +39,13 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex titlebar = new Gtk.Grid () { visible = false }; child = box; - child.realize.connect (() => { - connect_to_shell (); + child.realize.connect (connect_to_shell); + + /* + * Since we hide our surface doesn't get destroyed. + * But Gala "forgets" about us so every time we present we have to keep above and center again. + */ + child.map.connect (() => { set_keep_above (); make_centered (); @@ -48,6 +53,8 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex if (surface is Gdk.Toplevel) { ((Gdk.Toplevel) surface).inhibit_system_shortcuts (null); } + + update_default_size (); }); var key_controller = new Gtk.EventControllerKey () { @@ -113,6 +120,21 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex present (); } + private void update_default_size () { + Gtk.Requisition natural; + child.get_preferred_size (null, out natural); + + var max_width = Gdk.Display.get_default ().get_monitor_at_surface (get_surface ()).get_geometry ().width - 50; + + if (natural.width < max_width) { + default_width = natural.width; + } else { + default_width = max_width; + } + + default_height = 1; + } + public void close_switcher () { hide (); From 8b5df48b1c3ebdb8bd5bb2aafdd2c23eee1873da Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 19:17:51 +0200 Subject: [PATCH 06/15] Pretty much the same now --- windowSwitcher/WindowSwitcher.vala | 30 +++++++++++++++----------- windowSwitcher/WindowSwitcherIcon.vala | 4 ++++ windowSwitcher/meson.build | 2 +- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index d50fbd174..0a62b642c 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -15,12 +15,15 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex private Gtk.FlowBox flow_box; private Gtk.Label title_label; + private int n_windows = 0; + construct { flow_box = new Gtk.FlowBox () { homogeneous = true, - selection_mode = BROWSE, + selection_mode = NONE, column_spacing = 3, - row_spacing = 3 + row_spacing = 3, + activate_on_single_click = true }; title_label = new Gtk.Label (null) { @@ -93,6 +96,8 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex } catch (Error e) { warning ("Failed to get the desktop integration: %s", e.message); } + + flow_box.child_activated.connect (() => close_switcher ()); } public void activate_switcher () { @@ -101,13 +106,13 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex default_height = 1; try { - int i = 0; + n_windows = 0; foreach (var window in desktop_integration.get_windows ()) { if (is_eligible_window (window)) { var icon = new WindowSwitcherIcon (window.uid, (string) window.properties["title"], (string) window.properties["app-id"]); flow_box.append (icon); - if (++i == 2) { + if (++n_windows == 2) { flow_box.set_focus_child (icon); } } @@ -121,17 +126,18 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex } private void update_default_size () { - Gtk.Requisition natural; - child.get_preferred_size (null, out natural); + Gtk.Requisition natural_size; + flow_box.get_first_child ().get_preferred_size (null, out natural_size); - var max_width = Gdk.Display.get_default ().get_monitor_at_surface (get_surface ()).get_geometry ().width - 50; + var display_width = Gdk.Display.get_default ().get_monitor_at_surface (get_surface ()).get_geometry ().width - 50; - if (natural.width < max_width) { - default_width = natural.width; - } else { - default_width = max_width; - } + var max_children = (int) display_width / (natural_size.width + 3); + var min_children = (int) Math.fmin (n_windows, max_children); + + flow_box.min_children_per_line = min_children; + flow_box.max_children_per_line = max_children; + default_width = 1; default_height = 1; } diff --git a/windowSwitcher/WindowSwitcherIcon.vala b/windowSwitcher/WindowSwitcherIcon.vala index 2a6ee6ddc..20b432143 100644 --- a/windowSwitcher/WindowSwitcherIcon.vala +++ b/windowSwitcher/WindowSwitcherIcon.vala @@ -23,5 +23,9 @@ public class WindowSwitcherIcon : Gtk.FlowBoxChild { }; child = image; + + var hover_controller = new Gtk.EventControllerMotion (); + hover_controller.enter.connect (() => grab_focus ()); + add_controller (hover_controller); } } diff --git a/windowSwitcher/meson.build b/windowSwitcher/meson.build index 14c64854b..5173fed2c 100644 --- a/windowSwitcher/meson.build +++ b/windowSwitcher/meson.build @@ -9,7 +9,7 @@ window_switcher_bin = executable( 'io.elementary.window-switcher', window_switcher_sources, gala_resources, - dependencies: [config_dep, granite_dep, gtk4_dep, dependency('pantheon-wayland-1')], + dependencies: [config_dep, granite_dep, gtk4_dep, dependency('pantheon-wayland-1'), m_dep], include_directories: config_inc_dir, install: true, ) From c3ddcfa8173137896ee45abe17b1cef571876b7a Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 19:21:08 +0200 Subject: [PATCH 07/15] Only eligible windows --- windowSwitcher/WindowSwitcher.vala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index 0a62b642c..b3182c6a6 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -181,6 +181,10 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex } private bool is_eligible_window (DesktopIntegration.Window window) { + if (!(bool) window.properties["on-active-workspace"]) { + return false; + } + return true; } } From afc858b7d4c11b4df86031e1606105c2e8a6a693 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 20:33:32 +0200 Subject: [PATCH 08/15] It works properly now --- data/gala.gschema.xml | 19 ++++++++ windowSwitcher/Application.vala | 58 +++++++++++++++++++----- windowSwitcher/ShellKeyGrabber.vala | 60 +++++++++++++------------ windowSwitcher/WindowSwitcher.vala | 69 +++++++++++++++++++++-------- 4 files changed, 150 insertions(+), 56 deletions(-) diff --git a/data/gala.gschema.xml b/data/gala.gschema.xml index 7cea18fd0..416206e46 100644 --- a/data/gala.gschema.xml +++ b/data/gala.gschema.xml @@ -271,4 +271,23 @@ + + + + Tab']]]> + Switch between windows. + + + Tab']]]> + Switch between windows backwards. + + + ~']]]> + Switch between windows of the current application. + + + ~']]]> + Switch between windows of the current application backwards. + + diff --git a/windowSwitcher/Application.vala b/windowSwitcher/Application.vala index 44f527d81..cf1673ac1 100644 --- a/windowSwitcher/Application.vala +++ b/windowSwitcher/Application.vala @@ -3,7 +3,22 @@ * SPDX-License-Identifier: GPL-3.0-or-later */ - public class Gala.WindowSwitcher.Application : Gtk.Application { +public class Gala.WindowSwitcher.Application : Gtk.Application { + public const string ACTION_PREFIX = "app."; + public const string CYCLE_FORWARD_ACTION = "switch-windows"; + public const string CYCLE_BACKWARD_ACTION = "switch-windows-backward"; + public const string CYCLE_CURRENT_FORWARD_ACTION = "switch-group"; + public const string CYCLE_CURRENT_BACKWARD_ACTION = "switch-group-backward"; + + private const ActionEntry[] ACTIONS = { + {CYCLE_FORWARD_ACTION, cycle, null, null, null}, + {CYCLE_BACKWARD_ACTION, cycle_backward, null, null, null}, + {CYCLE_CURRENT_FORWARD_ACTION, cycle_current, null, null, null}, + {CYCLE_CURRENT_BACKWARD_ACTION, cycle_current_backward, null, null, null} + }; + + private static Settings settings; + private WindowSwitcher window_switcher; public Application () { @@ -13,19 +28,42 @@ 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; + settings = new Settings ("io.elementary.desktop.window-switcher"); + settings.changed.connect (setup_accels); + setup_accels (); - granite_settings.notify["prefers-color-scheme"].connect (() => { - gtk_settings.gtk_application_prefer_dark_theme = granite_settings.prefers_color_scheme == Granite.Settings.ColorScheme.DARK; - }); + add_action_entries (ACTIONS, this); Granite.init (); - hold (); - window_switcher = new WindowSwitcher (); + window_switcher = new WindowSwitcher (this); + + ShellKeyGrabber.init ({CYCLE_FORWARD_ACTION, CYCLE_BACKWARD_ACTION, + CYCLE_CURRENT_FORWARD_ACTION, CYCLE_CURRENT_BACKWARD_ACTION}, settings); + } + + private void setup_accels () { + set_accels_for_action (ACTION_PREFIX + CYCLE_FORWARD_ACTION, settings.get_strv (CYCLE_FORWARD_ACTION)); + set_accels_for_action (ACTION_PREFIX + CYCLE_BACKWARD_ACTION, settings.get_strv (CYCLE_BACKWARD_ACTION)); + set_accels_for_action (ACTION_PREFIX + CYCLE_CURRENT_FORWARD_ACTION, settings.get_strv (CYCLE_CURRENT_FORWARD_ACTION)); + set_accels_for_action (ACTION_PREFIX + CYCLE_CURRENT_BACKWARD_ACTION, settings.get_strv (CYCLE_CURRENT_BACKWARD_ACTION)); + } + + private void cycle () { + warning ("CYCLE"); + window_switcher.cycle (false, false); + } + + private void cycle_backward () { + window_switcher.cycle (false, true); + } + + private void cycle_current () { + window_switcher.cycle (true, false); + } + + private void cycle_current_backward () { + window_switcher.cycle (true, true); } public override void activate () { } diff --git a/windowSwitcher/ShellKeyGrabber.vala b/windowSwitcher/ShellKeyGrabber.vala index 03e96720f..a2a8e3640 100644 --- a/windowSwitcher/ShellKeyGrabber.vala +++ b/windowSwitcher/ShellKeyGrabber.vala @@ -54,19 +54,18 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { public abstract bool ungrab_accelerator (uint action) throws GLib.DBusError, GLib.IOError; public abstract bool ungrab_accelerators (uint[] actions) throws GLib.DBusError, GLib.IOError; + private static string[] actions; private static Settings settings; - private static ShellKeyGrabber? instance; - - private static Gee.HashSet saved_action_ids; - private static WindowSwitcher window_switcher; + private static Gee.HashMap saved_action_ids; - public static void init (WindowSwitcher _window_switcher) { - window_switcher = _window_switcher; + private static ShellKeyGrabber? instance; - saved_action_ids = new Gee.HashSet (); + public static void init (string[] _actions, Settings _settings) { + actions = _actions; + settings = _settings; - settings = new Settings ("org.gnome.desktop.wm.keybindings"); + saved_action_ids = new Gee.HashMap (); settings.changed.connect (() => { ungrab_keybindings (); @@ -86,39 +85,44 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { } private static void setup_grabs () requires (instance != null) { - // var keybindings = settings.get_strv ("switch-windows"); - string[] keybindings = {"Tab"}; - Accelerator[] accelerators = {}; - for (int j = 0; j < keybindings.length; j++) { - accelerators += Accelerator () { - name = keybindings[j], - mode_flags = ActionMode.NONE, - grab_flags = Meta.KeyBindingFlags.NONE - }; - } + foreach (var action in actions) { + string[] keybindings = settings.get_strv (action); + Accelerator[] accelerators = {}; + + for (int j = 0; j < keybindings.length; j++) { + accelerators += Accelerator () { + name = keybindings[j], + mode_flags = ActionMode.NONE, + grab_flags = Meta.KeyBindingFlags.NONE + }; + } - try { - foreach (var id in instance.grab_accelerators (accelerators)) { - saved_action_ids.add (id); + try { + foreach (var id in instance.grab_accelerators (accelerators)) { + saved_action_ids.set (id, action); + } + } catch (Error e) { + critical ("Couldn't grab accelerators: %s", e.message); } - } catch (Error e) { - critical ("Couldn't grab accelerators: %s", e.message); - } - instance.accelerator_activated.connect (on_accelerator_activated); + instance.accelerator_activated.connect (on_accelerator_activated); + } } private static void on_accelerator_activated (uint action, GLib.HashTable parameters_dict) { - if (!(action in saved_action_ids)) { + if (!saved_action_ids.has_key (action)) { return; } - window_switcher.activate_switcher (); + ((Gtk.Application) GLib.Application.get_default ()).activate_action ( + saved_action_ids[action], + null + ); } private static void ungrab_keybindings () requires (instance != null) { try { - instance.ungrab_accelerators (saved_action_ids.to_array ()); + instance.ungrab_accelerators (saved_action_ids.keys.to_array ()); } catch (Error e) { critical ("Couldn't ungrab accelerators: %s", e.message); } diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index b3182c6a6..d2d47a5e7 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -9,7 +9,7 @@ public interface DesktopIntegration : Object { public abstract void focus_window (uint64 uid) throws GLib.DBusError, GLib.IOError; } -public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.ExtendedBehavior { +public class Gala.WindowSwitcher.WindowSwitcher : Gtk.ApplicationWindow, PantheonWayland.ExtendedBehavior { private DesktopIntegration? desktop_integration; private Gtk.FlowBox flow_box; @@ -17,6 +17,15 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex private int n_windows = 0; + private bool active = false; + private bool only_current = false; + + public WindowSwitcher (Application application) { + Object ( + application: application + ); + } + construct { flow_box = new Gtk.FlowBox () { homogeneous = true, @@ -45,7 +54,7 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex child.realize.connect (connect_to_shell); /* - * Since we hide our surface doesn't get destroyed. + * Because we hide, our surface doesn't get destroyed. * But Gala "forgets" about us so every time we present we have to keep above and center again. */ child.map.connect (() => { @@ -71,26 +80,21 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex }); key_controller.key_pressed.connect ((val, code, modifier_state) => { - if (val == Gdk.Key.Tab) { - cycle (SHIFT_MASK in modifier_state); - return Gdk.EVENT_STOP; - } - if (val == Gdk.Key.Right) { - cycle (false); + cycle (only_current, false); return Gdk.EVENT_STOP; } if (val == Gdk.Key.Left) { - cycle (true); + cycle (only_current, true); return Gdk.EVENT_STOP; } + + return Gdk.EVENT_PROPAGATE; }); ((Gtk.Widget) this).add_controller (key_controller); - ShellKeyGrabber.init (this); - try { desktop_integration = Bus.get_proxy_sync (SESSION, "org.pantheon.gala", "/org/pantheon/gala/DesktopInterface"); } catch (Error e) { @@ -100,15 +104,19 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex flow_box.child_activated.connect (() => close_switcher ()); } - public void activate_switcher () { + public void activate_switcher (bool only_current) { + active = true; + this.only_current = only_current; + flow_box.remove_all (); - default_width = 1; - default_height = 1; try { + var windows = desktop_integration.get_windows (); + var current_app_id = only_current ? get_current_app_id (windows) : null; + n_windows = 0; - foreach (var window in desktop_integration.get_windows ()) { - if (is_eligible_window (window)) { + foreach (var window in windows) { + if (is_eligible_window (window, current_app_id)) { var icon = new WindowSwitcherIcon (window.uid, (string) window.properties["title"], (string) window.properties["app-id"]); flow_box.append (icon); @@ -151,9 +159,20 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex } catch (Error e) { warning ("Failed to focus window"); } + + active = false; } - private void cycle (bool backwards) { + public void cycle (bool only_current, bool backwards) { + if (!active) { + activate_switcher (only_current); + } + + if (this.only_current != only_current) { + //todo: gdk beep? + return; + } + if (backwards) { if (!(flow_box.get_focus_child ().get_prev_sibling () is WindowSwitcherIcon)) { flow_box.set_focus_child (flow_box.get_last_child ()); @@ -180,11 +199,25 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.Window, PantheonWayland.Ex } } - private bool is_eligible_window (DesktopIntegration.Window window) { + private bool is_eligible_window (DesktopIntegration.Window window, string? current_app_id) { if (!(bool) window.properties["on-active-workspace"]) { return false; } + if (current_app_id != null && (string) window.properties["app-id"] != current_app_id) { + return false; + } + return true; } + + private string? get_current_app_id (DesktopIntegration.Window[] windows) { + foreach (var window in windows) { + if ((bool) window.properties["has-focus"]) { + return (string) window.properties["app-id"]; + } + } + + return null; + } } From c1cc494845237b3cb21d09e445a458e721cc3d38 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 20:40:41 +0200 Subject: [PATCH 09/15] Cleanup and fixes --- data/gala.gschema.xml | 4 ++-- windowSwitcher/Application.vala | 1 - windowSwitcher/ShellKeyGrabber.vala | 9 ++++----- windowSwitcher/WindowSwitcher.vala | 1 + 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/data/gala.gschema.xml b/data/gala.gschema.xml index 416206e46..ce5949f28 100644 --- a/data/gala.gschema.xml +++ b/data/gala.gschema.xml @@ -282,11 +282,11 @@ Switch between windows backwards. - ~']]]> + grave']]]> Switch between windows of the current application. - ~']]]> + grave']]]> Switch between windows of the current application backwards. diff --git a/windowSwitcher/Application.vala b/windowSwitcher/Application.vala index cf1673ac1..921a04cf4 100644 --- a/windowSwitcher/Application.vala +++ b/windowSwitcher/Application.vala @@ -50,7 +50,6 @@ public class Gala.WindowSwitcher.Application : Gtk.Application { } private void cycle () { - warning ("CYCLE"); window_switcher.cycle (false, false); } diff --git a/windowSwitcher/ShellKeyGrabber.vala b/windowSwitcher/ShellKeyGrabber.vala index a2a8e3640..69fec2345 100644 --- a/windowSwitcher/ShellKeyGrabber.vala +++ b/windowSwitcher/ShellKeyGrabber.vala @@ -78,7 +78,9 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { private static async void on_watch () { try { instance = yield Bus.get_proxy (SESSION, "org.gnome.Shell", "/org/gnome/Shell"); + setup_grabs (); + instance.accelerator_activated.connect (on_accelerator_activated); } catch (Error e) { warning ("Failed to connect to bus for keyboard shortcut grabs: %s", e.message); } @@ -86,12 +88,11 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { private static void setup_grabs () requires (instance != null) { foreach (var action in actions) { - string[] keybindings = settings.get_strv (action); Accelerator[] accelerators = {}; - for (int j = 0; j < keybindings.length; j++) { + foreach (var keybinding in settings.get_strv (action)) { accelerators += Accelerator () { - name = keybindings[j], + name = keybinding, mode_flags = ActionMode.NONE, grab_flags = Meta.KeyBindingFlags.NONE }; @@ -104,8 +105,6 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { } catch (Error e) { critical ("Couldn't grab accelerators: %s", e.message); } - - instance.accelerator_activated.connect (on_accelerator_activated); } } diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index d2d47a5e7..b296b605a 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -166,6 +166,7 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.ApplicationWindow, Pantheo public void cycle (bool only_current, bool backwards) { if (!active) { activate_switcher (only_current); + return; } if (this.only_current != only_current) { From e9e2b5c8e4f2072f41997e719c92e20265ab1156 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 20:48:39 +0200 Subject: [PATCH 10/15] Do beeps etc --- windowSwitcher/WindowSwitcher.vala | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index b296b605a..5c650f737 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -125,6 +125,15 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.ApplicationWindow, Pantheo } } } + + if (n_windows == 0) { + get_surface ().beep (); + return; + } + + if (n_windows == 1) { + flow_box.set_focus_child (flow_box.get_first_child ()); + } } catch (Error e) { warning ("Failed to get windows: %s", e.message); } From 88cbcd26a53c09570e1f566484d6fb85247965cb Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 20:49:41 +0200 Subject: [PATCH 11/15] Cleanup --- windowSwitcher/WindowSwitcher.vala | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/windowSwitcher/WindowSwitcher.vala b/windowSwitcher/WindowSwitcher.vala index 5c650f737..51d737e77 100644 --- a/windowSwitcher/WindowSwitcher.vala +++ b/windowSwitcher/WindowSwitcher.vala @@ -108,13 +108,13 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.ApplicationWindow, Pantheo active = true; this.only_current = only_current; + n_windows = 0; + flow_box.remove_all (); try { var windows = desktop_integration.get_windows (); var current_app_id = only_current ? get_current_app_id (windows) : null; - - n_windows = 0; foreach (var window in windows) { if (is_eligible_window (window, current_app_id)) { var icon = new WindowSwitcherIcon (window.uid, (string) window.properties["title"], (string) window.properties["app-id"]); @@ -125,19 +125,19 @@ public class Gala.WindowSwitcher.WindowSwitcher : Gtk.ApplicationWindow, Pantheo } } } - - if (n_windows == 0) { - get_surface ().beep (); - return; - } - - if (n_windows == 1) { - flow_box.set_focus_child (flow_box.get_first_child ()); - } } catch (Error e) { warning ("Failed to get windows: %s", e.message); } + if (n_windows == 0) { + get_surface ().beep (); + return; + } + + if (n_windows == 1) { + flow_box.set_focus_child (flow_box.get_first_child ()); + } + update_title (); present (); } From 9892802fbe3245c92e2306ba74b542048c03f754 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 20:55:24 +0200 Subject: [PATCH 12/15] Cleanup --- windowSwitcher/ShellKeyGrabber.vala | 47 +++-------------------------- 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/windowSwitcher/ShellKeyGrabber.vala b/windowSwitcher/ShellKeyGrabber.vala index 69fec2345..79b0c1737 100644 --- a/windowSwitcher/ShellKeyGrabber.vala +++ b/windowSwitcher/ShellKeyGrabber.vala @@ -3,55 +3,18 @@ * SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io) */ -/** - * ActionMode: - * @NONE: block action - * @NORMAL: allow action when in window mode, e.g. when the focus is in an application window - * @OVERVIEW: allow action while the overview is active - * @LOCK_SCREEN: allow action when the screen is locked, e.g. when the screen shield is shown - * @UNLOCK_SCREEN: allow action in the unlock dialog - * @LOGIN_SCREEN: allow action in the login screen - * @SYSTEM_MODAL: allow action when a system modal dialog (e.g. authentification or session dialogs) is open - * @LOOKING_GLASS: allow action in looking glass - * @POPUP: allow action while a shell menu is open - */ - -[Flags] -public enum ActionMode { - NONE = 0, - NORMAL = 1 << 0, - OVERVIEW = 1 << 1, - LOCK_SCREEN = 1 << 2, - UNLOCK_SCREEN = 1 << 3, - LOGIN_SCREEN = 1 << 4, - SYSTEM_MODAL = 1 << 5, - LOOKING_GLASS = 1 << 6, - POPUP = 1 << 7, -} - -[Flags] -public enum Meta.KeyBindingFlags { - NONE = 0, - PER_WINDOW = 1 << 0, - BUILTIN = 1 << 1, - IS_REVERSED = 1 << 2, - NON_MASKABLE = 1 << 3, - IGNORE_AUTOREPEAT = 1 << 4, -} - public struct Accelerator { public string name; - public ActionMode mode_flags; - public Meta.KeyBindingFlags grab_flags; + public uint mode_flags; + public uint grab_flags; } [DBus (name = "org.gnome.Shell")] public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { public abstract signal void accelerator_activated (uint action, GLib.HashTable parameters_dict); - public abstract uint grab_accelerator (string accelerator, ActionMode mode_flags, Meta.KeyBindingFlags grab_flags) throws GLib.DBusError, GLib.IOError; + public abstract uint grab_accelerator (string accelerator, uint mode_flags, uint grab_flags) throws GLib.DBusError, GLib.IOError; public abstract uint[] grab_accelerators (Accelerator[] accelerators) throws GLib.DBusError, GLib.IOError; - public abstract bool ungrab_accelerator (uint action) throws GLib.DBusError, GLib.IOError; public abstract bool ungrab_accelerators (uint[] actions) throws GLib.DBusError, GLib.IOError; private static string[] actions; @@ -93,8 +56,8 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { foreach (var keybinding in settings.get_strv (action)) { accelerators += Accelerator () { name = keybinding, - mode_flags = ActionMode.NONE, - grab_flags = Meta.KeyBindingFlags.NONE + mode_flags = 0, + grab_flags = 0 }; } From c4e6837e34819c31ad623792884e56047c6cb43a Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 20:56:34 +0200 Subject: [PATCH 13/15] More cleanup --- windowSwitcher/ShellKeyGrabber.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windowSwitcher/ShellKeyGrabber.vala b/windowSwitcher/ShellKeyGrabber.vala index 79b0c1737..1a76c9eac 100644 --- a/windowSwitcher/ShellKeyGrabber.vala +++ b/windowSwitcher/ShellKeyGrabber.vala @@ -35,7 +35,7 @@ public interface Gala.WindowSwitcher.ShellKeyGrabber : GLib.Object { setup_grabs (); }); - Bus.watch_name (BusType.SESSION, "org.gnome.Shell", BusNameWatcherFlags.NONE, () => on_watch.begin (), () => instance = null); + Bus.watch_name (SESSION, "org.gnome.Shell", NONE, () => on_watch.begin (), () => instance = null); } private static async void on_watch () { From c6852e2a7387975c166a68997eb91f76c802fa3c Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Fri, 20 Sep 2024 21:01:03 +0200 Subject: [PATCH 14/15] Actually remove the window switcher --- lib/Plugin.vala | 1 - src/PluginManager.vala | 8 - .../WindowSwitcher/WindowSwitcher.vala | 563 ------------------ .../WindowSwitcher/WindowSwitcherIcon.vala | 87 --- src/WindowManager.vala | 22 - src/meson.build | 2 - 6 files changed, 683 deletions(-) delete mode 100644 src/Widgets/WindowSwitcher/WindowSwitcher.vala delete mode 100644 src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala diff --git a/lib/Plugin.vala b/lib/Plugin.vala index 4bd011f0e..75f57349d 100644 --- a/lib/Plugin.vala +++ b/lib/Plugin.vala @@ -18,7 +18,6 @@ namespace Gala { public enum PluginFunction { ADDITION, - WINDOW_SWITCHER, WORKSPACE_VIEW, WINDOW_OVERVIEW } diff --git a/src/PluginManager.vala b/src/PluginManager.vala index 0af190e5b..b65a93b8d 100644 --- a/src/PluginManager.vala +++ b/src/PluginManager.vala @@ -36,7 +36,6 @@ namespace Gala { return _regions; } - public string? window_switcher_provider { get; private set; default = null; } public string? window_overview_provider { get; private set; default = null; } public string? workspace_view_provider { get; private set; default = null; } @@ -155,13 +154,6 @@ namespace Gala { } window_overview_provider = name; return true; - case PluginFunction.WINDOW_SWITCHER: - if (window_switcher_provider != null) { - warning (message, window_switcher_provider, name, "window switcher"); - return false; - } - window_switcher_provider = name; - return true; default: break; } diff --git a/src/Widgets/WindowSwitcher/WindowSwitcher.vala b/src/Widgets/WindowSwitcher/WindowSwitcher.vala deleted file mode 100644 index 4cf6d6890..000000000 --- a/src/Widgets/WindowSwitcher/WindowSwitcher.vala +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Copyright 2021 Aral Balkan - * Copyright 2020 Mark Story - * Copyright 2017 Popye - * Copyright 2014 Tom Beckmann - * Copyright 2023 elementary, Inc. - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -public class Gala.WindowSwitcher : CanvasActor { - public const int ICON_SIZE = 64; - public const int WRAPPER_PADDING = 12; - - private const string CAPTION_FONT_NAME = "Inter"; - private const int MIN_OFFSET = 64; - private const int ANIMATION_DURATION = 200; - // https://github.com/elementary/gala/issues/1317#issuecomment-982484415 - private const int GESTURE_RANGE_LIMIT = 10; - - public Gala.WindowManager? wm { get; construct; } - public GestureTracker gesture_tracker { get; construct; } - public bool opened { get; private set; default = false; } - - private bool handling_gesture = false; - private int modifier_mask; - private Gala.ModalProxy modal_proxy = null; - private Drawing.StyleManager style_manager; - private Clutter.Actor container; - private Clutter.Text caption; - private ShadowEffect shadow_effect; - - private WindowSwitcherIcon? _current_icon = null; - private WindowSwitcherIcon? current_icon { - get { - return _current_icon; - } - set { - if (_current_icon != null) { - _current_icon.selected = false; - } - - _current_icon = value; - if (_current_icon != null) { - _current_icon.selected = true; - _current_icon.grab_key_focus (); - } - - update_caption_text (); - } - } - - private float scaling_factor = 1.0f; - - public WindowSwitcher (Gala.WindowManager wm, GestureTracker gesture_tracker) { - Object ( - wm: wm, - gesture_tracker: gesture_tracker - ); - } - - construct { - style_manager = Drawing.StyleManager.get_instance (); - - container = new Clutter.Actor () { - reactive = true, -#if HAS_MUTTER46 - layout_manager = new Clutter.FlowLayout (Clutter.Orientation.HORIZONTAL) -#else - layout_manager = new Clutter.FlowLayout (Clutter.FlowOrientation.HORIZONTAL) -#endif - }; - - get_accessible ().accessible_name = _("Window switcher"); - container.get_accessible ().accessible_role = LIST; - - caption = new Clutter.Text () { - font_name = CAPTION_FONT_NAME, - ellipsize = END, - line_alignment = CENTER - }; - - add_child (container); - add_child (caption); - - reactive = true; - visible = false; - opacity = 0; - layout_manager = new Clutter.BoxLayout () { - orientation = VERTICAL - }; - - shadow_effect = new ShadowEffect ("window-switcher") { - border_radius = InternalUtils.scale_to_int (9, scaling_factor), - shadow_opacity = 100 - }; - add_effect (shadow_effect); - - scale (); - - container.button_release_event.connect (container_mouse_release); - - // Redraw the components if the colour scheme changes. - style_manager.notify["prefers-color-scheme"].connect (content.invalidate); - - unowned var monitor_manager = wm.get_display ().get_context ().get_backend ().get_monitor_manager (); - monitor_manager.monitors_changed.connect (scale); - - notify["opacity"].connect (() => visible = opacity != 0); - } - - private void scale () { - scaling_factor = wm.get_display ().get_monitor_scale (wm.get_display ().get_current_monitor ()); - - shadow_effect.scale_factor = scaling_factor; - - var margin = InternalUtils.scale_to_int (WRAPPER_PADDING, scaling_factor); - - container.margin_left = margin; - container.margin_right = margin; - container.margin_bottom = margin; - container.margin_top = margin; - - caption.margin_left = margin; - caption.margin_right = margin; - caption.margin_bottom = margin; - } - - protected override void get_preferred_width (float for_height, out float min_width, out float natural_width) { - min_width = 0; - - float preferred_nat_width; - base.get_preferred_width (for_height, null, out preferred_nat_width); - - unowned var display = wm.get_display (); - var monitor = display.get_current_monitor (); - var geom = display.get_monitor_geometry (monitor); - - float container_nat_width; - container.get_preferred_size (null, null, out container_nat_width, null); - - var max_width = float.min ( - geom.width - InternalUtils.scale_to_int (MIN_OFFSET, scaling_factor) * 2, //Don't overflow the monitor - container_nat_width //Ellipsize the label if it's longer than the icons - ); - - natural_width = float.min (max_width, preferred_nat_width); - } - - protected override void draw (Cairo.Context ctx, int width, int height) { - var background_color = Drawing.Color.LIGHT_BACKGROUND; - var border_color = Drawing.Color.LIGHT_BORDER; - var caption_color = "#2e2e31"; - var highlight_color = Drawing.Color.LIGHT_HIGHLIGHT; - - if (style_manager.prefers_color_scheme == Drawing.StyleManager.ColorScheme.DARK) { - background_color = Drawing.Color.DARK_BACKGROUND; - border_color = Drawing.Color.DARK_BORDER; - caption_color = "#fafafa"; - highlight_color = Drawing.Color.DARK_HIGHLIGHT; - } - - var stroke_width = scaling_factor; - - caption.color = Clutter.Color.from_string (caption_color); - - ctx.save (); - ctx.set_operator (Cairo.Operator.CLEAR); - ctx.paint (); - ctx.clip (); - ctx.reset_clip (); - - ctx.set_operator (Cairo.Operator.SOURCE); - - // Offset by 0.5 so cairo draws a stroke on real pixels - Drawing.Utilities.cairo_rounded_rectangle ( - ctx, 0.5, 0.5, - width - stroke_width, - height - stroke_width, - InternalUtils.scale_to_int (9, scaling_factor) - ); - - ctx.set_source_rgba ( - background_color.red, - background_color.green, - background_color.blue, - background_color.alpha - ); - ctx.fill_preserve (); - - ctx.set_line_width (stroke_width); - ctx.set_source_rgba ( - border_color.red, - border_color.green, - border_color.blue, - border_color.alpha - ); - ctx.stroke (); - ctx.restore (); - - // Offset by 0.5 so cairo draws a stroke on real pixels - Drawing.Utilities.cairo_rounded_rectangle ( - ctx, stroke_width + 0.5, stroke_width + 0.5, - width - stroke_width * 2 - 1, - height - stroke_width * 2 - 1, - InternalUtils.scale_to_int (8, scaling_factor) - ); - - ctx.set_line_width (stroke_width); - ctx.set_source_rgba ( - highlight_color.red, - highlight_color.green, - highlight_color.blue, - highlight_color.alpha - ); - ctx.stroke (); - ctx.restore (); - } - - [CCode (instance_pos = -1)] - public void handle_switch_windows ( - Meta.Display display, Meta.Window? window, - Clutter.KeyEvent event, Meta.KeyBinding binding - ) { - if (handling_gesture) { - return; - } - - var workspace = display.get_workspace_manager ().get_active_workspace (); - - // copied from gnome-shell, finds the primary modifier in the mask - var mask = binding.get_mask (); - if (mask == 0) { - modifier_mask = 0; - } else { - modifier_mask = 1; - while (mask > 1) { - mask >>= 1; - modifier_mask <<= 1; - } - } - - if (!opened) { - bool windows_exist; - if (binding.get_name ().has_prefix ("switch-group")) { - windows_exist = collect_current_windows (display, workspace); - } else { - windows_exist = collect_all_windows (display, workspace); - } - - if (!windows_exist) { - return; - } - - open_switcher (); - } - - var binding_name = binding.get_name (); - var backward = binding_name.has_suffix ("-backward"); - - next_window (backward); - } - - public void handle_gesture (GestureDirection direction) { - handling_gesture = true; - - unowned var display = wm.get_display (); - unowned var workspace_manager = display.get_workspace_manager (); - unowned var active_workspace = workspace_manager.get_active_workspace (); - - var windows_exist = collect_all_windows (display, active_workspace); - if (!windows_exist) { - return; - } - open_switcher (); - - // if direction == LEFT we need to move to the end of the list first, thats why last_window_index is set to -1 - var last_window_index = direction == RIGHT ? 0 : -1; - GestureTracker.OnUpdate on_animation_update = (percentage) => { - var window_index = GestureTracker.animation_value (0, GESTURE_RANGE_LIMIT, percentage, true); - - if (window_index >= container.get_n_children ()) { - return; - } - - if (window_index > last_window_index) { - while (last_window_index < window_index) { - next_window (direction == LEFT); - last_window_index++; - } - } else if (window_index < last_window_index) { - while (last_window_index > window_index) { - next_window (direction == RIGHT); - last_window_index--; - } - } - }; - - GestureTracker.OnEnd on_animation_end = (percentage, cancel_action, calculated_duration) => { - handling_gesture = false; - close_switcher (wm.get_display ().get_current_time ()); - }; - - gesture_tracker.connect_handlers (null, (owned) on_animation_update, (owned) on_animation_end); - } - - private bool collect_all_windows (Meta.Display display, Meta.Workspace? workspace) { - var windows = display.get_tab_list (Meta.TabList.NORMAL, workspace); - if (windows == null) { - return false; - } - - unowned var current_window = display.get_tab_current (Meta.TabList.NORMAL, workspace); - if (current_window == null) { - current_icon = null; - } - - container.remove_all_children (); - - foreach (unowned var window in windows) { - var icon = new WindowSwitcherIcon (window, ICON_SIZE, scaling_factor); - if (window == current_window) { - current_icon = icon; - } - - add_icon (icon); - } - - return true; - } - - private bool collect_current_windows (Meta.Display display, Meta.Workspace? workspace) { - var windows = display.get_tab_list (Meta.TabList.NORMAL, workspace); - if (windows == null) { - return false; - } - - unowned var current_window = display.get_tab_current (Meta.TabList.NORMAL, workspace); - if (current_window == null) { - current_icon = null; - return false; - } - - container.remove_all_children (); - - unowned var window_tracker = ((WindowManagerGala) wm).window_tracker; - var app = window_tracker.get_app_for_window (current_window); - foreach (unowned var window in windows) { - if (window_tracker.get_app_for_window (window) == app) { - var icon = new WindowSwitcherIcon (window, ICON_SIZE, scaling_factor); - if (window == current_window) { - current_icon = icon; - } - - add_icon (icon); - } - } - - return true; - } - - private void add_icon (WindowSwitcherIcon icon) { - container.add_child (icon); - icon.get_accessible ().accessible_parent = container.get_accessible (); - - icon.motion_event.connect ((_icon, event) => { - if (current_icon != _icon && !handling_gesture) { - current_icon = (WindowSwitcherIcon) _icon; - } - - return Clutter.EVENT_PROPAGATE; - }); - } - - private void open_switcher () { - if (container.get_n_children () == 0) { - Clutter.get_default_backend ().get_default_seat ().bell_notify (); - return; - } - - if (opened) { - return; - } - - //Although we are setting visible via the opacity notify handler anyway - //we have to set it here manually otherwise the size gotten via get_preferred_size is wrong - visible = true; - - float width, height; - get_preferred_size (null, null, out width, out height); - - unowned var display = wm.get_display (); - var monitor = display.get_current_monitor (); - var geom = display.get_monitor_geometry (monitor); - - set_position ( - (int) (geom.x + (geom.width - width) / 2), - (int) (geom.y + (geom.height - height) / 2) - ); - - toggle_display (true); - } - - private void toggle_display (bool show) { - if (opened == show) { - return; - } - - opened = show; - if (show) { - push_modal (); - } else { - wm.pop_modal (modal_proxy); - get_stage ().set_key_focus (null); - } - - save_easing_state (); - set_easing_duration (wm.enable_animations ? ANIMATION_DURATION : 0); - opacity = show ? 255 : 0; - restore_easing_state (); - } - - private void push_modal () { - modal_proxy = wm.push_modal (this); - modal_proxy.set_keybinding_filter ((binding) => { - var action = Meta.Prefs.get_keybinding_action (binding.get_name ()); - - switch (action) { - case Meta.KeyBindingAction.NONE: - case Meta.KeyBindingAction.LOCATE_POINTER_KEY: - case Meta.KeyBindingAction.SWITCH_APPLICATIONS: - case Meta.KeyBindingAction.SWITCH_APPLICATIONS_BACKWARD: - case Meta.KeyBindingAction.SWITCH_WINDOWS: - case Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD: - case Meta.KeyBindingAction.SWITCH_GROUP: - case Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD: - return false; - default: - break; - } - - return true; - }); - - } - - private void close_switcher (uint32 time, bool cancel = false) { - if (!opened) { - return; - } - - var window = current_icon.window; - if (window == null) { - return; - } - - if (!cancel) { - unowned var workspace = window.get_workspace (); - if (workspace != wm.get_display ().get_workspace_manager ().get_active_workspace ()) { - workspace.activate_with_focus (window, time); - } else { - window.activate (time); - } - } - - toggle_display (false); - } - - private void next_window (bool backward) { - Clutter.Actor actor; - - if (container.get_n_children () == 1 && current_icon != null) { - Clutter.get_default_backend ().get_default_seat ().bell_notify (); - return; - } - - if (current_icon == null) { - actor = container.get_first_child (); - } else if (!backward) { - actor = current_icon.get_next_sibling (); - if (actor == null) { - actor = container.get_first_child (); - } - } else { - actor = current_icon.get_previous_sibling (); - if (actor == null) { - actor = container.get_last_child (); - } - } - - current_icon = (WindowSwitcherIcon) actor; - } - - private void update_caption_text () { - var current_window = current_icon != null ? current_icon.window : null; - var current_caption = current_window != null ? current_window.title : "n/a"; - caption.set_text (current_caption); - } - - public override void key_focus_out () { - if (!handling_gesture) { - close_switcher (wm.get_display ().get_current_time ()); - } - } - -#if HAS_MUTTER45 - private bool container_mouse_release (Clutter.Event event) { -#else - private bool container_mouse_release (Clutter.ButtonEvent event) { -#endif - if (opened && event.get_button () == Clutter.Button.PRIMARY && !handling_gesture) { - close_switcher (event.get_time ()); - } - - return true; - } - -#if HAS_MUTTER45 - public override bool key_release_event (Clutter.Event event) { -#else - public override bool key_release_event (Clutter.KeyEvent event) { -#endif - if ((get_current_modifiers () & modifier_mask) == 0 && !handling_gesture) { - close_switcher (event.get_time ()); - } - - return Clutter.EVENT_PROPAGATE; - } - -#if HAS_MUTTER45 - public override bool key_press_event (Clutter.Event event) { -#else - public override bool key_press_event (Clutter.KeyEvent event) { -#endif - switch (event.get_key_symbol ()) { - case Clutter.Key.Right: - if (!handling_gesture) { - next_window (false); - } - return Clutter.EVENT_STOP; - case Clutter.Key.Left: - if (!handling_gesture) { - next_window (true); - } - return Clutter.EVENT_STOP; - case Clutter.Key.Escape: - close_switcher (event.get_time (), true); - return Clutter.EVENT_PROPAGATE; - case Clutter.Key.Return: - close_switcher (event.get_time (), false); - return Clutter.EVENT_PROPAGATE; - } - - return Clutter.EVENT_PROPAGATE; - } - - - private inline Clutter.ModifierType get_current_modifiers () { - Clutter.ModifierType modifiers; - wm.get_display ().get_cursor_tracker ().get_pointer (null, out modifiers); - - return modifiers & Clutter.ModifierType.MODIFIER_MASK; - } -} diff --git a/src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala b/src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala deleted file mode 100644 index c85a12cda..000000000 --- a/src/Widgets/WindowSwitcher/WindowSwitcherIcon.vala +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2023 elementary, Inc. - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -public class Gala.WindowSwitcherIcon : CanvasActor { - private const int WRAPPER_BORDER_RADIUS = 3; - - public Meta.Window window { get; construct; } - - private WindowIcon icon; - - private bool _selected = false; - public bool selected { - get { - return _selected; - } - set { - _selected = value; - content.invalidate (); - } - } - - private float _scale_factor = 1.0f; - public float scale_factor { - get { - return _scale_factor; - } - set { - _scale_factor = value; - - update_size (); - } - } - - public WindowSwitcherIcon (Meta.Window window, int icon_size, float scale_factor) { - Object (window: window); - - icon = new WindowIcon (window, InternalUtils.scale_to_int (icon_size, scale_factor)); - icon.add_constraint (new Clutter.AlignConstraint (this, Clutter.AlignAxis.BOTH, 0.5f)); - add_child (icon); - - get_accessible ().accessible_name = window.title; - get_accessible ().accessible_role = LIST_ITEM; - get_accessible ().notify_state_change (Atk.StateType.FOCUSABLE, true); - - reactive = true; - - this.scale_factor = scale_factor; - } - - private void update_size () { - var indicator_size = InternalUtils.scale_to_int ( - (WindowSwitcher.ICON_SIZE + WindowSwitcher.WRAPPER_PADDING * 2), - scale_factor - ); - set_size (indicator_size, indicator_size); - } - - protected override void draw (Cairo.Context ctx, int width, int height) { - ctx.save (); - ctx.set_operator (Cairo.Operator.CLEAR); - ctx.paint (); - ctx.clip (); - ctx.reset_clip (); - - if (selected) { - // draw rect - var rgba = Drawing.StyleManager.get_instance ().theme_accent_color; - ctx.set_source_rgba ( - rgba.red, - rgba.green, - rgba.blue, - rgba.alpha - ); - var rect_radius = InternalUtils.scale_to_int (WRAPPER_BORDER_RADIUS, scale_factor); - Drawing.Utilities.cairo_rounded_rectangle (ctx, 0, 0, width, height, rect_radius); - ctx.set_operator (Cairo.Operator.SOURCE); - ctx.fill (); - - ctx.restore (); - } - - get_accessible ().notify_state_change (Atk.StateType.SELECTED, selected); - get_accessible ().notify_state_change (Atk.StateType.FOCUSED, selected); - } -} diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 8d1122132..96c7c00da 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -62,8 +62,6 @@ namespace Gala { private Meta.PluginInfo info; - private WindowSwitcher? window_switcher = null; - public ActivatableComponent? window_overview { get; private set; } public ScreenSaverManager? screensaver { get; private set; } @@ -325,18 +323,6 @@ namespace Gala { workspace_view.open (); }); - if (plugin_manager.window_switcher_provider == null) { - window_switcher = new WindowSwitcher (this, gesture_tracker); - ui_group.add_child (window_switcher); - - Meta.KeyBinding.set_custom_handler ("switch-applications", (Meta.KeyHandlerFunc) window_switcher.handle_switch_windows); - Meta.KeyBinding.set_custom_handler ("switch-applications-backward", (Meta.KeyHandlerFunc) window_switcher.handle_switch_windows); - Meta.KeyBinding.set_custom_handler ("switch-windows", (Meta.KeyHandlerFunc) window_switcher.handle_switch_windows); - Meta.KeyBinding.set_custom_handler ("switch-windows-backward", (Meta.KeyHandlerFunc) window_switcher.handle_switch_windows); - Meta.KeyBinding.set_custom_handler ("switch-group", (Meta.KeyHandlerFunc) window_switcher.handle_switch_windows); - Meta.KeyBinding.set_custom_handler ("switch-group-backward", (Meta.KeyHandlerFunc) window_switcher.handle_switch_windows); - } - if (plugin_manager.window_overview_provider == null || (window_overview = (plugin_manager.get_plugin (plugin_manager.window_overview_provider) as ActivatableComponent)) == null) { window_overview = new WindowOverview (this); @@ -562,9 +548,6 @@ namespace Gala { var three_fingers_move_to_workspace = fingers == 3 && three_finger_swipe_horizontal == "move-to-workspace"; var four_fingers_move_to_workspace = fingers == 4 && four_finger_swipe_horizontal == "move-to-workspace"; - var three_fingers_switch_windows = fingers == 3 && three_finger_swipe_horizontal == "switch-windows"; - var four_fingers_switch_windows = fingers == 4 && four_finger_swipe_horizontal == "switch-windows"; - switch_workspace_with_gesture = three_fingers_switch_to_workspace || four_fingers_switch_to_workspace; if (switch_workspace_with_gesture) { var direction = gesture_tracker.settings.get_natural_scroll_direction (gesture); @@ -586,11 +569,6 @@ namespace Gala { switch_to_next_workspace (direction, display.get_current_time ()); return; } - - var switch_windows = three_fingers_switch_windows || four_fingers_switch_windows; - if (switch_windows && !window_switcher.opened) { - window_switcher.handle_gesture (gesture.direction); - } } /** diff --git a/src/meson.build b/src/meson.build index e2e61807b..450c7b6a0 100644 --- a/src/meson.build +++ b/src/meson.build @@ -63,8 +63,6 @@ gala_bin_sources = files( 'Widgets/WindowCloneContainer.vala', 'Widgets/WindowIconActor.vala', 'Widgets/WindowOverview.vala', - 'Widgets/WindowSwitcher/WindowSwitcher.vala', - 'Widgets/WindowSwitcher/WindowSwitcherIcon.vala', 'Widgets/WorkspaceClone.vala', 'Widgets/WorkspaceInsertThumb.vala', ) From 93c0c39a15189d5728b8f93577fdd68ea568e412 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Sat, 21 Sep 2024 11:09:25 +0200 Subject: [PATCH 15/15] Add DBUsGestureProvider --- src/DBus.vala | 9 +++++++++ src/Gestures/DBusGestureProvider.vala | 19 +++++++++++++++++++ src/Gestures/Gesture.vala | 2 +- src/meson.build | 1 + 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 src/Gestures/DBusGestureProvider.vala diff --git a/src/DBus.vala b/src/DBus.vala index 1034883b3..8fedcc9f1 100644 --- a/src/DBus.vala +++ b/src/DBus.vala @@ -30,6 +30,15 @@ public class Gala.DBus { () => {}, () => warning ("Could not acquire name\n") ); + Bus.own_name (BusType.SESSION, "io.elementary.desktop.wm", BusNameOwnerFlags.NONE, + (connection) => { + try { + connection.register_object ("/io/elementary/desktop/wm", new DBusGestureProvider ()); + } catch (Error e) { warning (e.message); } + }, + () => {}, + () => warning ("Could not acquire name") ); + Bus.own_name (BusType.SESSION, "org.gnome.Shell", BusNameOwnerFlags.NONE, (connection) => { try { diff --git a/src/Gestures/DBusGestureProvider.vala b/src/Gestures/DBusGestureProvider.vala new file mode 100644 index 000000000..d94023fa1 --- /dev/null +++ b/src/Gestures/DBusGestureProvider.vala @@ -0,0 +1,19 @@ +[DBus (name = "io.elementary.desktop.wm.GestureProvider")] +public class Gala.DBusGestureProvider : Object { + public signal void on_gesture_detected (Gesture gesture); + public signal void on_begin (double percentage); + public signal void on_update (double percentage); + public signal void on_end (double percentage, bool cancel_action, int calculated_duration); + + private GestureTracker gesture_tracker; + + construct { + gesture_tracker = new GestureTracker (0, 0); + gesture_tracker.enable_touchpad (); + + gesture_tracker.on_gesture_detected.connect ((gesture) => on_gesture_detected (gesture)); + gesture_tracker.on_begin.connect ((percentage) => on_begin (percentage)); + gesture_tracker.on_update.connect ((percentage) => on_update (percentage)); + gesture_tracker.on_end.connect ((percentage, cancel_action, calculated_duration) => on_end (percentage, cancel_action, calculated_duration)); + } +} diff --git a/src/Gestures/Gesture.vala b/src/Gestures/Gesture.vala index 9bccedaf7..dd7a59f31 100644 --- a/src/Gestures/Gesture.vala +++ b/src/Gestures/Gesture.vala @@ -34,7 +34,7 @@ namespace Gala { OUT = 6, } - public class Gesture { + public struct Gesture { public Clutter.EventType type; public GestureDirection direction; public int fingers; diff --git a/src/meson.build b/src/meson.build index 450c7b6a0..348380635 100644 --- a/src/meson.build +++ b/src/meson.build @@ -33,6 +33,7 @@ gala_bin_sources = files( 'ColorFilters/ColorblindnessCorrectionEffect.vala', 'ColorFilters/FilterManager.vala', 'ColorFilters/MonochromeEffect.vala', + 'Gestures/DBusGestureProvider.vala', 'Gestures/Gesture.vala', 'Gestures/GestureSettings.vala', 'Gestures/GestureTracker.vala',