Skip to main content

ib_ime/
hook.rs

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            // Save original IME state
37            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            // Get config from subclass data to determine preferred state
46            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            // Store original state in subclass data
57            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            // Restore original IME state and conversion mode
66            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            // Free the subclass data when window is destroyed
80            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/// Configuration for IME hook behavior.
92#[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        // Allocate subclass data on heap
110        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            // Cleanup if setting subclass failed
122            let _ = unsafe { Box::from_raw(subclass_data_ptr as *mut SubclassData) };
123        }
124
125        result
126    }
127}