-
Notifications
You must be signed in to change notification settings - Fork 0
/
window.rs
226 lines (183 loc) · 6.7 KB
/
window.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
use std::{ffi::CString, process, ptr, time::Duration};
use thiserror::Error;
use winapi::{
shared::{
minwindef::{BOOL, DWORD, FALSE, LPARAM, TRUE},
windef::HWND,
},
um::{
processthreadsapi::GetCurrentThreadId,
winuser::{
AttachThreadInput, EnumWindows, FindWindowA, GetForegroundWindow, GetWindow,
GetWindowThreadProcessId, IsHungAppWindow, IsIconic, IsWindowVisible,
SetForegroundWindow, ShowWindow, GW_OWNER, SW_RESTORE,
},
},
};
const SLEEP_DURATION: u64 = 10;
#[derive(Debug, Error)]
pub enum Error {
#[error("Failed to set foreground window")]
SetForegroundWindowFailed,
#[error("Failed to locate a top-level window for process {0:X}")]
FindTopLevelWindowFailed(u32),
#[error("Failed to find the foreground window")]
NoForegroundWindow,
#[error("Cannot set hung window as foreground window")]
TargetWindowHung,
}
#[derive(Debug, Eq, PartialEq)]
struct Window {
handle: HWND,
}
impl Window {
pub unsafe fn from_raw_handle(handle: HWND) -> Self {
Self { handle }
}
pub fn process_id(&self) -> DWORD {
let mut process_id: DWORD = 0;
unsafe { GetWindowThreadProcessId(self.handle, &mut process_id as *mut _) };
process_id
}
pub fn thread_id(&self) -> DWORD {
unsafe { GetWindowThreadProcessId(self.handle, ptr::null_mut()) }
}
pub fn is_visible(&self) -> bool {
(unsafe { IsWindowVisible(self.handle) }) != FALSE
}
fn inner_set_foreground(&self) -> Result<(), Error> {
// Calling SetForegroundWindow on a hung window while AttachThreadInput
// is in effect can cause us to hang too.
if self.is_hung() {
return Err(Error::TargetWindowHung);
}
if self.is_foreground()? {
return Ok(());
}
// Autohotkey source:
// > Probably best not to trust its return value. It's been shown to be
// > unreliable at times. Example: I've confirmed that
// > SetForegroundWindow() sometimes (perhaps about 10% of the time)
// > indicates failure even though it succeeds.
let previous_foreground_window = get_foreground_window()?;
unsafe { SetForegroundWindow(self.handle) };
std::thread::sleep(Duration::from_millis(SLEEP_DURATION));
let new_foreground_window = get_foreground_window()?;
if new_foreground_window == *self
|| new_foreground_window != previous_foreground_window
&& self.handle == unsafe { GetWindow(new_foreground_window.handle, GW_OWNER) }
{
Ok(())
} else {
Err(Error::SetForegroundWindowFailed)
}
}
pub fn try_set_foreground(&self, max_tries: i32) -> Result<(), Error> {
let detach = attach_foreground(self.thread_id())?;
// The AutoHotkey source mentions that this "never seems to take more
// than two tries" and that "the number of tries needed might vary
// depending on how fast the CPU is." I don't know...
for try_count in 1..=max_tries {
match self.inner_set_foreground() {
Ok(()) => {
detach();
return Ok(());
}
Err(err) if try_count == max_tries => {
detach();
return Err(err);
}
Err(_) => continue,
}
}
Ok(())
}
pub fn is_foreground(&self) -> Result<bool, Error> {
let foreground = get_foreground_window()?;
Ok(foreground == *self)
}
pub fn is_minimized(&self) -> bool {
(unsafe { IsIconic(self.handle) }) != FALSE
}
pub fn is_hung(&self) -> bool {
(unsafe { IsHungAppWindow(self.handle) }) != FALSE
}
pub fn restore_if_minimized(&self) {
if self.is_minimized() {
unsafe { ShowWindow(self.handle, SW_RESTORE) };
}
}
}
fn enum_windows<F>(mut func: F)
where
F: FnMut(Window) -> bool,
{
unsafe extern "system" fn callback<F>(hwnd: HWND, func_ptr: LPARAM) -> BOOL
where
F: FnMut(Window) -> bool,
{
let func: &mut &mut F = &mut *(func_ptr as *mut _);
func(Window::from_raw_handle(hwnd)) as BOOL
}
let func_ptr = &mut &mut func as *mut _;
let lparam = func_ptr as LPARAM;
unsafe { EnumWindows(Some(callback::<F>), lparam) };
}
fn attach_foreground(target_thread: u32) -> Result<impl FnOnce(), Error> {
let foreground_window = get_foreground_window()?;
let foreground_thread = foreground_window.thread_id();
let current_thread = unsafe { GetCurrentThreadId() };
let did_attach_current_to_foreground = !foreground_window.is_hung()
&& unsafe { AttachThreadInput(current_thread, foreground_thread, TRUE) } != FALSE;
let did_attach_foreground_to_target =
unsafe { AttachThreadInput(foreground_thread, target_thread, TRUE) } != FALSE;
Ok(move || {
if did_attach_current_to_foreground {
unsafe { AttachThreadInput(current_thread, foreground_thread, FALSE) };
};
if did_attach_foreground_to_target {
unsafe { AttachThreadInput(foreground_thread, target_thread, FALSE) };
};
})
}
fn get_foreground_window() -> Result<Window, Error> {
let mut foreground_hwnd = unsafe { GetForegroundWindow() };
// The taskbar might be focused if the foreground window is null?
if foreground_hwnd.is_null() {
let window_name = CString::new("Shell_TrayWnd").unwrap();
let maybe_taskbar_window = unsafe { FindWindowA(window_name.as_ptr(), ptr::null_mut()) };
if maybe_taskbar_window.is_null() {
return Err(Error::NoForegroundWindow);
} else {
foreground_hwnd = maybe_taskbar_window
}
}
Ok(unsafe { Window::from_raw_handle(foreground_hwnd) })
}
fn get_top_level_window(process: &process::Child) -> Result<Window, Error> {
let mut result_hwnd = ptr::null_mut();
enum_windows(|window| {
if window.process_id() == process.id() && window.is_visible() {
result_hwnd = window.handle;
false
} else {
true
}
});
if !result_hwnd.is_null() {
Ok(unsafe { Window::from_raw_handle(result_hwnd) })
} else {
Err(Error::FindTopLevelWindowFailed(process.id()))
}
}
pub fn activate_top_level_window(process: &process::Child) -> Result<(), Error> {
let window = get_top_level_window(process)?;
// The target window is already in the foreground, so nothing more needs
// to be done.
if window.is_foreground()? {
return Ok(());
}
window.restore_if_minimized();
window.try_set_foreground(5)?;
Ok(())
}