linux/x11: implement window focus (#8002)

Dzmitry Malyshau created

Release Notes:
- N/A

Change summary

crates/gpui/src/platform/linux/x11/client.rs | 20 ++++++++++
crates/gpui/src/platform/linux/x11/window.rs | 41 +++++++++++++++------
2 files changed, 47 insertions(+), 14 deletions(-)

Detailed changes

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

@@ -11,7 +11,8 @@ use crate::platform::{
     LinuxPlatformInner, PlatformWindow, X11Display, X11Window, X11WindowState, XcbAtoms,
 };
 use crate::{
-    AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, Size, WindowOptions,
+    AnyWindowHandle, Bounds, DisplayId, PlatformDisplay, PlatformInput, Point, ScrollDelta, Size,
+    TouchPhase, WindowOptions,
 };
 
 pub(crate) struct X11ClientState {
@@ -106,6 +107,14 @@ impl Client for X11Client {
                     window.request_refresh();
                 }
                 xcb::Event::Present(xcb::present::Event::IdleNotify(_ev)) => {}
+                xcb::Event::X(x::Event::FocusIn(ev)) => {
+                    let window = self.get_window(ev.event());
+                    window.set_focused(true);
+                }
+                xcb::Event::X(x::Event::FocusOut(ev)) => {
+                    let window = self.get_window(ev.event());
+                    window.set_focused(false);
+                }
                 xcb::Event::X(x::Event::KeyPress(ev)) => {
                     let window = self.get_window(ev.event());
                     let modifiers = super::modifiers_from_state(ev.state());
@@ -155,6 +164,15 @@ impl Client for X11Client {
                             modifiers,
                             click_count: 1,
                         }));
+                    } else if ev.detail() >= 4 && ev.detail() <= 5 {
+                        // https://stackoverflow.com/questions/15510472/scrollwheel-event-in-x11
+                        let delta_x = if ev.detail() == 4 { 1.0 } else { -1.0 };
+                        window.handle_input(PlatformInput::ScrollWheel(crate::ScrollWheelEvent {
+                            position,
+                            delta: ScrollDelta::Lines(Point::new(0.0, delta_x)),
+                            modifiers,
+                            touch_phase: TouchPhase::default(),
+                        }));
                     } else {
                         log::warn!("Unknown button press: {ev:?}");
                     }

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

@@ -2,9 +2,9 @@
 #![allow(unused)]
 
 use crate::{
-    platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Pixels, PlatformDisplay,
-    PlatformInput, PlatformInputHandler, PlatformWindow, Point, Size, WindowAppearance,
-    WindowBounds, WindowOptions, X11Display,
+    platform::blade::BladeRenderer, size, Bounds, GlobalPixels, Modifiers, Pixels, PlatformAtlas,
+    PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel,
+    Scene, Size, WindowAppearance, WindowBounds, WindowOptions, X11Display,
 };
 use blade_graphics as gpu;
 use parking_lot::Mutex;
@@ -27,7 +27,7 @@ use std::{
 #[derive(Default)]
 struct Callbacks {
     request_frame: Option<Box<dyn FnMut()>>,
-    input: Option<Box<dyn FnMut(crate::PlatformInput) -> bool>>,
+    input: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
     active_status_change: Option<Box<dyn FnMut(bool)>>,
     resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
     fullscreen: Option<Box<dyn FnMut(bool)>>,
@@ -155,6 +155,9 @@ impl X11WindowState {
             x::Cw::EventMask(
                 x::EventMask::EXPOSURE
                     | x::EventMask::STRUCTURE_NOTIFY
+                    | x::EventMask::ENTER_WINDOW
+                    | x::EventMask::LEAVE_WINDOW
+                    | x::EventMask::FOCUS_CHANGE
                     | x::EventMask::KEY_PRESS
                     | x::EventMask::KEY_RELEASE
                     | x::EventMask::BUTTON_PRESS
@@ -345,6 +348,12 @@ impl X11WindowState {
             }
         }
     }
+
+    pub fn set_focused(&self, focus: bool) {
+        if let Some(ref mut fun) = self.callbacks.lock().active_status_change {
+            fun(focus);
+        }
+    }
 }
 
 impl PlatformWindow for X11Window {
@@ -374,14 +383,20 @@ impl PlatformWindow for X11Window {
         Rc::clone(&self.0.display)
     }
 
-    //todo!(linux)
     fn mouse_position(&self) -> Point<Pixels> {
-        Point::default()
+        let cookie = self.0.xcb_connection.send_request(&x::QueryPointer {
+            window: self.0.x_window,
+        });
+        let reply: x::QueryPointerReply = self.0.xcb_connection.wait_for_reply(cookie).unwrap();
+        Point::new(
+            (reply.root_x() as u32).into(),
+            (reply.root_y() as u32).into(),
+        )
     }
 
     //todo!(linux)
-    fn modifiers(&self) -> crate::Modifiers {
-        crate::Modifiers::default()
+    fn modifiers(&self) -> Modifiers {
+        Modifiers::default()
     }
 
     fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
@@ -399,7 +414,7 @@ impl PlatformWindow for X11Window {
     //todo!(linux)
     fn prompt(
         &self,
-        _level: crate::PromptLevel,
+        _level: PromptLevel,
         _msg: &str,
         _detail: Option<&str>,
         _answers: &[&str],
@@ -456,7 +471,7 @@ impl PlatformWindow for X11Window {
         self.0.callbacks.lock().request_frame = Some(callback);
     }
 
-    fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
+    fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> bool>) {
         self.0.callbacks.lock().input = Some(callback);
     }
 
@@ -489,16 +504,16 @@ impl PlatformWindow for X11Window {
     }
 
     //todo!(linux)
-    fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
+    fn is_topmost_for_position(&self, _position: Point<Pixels>) -> bool {
         unimplemented!()
     }
 
-    fn draw(&self, scene: &crate::Scene) {
+    fn draw(&self, scene: &Scene) {
         let mut inner = self.0.inner.lock();
         inner.renderer.draw(scene);
     }
 
-    fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
+    fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
         let inner = self.0.inner.lock();
         inner.renderer.sprite_atlas().clone()
     }