Kaynağa Gözat

Add CoreGraphics window utilities

Palana 11 yıl önce
ebeveyn
işleme
bc64fa97a0

+ 5 - 2
plugins/mac-capture/CMakeLists.txt

@@ -14,15 +14,18 @@ include_directories(${COREAUDIO}
 
 set(mac-capture_HEADERS
 	audio-device-enum.h
-	mac-helpers.h)
+	mac-helpers.h
+	window-utils.h)
 
 set(mac-capture_SOURCES
 	plugin-main.c
 	audio-device-enum.c
 	mac-audio.c
-	mac-display-capture.m)
+	mac-display-capture.m
+	window-utils.m)
 	
 set_source_files_properties(mac-display-capture.m
+			    window-utils.m
 	PROPERTIES LANGUAGE C)
 
 add_library(mac-capture MODULE

+ 2 - 0
plugins/mac-capture/data/locale/en-US.ini

@@ -5,3 +5,5 @@ CoreAudio.Device.Default="Default"
 DisplayCapture="Display Capture"
 DisplayCapture.Display="Display"
 DisplayCapture.ShowCursor="Show Cursor"
+WindowUtils.Window="Window"
+WindowUtils.ShowEmptyNames="Show Windows with empty names"

+ 32 - 0
plugins/mac-capture/window-utils.h

@@ -0,0 +1,32 @@
+#import <CoreGraphics/CGWindow.h>
+#import <Cocoa/Cocoa.h>
+
+#include <util/threading.h>
+#include <obs-module.h>
+
+struct cocoa_window {
+	CGWindowID      window_id;
+
+	pthread_mutex_t name_lock;
+	NSString        *owner_name;
+	NSString        *window_name;
+
+	uint64_t next_search_time;
+};
+typedef struct cocoa_window *cocoa_window_t;
+
+NSArray *enumerate_cocoa_windows(void);
+
+bool find_window(cocoa_window_t cw, obs_data_t settings, bool force);
+
+void init_window(cocoa_window_t cw, obs_data_t settings);
+
+void destroy_window(cocoa_window_t cw);
+
+void update_window(cocoa_window_t cw, obs_data_t settings);
+
+void window_defaults(obs_data_t settings);
+
+void add_window_properties(obs_properties_t props);
+
+void show_window_properties(obs_properties_t props, bool show);

+ 233 - 0
plugins/mac-capture/window-utils.m

@@ -0,0 +1,233 @@
+#include "window-utils.h"
+
+#include <util/platform.h>
+
+#define WINDOW_NAME   ((NSString*)kCGWindowName)
+#define WINDOW_NUMBER ((NSString*)kCGWindowNumber)
+#define OWNER_NAME    ((NSString*)kCGWindowOwnerName)
+#define OWNER_PID     ((NSNumber*)kCGWindowOwnerPID)
+
+static NSComparator win_info_cmp = ^(NSDictionary *o1, NSDictionary *o2)
+{
+	NSComparisonResult res = [o1[OWNER_NAME] compare:o2[OWNER_NAME]];
+	if (res != NSOrderedSame)
+		return res;
+
+	res = [o1[OWNER_PID] compare:o2[OWNER_PID]];
+	if (res != NSOrderedSame)
+		return res;
+
+	res = [o1[WINDOW_NAME] compare:o2[WINDOW_NAME]];
+	if (res != NSOrderedSame)
+		return res;
+
+	return [o1[WINDOW_NUMBER] compare:o2[WINDOW_NUMBER]];
+};
+
+NSArray *enumerate_windows(void)
+{
+	NSArray *arr = (NSArray*)CGWindowListCopyWindowInfo(
+			kCGWindowListOptionOnScreenOnly,
+			kCGNullWindowID);
+
+	[arr autorelease];
+
+	return [arr sortedArrayUsingComparator:win_info_cmp];
+}
+
+#define WAIT_TIME_MS 500
+#define WAIT_TIME_US WAIT_TIME_MS * 1000
+#define WAIT_TIME_NS WAIT_TIME_US * 1000
+
+bool find_window(cocoa_window_t cw, obs_data_t settings, bool force)
+{
+	if (!force && cw->next_search_time > os_gettime_ns())
+		return false;
+
+	cw->next_search_time = os_gettime_ns() + WAIT_TIME_NS;
+
+	pthread_mutex_lock(&cw->name_lock);
+
+	if (!cw->window_name.length && !cw->owner_name.length)
+		goto invalid_name;
+
+	for (NSDictionary *dict in enumerate_windows()) {
+		if (![cw->owner_name isEqualToString:dict[OWNER_NAME]])
+			continue;
+
+		if (![cw->window_name isEqualToString:dict[WINDOW_NAME]])
+			continue;
+
+		pthread_mutex_unlock(&cw->name_lock);
+
+		NSNumber *window_id = (NSNumber*)dict[WINDOW_NUMBER];
+		cw->window_id       = window_id.intValue;
+
+		obs_data_set_int(settings, "window", cw->window_id);
+		return true;
+	}
+
+invalid_name:
+	pthread_mutex_unlock(&cw->name_lock);
+	return false;
+}
+
+void init_window(cocoa_window_t cw, obs_data_t settings)
+{
+	pthread_mutex_init(&cw->name_lock, NULL);
+
+	cw->owner_name  = @(obs_data_get_string(settings, "owner_name"));
+	cw->window_name = @(obs_data_get_string(settings, "window_name"));
+	[cw->owner_name  retain];
+	[cw->window_name retain];
+	find_window(cw, settings, true);
+}
+
+void destroy_window(cocoa_window_t cw)
+{
+	pthread_mutex_destroy(&cw->name_lock);
+	[cw->owner_name  release];
+	[cw->window_name release];
+}
+
+void update_window(cocoa_window_t cw, obs_data_t settings)
+{
+	pthread_mutex_lock(&cw->name_lock);
+	[cw->owner_name  release];
+	[cw->window_name release];
+	cw->owner_name   = @(obs_data_get_string(settings, "owner_name"));
+	cw->window_name  = @(obs_data_get_string(settings, "window_name"));
+	[cw->owner_name  retain];
+	[cw->window_name retain];
+	pthread_mutex_unlock(&cw->name_lock);
+
+	cw->window_id    = obs_data_get_int(settings, "window");
+}
+
+static inline const char *make_name(NSString *owner, NSString *name)
+{
+	if (!owner.length)
+		return "";
+
+	NSString *str = [NSString stringWithFormat:@"[%@] %@", owner, name];
+	return str.UTF8String;
+}
+
+static inline NSDictionary *find_window_dict(NSArray *arr, int window_id)
+{
+	for (NSDictionary *dict in arr) {
+		NSNumber *wid   = (NSNumber*)dict[WINDOW_NUMBER];
+		if (wid.intValue == window_id)
+			return dict;
+	}
+
+	return nil;
+}
+
+static inline bool window_changed_internal(obs_property_t p,
+		obs_data_t settings)
+{
+	int       window_id    = obs_data_get_int(settings, "window");
+	NSString *window_owner = @(obs_data_get_string(settings, "owner_name"));
+	NSString *window_name  =
+		@(obs_data_get_string(settings, "window_name"));
+
+	NSDictionary *win_info = @{
+		OWNER_NAME: window_owner,
+		WINDOW_NAME: window_name,
+	};
+
+	NSArray *arr = enumerate_windows();
+
+	bool show_empty_names = obs_data_get_bool(settings, "show_empty_names");
+
+	NSDictionary *cur = find_window_dict(arr, window_id);
+	bool window_found = cur != nil;
+	bool window_added = window_found;
+
+	obs_property_list_clear(p);
+	for (NSDictionary *dict in arr) {
+		NSString *owner = (NSString*)dict[OWNER_NAME];
+		NSString *name  = (NSString*)dict[WINDOW_NAME];
+		NSNumber *wid   = (NSNumber*)dict[WINDOW_NUMBER];
+
+		if (!window_added &&
+			win_info_cmp(win_info, dict) == NSOrderedAscending) {
+			window_added = true;
+			size_t idx = obs_property_list_add_int(p,
+					make_name(window_owner, window_name),
+					window_id);
+			obs_property_list_item_disable(p, idx, true);
+		}
+
+		if (!show_empty_names && !name.length &&
+				window_id != wid.intValue)
+			continue;
+
+		obs_property_list_add_int(p, make_name(owner, name),
+				wid.intValue);
+	}
+
+	if (!window_added) {
+		size_t idx = obs_property_list_add_int(p,
+				make_name(window_owner, window_name),
+				window_id);
+		obs_property_list_item_disable(p, idx, true);
+	}
+
+	if (!window_found)
+		return true;
+
+	NSString *owner  = (NSString*)cur[OWNER_NAME];
+	NSString *window = (NSString*)cur[WINDOW_NAME];
+
+	obs_data_set_string(settings, "owner_name", owner.UTF8String);
+	obs_data_set_string(settings, "window_name", window.UTF8String);
+
+	return true;
+}
+
+static bool window_changed(obs_properties_t props, obs_property_t p,
+		obs_data_t settings)
+{
+	UNUSED_PARAMETER(props);
+
+	@autoreleasepool {
+		return window_changed_internal(p, settings);
+	}
+}
+
+static bool toggle_empty_names(obs_properties_t props, obs_property_t p,
+		obs_data_t settings)
+{
+	UNUSED_PARAMETER(p);
+
+	return window_changed(props, obs_properties_get(props, "window"),
+			settings);
+}
+
+void window_defaults(obs_data_t settings)
+{
+	obs_data_set_default_int(settings, "window", kCGNullWindowID);
+	obs_data_set_default_bool(settings, "show_empty_names", false);
+}
+
+void add_window_properties(obs_properties_t props)
+{
+	obs_property_t window_list = obs_properties_add_list(props,
+			"window", obs_module_text("WindowUtils.Window"),
+			OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
+	obs_property_set_modified_callback(window_list, window_changed);
+
+	obs_property_t empty = obs_properties_add_bool(props,
+			"show_empty_names",
+			obs_module_text("WindowUtils.ShowEmptyNames"));
+	obs_property_set_modified_callback(empty, toggle_empty_names);
+}
+
+void show_window_properties(obs_properties_t props, bool show)
+{
+	obs_property_set_visible(obs_properties_get(props, "window"), show);
+	obs_property_set_visible(
+			obs_properties_get(props, "show_empty_names"), show);
+}