Я работаю над программой, которая использует низкоуровневые перехватчики клавиатуры (LowLevelKeyboardProc) для обнаружения комбинаций клавиш и переключения фокуса между окнами определенных процессов. Цель состоит в том, чтобы программа переместила фокус на следующее или предыдущее окно набора программ при обнаружении Shift + E или Shift + Q.
Однако, когда текущее окно не является окном моей программы, функция SetForegroundWindow не работает, поэтому мне приходится использовать AttachThreadInput, чтобы вывести окно наверх. Проблема в том, что окно не получает фокуса, поэтому, когда я отправляю команды с клавиатуры, они все равно переходят к предыдущему окну.
Как включить фокус для окна, которое находится сверху?
Вот фрагмент функции, которую я использую:
let target_window =
self.get_window_by_name(process_id, "Window Name".to_string());
if let Some(target_window) = target_window {
unsafe {
if current_process_id.is_none() {
ShowWindow(target_window.0, SW_NORMAL);
SetForegroundWindow(target_window.0);
SetActiveWindow(target_window.0);
} else {
let foreground_window = GetForegroundWindow();
let foreground_thread_id =
GetWindowThreadProcessId(foreground_window, std::ptr::null_mut());
let target_thread_id =
GetWindowThreadProcessId(target_window.0, std::ptr::null_mut());
AttachThreadInput(foreground_thread_id, target_thread_id, TRUE);
BringWindowToTop(target_window.0);
ShowWindow(target_window.0, SW_NORMAL);
// SetForegroundWindow(target_window.0);
// SetFocus(target_window.0);
SetActiveWindow(target_window.0);
AttachThreadInput(foreground_thread_id, target_thread_id, FALSE);
}
}
}
Наблюдение:
Когда current_process_id имеет значение None, это означает, что текущий процесс — моя программа, поэтому я выполняю только SetForegroundWindow, и все работает. Когда это «Некоторые», это означает, что я нахожусь в одном из окон, между которыми хочу перейти, и оно работает неправильно.
Позже посмотрю повнимательнее ссылку, которую вы предоставили. Что касается низкоуровневого использования, я обычно использовал его в своих приложениях с WinAPI для отслеживания ввода с клавиатуры. Я не знал о RegisterHotKey, и в данном случае он кажется полезным, лучше, чем низкоуровневый. Я проверю это позже.
Почему это сложно и дорого? Я думал, что это самый легкий вариант.
«Почему это сложно и дорого?» — Перехватчик низкого уровня — один из немногих способов, влияющих на глобальную отзывчивость системы. Вся цепочка установленных хуков обязательно должна вызываться последовательно для поддержки отклонения ввода. Каждый вызов функции-перехватчика влечет за собой дорогостоящее переключение контекста (перехват низкого уровня вызывается в потоке, который его установил). Это быстро складывается для каждого ввода с клавиатуры. Гораздо менее навязчивый способ контролировать ввод с клавиатуры — это Необработанный ввод.
Как было предложено @IInspectable в комментариях, переключение с LowLevelKeyboardProc на RegisterHotKey позволило вызвать SetForegroundWindow, чтобы вывести окна на передний план и сфокусировать их для приема ввода с клавиатуры.
Благодаря этому изменению также отпала необходимость использовать AttachThreadInput.
Вот краткий фрагмент обновленного кода:
hotkey.rs
//call in main trehad
pub struct HotkeyEvent {}
impl HotkeyEvent {
pub fn register() -> Result<(), ()> {
unsafe {
if RegisterHotKey(null_mut(), HOTKEY_ID_Q, MOD_SHIFT as u32, 0x51 as u32) == 0 {
eprintln!("Failed to register hotkey SHIFT + Q");
return Err(());
}
if RegisterHotKey(null_mut(), HOTKEY_ID_E, MOD_SHIFT as u32, 0x45 as u32) == 0 {
eprintln!("Failed to register hotkey SHIFT + E");
return Err(());
}
Ok(())
}
}
pub fn start_loop_message() {
unsafe {
let mut msg: MSG = std::mem::zeroed();
while GetMessageW(&mut msg, null_mut(), 0, 0) > 0 {
if msg.message == WM_HOTKEY {
// Here, I perform the logic to obtain the ID of the next window
// and call the set_window_foreground function.
process_message_hotkey(msg.wParam as u32)
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
}
}
pub fn set_window_foreground(
&self,
process_id: u32,
current_process_id: Option<u32>,
) -> Option<bool> {
let target_window =
self.get_window_by_name(process_id, "Window Name".to_string());
if let Some(target_window) = target_window {
unsafe {
ShowWindow(target_window.0, SW_NORMAL);
SetForegroundWindow(target_window.0);
SetActiveWindow(target_window.0);
}
}
None
}
Вызов SetActiveWindow не требуется (и, вероятно, просто завершается с ошибкой). См. В конце концов, уже ничего особенного не понять, что такое «активное окно».
Это не проблема фокуса. Вам не удалось установить окно переднего плана. Разрешение на активацию переднего плана похоже на любовь: его нельзя украсть, его нужно дать вам , а хак
AttachThreadInput
перестал работать много лет назад. Теперь реальный вопрос: почему бы вам не вызвать RegisterHotKey вместо дорогостоящего и сложного низкоуровневого хука? Я не удивлюсь, если это также предоставит вам разрешение на активацию на переднем плане.