From 7ba6f39e8284cb7b3522f137b0d724ad59740ddd Mon Sep 17 00:00:00 2001
From: Andre Roelofs
Date: Tue, 16 Dec 2025 16:36:45 +0100
Subject: [PATCH] Fix macros on x11 sometimes resulting in incorrect input
(#44234)
Closes #40678
The python file below simulates the macros at various timings and can be
run by running:
1. `sudo python3 -m pip install evdev --break-system-packages`
2. `sudo python3 zed_shift_brace_replayer.py`
Checked timings for hold=0.1, =0.01 and =0.001 with the latter two no
longer causing incorrect inputs.
[zed_shift_brace_replayer.py](https://github.com/user-attachments/files/23560570/zed_shift_brace_replayer.py)
Release Notes:
- linux: fixed a race condition where the macros containing modifier +
key would sometimes be processed without the modifier
---
crates/gpui/src/platform/linux/x11/client.rs | 29 +++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs
index 60400dada57775a295fdb36c7f1ddd9dd8b83a67..5e9089b09809a7ec1b8b257427b0a670adc0f123 100644
--- a/crates/gpui/src/platform/linux/x11/client.rs
+++ b/crates/gpui/src/platform/linux/x11/client.rs
@@ -29,7 +29,7 @@ use x11rb::{
protocol::xkb::ConnectionExt as _,
protocol::xproto::{
AtomEnum, ChangeWindowAttributesAux, ClientMessageData, ClientMessageEvent,
- ConnectionExt as _, EventMask, Visibility,
+ ConnectionExt as _, EventMask, ModMask, Visibility,
},
protocol::{Event, randr, render, xinput, xkb, xproto},
resource_manager::Database,
@@ -1018,6 +1018,12 @@ impl X11Client {
let modifiers = modifiers_from_state(event.state);
state.modifiers = modifiers;
state.pre_key_char_down.take();
+
+ // Macros containing modifiers might result in
+ // the modifiers missing from the event.
+ // We therefore update the mask from the global state.
+ update_xkb_mask_from_event_state(&mut state.xkb, event.state);
+
let keystroke = {
let code = event.detail.into();
let mut keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
@@ -1083,6 +1089,11 @@ impl X11Client {
let modifiers = modifiers_from_state(event.state);
state.modifiers = modifiers;
+ // Macros containing modifiers might result in
+ // the modifiers missing from the event.
+ // We therefore update the mask from the global state.
+ update_xkb_mask_from_event_state(&mut state.xkb, event.state);
+
let keystroke = {
let code = event.detail.into();
let keystroke = crate::Keystroke::from_xkb(&state.xkb, modifiers, code);
@@ -2516,3 +2527,19 @@ fn get_dpi_factor((width_px, height_px): (u32, u32), (width_mm, height_mm): (u64
fn valid_scale_factor(scale_factor: f32) -> bool {
scale_factor.is_sign_positive() && scale_factor.is_normal()
}
+
+#[inline]
+fn update_xkb_mask_from_event_state(xkb: &mut xkbc::State, event_state: xproto::KeyButMask) {
+ let depressed_mods = event_state.remove((ModMask::LOCK | ModMask::M2).bits());
+ let latched_mods = xkb.serialize_mods(xkbc::STATE_MODS_LATCHED);
+ let locked_mods = xkb.serialize_mods(xkbc::STATE_MODS_LOCKED);
+ let locked_layout = xkb.serialize_layout(xkbc::STATE_LAYOUT_LOCKED);
+ xkb.update_mask(
+ depressed_mods.into(),
+ latched_mods,
+ locked_mods,
+ 0,
+ 0,
+ locked_layout,
+ );
+}