Use ScreenCaptureKit under macOS 15 and above

This commit is contained in:
Paulchen-Panther 2024-11-16 16:18:40 +01:00
parent 2d61ae644d
commit b76ccd200b
5 changed files with 129 additions and 39 deletions

@ -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"
}

@ -26,6 +26,8 @@
<string>APPL</string>
<key>LSUIElement</key>
<string>1</string>
<key>NSCameraUsageDescription</key>
<string>Hyperion uses this access to record screencasts</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>Source Code</key>

@ -1,6 +1,6 @@
#pragma once
// OSX includes
// CoreGraphics
#include <CoreGraphics/CoreGraphics.h>
// Utils includes

@ -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
"$<LINK_LIBRARY:FRAMEWORK,CoreGraphics.framework>"
)
file(WRITE ${CMAKE_BINARY_DIR}/tmp/SDK15Available.c
"#include <AvailabilityMacros.h>
#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 "$<LINK_LIBRARY:WEAK_FRAMEWORK,ScreenCaptureKit.framework>")
endif()

@ -1,10 +1,16 @@
// STL includes
#include <cassert>
#include <iostream>
#include <mutex>
// Local includes
// Header
#include <grabber/osx/OsxFrameGrabber.h>
// ScreenCaptureKit
#if defined(SDK_15_AVAILABLE)
#include <ScreenCaptureKit/ScreenCaptureKit.h>
#endif
//Qt
#include <QJsonObject>
#include <QJsonArray>
@ -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<ColorRgb> & 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<int>(dspWidth),
static_cast<int>(dspHeight),
static_cast<int>(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<int>(CGImageGetWidth(dispImage)), static_cast<int>(CGImageGetHeight(dispImage)), static_cast<int>(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);