From c19acedefd83f2fea58c8ac34f03558740201a61 Mon Sep 17 00:00:00 2001 From: XDeme Date: Thu, 30 May 2024 13:51:01 -0300 Subject: [PATCH] linux: fix IME panel position while enumerating input methods --- crates/editor/src/editor.rs | 4 ++ crates/gpui/src/platform.rs | 3 ++ .../gpui/src/platform/linux/wayland/client.rs | 25 +++++++++ .../gpui/src/platform/linux/wayland/window.rs | 13 +++++ crates/gpui/src/platform/linux/x11/client.rs | 52 +++++++++++++++++-- crates/gpui/src/platform/linux/x11/window.rs | 11 ++++ crates/gpui/src/window.rs | 9 ++++ 7 files changed, 114 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 1e61dc9f4a..74bd355671 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -2172,6 +2172,9 @@ impl Editor { show_completions: bool, cx: &mut ViewContext, ) { + #[cfg(target_os = "linux")] + cx.update_ime_position(); + // Copy selections to primary selection buffer #[cfg(target_os = "linux")] if local { @@ -11620,6 +11623,7 @@ impl ViewInputHandler for Editor { fn selected_text_range(&mut self, cx: &mut ViewContext) -> Option> { // Prevent the IME menu from appearing when holding down an alphabetic key // while input is disabled. + #[cfg(target_os = "macos")] if !self.input_enabled { return None; } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index a932ac8513..1795415183 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -247,6 +247,9 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle { fn start_system_move(&self); fn should_render_window_controls(&self) -> bool; + #[cfg(target_os = "linux")] + fn update_ime_position(&self); + #[cfg(any(test, feature = "test-support"))] fn as_test(&mut self) -> Option<&mut TestWindow> { None diff --git a/crates/gpui/src/platform/linux/wayland/client.rs b/crates/gpui/src/platform/linux/wayland/client.rs index 2dfd878755..8cc32bfa1c 100644 --- a/crates/gpui/src/platform/linux/wayland/client.rs +++ b/crates/gpui/src/platform/linux/wayland/client.rs @@ -283,6 +283,30 @@ impl WaylandClientStatePtr { } } + pub fn update_ime_position(&self) { + let client = self.get_client(); + let mut state = client.borrow_mut(); + if state.composing || state.text_input.is_none() { + return; + } + let Some(window) = state.keyboard_focused_window.clone() else { + return; + }; + + let text_input = state.text_input.take().unwrap(); + drop(state); + if let Some(bounds) = window.get_ime_area() { + text_input.set_cursor_rectangle( + bounds.origin.x.0 as i32, + bounds.origin.y.0 as i32, + bounds.size.width.0 as i32, + bounds.size.height.0 as i32, + ); + text_input.commit(); + } + client.borrow_mut().text_input = Some(text_input); + } + pub fn drop_window(&self, surface_id: &ObjectId) { let mut client = self.get_client(); let mut state = client.borrow_mut(); @@ -1277,6 +1301,7 @@ impl Dispatch for WaylandClientStatePtr { } } } else { + state.composing = false; drop(state); window.handle_ime(ImeInput::DeleteText); } diff --git a/crates/gpui/src/platform/linux/wayland/window.rs b/crates/gpui/src/platform/linux/wayland/window.rs index 972a32e0e4..5c20131952 100644 --- a/crates/gpui/src/platform/linux/wayland/window.rs +++ b/crates/gpui/src/platform/linux/wayland/window.rs @@ -858,6 +858,19 @@ impl PlatformWindow for WaylandWindow { fn should_render_window_controls(&self) -> bool { self.borrow().decoration_state == WaylandDecorationState::Client } + + fn update_ime_position(&self) { + let state = self.borrow(); + let client = state.client.clone(); + let state_ptr = client.clone(); + state + .globals + .executor + .spawn(async move { + client.update_ime_position(); + }) + .detach(); + } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] diff --git a/crates/gpui/src/platform/linux/x11/client.rs b/crates/gpui/src/platform/linux/x11/client.rs index 36d1129574..774277efd9 100644 --- a/crates/gpui/src/platform/linux/x11/client.rs +++ b/crates/gpui/src/platform/linux/x11/client.rs @@ -137,8 +137,12 @@ pub struct X11ClientState { pub struct X11ClientStatePtr(pub Weak>); impl X11ClientStatePtr { + fn get_client(&self) -> X11Client { + X11Client(self.0.upgrade().expect("client already dropped")) + } + pub fn drop_window(&self, x_window: u32) { - let client = X11Client(self.0.upgrade().expect("client already dropped")); + let client = self.get_client(); let mut state = client.0.borrow_mut(); if let Some(window_ref) = state.windows.remove(&x_window) { @@ -151,6 +155,48 @@ impl X11ClientStatePtr { state.common.signal.stop(); } } + + pub fn update_ime_position(&self) { + let client = self.get_client(); + let mut state = client.0.borrow_mut(); + if state.composing || state.ximc.is_none() { + return; + } + let Some(window_id) = state.focused_window else { + return; + }; + + let mut ximc = state.ximc.take().unwrap(); + let xim_handler = state.xim_handler.take().unwrap(); + drop(state); + let window = client.get_window(window_id).unwrap(); + if let Some(area) = window.get_ime_area() { + let ic_attributes = ximc + .build_ic_attributes() + .push( + xim::AttributeName::InputStyle, + xim::InputStyle::PREEDIT_CALLBACKS + | xim::InputStyle::STATUS_NOTHING + | xim::InputStyle::PREEDIT_POSITION, + ) + .push(xim::AttributeName::ClientWindow, xim_handler.window) + .push(xim::AttributeName::FocusWindow, xim_handler.window) + .nested_list(xim::AttributeName::PreeditAttributes, |b| { + b.push( + xim::AttributeName::SpotLocation, + xim::Point { + x: u32::from(area.origin.x + area.size.width) as i16, + y: u32::from(area.origin.y + area.size.height) as i16, + }, + ); + }) + .build(); + ximc.set_ic_values(xim_handler.im_id, xim_handler.ic_id, ic_attributes); + } + state = client.0.borrow_mut(); + state.ximc = Some(ximc); + state.xim_handler = Some(xim_handler); + } } #[derive(Clone)] @@ -832,13 +878,13 @@ impl X11Client { fn xim_handle_preedit(&self, window: xproto::Window, text: String) -> Option<()> { let window = self.get_window(window).unwrap(); - window.handle_ime_preedit(text); let mut state = self.0.borrow_mut(); let mut ximc = state.ximc.take().unwrap(); let mut xim_handler = state.xim_handler.take().unwrap(); - state.composing = true; + state.composing = !text.is_empty(); drop(state); + window.handle_ime_preedit(text); if let Some(area) = window.get_ime_area() { let ic_attributes = ximc diff --git a/crates/gpui/src/platform/linux/x11/window.rs b/crates/gpui/src/platform/linux/x11/window.rs index d5c1846d56..d5657bca70 100644 --- a/crates/gpui/src/platform/linux/x11/window.rs +++ b/crates/gpui/src/platform/linux/x11/window.rs @@ -958,4 +958,15 @@ impl PlatformWindow for X11Window { fn should_render_window_controls(&self) -> bool { false } + + fn update_ime_position(&self) { + let mut state = self.0.state.borrow_mut(); + let client = state.client.clone(); + state + .executor + .spawn(async move { + client.update_ime_position(); + }) + .detach(); + } } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 78cb5dc115..42a3d84d4d 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -693,6 +693,7 @@ impl Window { .update(&mut cx, |_, cx| { cx.draw(); cx.present(); + cx.update_ime_position(); }) .log_err(); }) @@ -3572,6 +3573,14 @@ impl WindowContext<'_> { } } +#[cfg(target_os = "linux")] +impl WindowContext<'_> { + /// Updates the IME panel position suggestions for languages like japanese, chinese. + pub fn update_ime_position(&self) { + self.window.platform_window.update_ime_position(); + } +} + impl Context for WindowContext<'_> { type Result = T;