From 9dc99ae17e3e5a6b6f37b1451f394cdcc337b5c6 Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Mon, 2 Mar 2026 14:16:14 +0100 Subject: [PATCH 1/2] Implement maximize gesture --- lib/Gestures/Gesture.vala | 1 + lib/Gestures/GestureSettings.vala | 5 ++ src/Widgets/WindowMaximizer.vala | 94 +++++++++++++++++++++++++++++++ src/WindowManager.vala | 1 + src/meson.build | 1 + 5 files changed, 102 insertions(+) create mode 100644 src/Widgets/WindowMaximizer.vala diff --git a/lib/Gestures/Gesture.vala b/lib/Gestures/Gesture.vala index 828975ac2..3f316a063 100644 --- a/lib/Gestures/Gesture.vala +++ b/lib/Gestures/Gesture.vala @@ -40,6 +40,7 @@ namespace Gala { SWITCH_WINDOWS, MULTITASKING_VIEW, ZOOM, + TOGGLE_MAXIMIZED, CUSTOM, N_ACTIONS; diff --git a/lib/Gestures/GestureSettings.vala b/lib/Gestures/GestureSettings.vala index a6a5c367c..162ebd66e 100644 --- a/lib/Gestures/GestureSettings.vala +++ b/lib/Gestures/GestureSettings.vala @@ -72,6 +72,11 @@ private class Gala.GestureSettings : Object { fingers == 4 && four_finger_swipe_up == "multitasking-view") { return MULTITASKING_VIEW; } + + if (fingers == 3 && three_finger_swipe_up == "toggle-maximized" || + fingers == 4 && four_finger_swipe_up == "toggle-maximized") { + return TOGGLE_MAXIMIZED; + } } break; diff --git a/src/Widgets/WindowMaximizer.vala b/src/Widgets/WindowMaximizer.vala new file mode 100644 index 000000000..664881d62 --- /dev/null +++ b/src/Widgets/WindowMaximizer.vala @@ -0,0 +1,94 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.WindowMaximizer : ActorTarget, RootTarget { + public WindowManager wm { get; construct; } + + public Clutter.Actor? actor { get { return tile; } } + + private Clutter.Actor tile; + private GestureController controller; + + private Meta.Window? current_window; + + public WindowMaximizer (WindowManager wm) { + Object (wm: wm); + } + + construct { + tile = new Clutter.Actor () { + opacity = 200 + }; + Drawing.StyleManager.get_instance ().bind_property ("theme-accent-color", tile, "background-color", SYNC_CREATE); + + add_child (tile); + + controller = new GestureController (TOGGLE_MAXIMIZED); + controller.add_trigger (new GlobalTrigger (TOGGLE_MAXIMIZED, wm)); + add_gesture_controller (controller); + + } + + public override void start_progress (GestureAction action) requires (action == TOGGLE_MAXIMIZED) { + current_window = wm.get_display ().focus_window; + + if (current_window == null || !current_window.can_maximize ()) { + return; + } + + visible = true; + controller.progress = (double) current_window.maximized_horizontally; + + var workarea = wm.get_display ().get_workspace_manager ().get_active_workspace ().get_work_area_for_monitor (current_window.get_monitor ()); + var initial_rect = get_initial_rect (workarea); + + add_target (new PropertyTarget (TOGGLE_MAXIMIZED, tile, "x", typeof (float), (float) initial_rect.x, (float) workarea.x)); + add_target (new PropertyTarget (TOGGLE_MAXIMIZED, tile, "y", typeof (float), (float) initial_rect.y, (float) workarea.y)); + add_target (new PropertyTarget (TOGGLE_MAXIMIZED, tile, "width", typeof (float), (float) initial_rect.width, (float) workarea.width)); + add_target (new PropertyTarget (TOGGLE_MAXIMIZED, tile, "height", typeof (float), (float) initial_rect.height, (float) workarea.height)); + + var window_actor = (Meta.WindowActor) current_window.get_compositor_private (); + wm.window_group.set_child_above_sibling (this, window_actor); + } + + private Mtk.Rectangle get_initial_rect (Mtk.Rectangle workarea) requires (current_window != null) { + if (!current_window.maximized_horizontally) { + return current_window.get_frame_rect (); + } + + var unmaximized_geometry = WindowListener.get_default ().get_unmaximized_state_geometry (current_window); + if (unmaximized_geometry != null) { + return unmaximized_geometry.inner; + } + + return { workarea.x + (workarea.width / 2), workarea.y + (workarea.height / 2), 0, 0 }; + } + + public override void commit_progress (GestureAction action, double commit) requires (action == TOGGLE_MAXIMIZED) { + if (current_window == null) { + return; + } + + if (commit == 0 && current_window.maximized_horizontally) { +#if HAS_MUTTER49 + current_window.unmaximize (); +#else + current_window.unmaximize (BOTH); +#endif + } else if (commit == 1 && !current_window.maximized_horizontally) { +#if HAS_MUTTER49 + current_window.maximize (); +#else + current_window.maximize (BOTH); +#endif + } + + remove_all_targets (); + visible = false; + current_window = null; + } +} diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 7935aa2e0..88d7bf12a 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -260,6 +260,7 @@ namespace Gala { background_group = new BackgroundContainer (display); ((BackgroundContainer)background_group).show_background_menu.connect (daemon_manager.show_background_menu); window_group.add_child (background_group); + window_group.add_child (new WindowMaximizer (this)); window_group.set_child_below_sibling (background_group, null); #if HAS_MUTTER48 diff --git a/src/meson.build b/src/meson.build index 1266899a7..a7dc9d604 100644 --- a/src/meson.build +++ b/src/meson.build @@ -66,6 +66,7 @@ gala_bin_sources = files( 'Widgets/PointerLocator.vala', 'Widgets/SessionLocker.vala', 'Widgets/SelectionArea.vala', + 'Widgets/WindowMaximizer.vala', 'Widgets/WindowOverview.vala', 'Widgets/WindowSwitcher/WindowSwitcher.vala', 'Widgets/WindowSwitcher/WindowSwitcherIcon.vala', From bc38426a07bd7e1e29079c456333a038cb2367ce Mon Sep 17 00:00:00 2001 From: Leonhard Kargl Date: Mon, 2 Mar 2026 21:43:13 +0100 Subject: [PATCH 2/2] TouchpadBackend: Make sure we don't steal gestures from other instances If we managed to register our captured event handler before another instance it can happen that we steal the gesture from the other instance. E.g. the user clearly swipes horizontal but once they swiped long enough they drifted over the vertical threshold and we will start stealing the gesture. --- lib/Gestures/Backends/TouchpadBackend.vala | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/Gestures/Backends/TouchpadBackend.vala b/lib/Gestures/Backends/TouchpadBackend.vala index c1219cc9b..2eefeea93 100644 --- a/lib/Gestures/Backends/TouchpadBackend.vala +++ b/lib/Gestures/Backends/TouchpadBackend.vala @@ -74,6 +74,14 @@ private class Gala.TouchpadBackend : Object, GestureBackend { event.get_gesture_motion_delta_unaccelerated (out delta_x, out delta_y); if (state != ONGOING) { + foreach (var instance in instances) { + if (instance != this && (instance.group == NONE || instance.group != group) && instance.state == ONGOING) { + /* Another instance is already recognizing this gesture, make sure we don't steal it */ + state = IGNORED; + return Clutter.EVENT_PROPAGATE; + } + } + distance_x += delta_x; distance_y += delta_y;