window-utils.m 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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 ((NSString *) 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(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
  22. [arr autorelease];
  23. return [arr sortedArrayUsingComparator:win_info_cmp];
  24. }
  25. #define WAIT_TIME_MS 500
  26. #define WAIT_TIME_US WAIT_TIME_MS * 1000
  27. #define WAIT_TIME_NS WAIT_TIME_US * 1000
  28. bool find_window(cocoa_window_t cw, obs_data_t *settings, bool force)
  29. {
  30. if (!force && cw->next_search_time > os_gettime_ns())
  31. return false;
  32. cw->next_search_time = os_gettime_ns() + WAIT_TIME_NS;
  33. pthread_mutex_lock(&cw->name_lock);
  34. if (!cw->window_name.length && !cw->owner_name.length)
  35. goto invalid_name;
  36. for (NSDictionary *dict in enumerate_windows()) {
  37. if (![cw->owner_name isEqualToString:dict[OWNER_NAME]])
  38. continue;
  39. if (![cw->window_name isEqualToString:dict[WINDOW_NAME]])
  40. continue;
  41. pthread_mutex_unlock(&cw->name_lock);
  42. NSNumber *window_id = (NSNumber *) dict[WINDOW_NUMBER];
  43. cw->window_id = window_id.intValue;
  44. NSNumber *owner_pid = (NSNumber *) dict[OWNER_PID];
  45. cw->owner_pid = owner_pid.intValue;
  46. obs_data_set_int(settings, "window", cw->window_id);
  47. obs_data_set_int(settings, "owner_pid", cw->owner_pid);
  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 initial window.
  62. pthread_mutex_lock(&cw->name_lock);
  63. if (!cw->window_name.length && !cw->owner_name.length)
  64. goto invalid_name;
  65. NSNumber *owner_pid = @(obs_data_get_int(settings, "owner_pid"));
  66. NSNumber *window_id = @(obs_data_get_int(settings, "window"));
  67. for (NSDictionary *dict in enumerate_windows()) {
  68. bool owner_names_match = [cw->owner_name isEqualToString:dict[OWNER_NAME]];
  69. bool ids_match = [owner_pid isEqualToNumber:dict[OWNER_PID]] && [window_id isEqualToNumber:dict[WINDOW_NUMBER]];
  70. bool window_names_match = [cw->window_name isEqualToString:dict[WINDOW_NAME]];
  71. if (owner_names_match && (ids_match || window_names_match)) {
  72. pthread_mutex_unlock(&cw->name_lock);
  73. cw->window_id = [dict[WINDOW_NUMBER] intValue];
  74. cw->owner_pid = [dict[OWNER_PID] intValue];
  75. obs_data_set_int(settings, "window", cw->window_id);
  76. obs_data_set_int(settings, "owner_pid", cw->owner_pid);
  77. return;
  78. }
  79. }
  80. invalid_name:
  81. pthread_mutex_unlock(&cw->name_lock);
  82. return;
  83. }
  84. void destroy_window(cocoa_window_t cw)
  85. {
  86. pthread_mutex_destroy(&cw->name_lock);
  87. [cw->owner_name release];
  88. [cw->window_name release];
  89. }
  90. void update_window(cocoa_window_t cw, obs_data_t *settings)
  91. {
  92. pthread_mutex_lock(&cw->name_lock);
  93. [cw->owner_name release];
  94. [cw->window_name release];
  95. cw->owner_name = @(obs_data_get_string(settings, "owner_name"));
  96. cw->window_name = @(obs_data_get_string(settings, "window_name"));
  97. [cw->owner_name retain];
  98. [cw->window_name retain];
  99. pthread_mutex_unlock(&cw->name_lock);
  100. cw->owner_pid = (int) obs_data_get_int(settings, "owner_pid");
  101. cw->window_id = (unsigned int) obs_data_get_int(settings, "window");
  102. }
  103. static inline const char *make_name(NSString *owner, NSString *name)
  104. {
  105. if (!owner.length)
  106. return "";
  107. NSString *str = [NSString stringWithFormat:@"[%@] %@", owner, name];
  108. return str.UTF8String;
  109. }
  110. static inline NSDictionary *find_window_dict(NSArray *arr, int window_id)
  111. {
  112. for (NSDictionary *dict in arr) {
  113. NSNumber *wid = (NSNumber *) dict[WINDOW_NUMBER];
  114. if (wid.intValue == window_id)
  115. return dict;
  116. }
  117. return nil;
  118. }
  119. static inline bool window_changed_internal(obs_property_t *p, obs_data_t *settings)
  120. {
  121. int window_id = (int) obs_data_get_int(settings, "window");
  122. NSString *window_owner = @(obs_data_get_string(settings, "owner_name"));
  123. NSString *window_name = @(obs_data_get_string(settings, "window_name"));
  124. NSDictionary *win_info = @ {
  125. OWNER_NAME: window_owner,
  126. WINDOW_NAME: window_name,
  127. };
  128. NSArray *arr = enumerate_windows();
  129. bool show_empty_names = obs_data_get_bool(settings, "show_empty_names");
  130. NSDictionary *cur = find_window_dict(arr, window_id);
  131. bool window_found = cur != nil;
  132. bool window_added = window_found;
  133. obs_property_list_clear(p);
  134. for (NSDictionary *dict in arr) {
  135. NSString *owner = (NSString *) dict[OWNER_NAME];
  136. NSString *name = (NSString *) dict[WINDOW_NAME];
  137. NSNumber *wid = (NSNumber *) dict[WINDOW_NUMBER];
  138. if (!window_added && win_info_cmp(win_info, dict) == NSOrderedAscending) {
  139. window_added = true;
  140. size_t idx = obs_property_list_add_int(p, make_name(window_owner, window_name), window_id);
  141. obs_property_list_item_disable(p, idx, true);
  142. }
  143. if (!show_empty_names && !name.length && window_id != wid.intValue)
  144. continue;
  145. obs_property_list_add_int(p, make_name(owner, name), wid.intValue);
  146. }
  147. if (!window_added) {
  148. size_t idx = obs_property_list_add_int(p, make_name(window_owner, window_name), window_id);
  149. obs_property_list_item_disable(p, idx, true);
  150. }
  151. if (!window_found)
  152. return true;
  153. NSString *owner = (NSString *) cur[OWNER_NAME];
  154. NSString *window = (NSString *) cur[WINDOW_NAME];
  155. obs_data_set_string(settings, "owner_name", owner.UTF8String);
  156. obs_data_set_string(settings, "window_name", window.UTF8String);
  157. return true;
  158. }
  159. static bool window_changed(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  160. {
  161. UNUSED_PARAMETER(props);
  162. @autoreleasepool {
  163. return window_changed_internal(p, settings);
  164. }
  165. }
  166. static bool toggle_empty_names(obs_properties_t *props, obs_property_t *p, obs_data_t *settings)
  167. {
  168. UNUSED_PARAMETER(p);
  169. return window_changed(props, obs_properties_get(props, "window"), settings);
  170. }
  171. void window_defaults(obs_data_t *settings)
  172. {
  173. obs_data_set_default_int(settings, "window", kCGNullWindowID);
  174. obs_data_set_default_bool(settings, "show_empty_names", false);
  175. }
  176. void add_window_properties(obs_properties_t *props)
  177. {
  178. obs_property_t *window_list = obs_properties_add_list(props, "window", obs_module_text("WindowUtils.Window"),
  179. OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
  180. obs_property_set_modified_callback(window_list, window_changed);
  181. obs_property_t *empty =
  182. obs_properties_add_bool(props, "show_empty_names", obs_module_text("WindowUtils.ShowEmptyNames"));
  183. obs_property_set_modified_callback(empty, toggle_empty_names);
  184. }
  185. void show_window_properties(obs_properties_t *props, bool show)
  186. {
  187. obs_property_set_visible(obs_properties_get(props, "window"), show);
  188. obs_property_set_visible(obs_properties_get(props, "show_empty_names"), show);
  189. }
  190. CGDirectDisplayID get_display_migrate_settings(obs_data_t *settings)
  191. {
  192. bool legacy_display_id = obs_data_has_user_value(settings, "display");
  193. bool new_display_id = obs_data_has_user_value(settings, "display_uuid");
  194. if (legacy_display_id && !new_display_id) {
  195. CGDirectDisplayID display_id = (CGDirectDisplayID) obs_data_get_int(settings, "display");
  196. CFUUIDRef display_uuid = CGDisplayCreateUUIDFromDisplayID(display_id);
  197. if (display_uuid) {
  198. CFStringRef uuid_string = CFUUIDCreateString(kCFAllocatorDefault, display_uuid);
  199. obs_data_set_string(settings, "display_uuid", CFStringGetCStringPtr(uuid_string, kCFStringEncodingUTF8));
  200. obs_data_erase(settings, "display");
  201. CFRelease(uuid_string);
  202. CFRelease(display_uuid);
  203. } else {
  204. return 0;
  205. }
  206. } else if (legacy_display_id && new_display_id) {
  207. obs_data_erase(settings, "display");
  208. }
  209. const char *display_uuid = obs_data_get_string(settings, "display_uuid");
  210. if (display_uuid) {
  211. CFStringRef uuid_string = CFStringCreateWithCString(kCFAllocatorDefault, display_uuid, kCFStringEncodingUTF8);
  212. CFUUIDRef uuid_ref = CFUUIDCreateFromString(kCFAllocatorDefault, uuid_string);
  213. CGDirectDisplayID display = CGDisplayGetDisplayIDFromUUID(uuid_ref);
  214. CFRelease(uuid_string);
  215. CFRelease(uuid_ref);
  216. return display;
  217. } else {
  218. return 0;
  219. }
  220. }