window-utils.m 6.0 KB

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