x11: Differentiate between mouse and keyboard focus (#13943)

apricotbucket28 created

Fixes https://github.com/zed-industries/zed/issues/13897

Release Notes:

- N/A

Change summary

crates/gpui/src/platform/linux/x11/client.rs | 39 +++++++++++++++------
crates/gpui/src/platform/linux/x11/window.rs |  1 
2 files changed, 28 insertions(+), 12 deletions(-)

Detailed changes

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

@@ -115,7 +115,8 @@ pub struct X11ClientState {
     pub(crate) _resource_database: Database,
     pub(crate) atoms: XcbAtoms,
     pub(crate) windows: HashMap<xproto::Window, WindowRef>,
-    pub(crate) focused_window: Option<xproto::Window>,
+    pub(crate) mouse_focused_window: Option<xproto::Window>,
+    pub(crate) keyboard_focused_window: Option<xproto::Window>,
     pub(crate) xkb: xkbc::State,
     pub(crate) ximc: Option<X11rbClient<Rc<XCBConnection>>>,
     pub(crate) xim_handler: Option<XimHandler>,
@@ -151,7 +152,12 @@ impl X11ClientStatePtr {
                 x_window
             );
         }
-
+        if state.mouse_focused_window == Some(x_window) {
+            state.mouse_focused_window = None;
+        }
+        if state.keyboard_focused_window == Some(x_window) {
+            state.keyboard_focused_window = None;
+        }
         state.cursor_styles.remove(&x_window);
 
         if state.windows.is_empty() {
@@ -320,7 +326,8 @@ impl X11Client {
             _resource_database: resource_database,
             atoms,
             windows: HashMap::default(),
-            focused_window: None,
+            mouse_focused_window: None,
+            keyboard_focused_window: None,
             xkb: xkb_state,
             ximc,
             xim_handler,
@@ -361,7 +368,7 @@ impl X11Client {
             .push(AttributeName::ClientWindow, xim_handler.window)
             .push(AttributeName::FocusWindow, xim_handler.window);
 
-        let window_id = state.focused_window;
+        let window_id = state.keyboard_focused_window;
         drop(state);
         if let Some(window_id) = window_id {
             let window = self.get_window(window_id).unwrap();
@@ -558,19 +565,19 @@ impl X11Client {
                 let window = self.get_window(event.window)?;
                 window.refresh();
             }
-            Event::FocusIn(event) => {
+            Event::FocusIn(event) if event.mode == xproto::NotifyMode::NORMAL => {
                 let window = self.get_window(event.event)?;
                 window.set_focused(true);
                 let mut state = self.0.borrow_mut();
-                state.focused_window = Some(event.event);
+                state.keyboard_focused_window = Some(event.event);
                 drop(state);
                 self.enable_ime();
             }
-            Event::FocusOut(event) => {
+            Event::FocusOut(event) if event.mode == xproto::NotifyMode::NORMAL => {
                 let window = self.get_window(event.event)?;
                 window.set_focused(false);
                 let mut state = self.0.borrow_mut();
-                state.focused_window = None;
+                state.keyboard_focused_window = None;
                 if let Some(compose_state) = state.compose_state.as_mut() {
                     compose_state.reset();
                 }
@@ -594,7 +601,7 @@ impl X11Client {
                 if state.modifiers == modifiers {
                     drop(state);
                 } else {
-                    let focused_window_id = state.focused_window?;
+                    let focused_window_id = state.keyboard_focused_window?;
                     state.modifiers = modifiers;
                     drop(state);
 
@@ -844,12 +851,18 @@ impl X11Client {
                     valuator_idx += 1;
                 }
             }
+            Event::XinputEnter(event) if event.mode == xinput::NotifyMode::NORMAL => {
+                let window = self.get_window(event.event)?;
+                window.set_focused(true);
+                let mut state = self.0.borrow_mut();
+                state.mouse_focused_window = Some(event.event);
+            }
             Event::XinputLeave(event) if event.mode == xinput::NotifyMode::NORMAL => {
                 self.0.borrow_mut().scroll_x = None; // Set last scroll to `None` so that a large delta isn't created if scrolling is done outside the window (the valuator is global)
                 self.0.borrow_mut().scroll_y = None;
 
-                let window = self.get_window(event.event)?;
                 let mut state = self.0.borrow_mut();
+                state.mouse_focused_window = None;
                 let pressed_button = pressed_button_from_mask(event.buttons[0]);
                 let position = point(
                     px(event.event_x as f32 / u16::MAX as f32 / state.scale_factor),
@@ -859,11 +872,13 @@ impl X11Client {
                 state.modifiers = modifiers;
                 drop(state);
 
+                let window = self.get_window(event.event)?;
                 window.handle_input(PlatformInput::MouseExited(crate::MouseExitEvent {
                     pressed_button,
                     position,
                     modifiers,
                 }));
+                window.set_focused(false);
             }
             _ => {}
         };
@@ -1046,7 +1061,7 @@ impl LinuxClient for X11Client {
 
     fn set_cursor_style(&self, style: CursorStyle) {
         let mut state = self.0.borrow_mut();
-        let Some(focused_window) = state.focused_window else {
+        let Some(focused_window) = state.mouse_focused_window else {
             return;
         };
         let current_style = state
@@ -1294,7 +1309,7 @@ impl LinuxClient for X11Client {
 
     fn active_window(&self) -> Option<AnyWindowHandle> {
         let state = self.0.borrow();
-        state.focused_window.and_then(|focused_window| {
+        state.keyboard_focused_window.and_then(|focused_window| {
             state
                 .windows
                 .get(&focused_window)

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

@@ -450,6 +450,7 @@ impl X11WindowState {
                         xinput::XIEventMask::MOTION
                             | xinput::XIEventMask::BUTTON_PRESS
                             | xinput::XIEventMask::BUTTON_RELEASE
+                            | xinput::XIEventMask::ENTER
                             | xinput::XIEventMask::LEAVE,
                     ],
                 }],