From b76ccd200b4a4f172ef55b79ae3fd6e1b76bf9a1 Mon Sep 17 00:00:00 2001 From: Paulchen-Panther <16664240+Paulchen-Panther@users.noreply.github.com> Date: Sat, 16 Nov 2024 16:18:40 +0100 Subject: [PATCH] Use ScreenCaptureKit under macOS 15 and above --- CMakePresets.json | 10 -- cmake/osxbundle/Info.plist.in | 2 + include/grabber/osx/OsxFrameGrabber.h | 2 +- libsrc/grabber/osx/CMakeLists.txt | 20 ++- ...OsxFrameGrabber.cpp => OsxFrameGrabber.mm} | 134 ++++++++++++++---- 5 files changed, 129 insertions(+), 39 deletions(-) rename libsrc/grabber/osx/{OsxFrameGrabber.cpp => OsxFrameGrabber.mm} (56%) diff --git a/CMakePresets.json b/CMakePresets.json index 83ed2dc3..1f0dc222 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -51,7 +51,6 @@ "name": "hyperion-bare-minimum", "hidden": true, "cacheVariables": { - // Disable Grabbers "ENABLE_AMLOGIC": "OFF", "ENABLE_DDA": "OFF", "ENABLE_DISPMANX": "OFF", @@ -64,8 +63,6 @@ "ENABLE_X11": "OFF", "ENABLE_XCB": "OFF", "ENABLE_AUDIO": "OFF", - - // LED-Devices "ENABLE_DEV_FTDI": "OFF", "ENABLE_DEV_NETWORK": "OFF", "ENABLE_DEV_SERIAL": "ON", @@ -73,23 +70,16 @@ "ENABLE_DEV_TINKERFORGE": "OFF", "ENABLE_DEV_USB_HID": "OFF", "ENABLE_DEV_WS281XPWM": "OFF", - - // Disable Input Servers "ENABLE_BOBLIGHT_SERVER": "OFF", "ENABLE_CEC": "OFF", "ENABLE_FLATBUF_SERVER": "OFF", "ENABLE_PROTOBUF_SERVER": "OFF", - - // Disable Output Connectors "ENABLE_FORWARDER": "OFF", "ENABLE_FLATBUF_CONNECT": "OFF", - - // Disable Services "ENABLE_EXPERIMENTAL": "OFF", "ENABLE_MDNS": "OFF", "ENABLE_REMOTE_CTL": "OFF", "ENABLE_EFFECTENGINE": "OFF", - "ENABLE_JSONCHECKS": "ON", "ENABLE_DEPLOY_DEPENDENCIES": "ON" } diff --git a/cmake/osxbundle/Info.plist.in b/cmake/osxbundle/Info.plist.in index 9774dd79..d6c77d50 100644 --- a/cmake/osxbundle/Info.plist.in +++ b/cmake/osxbundle/Info.plist.in @@ -26,6 +26,8 @@ APPL LSUIElement 1 + NSCameraUsageDescription + Hyperion uses this access to record screencasts NSHumanReadableCopyright ${MACOSX_BUNDLE_COPYRIGHT} Source Code diff --git a/include/grabber/osx/OsxFrameGrabber.h b/include/grabber/osx/OsxFrameGrabber.h index afb430fc..1adc3696 100644 --- a/include/grabber/osx/OsxFrameGrabber.h +++ b/include/grabber/osx/OsxFrameGrabber.h @@ -1,6 +1,6 @@ #pragma once -// OSX includes +// CoreGraphics #include // Utils includes diff --git a/libsrc/grabber/osx/CMakeLists.txt b/libsrc/grabber/osx/CMakeLists.txt index 7d18e8d3..a5f01acb 100644 --- a/libsrc/grabber/osx/CMakeLists.txt +++ b/libsrc/grabber/osx/CMakeLists.txt @@ -1,10 +1,28 @@ add_library(osx-grabber ${CMAKE_SOURCE_DIR}/include/grabber/osx/OsxFrameGrabber.h ${CMAKE_SOURCE_DIR}/include/grabber/osx/OsxWrapper.h - ${CMAKE_SOURCE_DIR}/libsrc/grabber/osx/OsxFrameGrabber.cpp + ${CMAKE_SOURCE_DIR}/libsrc/grabber/osx/OsxFrameGrabber.mm ${CMAKE_SOURCE_DIR}/libsrc/grabber/osx/OsxWrapper.cpp ) target_link_libraries(osx-grabber hyperion + "$" ) + +file(WRITE ${CMAKE_BINARY_DIR}/tmp/SDK15Available.c + "#include + #if __MAC_OS_X_VERSION_MAX_ALLOWED < 150000 + #error __MAC_OS_X_VERSION_MAX_ALLOWED < 150000 + #endif + int main(int argc, char** argv) + { + return 0; + }" +) + +try_compile(SDK_15_AVAILABLE ${CMAKE_BINARY_DIR} SOURCES ${CMAKE_BINARY_DIR}/tmp/SDK15Available.c) +if(SDK_15_AVAILABLE) + target_compile_definitions(osx-grabber PRIVATE SDK_15_AVAILABLE) + target_link_libraries(osx-grabber "$") +endif() diff --git a/libsrc/grabber/osx/OsxFrameGrabber.cpp b/libsrc/grabber/osx/OsxFrameGrabber.mm similarity index 56% rename from libsrc/grabber/osx/OsxFrameGrabber.cpp rename to libsrc/grabber/osx/OsxFrameGrabber.mm index 916402c2..ef537c16 100644 --- a/libsrc/grabber/osx/OsxFrameGrabber.cpp +++ b/libsrc/grabber/osx/OsxFrameGrabber.mm @@ -1,10 +1,16 @@ // STL includes #include #include +#include -// Local includes +// Header #include +// ScreenCaptureKit +#if defined(SDK_15_AVAILABLE) +#include +#endif + //Qt #include #include @@ -15,9 +21,70 @@ namespace { const bool verbose = false; } //End of constants +#if defined(SDK_15_AVAILABLE) + static CGImageRef capture15(CGDirectDisplayID id, CGRect diIntersectDisplayLocal) + { + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + __block CGImageRef image1 = nil; + [SCShareableContent getShareableContentWithCompletionHandler:^(SCShareableContent* content, NSError* error) + { + @autoreleasepool + { + if (error || !content) + { + dispatch_semaphore_signal(semaphore); + return; + } + + SCDisplay* target = nil; + for (SCDisplay *display in content.displays) + { + if (display.displayID == id) + { + target = display; + break; + } + } + if (!target) + { + dispatch_semaphore_signal(semaphore); + return; + } + + SCContentFilter* filter = [[SCContentFilter alloc] initWithDisplay:target excludingWindows:@[]]; + SCStreamConfiguration* config = [[SCStreamConfiguration alloc] init]; + config.queueDepth = 5; + config.sourceRect = diIntersectDisplayLocal; + config.scalesToFit = false; + config.captureResolution = SCCaptureResolutionBest; + + CGDisplayModeRef modeRef = CGDisplayCopyDisplayMode(id); + double sysScale = CGDisplayModeGetPixelWidth(modeRef) / CGDisplayModeGetWidth(modeRef); + config.width = diIntersectDisplayLocal.size.width * sysScale; + config.height = diIntersectDisplayLocal.size.height * sysScale; + + [SCScreenshotManager captureImageWithFilter:filter + configuration:config + completionHandler:^(CGImageRef img, NSError* error) + { + if (!error) + { + image1 = CGImageCreateCopyWithColorSpace(img, CGColorSpaceCreateDeviceRGB()); + } + dispatch_semaphore_signal(semaphore); + }]; + } + }]; + + dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + dispatch_release(semaphore); + return image1; + } +#endif + OsxFrameGrabber::OsxFrameGrabber(int display) : Grabber("GRABBER-OSX") - , _screenIndex(display) + , _screenIndex(display) { _isEnabled = false; _useImageResampler = true; @@ -31,6 +98,15 @@ bool OsxFrameGrabber::setupDisplay() { bool rc (false); +#if defined(SDK_15_AVAILABLE) + if (!CGPreflightScreenCaptureAccess()) + { + if(!CGRequestScreenCaptureAccess()) + Error(_log, "Screen capture permission required to start the grabber"); + return false; + } +#endif + rc = setDisplayIndex(_screenIndex); return rc; @@ -41,39 +117,38 @@ int OsxFrameGrabber::grabFrame(Image & image) int rc = 0; if (_isEnabled && !_isDeviceInError) { - CGImageRef dispImage; - CFDataRef imgData; - unsigned char * pImgData; - unsigned dspWidth; - unsigned dspHeight; - dispImage = CGDisplayCreateImage(_display); + #if defined(SDK_15_AVAILABLE) + dispImage = capture15(_display, CGDisplayBounds(_display)); + #else + dispImage = CGDisplayCreateImageForRect(_display, CGDisplayBounds(_display)); + #endif // display lost, use main if (dispImage == nullptr && _display != 0) { - dispImage = CGDisplayCreateImage(kCGDirectMainDisplay); - // no displays connected, return - if (dispImage == nullptr) - { - Error(_log, "No display connected..."); - return -1; - } + #if defined(SDK_15_AVAILABLE) + dispImage = capture15(kCGDirectMainDisplay, CGDisplayBounds(kCGDirectMainDisplay)); + #else + dispImage = CGDisplayCreateImageForRect(kCGDirectMainDisplay, CGDisplayBounds(kCGDirectMainDisplay)); + #endif } - imgData = CGDataProviderCopyData(CGImageGetDataProvider(dispImage)); - pImgData = (unsigned char*) CFDataGetBytePtr(imgData); - dspWidth = CGImageGetWidth(dispImage); - dspHeight = CGImageGetHeight(dispImage); - _imageResampler.processImage( pImgData, - static_cast(dspWidth), - static_cast(dspHeight), - static_cast(CGImageGetBytesPerRow(dispImage)), - PixelFormat::BGR32, - image); + // no displays connected, return + if (dispImage == nullptr) + { + Error(_log, "No display connected..."); + return -1; + } + + CFDataRef imgData = CGDataProviderCopyData(CGImageGetDataProvider(dispImage)); + if (imgData != nullptr) + { + _imageResampler.processImage((uint8_t *)CFDataGetBytePtr(imgData), static_cast(CGImageGetWidth(dispImage)), static_cast(CGImageGetHeight(dispImage)), static_cast(CGImageGetBytesPerRow(dispImage)), PixelFormat::BGR32, image); + CFRelease(imgData); + } - CFRelease(imgData); CGImageRelease(dispImage); } @@ -108,7 +183,12 @@ bool OsxFrameGrabber::setDisplayIndex(int index) { _display = activeDspys[_screenIndex]; - image = CGDisplayCreateImage(_display); + #if defined(SDK_15_AVAILABLE) + image = capture15(_display, CGDisplayBounds(_display)); + #else + image = CGDisplayCreateImageForRect(_display, CGDisplayBounds(_display)); + #endif + if(image == nullptr) { setEnabled(false);