window-helpers.c 13 KB

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