屏幕捕捉权限检测
#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;
}
}
还有一段代码:
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);
}
参考:Detecting screen recording settings on macOS Catalina
官方资料,WWDC 2019 中 Advances in macOS Security 介绍screen capture权限(14:17-)的视频对应的台词:
Beginning in macOS Mojave, users have to consent before an app can access the camera or microphone.
And then macOS Catalina further requires consent to record the contents of your screen or the keys that you type on your keyboard.
And this is important because just like we don’t want people shoulder surfing, looking over our shoulder to see what we’re doing or are typing, we don’t want apps to eavesdrop on our contact information, our bank details or passwords and so on, whether that’s intentionally or accidentally.
So how do we do that? Let’s start by looking at screen recording.
Here’s a simple example of using CGDisplayStream to record the contents of a display in real time.
On macOS Catalina, the first time this app runs and this call to create the CGDisplayStream is made that’ll return nil, and a dialogue is displayed directing the user to the security and privacy preference pane, where the user can approve the app to record the screen if that’s what they want to do.
The same is true when reading the contents of other apps windows.
For example, here’s a function that saves the contents of a window to an image on disk.
Now, notably, the call to CGWindowListCreateImage can return nil, if passed the window ID that does not belong to the calling app, and doesn’t belong to the desktop background image or to the menu bar.
And I like to emphasize, this is the background image, it doesn’t include the icons or names of any files on the desktop.
Again, an authorization dialog, directing the user to approve the app for screen recording, may be displayed.
And I say may, because the dialog is only displayed the first time CGWindowListCreateImage or CGDisplayStream fails due to a lack of approval for screen recording.
Another topic I’d like to cover that’s peripherally related to screen recording is window metadata.
Apps can query metadata about the windows that are on or off screen using the core graphics CGWindowListCopyWindowInfo function.
The metadata returned includes the size and position of the window and the unique window identifier, as well as the name and process of identifier of the app that owns the window.
However, the window name and sharing state are not available, unless the user has preapproved the app for screen recording.
And this is because some apps put sensitive data such as account names or more likely web page URLs in the window’s name.
And CGWindowListCopyWindowInfo never triggers an authorization prompt, instead it filters the set of metadata that it returns to the caller.
So, if in your app you depend on getting the window name, for example, and you find that the metadata that’s being returned doesn’t include the window name, you may want to alert the user and direct them to the privacy-- security and privacy preference pane.
So, here’s an example of a function that gets the unique window identifiers for the desktop background image for each display.
And once again, the background image doesn’t include the icons on the desktop.
This function first gets a list of all of the windows on the screen, using the CGWindowListCopyWindowInfo function.
But then, it gets the window level or Z order that core graphics uses for the desktop background image window.
Then it filters that whole window list to just those windows at the desktop background window level.
So if you look on the internet, you’ll find a lot of code samples filter by the kCG window name which, since the window names may contain privacy sensitive information, would require the app to be preapproved for screen recording.
However, by identifying the desktop background windows by their window level rather than by the window name, this works regardless of whether the user has preapproved the app for screen recording.
And this is an example of how a small change in your-- the design of your app can make a big difference in the user experience.
So that’s how macOS Catalina protects the contents of your screen from being recorded without your permission.
Apps can freely record the contents of their own windows, the menu bar and the desktop background image.
But the user must use the security and privacy preference pane to preapprove apps to record the entire screen or the contents of windows other than their own.
另一个资料 mac开发-10.15检测屏幕录制权限
参考 killgxlin的一段代码:
BOOL canRecordScreen()
{
if (@available(macOS 10.15, *)) {
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger numberOfWindows = CFArrayGetCount(windowList);
NSUInteger numberOfWindowsWithName = 0;
for (int idx = 0; idx < numberOfWindows; idx++) {
NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, idx);
NSString *windowName = windowInfo[(id)kCGWindowName];
if (windowName) {
numberOfWindowsWithName++;
} else {
//no kCGWindowName detected -> not enabled
break; //breaking early, numberOfWindowsWithName not increased
}
}
CFRelease(windowList);
return numberOfWindows == numberOfWindowsWithName;
}
return YES;
}
void openScreenCaptureSetting(){
NSString *urlString = @"x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:urlString]];
}
NSString * runCommand(NSString *commandToRun)
{
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/bin/sh"];
NSArray *arguments = [NSArray arrayWithObjects:
@"-c" ,
[NSString stringWithFormat:@"%@", commandToRun],
nil];
NSLog(@"run command:%@", commandToRun);
[task setArguments:arguments];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle *file = [pipe fileHandleForReading];
[task launch];
NSData *data = [file readDataToEndOfFile];
NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return output;
}
void openTipsDialog(void) {
NSAlert *alert = [[NSAlert alloc] init];
[alert addButtonWithTitle:@"好的"];
[alert addButtonWithTitle:@"取消"];
[alert setMessageText:@"设置权限"];
[alert setInformativeText:@"请设置屏幕捕捉权限,重启SDK Demo"];
[alert setAlertStyle:NSWarningAlertStyle];
if ([alert runModal] == NSAlertFirstButtonReturn) {
openScreenCaptureSetting();
}
}
bool authRecordScreen()
{
if (!canRecordScreen()) {
runCommand(@"tccutil reset ScreenCapture com.netease.nmc.NERTCDemo-Mac");
openTipsDialog();
return false;
}
return true;
}