Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add .isAccessGranted() method #90

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
236ae03
feat: Added new method isAccessGranted that will return true if the a…
Mubramaj Nov 23, 2020
00a625c
Update Package.swift
sindresorhus Nov 24, 2020
c083464
Update macos.js
sindresorhus Nov 24, 2020
b521fe5
Update main.swift
sindresorhus Nov 24, 2020
7a13e1d
Update readme.md
Mubramaj Nov 24, 2020
270407a
Update Sources/is-access-granted/main.swift
Mubramaj Nov 24, 2020
9d8f650
Update Sources/is-access-granted/main.swift
Mubramaj Nov 24, 2020
3c5fa26
fix: added 'is-access-granted' binary file to package.json
Mubramaj Nov 24, 2020
0085035
fix: removed path to is-access-granted in package.swift (not needed)
Mubramaj Nov 24, 2020
f31a0af
fix(is-access-granted): Use dictionary instead of 'dict' for variable…
Mubramaj Nov 24, 2020
e8583c2
fix: Don't use * in front.
Mubramaj Nov 24, 2020
ac3bbe0
fix: updated method to get status of screen recording permission
Mubramaj Nov 24, 2020
e74a431
use `package` instead of `app` in the readme wording
Mubramaj Nov 24, 2020
4a5797a
rollback on `isScreenRecordingGranted` method
Mubramaj Nov 24, 2020
38bf339
fix(is-access-granted): Moved utilities functions to separate utiliti…
Mubramaj Nov 25, 2020
440d4a9
fix(isAccessGranted): Method now returns JSON with all access statuses
Mubramaj Nov 25, 2020
fa2c680
fix: keep only 1 type for accessResult and do not return different re…
Mubramaj Dec 15, 2020
44b2559
fix: updated outdated comment for method isAccessGranted
Mubramaj Dec 15, 2020
c196eaa
style: changed name of unsupported_platform_error_message variable
Mubramaj Jan 3, 2021
1e0b86e
format: removed blank line
Mubramaj Jan 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ yarn.lock
/.build
/*.xcodeproj
/main
/is-access-granted
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PackageDescription
let package = Package(
name: "active-win",
targets: [
.target(name: "active-win")
.target(name: "active-win"),
.target(name: "is-access-granted")
]
)
19 changes: 19 additions & 0 deletions Sources/is-access-granted/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* Executable that will return a JSON with the granted permissions:
* {
* "isAccessibilityGranted": boolean
* "isScreenRecordingGranted": boolean
* }
*
*/

import AppKit

var dictionary: [String: Any] = [
"isAccessibilityGranted": AXIsProcessTrustedWithOptions(
["AXTrustedCheckOptionPrompt": false] as CFDictionary),
"isScreenRecordingGranted": isScreenRecordingGrantedNoDialog()
]

print(try! toJson(dictionary))

exit(0)
59 changes: 59 additions & 0 deletions Sources/is-access-granted/utilities.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import AppKit

func toJson<T>(_ 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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not how you write macOS.

if #available(macOS 10.15, *) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use guard. Applies to many other places.

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 {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use guard.

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
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use tab-indentation

} else {
return true;
}
}
26 changes: 26 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}

Mubramaj marked this conversation as resolved.
Show resolved Hide resolved
interface LinuxAccessResult extends BaseAccessResult {
platform: 'linux';
}
Mubramaj marked this conversation as resolved.
Show resolved Hide resolved

type Result = MacOSResult | LinuxResult | WindowsResult;
type AccessResult = MacOSAccessResult | LinuxAccessResult | WindowsAccessResult;
}

declare const activeWin: {
Expand Down Expand Up @@ -133,6 +152,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
Mubramaj marked this conversation as resolved.
Show resolved Hide resolved
To prompt user with a dialog to give access call activeWin() or activeWin.sync()
*/
isAccessGranted(): activeWin.AccessResult;
};

export = activeWin;
38 changes: 36 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

const PLATFORM_NOT_RECOGNIZED_ERROR = 'macOS, Linux, and Windows only';
Mubramaj marked this conversation as resolved.
Show resolved Hide resolved

module.exports = () => {
if (process.platform === 'darwin') {
return require('./lib/macos')();
Expand All @@ -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 = () => {
Expand All @@ -29,5 +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 = () => {
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;
}

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);
}
}
};
24 changes: 21 additions & 3 deletions lib/macos.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +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 {
Expand All @@ -19,9 +20,26 @@ 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 {
all: result.isAccessibilityGranted && result.isScreenRecordingGranted,
screen: result.isScreenRecordingGranted,
accessibility: result.isAccessibilityGranted
};
};

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;
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@
},
"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": [
Mubramaj marked this conversation as resolved.
Show resolved Hide resolved
"index.js",
"index.d.ts",
"lib",
"main"
"main",
"is-access-granted"
],
"keywords": [
"macos",
Expand Down
8 changes: 8 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ 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()

Check if the package has enough access to get the active window.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not correct. It always has access, just not access to the window title, etc.


Returns `true` if there is enough access, `false` otherwise.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is outdated.


To prompt the user with a dialog to give access, call `activeWin()` or `activeWin.sync()`.

## OS support

It works on macOS, Linux, and Windows 7+.
Expand Down
28 changes: 28 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,31 @@ test('async', async t => {
test('sync', t => {
asserter(t, activeWin.sync());
});

test('isAccessGranted', t => {
const result = activeWin.isAccessGranted();
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');
});