diff --git a/data/gala.gschema.xml b/data/gala.gschema.xml
index e6bff01ba..b727649b0 100644
--- a/data/gala.gschema.xml
+++ b/data/gala.gschema.xml
@@ -14,6 +14,12 @@
+
+
+
+
+
+
true
@@ -96,6 +102,11 @@
Whether hotcorners should be enabled when fullscreen window is opened
+
+ "none"
+ What action should be performed on Super + Scroll
+
+
diff --git a/lib/WindowManager.vala b/lib/WindowManager.vala
index 38cb294a6..5fc5e184f 100644
--- a/lib/WindowManager.vala
+++ b/lib/WindowManager.vala
@@ -123,6 +123,12 @@ namespace Gala {
*/
public abstract Gala.ActivatableComponent workspace_view { get; protected set; }
+ /*
+ * This signal is sent when Super + Scroll was performed and WindowManager couldn't handle it
+ * which probably means that the user doesn't want to switch workspaces on Super + Scroll.
+ */
+ public abstract signal void super_scroll_triggered (uint32 timestamp, double dx, double dy);
+
/**
* Enters the modal mode, which means that all events are directed to the stage instead
* of the windows. This is the only way to receive keyboard events besides shortcut listeners.
diff --git a/src/SuperScrollAction.vala b/src/SuperScrollAction.vala
new file mode 100644
index 000000000..430ba82ec
--- /dev/null
+++ b/src/SuperScrollAction.vala
@@ -0,0 +1,47 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2024 elementary, Inc. (https://elementary.io)
+ */
+
+public class Gala.SuperScrollAction : Clutter.Action {
+ public signal void triggered (uint32 timestamp, double dx, double dy);
+
+ public Meta.Display display { private get; construct; }
+
+ public SuperScrollAction (Meta.Display display) {
+ Object (display: display);
+ }
+
+ public override bool handle_event (Clutter.Event event) {
+ if (
+ event.get_type () == SCROLL &&
+ (event.get_state () & display.compositor_modifiers) != 0
+ ) {
+ double dx = 0.0, dy = 0.0;
+ switch (event.get_scroll_direction ()) {
+ case LEFT:
+ dx = -1.0;
+ break;
+ case RIGHT:
+ dx = 1.0;
+ break;
+ case UP:
+ dy = 1.0;
+ break;
+ case DOWN:
+ dy = -1.0;
+ break;
+ default:
+ break;
+ }
+
+ // TODO: support natural scroll settings
+
+ triggered (event.get_time (), dx, dy);
+
+ return Clutter.EVENT_STOP;
+ }
+
+ return Clutter.EVENT_PROPAGATE;
+ }
+}
diff --git a/src/WindowManager.vala b/src/WindowManager.vala
index 825b11cf3..8886bb827 100644
--- a/src/WindowManager.vala
+++ b/src/WindowManager.vala
@@ -159,6 +159,21 @@ namespace Gala {
#endif
}
+ private void handle_super_scroll (uint32 timestamp, double dx, double dy) {
+ if (behavior_settings.get_enum ("super-scroll-action") != 1) {
+ super_scroll_triggered (timestamp, dx, dy);
+ return;
+ }
+
+ var d = dx.abs () > dy.abs () ? dx : dy;
+
+ if (d > 0) {
+ switch_to_next_workspace (Meta.MotionDirection.RIGHT, timestamp);
+ } else if (d < 0) {
+ switch_to_next_workspace (Meta.MotionDirection.LEFT, timestamp);
+ }
+ }
+
#if WITH_SYSTEMD
private async void start_x11_services (GLib.Task task) {
try {
@@ -396,6 +411,10 @@ namespace Gala {
display.window_created.connect ((window) => window_created (window));
+ var scroll_action = new SuperScrollAction (display);
+ scroll_action.triggered.connect (handle_super_scroll);
+ stage.add_action_full ("super-scroll-action", CAPTURE, scroll_action);
+
stage.show ();
Idle.add (() => {
diff --git a/src/Zoom.vala b/src/Zoom.vala
index 29d471d47..db9dc8d98 100644
--- a/src/Zoom.vala
+++ b/src/Zoom.vala
@@ -19,6 +19,7 @@ public class Gala.Zoom : Object {
private ulong wins_handler_id = 0UL;
private GestureTracker gesture_tracker;
+ private GLib.Settings behavior_settings;
public Zoom (WindowManager wm) {
Object (wm: wm);
@@ -32,6 +33,9 @@ public class Gala.Zoom : Object {
gesture_tracker = new GestureTracker (ANIMATION_DURATION, ANIMATION_DURATION);
gesture_tracker.enable_touchpad ();
gesture_tracker.on_gesture_detected.connect (on_gesture_detected);
+
+ behavior_settings = new GLib.Settings ("io.elementary.desktop.wm.behavior");
+ wm.super_scroll_triggered.connect (handle_super_scroll);
}
~Zoom () {
@@ -75,6 +79,20 @@ public class Gala.Zoom : Object {
}
}
+ private void handle_super_scroll (uint32 timestamp, double dx, double dy) {
+ if (behavior_settings.get_enum ("super-scroll-action") != 2) {
+ return;
+ }
+
+ var d = dx.abs () > dy.abs () ? dx : dy;
+
+ if (d > 0) {
+ zoom (SHORTCUT_DELTA, true, AnimationsSettings.get_enable_animations ());
+ } else if (d < 0) {
+ zoom (-SHORTCUT_DELTA, true, AnimationsSettings.get_enable_animations ());
+ }
+ }
+
private void zoom_with_gesture (GestureDirection direction) {
var initial_zoom = current_zoom;
var target_zoom = (direction == GestureDirection.IN)
diff --git a/src/meson.build b/src/meson.build
index 70d9d7b97..9660dd2b8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -14,6 +14,7 @@ gala_bin_sources = files(
'ScreenSaverManager.vala',
'ScreenshotManager.vala',
'SessionManager.vala',
+ 'SuperScrollAction.vala',
'WindowAttentionTracker.vala',
'WindowGrabTracker.vala',
'WindowListener.vala',