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