1use bon::Builder;
2use windows_sys::Win32::{
3 Foundation::{HWND, LPARAM, LRESULT, WPARAM},
4 UI::{
5 Shell::{DefSubclassProc, SetWindowSubclass},
6 WindowsAndMessaging::{WM_KILLFOCUS, WM_NCDESTROY, WM_SETFOCUS},
7 },
8};
9
10use crate::imm::{
11 ImeConversionMode, ImeState, get_window_ime_conversion_mode, get_window_ime_state,
12 set_ime_conversion_mode, set_window_ime_state,
13};
14
15const SUBCLASS_ID: usize = 0;
16
17struct SubclassData {
18 config: ImeHookConfig,
19 original_state: ImeState,
20 original_conversion_mode: ImeConversionMode,
21}
22
23unsafe extern "system" fn wnd_proc(
24 hwnd: HWND,
25 umsg: u32,
26 wparam: WPARAM,
27 lparam: LPARAM,
28 _id: usize,
29 data: usize,
30) -> LRESULT {
31 #[cfg(test)]
32 eprintln!("hwnd: {hwnd:p}, msg: {umsg:X}, wparam: {wparam:X}, wparam: {lparam:X}");
33
34 match umsg {
35 WM_SETFOCUS => {
36 let original_state = get_window_ime_state(hwnd);
38 let original_conversion_mode = get_window_ime_conversion_mode(hwnd);
39 #[cfg(feature = "log")]
40 {
41 eprintln!("get_ime_state: {original_state:?}");
42 eprintln!("get_ime_conversion_mode: {original_conversion_mode:?}");
43 }
44
45 let subclass_data = unsafe { (data as *mut SubclassData).read() };
47 let config = subclass_data.config;
48
49 if let Some(conversion_mode) = config.default_ime_conversion_mode {
50 set_ime_conversion_mode(conversion_mode);
51 }
52 if let Some(state) = config.default_ime_state {
53 set_window_ime_state(hwnd, state);
54 }
55
56 let original_data = Box::new(SubclassData {
58 original_state,
59 original_conversion_mode,
60 config,
61 });
62 unsafe { (data as *mut SubclassData).write(*original_data) };
63 }
64 WM_KILLFOCUS if data != 0 => {
65 let data = unsafe { (data as *mut SubclassData).read() };
67 #[cfg(feature = "log")]
68 {
69 eprintln!("set_ime_state: {:?}", data.original_state);
70 eprintln!(
71 "set_ime_conversion_mode: {:?}",
72 data.original_conversion_mode
73 );
74 }
75 set_ime_conversion_mode(data.original_conversion_mode);
76 set_window_ime_state(wparam as _, data.original_state);
77 }
78 WM_NCDESTROY => {
79 if data != 0 {
81 let _ = unsafe { Box::from_raw(data as *mut SubclassData) };
82 }
83 }
84 _ => {}
85 }
86
87 let res = unsafe { DefSubclassProc(hwnd, umsg, wparam, lparam) };
88 res
89}
90
91#[derive(Clone, Copy, PartialEq, Eq, Debug, Builder)]
93pub struct ImeHookConfig {
94 pub default_ime_state: Option<ImeState>,
95 pub default_ime_conversion_mode: Option<ImeConversionMode>,
96}
97
98impl ImeHookConfig {
99 pub fn default_off() -> Self {
100 Self {
101 default_ime_state: Some(false),
102 default_ime_conversion_mode: Some(ImeConversionMode::ALPHANUMERIC),
103 }
104 }
105}
106
107impl ImeHookConfig {
108 pub fn hook_window(self, hwnd: HWND) -> bool {
109 let subclass_data = Box::new(SubclassData {
111 config: self,
112 original_state: false,
113 original_conversion_mode: ImeConversionMode::empty(),
114 });
115 let subclass_data_ptr = Box::into_raw(subclass_data) as usize;
116
117 let result =
118 unsafe { SetWindowSubclass(hwnd, Some(wnd_proc), SUBCLASS_ID, subclass_data_ptr) != 0 };
119
120 if !result {
121 let _ = unsafe { Box::from_raw(subclass_data_ptr as *mut SubclassData) };
123 }
124
125 result
126 }
127}