1 #include <obs.h>
2 #include <util/dstr.h>
3
4 #include <dwmapi.h>
5 #include <psapi.h>
6 #include <windows.h>
7 #include "window-helpers.h"
8 #include "obfuscate.h"
9
encode_dstr(struct dstr * str)10 static inline void encode_dstr(struct dstr *str)
11 {
12 dstr_replace(str, "#", "#22");
13 dstr_replace(str, ":", "#3A");
14 }
15
decode_str(const char * src)16 static inline char *decode_str(const char *src)
17 {
18 struct dstr str = {0};
19 dstr_copy(&str, src);
20 dstr_replace(&str, "#3A", ":");
21 dstr_replace(&str, "#22", "#");
22 return str.array;
23 }
24
build_window_strings(const char * str,char ** class,char ** title,char ** exe)25 extern void build_window_strings(const char *str, char **class, char **title,
26 char **exe)
27 {
28 char **strlist;
29
30 *class = NULL;
31 *title = NULL;
32 *exe = NULL;
33
34 if (!str) {
35 return;
36 }
37
38 strlist = strlist_split(str, ':', true);
39
40 if (strlist && strlist[0] && strlist[1] && strlist[2]) {
41 *title = decode_str(strlist[0]);
42 *class = decode_str(strlist[1]);
43 *exe = decode_str(strlist[2]);
44 }
45
46 strlist_free(strlist);
47 }
48
kernel32(void)49 static HMODULE kernel32(void)
50 {
51 static HMODULE kernel32_handle = NULL;
52 if (!kernel32_handle)
53 kernel32_handle = GetModuleHandleA("kernel32");
54 return kernel32_handle;
55 }
56
open_process(DWORD desired_access,bool inherit_handle,DWORD process_id)57 static inline HANDLE open_process(DWORD desired_access, bool inherit_handle,
58 DWORD process_id)
59 {
60 typedef HANDLE(WINAPI * PFN_OpenProcess)(DWORD, BOOL, DWORD);
61 static PFN_OpenProcess open_process_proc = NULL;
62 if (!open_process_proc)
63 open_process_proc = (PFN_OpenProcess)get_obfuscated_func(
64 kernel32(), "B}caZyah`~q", 0x2D5BEBAF6DDULL);
65
66 return open_process_proc(desired_access, inherit_handle, process_id);
67 }
68
get_window_exe(struct dstr * name,HWND window)69 bool get_window_exe(struct dstr *name, HWND window)
70 {
71 wchar_t wname[MAX_PATH];
72 struct dstr temp = {0};
73 bool success = false;
74 HANDLE process = NULL;
75 char *slash;
76 DWORD id;
77
78 GetWindowThreadProcessId(window, &id);
79 if (id == GetCurrentProcessId())
80 return false;
81
82 process = open_process(PROCESS_QUERY_LIMITED_INFORMATION, false, id);
83 if (!process)
84 goto fail;
85
86 if (!GetProcessImageFileNameW(process, wname, MAX_PATH))
87 goto fail;
88
89 dstr_from_wcs(&temp, wname);
90 slash = strrchr(temp.array, '\\');
91 if (!slash)
92 goto fail;
93
94 dstr_copy(name, slash + 1);
95 success = true;
96
97 fail:
98 if (!success)
99 dstr_copy(name, "unknown");
100
101 dstr_free(&temp);
102 CloseHandle(process);
103 return true;
104 }
105
get_window_title(struct dstr * name,HWND hwnd)106 void get_window_title(struct dstr *name, HWND hwnd)
107 {
108 wchar_t *temp;
109 int len;
110
111 len = GetWindowTextLengthW(hwnd);
112 if (!len)
113 return;
114
115 temp = malloc(sizeof(wchar_t) * (len + 1));
116 if (GetWindowTextW(hwnd, temp, len + 1))
117 dstr_from_wcs(name, temp);
118 free(temp);
119 }
120
get_window_class(struct dstr * class,HWND hwnd)121 void get_window_class(struct dstr *class, HWND hwnd)
122 {
123 wchar_t temp[256];
124
125 temp[0] = 0;
126 if (GetClassNameW(hwnd, temp, sizeof(temp) / sizeof(wchar_t)))
127 dstr_from_wcs(class, temp);
128 }
129
130 /* not capturable or internal windows, exact executable names */
131 static const char *internal_microsoft_exes_exact[] = {
132 "startmenuexperiencehost.exe",
133 "applicationframehost.exe",
134 "peopleexperiencehost.exe",
135 "shellexperiencehost.exe",
136 "microsoft.notes.exe",
137 "systemsettings.exe",
138 "textinputhost.exe",
139 "searchapp.exe",
140 "video.ui.exe",
141 "searchui.exe",
142 "lockapp.exe",
143 "cortana.exe",
144 "gamebar.exe",
145 "tabtip.exe",
146 "time.exe",
147 NULL,
148 };
149
150 /* partial matches start from the beginning of the executable name */
151 static const char *internal_microsoft_exes_partial[] = {
152 "windowsinternal",
153 NULL,
154 };
155
is_microsoft_internal_window_exe(const char * exe)156 static bool is_microsoft_internal_window_exe(const char *exe)
157 {
158 if (!exe)
159 return false;
160
161 for (const char **vals = internal_microsoft_exes_exact; *vals; vals++) {
162 if (astrcmpi(exe, *vals) == 0)
163 return true;
164 }
165
166 for (const char **vals = internal_microsoft_exes_partial; *vals;
167 vals++) {
168 if (astrcmpi_n(exe, *vals, strlen(*vals)) == 0)
169 return true;
170 }
171
172 return false;
173 }
174
add_window(obs_property_t * p,HWND hwnd,add_window_cb callback)175 static void add_window(obs_property_t *p, HWND hwnd, add_window_cb callback)
176 {
177 struct dstr class = {0};
178 struct dstr title = {0};
179 struct dstr exe = {0};
180 struct dstr encoded = {0};
181 struct dstr desc = {0};
182
183 if (!get_window_exe(&exe, hwnd))
184 return;
185 if (is_microsoft_internal_window_exe(exe.array)) {
186 dstr_free(&exe);
187 return;
188 }
189
190 get_window_title(&title, hwnd);
191 if (dstr_cmp(&exe, "explorer.exe") == 0 && dstr_is_empty(&title)) {
192 dstr_free(&exe);
193 dstr_free(&title);
194 return;
195 }
196
197 get_window_class(&class, hwnd);
198
199 if (callback && !callback(title.array, class.array, exe.array)) {
200 dstr_free(&title);
201 dstr_free(&class);
202 dstr_free(&exe);
203 return;
204 }
205
206 dstr_printf(&desc, "[%s]: %s", exe.array, title.array);
207
208 encode_dstr(&title);
209 encode_dstr(&class);
210 encode_dstr(&exe);
211
212 dstr_cat_dstr(&encoded, &title);
213 dstr_cat(&encoded, ":");
214 dstr_cat_dstr(&encoded, &class);
215 dstr_cat(&encoded, ":");
216 dstr_cat_dstr(&encoded, &exe);
217
218 obs_property_list_add_string(p, desc.array, encoded.array);
219
220 dstr_free(&encoded);
221 dstr_free(&desc);
222 dstr_free(&class);
223 dstr_free(&title);
224 dstr_free(&exe);
225 }
226
check_window_valid(HWND window,enum window_search_mode mode)227 static bool check_window_valid(HWND window, enum window_search_mode mode)
228 {
229 DWORD styles, ex_styles;
230 RECT rect;
231
232 if (!IsWindowVisible(window) ||
233 (mode == EXCLUDE_MINIMIZED && IsIconic(window)))
234 return false;
235
236 GetClientRect(window, &rect);
237 styles = (DWORD)GetWindowLongPtr(window, GWL_STYLE);
238 ex_styles = (DWORD)GetWindowLongPtr(window, GWL_EXSTYLE);
239
240 if (ex_styles & WS_EX_TOOLWINDOW)
241 return false;
242 if (styles & WS_CHILD)
243 return false;
244 if (mode == EXCLUDE_MINIMIZED && (rect.bottom == 0 || rect.right == 0))
245 return false;
246
247 return true;
248 }
249
is_uwp_window(HWND hwnd)250 bool is_uwp_window(HWND hwnd)
251 {
252 wchar_t name[256];
253
254 name[0] = 0;
255 if (!GetClassNameW(hwnd, name, sizeof(name) / sizeof(wchar_t)))
256 return false;
257
258 return wcscmp(name, L"ApplicationFrameWindow") == 0;
259 }
260
get_uwp_actual_window(HWND parent)261 HWND get_uwp_actual_window(HWND parent)
262 {
263 DWORD parent_id = 0;
264 HWND child;
265
266 GetWindowThreadProcessId(parent, &parent_id);
267 child = FindWindowEx(parent, NULL, NULL, NULL);
268
269 while (child) {
270 DWORD child_id = 0;
271 GetWindowThreadProcessId(child, &child_id);
272
273 if (child_id != parent_id)
274 return child;
275
276 child = FindWindowEx(parent, child, NULL, NULL);
277 }
278
279 return NULL;
280 }
281
next_window(HWND window,enum window_search_mode mode,HWND * parent,bool use_findwindowex)282 static HWND next_window(HWND window, enum window_search_mode mode, HWND *parent,
283 bool use_findwindowex)
284 {
285 if (*parent) {
286 window = *parent;
287 *parent = NULL;
288 }
289
290 while (true) {
291 if (use_findwindowex)
292 window = FindWindowEx(GetDesktopWindow(), window, NULL,
293 NULL);
294 else
295 window = GetNextWindow(window, GW_HWNDNEXT);
296
297 if (!window || check_window_valid(window, mode))
298 break;
299 }
300
301 if (is_uwp_window(window)) {
302 HWND child = get_uwp_actual_window(window);
303 if (child) {
304 *parent = window;
305 return child;
306 }
307 }
308
309 return window;
310 }
311
first_window(enum window_search_mode mode,HWND * parent,bool * use_findwindowex)312 static HWND first_window(enum window_search_mode mode, HWND *parent,
313 bool *use_findwindowex)
314 {
315 HWND window = FindWindowEx(GetDesktopWindow(), NULL, NULL, NULL);
316
317 if (!window) {
318 *use_findwindowex = false;
319 window = GetWindow(GetDesktopWindow(), GW_CHILD);
320 } else {
321 *use_findwindowex = true;
322 }
323
324 *parent = NULL;
325
326 if (!check_window_valid(window, mode)) {
327 window = next_window(window, mode, parent, *use_findwindowex);
328
329 if (!window && *use_findwindowex) {
330 *use_findwindowex = false;
331
332 window = GetWindow(GetDesktopWindow(), GW_CHILD);
333 if (!check_window_valid(window, mode))
334 window = next_window(window, mode, parent,
335 *use_findwindowex);
336 }
337 }
338
339 if (is_uwp_window(window)) {
340 HWND child = get_uwp_actual_window(window);
341 if (child) {
342 *parent = window;
343 return child;
344 }
345 }
346
347 return window;
348 }
349
fill_window_list(obs_property_t * p,enum window_search_mode mode,add_window_cb callback)350 void fill_window_list(obs_property_t *p, enum window_search_mode mode,
351 add_window_cb callback)
352 {
353 HWND parent;
354 bool use_findwindowex = false;
355
356 HWND window = first_window(mode, &parent, &use_findwindowex);
357
358 while (window) {
359 add_window(p, window, callback);
360 window = next_window(window, mode, &parent, use_findwindowex);
361 }
362 }
363
window_rating(HWND window,enum window_priority priority,const char * class,const char * title,const char * exe,bool generic_class)364 static int window_rating(HWND window, enum window_priority priority,
365 const char *class, const char *title, const char *exe,
366 bool generic_class)
367 {
368 struct dstr cur_class = {0};
369 struct dstr cur_title = {0};
370 struct dstr cur_exe = {0};
371 int val = 0x7FFFFFFF;
372
373 if (!get_window_exe(&cur_exe, window))
374 return 0x7FFFFFFF;
375 get_window_title(&cur_title, window);
376 get_window_class(&cur_class, window);
377
378 bool class_matches = dstr_cmpi(&cur_class, class) == 0;
379 bool exe_matches = dstr_cmpi(&cur_exe, exe) == 0;
380 int title_val = abs(dstr_cmpi(&cur_title, title));
381
382 /* always match by name if class is generic */
383 if (generic_class) {
384 if (priority == WINDOW_PRIORITY_EXE && !exe_matches)
385 val = 0x7FFFFFFF;
386 else
387 val = title_val == 0 ? 0 : 0x7FFFFFFF;
388
389 } else if (priority == WINDOW_PRIORITY_CLASS) {
390 val = class_matches ? title_val : 0x7FFFFFFF;
391 if (val != 0x7FFFFFFF && !exe_matches)
392 val += 0x1000;
393
394 } else if (priority == WINDOW_PRIORITY_TITLE) {
395 val = title_val == 0 ? 0 : 0x7FFFFFFF;
396
397 } else if (priority == WINDOW_PRIORITY_EXE) {
398 val = exe_matches ? title_val : 0x7FFFFFFF;
399 }
400
401 dstr_free(&cur_class);
402 dstr_free(&cur_title);
403 dstr_free(&cur_exe);
404
405 return val;
406 }
407
408 static const char *generic_class_substrings[] = {
409 "Chrome",
410 NULL,
411 };
412
413 static const char *generic_classes[] = {
414 "Windows.UI.Core.CoreWindow",
415 NULL,
416 };
417
is_generic_class(const char * current_class)418 static bool is_generic_class(const char *current_class)
419 {
420 const char **class = generic_class_substrings;
421 while (*class) {
422 if (astrstri(current_class, *class) != NULL) {
423 return true;
424 }
425 class ++;
426 }
427
428 class = generic_classes;
429 while (*class) {
430 if (astrcmpi(current_class, *class) == 0) {
431 return true;
432 }
433 class ++;
434 }
435
436 return false;
437 }
438
find_window(enum window_search_mode mode,enum window_priority priority,const char * class,const char * title,const char * exe)439 HWND find_window(enum window_search_mode mode, enum window_priority priority,
440 const char *class, const char *title, const char *exe)
441 {
442 HWND parent;
443 bool use_findwindowex = false;
444
445 HWND window = first_window(mode, &parent, &use_findwindowex);
446 HWND best_window = NULL;
447 int best_rating = 0x7FFFFFFF;
448
449 if (!class)
450 return NULL;
451
452 bool generic_class = is_generic_class(class);
453
454 while (window) {
455 int rating = window_rating(window, priority, class, title, exe,
456 generic_class);
457 if (rating < best_rating) {
458 best_rating = rating;
459 best_window = window;
460 if (rating == 0)
461 break;
462 }
463
464 window = next_window(window, mode, &parent, use_findwindowex);
465 }
466
467 return best_window;
468 }
469
470 struct top_level_enum_data {
471 enum window_search_mode mode;
472 enum window_priority priority;
473 const char *class;
474 const char *title;
475 const char *exe;
476 bool generic_class;
477 HWND best_window;
478 int best_rating;
479 };
480
enum_windows_proc(HWND window,LPARAM lParam)481 BOOL CALLBACK enum_windows_proc(HWND window, LPARAM lParam)
482 {
483 struct top_level_enum_data *data = (struct top_level_enum_data *)lParam;
484
485 if (!check_window_valid(window, data->mode))
486 return TRUE;
487
488 int cloaked;
489 if (SUCCEEDED(DwmGetWindowAttribute(window, DWMWA_CLOAKED, &cloaked,
490 sizeof(cloaked))) &&
491 cloaked)
492 return TRUE;
493
494 const int rating = window_rating(window, data->priority, data->class,
495 data->title, data->exe,
496 data->generic_class);
497 if (rating < data->best_rating) {
498 data->best_rating = rating;
499 data->best_window = window;
500 }
501
502 return rating > 0;
503 }
504
find_window_top_level(enum window_search_mode mode,enum window_priority priority,const char * class,const char * title,const char * exe)505 HWND find_window_top_level(enum window_search_mode mode,
506 enum window_priority priority, const char *class,
507 const char *title, const char *exe)
508 {
509 if (!class)
510 return NULL;
511
512 struct top_level_enum_data data;
513 data.mode = mode;
514 data.priority = priority;
515 data.class = class;
516 data.title = title;
517 data.exe = exe;
518 data.generic_class = is_generic_class(class);
519 data.best_window = NULL;
520 data.best_rating = 0x7FFFFFFF;
521 EnumWindows(enum_windows_proc, (LPARAM)&data);
522 return data.best_window;
523 }
524