From cfe5fb7e6a876e7d3846490ddd1a358d3123b946 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/LayoutManager.vala | 2 + src/Widgets/WindowMaximizer.vala | 94 +++++++++++++++++++++++++++++++ src/meson.build | 1 + 5 files changed, 103 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/LayoutManager.vala b/src/LayoutManager.vala index ec5954171..50b7f261f 100644 --- a/src/LayoutManager.vala +++ b/src/LayoutManager.vala @@ -105,6 +105,8 @@ public class Gala.LayoutManager : Object { } public void init_ui (WindowManager wm) { + window_group.add_child (new WindowMaximizer (wm)); + multitasking_view = new MultitaskingView (wm); ui_group.add_child (multitasking_view); diff --git a/src/Widgets/WindowMaximizer.vala b/src/Widgets/WindowMaximizer.vala new file mode 100644 index 000000000..28e02a3b3 --- /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 ()) { + current_window = null; + 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 (); + get_parent ().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/meson.build b/src/meson.build index 6b941f249..cea055c59 100644 --- a/src/meson.build +++ b/src/meson.build @@ -77,6 +77,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 b307b7a750dd0bcfd67ee637d22edd2d9cbb87e5 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;