From 867952d1274aa5644b3967b30d78ee0d4e92ef63 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Sat, 15 Aug 2026 13:17:47 +0200 Subject: [PATCH 1/5] Rename TouchpadBackend to TouchpadSwipeBackend --- .../{TouchpadBackend.vala => TouchpadSwipeBackend.vala} | 8 ++++---- lib/Gestures/Triggers/GlobalTrigger.vala | 4 ++-- lib/meson.build | 2 +- po/POTFILES | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename lib/Gestures/Backends/{TouchpadBackend.vala => TouchpadSwipeBackend.vala} (94%) diff --git a/lib/Gestures/Backends/TouchpadBackend.vala b/lib/Gestures/Backends/TouchpadSwipeBackend.vala similarity index 94% rename from lib/Gestures/Backends/TouchpadBackend.vala rename to lib/Gestures/Backends/TouchpadSwipeBackend.vala index c1219cc9b..e6c6bded2 100644 --- a/lib/Gestures/Backends/TouchpadBackend.vala +++ b/lib/Gestures/Backends/TouchpadSwipeBackend.vala @@ -5,7 +5,7 @@ * Authored by: Leonhard Kargl */ -private class Gala.TouchpadBackend : Object, GestureBackend { +private class Gala.TouchpadSwipeBackend : Object, GestureBackend { public enum Group { NONE, MULTITASKING_VIEW, @@ -26,7 +26,7 @@ private class Gala.TouchpadBackend : Object, GestureBackend { public Clutter.Actor actor { get; construct; } public Group group { get; construct; } - private static List instances = new List (); + private static List instances = new List (); private State state = NONE; private GestureDirection direction = UNKNOWN; @@ -34,11 +34,11 @@ private class Gala.TouchpadBackend : Object, GestureBackend { private double distance_y = 0; private double distance = 0; - public TouchpadBackend (Clutter.Actor actor, Group group) { + public TouchpadSwipeBackend (Clutter.Actor actor, Group group) { Object (actor: actor, group: group); } - ~TouchpadBackend () { + ~TouchpadSwipeBackend () { instances.remove (this); } diff --git a/lib/Gestures/Triggers/GlobalTrigger.vala b/lib/Gestures/Triggers/GlobalTrigger.vala index 5d856309a..82fd19edd 100644 --- a/lib/Gestures/Triggers/GlobalTrigger.vala +++ b/lib/Gestures/Triggers/GlobalTrigger.vala @@ -27,8 +27,8 @@ public class Gala.GlobalTrigger : Object, GestureTrigger { } internal void enable_backends (GestureController controller) { - var group = action == MULTITASKING_VIEW || action == SWITCH_WORKSPACE ? TouchpadBackend.Group.MULTITASKING_VIEW : TouchpadBackend.Group.NONE; + var group = action == MULTITASKING_VIEW || action == SWITCH_WORKSPACE ? TouchpadSwipeBackend.Group.MULTITASKING_VIEW : TouchpadSwipeBackend.Group.NONE; controller.enable_backend (ToucheggBackend.get_default ()); - controller.enable_backend (new TouchpadBackend (wm.stage, group)); + controller.enable_backend (new TouchpadSwipeBackend (wm.stage, group)); } } diff --git a/lib/meson.build b/lib/meson.build index 137badfdb..20d801f48 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -22,7 +22,7 @@ gala_lib_sources = files( 'Effects/ShadowEffect.vala', 'Gestures/Backends/GestureBackend.vala', 'Gestures/Backends/ToucheggBackend.vala', - 'Gestures/Backends/TouchpadBackend.vala', + 'Gestures/Backends/TouchpadSwipeBackend.vala', 'Gestures/Backends/ScrollBackend.vala', 'Gestures/Targets/ActorTarget.vala', 'Gestures/Targets/GestureTarget.vala', diff --git a/po/POTFILES b/po/POTFILES index 584195233..870388dc4 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -34,7 +34,7 @@ lib/Effects/RoundedCornersEffect.vala lib/Effects/ShadowEffect.vala lib/Gestures/Backends/GestureBackend.vala lib/Gestures/Backends/ToucheggBackend.vala -lib/Gestures/Backends/TouchpadBackend.vala +lib/Gestures/Backends/TouchpadSwipeBackend.vala lib/Gestures/Backends/ScrollBackend.vala lib/Gestures/Targets/ActorTarget.vala lib/Gestures/Targets/GestureTarget.vala From 938e11405ce00bb1cd0869c7f06660c3c0603cf6 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Sat, 15 Aug 2026 13:19:52 +0200 Subject: [PATCH 2/5] Implement a TouchpadPinchBackend --- lib/Gestures/Backends/ToucheggBackend.vala | 2 +- .../Backends/TouchpadPinchBackend.vala | 89 +++++++++++++++++++ lib/Gestures/Triggers/GlobalTrigger.vala | 1 + lib/meson.build | 1 + 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 lib/Gestures/Backends/TouchpadPinchBackend.vala diff --git a/lib/Gestures/Backends/ToucheggBackend.vala b/lib/Gestures/Backends/ToucheggBackend.vala index c280efc09..3d826de74 100644 --- a/lib/Gestures/Backends/ToucheggBackend.vala +++ b/lib/Gestures/Backends/ToucheggBackend.vala @@ -180,7 +180,7 @@ private class Gala.ToucheggBackend : Object, GestureBackend { signal_params.get ("(uudiut)", out type, out direction, out percentage, out fingers, out performed_on_device_type, out elapsed_time); - if (Meta.Util.is_wayland_compositor () && performed_on_device_type != DeviceType.TOUCHSCREEN && type != PINCH) { + if (Meta.Util.is_wayland_compositor () && performed_on_device_type != DeviceType.TOUCHSCREEN) { return; } diff --git a/lib/Gestures/Backends/TouchpadPinchBackend.vala b/lib/Gestures/Backends/TouchpadPinchBackend.vala new file mode 100644 index 000000000..3e6925e63 --- /dev/null +++ b/lib/Gestures/Backends/TouchpadPinchBackend.vala @@ -0,0 +1,89 @@ +/* + * Copyright 2026 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +private class Gala.TouchpadPinchBackend : Object, GestureBackend { + private enum State { + NONE, + IGNORED, + ONGOING + } + + public Clutter.Actor actor { get; construct; } + + private State state = NONE; + private double percentage = 0.0; + + public TouchpadPinchBackend (Clutter.Actor actor) { + Object (actor: actor); + } + + construct { + actor.captured_event.connect (handle_event); + } + + public override void cancel_gesture () { + state = IGNORED; + } + + private bool handle_event (Clutter.Event event) { + if (event.get_type () != TOUCHPAD_PINCH) { + return Clutter.EVENT_PROPAGATE; + } + + if (state != ONGOING && (event.get_gesture_phase () == END || event.get_gesture_phase () == CANCEL)) { + reset (); + return Clutter.EVENT_PROPAGATE; + } + + if (state == IGNORED) { + return Clutter.EVENT_PROPAGATE; + } + + if (state != ONGOING) { + var gesture = new Gesture (); + gesture.direction = OUT; + gesture.type = event.get_type (); + gesture.fingers = (int) event.get_touchpad_gesture_finger_count (); + gesture.performed_on_device_type = event.get_source_device ().get_device_type (); + + if (!on_gesture_detected (gesture, event.get_time ())) { + state = IGNORED; + return Clutter.EVENT_PROPAGATE; + } + + state = ONGOING; + } + + if (event.get_gesture_phase () != END) { + /* When the gesture ends the pinch scale is already reset */ + percentage = event.get_gesture_pinch_scale () - 1.0; + } + + switch (event.get_gesture_phase ()) { + case BEGIN: + on_begin (0, event.get_time ()); + break; + + case UPDATE: + on_update (percentage, event.get_time ()); + break; + + case END: + case CANCEL: + on_end (percentage, event.get_time ()); + reset (); + break; + } + + return Clutter.EVENT_STOP; + } + + private void reset () { + state = NONE; + percentage = 0.0; + } +} diff --git a/lib/Gestures/Triggers/GlobalTrigger.vala b/lib/Gestures/Triggers/GlobalTrigger.vala index 82fd19edd..a1fa357af 100644 --- a/lib/Gestures/Triggers/GlobalTrigger.vala +++ b/lib/Gestures/Triggers/GlobalTrigger.vala @@ -30,5 +30,6 @@ public class Gala.GlobalTrigger : Object, GestureTrigger { var group = action == MULTITASKING_VIEW || action == SWITCH_WORKSPACE ? TouchpadSwipeBackend.Group.MULTITASKING_VIEW : TouchpadSwipeBackend.Group.NONE; controller.enable_backend (ToucheggBackend.get_default ()); controller.enable_backend (new TouchpadSwipeBackend (wm.stage, group)); + controller.enable_backend (new TouchpadPinchBackend (wm.stage)); } } diff --git a/lib/meson.build b/lib/meson.build index 20d801f48..11821b0a7 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -22,6 +22,7 @@ gala_lib_sources = files( 'Effects/ShadowEffect.vala', 'Gestures/Backends/GestureBackend.vala', 'Gestures/Backends/ToucheggBackend.vala', + 'Gestures/Backends/TouchpadPinchBackend.vala', 'Gestures/Backends/TouchpadSwipeBackend.vala', 'Gestures/Backends/ScrollBackend.vala', 'Gestures/Targets/ActorTarget.vala', From 5b4217ab807c93bd3815744e3798c520792c14b1 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Sat, 15 Aug 2026 13:28:29 +0200 Subject: [PATCH 3/5] Only use on MUTTER49 or higher --- lib/Gestures/Backends/ToucheggBackend.vala | 4 ++++ lib/Gestures/Triggers/GlobalTrigger.vala | 3 +++ 2 files changed, 7 insertions(+) diff --git a/lib/Gestures/Backends/ToucheggBackend.vala b/lib/Gestures/Backends/ToucheggBackend.vala index 3d826de74..137563024 100644 --- a/lib/Gestures/Backends/ToucheggBackend.vala +++ b/lib/Gestures/Backends/ToucheggBackend.vala @@ -180,7 +180,11 @@ private class Gala.ToucheggBackend : Object, GestureBackend { signal_params.get ("(uudiut)", out type, out direction, out percentage, out fingers, out performed_on_device_type, out elapsed_time); +#if HAS_MUTTER49 if (Meta.Util.is_wayland_compositor () && performed_on_device_type != DeviceType.TOUCHSCREEN) { +#else + if (Meta.Util.is_wayland_compositor () && performed_on_device_type != DeviceType.TOUCHSCREEN && type != PINCH) { +#endif return; } diff --git a/lib/Gestures/Triggers/GlobalTrigger.vala b/lib/Gestures/Triggers/GlobalTrigger.vala index a1fa357af..4043973ee 100644 --- a/lib/Gestures/Triggers/GlobalTrigger.vala +++ b/lib/Gestures/Triggers/GlobalTrigger.vala @@ -30,6 +30,9 @@ public class Gala.GlobalTrigger : Object, GestureTrigger { var group = action == MULTITASKING_VIEW || action == SWITCH_WORKSPACE ? TouchpadSwipeBackend.Group.MULTITASKING_VIEW : TouchpadSwipeBackend.Group.NONE; controller.enable_backend (ToucheggBackend.get_default ()); controller.enable_backend (new TouchpadSwipeBackend (wm.stage, group)); +#if HAS_MUTTER49 + // On mutter < 49 there is a bug that pinch gestures aren't delivered when over a window so rely on touch egg there controller.enable_backend (new TouchpadPinchBackend (wm.stage)); +#endif } } From 36a9ec9f3a61a32a01d10cf25974031e5b92e299 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Sun, 16 Aug 2026 18:38:30 +0200 Subject: [PATCH 4/5] Dont cancel --- lib/Gestures/Backends/TouchpadPinchBackend.vala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Gestures/Backends/TouchpadPinchBackend.vala b/lib/Gestures/Backends/TouchpadPinchBackend.vala index 3e6925e63..7ddd4fc2a 100644 --- a/lib/Gestures/Backends/TouchpadPinchBackend.vala +++ b/lib/Gestures/Backends/TouchpadPinchBackend.vala @@ -58,7 +58,7 @@ private class Gala.TouchpadPinchBackend : Object, GestureBackend { state = ONGOING; } - if (event.get_gesture_phase () != END) { + if (event.get_gesture_phase () == UPDATE) { /* When the gesture ends the pinch scale is already reset */ percentage = event.get_gesture_pinch_scale () - 1.0; } From ae96b769ceb687c17e3a88018432eb7fbc8ff6a6 Mon Sep 17 00:00:00 2001 From: Leonhard Date: Sun, 16 Aug 2026 18:39:07 +0200 Subject: [PATCH 5/5] Ignore gesture where we didn't receive a begin --- lib/Gestures/Backends/TouchpadPinchBackend.vala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/Gestures/Backends/TouchpadPinchBackend.vala b/lib/Gestures/Backends/TouchpadPinchBackend.vala index 7ddd4fc2a..45fb0a295 100644 --- a/lib/Gestures/Backends/TouchpadPinchBackend.vala +++ b/lib/Gestures/Backends/TouchpadPinchBackend.vala @@ -43,6 +43,12 @@ private class Gala.TouchpadPinchBackend : Object, GestureBackend { return Clutter.EVENT_PROPAGATE; } + if (state == NONE && event.get_gesture_phase () != BEGIN) { + /* We never got a begin phase so something else initially handled the gesture + but disappeared. Don't start handling it now. Will probably never happen */ + return Clutter.EVENT_PROPAGATE; + } + if (state != ONGOING) { var gesture = new Gesture (); gesture.direction = OUT;