x11: Fix preedit for CJK and partially fix unresponsive keyboard with xim (#17373)

Fernando Tagawa created

Closes #15833
Related to [#12495
comment](https://github.com/zed-industries/zed/pull/12495#issuecomment-2328356125)

Destroying and recreating the Input context was the only way to reset
the IME but it's making the keyboard unresponsive sometimes due to a XIM
error.

The keyboard will still be unresponsive if you close your IME while
using zed, but I don't know how to fix this.

* Fixed preedit drawing for CJK
* Fixed unresponsive keyboard by properly implementing reset_ic in
`xim-rs`

Release Notes:

- N/A

Change summary

Cargo.lock                                        |  6 +-
crates/gpui/Cargo.toml                            |  2 
crates/gpui/src/platform/linux/x11/client.rs      | 27 ++++++----------
crates/gpui/src/platform/linux/x11/xim_handler.rs | 16 ---------
4 files changed, 15 insertions(+), 36 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -14079,7 +14079,7 @@ dependencies = [
 [[package]]
 name = "xim"
 version = "0.4.0"
-source = "git+https://github.com/npmania/xim-rs?rev=27132caffc5b9bc9c432ca4afad184ab6e7c16af#27132caffc5b9bc9c432ca4afad184ab6e7c16af"
+source = "git+https://github.com/XDeme1/xim-rs?rev=d50d461764c2213655cd9cf65a0ea94c70d3c4fd#d50d461764c2213655cd9cf65a0ea94c70d3c4fd"
 dependencies = [
  "ahash 0.8.11",
  "hashbrown 0.14.5",
@@ -14092,7 +14092,7 @@ dependencies = [
 [[package]]
 name = "xim-ctext"
 version = "0.3.0"
-source = "git+https://github.com/npmania/xim-rs?rev=27132caffc5b9bc9c432ca4afad184ab6e7c16af#27132caffc5b9bc9c432ca4afad184ab6e7c16af"
+source = "git+https://github.com/XDeme1/xim-rs?rev=d50d461764c2213655cd9cf65a0ea94c70d3c4fd#d50d461764c2213655cd9cf65a0ea94c70d3c4fd"
 dependencies = [
  "encoding_rs",
 ]
@@ -14100,7 +14100,7 @@ dependencies = [
 [[package]]
 name = "xim-parser"
 version = "0.2.1"
-source = "git+https://github.com/npmania/xim-rs?rev=27132caffc5b9bc9c432ca4afad184ab6e7c16af#27132caffc5b9bc9c432ca4afad184ab6e7c16af"
+source = "git+https://github.com/XDeme1/xim-rs?rev=d50d461764c2213655cd9cf65a0ea94c70d3c4fd#d50d461764c2213655cd9cf65a0ea94c70d3c4fd"
 dependencies = [
  "bitflags 2.6.0",
 ]

crates/gpui/Cargo.toml 🔗

@@ -143,7 +143,7 @@ xkbcommon = { git = "https://github.com/ConradIrwin/xkbcommon-rs", rev = "fcbb46
     "wayland",
     "x11",
 ] }
-xim = { git = "https://github.com/npmania/xim-rs", rev = "27132caffc5b9bc9c432ca4afad184ab6e7c16af", features = [
+xim = { git = "https://github.com/XDeme1/xim-rs", rev = "d50d461764c2213655cd9cf65a0ea94c70d3c4fd", features = [
     "x11rb-xcb",
     "x11rb-client",
 ] }

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

@@ -201,9 +201,7 @@ impl X11ClientStatePtr {
             .build_ic_attributes()
             .push(
                 xim::AttributeName::InputStyle,
-                xim::InputStyle::PREEDIT_CALLBACKS
-                    | xim::InputStyle::STATUS_NOTHING
-                    | xim::InputStyle::PREEDIT_POSITION,
+                xim::InputStyle::PREEDIT_CALLBACKS,
             )
             .push(xim::AttributeName::ClientWindow, xim_handler.window)
             .push(xim::AttributeName::FocusWindow, xim_handler.window)
@@ -572,12 +570,7 @@ impl X11Client {
         let mut xim_handler = state.xim_handler.take().unwrap();
         let mut ic_attributes = ximc
             .build_ic_attributes()
-            .push(
-                AttributeName::InputStyle,
-                InputStyle::PREEDIT_CALLBACKS
-                    | InputStyle::STATUS_NOTHING
-                    | InputStyle::PREEDIT_NONE,
-            )
+            .push(AttributeName::InputStyle, InputStyle::PREEDIT_CALLBACKS)
             .push(AttributeName::ClientWindow, xim_handler.window)
             .push(AttributeName::FocusWindow, xim_handler.window);
 
@@ -605,12 +598,12 @@ impl X11Client {
         state.ximc = Some(ximc);
     }
 
-    pub fn disable_ime(&self) {
+    pub fn reset_ime(&self) {
         let mut state = self.0.borrow_mut();
         state.composing = false;
         if let Some(mut ximc) = state.ximc.take() {
             let xim_handler = state.xim_handler.as_ref().unwrap();
-            ximc.destroy_ic(xim_handler.im_id, xim_handler.ic_id).ok();
+            ximc.reset_ic(xim_handler.im_id, xim_handler.ic_id).ok();
             state.ximc = Some(ximc);
         }
     }
@@ -768,6 +761,9 @@ impl X11Client {
                 window.set_active(true);
                 let mut state = self.0.borrow_mut();
                 state.keyboard_focused_window = Some(event.event);
+                if let Some(handler) = state.xim_handler.as_mut() {
+                    handler.window = event.event;
+                }
                 drop(state);
                 self.enable_ime();
             }
@@ -781,7 +777,7 @@ impl X11Client {
                 }
                 state.pre_edit_text.take();
                 drop(state);
-                self.disable_ime();
+                self.reset_ime();
                 window.handle_ime_delete();
             }
             Event::XkbNewKeyboardNotify(_) | Event::MapNotify(_) => {
@@ -940,8 +936,7 @@ impl X11Client {
 
                 if state.composing && state.ximc.is_some() {
                     drop(state);
-                    self.disable_ime();
-                    self.enable_ime();
+                    self.reset_ime();
                     window.handle_ime_unmark();
                     state = self.0.borrow_mut();
                 } else if let Some(text) = state.pre_edit_text.take() {
@@ -1199,9 +1194,7 @@ impl X11Client {
                 .build_ic_attributes()
                 .push(
                     xim::AttributeName::InputStyle,
-                    xim::InputStyle::PREEDIT_CALLBACKS
-                        | xim::InputStyle::STATUS_NOTHING
-                        | xim::InputStyle::PREEDIT_POSITION,
+                    xim::InputStyle::PREEDIT_CALLBACKS,
                 )
                 .push(xim::AttributeName::ClientWindow, xim_handler.window)
                 .push(xim::AttributeName::FocusWindow, xim_handler.window)

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

@@ -48,12 +48,7 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
     ) -> Result<(), ClientError> {
         let ic_attributes = client
             .build_ic_attributes()
-            .push(
-                AttributeName::InputStyle,
-                InputStyle::PREEDIT_CALLBACKS
-                    | InputStyle::STATUS_NOTHING
-                    | InputStyle::PREEDIT_NONE,
-            )
+            .push(AttributeName::InputStyle, InputStyle::PREEDIT_CALLBACKS)
             .push(AttributeName::ClientWindow, self.window)
             .push(AttributeName::FocusWindow, self.window)
             .build();
@@ -110,15 +105,6 @@ impl<C: Client<XEvent = xproto::KeyPressEvent>> ClientHandler<C> for XimHandler
         client.disconnect()
     }
 
-    fn handle_destroy_ic(
-        &mut self,
-        client: &mut C,
-        input_method_id: u16,
-        _input_context_id: u16,
-    ) -> Result<(), ClientError> {
-        client.close(input_method_id)
-    }
-
     fn handle_preedit_draw(
         &mut self,
         _client: &mut C,