window-utils.m 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  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. NSNumber *window_id = (NSNumber *)dict[WINDOW_NUMBER];
  79. cw->window_id = window_id.intValue;
  80. NSNumber *owner_pid = (NSNumber *)dict[OWNER_PID];
  81. cw->owner_pid = owner_pid.intValue;
  82. obs_data_set_int(settings, "window", cw->window_id);
  83. obs_data_set_int(settings, "owner_pid", cw->owner_pid);
  84. return;
  85. }
  86. }
  87. invalid_name:
  88. pthread_mutex_unlock(&cw->name_lock);
  89. return;
  90. }
  91. void destroy_window(cocoa_window_t cw)
  92. {
  93. pthread_mutex_destroy(&cw->name_lock);
  94. [cw->owner_name release];
  95. [cw->window_name release];
  96. }
  97. void update_window(cocoa_window_t cw, obs_data_t *settings)
  98. {
  99. pthread_mutex_lock(&cw->name_lock);
  100. [cw->owner_name release];
  101. [cw->window_name release];
  102. cw->owner_name = @(obs_data_get_string(settings, "owner_name"));
  103. cw->window_name = @(obs_data_get_string(settings, "window_name"));
  104. [cw->owner_name retain];
  105. [cw->window_name retain];
  106. pthread_mutex_unlock(&cw->name_lock);
  107. cw->owner_pid = obs_data_get_int(settings, "owner_pid");
  108. cw->window_id = obs_data_get_int(settings, "window");
  109. }
  110. static inline const char *make_name(NSString *owner, NSString *name)
  111. {
  112. if (!owner.length)
  113. return "";
  114. NSString *str = [NSString stringWithFormat:@"[%@] %@", owner, name];
  115. return str.UTF8String;
  116. }
  117. static inline NSDictionary *find_window_dict(NSArray *arr, int window_id)
  118. {
  119. for (NSDictionary *dict in arr) {
  120. NSNumber *wid = (NSNumber *)dict[WINDOW_NUMBER];
  121. if (wid.intValue == window_id)
  122. return dict;
  123. }
  124. return nil;
  125. }
  126. static inline bool window_changed_internal(obs_property_t *p,
  127. obs_data_t *settings)
  128. {
  129. int window_id = obs_data_get_int(settings, "window");
  130. NSString *window_owner = @(obs_data_get_string(settings, "owner_name"));
  131. NSString *window_name = @(obs_data_get_string(settings, "window_name"));
  132. NSDictionary *win_info = @{
  133. OWNER_NAME: window_owner,
  134. WINDOW_NAME: window_name,
  135. };
  136. NSArray *arr = enumerate_windows();
  137. bool show_empty_names = obs_data_get_bool(settings, "show_empty_names");
  138. NSDictionary *cur = find_window_dict(arr, window_id);
  139. bool window_found = cur != nil;
  140. bool window_added = window_found;
  141. obs_property_list_clear(p);
  142. for (NSDictionary *dict in arr) {
  143. NSString *owner = (NSString *)dict[OWNER_NAME];
  144. NSString *name = (NSString *)dict[WINDOW_NAME];
  145. NSNumber *wid = (NSNumber *)dict[WINDOW_NUMBER];
  146. if (!window_added &&
  147. win_info_cmp(win_info, dict) == NSOrderedAscending) {
  148. window_added = true;
  149. size_t idx = obs_property_list_add_int(
  150. p, make_name(window_owner, window_name),
  151. window_id);
  152. obs_property_list_item_disable(p, idx, true);
  153. }
  154. if (!show_empty_names && !name.length &&
  155. window_id != wid.intValue)
  156. continue;
  157. obs_property_list_add_int(p, make_name(owner, name),
  158. wid.intValue);
  159. }
  160. if (!window_added) {
  161. size_t idx = obs_property_list_add_int(
  162. p, make_name(window_owner, window_name), window_id);
  163. obs_property_list_item_disable(p, idx, true);
  164. }
  165. if (!window_found)
  166. return true;
  167. NSString *owner = (NSString *)cur[OWNER_NAME];
  168. NSString *window = (NSString *)cur[WINDOW_NAME];
  169. obs_data_set_string(settings, "owner_name", owner.UTF8String);
  170. obs_data_set_string(settings, "window_name", window.UTF8String);
  171. return true;
  172. }
  173. static bool window_changed(obs_properties_t *props, obs_property_t *p,
  174. obs_data_t *settings)
  175. {
  176. UNUSED_PARAMETER(props);
  177. @autoreleasepool {
  178. return window_changed_internal(p, settings);
  179. }
  180. }
  181. static bool toggle_empty_names(obs_properties_t *props, obs_property_t *p,
  182. obs_data_t *settings)
  183. {
  184. UNUSED_PARAMETER(p);
  185. return window_changed(props, obs_properties_get(props, "window"),
  186. settings);
  187. }
  188. void window_defaults(obs_data_t *settings)
  189. {
  190. obs_data_set_default_int(settings, "window", kCGNullWindowID);
  191. obs_data_set_default_bool(settings, "show_empty_names", false);
  192. }
  193. void add_window_properties(obs_properties_t *props)
  194. {
  195. obs_property_t *window_list = obs_properties_add_list(
  196. props, "window", obs_module_text("WindowUtils.Window"),
  197. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  198. obs_property_set_modified_callback(window_list, window_changed);
  199. obs_property_t *empty = obs_properties_add_bool(
  200. props, "show_empty_names",
  201. obs_module_text("WindowUtils.ShowEmptyNames"));
  202. obs_property_set_modified_callback(empty, toggle_empty_names);
  203. }
  204. void show_window_properties(obs_properties_t *props, bool show)
  205. {
  206. obs_property_set_visible(obs_properties_get(props, "window"), show);
  207. obs_property_set_visible(obs_properties_get(props, "show_empty_names"),
  208. show);
  209. }