Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a TouchpadBackend #2204

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/Gestures/GestureTracker.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/Gestures/ToucheggBackend.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
125 changes: 125 additions & 0 deletions src/Gestures/TouchpadBackend.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2025 elementary, Inc. (https://elementary.io)
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Authored by: Leonhard Kargl <[email protected]>
*/

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;
}
}
}
6 changes: 3 additions & 3 deletions src/Widgets/MultitaskingView.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 ();
Expand Down
4 changes: 2 additions & 2 deletions src/Widgets/WindowSwitcher/WindowSwitcher.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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 ();
Expand Down Expand Up @@ -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);
Expand All @@ -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 ());

Expand Down
10 changes: 5 additions & 5 deletions src/WindowManager.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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"};

Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/Zoom.vala
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading