Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting screen recording settings on macOS Catalina

What's is a reliable way to detect if user has enabled this API?

CGWindowListCreateImage returns a valid object even if screen recording API is disabled. There are multiple combinations possible (kCGWindowListOptionIncludingWindow, kCGWindowListOptionOnScreenBelowWindow) and only some will return NULL.

- (CGImageRef)createScreenshotImage
{
    NSWindow *window = [[self view] window];
    NSRect rect = [window frame];

    rect.origin.y = NSHeight([[window screen] frame]) - NSMaxY([window frame]);
    CGImageRef screenshot = CGWindowListCreateImage(
                                                    rect,
                                                    kCGWindowListOptionIncludingWindow,
                                                    //kCGWindowListOptionOnScreenBelowWindow,
                                                    0,//(CGWindowID)[window windowNumber],
                                                    kCGWindowImageBoundsIgnoreFraming);//kCGWindowImageDefault
    return screenshot;
}

The only reliable way is through CGDisplayStreamCreate which is risky as Apple always changes privacy settings every year.

   - (BOOL)canRecordScreen
    {
        if (@available(macOS 10.15, *)) {
            CGDisplayStreamRef stream = CGDisplayStreamCreate(CGMainDisplayID(), 1, 1, kCVPixelFormatType_32BGRA, nil, ^(CGDisplayStreamFrameStatus status, uint64_t displayTime, IOSurfaceRef frameSurface, CGDisplayStreamUpdateRef updateRef) {
                ;
            });
            BOOL canRecord = stream != NULL;
            if (stream) { 
              CFRelease(stream); 
            }
            return canRecord;
        } else {
            return YES;
        }
    }
like image 972
Marek H Avatar asked Jun 14 '19 11:06

Marek H


People also ask

Where is Screen Recording settings on Mac?

On your Mac, choose Apple menu > System Preferences, click Security & Privacy , then click Privacy. Select Screen Recording. Select the checkbox next to an app to allow it to record your screen.

How do I change my Screen Recording settings?

Swipe down twice from the top of your screen. Tap Screen record . You might need to swipe right to find it. If it's not there, tap Edit and drag Screen record to your Quick Settings.

How do I change Screen Recording settings in QuickTime?

With QuickTime Player open, go to File > New Screen Recording. A Screen Recording prompt will appear. Before you click the record button, Click the down arrow next to the button to ensure you are set up properly. Microphone Options: If you prefer to have your voice recorded, choose the microphone you prefer.


3 Answers

All of the solutions presented here have a flaw in one way or another. The root of the problem is that there's no correlation between your permission to know about a window (via the name in the window list), your permission to know about the process owner of the window (such as WindowServer and Dock). Your permission to view the pixels on screen is a combination of two sparse sets of information.

Here is a heuristic that covers all the cases as of macOS 10.15.1:

BOOL canRecordScreen = YES;
if (@available(macOS 10.15, *)) {
    canRecordScreen = NO;
    NSRunningApplication *runningApplication = NSRunningApplication.currentApplication;
    NSNumber *ourProcessIdentifier = [NSNumber numberWithInteger:runningApplication.processIdentifier];

    CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
    NSUInteger numberOfWindows = CFArrayGetCount(windowList);
    for (int index = 0; index < numberOfWindows; index++) {
        // get information for each window
        NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, index);
        NSString *windowName = windowInfo[(id)kCGWindowName];
        NSNumber *processIdentifier = windowInfo[(id)kCGWindowOwnerPID];

        // don't check windows owned by this process
        if (! [processIdentifier isEqual:ourProcessIdentifier]) {
            // get process information for each window
            pid_t pid = processIdentifier.intValue;
            NSRunningApplication *windowRunningApplication = [NSRunningApplication runningApplicationWithProcessIdentifier:pid];
            if (! windowRunningApplication) {
                // ignore processes we don't have access to, such as WindowServer, which manages the windows named "Menubar" and "Backstop Menubar"
            }
            else {
                NSString *windowExecutableName = windowRunningApplication.executableURL.lastPathComponent;
                if (windowName) {
                    if ([windowExecutableName isEqual:@"Dock"]) {
                        // ignore the Dock, which provides the desktop picture
                    }
                    else {
                        canRecordScreen = YES;
                        break;
                    }
                }
            }
        }
    }
    CFRelease(windowList);
}

If canRecordScreen is not set, you'll need to put up some kind of dialog that warns the user that they'll only be able to see the menubar, desktop picture, and the app's own windows. Here's how we presented it in our app xScope.

And yes, I'm still bitter that these protections were introduced with little regard to usability.

like image 199
chockenberry Avatar answered Oct 17 '22 18:10

chockenberry


Apple provides direct low level api to check for access and grant access. No need to use tricky workarounds.

/* Checks whether the current process already has screen capture access */
@available(macOS 10.15, *)
public func CGPreflightScreenCaptureAccess() -> Bool

Use the above functions to check for screen capture access.

if access is not given use the below function to prompt for access

/* Requests event listening access if absent, potentially prompting */
@available(macOS 10.15, *)
public func CGRequestScreenCaptureAccess() -> Bool

Screenshot taken from documentation

like image 42
Somesh Karthik Avatar answered Oct 17 '22 17:10

Somesh Karthik


@marek-h posted a good example that can detect the screen recording setting without showing privacy alert. Btw, @jordan-h mentioned that this solution doesn't work when the app presents an alert via beginSheetModalForWindow.

I found that SystemUIServer process is always creating some windows with names: AppleVolumeExtra, AppleClockExtra, AppleBluetoothExtra ...

We can't get the names of these windows, before the screen recording is enabled in Privacy preferences. And when we can get one of these names at least, then it means that the user has enabled screen recording.

So we can check the names of the windows (created by SystemUIServer process) to detect the screen recording preference, and it works fine on macOS Catalina.

#include <AppKit/AppKit.h>
#include <libproc.h>

bool isScreenRecordingEnabled()
{
    if (@available(macos 10.15, *)) {
        bool bRet = false;
        CFArrayRef list = CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID);
        if (list) {
            int n = (int)(CFArrayGetCount(list));
            for (int i = 0; i < n; i++) {
                NSDictionary* info = (NSDictionary*)(CFArrayGetValueAtIndex(list, (CFIndex)i));
                NSString* name = info[(id)kCGWindowName];
                NSNumber* pid = info[(id)kCGWindowOwnerPID];
                if (pid != nil && name != nil) {
                    int nPid = [pid intValue];
                    char path[PROC_PIDPATHINFO_MAXSIZE+1];
                    int lenPath = proc_pidpath(nPid, path, PROC_PIDPATHINFO_MAXSIZE);
                    if (lenPath > 0) {
                        path[lenPath] = 0;
                        if (strcmp(path, "/System/Library/CoreServices/SystemUIServer.app/Contents/MacOS/SystemUIServer") == 0) {
                            bRet = true;
                            break;
                        }
                    }
                }
            }
            CFRelease(list);
        }
        return bRet;
    } else {
        return true;
    }
}
like image 4
Fred Zhang Avatar answered Oct 17 '22 18:10

Fred Zhang