From fb9b5c829a18d57722b46478d704adf34fa9be5c Mon Sep 17 00:00:00 2001 From: PatTheMav Date: Sun, 30 Apr 2023 20:48:22 +0200 Subject: [PATCH] mac-capture: Switch to UUID-based display management Using UUIDs to store display references in obs-studio's settings file allows us to "rediscover" devices even between restarts (as CGDirectDisplayID changes between those) and select the same device. --- plugins/mac-capture/mac-display-capture.m | 105 +++++++++++++++++++--- plugins/mac-capture/mac-screen-capture.m | 80 ++++++++++++++--- 2 files changed, 163 insertions(+), 22 deletions(-) diff --git a/plugins/mac-capture/mac-display-capture.m b/plugins/mac-capture/mac-display-capture.m index c34bfc0d0..87c7c6e35 100644 --- a/plugins/mac-capture/mac-display-capture.m +++ b/plugins/mac-capture/mac-display-capture.m @@ -182,10 +182,24 @@ static inline void display_stream_update(struct display_capture *dc, static bool init_display_stream(struct display_capture *dc) { - if (dc->display >= [NSScreen screens].count) - return false; + [[NSScreen screens] + enumerateObjectsUsingBlock:^(NSScreen *_Nonnull screen, + NSUInteger index __unused, + BOOL *_Nonnull stop __unused) { + NSNumber *screenNumber = + screen.deviceDescription[@"NSScreenNumber"]; + CGDirectDisplayID display_id = + (CGDirectDisplayID)screenNumber.intValue; - dc->screen = [[NSScreen screens][dc->display] retain]; + if (display_id == dc->display) { + dc->screen = [screen retain]; + *stop = YES; + } + }]; + + if (!dc->screen) { + return false; + } dc->frame = [dc->screen convertRectToBacking:dc->screen.frame]; @@ -283,7 +297,39 @@ static void *display_capture_create(obs_data_t *settings, obs_source_t *source) init_window(&dc->window, settings); load_crop(dc, settings); - dc->display = (unsigned int)obs_data_get_int(settings, "display"); + bool legacy_display_id = obs_data_has_user_value(settings, "display"); + if (legacy_display_id) { + CGDirectDisplayID display_id = + (CGDirectDisplayID)obs_data_get_int(settings, + "display"); + CFUUIDRef display_uuid = + CGDisplayCreateUUIDFromDisplayID(display_id); + CFStringRef uuid_string = + CFUUIDCreateString(kCFAllocatorDefault, display_uuid); + obs_data_set_string( + settings, "display_uuid", + CFStringGetCStringPtr(uuid_string, + kCFStringEncodingUTF8)); + obs_data_erase(settings, "display"); + CFRelease(uuid_string); + CFRelease(display_uuid); + } + + const char *display_uuid = + obs_data_get_string(settings, "display_uuid"); + if (display_uuid) { + CFStringRef uuid_string = CFStringCreateWithCString( + kCFAllocatorDefault, display_uuid, + kCFStringEncodingUTF8); + CFUUIDRef uuid_ref = CFUUIDCreateFromString(kCFAllocatorDefault, + uuid_string); + dc->display = CGDisplayGetDisplayIDFromUUID(uuid_ref); + CFRelease(uuid_string); + CFRelease(uuid_ref); + } else { + dc->display = CGMainDisplayID(); + } + pthread_mutex_init(&dc->mutex, NULL); if (!init_display_stream(dc)) @@ -482,7 +528,18 @@ static uint32_t display_capture_getheight(void *data) static void display_capture_defaults(obs_data_t *settings) { - obs_data_set_default_int(settings, "display", 0); + NSNumber *screen = + [[NSScreen mainScreen] deviceDescription][@"NSScreenNumber"]; + + CFUUIDRef display_uuid = CGDisplayCreateUUIDFromDisplayID( + (CGDirectDisplayID)screen.intValue); + CFStringRef uuid_string = + CFUUIDCreateString(kCFAllocatorDefault, display_uuid); + obs_data_set_default_string( + settings, "display_uuid", + CFStringGetCStringPtr(uuid_string, kCFStringEncodingUTF8)); + CFRelease(uuid_string); + CFRelease(display_uuid); obs_data_set_default_bool(settings, "show_cursor", true); obs_data_set_default_int(settings, "crop_mode", CROP_NONE); @@ -536,7 +593,16 @@ static void display_capture_update(void *data, obs_data_t *settings) if (requires_window(dc->crop)) update_window(&dc->window, settings); - unsigned display = (unsigned int)obs_data_get_int(settings, "display"); + CFStringRef uuid_string = CFStringCreateWithCString( + kCFAllocatorDefault, + obs_data_get_string(settings, "display_uuid"), + kCFStringEncodingUTF8); + CFUUIDRef display_uuid = + CFUUIDCreateFromString(kCFAllocatorDefault, uuid_string); + CGDirectDisplayID display = CGDisplayGetDisplayIDFromUUID(display_uuid); + CFRelease(uuid_string); + CFRelease(display_uuid); + bool show_cursor = obs_data_get_bool(settings, "show_cursor"); if (dc->display == display && dc->hide_cursor != show_cursor) return; @@ -596,13 +662,14 @@ static obs_properties_t *display_capture_properties(void *unused) obs_properties_t *props = obs_properties_create(); obs_property_t *list = obs_properties_add_list( - props, "display", obs_module_text("DisplayCapture.Display"), - OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + props, "display_uuid", + obs_module_text("DisplayCapture.Display"), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); [[NSScreen screens] enumerateObjectsUsingBlock:^( - NSScreen *_Nonnull screen, NSUInteger index, - BOOL *_Nonnull stop - __attribute__((unused))) { + NSScreen *_Nonnull screen, + NSUInteger index __unused, + BOOL *_Nonnull stop __unused) { char dimension_buffer[4][12]; char name_buffer[256]; snprintf(dimension_buffer[0], sizeof(dimension_buffer[0]), "%u", @@ -620,7 +687,21 @@ static obs_properties_t *display_capture_properties(void *unused) dimension_buffer[0], dimension_buffer[1], dimension_buffer[2], dimension_buffer[3]); - obs_property_list_add_int(list, name_buffer, index); + NSNumber *screenNumber = + screen.deviceDescription[@"NSScreenNumber"]; + + CGDirectDisplayID display_id = + (CGDirectDisplayID)screenNumber.intValue; + CFUUIDRef display_uuid = + CGDisplayCreateUUIDFromDisplayID(display_id); + CFStringRef uuid_string = + CFUUIDCreateString(kCFAllocatorDefault, display_uuid); + obs_property_list_add_string( + list, name_buffer, + CFStringGetCStringPtr(uuid_string, + kCFStringEncodingUTF8)); + CFRelease(uuid_string); + CFRelease(display_uuid); }]; obs_properties_add_bool(props, "show_cursor", diff --git a/plugins/mac-capture/mac-screen-capture.m b/plugins/mac-capture/mac-screen-capture.m index 22ba8d311..959186506 100644 --- a/plugins/mac-capture/mac-screen-capture.m +++ b/plugins/mac-capture/mac-screen-capture.m @@ -633,7 +633,39 @@ static void *screen_capture_create(obs_data_t *settings, obs_source_t *source) if (!sc->effect) goto fail; - sc->display = (CGDirectDisplayID)obs_data_get_int(settings, "display"); + bool legacy_display_id = obs_data_has_user_value(settings, "display"); + if (legacy_display_id) { + CGDirectDisplayID display_id = + (CGDirectDisplayID)obs_data_get_int(settings, + "display"); + CFUUIDRef display_uuid = + CGDisplayCreateUUIDFromDisplayID(display_id); + CFStringRef uuid_string = + CFUUIDCreateString(kCFAllocatorDefault, display_uuid); + obs_data_set_string( + settings, "display_uuid", + CFStringGetCStringPtr(uuid_string, + kCFStringEncodingUTF8)); + obs_data_erase(settings, "display"); + CFRelease(uuid_string); + CFRelease(display_uuid); + } + + const char *display_uuid = + obs_data_get_string(settings, "display_uuid"); + if (display_uuid) { + CFStringRef uuid_string = CFStringCreateWithCString( + kCFAllocatorDefault, display_uuid, + kCFStringEncodingUTF8); + CFUUIDRef uuid_ref = CFUUIDCreateFromString(kCFAllocatorDefault, + uuid_string); + sc->display = CGDisplayGetDisplayIDFromUUID(uuid_ref); + CFRelease(uuid_string); + CFRelease(uuid_ref); + } else { + sc->display = CGMainDisplayID(); + } + sc->application_id = [[NSString alloc] initWithUTF8String:obs_data_get_string(settings, "application")]; @@ -740,7 +772,15 @@ static void screen_capture_defaults(obs_data_t *settings) } } - obs_data_set_default_int(settings, "display", initial_display); + CFUUIDRef display_uuid = + CGDisplayCreateUUIDFromDisplayID(initial_display); + CFStringRef uuid_string = + CFUUIDCreateString(kCFAllocatorDefault, display_uuid); + obs_data_set_default_string( + settings, "display_uuid", + CFStringGetCStringPtr(uuid_string, kCFStringEncodingUTF8)); + CFRelease(uuid_string); + CFRelease(display_uuid); obs_data_set_default_obj(settings, "application", NULL); obs_data_set_default_int(settings, "type", ScreenCaptureDisplayStream); @@ -764,8 +804,17 @@ static void screen_capture_update(void *data, obs_data_t *settings) ScreenCaptureStreamType capture_type = (ScreenCaptureStreamType)obs_data_get_int(settings, "type"); - CGDirectDisplayID display = - (CGDirectDisplayID)obs_data_get_int(settings, "display"); + + CFStringRef uuid_string = CFStringCreateWithCString( + kCFAllocatorDefault, + obs_data_get_string(settings, "display_uuid"), + kCFStringEncodingUTF8); + CFUUIDRef display_uuid = + CFUUIDCreateFromString(kCFAllocatorDefault, uuid_string); + CGDirectDisplayID display = CGDisplayGetDisplayIDFromUUID(display_uuid); + CFRelease(uuid_string); + CFRelease(display_uuid); + NSString *application_id = [[NSString alloc] initWithUTF8String:obs_data_get_string(settings, "application")]; @@ -827,7 +876,8 @@ static bool build_display_list(struct screen_capture *sc, { os_sem_wait(sc->shareable_content_available); - obs_property_t *display_list = obs_properties_get(props, "display"); + obs_property_t *display_list = + obs_properties_get(props, "display_uuid"); obs_property_list_clear(display_list); [sc->shareable_content.displays enumerateObjectsUsingBlock:^( @@ -869,8 +919,16 @@ static bool build_display_list(struct screen_capture *sc, dimension_buffer[1], dimension_buffer[2], dimension_buffer[3]); - obs_property_list_add_int(display_list, name_buffer, - display.displayID); + CFUUIDRef display_uuid = + CGDisplayCreateUUIDFromDisplayID(display.displayID); + CFStringRef uuid_string = + CFUUIDCreateString(kCFAllocatorDefault, display_uuid); + obs_property_list_add_string( + display_list, name_buffer, + CFStringGetCStringPtr(uuid_string, + kCFStringEncodingUTF8)); + CFRelease(uuid_string); + CFRelease(display_uuid); }]; os_sem_post(sc->shareable_content_available); @@ -946,7 +1004,8 @@ static bool content_settings_changed(void *data, obs_properties_t *props, unsigned int capture_type_id = (unsigned int)obs_data_get_int(settings, "type"); - obs_property_t *display_list = obs_properties_get(props, "display"); + obs_property_t *display_list = + obs_properties_get(props, "display_uuid"); obs_property_t *window_list = obs_properties_get(props, "window"); obs_property_t *app_list = obs_properties_get(props, "application"); obs_property_t *empty = obs_properties_get(props, "show_empty_names"); @@ -1037,8 +1096,9 @@ static obs_properties_t *screen_capture_properties(void *data) obs_module_text("ApplicationCapture"), 2); obs_property_t *display_list = obs_properties_add_list( - props, "display", obs_module_text("DisplayCapture.Display"), - OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + props, "display_uuid", + obs_module_text("DisplayCapture.Display"), OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); obs_property_t *app_list = obs_properties_add_list( props, "application", obs_module_text("Application"),