diff --git a/src/Gestures/GestureTracker.vala b/src/Gestures/GestureTracker.vala index 9ca525bb9..f5a62c975 100644 --- a/src/Gestures/GestureTracker.vala +++ b/src/Gestures/GestureTracker.vala @@ -122,9 +122,15 @@ public class Gala.GestureTracker : Object { public delegate void OnEnd (double percentage, int completions, int calculated_duration); /** - * Backend used if enable_touchpad is called. + * Backend used if enable_touchpad is called on wayland. */ - private ToucheggBackend touchpad_backend; + private TouchpadBackend touchpad_backend; + + /** + * Backend used if enable_touchpad is called on X or for + * touchscreen and pinch gestures if enable_touchpad is called on wayland. + */ + private ToucheggBackend touchegg_backend; /** * Scroll backend used if enable_scroll is called. @@ -155,12 +161,20 @@ public class Gala.GestureTracker : Object { /** * Allow to receive touchpad multi-touch gestures. */ - public void enable_touchpad () { - touchpad_backend = ToucheggBackend.get_default (); - touchpad_backend.on_gesture_detected.connect (gesture_detected); - touchpad_backend.on_begin.connect (gesture_begin); - touchpad_backend.on_update.connect (gesture_update); - touchpad_backend.on_end.connect (gesture_end); + public void enable_touchpad (Clutter.Actor actor) { + if (Meta.Util.is_wayland_compositor ()) { + touchpad_backend = new TouchpadBackend (actor); + touchpad_backend.on_gesture_detected.connect (gesture_detected); + touchpad_backend.on_begin.connect (gesture_begin); + touchpad_backend.on_update.connect (gesture_update); + touchpad_backend.on_end.connect (gesture_end); + } + + touchegg_backend = ToucheggBackend.get_default (); // Will automatically filter events on wayland + touchegg_backend.on_gesture_detected.connect (gesture_detected); + touchegg_backend.on_begin.connect (gesture_begin); + touchegg_backend.on_update.connect (gesture_update); + touchegg_backend.on_end.connect (gesture_end); } /** diff --git a/src/Gestures/ToucheggBackend.vala b/src/Gestures/ToucheggBackend.vala index df3f076d1..9169f5deb 100644 --- a/src/Gestures/ToucheggBackend.vala +++ b/src/Gestures/ToucheggBackend.vala @@ -192,6 +192,10 @@ public 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) { + return; + } + var delta = percentage * DELTA_MULTIPLIER; switch (signal_name) { diff --git a/src/Gestures/TouchpadBackend.vala b/src/Gestures/TouchpadBackend.vala new file mode 100644 index 000000000..4d0819699 --- /dev/null +++ b/src/Gestures/TouchpadBackend.vala @@ -0,0 +1,125 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-3.0-or-later + * + * Authored by: Leonhard Kargl + */ + +public class Gala.TouchpadBackend : Object, GestureBackend { + private const int TOUCHPAD_BASE_HEIGHT = 300; + private const int TOUCHPAD_BASE_WIDTH = 400; + private const int DRAG_THRESHOLD_DISTANCE = 16; + + private enum State { + NONE, + IGNORED, + ONGOING + } + + public Clutter.Actor actor { get; construct; } + + private State state = NONE; + private GestureDirection direction = UNKNOWN; + private double distance_x = 0; + private double distance_y = 0; + private double distance = 0; + + public TouchpadBackend (Clutter.Actor actor) { + Object (actor: actor); + } + + construct { + actor.captured_event.connect (on_captured_event); + } + + private bool on_captured_event (Clutter.Event event) { + if (event.get_type () != TOUCHPAD_SWIPE) { + return Clutter.EVENT_PROPAGATE; + } + + if (state == IGNORED) { + if (event.get_gesture_phase () == END || event.get_gesture_phase () == CANCEL) { + reset (); + } + + return Clutter.EVENT_PROPAGATE; + } + + double delta_x, delta_y; + event.get_gesture_motion_delta_unaccelerated (out delta_x, out delta_y); + + if (state == NONE) { + distance_x += delta_x; + distance_y += delta_y; + + var distance = Math.sqrt (distance_x * distance_x + distance_y * distance_y); + + if (distance >= DRAG_THRESHOLD_DISTANCE) { + var gesture = new Gesture (); + gesture.type = event.get_type (); + gesture.fingers = (int) event.get_touchpad_gesture_finger_count (); + gesture.performed_on_device_type = event.get_device ().get_device_type (); + direction = gesture.direction = get_direction (distance_x, distance_y); + + if (!on_gesture_detected (gesture, event.get_time ())) { + state = IGNORED; + return Clutter.EVENT_PROPAGATE; + } + + state = ONGOING; + } else { + return Clutter.EVENT_PROPAGATE; + } + } + + distance += get_value_for_direction (delta_x, delta_y); + + var percentage = get_percentage (distance); + + switch (event.get_gesture_phase ()) { + case BEGIN: + on_begin (distance, 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; + distance = 0; + direction = UNKNOWN; + distance_x = 0; + distance_y = 0; + } + + private double get_percentage (double value) { + return value / (direction == LEFT || direction == RIGHT ? TOUCHPAD_BASE_WIDTH : TOUCHPAD_BASE_HEIGHT); + } + + private double get_value_for_direction (double delta_x, double delta_y) { + if (direction == LEFT || direction == RIGHT) { + return direction == LEFT ? -delta_x : delta_x; + } else { + return direction == UP ? -delta_y : delta_y; + } + } + + private GestureDirection get_direction (double delta_x, double delta_y) { + if (delta_x.abs () > delta_y.abs ()) { + return delta_x > 0 ? GestureDirection.RIGHT : GestureDirection.LEFT; + } else { + return delta_y > 0 ? GestureDirection.DOWN : GestureDirection.UP; + } + } +} diff --git a/src/Widgets/MultitaskingView.vala b/src/Widgets/MultitaskingView.vala index 6c00b783f..5a11d10c2 100644 --- a/src/Widgets/MultitaskingView.vala +++ b/src/Widgets/MultitaskingView.vala @@ -69,12 +69,12 @@ namespace Gala { display = wm.get_display (); multitasking_gesture_tracker = new GestureTracker (ANIMATION_DURATION, ANIMATION_DURATION); - multitasking_gesture_tracker.enable_touchpad (); + multitasking_gesture_tracker.enable_touchpad (wm.stage); multitasking_gesture_tracker.on_gesture_detected.connect (on_multitasking_gesture_detected); multitasking_gesture_tracker.on_gesture_handled.connect (() => toggle (true, false)); workspace_gesture_tracker = new GestureTracker (AnimationDuration.WORKSPACE_SWITCH_MIN, AnimationDuration.WORKSPACE_SWITCH); - workspace_gesture_tracker.enable_touchpad (); + workspace_gesture_tracker.enable_touchpad (this); workspace_gesture_tracker.enable_scroll (this, Clutter.Orientation.HORIZONTAL); workspace_gesture_tracker.on_gesture_detected.connect (on_workspace_gesture_detected); workspace_gesture_tracker.on_gesture_handled.connect (switch_workspace_with_gesture); @@ -625,7 +625,7 @@ namespace Gala { } if (opening) { - modal_proxy = wm.push_modal (this); + modal_proxy = wm.push_modal (get_stage ()); modal_proxy.set_keybinding_filter (keybinding_filter); wm.background_group.hide (); diff --git a/src/Widgets/WindowSwitcher/WindowSwitcher.vala b/src/Widgets/WindowSwitcher/WindowSwitcher.vala index 41ab88f55..6d4d189bf 100644 --- a/src/Widgets/WindowSwitcher/WindowSwitcher.vala +++ b/src/Widgets/WindowSwitcher/WindowSwitcher.vala @@ -42,7 +42,6 @@ public class Gala.WindowSwitcher : CanvasActor { _current_icon = value; if (_current_icon != null) { _current_icon.selected = true; - _current_icon.grab_key_focus (); } update_caption_text (); @@ -412,6 +411,7 @@ public class Gala.WindowSwitcher : CanvasActor { opened = show; if (show) { push_modal (); + get_stage ().set_key_focus (this); } else { wm.pop_modal (modal_proxy); get_stage ().set_key_focus (null); @@ -424,7 +424,7 @@ public class Gala.WindowSwitcher : CanvasActor { } private void push_modal () { - modal_proxy = wm.push_modal (this); + modal_proxy = wm.push_modal (get_stage ()); modal_proxy.set_keybinding_filter ((binding) => { var action = Meta.Prefs.get_keybinding_action (binding.get_name ()); diff --git a/src/WindowManager.vala b/src/WindowManager.vala index 75b488b78..d9096de92 100644 --- a/src/WindowManager.vala +++ b/src/WindowManager.vala @@ -117,11 +117,6 @@ namespace Gala { public const int WORKSPACE_GAP = 24; construct { - gesture_tracker = new GestureTracker (AnimationDuration.WORKSPACE_SWITCH_MIN, AnimationDuration.WORKSPACE_SWITCH); - gesture_tracker.enable_touchpad (); - gesture_tracker.on_gesture_detected.connect (on_gesture_detected); - gesture_tracker.on_gesture_handled.connect (on_gesture_handled); - info = Meta.PluginInfo () {name = "Gala", version = Config.VERSION, author = "Gala Developers", license = "GPLv3", description = "A nice elementary window manager"}; @@ -205,6 +200,11 @@ namespace Gala { stage.background_color = Clutter.Color.from_string (color); #endif + gesture_tracker = new GestureTracker (AnimationDuration.WORKSPACE_SWITCH_MIN, AnimationDuration.WORKSPACE_SWITCH); + gesture_tracker.enable_touchpad (stage); + gesture_tracker.on_gesture_detected.connect (on_gesture_detected); + gesture_tracker.on_gesture_handled.connect (on_gesture_handled); + unowned var laters = display.get_compositor ().get_laters (); laters.add (Meta.LaterType.BEFORE_REDRAW, () => { WorkspaceManager.init (this); diff --git a/src/Zoom.vala b/src/Zoom.vala index e833f4134..82a793181 100644 --- a/src/Zoom.vala +++ b/src/Zoom.vala @@ -31,7 +31,7 @@ public class Gala.Zoom : Object { display.add_keybinding ("zoom-out", schema, Meta.KeyBindingFlags.NONE, (Meta.KeyHandlerFunc) zoom_out); gesture_tracker = new GestureTracker (ANIMATION_DURATION, ANIMATION_DURATION); - gesture_tracker.enable_touchpad (); + gesture_tracker.enable_touchpad (wm.stage); gesture_tracker.on_gesture_detected.connect (on_gesture_detected); gesture_tracker.on_gesture_handled.connect ((gesture) => zoom_with_gesture (gesture.direction)); diff --git a/src/meson.build b/src/meson.build index 9660dd2b8..87b217560 100644 --- a/src/meson.build +++ b/src/meson.build @@ -40,6 +40,7 @@ gala_bin_sources = files( 'Gestures/GestureTracker.vala', 'Gestures/ScrollBackend.vala', 'Gestures/ToucheggBackend.vala', + 'Gestures/TouchpadBackend.vala', 'HotCorners/Barrier.vala', 'HotCorners/HotCorner.vala', 'HotCorners/HotCornerManager.vala',