win_wrap/
input.rs

1/*
2 * Copyright (c) 2023. The RigelA open source project team and
3 * its contributors reserve all rights.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 * See the License for the specific language governing permissions and limitations under the License.
12 */
13
14use crate::memory::HeapMemory;
15use crate::{
16    common::{HWND, LPARAM},
17    ext::StringExt,
18};
19pub use windows::Win32::UI::{
20    Controls::{HOTKEYF_ALT, HOTKEYF_CONTROL, HOTKEYF_EXT, HOTKEYF_SHIFT},
21    Input::{
22        Ime::{
23            CANDIDATELIST, HIMC, IME_CMODE_ALPHANUMERIC, IME_CMODE_CHARCODE, IME_CMODE_CHINESE,
24            IME_CMODE_EUDC, IME_CMODE_FIXED, IME_CMODE_FULLSHAPE, IME_CMODE_HANGEUL,
25            IME_CMODE_HANGUL, IME_CMODE_HANJACONVERT, IME_CMODE_JAPANESE, IME_CMODE_KATAKANA,
26            IME_CMODE_LANGUAGE, IME_CMODE_NATIVE, IME_CMODE_NATIVESYMBOL, IME_CMODE_NOCONVERSION,
27            IME_CMODE_RESERVED, IME_CMODE_ROMAN, IME_CMODE_SOFTKBD, IME_CMODE_SYMBOL,
28            IME_CONVERSION_MODE, IME_SENTENCE_MODE, IME_SMODE_AUTOMATIC, IME_SMODE_CONVERSATION,
29            IME_SMODE_NONE, IME_SMODE_PHRASEPREDICT, IME_SMODE_PLAURALCLAUSE, IME_SMODE_RESERVED,
30            IME_SMODE_SINGLECONVERT, IMN_CHANGECANDIDATE, IMN_CLOSECANDIDATE,
31            IMN_CLOSESTATUSWINDOW, IMN_GUIDELINE, IMN_OPENCANDIDATE, IMN_OPENSTATUSWINDOW,
32            IMN_PRIVATE, IMN_SETCANDIDATEPOS, IMN_SETCOMPOSITIONFONT, IMN_SETCOMPOSITIONWINDOW,
33            IMN_SETCONVERSIONMODE, IMN_SETOPENSTATUS, IMN_SETSENTENCEMODE, IMN_SETSTATUSWINDOWPOS,
34            IMN_SOFTKBDDESTROYED,
35        },
36        KeyboardAndMouse::{
37            VIRTUAL_KEY, VK_0, VK_1, VK_2, VK_3, VK_4, VK_5, VK_6, VK_7, VK_8, VK_9, VK_A,
38            VK_ABNT_C1, VK_ABNT_C2, VK_ACCEPT, VK_ADD, VK_APPS, VK_ATTN, VK_B, VK_BACK,
39            VK_BROWSER_BACK, VK_BROWSER_FAVORITES, VK_BROWSER_FORWARD, VK_BROWSER_HOME,
40            VK_BROWSER_REFRESH, VK_BROWSER_SEARCH, VK_BROWSER_STOP, VK_C, VK_CANCEL, VK_CAPITAL,
41            VK_CLEAR, VK_CONTROL, VK_CONVERT, VK_CRSEL, VK_D, VK_DBE_ALPHANUMERIC,
42            VK_DBE_CODEINPUT, VK_DBE_DBCSCHAR, VK_DBE_DETERMINESTRING,
43            VK_DBE_ENTERDLGCONVERSIONMODE, VK_DBE_ENTERIMECONFIGMODE, VK_DBE_ENTERWORDREGISTERMODE,
44            VK_DBE_FLUSHSTRING, VK_DBE_HIRAGANA, VK_DBE_KATAKANA, VK_DBE_NOCODEINPUT,
45            VK_DBE_NOROMAN, VK_DBE_ROMAN, VK_DBE_SBCSCHAR, VK_DECIMAL, VK_DELETE, VK_DIVIDE,
46            VK_DOWN, VK_E, VK_END, VK_EREOF, VK_ESCAPE, VK_EXECUTE, VK_EXSEL, VK_F, VK_F1, VK_F10,
47            VK_F11, VK_F12, VK_F13, VK_F14, VK_F15, VK_F16, VK_F17, VK_F18, VK_F19, VK_F2, VK_F20,
48            VK_F21, VK_F22, VK_F23, VK_F24, VK_F3, VK_F4, VK_F5, VK_F6, VK_F7, VK_F8, VK_F9,
49            VK_FINAL, VK_FPARAM, VK_G, VK_GAMEPAD_A, VK_GAMEPAD_B, VK_GAMEPAD_DPAD_DOWN,
50            VK_GAMEPAD_DPAD_LEFT, VK_GAMEPAD_DPAD_RIGHT, VK_GAMEPAD_DPAD_UP,
51            VK_GAMEPAD_LEFT_SHOULDER, VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON,
52            VK_GAMEPAD_LEFT_THUMBSTICK_DOWN, VK_GAMEPAD_LEFT_THUMBSTICK_LEFT,
53            VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT, VK_GAMEPAD_LEFT_THUMBSTICK_UP,
54            VK_GAMEPAD_LEFT_TRIGGER, VK_GAMEPAD_MENU, VK_GAMEPAD_RIGHT_SHOULDER,
55            VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON, VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN,
56            VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT, VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT,
57            VK_GAMEPAD_RIGHT_THUMBSTICK_UP, VK_GAMEPAD_RIGHT_TRIGGER, VK_GAMEPAD_VIEW,
58            VK_GAMEPAD_X, VK_GAMEPAD_Y, VK_H, VK_HANGEUL, VK_HANGUL, VK_HANJA, VK_HELP, VK_HOME,
59            VK_I, VK_ICO_00, VK_ICO_CLEAR, VK_ICO_HELP, VK_IME_OFF, VK_IME_ON, VK_INSERT, VK_J,
60            VK_JUNJA, VK_K, VK_KANA, VK_KANJI, VK_L, VK_LAUNCH_APP1, VK_LAUNCH_APP2,
61            VK_LAUNCH_MAIL, VK_LAUNCH_MEDIA_SELECT, VK_LBUTTON, VK_LCONTROL, VK_LEFT, VK_LMENU,
62            VK_LSHIFT, VK_LWIN, VK_M, VK_MBUTTON, VK_MEDIA_NEXT_TRACK, VK_MEDIA_PLAY_PAUSE,
63            VK_MEDIA_PREV_TRACK, VK_MEDIA_STOP, VK_MENU, VK_MODECHANGE, VK_MULTIPLY, VK_N,
64            VK_NAVIGATION_ACCEPT, VK_NAVIGATION_CANCEL, VK_NAVIGATION_DOWN, VK_NAVIGATION_LEFT,
65            VK_NAVIGATION_MENU, VK_NAVIGATION_RIGHT, VK_NAVIGATION_UP, VK_NAVIGATION_VIEW, VK_NEXT,
66            VK_NONAME, VK_NONCONVERT, VK_NUMLOCK, VK_NUMPAD0, VK_NUMPAD1, VK_NUMPAD2, VK_NUMPAD3,
67            VK_NUMPAD4, VK_NUMPAD5, VK_NUMPAD6, VK_NUMPAD7, VK_NUMPAD8, VK_NUMPAD9, VK_O, VK_OEM_1,
68            VK_OEM_102, VK_OEM_2, VK_OEM_3, VK_OEM_4, VK_OEM_5, VK_OEM_6, VK_OEM_7, VK_OEM_8,
69            VK_OEM_ATTN, VK_OEM_AUTO, VK_OEM_AX, VK_OEM_BACKTAB, VK_OEM_CLEAR, VK_OEM_COMMA,
70            VK_OEM_COPY, VK_OEM_CUSEL, VK_OEM_ENLW, VK_OEM_FINISH, VK_OEM_FJ_JISHO, VK_OEM_FJ_LOYA,
71            VK_OEM_FJ_MASSHOU, VK_OEM_FJ_ROYA, VK_OEM_FJ_TOUROKU, VK_OEM_JUMP, VK_OEM_MINUS,
72            VK_OEM_NEC_EQUAL, VK_OEM_PA1, VK_OEM_PA2, VK_OEM_PA3, VK_OEM_PERIOD, VK_OEM_PLUS,
73            VK_OEM_RESET, VK_OEM_WSCTRL, VK_P, VK_PA1, VK_PACKET, VK_PAUSE, VK_PLAY, VK_PRINT,
74            VK_PRIOR, VK_PROCESSKEY, VK_Q, VK_R, VK_RBUTTON, VK_RCONTROL, VK_RETURN, VK_RIGHT,
75            VK_RMENU, VK_RSHIFT, VK_RWIN, VK_S, VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT,
76            VK_SLEEP, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_T, VK_TAB, VK_U, VK_UP, VK_V,
77            VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_W, VK_X, VK_XBUTTON1, VK_XBUTTON2,
78            VK_Y, VK_Z, VK_ZOOM,
79        },
80    },
81    WindowsAndMessaging::{
82        WM_CHAR, WM_DEADCHAR, WM_IME_CHAR, WM_IME_COMPOSITION, WM_IME_COMPOSITIONFULL,
83        WM_IME_CONTROL, WM_IME_ENDCOMPOSITION, WM_IME_KEYDOWN, WM_IME_KEYLAST, WM_IME_KEYUP,
84        WM_IME_NOTIFY, WM_IME_REQUEST, WM_IME_SELECT, WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION,
85        WM_KEYDOWN, WM_KEYUP, WM_MOUSEMOVE, WM_SYSCHAR, WM_SYSDEADCHAR, WM_SYSKEYDOWN, WM_SYSKEYUP,
86    },
87};
88use windows::Win32::{
89    Foundation::POINT,
90    UI::{
91        Input::{
92            Ime::{
93                ImmGetCandidateListCountW, ImmGetCandidateListW, ImmGetContext,
94                ImmGetConversionStatus, ImmReleaseContext,
95            },
96            KeyboardAndMouse::{
97                keybd_event, mouse_event, GetAsyncKeyState, GetFocus, GetKeyNameTextW, GetKeyState,
98                SetActiveWindow, KEYEVENTF_EXTENDEDKEY, KEYEVENTF_KEYUP, MOUSEEVENTF_LEFTDOWN,
99                MOUSEEVENTF_LEFTUP, MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP,
100            },
101        },
102        WindowsAndMessaging::{GetCursorPos, SetCursorPos},
103    },
104};
105
106/**
107激活窗口。 窗口必须附加到调用线程的消息队列。
108如果函数成功,则返回值是以前处于活动状态的窗口的句柄。
109如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 get_last_error。
110set_active_window 函数激活窗口,但如果应用程序在后台,则不会激活窗口。 当系统激活窗口时,如果窗口的应用程序位于前台,则窗口将进入 Z的前台 (顶部顺序)。
111如果 由 h_wnd 参数标识的窗口是由调用线程创建的,则调用线程的活动窗口状态将设置为 h_wnd。 否则,调用线程的活动窗口状态设置为 NULL。
112`h_wnd` 要激活的顶级窗口的句柄。
113*/
114pub fn set_active_window(h_wnd: HWND) -> HWND {
115    unsafe { SetActiveWindow(h_wnd).unwrap_or(Default::default()) }
116}
117
118//noinspection StructuralWrap
119/**
120如果窗口附加到调用线程的消息队列,则检索具有键盘焦点的窗口的句柄。
121返回值是具有键盘焦点的窗口的句柄。 如果调用线程的消息队列没有与键盘焦点关联的窗口,则返回值为 NULL。
122get_focus 返回具有当前线程消息队列的键盘焦点的窗口。 如果 get_focus 返回 NULL,则另一个线程的队列可能会附加到具有键盘焦点的窗口。
123使用 get_foreground_window 函数检索用户当前正在使用的窗口的句柄。 可以使用 attach_thread_input 函数将线程的消息队列与其他线程拥有的窗口相关联。
124若要获取键盘焦点位于前台队列或其他线程队列上的窗口,请使用 get_gui_thread_info 函数。
125*/
126pub fn get_focus() -> HWND {
127    unsafe { GetFocus() }
128}
129
130/**
131获取一个键的按下状态(从上一次调用此函数开始计算),返回的第一个值表示按下过这个键并一直到现在都处于按下状态,第二个值表示是否再次按下过他。
132`key` 虚拟键常亮。
133*/
134pub fn get_async_key_state(key: VIRTUAL_KEY) -> (bool, bool) {
135    let state = unsafe { GetAsyncKeyState(key.0 as i32) };
136    let (high, low) = (state >> 8, state & 0xff);
137    (high != 0, low != 0)
138}
139
140/**
141获取某个键的当前状态,返回的第一个值表示当前是否按下,第二个值表示锁定状态(例如大小写锁定键)。
142`key` 虚拟键常亮。
143*/
144pub fn get_key_state(key: VIRTUAL_KEY) -> (bool, bool) {
145    let state = unsafe { GetKeyState(key.0 as i32) };
146    let high = state >> 8;
147    let low = state & 0xffi16;
148    (high != 0, low != 0)
149}
150
151/// 模拟按键
152pub fn send_key(key: VIRTUAL_KEY) {
153    unsafe {
154        keybd_event(key.0 as u8, 0, KEYEVENTF_EXTENDEDKEY, 0);
155        keybd_event(key.0 as u8, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
156    }
157}
158
159/**
160获取一个键的名称。
161*/
162pub fn get_key_name(l_param: &LPARAM) -> String {
163    unsafe {
164        let mut text: [u16; 16] = [0; 16];
165        let len = GetKeyNameTextW(l_param.0 as i32, &mut text);
166        String::from_utf16_lossy(&text[..len as usize])
167    }
168}
169
170/**
171此函数查询与指定窗口关联的输入上下文。
172在尝试访问上下文中的信息之前,应用程序应常规使用此函数来查询当前输入上下文。
173`h_wnd` 要查询其输入上下文的窗口的句柄。
174*/
175pub fn imm_get_context(h_wnd: HWND) -> HIMC {
176    unsafe { ImmGetContext(h_wnd) }
177}
178
179/**
180此函数释放输入上下文并解锁上下文中关联的内存。应用程序必须为每次调用 imm_get_context 函数调用 imm_release_context。
181`h_wnd` 先前查询其输入上下文的窗口的句柄。
182`h_imc` 输入上下文的句柄。
183*/
184pub fn imm_release_context(h_wnd: HWND, h_imc: HIMC) -> bool {
185    unsafe { ImmReleaseContext(h_wnd, h_imc) }.as_bool()
186}
187
188/**
189查询候选列表的大小。
190`h_imc` 输入上下文的句柄。
191*/
192pub fn imm_get_candidate_list_count(h_imc: HIMC) -> (u32, u32) {
193    unsafe {
194        let mut list_count = 0u32;
195        let buffer_len = ImmGetCandidateListCountW(h_imc, &mut list_count);
196        (buffer_len, list_count)
197    }
198}
199
200/**
201此函数查询指定的候选列表,并将该列表复制到指定的缓冲区。
202`h_imc` 输入上下文的句柄。
203`index` 候选列表的从零开始的索引。
204*/
205pub fn imm_get_candidate_list(h_imc: HIMC, index: u32) -> Option<(CANDIDATELIST, Vec<String>)> {
206    unsafe {
207        let len = ImmGetCandidateListW(h_imc, index, None, 0);
208        if len < 1 {
209            return None;
210        }
211        let heap = HeapMemory::default();
212        let ptr = heap.alloc((len + 1) as usize);
213        // 初始化内存,防止内存异常
214        ptr.write_bytes(b'0', (len + 1) as usize);
215        let p_list = ptr as *mut CANDIDATELIST;
216        ImmGetCandidateListW(h_imc, index, Some(p_list), len);
217        let list = *p_list;
218        let p1 = p_list as *const u32;
219        let p2 = p_list as *const u8;
220        let mut data = Vec::new();
221        for i in 0..list.dwCount {
222            // 因为list.dwOffset在rust编译器处理后,数组大小固定为1,所以不可以访问数组中的其他元素,这里需通过裸指针实现
223            let offset = *p1.wrapping_add(6 + i as usize); // 6表示dwOffset在CANDIDATELIST结构中的第六个元素
224                                                           // 获取候选字符串
225            data.push(p2.wrapping_add(offset as usize).to_string_utf16());
226        }
227        heap.free(ptr);
228        Some((list, data))
229    }
230}
231
232//noinspection StructuralWrap
233/**
234查询当前转换状态(转换模式、句子模式)。
235仅当 IME 支持这些模式时,才会设置转换和句子模式值。
236`h_imc` 输入上下文的句柄。
237*/
238pub fn imm_get_conversion_status(h_imc: HIMC) -> Option<(IME_CONVERSION_MODE, IME_SENTENCE_MODE)> {
239    unsafe {
240        let (mut conversion, mut sentence) = std::mem::zeroed();
241        if !ImmGetConversionStatus(h_imc, Some(&mut conversion), Some(&mut sentence)).as_bool() {
242            return None;
243        }
244        Some((conversion, sentence))
245    }
246}
247
248/// 鼠标单击
249pub fn click(x: i32, y: i32) {
250    unsafe {
251        SetCursorPos(x, y).expect("SetCursorPos failed");
252        mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
253        mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
254    }
255}
256
257/// 鼠标右键单击
258pub fn right_click(x: i32, y: i32) {
259    unsafe {
260        SetCursorPos(x, y).expect("SetCursorPos failed");
261        mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
262        mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);
263    }
264}
265
266/// 获取鼠标当前坐标
267pub fn get_cur_mouse_point() -> (i32, i32) {
268    unsafe {
269        let mut point = POINT { x: 0, y: 0 };
270        GetCursorPos(&mut point).expect("GetCursorPos failed");
271        (point.x, point.y)
272    }
273}