From 236ae034a0a79462675386cc064407e27bca39dd Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Mon, 23 Nov 2020 12:16:31 +0100 Subject: [PATCH 01/20] feat: Added new method isAccessGranted that will return true if the app has the right access to perform the active window fixed typo removed unused console log --- .gitignore | 1 + Package.swift | 5 +- Sources/is-access-granted/main.swift | 77 ++++++++++++++++++++++++++++ index.d.ts | 7 +++ index.js | 8 +++ lib/macos.js | 21 ++++++-- package.json | 2 +- readme.md | 7 +++ test.js | 5 ++ 9 files changed, 127 insertions(+), 6 deletions(-) create mode 100644 Sources/is-access-granted/main.swift diff --git a/.gitignore b/.gitignore index 5203350..87ae3ee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ yarn.lock /.build /*.xcodeproj /main +/is-access-granted diff --git a/Package.swift b/Package.swift index 22fe73d..2c9f34f 100644 --- a/Package.swift +++ b/Package.swift @@ -4,6 +4,9 @@ import PackageDescription let package = Package( name: "active-win", targets: [ - .target(name: "active-win") + .target(name: "active-win"), + .target( + name: "is-access-granted", + path: "Sources/is-access-granted") ] ) diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift new file mode 100644 index 0000000..37d5171 --- /dev/null +++ b/Sources/is-access-granted/main.swift @@ -0,0 +1,77 @@ +/* Executable that will return a JSON with the granted permissions: + * { + * "isAccessibilityGranted": boolean + * "isScreenRecordingGranted": boolean + * } + * + */ + + import AppKit + +func toJson(_ data: T) throws -> String { + let json = try JSONSerialization.data(withJSONObject: data) + return String(data: json, encoding: .utf8)! +} + +/** + * Returns true if the Screen recording is granted by the user for this app. False + * otherwise. + * CREDITS: https://gist.github.com/soffes/da6ea98be4f56bc7b8e75079a5224b37 + */ +func isScreenRecordingGranted() -> Bool { + // The screen recording security was introduced in version 10.15 of MAC os + if #available(macOS 10.15, *) { + let runningApplication = NSRunningApplication.current + let processIdentifier = runningApplication.processIdentifier + + guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) + as? [[String: AnyObject]] else + { + assertionFailure("Invalid window info") + return false + } + + for window in windows { + // Get information for each window + guard let windowProcessIdentifier = (window[String(kCGWindowOwnerPID)] as? Int).flatMap(pid_t.init) else { + assertionFailure("Invalid window info") + continue + } + + // Don't check windows owned by this process + if windowProcessIdentifier == processIdentifier { + continue + } + + // Get process information for each window + guard let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier) else { + // Ignore processes we don't have access to, such as WindowServer, which manages the windows named + // "Menubar" and "Backstop Menubar" + continue + } + + if window[String(kCGWindowName)] as? String != nil { + if windowRunningApplication.executableURL?.lastPathComponent == "Dock" { + // Ignore the Dock, which provides the desktop picture + continue + } else { + return true + } + } + } + + return false + } else { + return true; + } +} + +var dict: [String: Any] = [ + "isAccessibilityGranted": AXIsProcessTrustedWithOptions( + ["AXTrustedCheckOptionPrompt": false] as CFDictionary), + "isScreenRecordingGranted": isScreenRecordingGranted() +] + +print(try! toJson(dict)) + +exit(0) \ No newline at end of file diff --git a/index.d.ts b/index.d.ts index 13fca36..3daf706 100644 --- a/index.d.ts +++ b/index.d.ts @@ -133,6 +133,13 @@ declare const activeWin: { ``` */ sync(): activeWin.Result | undefined; + + /** + * Checks if the app has enough access to get the active window + * Returns true if there is enough access, false otherwise + * To prompt user with a dialog to give access call activeWin() or activeWin.sync() + */ + isAccessGranted(): boolean; }; export = activeWin; diff --git a/index.js b/index.js index e18ec94..6457043 100644 --- a/index.js +++ b/index.js @@ -31,3 +31,11 @@ module.exports.sync = () => { throw new Error('macOS, Linux, and Windows only'); }; + +module.exports.isAccessGranted = () => { + if (process.platform === 'darwin') { + return require('./lib/macos').isAccessGranted(); + } + + return true; +}; diff --git a/lib/macos.js b/lib/macos.js index ba4062a..e84c82b 100644 --- a/lib/macos.js +++ b/lib/macos.js @@ -4,8 +4,8 @@ const {promisify} = require('util'); const childProcess = require('child_process'); const execFile = promisify(childProcess.execFile); -const bin = path.join(__dirname, '../main'); - +const activeWinBin = path.join(__dirname, '../main'); +const isAccessGrantedBin = path.join(__dirname, '../is-access-granted'); const parseMac = stdout => { try { const result = JSON.parse(stdout); @@ -19,9 +19,22 @@ const parseMac = stdout => { } }; +const isAccessGranted = () => { + let result = childProcess.execFileSync(isAccessGrantedBin); + try { + result = JSON.parse(result); + } catch (error) { + console.error(error); + throw new Error('Error parsing access granted data'); + } + + return result.isAccessibilityGranted && result.isScreenRecordingGranted; +}; + module.exports = async () => { - const {stdout} = await execFile(bin); + const {stdout} = await execFile(activeWinBin); return parseMac(stdout); }; -module.exports.sync = () => parseMac(childProcess.execFileSync(bin, {encoding: 'utf8'})); +module.exports.sync = () => parseMac(childProcess.execFileSync(activeWinBin, {encoding: 'utf8'})); +module.exports.isAccessGranted = isAccessGranted; diff --git a/package.json b/package.json index 0e913a7..bb38a79 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "scripts": { "test": "xo && ava && tsd", - "build": "swift build --configuration=release && mv .build/release/active-win main", + "build": "swift build --configuration=release && mv .build/release/active-win main && mv .build/release/is-access-granted is-access-granted", "prepublishOnly": "npm run build" }, "files": [ diff --git a/readme.md b/readme.md index 3fc78b3..d69cb79 100644 --- a/readme.md +++ b/readme.md @@ -71,6 +71,13 @@ Returns an `Object` with the result, or `undefined` if there is no active window - `url` *(string?)* - URL of the active browser tab if the active window is Safari, Chrome, Edge, or Brave *(macOS only)* - `memoryUsage` *(number)* - Memory usage by the window owner process +### activeWin.isAccessGranted() + +Checks if the app has enough access to get the active window +Returns `true` if there is enough access, `false` otherwise. + +To prompt user with a dialog to give access call activeWin() or activeWin.sync() + ## OS support It works on macOS, Linux, and Windows 7+. diff --git a/test.js b/test.js index 5d7cdec..841f6b0 100644 --- a/test.js +++ b/test.js @@ -18,3 +18,8 @@ test('async', async t => { test('sync', t => { asserter(t, activeWin.sync()); }); + +test('isAccessGranted', t => { + const result = activeWin.isAccessGranted(); + t.is(typeof result, 'boolean'); +}); From 00a625cdc1f2cbfaf55137c848ce2130ddd77933 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 24 Nov 2020 22:22:16 +0700 Subject: [PATCH 02/20] Update Package.swift --- Package.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 2c9f34f..bb9e57e 100644 --- a/Package.swift +++ b/Package.swift @@ -7,6 +7,7 @@ let package = Package( .target(name: "active-win"), .target( name: "is-access-granted", - path: "Sources/is-access-granted") + path: "Sources/is-access-granted" + ) ] ) From c083464134cbf9e2815d64c305aeaa7f9f123b81 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 24 Nov 2020 22:25:05 +0700 Subject: [PATCH 03/20] Update macos.js --- lib/macos.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/macos.js b/lib/macos.js index e84c82b..a0fcc32 100644 --- a/lib/macos.js +++ b/lib/macos.js @@ -6,6 +6,7 @@ const childProcess = require('child_process'); const execFile = promisify(childProcess.execFile); const activeWinBin = path.join(__dirname, '../main'); const isAccessGrantedBin = path.join(__dirname, '../is-access-granted'); + const parseMac = stdout => { try { const result = JSON.parse(stdout); From b521fe56128acccb10f3bff554f1093d15e7b31f Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 24 Nov 2020 22:30:17 +0700 Subject: [PATCH 04/20] Update main.swift --- Sources/is-access-granted/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift index 37d5171..398cb7d 100644 --- a/Sources/is-access-granted/main.swift +++ b/Sources/is-access-granted/main.swift @@ -74,4 +74,4 @@ var dict: [String: Any] = [ print(try! toJson(dict)) -exit(0) \ No newline at end of file +exit(0) From 7a13e1dc2b27fcea22d1a1048b0494099cffc164 Mon Sep 17 00:00:00 2001 From: Loudghiri Ahmed Date: Tue, 24 Nov 2020 19:47:09 +0100 Subject: [PATCH 05/20] Update readme.md Co-authored-by: Sindre Sorhus --- readme.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index d69cb79..c9e6305 100644 --- a/readme.md +++ b/readme.md @@ -73,10 +73,11 @@ Returns an `Object` with the result, or `undefined` if there is no active window ### activeWin.isAccessGranted() -Checks if the app has enough access to get the active window +Check if the app has enough access to get the active window. + Returns `true` if there is enough access, `false` otherwise. -To prompt user with a dialog to give access call activeWin() or activeWin.sync() +To prompt the user with a dialog to give access, call `activeWin()` or `activeWin.sync()`. ## OS support From 270407a72c7deec32c8d5b49a744c31e5c080335 Mon Sep 17 00:00:00 2001 From: Loudghiri Ahmed Date: Tue, 24 Nov 2020 19:47:32 +0100 Subject: [PATCH 06/20] Update Sources/is-access-granted/main.swift Co-authored-by: Sindre Sorhus --- Sources/is-access-granted/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift index 398cb7d..cc73ce2 100644 --- a/Sources/is-access-granted/main.swift +++ b/Sources/is-access-granted/main.swift @@ -6,7 +6,7 @@ * */ - import AppKit +import AppKit func toJson(_ data: T) throws -> String { let json = try JSONSerialization.data(withJSONObject: data) From 9d8f6501b76c7d04f6111f89aceee4dafc5888c6 Mon Sep 17 00:00:00 2001 From: Loudghiri Ahmed Date: Tue, 24 Nov 2020 19:49:09 +0100 Subject: [PATCH 07/20] Update Sources/is-access-granted/main.swift Co-authored-by: Sindre Sorhus --- Sources/is-access-granted/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift index cc73ce2..d299710 100644 --- a/Sources/is-access-granted/main.swift +++ b/Sources/is-access-granted/main.swift @@ -66,7 +66,7 @@ func isScreenRecordingGranted() -> Bool { } } -var dict: [String: Any] = [ +var dictionary: [String: Any] = [ "isAccessibilityGranted": AXIsProcessTrustedWithOptions( ["AXTrustedCheckOptionPrompt": false] as CFDictionary), "isScreenRecordingGranted": isScreenRecordingGranted() From 3c5fa265723e323c148498af766066116fa04afd Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Tue, 24 Nov 2020 23:17:24 +0100 Subject: [PATCH 08/20] fix: added 'is-access-granted' binary file to package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bb38a79..fe24cf5 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "index.js", "index.d.ts", "lib", - "main" + "main", + "is-access-granted" ], "keywords": [ "macos", From 0085035df5be2e5dd2ab34ddf852469e26123399 Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Tue, 24 Nov 2020 23:17:45 +0100 Subject: [PATCH 09/20] fix: removed path to is-access-granted in package.swift (not needed) --- Package.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index bb9e57e..4b54413 100644 --- a/Package.swift +++ b/Package.swift @@ -5,9 +5,6 @@ let package = Package( name: "active-win", targets: [ .target(name: "active-win"), - .target( - name: "is-access-granted", - path: "Sources/is-access-granted" - ) + .target(name: "is-access-granted") ] ) From f31a0afaed5fbbb96b115f99c81590a5c2f1d04a Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Tue, 24 Nov 2020 23:18:16 +0100 Subject: [PATCH 10/20] fix(is-access-granted): Use dictionary instead of 'dict' for variable name --- Sources/is-access-granted/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift index d299710..dc66186 100644 --- a/Sources/is-access-granted/main.swift +++ b/Sources/is-access-granted/main.swift @@ -72,6 +72,6 @@ var dictionary: [String: Any] = [ "isScreenRecordingGranted": isScreenRecordingGranted() ] -print(try! toJson(dict)) +print(try! toJson(dictionary)) exit(0) From e8583c23c6630e74858b2b8a55173d505cda4124 Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Tue, 24 Nov 2020 23:25:38 +0100 Subject: [PATCH 11/20] fix: Don't use * in front. --- index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 3daf706..b4036f3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -135,10 +135,10 @@ declare const activeWin: { sync(): activeWin.Result | undefined; /** - * Checks if the app has enough access to get the active window - * Returns true if there is enough access, false otherwise - * To prompt user with a dialog to give access call activeWin() or activeWin.sync() - */ + Checks if the app has enough access to get the active window + Returns true if there is enough access, false otherwise + To prompt user with a dialog to give access call activeWin() or activeWin.sync() + */ isAccessGranted(): boolean; }; From ac3bbe07c33cbf0b44a5915e49ba4b9b2fdc7d87 Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Tue, 24 Nov 2020 23:37:44 +0100 Subject: [PATCH 12/20] fix: updated method to get status of screen recording permission --- Sources/is-access-granted/main.swift | 59 ++++++---------------------- 1 file changed, 13 insertions(+), 46 deletions(-) diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift index dc66186..ecb9a61 100644 --- a/Sources/is-access-granted/main.swift +++ b/Sources/is-access-granted/main.swift @@ -16,54 +16,21 @@ func toJson(_ data: T) throws -> String { /** * Returns true if the Screen recording is granted by the user for this app. False * otherwise. - * CREDITS: https://gist.github.com/soffes/da6ea98be4f56bc7b8e75079a5224b37 + * CREDITS: + * https://github.com/karaggeorge/mac-screen-capture-permissions/blob/master/Sources/screen-capture-permissions/ScreenCapturePermissions.swift */ func isScreenRecordingGranted() -> Bool { - // The screen recording security was introduced in version 10.15 of MAC os - if #available(macOS 10.15, *) { - let runningApplication = NSRunningApplication.current - let processIdentifier = runningApplication.processIdentifier - - guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) - as? [[String: AnyObject]] else - { - assertionFailure("Invalid window info") - return false - } - - for window in windows { - // Get information for each window - guard let windowProcessIdentifier = (window[String(kCGWindowOwnerPID)] as? Int).flatMap(pid_t.init) else { - assertionFailure("Invalid window info") - continue - } - - // Don't check windows owned by this process - if windowProcessIdentifier == processIdentifier { - continue - } - - // Get process information for each window - guard let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier) else { - // Ignore processes we don't have access to, such as WindowServer, which manages the windows named - // "Menubar" and "Backstop Menubar" - continue - } - - if window[String(kCGWindowName)] as? String != nil { - if windowRunningApplication.executableURL?.lastPathComponent == "Dock" { - // Ignore the Dock, which provides the desktop picture - continue - } else { - return true - } - } - } - - return false - } else { - return true; - } + let stream = CGDisplayStream( + dispatchQueueDisplay: CGMainDisplayID(), + outputWidth: 1, + outputHeight: 1, + pixelFormat: Int32(kCVPixelFormatType_32BGRA), + properties: nil, + queue: DispatchQueue.global(), + handler: { _, _, _, _ in } + ) + + return stream != nil } var dictionary: [String: Any] = [ From e74a431fa169a56b4d786d82f01ee6c020353c5c Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Wed, 25 Nov 2020 00:05:46 +0100 Subject: [PATCH 13/20] use `package` instead of `app` in the readme wording --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index c9e6305..cc3512a 100644 --- a/readme.md +++ b/readme.md @@ -73,7 +73,7 @@ Returns an `Object` with the result, or `undefined` if there is no active window ### activeWin.isAccessGranted() -Check if the app has enough access to get the active window. +Check if the package has enough access to get the active window. Returns `true` if there is enough access, `false` otherwise. From 4a5797a6bb01f8ac99cd73dd66f92eab42f44dbf Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Wed, 25 Nov 2020 00:08:34 +0100 Subject: [PATCH 14/20] rollback on `isScreenRecordingGranted` method --- Sources/is-access-granted/main.swift | 59 ++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift index ecb9a61..dc66186 100644 --- a/Sources/is-access-granted/main.swift +++ b/Sources/is-access-granted/main.swift @@ -16,21 +16,54 @@ func toJson(_ data: T) throws -> String { /** * Returns true if the Screen recording is granted by the user for this app. False * otherwise. - * CREDITS: - * https://github.com/karaggeorge/mac-screen-capture-permissions/blob/master/Sources/screen-capture-permissions/ScreenCapturePermissions.swift + * CREDITS: https://gist.github.com/soffes/da6ea98be4f56bc7b8e75079a5224b37 */ func isScreenRecordingGranted() -> Bool { - let stream = CGDisplayStream( - dispatchQueueDisplay: CGMainDisplayID(), - outputWidth: 1, - outputHeight: 1, - pixelFormat: Int32(kCVPixelFormatType_32BGRA), - properties: nil, - queue: DispatchQueue.global(), - handler: { _, _, _, _ in } - ) - - return stream != nil + // The screen recording security was introduced in version 10.15 of MAC os + if #available(macOS 10.15, *) { + let runningApplication = NSRunningApplication.current + let processIdentifier = runningApplication.processIdentifier + + guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) + as? [[String: AnyObject]] else + { + assertionFailure("Invalid window info") + return false + } + + for window in windows { + // Get information for each window + guard let windowProcessIdentifier = (window[String(kCGWindowOwnerPID)] as? Int).flatMap(pid_t.init) else { + assertionFailure("Invalid window info") + continue + } + + // Don't check windows owned by this process + if windowProcessIdentifier == processIdentifier { + continue + } + + // Get process information for each window + guard let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier) else { + // Ignore processes we don't have access to, such as WindowServer, which manages the windows named + // "Menubar" and "Backstop Menubar" + continue + } + + if window[String(kCGWindowName)] as? String != nil { + if windowRunningApplication.executableURL?.lastPathComponent == "Dock" { + // Ignore the Dock, which provides the desktop picture + continue + } else { + return true + } + } + } + + return false + } else { + return true; + } } var dictionary: [String: Any] = [ From 38bf339cdb801da5378387a3e594f03f7eba7592 Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Wed, 25 Nov 2020 19:48:10 +0100 Subject: [PATCH 15/20] fix(is-access-granted): Moved utilities functions to separate utilities file --- Sources/is-access-granted/main.swift | 60 +---------------------- Sources/is-access-granted/utilities.swift | 59 ++++++++++++++++++++++ 2 files changed, 60 insertions(+), 59 deletions(-) create mode 100644 Sources/is-access-granted/utilities.swift diff --git a/Sources/is-access-granted/main.swift b/Sources/is-access-granted/main.swift index dc66186..671dc24 100644 --- a/Sources/is-access-granted/main.swift +++ b/Sources/is-access-granted/main.swift @@ -8,68 +8,10 @@ import AppKit -func toJson(_ data: T) throws -> String { - let json = try JSONSerialization.data(withJSONObject: data) - return String(data: json, encoding: .utf8)! -} - -/** - * Returns true if the Screen recording is granted by the user for this app. False - * otherwise. - * CREDITS: https://gist.github.com/soffes/da6ea98be4f56bc7b8e75079a5224b37 - */ -func isScreenRecordingGranted() -> Bool { - // The screen recording security was introduced in version 10.15 of MAC os - if #available(macOS 10.15, *) { - let runningApplication = NSRunningApplication.current - let processIdentifier = runningApplication.processIdentifier - - guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) - as? [[String: AnyObject]] else - { - assertionFailure("Invalid window info") - return false - } - - for window in windows { - // Get information for each window - guard let windowProcessIdentifier = (window[String(kCGWindowOwnerPID)] as? Int).flatMap(pid_t.init) else { - assertionFailure("Invalid window info") - continue - } - - // Don't check windows owned by this process - if windowProcessIdentifier == processIdentifier { - continue - } - - // Get process information for each window - guard let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier) else { - // Ignore processes we don't have access to, such as WindowServer, which manages the windows named - // "Menubar" and "Backstop Menubar" - continue - } - - if window[String(kCGWindowName)] as? String != nil { - if windowRunningApplication.executableURL?.lastPathComponent == "Dock" { - // Ignore the Dock, which provides the desktop picture - continue - } else { - return true - } - } - } - - return false - } else { - return true; - } -} - var dictionary: [String: Any] = [ "isAccessibilityGranted": AXIsProcessTrustedWithOptions( ["AXTrustedCheckOptionPrompt": false] as CFDictionary), - "isScreenRecordingGranted": isScreenRecordingGranted() + "isScreenRecordingGranted": isScreenRecordingGrantedNoDialog() ] print(try! toJson(dictionary)) diff --git a/Sources/is-access-granted/utilities.swift b/Sources/is-access-granted/utilities.swift new file mode 100644 index 0000000..d6b39aa --- /dev/null +++ b/Sources/is-access-granted/utilities.swift @@ -0,0 +1,59 @@ +import AppKit + +func toJson(_ data: T) throws -> String { + let json = try JSONSerialization.data(withJSONObject: data) + return String(decoding: json, as: UTF8.self) +} + +/** + * Returns true if the Screen recording is granted by the user for this app. False + * otherwise. + * This method will not prompt the user with a dialog to grant permissions + * CREDITS: https://gist.github.com/soffes/da6ea98be4f56bc7b8e75079a5224b37 + */ +func isScreenRecordingGrantedNoDialog() -> Bool { + // The screen recording security was introduced in version 10.15 of MAC os + if #available(macOS 10.15, *) { + let runningApplication = NSRunningApplication.current + let processIdentifier = runningApplication.processIdentifier + + guard let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly], kCGNullWindowID) + as? [[String: AnyObject]] else + { + assertionFailure("Invalid window info") + return false + } + + for window in windows { + // Get information for each window + guard let windowProcessIdentifier = (window[String(kCGWindowOwnerPID)] as? Int).flatMap(pid_t.init) else { + assertionFailure("Invalid window info") + continue + } + + // Don't check windows owned by this process + if windowProcessIdentifier == processIdentifier { + continue + } + + // Get process information for each window + guard let windowRunningApplication = NSRunningApplication(processIdentifier: windowProcessIdentifier) else { + // Ignore processes we don't have access to, such as WindowServer, which manages the windows named + // "Menubar" and "Backstop Menubar" + continue + } + + if window[String(kCGWindowName)] as? String != nil { + if windowRunningApplication.executableURL?.lastPathComponent == "Dock" { + // Ignore the Dock, which provides the desktop picture + continue + } else { + return true + } + } + } + return false + } else { + return true; + } +} \ No newline at end of file From 440d4a9c60a67c653b1b2d6fe676ea3090a84e36 Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Wed, 25 Nov 2020 23:15:44 +0100 Subject: [PATCH 16/20] fix(isAccessGranted): Method now returns JSON with all access statuses --- index.d.ts | 21 ++++++++++++++++++++- index.js | 38 ++++++++++++++++++++++++++++++++------ lib/macos.js | 6 +++++- test.js | 25 ++++++++++++++++++++++++- 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/index.d.ts b/index.d.ts index b4036f3..d5c7b92 100644 --- a/index.d.ts +++ b/index.d.ts @@ -76,7 +76,26 @@ declare namespace activeWin { platform: 'windows'; } + interface BaseAccessResult { + all: boolean; + } + + interface MacOSAccessResult extends BaseAccessResult { + screen: boolean; + accessibility: boolean; + platform: 'macos'; + } + + interface WindowsAccessResult extends BaseAccessResult { + platform: 'windows'; + } + + interface LinuxAccessResult extends BaseAccessResult { + platform: 'linux'; + } + type Result = MacOSResult | LinuxResult | WindowsResult; + type AccessResult = MacOSAccessResult | LinuxAccessResult | WindowsAccessResult; } declare const activeWin: { @@ -139,7 +158,7 @@ declare const activeWin: { Returns true if there is enough access, false otherwise To prompt user with a dialog to give access call activeWin() or activeWin.sync() */ - isAccessGranted(): boolean; + isAccessGranted(): activeWin.AccessResult; }; export = activeWin; diff --git a/index.js b/index.js index 6457043..a095590 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,7 @@ 'use strict'; +const PLATFORM_NOT_RECOGNIZED_ERROR = 'macOS, Linux, and Windows only'; + module.exports = () => { if (process.platform === 'darwin') { return require('./lib/macos')(); @@ -13,7 +15,7 @@ module.exports = () => { return require('./lib/windows')(); } - return Promise.reject(new Error('macOS, Linux, and Windows only')); + return Promise.reject(new Error(PLATFORM_NOT_RECOGNIZED_ERROR)); }; module.exports.sync = () => { @@ -29,13 +31,37 @@ module.exports.sync = () => { return require('./lib/windows').sync(); } - throw new Error('macOS, Linux, and Windows only'); + throw new Error(PLATFORM_NOT_RECOGNIZED_ERROR); }; module.exports.isAccessGranted = () => { - if (process.platform === 'darwin') { - return require('./lib/macos').isAccessGranted(); - } + switch (process.platform) { + case 'darwin': { + // MAC OS needs specific accesses to get the active window. These accesses are + // resolved by the isAccessGranted method of the macos lib + const result = require('./lib/macos').isAccessGranted(); + result.platform = 'macos'; + return result; + } - return true; + case 'linux': { + // Linux does not need specific access to get the active window + return { + platform: 'linux', + all: true + }; + } + + // Windows does not need specific access to get the active window + case 'win32': { + return { + patform: 'windows', + all: true + }; + } + + default: { + throw new Error(PLATFORM_NOT_RECOGNIZED_ERROR); + } + } }; diff --git a/lib/macos.js b/lib/macos.js index a0fcc32..730d50e 100644 --- a/lib/macos.js +++ b/lib/macos.js @@ -29,7 +29,11 @@ const isAccessGranted = () => { throw new Error('Error parsing access granted data'); } - return result.isAccessibilityGranted && result.isScreenRecordingGranted; + return { + all: result.isAccessibilityGranted && result.isScreenRecordingGranted, + screen: result.isScreenRecordingGranted, + accessibility: result.isAccessibilityGranted + }; }; module.exports = async () => { diff --git a/test.js b/test.js index 841f6b0..da7c525 100644 --- a/test.js +++ b/test.js @@ -21,5 +21,28 @@ test('sync', t => { test('isAccessGranted', t => { const result = activeWin.isAccessGranted(); - t.is(typeof result, 'boolean'); + switch (process.platform) { + case 'darwin': { + t.is(result.platform, 'macos'); + t.is(typeof result.screen, 'boolean'); + t.is(typeof result.accessibility, 'boolean'); + break; + } + + case 'linux': { + t.is(result.platform, 'linux'); + break; + } + + case 'win32': { + t.is(result.platform, 'windows'); + break; + } + + default: { + throw new Error('Platform not recognized'); + } + } + + t.is(typeof result.all, 'boolean'); }); From fa2c680b55df8815791b0592e6fad273176a310b Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Tue, 15 Dec 2020 21:57:01 +0100 Subject: [PATCH 17/20] fix: keep only 1 type for accessResult and do not return different result depending on platform --- index.d.ts | 16 ++-------------- index.js | 20 ++++++-------------- test.js | 12 +++++------- 3 files changed, 13 insertions(+), 35 deletions(-) diff --git a/index.d.ts b/index.d.ts index d5c7b92..18d31c5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -76,26 +76,14 @@ declare namespace activeWin { platform: 'windows'; } - interface BaseAccessResult { + interface AccessResult { all: boolean; - } - - interface MacOSAccessResult extends BaseAccessResult { screen: boolean; accessibility: boolean; - platform: 'macos'; - } - - interface WindowsAccessResult extends BaseAccessResult { - platform: 'windows'; - } - - interface LinuxAccessResult extends BaseAccessResult { - platform: 'linux'; } type Result = MacOSResult | LinuxResult | WindowsResult; - type AccessResult = MacOSAccessResult | LinuxAccessResult | WindowsAccessResult; + } declare const activeWin: { diff --git a/index.js b/index.js index a095590..396b9c6 100644 --- a/index.js +++ b/index.js @@ -39,24 +39,16 @@ module.exports.isAccessGranted = () => { case 'darwin': { // MAC OS needs specific accesses to get the active window. These accesses are // resolved by the isAccessGranted method of the macos lib - const result = require('./lib/macos').isAccessGranted(); - result.platform = 'macos'; - return result; + return require('./lib/macos').isAccessGranted(); } - case 'linux': { - // Linux does not need specific access to get the active window - return { - platform: 'linux', - all: true - }; - } - - // Windows does not need specific access to get the active window + case 'linux': case 'win32': { + // Linux and Windows do not need specific access to get the active window, set all to true return { - patform: 'windows', - all: true + all: true, + screen: true, + accessibility: true }; } diff --git a/test.js b/test.js index da7c525..0a8b5fa 100644 --- a/test.js +++ b/test.js @@ -23,19 +23,17 @@ test('isAccessGranted', t => { const result = activeWin.isAccessGranted(); switch (process.platform) { case 'darwin': { - t.is(result.platform, 'macos'); + t.is(typeof result.all, 'boolean'); t.is(typeof result.screen, 'boolean'); t.is(typeof result.accessibility, 'boolean'); break; } - case 'linux': { - t.is(result.platform, 'linux'); - break; - } - + case 'linux': case 'win32': { - t.is(result.platform, 'windows'); + t.is(result.all, true); + t.is(result.screen, true); + t.is(result.accessibility, true); break; } From 44b2559da7d381dd7cf0df123cbb8652eac7c141 Mon Sep 17 00:00:00 2001 From: Mubramaj Date: Tue, 15 Dec 2020 22:00:21 +0100 Subject: [PATCH 18/20] fix: updated outdated comment for method isAccessGranted --- index.d.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 18d31c5..cbca9d5 100644 --- a/index.d.ts +++ b/index.d.ts @@ -142,9 +142,10 @@ declare const activeWin: { sync(): activeWin.Result | undefined; /** - Checks if the app has enough access to get the active window - Returns true if there is enough access, false otherwise - To prompt user with a dialog to give access call activeWin() or activeWin.sync() + Resolves all the statuses of the accesses needed to check the active win + Returns an object of type AccessResult + Note: this method will not prompte the user with a dialog to grant the accesses, + to prompt user with a dialog to give access call activeWin() or activeWin.sync() */ isAccessGranted(): activeWin.AccessResult; }; From c196eaac797d023dd7acf735953452546bba1c23 Mon Sep 17 00:00:00 2001 From: Loudghiri Ahmed Date: Sun, 3 Jan 2021 23:04:31 +0100 Subject: [PATCH 19/20] style: changed name of unsupported_platform_error_message variable Co-authored-by: Sindre Sorhus --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 396b9c6..1bc08fc 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ 'use strict'; -const PLATFORM_NOT_RECOGNIZED_ERROR = 'macOS, Linux, and Windows only'; +const UNSUPPORTED_PLATFORM_ERROR_MESSAGE = 'macOS, Linux, and Windows only'; module.exports = () => { if (process.platform === 'darwin') { From 1e0b86ec18ab793be930d56acc92fbb593172a11 Mon Sep 17 00:00:00 2001 From: Loudghiri Ahmed Date: Sun, 3 Jan 2021 23:05:05 +0100 Subject: [PATCH 20/20] format: removed blank line Co-authored-by: Sindre Sorhus --- index.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/index.d.ts b/index.d.ts index cbca9d5..690056a 100644 --- a/index.d.ts +++ b/index.d.ts @@ -83,7 +83,6 @@ declare namespace activeWin { } type Result = MacOSResult | LinuxResult | WindowsResult; - } declare const activeWin: {