diff --git a/plugins/mac-capture/data/locale/en-US.ini b/plugins/mac-capture/data/locale/en-US.ini index a000c535d..b6dd2728e 100644 --- a/plugins/mac-capture/data/locale/en-US.ini +++ b/plugins/mac-capture/data/locale/en-US.ini @@ -24,4 +24,5 @@ Crop.size.height="Crop bottom" SCK.Name="macOS Screen Capture" SCK.Name.Beta="macOS Screen Capture (BETA)" SCK.AudioUnavailable="Audio capture requires macOS 13 or newer." +SCK.CaptureTypeUnavailable="Selected capture type requires macOS 13 or newer." SCK.Method="Method" diff --git a/plugins/mac-capture/mac-screen-capture.m b/plugins/mac-capture/mac-screen-capture.m index 6efbf313f..5b01f6f5b 100644 --- a/plugins/mac-capture/mac-screen-capture.m +++ b/plugins/mac-capture/mac-screen-capture.m @@ -69,6 +69,8 @@ struct screen_capture { NSString *application_id; }; +#pragma mark - + static void destroy_screen_stream(struct screen_capture *sc) { if (sc->disp) { @@ -365,33 +367,9 @@ static bool init_screen_stream(struct screen_capture *sc) case ScreenCaptureDisplayStream: { SCDisplay *target_display = get_target_display(); - if (@available(macOS 13.0, *)) { - content_filter = [[SCContentFilter alloc] - initWithDisplay:target_display - excludingWindows:[[NSArray alloc] init]]; - } else { - NSArray *excluded = [sc->shareable_content.applications - filteredArrayUsingPredicate: - [NSPredicate predicateWithBlock:^BOOL( - SCRunningApplication - *application, - NSDictionary< - NSString *, - id> - *_Nullable bindings - __attribute__(( - unused))) { - return [application - .bundleIdentifier - isEqualToString: - @"com.apple.controlcenter"]; - }]]; - - content_filter = [[SCContentFilter alloc] - initWithDisplay:target_display - excludingApplications:excluded - exceptingWindows:[[NSArray alloc] init]]; - } + content_filter = [[SCContentFilter alloc] + initWithDisplay:target_display + excludingWindows:[[NSArray alloc] init]]; set_display_mode(sc, target_display); } break; @@ -464,13 +442,22 @@ static bool init_screen_stream(struct screen_capture *sc) [sc->stream_properties setShowsCursor:!sc->hide_cursor]; [sc->stream_properties setColorSpaceName:kCGColorSpaceDisplayP3]; [sc->stream_properties setPixelFormat:'l10r']; -#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000 + if (@available(macOS 13.0, *)) { +#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000 [sc->stream_properties setCapturesAudio:TRUE]; [sc->stream_properties setExcludesCurrentProcessAudio:TRUE]; [sc->stream_properties setChannelCount:2]; - } #endif + } else { + if (sc->capture_type != ScreenCaptureWindowStream) { + sc->disp = NULL; + os_event_init(&sc->disp_finished, OS_EVENT_TYPE_MANUAL); + os_event_init(&sc->stream_start_completed, + OS_EVENT_TYPE_MANUAL); + return true; + } + } sc->disp = [[SCStream alloc] initWithFilter:content_filter configuration:sc->stream_properties @@ -491,7 +478,7 @@ static bool init_screen_stream(struct screen_capture *sc) } #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000 - if (__builtin_available(macOS 13.0, *)) { + if (@available(macOS 13.0, *)) { did_add_output = [sc->disp addStreamOutput:sc->capture_delegate type:SCStreamOutputTypeAudio @@ -577,8 +564,8 @@ static void *screen_capture_create(obs_data_t *settings, obs_source_t *source) sc->show_empty_names = obs_data_get_bool(settings, "show_empty_names"); sc->show_hidden_windows = obs_data_get_bool(settings, "show_hidden_windows"); - sc->window = obs_data_get_int(settings, "window"); - sc->capture_type = obs_data_get_int(settings, "type"); + sc->window = (CGWindowID)obs_data_get_int(settings, "window"); + sc->capture_type = (unsigned int)obs_data_get_int(settings, "type"); os_sem_init(&sc->shareable_content_available, 1); screen_capture_build_content_list( @@ -591,7 +578,7 @@ static void *screen_capture_create(obs_data_t *settings, obs_source_t *source) if (!sc->effect) goto fail; - sc->display = obs_data_get_int(settings, "display"); + sc->display = (CGDirectDisplayID)obs_data_get_int(settings, "display"); sc->application_id = [[NSString alloc] initWithUTF8String:obs_data_get_string(settings, "application")]; @@ -699,10 +686,11 @@ static void screen_capture_defaults(obs_data_t *settings) } } - obs_data_set_default_int(settings, "type", 0); obs_data_set_default_int(settings, "display", initial_display); - obs_data_set_default_int(settings, "window", kCGNullWindowID); + obs_data_set_default_obj(settings, "application", NULL); + obs_data_set_default_int(settings, "type", ScreenCaptureDisplayStream); + obs_data_set_default_int(settings, "window", kCGNullWindowID); obs_data_set_default_bool(settings, "show_cursor", true); obs_data_set_default_bool(settings, "show_empty_names", false); obs_data_set_default_bool(settings, "show_hidden_windows", false); @@ -713,7 +701,8 @@ static void screen_capture_update(void *data, obs_data_t *settings) struct screen_capture *sc = data; CGWindowID old_window_id = sc->window; - CGWindowID new_window_id = obs_data_get_int(settings, "window"); + CGWindowID new_window_id = + (CGWindowID)obs_data_get_int(settings, "window"); if (new_window_id > 0 && new_window_id != old_window_id) sc->window = new_window_id; @@ -766,6 +755,8 @@ static void screen_capture_update(void *data, obs_data_t *settings) obs_leave_graphics(); } +#pragma mark - obs_properties + static bool build_display_list(struct screen_capture *sc, obs_properties_t *props) { @@ -894,7 +885,8 @@ static bool content_settings_changed(void *data, obs_properties_t *props, { struct screen_capture *sc = data; - unsigned int capture_type_id = obs_data_get_int(settings, "type"); + 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 *window_list = obs_properties_get(props, "window"); obs_property_t *app_list = obs_properties_get(props, "application"); @@ -902,6 +894,9 @@ static bool content_settings_changed(void *data, obs_properties_t *props, obs_property_t *hidden = obs_properties_get(props, "show_hidden_windows"); + obs_property_t *capture_type_error = + obs_properties_get(props, "capture_type_info"); + if (sc->capture_type != capture_type_id) { switch (capture_type_id) { case 0: { @@ -910,6 +905,11 @@ static bool content_settings_changed(void *data, obs_properties_t *props, obs_property_set_visible(app_list, false); obs_property_set_visible(empty, false); obs_property_set_visible(hidden, false); + + if (capture_type_error) { + obs_property_set_visible(capture_type_error, + true); + } break; } case 1: { @@ -918,6 +918,11 @@ static bool content_settings_changed(void *data, obs_properties_t *props, obs_property_set_visible(app_list, false); obs_property_set_visible(empty, true); obs_property_set_visible(hidden, true); + + if (capture_type_error) { + obs_property_set_visible(capture_type_error, + false); + } break; } case 2: { @@ -926,21 +931,26 @@ static bool content_settings_changed(void *data, obs_properties_t *props, obs_property_set_visible(window_list, false); obs_property_set_visible(empty, false); obs_property_set_visible(hidden, true); + + if (capture_type_error) { + obs_property_set_visible(capture_type_error, + true); + } break; } } } - sc->show_empty_names = obs_data_get_bool(settings, "show_empty_names"); - sc->show_hidden_windows = - obs_data_get_bool(settings, "show_hidden_windows"); - screen_capture_build_content_list( sc, capture_type_id == ScreenCaptureDisplayStream); build_display_list(sc, props); build_window_list(sc, props); build_application_list(sc, props); + sc->show_empty_names = obs_data_get_bool(settings, "show_empty_names"); + sc->show_hidden_windows = + obs_data_get_bool(settings, "show_hidden_windows"); + return true; } @@ -949,9 +959,11 @@ static obs_properties_t *screen_capture_properties(void *data) struct screen_capture *sc = data; obs_properties_t *props = obs_properties_create(); + obs_property_t *capture_type = obs_properties_add_list( props, "type", obs_module_text("SCK.Method"), OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(capture_type, obs_module_text("DisplayCapture"), 0); obs_property_list_add_int(capture_type, @@ -982,9 +994,6 @@ static obs_properties_t *screen_capture_properties(void *data) props, "show_hidden_windows", obs_module_text("WindowUtils.ShowHidden")); - obs_properties_add_bool(props, "show_cursor", - obs_module_text("DisplayCapture.ShowCursor")); - if (sc) { obs_property_set_modified_callback2( hidden, content_settings_changed, sc); @@ -1020,12 +1029,48 @@ static obs_properties_t *screen_capture_properties(void *data) empty, content_settings_changed, sc); } + obs_properties_add_bool(props, "show_cursor", + obs_module_text("DisplayCapture.ShowCursor")); + if (@available(macOS 13.0, *)) ; - else - obs_properties_add_text(props, "audio_info", - obs_module_text("SCK.AudioUnavailable"), - OBS_TEXT_INFO); + else { + obs_property_t *audio_warning = obs_properties_add_text( + props, "audio_info", + obs_module_text("SCK.AudioUnavailable"), OBS_TEXT_INFO); + obs_property_text_set_info_type(audio_warning, + OBS_TEXT_INFO_WARNING); + + obs_property_t *capture_type_error = obs_properties_add_text( + props, "capture_type_info", + obs_module_text("SCK.CaptureTypeUnavailable"), + OBS_TEXT_INFO); + + obs_property_text_set_info_type(capture_type_error, + OBS_TEXT_INFO_ERROR); + + if (sc) { + switch (sc->capture_type) { + case ScreenCaptureDisplayStream: { + obs_property_set_visible(capture_type_error, + true); + break; + } + case ScreenCaptureWindowStream: { + obs_property_set_visible(capture_type_error, + false); + break; + } + case ScreenCaptureApplicationStream: { + obs_property_set_visible(capture_type_error, + true); + break; + } + } + } else { + obs_property_set_visible(capture_type_error, false); + } + } return props; } @@ -1053,6 +1098,8 @@ enum gs_color_space screen_capture_video_get_color_space( return GS_CS_SRGB_16F; } +#pragma mark - obs_source_info + struct obs_source_info screen_capture_info = { .id = "screen_capture", .type = OBS_SOURCE_TYPE_INPUT, @@ -1077,6 +1124,8 @@ struct obs_source_info screen_capture_info = { .video_get_color_space = screen_capture_video_get_color_space, }; +#pragma mark - ScreenCaptureDelegate + @implementation ScreenCaptureDelegate - (void)stream:(SCStream *)stream