linux: scrolling improvements (#9103)

apricotbucket28 created

This PR adjusts scrolling to be a lot faster on Linux and also makes
terminal scrolling work.

For Wayland, it makes scrolling faster by handling the `AxisValue120`
event (which also allows high-resolution scrolling on supported mice)
On X11, changed the 1 line per scroll to 3.

### Different solutions

I tried replicating Chromium's scrolling behaviour, but it was
inconsistent in X11/Wayland and found it too fast on Wayland. Plus, it
also didn't match VSCode, since it seems that they do something
different.

Release Notes:

- Made scrolling faster on Linux
- Made terminal scroll on Linux

Change summary

crates/gpui/src/platform/linux/platform.rs       |  2 
crates/gpui/src/platform/linux/wayland/client.rs | 64 ++++++++++++++++-
crates/gpui/src/platform/linux/x11/client.rs     |  9 +-
crates/terminal/src/terminal.rs                  |  3 
4 files changed, 68 insertions(+), 10 deletions(-)

Detailed changes

crates/gpui/src/platform/linux/platform.rs 🔗

@@ -30,6 +30,8 @@ use crate::{
 
 use super::x11::X11Client;
 
+pub(super) const SCROLL_LINES: f64 = 3.0;
+
 #[derive(Default)]
 pub(crate) struct Callbacks {
     open_urls: Option<Box<dyn FnMut(Vec<String>)>>,

crates/gpui/src/platform/linux/wayland/client.rs 🔗

@@ -14,7 +14,7 @@ use wayland_backend::protocol::WEnum;
 use wayland_client::globals::{registry_queue_init, GlobalListContents};
 use wayland_client::protocol::wl_callback::WlCallback;
 use wayland_client::protocol::wl_output;
-use wayland_client::protocol::wl_pointer::AxisRelativeDirection;
+use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
 use wayland_client::{
     delegate_noop,
     protocol::{
@@ -64,6 +64,7 @@ pub(crate) struct WaylandClientStateInner {
     repeat: KeyRepeat,
     modifiers: Modifiers,
     scroll_direction: f64,
+    axis_source: AxisSource,
     mouse_location: Option<Point<Pixels>>,
     button_pressed: Option<MouseButton>,
     mouse_focused_window: Option<Rc<WaylandWindowState>>,
@@ -97,9 +98,22 @@ pub(crate) struct WaylandClient {
     qh: Arc<QueueHandle<WaylandClientState>>,
 }
 
-const WL_SEAT_VERSION: u32 = 4;
+const WL_SEAT_MIN_VERSION: u32 = 4;
 const WL_OUTPUT_VERSION: u32 = 2;
 
+fn wl_seat_version(version: u32) -> u32 {
+    if version >= wl_pointer::EVT_AXIS_VALUE120_SINCE {
+        wl_pointer::EVT_AXIS_VALUE120_SINCE
+    } else if version >= WL_SEAT_MIN_VERSION {
+        WL_SEAT_MIN_VERSION
+    } else {
+        panic!(
+            "wl_seat below required version: {} < {}",
+            version, WL_SEAT_MIN_VERSION
+        );
+    }
+}
+
 impl WaylandClient {
     pub(crate) fn new(linux_platform_inner: Rc<LinuxPlatformInner>) -> Self {
         let conn = Connection::connect_to_env().unwrap();
@@ -114,7 +128,7 @@ impl WaylandClient {
                     "wl_seat" => {
                         globals.registry().bind::<wl_seat::WlSeat, _, _>(
                             global.name,
-                            WL_SEAT_VERSION,
+                            wl_seat_version(global.version),
                             &qh,
                             (),
                         );
@@ -163,6 +177,7 @@ impl WaylandClient {
                 command: false,
             },
             scroll_direction: -1.0,
+            axis_source: AxisSource::Wheel,
             mouse_location: None,
             button_pressed: None,
             mouse_focused_window: None,
@@ -325,10 +340,10 @@ impl Dispatch<wl_registry::WlRegistry, GlobalListContents> for WaylandClientStat
             wl_registry::Event::Global {
                 name,
                 interface,
-                version: _,
+                version,
             } => match &interface[..] {
                 "wl_seat" => {
-                    registry.bind::<wl_seat::WlSeat, _, _>(name, WL_SEAT_VERSION, qh, ());
+                    registry.bind::<wl_seat::WlSeat, _, _>(name, wl_seat_version(version), qh, ());
                 }
                 "wl_output" => {
                     state.outputs.push((
@@ -914,12 +929,49 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
                     _ => -1.0,
                 }
             }
+            wl_pointer::Event::AxisSource {
+                axis_source: WEnum::Value(axis_source),
+            } => {
+                state.axis_source = axis_source;
+            }
+            wl_pointer::Event::AxisValue120 {
+                axis: WEnum::Value(axis),
+                value120,
+            } => {
+                let focused_window = &state.mouse_focused_window;
+                let mouse_location = &state.mouse_location;
+                if let (Some(focused_window), Some(mouse_location)) =
+                    (focused_window, mouse_location)
+                {
+                    let value = value120 as f64 * state.scroll_direction;
+                    focused_window.handle_input(PlatformInput::ScrollWheel(ScrollWheelEvent {
+                        position: *mouse_location,
+                        delta: match axis {
+                            wl_pointer::Axis::VerticalScroll => {
+                                ScrollDelta::Pixels(Point::new(Pixels(0.0), Pixels(value as f32)))
+                            }
+                            wl_pointer::Axis::HorizontalScroll => {
+                                ScrollDelta::Pixels(Point::new(Pixels(value as f32), Pixels(0.0)))
+                            }
+                            _ => unimplemented!(),
+                        },
+                        modifiers: state.modifiers,
+                        touch_phase: TouchPhase::Moved,
+                    }))
+                }
+            }
             wl_pointer::Event::Axis {
                 time,
                 axis: WEnum::Value(axis),
                 value,
                 ..
             } => {
+                if wl_pointer.version() >= wl_pointer::EVT_AXIS_VALUE120_SINCE
+                    && state.axis_source != AxisSource::Continuous
+                {
+                    return;
+                }
+
                 let focused_window = &state.mouse_focused_window;
                 let mouse_location = &state.mouse_location;
                 if let (Some(focused_window), Some(mouse_location)) =
@@ -938,7 +990,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientState {
                             _ => unimplemented!(),
                         },
                         modifiers: state.modifiers,
-                        touch_phase: TouchPhase::Started,
+                        touch_phase: TouchPhase::Moved,
                     }))
                 }
             }

crates/gpui/src/platform/linux/x11/client.rs 🔗

@@ -23,7 +23,7 @@ use crate::{
     ScrollDelta, Size, TouchPhase, WindowParams,
 };
 
-use super::{X11Display, X11Window, X11WindowState, XcbAtoms};
+use super::{super::SCROLL_LINES, X11Display, X11Window, X11WindowState, XcbAtoms};
 use calloop::{
     generic::{FdWrapper, Generic},
     RegistrationToken,
@@ -221,12 +221,13 @@ impl X11Client {
                     }));
                 } else if event.detail >= 4 && event.detail <= 5 {
                     // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
-                    let delta_x = if event.detail == 4 { 1.0 } else { -1.0 };
+                    let scroll_direction = if event.detail == 4 { 1.0 } else { -1.0 };
+                    let scroll_y = SCROLL_LINES * scroll_direction;
                     window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
                         position,
-                        delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
+                        delta: ScrollDelta::Lines(Point::new(0.0, scroll_y as f32)),
                         modifiers,
-                        touch_phase: TouchPhase::default(),
+                        touch_phase: TouchPhase::Moved,
                     }));
                 } else {
                     log::warn!("Unknown button press: {event:?}");

crates/terminal/src/terminal.rs 🔗

@@ -70,7 +70,10 @@ actions!(
 ///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
 ///Scroll multiplier that is set to 3 by default. This will be removed when I
 ///Implement scroll bars.
+#[cfg(target_os = "macos")]
 const SCROLL_MULTIPLIER: f32 = 4.;
+#[cfg(not(target_os = "macos"))]
+const SCROLL_MULTIPLIER: f32 = 1.;
 const MAX_SEARCH_LINES: usize = 100;
 const DEBUG_TERMINAL_WIDTH: Pixels = px(500.);
 const DEBUG_TERMINAL_HEIGHT: Pixels = px(30.);