diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7809d34fa1ef09..0576b00aa097c2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -243,6 +243,12 @@ jobs: name: 'obs-studio-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}' path: ${{env.PACKAGE_NAME}}-macos-release-${{ steps.setup.outputs.commitHash }}-${{ matrix.arch }}.tar.gz + - name: 'Upload debug files to Sentry' + if: startsWith(github.ref, 'refs/tags/') + run: 'python ./slobs_CI/sentry-osx.py' + env: + SENTRY_AUTH_TOKEN: ${{secrets.SENTRY_AUTH_TOKEN}} + BUILDCONFIG: RelWithDebInfo linux_build: name: '02 - Linux' diff --git a/CI/macos/fix_deps_paths.sh b/CI/macos/fix_deps_paths.sh new file mode 100755 index 00000000000000..28a5036aeea53d --- /dev/null +++ b/CI/macos/fix_deps_paths.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +# Check if a file path is passed as an argument +if [ -z "$1" ]; then + echo "Usage: $0 " + exit 1 +fi + +BINARY_PATH=$1 + +# Use otool to get the list of linked libraries +LIB_PATHS=$(otool -L "$BINARY_PATH" | grep "obs-deps" | awk '{print $1}') + +# Check if any obs-deps libraries were found +if [ -z "$LIB_PATHS" ]; then + echo "No obs-deps libraries found in $BINARY_PATH." +else + # Loop through each library path and change it to @loader_path, removing version from the name + for OLD_PATH in $LIB_PATHS; do + # Extract the base library name without version + LIB_NAME=$(basename "$OLD_PATH" | sed -E 's/\.[0-9]+\.dylib$/.dylib/') + + # Construct the new path using @loader_path + NEW_PATH="@loader_path/$LIB_NAME" + + # Print what we are changing for logging + echo "Changing $OLD_PATH to $NEW_PATH" + + # Run the install_name_tool command to make the change + install_name_tool -change "$OLD_PATH" "$NEW_PATH" "$BINARY_PATH" + done + + echo "All obs-deps libraries have been updated to use @loader_path and version-less names in $BINARY_PATH." +fi + +# Additional libraries that use @rpath should be converted to @loader_path +OTHER_LIBS=$(otool -L "$BINARY_PATH" | grep "@rpath" | awk '{print $1}') + +if [ -n "$OTHER_LIBS" ]; then + for OLD_PATH in $OTHER_LIBS; do + # Extract the base library name without version + LIB_NAME=$(basename "$OLD_PATH" | sed -E 's/\.[0-9]+\.dylib$/.dylib/') + + # Construct the new path using @loader_path + NEW_PATH="@loader_path/$LIB_NAME" + + # Print what we are changing for logging + echo "Changing $OLD_PATH to $NEW_PATH" + + # Run the install_name_tool command to make the change + install_name_tool -change "$OLD_PATH" "$NEW_PATH" "$BINARY_PATH" + done + + echo "All @rpath libraries have been updated to use @loader_path in $BINARY_PATH." +else + echo "No @rpath libraries found." +fi + +# Add @executable_path/../Frameworks to rpath as a fallback +echo "Adding @executable_path/../Frameworks as an rpath fallback" +install_name_tool -add_rpath "@executable_path/../Frameworks" "$BINARY_PATH" + +# Check if the binary is signed (for macOS app distribution) +CODESIGN_STATUS=$(codesign -vv "$BINARY_PATH" 2>&1) + +if [[ "$CODESIGN_STATUS" == *"not signed"* ]]; then + echo "Binary is not signed. Please sign it for distribution if necessary." +else + echo "Binary is already signed." +fi + +echo "All modifications are done." diff --git a/cmake/macos/helpers.cmake b/cmake/macos/helpers.cmake index 2faac1d7f5b69f..a8bdb1a8839494 100644 --- a/cmake/macos/helpers.cmake +++ b/cmake/macos/helpers.cmake @@ -11,7 +11,7 @@ include(helpers_common) # set_target_properties_obs: Set target properties for use in obs-studio function(set_target_properties_obs target) -message(STATUS "[set_target_properties_obs] Setting target properties for ${target}...") + message(STATUS "[set_target_properties_obs] Setting target properties for ${target}...") set(options "") set(oneValueArgs "") set(multiValueArgs PROPERTIES) @@ -23,6 +23,7 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ list(POP_FRONT _STPO_PROPERTIES key value) set_property(TARGET ${target} PROPERTY ${key} "${value}") endwhile() + get_target_property(target_type ${target} TYPE) # Target is a GUI or CLI application @@ -32,19 +33,19 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ set_target_properties( ${target} PROPERTIES OUTPUT_NAME OBS - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/Info.plist.in" - XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.obs-studio - XCODE_ATTRIBUTE_PRODUCT_NAME OBS - XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon - XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY YES - XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY YES - XCODE_EMBED_PLUGINS_REMOVE_HEADERS_ON_COPY YES - XCODE_EMBED_PLUGINS_CODE_SIGN_ON_COPY YES - XCODE_ATTRIBUTE_COPY_PHASE_STRIP NO - XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES - XCODE_ATTRIBUTE_SKIP_INSTALL NO - XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)") + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/Info.plist.in" + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.obs-studio + XCODE_ATTRIBUTE_PRODUCT_NAME OBS + XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AppIcon + XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY YES + XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY YES + XCODE_EMBED_PLUGINS_REMOVE_HEADERS_ON_COPY YES + XCODE_EMBED_PLUGINS_CODE_SIGN_ON_COPY YES + XCODE_ATTRIBUTE_COPY_PHASE_STRIP NO + XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES + XCODE_ATTRIBUTE_SKIP_INSTALL NO + XCODE_ATTRIBUTE_INSTALL_PATH "$(LOCAL_APPS_DIR)") get_property(obs_dependencies GLOBAL PROPERTY _OBS_DEPENDENCIES) add_dependencies(${target} ${obs_dependencies}) @@ -68,15 +69,16 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ get_property(obs_executables GLOBAL PROPERTY _OBS_EXECUTABLES) add_dependencies(${target} ${obs_executables}) + foreach(executable IN LISTS obs_executables) set_property( TARGET ${executable} PROPERTY XCODE_ATTRIBUTE_INSTALL_PATH - "$(LOCAL_APPS_DIR)/$/Contents/MacOS") + "$(LOCAL_APPS_DIR)/$/Contents/MacOS") add_custom_command( TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$" - "$/MacOS/" + "$/MacOS/" COMMENT "Copy ${executable} to application bundle") endforeach() @@ -87,12 +89,12 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ TARGET ${target} POST_BUILD COMMAND - /usr/bin/sed -i '' 's/font-size: 10pt\;/font-size: 12pt\;/' - "$/Resources/themes/Acri.qss" - "$/Resources/themes/Grey.qss" - "$/Resources/themes/Light.qss" - "$/Resources/themes/Rachni.qss" - "$/Resources/themes/Yami.qss" + /usr/bin/sed -i '' 's/font-size: 10pt\;/font-size: 12pt\;/' + "$/Resources/themes/Acri.qss" + "$/Resources/themes/Grey.qss" + "$/Resources/themes/Light.qss" + "$/Resources/themes/Rachni.qss" + "$/Resources/themes/Yami.qss" COMMENT "Patch Qt stylesheets to use larger default font size on macOS") add_custom_command( @@ -114,7 +116,7 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_directory "$" - "$/Resources/$" + "$/Resources/$" COMMENT "Add OBS DAL plugin to application bundle") endif() @@ -123,7 +125,7 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ TARGET ${target} POST_BUILD COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$/obspython.py" - "$/Resources" + "$/Resources" COMMENT "Add OBS::python import module") endif() @@ -141,25 +143,26 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ set_target_properties( ${target} PROPERTIES NO_SONAME TRUE - MACHO_COMPATIBILITY_VERSION 1.0 - MACHO_CURRENT_VERSION ${OBS_VERSION_MAJOR} - SOVERSION 0 - VERSION 0 - XCODE_ATTRIBUTE_DYLIB_COMPATIBILITY_VERSION 1.0 - XCODE_ATTRIBUTE_DYLIB_CURRENT_VERSION ${OBS_VERSION_MAJOR} - XCODE_ATTRIBUTE_PRODUCT_NAME ${target} - XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target} - XCODE_ATTRIBUTE_SKIP_INSTALL YES) + MACHO_COMPATIBILITY_VERSION 1.0 + MACHO_CURRENT_VERSION ${OBS_VERSION_MAJOR} + SOVERSION 0 + VERSION 0 + XCODE_ATTRIBUTE_DYLIB_COMPATIBILITY_VERSION 1.0 + XCODE_ATTRIBUTE_DYLIB_CURRENT_VERSION ${OBS_VERSION_MAJOR} + XCODE_ATTRIBUTE_PRODUCT_NAME ${target} + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target} + XCODE_ATTRIBUTE_SKIP_INSTALL YES) get_target_property(is_framework ${target} FRAMEWORK) + if(is_framework) _check_info_plist() set_target_properties( ${target} PROPERTIES FRAMEWORK_VERSION A - MACOSX_FRAMEWORK_IDENTIFIER com.obsproject.${target} - MACOSX_FRAMEWORK_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/Info.plist.in" - XCODE_ATTRIBUTE_SKIP_INSTALL YES) + MACOSX_FRAMEWORK_IDENTIFIER com.obsproject.${target} + MACOSX_FRAMEWORK_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/Info.plist.in" + XCODE_ATTRIBUTE_SKIP_INSTALL YES) endif() _add_entitlements() @@ -169,10 +172,10 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ elseif(target_type STREQUAL MODULE_LIBRARY) if(target STREQUAL obspython) set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_NAME ${target} - XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target}) + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target}) elseif(target STREQUAL obslua) set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_NAME ${target} - XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target}) + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target}) elseif(target STREQUAL obs-dal-plugin) set_target_properties(${target} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE) set_property(GLOBAL APPEND PROPERTY _OBS_DEPENDENCIES ${target}) @@ -182,15 +185,16 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ set_target_properties( ${target} PROPERTIES BUNDLE TRUE - BUNDLE_EXTENSION plugin - MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/Info.plist.in" - XCODE_ATTRIBUTE_PRODUCT_NAME ${target} - XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target}) + BUNDLE_EXTENSION plugin + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/Info.plist.in" + XCODE_ATTRIBUTE_PRODUCT_NAME ${target} + XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER com.obsproject.${target}) if(target STREQUAL obs-browser) # Good-enough for now as there are no other variants - in _theory_ we should only add the appropriate variant, # but that is only known at project generation and not build system configuration. get_target_property(imported_location CEF::Library IMPORTED_LOCATION_RELEASE) + if(imported_location) list(APPEND cef_items "${imported_location}") endif() @@ -211,6 +215,7 @@ message(STATUS "[set_target_properties_obs] Setting target properties for ${targ endif() target_install_resources(${target}) + target_install_ffmpeg_and_ffprobe(${target}) get_target_property(target_sources ${target} SOURCES) set(target_ui_files ${target_sources}) @@ -255,14 +260,14 @@ endmacro() macro(_add_entitlements) if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/entitlements.plist") set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS - "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/entitlements.plist") + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos/entitlements.plist") endif() endmacro() # target_export: Helper function to export target as CMake package function(target_export target) # Exclude CMake package from 'ALL' target - #set(exclude_variant EXCLUDE_FROM_ALL) + # set(exclude_variant EXCLUDE_FROM_ALL) set(exclude_variant "") _target_export(${target}) endfunction() @@ -270,11 +275,13 @@ endfunction() # target_install_resources: Helper function to add resources into bundle function(target_install_resources target) message(DEBUG "Installing resources for target ${target}...") + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/data") file(GLOB_RECURSE data_files "${CMAKE_CURRENT_SOURCE_DIR}/data/*") + foreach(data_file IN LISTS data_files) cmake_path(RELATIVE_PATH data_file BASE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/" OUTPUT_VARIABLE - relative_path) + relative_path) cmake_path(GET relative_path PARENT_PATH relative_path) target_sources(${target} PRIVATE "${data_file}") set_property(SOURCE "${data_file}" PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${relative_path}") @@ -282,6 +289,55 @@ function(target_install_resources target) endforeach() endif() endfunction() +# Function to install ffmpeg and ffprobe binaries +function(target_install_ffmpeg_and_ffprobe target) + if(TARGET OBS::ffmpeg) + # Adjust the path relative to FFmpeg_INCLUDE_DIRS + get_filename_component(ffmpeg_bin_dir "${FFmpeg_INCLUDE_DIRS}/../bin" REALPATH) + set(ffmpeg_path "${ffmpeg_bin_dir}/ffmpeg") + set(ffprobe_path "${ffmpeg_bin_dir}/ffprobe") + set(destination "${CMAKE_INSTALL_PREFIX}/OBS.app/Contents/Frameworks") + set(FINAL_FFMPEG_PATH "${destination}/ffmpeg") + set(FINAL_FFPROBE_PATH "${destination}/ffprobe") + + # Install ffmpeg + if(EXISTS "${ffmpeg_path}") + message(STATUS "Found ffmpeg at ${ffmpeg_path}") + install( + FILES "${ffmpeg_path}" + DESTINATION "${destination}" + PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + ) + + # Run the fix_deps_paths.sh script at install time with the full absolute path + install(CODE " + message(\"Running fix_deps_paths.sh on ${FINAL_FFMPEG_PATH}\") + execute_process(COMMAND bash \"${CMAKE_SOURCE_DIR}/CI/macos/fix_deps_paths.sh\" \"${FINAL_FFMPEG_PATH}\") + ") + else() + message(WARNING "ffmpeg not found at ${ffmpeg_path}") + endif() + + # Install ffprobe + if(EXISTS "${ffprobe_path}") + message(STATUS "Found ffprobe at ${ffprobe_path}") + install( + FILES "${ffprobe_path}" + DESTINATION "${destination}" + PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE + ) + + # Run the fix_deps_paths.sh script for ffprobe with the full absolute path + install(CODE " + message(\"Running fix_deps_paths.sh on ${FINAL_FFPROBE_PATH}\") + execute_process(COMMAND bash \"${CMAKE_SOURCE_DIR}/CI/macos/fix_deps_paths.sh\" \"${FINAL_FFPROBE_PATH}\") + ") + else() + message(WARNING "ffprobe not found at ${ffprobe_path}") + endif() + endif() +endfunction() + # target_add_resource: Helper function to add a specific resource to a bundle function(target_add_resource target resource) @@ -299,12 +355,14 @@ function(_bundle_dependencies target) get_property(obs_module_list GLOBAL PROPERTY OBS_MODULES_ENABLED) list(LENGTH obs_module_list num_modules) + if(num_modules GREATER 0) add_dependencies(${target} ${obs_module_list}) set_property( TARGET ${target} APPEND PROPERTY XCODE_EMBED_PLUGINS ${obs_module_list}) + foreach(module IN LISTS obs_module_list) find_dependencies(TARGET ${module} FOUND_VAR found_dependencies) endforeach() @@ -324,6 +382,7 @@ function(_bundle_dependencies target) if(is_imported) get_target_property(imported_location ${library} LOCATION) + if(NOT imported_location) continue() endif() @@ -335,8 +394,10 @@ function(_bundle_dependencies target) if(is_xcode_framework) break() endif() + cmake_path(IS_PREFIX sdk_library_path "${imported_location}" is_xcode_framework) endforeach() + cmake_path(IS_PREFIX system_library_path "${imported_location}" is_system_framework) if(is_system_framework OR is_xcode_framework) @@ -356,6 +417,7 @@ function(_bundle_dependencies target) if(library MATCHES "Qt[56]?::.+") find_qt_plugins(COMPONENT ${library} TARGET ${target} FOUND_VAR plugins_list) endif() + list(APPEND library_paths ${library_location}) elseif(NOT imported AND library_type STREQUAL "SHARED_LIBRARY") message(TRACE "${library} is a project target") @@ -364,6 +426,7 @@ function(_bundle_dependencies target) endforeach() list(REMOVE_DUPLICATES plugins_list) + foreach(plugin IN LISTS plugins_list) cmake_path(GET plugin PARENT_PATH plugin_path) set(plugin_base_dir "${plugin_path}/../") @@ -371,7 +434,7 @@ function(_bundle_dependencies target) cmake_path(RELATIVE_PATH plugin_path BASE_DIRECTORY "${plugin_stem_dir}" OUTPUT_VARIABLE plugin_file_name) target_sources(${target} PRIVATE "${plugin}") set_source_files_properties("${plugin}" PROPERTIES MACOSX_PACKAGE_LOCATION "plugins/${plugin_file_name}" - XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") + XCODE_FILE_ATTRIBUTES "CodeSignOnCopy") source_group("Qt plugins" FILES "${plugin}") endforeach() diff --git a/dependencies/obs-ndi.dll.txt b/dependencies/obs-ndi.dll.txt index c5d817e9f6c958..915672f21d3d78 100644 --- a/dependencies/obs-ndi.dll.txt +++ b/dependencies/obs-ndi.dll.txt @@ -10,7 +10,6 @@ VCRUNTIME140.dll VCRUNTIME140_1.dll api-ms-win-crt-runtime-l1-1-0.dll - api-ms-win-crt-convert-l1-1-0.dll api-ms-win-crt-heap-l1-1-0.dll > Summary diff --git a/libobs/obs.c b/libobs/obs.c index 626c69d6387b55..d2ad0bd48e7848 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -3267,6 +3267,7 @@ obs_context_data_init_wrap(struct obs_context_data *context, context->name = dup_name(name, private); context->settings = obs_data_newref(settings); context->hotkey_data = obs_data_newref(hotkey_data); + context->mutex = &obs->data.sources_mutex; return true; } @@ -3433,8 +3434,12 @@ void obs_context_data_remove_name(struct obs_context_data *context, void *phead) if (!context) return; + struct obs_context_data *item = NULL; pthread_mutex_lock(context->mutex); - HASH_DELETE(hh, *head, context); + HASH_FIND_STR(*head, context->name, item); + if (item) { + HASH_DELETE(hh, *head, context); + } pthread_mutex_unlock(context->mutex); } @@ -3448,8 +3453,14 @@ void obs_context_data_remove_uuid(struct obs_context_data *context, if (!context || !context->uuid || !uuid_head) return; + struct obs_context_data *item = NULL; + pthread_mutex_lock(context->mutex); - HASH_DELETE(hh_uuid, *uuid_head, context); + HASH_FIND_UUID(*uuid_head, context->uuid, item); + if (item) { + HASH_DELETE(hh_uuid, *uuid_head, context); + } + pthread_mutex_unlock(context->mutex); } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 924171496193ea..d563d8cf1fd38b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -86,6 +86,7 @@ if(OBS_CMAKE_VERSION VERSION_GREATER_EQUAL 3.0.0) OR OS_MACOS OR OS_LINUX) add_subdirectory(vlc-video) + add_subdirectory(obs-ndi) endif() if(OS_WINDOWS) add_subdirectory(win-capture) diff --git a/plugins/coreaudio-encoder/encoder.cpp b/plugins/coreaudio-encoder/encoder.cpp index 689be6a0dcac97..fdb401531056cf 100644 --- a/plugins/coreaudio-encoder/encoder.cpp +++ b/plugins/coreaudio-encoder/encoder.cpp @@ -1255,11 +1255,11 @@ static vector get_bitrates(DStr &log, ca_encoder *ca, auto handle_bitrate = [&](UInt32 bitrate) { if (find(begin(bitrates), end(bitrates), bitrate) == end(bitrates)) { - log_to_dstr(log, ca, "Adding bitrate %u\n", + log_to_dstr(log, ca, "Add %u, ", static_cast(bitrate)); bitrates.push_back(bitrate); } else { - log_to_dstr(log, ca, "Bitrate %u already added\n", + log_to_dstr(log, ca, "Has %u, ", static_cast(bitrate)); } }; @@ -1270,7 +1270,7 @@ static vector get_bitrates(DStr &log, ca_encoder *ca, if (min_ == max_) return; - log_to_dstr(log, ca, "Got actual bitrate range: %u<->%u\n", + log_to_dstr(log, ca, "Range %u<->%u, ", static_cast(min_), static_cast(max_)); @@ -1281,7 +1281,7 @@ static vector get_bitrates(DStr &log, ca_encoder *ca, return bitrates; for (UInt32 format_id : (ca ? *ca->allowed_formats : aac_formats)) { - log_to_dstr(log, ca, "Trying %s (0x%x) at %g" NBSP "hz\n", + log_to_dstr(log, ca, "Try %s (0x%x) at %g" NBSP "hz, ", format_id_to_str(format_id), static_cast(format_id), samplerate); diff --git a/plugins/mac-capture/mac-screen-capture.m b/plugins/mac-capture/mac-screen-capture.m index c92bfa79980394..7d9159b7ff5e14 100644 --- a/plugins/mac-capture/mac-screen-capture.m +++ b/plugins/mac-capture/mac-screen-capture.m @@ -23,6 +23,7 @@ bool is_screen_capture_available(void) #include #include #include +#include #define MACCAP_LOG(level, msg, ...) \ blog(level, "[ mac-screencapture ]: " msg, ##__VA_ARGS__) @@ -337,28 +338,65 @@ static inline void screen_stream_audio_update(struct screen_capture *sc, obs_source_output_audio(sc->source, &audio_data); } +static bool hasScreenRecordingPermission() +{ + if (@available(macOS 10.15, *)) { + return CGPreflightScreenCaptureAccess(); + } else { + return YES; // On earlier versions, no permission is required + } +} + +static void requestScreenRecordingPermission() +{ + if (@available(macOS 10.15, *)) { + dispatch_semaphore_t sema = dispatch_semaphore_create(0); + CGRequestScreenCaptureAccess(); + dispatch_semaphore_signal(sema); + } +} + static bool init_screen_stream(struct screen_capture *sc) { - SCContentFilter *content_filter; + MACCAP_LOG(LOG_WARNING, "Entering init_screen_stream"); + + if (!hasScreenRecordingPermission()) { + MACCAP_ERR("Screen recording permission not granted"); + dispatch_async(dispatch_get_main_queue(), ^{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Screen Recording Permission Required"]; + [alert setInformativeText:@"Please grant Screen Recording permission in System Preferences > Security & Privacy > Privacy > Screen Recording, then restart the application."]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + [alert release]; + + NSURL *url = [NSURL fileURLWithPath:@"/System/Library/PreferencePanes/Security.prefPane"]; + [[NSWorkspace sharedWorkspace] openURL:url]; + }); + + return false; + } + MACCAP_LOG(LOG_WARNING, "Initializing stream configuration"); + SCContentFilter *content_filter = nil; sc->frame = CGRectZero; sc->stream_properties = [[SCStreamConfiguration alloc] init]; + MACCAP_LOG(LOG_WARNING, "Waiting for shareable content"); os_sem_wait(sc->shareable_content_available); SCDisplay * (^get_target_display)() = ^SCDisplay *() { __block SCDisplay *target_display = nil; - [sc->shareable_content.displays - indexOfObjectPassingTest:^BOOL( - SCDisplay *_Nonnull display, NSUInteger idx, - BOOL *_Nonnull stop) { - if (display.displayID == sc->display) { - target_display = sc->shareable_content - .displays[idx]; - *stop = TRUE; - } - return *stop; - }]; + [sc->shareable_content.displays indexOfObjectPassingTest:^BOOL(SCDisplay *_Nonnull display, NSUInteger idx, BOOL *_Nonnull stop) { + if (display.displayID == sc->display) { + target_display = sc->shareable_content.displays[idx]; + *stop = TRUE; + } + return *stop; + }]; + if (!target_display) { + MACCAP_LOG(LOG_WARNING, "Target display not found. Display ID: %d", sc->display); + } return target_display; }; @@ -374,173 +412,193 @@ static bool init_screen_stream(struct screen_capture *sc) CGDisplayModeRelease(display_mode); }; + MACCAP_LOG(LOG_WARNING, "Capture type: %d", sc->capture_type); switch (sc->capture_type) { case ScreenCaptureDisplayStream: { + MACCAP_LOG(LOG_WARNING, "Initializing display stream capture"); SCDisplay *target_display = get_target_display(); + if (!target_display) { + MACCAP_ERR("Target display not found"); + os_sem_post(sc->shareable_content_available); + return false; + } + MACCAP_LOG(LOG_WARNING, "Creating content filter for display ID: %d", target_display.displayID); NSArray *empty = [[NSArray alloc] init]; - content_filter = [[SCContentFilter alloc] - initWithDisplay:target_display - excludingWindows:empty]; + content_filter = [[SCContentFilter alloc] initWithDisplay:target_display excludingWindows:empty]; [empty release]; set_display_mode(sc, target_display); } break; + case ScreenCaptureWindowStream: { + MACCAP_LOG(LOG_WARNING, "Initializing window stream capture"); __block SCWindow *target_window = nil; - if (sc->window != 0) { - [sc->shareable_content.windows - indexOfObjectPassingTest:^BOOL( - SCWindow *_Nonnull window, - NSUInteger idx, BOOL *_Nonnull stop) { - if (window.windowID == sc->window) { - target_window = - sc->shareable_content - .windows[idx]; - *stop = TRUE; - } - return *stop; - }]; - } else { - target_window = - [sc->shareable_content.windows objectAtIndex:0]; + [sc->shareable_content.windows indexOfObjectPassingTest:^BOOL(SCWindow *_Nonnull window, NSUInteger idx, BOOL *_Nonnull stop) { + if (window.windowID == sc->window) { + target_window = sc->shareable_content.windows[idx]; + *stop = TRUE; + } + return *stop; + }]; + + MACCAP_LOG(LOG_WARNING, "Window count: %lu", (unsigned long)[sc->shareable_content.windows count]); + if (!target_window && [sc->shareable_content.windows count] > 0) { + target_window = [sc->shareable_content.windows objectAtIndex:0]; sc->window = target_window.windowID; + MACCAP_LOG(LOG_WARNING, "Using fallback window. Window ID: %d", sc->window); } - content_filter = [[SCContentFilter alloc] - initWithDesktopIndependentWindow:target_window]; - if (target_window) { - [sc->stream_properties - setWidth:(size_t)target_window.frame.size.width]; - [sc->stream_properties - setHeight:(size_t)target_window.frame.size - .height]; + if (!target_window) { + MACCAP_ERR("Target window not found. Window ID: %d", sc->window); + os_sem_post(sc->shareable_content_available); + return false; } + MACCAP_LOG(LOG_WARNING, "Creating content filter for window ID: %d", target_window.windowID); + content_filter = [[SCContentFilter alloc] initWithDesktopIndependentWindow:target_window]; + + [sc->stream_properties setWidth:(size_t)target_window.frame.size.width]; + [sc->stream_properties setHeight:(size_t)target_window.frame.size.height]; + MACCAP_LOG(LOG_WARNING, "Window dimensions set to: %zux%zu", (size_t)target_window.frame.size.width, (size_t)target_window.frame.size.height); } break; + case ScreenCaptureApplicationStream: { + MACCAP_LOG(LOG_WARNING, "Initializing application stream capture"); SCDisplay *target_display = get_target_display(); + if (!target_display) { + MACCAP_ERR("Target display not found for application capture"); + os_sem_post(sc->shareable_content_available); + return false; + } + + MACCAP_LOG(LOG_WARNING, "Searching for application with bundle ID: %s", [sc->application_id UTF8String]); __block SCRunningApplication *target_application = nil; - { - [sc->shareable_content.applications - indexOfObjectPassingTest:^BOOL( - SCRunningApplication - *_Nonnull application, - NSUInteger idx, BOOL *_Nonnull stop) { - if ([application.bundleIdentifier - isEqualToString: - sc-> - application_id]) { - target_application = - sc->shareable_content - .applications - [idx]; - *stop = TRUE; - } - return *stop; - }]; + [sc->shareable_content.applications indexOfObjectPassingTest:^BOOL(SCRunningApplication *_Nonnull application, NSUInteger idx, BOOL *_Nonnull stop) { + if ([application.bundleIdentifier isEqualToString:sc->application_id]) { + target_application = sc->shareable_content.applications[idx]; + *stop = TRUE; + } + return *stop; + }]; + + if (!target_application) { + MACCAP_ERR("Target application not found with bundle ID: %s", [sc->application_id UTF8String]); + os_sem_post(sc->shareable_content_available); + return false; } - NSArray *target_application_array = [[NSArray alloc] - initWithObjects:target_application, nil]; + MACCAP_LOG(LOG_WARNING, "Creating content filter for application"); + NSArray *target_application_array = [[NSArray alloc] initWithObjects:target_application, nil]; NSArray *empty_array = [[NSArray alloc] init]; - content_filter = [[SCContentFilter alloc] - initWithDisplay:target_display - includingApplications:target_application_array - exceptingWindows:empty_array]; + + content_filter = [[SCContentFilter alloc] initWithDisplay:target_display includingApplications:target_application_array exceptingWindows:empty_array]; + [target_application_array release]; [empty_array release]; set_display_mode(sc, target_display); } break; } + os_sem_post(sc->shareable_content_available); + MACCAP_LOG(LOG_WARNING, "Configuring stream properties"); CGColorRef background = CGColorGetConstantColor(kCGColorClear); [sc->stream_properties setQueueDepth:8]; [sc->stream_properties setShowsCursor:!sc->hide_cursor]; [sc->stream_properties setColorSpaceName:kCGColorSpaceDisplayP3]; [sc->stream_properties setBackgroundColor:background]; - FourCharCode l10r_type = 0; - l10r_type = ('l' << 24) | ('1' << 16) | ('0' << 8) | 'r'; + + FourCharCode l10r_type = ('l' << 24) | ('1' << 16) | ('0' << 8) | 'r'; [sc->stream_properties setPixelFormat:l10r_type]; if (@available(macOS 13.0, *)) { + MACCAP_LOG(LOG_WARNING, "Configuring audio properties for macOS 13+"); #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; - [content_filter release]; - os_event_init(&sc->disp_finished, OS_EVENT_TYPE_MANUAL); - os_event_init(&sc->stream_start_completed, - OS_EVENT_TYPE_MANUAL); - return true; - } + } else if (sc->capture_type != ScreenCaptureWindowStream) { + MACCAP_LOG(LOG_WARNING, "Legacy OS path: initializing without audio"); + sc->disp = NULL; + [content_filter release]; + 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 - delegate:nil]; - + MACCAP_LOG(LOG_WARNING, "Initializing SCStream"); + sc->disp = [[SCStream alloc] initWithFilter:content_filter configuration:sc->stream_properties delegate:nil]; [content_filter release]; NSError *addStreamOutputError = nil; - BOOL did_add_output = [sc->disp addStreamOutput:sc->capture_delegate - type:SCStreamOutputTypeScreen - sampleHandlerQueue:nil - error:&addStreamOutputError]; + MACCAP_LOG(LOG_WARNING, "Adding stream output"); + BOOL did_add_output = [sc->disp addStreamOutput:sc->capture_delegate type:SCStreamOutputTypeScreen sampleHandlerQueue:nil error:&addStreamOutputError]; + if (!did_add_output) { - MACCAP_ERR( - "init_screen_stream: Failed to add stream output with error %s\n", - [[addStreamOutputError localizedFailureReason] - cStringUsingEncoding:NSUTF8StringEncoding]); + NSString *errorDescription = addStreamOutputError ? [addStreamOutputError localizedDescription] : @"Unknown error"; + MACCAP_ERR("Failed to add stream output with error: %s", [errorDescription cStringUsingEncoding:NSUTF8StringEncoding]); [addStreamOutputError release]; - return !did_add_output; + return false; } #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 130000 if (@available(macOS 13.0, *)) { - did_add_output = [sc->disp - addStreamOutput:sc->capture_delegate - type:SCStreamOutputTypeAudio - sampleHandlerQueue:nil - error:&addStreamOutputError]; + MACCAP_LOG(LOG_WARNING, "Adding audio stream output"); + did_add_output = [sc->disp addStreamOutput:sc->capture_delegate type:SCStreamOutputTypeAudio sampleHandlerQueue:nil error:&addStreamOutputError]; + if (!did_add_output) { - MACCAP_ERR( - "init_screen_stream: Failed to add audio stream output with error %s\n", - [[addStreamOutputError localizedFailureReason] - cStringUsingEncoding: - NSUTF8StringEncoding]); + NSString *errorDescription = addStreamOutputError ? [addStreamOutputError localizedDescription] : @"Unknown error"; + MACCAP_ERR("Failed to add audio stream output with error: %s", [errorDescription cStringUsingEncoding:NSUTF8StringEncoding]); [addStreamOutputError release]; - return !did_add_output; + return false; } } #endif + os_event_init(&sc->disp_finished, OS_EVENT_TYPE_MANUAL); os_event_init(&sc->stream_start_completed, OS_EVENT_TYPE_MANUAL); + MACCAP_LOG(LOG_WARNING, "Starting capture"); __block BOOL did_stream_start = false; - [sc->disp startCaptureWithCompletionHandler:^( - NSError *_Nullable error) { - did_stream_start = (BOOL)(error == nil); + [sc->disp startCaptureWithCompletionHandler:^(NSError *_Nullable error) { + did_stream_start = (error == nil); if (!did_stream_start) { - MACCAP_ERR( - "init_screen_stream: Failed to start capture with error %s\n", - [[error localizedFailureReason] - cStringUsingEncoding: - NSUTF8StringEncoding]); - // Clean up disp so it isn't stopped + NSString *errorDescription = error ? [error localizedDescription] : @"Unknown error"; + MACCAP_ERR("Failed to start capture with error: %s", [errorDescription cStringUsingEncoding:NSUTF8StringEncoding]); + + if ([error.domain isEqualToString:@"com.apple.ScreenCaptureKit.ErrorDomain"]) { + MACCAP_LOG(LOG_WARNING, "ScreenCaptureKit error domain detected, code: %ld", (long)error.code); + NSInteger permissionDeniedErrorCode = -3801; + if (error.code == permissionDeniedErrorCode) { + MACCAP_ERR("Permission denied error detected"); + dispatch_async(dispatch_get_main_queue(), ^{ + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Screen Recording Permission Denied"]; + [alert setInformativeText:@"Please grant Screen Recording permission in System Preferences > Security & Privacy > Privacy > Screen Recording, then restart the application."]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + [alert release]; + + NSURL *url = [NSURL fileURLWithPath:@"/System/Library/PreferencePanes/Security.prefPane"]; + [[NSWorkspace sharedWorkspace] openURL:url]; + }); + } + } + [sc->disp release]; sc->disp = NULL; + } else { + MACCAP_LOG(LOG_WARNING, "Capture started successfully"); } os_event_signal(sc->stream_start_completed); }]; - os_event_wait(sc->stream_start_completed); + MACCAP_LOG(LOG_WARNING, "Waiting for stream start completion"); + os_event_wait(sc->stream_start_completed); + MACCAP_LOG(LOG_WARNING, "Stream initialization %s", did_stream_start ? "succeeded" : "failed"); return did_stream_start; } diff --git a/plugins/obs-browser b/plugins/obs-browser index 630848ac1f484f..33ec6b80fcb387 160000 --- a/plugins/obs-browser +++ b/plugins/obs-browser @@ -1 +1 @@ -Subproject commit 630848ac1f484fcdab1160f00be11cbc7fd8227d +Subproject commit 33ec6b80fcb38754276bd3e8b84d0e6e42bdd03c diff --git a/plugins/obs-ndi b/plugins/obs-ndi index c9cb15ec683769..4c05702128c6b5 160000 --- a/plugins/obs-ndi +++ b/plugins/obs-ndi @@ -1 +1 @@ -Subproject commit c9cb15ec6837690ea94fcbe275c8323ad1652899 +Subproject commit 4c05702128c6b5d2f25096edc578e63e1b8ef600 diff --git a/slobs_CI/sentry-osx.py b/slobs_CI/sentry-osx.py index 1861a25417cbe5..c86630dfe0317a 100644 --- a/slobs_CI/sentry-osx.py +++ b/slobs_CI/sentry-osx.py @@ -1,15 +1,58 @@ import os -os.system('curl -sL https://sentry.io/get-cli/ | bash') +import subprocess + +def run_command(command): + print(f"Running command: {command}") + result = subprocess.run(command, shell=True, capture_output=True, text=True) + if result.stdout: + print(f"Output: {result.stdout}") + if result.stderr: + print(f"Error: {result.stderr}") + return result + +# Print all environment variables +print("Environment Variables:") +for key, value in os.environ.items(): + print(f"{key}={value}") + +# Run the command to install the Sentry CLI +run_command('curl -sL https://sentry.io/get-cli/ | bash') def process_sentry(directory): + print(f"Processing directory: {directory}") + if not os.path.exists(directory): + print(f"Error: Directory {directory} does not exist!") + return + + # List the contents of the directory + print(f"Listing contents of directory: {directory}") + run_command(f'ls -la {directory}') + for root, dirs, files in os.walk(directory): + print(f"Current directory: {root}") + print(f"Subdirectories: {dirs}") + print(f"Files: {files}") for file in files: if '.so' in file or '.dylib' in file or '.' not in file: path = os.path.join(root, file) - os.system("dsymutil " + path) - os.system("sentry-cli --auth-token ${SENTRY_AUTH_TOKEN} upload-dif --org streamlabs-desktop --project obs-server " + path + ".dSYM/Contents/Resources/DWARF/" + file) - os.system("dsymutil " + path) - os.system("sentry-cli --auth-token ${SENTRY_AUTH_TOKEN} upload-dif --org streamlabs-desktop --project obs-server-preview " + path + ".dSYM/Contents/Resources/DWARF/" + file) + print(f"Processing file: {path}") + + # Run dsymutil on the file + run_command(f"dsymutil {path}") + + # Upload the debug file to Sentry + sentry_command = f"sentry-cli --auth-token {os.environ.get('SENTRY_AUTH_TOKEN', '')} upload-dif --org streamlabs-desktop --project obs-server {path}.dSYM/Contents/Resources/DWARF/{file}" + run_command(sentry_command) + + # Repeat the upload for the second project + sentry_command_preview = f"sentry-cli --auth-token {os.environ.get('SENTRY_AUTH_TOKEN', '')} upload-dif --org streamlabs-desktop --project obs-server-preview {path}.dSYM/Contents/Resources/DWARF/{file}" + run_command(sentry_command_preview) + +# Check if the required environment variables are set +required_env_vars = ['PWD', 'InstallPath', 'SENTRY_AUTH_TOKEN'] +for var in required_env_vars: + if var not in os.environ: + print(f"Warning: Environment variable {var} is not set!") # Upload obs debug files -process_sentry(os.path.join(os.environ['PWD'], os.environ['InstallPath'])) \ No newline at end of file +process_sentry(os.path.join(os.environ.get('PWD', ''), "build" , os.environ.get('InstallPath', '')))