window-utils.m 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #include "window-utils.h"
  2. #include <util/platform.h>
  3. #define WINDOW_NAME ((NSString *)kCGWindowName)
  4. #define WINDOW_NUMBER ((NSString *)kCGWindowNumber)
  5. #define OWNER_NAME ((NSString *)kCGWindowOwnerName)
  6. #define OWNER_PID ((NSNumber *)kCGWindowOwnerPID)
  7. static NSComparator win_info_cmp = ^(NSDictionary *o1, NSDictionary *o2) {
  8. NSComparisonResult res = [o1[OWNER_NAME] compare:o2[OWNER_NAME]];
  9. if (res != NSOrderedSame)
  10. return res;
  11. res = [o1[OWNER_PID] compare:o2[OWNER_PID]];
  12. if (res != NSOrderedSame)
  13. return res;
  14. res = [o1[WINDOW_NAME] compare:o2[WINDOW_NAME]];
  15. if (res != NSOrderedSame)
  16. return res;
  17. return [o1[WINDOW_NUMBER] compare:o2[WINDOW_NUMBER]];
  18. };
  19. NSArray *enumerate_windows(void)
  20. {
  21. NSArray *arr = (NSArray *)CGWindowListCopyWindowInfo(
  22. kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
  23. [arr autorelease];
  24. return [arr sortedArrayUsingComparator:win_info_cmp];
  25. }
  26. #define WAIT_TIME_MS 500
  27. #define WAIT_TIME_US WAIT_TIME_MS * 1000
  28. #define WAIT_TIME_NS WAIT_TIME_US * 1000
  29. bool find_window(cocoa_window_t cw, obs_data_t *settings, bool force)
  30. {
  31. if (!force && cw->next_search_time > os_gettime_ns())
  32. return false;
  33. cw->next_search_time = os_gettime_ns() + WAIT_TIME_NS;
  34. pthread_mutex_lock(&cw->name_lock);
  35. if (!cw->window_name.length && !cw->owner_name.length)
  36. goto invalid_name;
  37. for (NSDictionary *dict in enumerate_windows()) {
  38. if (![cw->owner_name isEqualToString:dict[OWNER_NAME]])
  39. continue;
  40. if (![cw->window_name isEqualToString:dict[WINDOW_NAME]])
  41. continue;
  42. pthread_mutex_unlock(&cw->name_lock);
  43. NSNumber *window_id = (NSNumber *)dict[WINDOW_NUMBER];
  44. cw->window_id = window_id.intValue;
  45. NSNumber *owner_pid = (NSNumber *)dict[OWNER_PID];
  46. cw->owner_pid = owner_pid.intValue;
  47. obs_data_set_int(settings, "window", cw->window_id);
  48. obs_data_set_int(settings, "owner_pid", cw->owner_pid);
  49. return true;
  50. }
  51. invalid_name:
  52. pthread_mutex_unlock(&cw->name_lock);
  53. return false;
  54. }
  55. void init_window(cocoa_window_t cw, obs_data_t *settings)
  56. {
  57. pthread_mutex_init(&cw->name_lock, NULL);
  58. cw->owner_name = @(obs_data_get_string(settings, "owner_name"));
  59. cw->window_name = @(obs_data_get_string(settings, "window_name"));
  60. [cw->owner_name retain];
  61. [cw->window_name retain];
  62. // Find initial window.
  63. pthread_mutex_lock(&cw->name_lock);
  64. if (!cw->window_name.length && !cw->owner_name.length)
  65. goto invalid_name;
  66. NSNumber *owner_pid = @(obs_data_get_int(settings, "owner_pid"));
  67. NSNumber *window_id = @(obs_data_get_int(settings, "window"));
  68. for (NSDictionary *dict in enumerate_windows()) {
  69. bool owner_names_match =
  70. [cw->owner_name isEqualToString:dict[OWNER_NAME]];
  71. bool ids_match =
  72. [owner_pid isEqualToNumber:dict[OWNER_PID]] &&
  73. [window_id isEqualToNumber:dict[WINDOW_NUMBER]];
  74. bool window_names_match =
  75. [cw->window_name isEqualToString:dict[WINDOW_NAME]];
  76. if (owner_names_match && (ids_match || window_names_match)) {
  77. pthread_mutex_unlock(&cw->name_lock);
  78. cw->window_id = [dict[WINDOW_NUMBER] intValue];
  79. cw->owner_pid = [dict[OWNER_PID] intValue];
  80. obs_data_set_int(settings, "window", cw->window_id);
  81. obs_data_set_int(settings, "owner_pid", cw->owner_pid);
  82. return;
  83. }
  84. }
  85. invalid_name:
  86. pthread_mutex_unlock(&cw->name_lock);
  87. return;
  88. }
  89. void destroy_window(cocoa_window_t cw)
  90. {
  91. pthread_mutex_destroy(&cw->name_lock);
  92. [cw->owner_name release];
  93. [cw->window_name release];
  94. }
  95. void update_window(cocoa_window_t cw, obs_data_t *settings)
  96. {
  97. pthread_mutex_lock(&cw->name_lock);
  98. [cw->owner_name release];
  99. [cw->window_name release];
  100. cw->owner_name = @(obs_data_get_string(settings, "owner_name"));
  101. cw->window_name = @(obs_data_get_string(settings, "window_name"));
  102. [cw->owner_name retain];
  103. [cw->window_name retain];
  104. pthread_mutex_unlock(&cw->name_lock);
  105. cw->owner_pid = (int)obs_data_get_int(settings, "owner_pid");
  106. cw->window_id = (unsigned int)obs_data_get_int(settings, "window");
  107. }
  108. static inline const char *make_name(NSString *owner, NSString *name)
  109. {
  110. if (!owner.length)
  111. return "";
  112. NSString *str = [NSString stringWithFormat:@"[%@] %@", owner, name];
  113. return str.UTF8String;
  114. }
  115. static inline NSDictionary *find_window_dict(NSArray *arr, int window_id)
  116. {
  117. for (NSDictionary *dict in arr) {
  118. NSNumber *wid = (NSNumber *)dict[WINDOW_NUMBER];
  119. if (wid.intValue == window_id)
  120. return dict;
  121. }
  122. return nil;
  123. }
  124. static inline bool window_changed_internal(obs_property_t *p,
  125. obs_data_t *settings)
  126. {
  127. int window_id = (int)obs_data_get_int(settings, "window");
  128. NSString *window_owner = @(obs_data_get_string(settings, "owner_name"));
  129. NSString *window_name = @(obs_data_get_string(settings, "window_name"));
  130. NSDictionary *win_info = @{
  131. OWNER_NAME: window_owner,
  132. WINDOW_NAME: window_name,
  133. };
  134. NSArray *arr = enumerate_windows();
  135. bool show_empty_names = obs_data_get_bool(settings, "show_empty_names");
  136. NSDictionary *cur = find_window_dict(arr, window_id);
  137. bool window_found = cur != nil;
  138. bool window_added = window_found;
  139. obs_property_list_clear(p);
  140. for (NSDictionary *dict in arr) {
  141. NSString *owner = (NSString *)dict[OWNER_NAME];
  142. NSString *name = (NSString *)dict[WINDOW_NAME];
  143. NSNumber *wid = (NSNumber *)dict[WINDOW_NUMBER];
  144. if (!window_added &&
  145. win_info_cmp(win_info, dict) == NSOrderedAscending) {
  146. window_added = true;
  147. size_t idx = obs_property_list_add_int(
  148. p, make_name(window_owner, window_name),
  149. window_id);
  150. obs_property_list_item_disable(p, idx, true);
  151. }
  152. if (!show_empty_names && !name.length &&
  153. window_id != wid.intValue)
  154. continue;
  155. obs_property_list_add_int(p, make_name(owner, name),
  156. wid.intValue);
  157. }
  158. if (!window_added) {
  159. size_t idx = obs_property_list_add_int(
  160. p, make_name(window_owner, window_name), window_id);
  161. obs_property_list_item_disable(p, idx, true);
  162. }
  163. if (!window_found)
  164. return true;
  165. NSString *owner = (NSString *)cur[OWNER_NAME];
  166. NSString *window = (NSString *)cur[WINDOW_NAME];
  167. obs_data_set_string(settings, "owner_name", owner.UTF8String);
  168. obs_data_set_string(settings, "window_name", window.UTF8String);
  169. return true;
  170. }
  171. static bool window_changed(obs_properties_t *props, obs_property_t *p,
  172. obs_data_t *settings)
  173. {
  174. UNUSED_PARAMETER(props);
  175. @autoreleasepool {
  176. return window_changed_internal(p, settings);
  177. }
  178. }
  179. static bool toggle_empty_names(obs_properties_t *props, obs_property_t *p,
  180. obs_data_t *settings)
  181. {
  182. UNUSED_PARAMETER(p);
  183. return window_changed(props, obs_properties_get(props, "window"),
  184. settings);
  185. }
  186. void window_defaults(obs_data_t *settings)
  187. {
  188. obs_data_set_default_int(settings, "window", kCGNullWindowID);
  189. obs_data_set_default_bool(settings, "show_empty_names", false);
  190. }
  191. void add_window_properties(obs_properties_t *props)
  192. {
  193. obs_property_t *window_list = obs_properties_add_list(
  194. props, "window", obs_module_text("WindowUtils.Window"),
  195. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  196. obs_property_set_modified_callback(window_list, window_changed);
  197. obs_property_t *empty = obs_properties_add_bool(
  198. props, "show_empty_names",
  199. obs_module_text("WindowUtils.ShowEmptyNames"));
  200. obs_property_set_modified_callback(empty, toggle_empty_names);
  201. }
  202. void show_window_properties(obs_properties_t *props, bool show)
  203. {
  204. obs_property_set_visible(obs_properties_get(props, "window"), show);
  205. obs_property_set_visible(obs_properties_get(props, "show_empty_names"),
  206. show);
  207. }