From 01773d65f6de5a76ccb055700abd0a7f54bc5aa3 Mon Sep 17 00:00:00 2001
From: MilkCOCO_
Date: Thu, 16 Apr 2026 00:15:14 +0800
Subject: [PATCH 1/3] =?UTF-8?q?fit:=20windows=E7=9A=84ime=20preedit?=
=?UTF-8?q?=E5=92=8Ccommit=E4=BA=8B=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 3 +-
Cargo.toml | 2 +
src/event.rs | 6 ++
src/lib.rs | 9 ++-
src/native.rs | 4 +-
src/native/linux_x11/xi_input.rs | 6 +-
src/native/windows.rs | 110 +++++++++++++++++++++----------
7 files changed, 101 insertions(+), 39 deletions(-)
diff --git a/.gitignore b/.gitignore
index 2f88dbac5..97d204ad6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/target
**/*.rs.bk
-Cargo.lock
\ No newline at end of file
+Cargo.lock
+/.idea
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 224bebe17..2c5bf5314 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,6 +37,8 @@ winapi = { version = "0.3", features = [
"hidusage",
"shellapi",
"imm",
+ "minwindef",
+ "ntdef"
] }
[target.'cfg(target_os = "android")'.dependencies]
diff --git a/src/event.rs b/src/event.rs
index 82e0a9258..2eb739256 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -233,4 +233,10 @@ pub trait EventHandler {
/// `ctx.dropped_file_path()`, and for wasm targets the file bytes
/// can be requested with `ctx.dropped_file_bytes()`.
fn files_dropped_event(&mut self) {}
+
+ /// Get ime preedit text
+ fn on_ime_preedit(&mut self, _text: &str) {}
+
+ /// Get ime commit text before preedit
+ fn on_ime_commit(&mut self, _text: Option<&str>) {}
}
diff --git a/src/lib.rs b/src/lib.rs
index e7f9d5004..773b154ad 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -410,7 +410,9 @@ pub mod window {
/// # Arguments
/// * `enabled` - `true` to enable IME (for text input), `false` to disable (for game controls)
pub fn set_ime_enabled(enabled: bool) {
- let d = native_display().lock().unwrap();
+ let mut d = native_display().lock().unwrap();
+ d.ime_enabled = enabled;
+
#[cfg(target_os = "android")]
{
let _ = enabled; // IME control not applicable on Android
@@ -423,6 +425,11 @@ pub mod window {
.unwrap();
}
}
+
+ pub fn is_ime_enabled() -> bool {
+ let d = native_display().lock().unwrap();
+ d.ime_enabled
+ }
#[cfg(target_vendor = "apple")]
pub fn apple_gfx_api() -> crate::conf::AppleGfxApi {
diff --git a/src/native.rs b/src/native.rs
index f38200360..2d06c578b 100644
--- a/src/native.rs
+++ b/src/native.rs
@@ -12,6 +12,7 @@ pub(crate) struct NativeDisplayData {
pub screen_width: i32,
pub screen_height: i32,
pub screen_position: (u32, u32),
+ pub ime_enabled: bool,
pub dpi_scale: f32,
pub high_dpi: bool,
pub quit_requested: bool,
@@ -23,7 +24,7 @@ pub(crate) struct NativeDisplayData {
pub clipboard: Box,
pub dropped_files: DroppedFiles,
pub blocking_event_loop: bool,
-
+
#[cfg(target_vendor = "apple")]
pub view: crate::native::apple::frameworks::ObjcId,
#[cfg(target_os = "ios")]
@@ -48,6 +49,7 @@ impl NativeDisplayData {
screen_width,
screen_height,
screen_position: (0, 0),
+ ime_enabled: false,
dpi_scale: 1.,
high_dpi: false,
quit_requested: false,
diff --git a/src/native/linux_x11/xi_input.rs b/src/native/linux_x11/xi_input.rs
index ffb6f9876..1efca7a31 100644
--- a/src/native/linux_x11/xi_input.rs
+++ b/src/native/linux_x11/xi_input.rs
@@ -124,9 +124,9 @@ impl LibXi {
let raw_event = xcookie.data as *mut xi_input::XIRawEvent;
// Data returned from Xlib is not guaranteed to be aligned
- let ptr = (*raw_event).raw_values as *const u8;
- let dx = std::ptr::read_unaligned(ptr as *const f64);
- let dy = std::ptr::read_unaligned(ptr.add(1) as *const f64);
+ let ptr = (*raw_event).raw_values as *const f64;
+ let dx = std::ptr::read_unaligned(ptr);
+ let dy = std::ptr::read_unaligned(ptr.add(1));
(self.XFreeEventData)(display, &mut (*xcookie) as *mut _);
diff --git a/src/native/windows.rs b/src/native/windows.rs
index 01ffe5fa4..5a3c8c1fa 100644
--- a/src/native/windows.rs
+++ b/src/native/windows.rs
@@ -25,8 +25,13 @@ use winapi::{
},
};
-// IME constants
+// IME composition string flags
+/// Composition String
+const GCS_COMPSTR: DWORD = 0x0008;
+/// Result String
const GCS_RESULTSTR: DWORD = 0x0800;
+const GCS_CURSORPOS: DWORD = 0x0100;
+const GCS_DELTASTART: DWORD = 0x0200;
// IME message constants
const WM_IME_SETCONTEXT: UINT = 0x0281;
@@ -168,15 +173,20 @@ impl WindowsDisplay {
/// Enable or disable IME for the window.
/// When disabled, the IME will not process keyboard input, useful for game controls.
+ // src/native/windows.rs (WindowsDisplay::set_ime_enabled)
fn set_ime_enabled(&mut self, enabled: bool) {
unsafe {
if enabled {
IME_USER_DISABLED.store(false, std::sync::atomic::Ordering::Relaxed);
- // Re-associate IME context with the window
- ImmAssociateContextEx(self.wnd, std::ptr::null_mut(), IACE_DEFAULT);
+ let himc = ImmGetContext(self.wnd);
+ if !himc.is_null() {
+ // 强制打开并重置
+ ImmSetOpenStatus(himc, 1);
+ ImmAssociateContextEx(self.wnd, himc, IACE_DEFAULT);
+ ImmReleaseContext(self.wnd, himc);
+ }
} else {
IME_USER_DISABLED.store(true, std::sync::atomic::Ordering::Relaxed);
- // Disassociate IME context from the window
ImmAssociateContextEx(self.wnd, std::ptr::null_mut(), 0);
}
}
@@ -202,6 +212,7 @@ impl WindowsDisplay {
self.user_cursor = cursor_icon != CursorIcon::Default;
}
+
fn set_window_size(&mut self, new_width: u32, new_height: u32) {
let mut x = 0;
let mut y = 0;
@@ -590,49 +601,80 @@ unsafe extern "system" fn win32_wndproc(
}
WM_IME_COMPOSITION => {
let flags = lparam as u32;
+ let himc = ImmGetContext(hwnd);
- // Extract and dispatch the result string manually to avoid duplicates
- if (flags & GCS_RESULTSTR) != 0 {
- let himc = ImmGetContext(hwnd);
- if !himc.is_null() {
- let len = ImmGetCompositionStringW(himc, GCS_RESULTSTR, std::ptr::null_mut(), 0);
+ if !himc.is_null() {
+ let mut should_notify_end = false;
+
+ // Composition String
+ if (flags & GCS_COMPSTR) != 0 {
+ let len = ImmGetCompositionStringW(himc, GCS_COMPSTR, std::ptr::null_mut(), 0);
if len > 0 {
let mut buffer: Vec = vec![0; (len as usize / 2) + 1];
- let actual_len = ImmGetCompositionStringW(
- himc,
- GCS_RESULTSTR,
- buffer.as_mut_ptr() as *mut _,
- len as u32
- );
- if actual_len > 0 {
- let char_count = actual_len as usize / 2;
- let mods = key_mods();
- // Send chars in order
- for i in 0..char_count {
- let chr = buffer[i];
- if let Some(c) = char::from_u32(chr as u32) {
- event_handler.char_event(c, mods, false);
- }
- }
+ ImmGetCompositionStringW(himc, GCS_COMPSTR, buffer.as_mut_ptr() as *mut _, len as u32);
+ let char_count = len as usize / 2;
+ let preedit_str = String::from_utf16_lossy(&buffer[..char_count]);
+
+ event_handler.on_ime_preedit(&preedit_str);
+ } else {
+ should_notify_end = true;
+ }
+ }
+
+
+ // Check Cursor
+ if (flags & GCS_CURSORPOS) != 0 || (flags & GCS_DELTASTART) != 0 {
+ let cursor_pos_len = ImmGetCompositionStringW(himc, GCS_CURSORPOS, std::ptr::null_mut(), 0);
+ let delta_start_len = ImmGetCompositionStringW(himc, GCS_DELTASTART, std::ptr::null_mut(), 0);
+
+ if cursor_pos_len == 0 && delta_start_len == 0 {
+ let comp_len = ImmGetCompositionStringW(himc, GCS_COMPSTR, std::ptr::null_mut(), 0);
+ if comp_len == 0 {
+ should_notify_end = true;
}
}
- ImmReleaseContext(hwnd, himc);
}
- return 0;
+
+ // Result String
+ if (flags & GCS_RESULTSTR) != 0 {
+ let len = ImmGetCompositionStringW(himc, GCS_RESULTSTR, std::ptr::null_mut(), 0);
+ if len > 0 {
+ let mut buffer: Vec = vec![0; (len as usize / 2) + 1];
+ ImmGetCompositionStringW(himc, GCS_RESULTSTR, buffer.as_mut_ptr() as *mut _, len as u32);
+ let char_count = len as usize / 2;
+ let result_str = String::from_utf16_lossy(&buffer[..char_count]);
+ event_handler.on_ime_commit(Some(&result_str));
+ should_notify_end = false;
+ } else {
+ should_notify_end = true;
+ }
+ }
+
+ if should_notify_end {
+ event_handler.on_ime_commit(None);
+ }
+ ImmReleaseContext(hwnd, himc);
}
- // For non-result messages (composition state updates), pass to DefWindowProc
- return DefWindowProcW(hwnd, umsg, wparam, lparam);
+ return 0;
}
WM_IME_SETCONTEXT => {
- let user_disabled = IME_USER_DISABLED.load(std::sync::atomic::Ordering::Relaxed);
+ // wparam 的高位 (HIWORD) 表示是否请求显示默认窗口
+ let fShow = HIWORD(wparam as _) != 0;
- // If user explicitly disabled IME, don't auto-restore
- if user_disabled {
+ // 我们想要隐藏默认窗口(因为我们要自己画)
+ // 所以返回 1 (TRUE) 告诉系统:"是的,我收到了,但我决定不显示默认窗口"
+ // 注意:这里不需要调用 DefWindowProc,直接返回即可,或者根据需求微调
+
+ // 关键:如果用户没有禁用 IME,我们强制让系统不要画默认框
+ let user_disabled = IME_USER_DISABLED.load(std::sync::atomic::Ordering::Relaxed);
+ if !user_disabled {
+ // 返回 1 表示 "我处理了,别画默认框"
+ return 1;
+ } else {
return 0;
}
- // Must pass to DefWindowProc to enable IME properly
return DefWindowProcW(hwnd, umsg, wparam, lparam);
}
WM_IME_STARTCOMPOSITION => {
@@ -687,6 +729,8 @@ unsafe extern "system" fn win32_wndproc(
return DefWindowProcW(hwnd, umsg, wparam, lparam);
}
WM_INPUTLANGCHANGEREQUEST | WM_INPUTLANGCHANGE => {
+ event_handler.on_ime_commit(None);
+
// Pass input language change messages to default handler
return DefWindowProcW(hwnd, umsg, wparam, lparam);
}
From 85bc0ec15a9999bdfc96136cdf8bd2a7bafb2ab7 Mon Sep 17 00:00:00 2001
From: MilkCOCO_
Date: Sun, 19 Apr 2026 00:05:08 +0800
Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=E8=AF=AF=E8=A7=A6=E5=88=A0=E9=99=A4?=
=?UTF-8?q?=E7=9A=84=E6=8C=87=E9=92=88=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/native/linux_x11/xi_input.rs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/native/linux_x11/xi_input.rs b/src/native/linux_x11/xi_input.rs
index 1efca7a31..ffb6f9876 100644
--- a/src/native/linux_x11/xi_input.rs
+++ b/src/native/linux_x11/xi_input.rs
@@ -124,9 +124,9 @@ impl LibXi {
let raw_event = xcookie.data as *mut xi_input::XIRawEvent;
// Data returned from Xlib is not guaranteed to be aligned
- let ptr = (*raw_event).raw_values as *const f64;
- let dx = std::ptr::read_unaligned(ptr);
- let dy = std::ptr::read_unaligned(ptr.add(1));
+ let ptr = (*raw_event).raw_values as *const u8;
+ let dx = std::ptr::read_unaligned(ptr as *const f64);
+ let dy = std::ptr::read_unaligned(ptr.add(1) as *const f64);
(self.XFreeEventData)(display, &mut (*xcookie) as *mut _);
From b77104e8f35754b4093e150ec8c7d1f8f3741725 Mon Sep 17 00:00:00 2001
From: MilkCOCO_
Date: Sun, 19 Apr 2026 00:16:52 +0800
Subject: [PATCH 3/3] =?UTF-8?q?fix:=20set=5Fime=5Fenabled=E4=B8=AD?=
=?UTF-8?q?=E9=94=99=E8=AF=AF=E7=9A=84=E5=BC=BA=E5=88=B6=E9=87=8D=E8=AE=BE?=
=?UTF-8?q?ime=E4=B8=8A=E4=B8=8B=E6=96=87=20=E8=BF=99=E6=AE=B5=E6=98=AFai?=
=?UTF-8?q?=E5=86=99=E7=9A=84=E4=B8=8D=E6=80=A8=E6=88=91=EF=BC=81=EF=BC=81?=
=?UTF-8?q?qwq=EF=BC=81=EF=BC=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/native/windows.rs | 21 ++++++---------------
1 file changed, 6 insertions(+), 15 deletions(-)
diff --git a/src/native/windows.rs b/src/native/windows.rs
index 5a3c8c1fa..d8182ff61 100644
--- a/src/native/windows.rs
+++ b/src/native/windows.rs
@@ -173,20 +173,15 @@ impl WindowsDisplay {
/// Enable or disable IME for the window.
/// When disabled, the IME will not process keyboard input, useful for game controls.
- // src/native/windows.rs (WindowsDisplay::set_ime_enabled)
fn set_ime_enabled(&mut self, enabled: bool) {
unsafe {
if enabled {
IME_USER_DISABLED.store(false, std::sync::atomic::Ordering::Relaxed);
- let himc = ImmGetContext(self.wnd);
- if !himc.is_null() {
- // 强制打开并重置
- ImmSetOpenStatus(himc, 1);
- ImmAssociateContextEx(self.wnd, himc, IACE_DEFAULT);
- ImmReleaseContext(self.wnd, himc);
- }
+ // Re-associate IME context with the window
+ ImmAssociateContextEx(self.wnd, std::ptr::null_mut(), IACE_DEFAULT);
} else {
IME_USER_DISABLED.store(true, std::sync::atomic::Ordering::Relaxed);
+ // Disassociate IME context from the window
ImmAssociateContextEx(self.wnd, std::ptr::null_mut(), 0);
}
}
@@ -659,14 +654,8 @@ unsafe extern "system" fn win32_wndproc(
return 0;
}
WM_IME_SETCONTEXT => {
- // wparam 的高位 (HIWORD) 表示是否请求显示默认窗口
let fShow = HIWORD(wparam as _) != 0;
-
- // 我们想要隐藏默认窗口(因为我们要自己画)
- // 所以返回 1 (TRUE) 告诉系统:"是的,我收到了,但我决定不显示默认窗口"
- // 注意:这里不需要调用 DefWindowProc,直接返回即可,或者根据需求微调
-
- // 关键:如果用户没有禁用 IME,我们强制让系统不要画默认框
+
let user_disabled = IME_USER_DISABLED.load(std::sync::atomic::Ordering::Relaxed);
if !user_disabled {
// 返回 1 表示 "我处理了,别画默认框"
@@ -841,6 +830,8 @@ unsafe extern "system" fn win32_wndproc(
return DefWindowProcW(hwnd, umsg, wparam, lparam);
}
WM_KILLFOCUS => {
+ event_handler.on_ime_commit(None);
+
return DefWindowProcW(hwnd, umsg, wparam, lparam);
}
_ => {}