From 845eb64615f456df4444ea2fc5fb7042da8cdf4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=B0=8F=E7=99=BD?= <364772080@qq.com> Date: Fri, 15 Mar 2024 06:29:25 +0800 Subject: [PATCH] Windows: IME support (#9188) Waiting for #9180 to be merged. The behavior of IME windows is consistent with VS Code. https://github.com/zed-industries/zed/assets/14981363/14913219-7345-4fec-9cc5-623d0c60acc9 Release Notes: - N/A --- Cargo.toml | 2 + crates/gpui/src/platform/windows/window.rs | 124 +++++++++++++++++++-- 2 files changed, 115 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 458bf0c064..f6dc769635 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -340,6 +340,7 @@ version = "0.53.0" features = [ "implement", "Wdk_System_SystemServices", + "Win32_Globalization", "Win32_Graphics_DirectComposition", "Win32_Graphics_Gdi", "Win32_Security", @@ -353,6 +354,7 @@ features = [ "Win32_System_Time", "Win32_System_Threading", "Win32_UI_Controls", + "Win32_UI_Input_Ime", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", diff --git a/crates/gpui/src/platform/windows/window.rs b/crates/gpui/src/platform/windows/window.rs index 36ca490960..c5d85d3759 100644 --- a/crates/gpui/src/platform/windows/window.rs +++ b/crates/gpui/src/platform/windows/window.rs @@ -22,7 +22,7 @@ use smallvec::SmallVec; use windows::{ core::{implement, w, HSTRING, PCWSTR}, Win32::{ - Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, POINTL, S_OK, WPARAM}, + Foundation::{FALSE, HINSTANCE, HWND, LPARAM, LRESULT, POINT, POINTL, S_OK, WPARAM}, Graphics::Gdi::{BeginPaint, EndPaint, InvalidateRect, PAINTSTRUCT}, System::{ Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL}, @@ -39,22 +39,28 @@ use windows::{ TaskDialogIndirect, TASKDIALOGCONFIG, TASKDIALOG_BUTTON, TD_ERROR_ICON, TD_INFORMATION_ICON, TD_WARNING_ICON, }, - Input::KeyboardAndMouse::{ - GetKeyState, VIRTUAL_KEY, VK_0, VK_A, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, - VK_ESCAPE, VK_F1, VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, - VK_PRIOR, VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_TAB, VK_UP, + Input::{ + Ime::{ + ImmGetCompositionStringW, ImmGetContext, ImmReleaseContext, + ImmSetCandidateWindow, CANDIDATEFORM, CFS_CANDIDATEPOS, GCS_COMPSTR, + }, + KeyboardAndMouse::{ + GetKeyState, VIRTUAL_KEY, VK_0, VK_A, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, + VK_ESCAPE, VK_F1, VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, + VK_NEXT, VK_PRIOR, VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_TAB, VK_UP, + }, }, Shell::{DragQueryFileW, HDROP}, WindowsAndMessaging::{ CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage, RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WHEEL_DELTA, - WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WM_CHAR, WM_CLOSE, WM_DESTROY, WM_KEYDOWN, - WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, - WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOVE, WM_NCCREATE, WM_NCDESTROY, - WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, - WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE, XBUTTON1, - XBUTTON2, + WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WM_CHAR, WM_CLOSE, WM_DESTROY, WM_IME_CHAR, + WM_IME_COMPOSITION, WM_IME_STARTCOMPOSITION, WM_KEYDOWN, WM_KEYUP, WM_LBUTTONDOWN, + WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP, WM_MOUSEHWHEEL, WM_MOUSEMOVE, + WM_MOUSEWHEEL, WM_MOVE, WM_NCCREATE, WM_NCDESTROY, WM_PAINT, WM_RBUTTONDOWN, + WM_RBUTTONUP, WM_SIZE, WM_SYSKEYDOWN, WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP, + WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE, XBUTTON1, XBUTTON2, }, }, }, @@ -211,6 +217,9 @@ impl WindowsWindowInner { WM_KEYDOWN => self.handle_keydown_msg(msg, wparam, lparam), WM_KEYUP => self.handle_keyup_msg(msg, wparam), WM_CHAR => self.handle_char_msg(msg, wparam, lparam), + WM_IME_STARTCOMPOSITION => self.handle_ime_position(), + WM_IME_COMPOSITION => self.handle_ime_composition(msg, wparam, lparam), + WM_IME_CHAR => self.handle_ime_char(wparam), _ => unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }, } } @@ -612,6 +621,99 @@ impl WindowsWindowInner { LRESULT(1) } + fn handle_ime_position(&self) -> LRESULT { + unsafe { + let ctx = ImmGetContext(self.hwnd); + let Some(mut input_handler) = self.input_handler.take() else { + return LRESULT(1); + }; + // we are composing, this should never fail + let caret_range = input_handler.selected_text_range().unwrap(); + let caret_position = input_handler.bounds_for_range(caret_range).unwrap(); + self.input_handler.set(Some(input_handler)); + let config = CANDIDATEFORM { + dwStyle: CFS_CANDIDATEPOS, + ptCurrentPos: POINT { + x: caret_position.origin.x.0 as i32, + y: caret_position.origin.y.0 as i32 + (caret_position.size.height.0 as i32 / 2), + }, + ..Default::default() + }; + ImmSetCandidateWindow(ctx, &config as _); + ImmReleaseContext(self.hwnd, ctx); + LRESULT(0) + } + } + + fn parse_ime_compostion_string(&self) -> Option<(String, usize)> { + unsafe { + let ctx = ImmGetContext(self.hwnd); + let string_len = ImmGetCompositionStringW(ctx, GCS_COMPSTR, None, 0); + let result = if string_len >= 0 { + let mut buffer = vec![0u8; string_len as usize + 2]; + // let mut buffer = [0u8; MAX_PATH as _]; + ImmGetCompositionStringW( + ctx, + GCS_COMPSTR, + Some(buffer.as_mut_ptr() as _), + string_len as _, + ); + let wstring = std::slice::from_raw_parts::( + buffer.as_mut_ptr().cast::(), + string_len as usize / 2, + ); + let string = String::from_utf16_lossy(wstring); + Some((string, string_len as usize / 2)) + } else { + None + }; + ImmReleaseContext(self.hwnd, ctx); + result + } + } + + fn handle_ime_composition(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT { + if lparam.0 as u32 & GCS_COMPSTR.0 > 0 { + let Some((string, string_len)) = self.parse_ime_compostion_string() else { + return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }; + }; + let Some(mut input_handler) = self.input_handler.take() else { + return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }; + }; + input_handler.replace_and_mark_text_in_range( + None, + string.as_str(), + Some(0..string_len), + ); + self.input_handler.set(Some(input_handler)); + unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) } + } else { + // currently, we don't care other stuff + unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) } + } + } + + fn parse_ime_char(&self, wparam: WPARAM) -> Option { + let src = [wparam.0 as u16]; + let Ok(first_char) = char::decode_utf16(src).collect::>()[0] else { + return None; + }; + Some(first_char.to_string()) + } + + fn handle_ime_char(&self, wparam: WPARAM) -> LRESULT { + let Some(ime_char) = self.parse_ime_char(wparam) else { + return LRESULT(1); + }; + let Some(mut input_handler) = self.input_handler.take() else { + return LRESULT(1); + }; + input_handler.replace_text_in_range(None, &ime_char); + self.input_handler.set(Some(input_handler)); + self.invalidate_client_area(); + LRESULT(0) + } + fn handle_drag_drop(&self, input: PlatformInput) { let mut callbacks = self.callbacks.borrow_mut(); let Some(ref mut func) = callbacks.input else {