在 macOS Catalina 上检测屏幕录制设置
Detecting screen recording settings on macOS Catalina
检测用户是否启用此功能的可靠方法是什么API?
CGWindowListCreateImage
return 即使禁用屏幕录制 API 也是一个有效的对象。有多种可能的组合(kCGWindowListOptionIncludingWindow
、kCGWindowListOptionOnScreenBelowWindow
),只有一些组合会 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;
}
唯一可靠的方法是通过 CGDisplayStreamCreate
,这是有风险的,因为 Apple 每年都会更改隐私设置。
- (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;
}
}
我不知道 API 专门用于获取屏幕录制权限状态。除了创建 CGDisplayStream
并检查 nil 之外,Advances in macOS Security WWDC 演示文稿还提到除非获得许可,否则不会返回 CGWindowListCopyWindowInfo()
API 中的某些元数据。所以像这样的东西似乎确实有效,尽管它有同样的问题依赖于该函数的实现细节:
private func canRecordScreen() -> Bool {
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: AnyObject]] else { return false }
return windows.allSatisfy({ window in
let windowName = window[kCGWindowName as String] as? String
return windowName != nil
})
}
截至 11 月 19 日 有正确答案。
正如@onelittlefish 指出的那样,如果用户未在隐私窗格中启用屏幕录制访问,则省略 kCGWindowName
。此方法也不会触发隐私警报。
- (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;
}
@marek-h 发布了一个很好的示例,可以在不显示隐私警报的情况下检测屏幕录制设置。
顺便说一句,@jordan-h 提到当应用程序通过 beginSheetModalForWindow 显示警报时,此解决方案不起作用。
我发现 SystemUIServer 进程总是创建一些 windows 名称:AppleVolumeExtra、AppleClockExtra、AppleBluetoothExtra ...
在隐私首选项中启用屏幕录制之前,我们无法获取这些 windows 的名称。而当我们至少能得到其中一个名字时,就说明用户开启了屏幕录制。
所以我们可以检查 windows(由 SystemUIServer 进程创建)的名称来检测屏幕录制首选项,它在 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;
}
}
这里介绍的所有解决方案都存在这样或那样的缺陷。问题的根源在于,您了解 window 的权限(通过 window 列表中的名称)与您了解 [=23= 的进程所有者的权限之间没有关联](例如 WindowServer 和 Dock)。您查看屏幕像素的权限是两组稀疏信息的组合。
这是一个启发式算法,涵盖了 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);
}
如果未设置 canRecordScreen
,您需要设置某种对话框,警告用户他们只能看到菜单栏、桌面图片和应用程序自己的 windows。这里是 how we presented it in our app xScope.
是的,我仍然对 little regard to usability 引入的这些保护感到痛苦。
以上答案无法正常工作。以下是正确答案。
private var canRecordScreen : Bool {
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: AnyObject]] else { return false }
return windows.allSatisfy({ window in
let windowName = window[kCGWindowName as String] as? String
let isSharingEnabled = window[kCGWindowSharingState as String] as? Int
return windowName != nil || isSharingEnabled == 1
})
}
最赞的答案并不完全正确,他遗漏了一些句子,比如共享状态。
我们可以在WWDC中找到答案(https://developer.apple.com/videos/play/wwdc2019/701/?time=1007)
以下是 WWDC 的一些摘录:
window 名称和共享状态不可用,除非用户预先批准应用程序进行屏幕录制。这是因为某些应用程序将敏感数据(例如帐户名或更可能的网页 URL)放入 window 的名称中。
- (BOOL)ScreeningRecordPermissionCheck {
if (@available(macOS 10.15, *)) {
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger numberOfWindows = CFArrayGetCount(windowList);
NSUInteger numberOfWindowsWithInfoGet = 0;
for (int idx = 0; idx < numberOfWindows; idx++) {
NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, idx);
NSString *windowName = windowInfo[(id)kCGWindowName];
NSNumber* sharingType = windowInfo[(id)kCGWindowSharingState];
if (windowName || kCGWindowSharingNone != sharingType.intValue) {
numberOfWindowsWithInfoGet++;
} else {
NSNumber* pid = windowInfo[(id)kCGWindowOwnerPID];
NSString* appName = windowInfo[(id)kCGWindowOwnerName];
NSLog(@"windowInfo get Fail pid:%lu appName:%@", pid.integerValue, appName);
}
}
CFRelease(windowList);
if (numberOfWindows == numberOfWindowsWithInfoGet) {
return YES;
} else {
return NO;
}
}
return YES;
}
从 MacOS 10.15.7 开始,为可见 windows 获取 window-names 的试探法并不总是有效,因此知道我们拥有 screen-capture 权限。有时我们只是找不到有效的 windows 我们可以查询,并且会错误地推断我们没有权限。
然而,我找到了另一种直接查询(使用sqlite)Apple TCC 数据库的方法——权限持久化的模型。 screen-recording 权限可在“系统级”TCC 数据库(位于 /Library/Application Support/com.apple.TCC/TCC.db
中)中找到。如果您使用 sqlite 打开数据库并查询:SELECT allowed FROM access WHERE client="com.myCompany.myApp" AND service="kTCCServiceScreenCapture"
您将得到答案。
与其他答案相比有两个缺点:
- 要打开此 TCC.db 数据库,您的应用程序必须具有“全盘访问权限”权限。它不需要 运行 和 'root' 权限,如果您没有“全盘访问权限”,root 权限也无济于事。
- 到 运行 大约需要 15 毫秒,这比查询 window 列表慢。
好的方面 -- 它是对实际事物的直接查询,不依赖于任何 windows 或查询时存在的进程。
下面是执行此操作的一些代码草案:
NSString *client = @"com.myCompany.myApp";
sqlite3 *tccDb = NULL;
sqlite3_stmt *statement = NULL;
NSString *pathToSystemTCCDB = @"/Library/Application Support/com.apple.TCC/TCC.db";
const char *pathToDBFile = [pathToSystemTCCDB fileSystemRepresentation];
if (sqlite3_open(pathToDBFile, &tccDb) != SQLITE_OK)
return nil;
const char *query = [[NSString stringWithFormat: @"SELECT allowed FROM access WHERE client=\"%@\" AND service=\"kTCCServiceScreenCapture\"",client] UTF8String];
if (sqlite3_prepare_v2(tccDb, query , -1, &statement, nil) != SQLITE_OK)
return nil;
BOOL allowed = NO;
while (sqlite3_step(statement) == SQLITE_ROW)
allowed |= (sqlite3_column_int(statement, 0) == 1);
if (statement)
sqlite3_finalize(statement);
if (tccDb)
sqlite3_close(tccDb);
return @(allowed);
}
Apple 提供直接低级别 api 来检查访问权限和授予访问权限。无需使用棘手的解决方法。
/* Checks whether the current process already has screen capture access */
@available(macOS 10.15, *)
public func CGPreflightScreenCaptureAccess() -> Bool
使用上述功能检查屏幕捕获权限。
如果未授予访问权限,请使用以下函数提示访问权限
/* Requests event listening access if absent, potentially prompting */
@available(macOS 10.15, *)
public func CGRequestScreenCaptureAccess() -> Bool
Screenshot taken from documentation
为我工作。
代码来自:https://gist.github.com/code4you2021/270859c71f90720d880ccb2474f4e7df
import Cocoa
struct ScreenRecordPermission {
static var hasPermission: Bool {
permissionCheck()
}
static func permissionCheck() -> Bool {
if #available(macOS 10.15, *) {
let runningApplication = NSRunningApplication.current
let processIdentifier = runningApplication.processIdentifier
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID)
as? [[String: AnyObject]],
let _ = windows.first(where: { window -> Bool in
guard let windowProcessIdentifier = (window[kCGWindowOwnerPID as String] as? Int).flatMap(pid_t.init),
windowProcessIdentifier != processIdentifier,
let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier),
windowRunningApplication.executableURL?.lastPathComponent != "Dock",
let _ = window[String(kCGWindowName)] as? String
else {
return false
}
return true
})
else {
return false
}
}
return true
}
static func requestPermission() {
if #available(macOS 10.15, *) {
CGWindowListCreateImage(CGRect(x: 0, y: 0, width: 1, height: 1), .optionOnScreenOnly, kCGNullWindowID, [])
}
}
}
# how to use
# print("hasPermission: ", ScreenRecordPermission.hasPermission)
检测用户是否启用此功能的可靠方法是什么API?
CGWindowListCreateImage
return 即使禁用屏幕录制 API 也是一个有效的对象。有多种可能的组合(kCGWindowListOptionIncludingWindow
、kCGWindowListOptionOnScreenBelowWindow
),只有一些组合会 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;
}
唯一可靠的方法是通过 CGDisplayStreamCreate
,这是有风险的,因为 Apple 每年都会更改隐私设置。
- (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;
}
}
我不知道 API 专门用于获取屏幕录制权限状态。除了创建 CGDisplayStream
并检查 nil 之外,Advances in macOS Security WWDC 演示文稿还提到除非获得许可,否则不会返回 CGWindowListCopyWindowInfo()
API 中的某些元数据。所以像这样的东西似乎确实有效,尽管它有同样的问题依赖于该函数的实现细节:
private func canRecordScreen() -> Bool {
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: AnyObject]] else { return false }
return windows.allSatisfy({ window in
let windowName = window[kCGWindowName as String] as? String
return windowName != nil
})
}
截至 11 月 19 日
正如@onelittlefish 指出的那样,如果用户未在隐私窗格中启用屏幕录制访问,则省略 kCGWindowName
。此方法也不会触发隐私警报。
- (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;
}
@marek-h 发布了一个很好的示例,可以在不显示隐私警报的情况下检测屏幕录制设置。 顺便说一句,@jordan-h 提到当应用程序通过 beginSheetModalForWindow 显示警报时,此解决方案不起作用。
我发现 SystemUIServer 进程总是创建一些 windows 名称:AppleVolumeExtra、AppleClockExtra、AppleBluetoothExtra ...
在隐私首选项中启用屏幕录制之前,我们无法获取这些 windows 的名称。而当我们至少能得到其中一个名字时,就说明用户开启了屏幕录制。
所以我们可以检查 windows(由 SystemUIServer 进程创建)的名称来检测屏幕录制首选项,它在 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;
}
}
这里介绍的所有解决方案都存在这样或那样的缺陷。问题的根源在于,您了解 window 的权限(通过 window 列表中的名称)与您了解 [=23= 的进程所有者的权限之间没有关联](例如 WindowServer 和 Dock)。您查看屏幕像素的权限是两组稀疏信息的组合。
这是一个启发式算法,涵盖了 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);
}
如果未设置 canRecordScreen
,您需要设置某种对话框,警告用户他们只能看到菜单栏、桌面图片和应用程序自己的 windows。这里是 how we presented it in our app xScope.
是的,我仍然对 little regard to usability 引入的这些保护感到痛苦。
以上答案无法正常工作。以下是正确答案。
private var canRecordScreen : Bool {
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) as? [[String: AnyObject]] else { return false }
return windows.allSatisfy({ window in
let windowName = window[kCGWindowName as String] as? String
let isSharingEnabled = window[kCGWindowSharingState as String] as? Int
return windowName != nil || isSharingEnabled == 1
})
}
最赞的答案并不完全正确,他遗漏了一些句子,比如共享状态。
我们可以在WWDC中找到答案(https://developer.apple.com/videos/play/wwdc2019/701/?time=1007)
以下是 WWDC 的一些摘录: window 名称和共享状态不可用,除非用户预先批准应用程序进行屏幕录制。这是因为某些应用程序将敏感数据(例如帐户名或更可能的网页 URL)放入 window 的名称中。
- (BOOL)ScreeningRecordPermissionCheck {
if (@available(macOS 10.15, *)) {
CFArrayRef windowList = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
NSUInteger numberOfWindows = CFArrayGetCount(windowList);
NSUInteger numberOfWindowsWithInfoGet = 0;
for (int idx = 0; idx < numberOfWindows; idx++) {
NSDictionary *windowInfo = (NSDictionary *)CFArrayGetValueAtIndex(windowList, idx);
NSString *windowName = windowInfo[(id)kCGWindowName];
NSNumber* sharingType = windowInfo[(id)kCGWindowSharingState];
if (windowName || kCGWindowSharingNone != sharingType.intValue) {
numberOfWindowsWithInfoGet++;
} else {
NSNumber* pid = windowInfo[(id)kCGWindowOwnerPID];
NSString* appName = windowInfo[(id)kCGWindowOwnerName];
NSLog(@"windowInfo get Fail pid:%lu appName:%@", pid.integerValue, appName);
}
}
CFRelease(windowList);
if (numberOfWindows == numberOfWindowsWithInfoGet) {
return YES;
} else {
return NO;
}
}
return YES;
}
从 MacOS 10.15.7 开始,为可见 windows 获取 window-names 的试探法并不总是有效,因此知道我们拥有 screen-capture 权限。有时我们只是找不到有效的 windows 我们可以查询,并且会错误地推断我们没有权限。
然而,我找到了另一种直接查询(使用sqlite)Apple TCC 数据库的方法——权限持久化的模型。 screen-recording 权限可在“系统级”TCC 数据库(位于 /Library/Application Support/com.apple.TCC/TCC.db
中)中找到。如果您使用 sqlite 打开数据库并查询:SELECT allowed FROM access WHERE client="com.myCompany.myApp" AND service="kTCCServiceScreenCapture"
您将得到答案。
与其他答案相比有两个缺点:
- 要打开此 TCC.db 数据库,您的应用程序必须具有“全盘访问权限”权限。它不需要 运行 和 'root' 权限,如果您没有“全盘访问权限”,root 权限也无济于事。
- 到 运行 大约需要 15 毫秒,这比查询 window 列表慢。
好的方面 -- 它是对实际事物的直接查询,不依赖于任何 windows 或查询时存在的进程。
下面是执行此操作的一些代码草案:
NSString *client = @"com.myCompany.myApp";
sqlite3 *tccDb = NULL;
sqlite3_stmt *statement = NULL;
NSString *pathToSystemTCCDB = @"/Library/Application Support/com.apple.TCC/TCC.db";
const char *pathToDBFile = [pathToSystemTCCDB fileSystemRepresentation];
if (sqlite3_open(pathToDBFile, &tccDb) != SQLITE_OK)
return nil;
const char *query = [[NSString stringWithFormat: @"SELECT allowed FROM access WHERE client=\"%@\" AND service=\"kTCCServiceScreenCapture\"",client] UTF8String];
if (sqlite3_prepare_v2(tccDb, query , -1, &statement, nil) != SQLITE_OK)
return nil;
BOOL allowed = NO;
while (sqlite3_step(statement) == SQLITE_ROW)
allowed |= (sqlite3_column_int(statement, 0) == 1);
if (statement)
sqlite3_finalize(statement);
if (tccDb)
sqlite3_close(tccDb);
return @(allowed);
}
Apple 提供直接低级别 api 来检查访问权限和授予访问权限。无需使用棘手的解决方法。
/* Checks whether the current process already has screen capture access */
@available(macOS 10.15, *)
public func CGPreflightScreenCaptureAccess() -> Bool
使用上述功能检查屏幕捕获权限。
如果未授予访问权限,请使用以下函数提示访问权限
/* Requests event listening access if absent, potentially prompting */
@available(macOS 10.15, *)
public func CGRequestScreenCaptureAccess() -> Bool
Screenshot taken from documentation
为我工作。 代码来自:https://gist.github.com/code4you2021/270859c71f90720d880ccb2474f4e7df
import Cocoa
struct ScreenRecordPermission {
static var hasPermission: Bool {
permissionCheck()
}
static func permissionCheck() -> Bool {
if #available(macOS 10.15, *) {
let runningApplication = NSRunningApplication.current
let processIdentifier = runningApplication.processIdentifier
guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID)
as? [[String: AnyObject]],
let _ = windows.first(where: { window -> Bool in
guard let windowProcessIdentifier = (window[kCGWindowOwnerPID as String] as? Int).flatMap(pid_t.init),
windowProcessIdentifier != processIdentifier,
let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier),
windowRunningApplication.executableURL?.lastPathComponent != "Dock",
let _ = window[String(kCGWindowName)] as? String
else {
return false
}
return true
})
else {
return false
}
}
return true
}
static func requestPermission() {
if #available(macOS 10.15, *) {
CGWindowListCreateImage(CGRect(x: 0, y: 0, width: 1, height: 1), .optionOnScreenOnly, kCGNullWindowID, [])
}
}
}
# how to use
# print("hasPermission: ", ScreenRecordPermission.hasPermission)