Skip to content

Commit

Permalink
Add a helper script
Browse files Browse the repository at this point in the history
This runs unsandboxed and grants access to things that are otherwise
inaccessible to the main app.
  • Loading branch information
saagarjha committed Feb 13, 2024
1 parent e618813 commit e90536e
Show file tree
Hide file tree
Showing 6 changed files with 196 additions and 50 deletions.
26 changes: 26 additions & 0 deletions Ensemble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
49226A302AE447C10044CFC9 /* ScreenRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenRecorder.swift; sourceTree = "<group>"; };
49226A322AE45D710044CFC9 /* RootWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootWindowView.swift; sourceTree = "<group>"; };
494DFBD02B29111C00205CAC /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; };
495A72332B78F0F500970992 /* ensemble_helper_v1.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = ensemble_helper_v1.sh; sourceTree = "<group>"; };
495A8AB12B6478AE00520461 /* Bundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bundle.swift; sourceTree = "<group>"; };
495A8AB42B651F3D00520461 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
495E8E3A2AD5CE2400946419 /* ImageBufferView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageBufferView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -161,6 +162,7 @@
children = (
49E09B5F2AD2EE5100B56CD3 /* Ensemble.entitlements */,
495A8AB42B651F3D00520461 /* Info.plist */,
495A72332B78F0F500970992 /* ensemble_helper_v1.sh */,
49E09B582AD2EE5000B56CD3 /* ContentView.swift */,
49E09B562AD2EE5000B56CD3 /* EnsembleApp.swift */,
49EDAA6D2B28E58A00546EAB /* Events.swift */,
Expand Down Expand Up @@ -263,6 +265,7 @@
49E09B4F2AD2EE5000B56CD3 /* Sources */,
49E09B502AD2EE5000B56CD3 /* Frameworks */,
49E09B512AD2EE5000B56CD3 /* Resources */,
495A72372B78F70F00970992 /* Copy Helper */,
);
buildRules = (
);
Expand Down Expand Up @@ -362,6 +365,29 @@
};
/* End PBXResourcesBuildPhase section */

/* Begin PBXShellScriptBuildPhase section */
495A72372B78F70F00970992 /* Copy Helper */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"$(SRCROOT)/macOS/ensemble_helper_v1.sh",
);
name = "Copy Helper";
outputFileListPaths = (
);
outputPaths = (
"$(BUILT_PRODUCTS_DIR)/$(SHARED_SUPPORT_FOLDER_PATH)/ensemble_helper_v1.sh",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "# This is a copy files phase but it preserves the execute bit\nset -x\n\ncp \"$SRCROOT/macOS/ensemble_helper_v1.sh\" \"$BUILT_PRODUCTS_DIR/$SHARED_SUPPORT_FOLDER_PATH/\"\n";
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
49E09B4F2AD2EE5000B56CD3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
Expand Down
18 changes: 16 additions & 2 deletions macOS/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ struct ContentView: View {
var remote: Remote?
@State
var askForPermissions = false

@State
var prewarmedHelper = false

@Preference("SuppressPermissionsView", defaultValue: false)
var suppressPermissionsView

init() {
_askForPermissions = State(initialValue: !Permission.allCases.allSatisfy(\.enabled) && !suppressPermissionsView)
}
Expand Down Expand Up @@ -85,6 +87,18 @@ struct ContentView: View {
.sheet(isPresented: $askForPermissions) {
PermissionsView(askForPermissions: $askForPermissions, suppressPermissionsView: _suppressPermissionsView)
}
.onChange(of: askForPermissions, initial: true) {
// FIXME: This races with the first code that calls accessibility
// APIs, and this is an awful place to put it to boot.
Task {
if !prewarmedHelper,
Permission.helper.supported,
Permission.helper.enabled
{
prewarmedHelper = await Permission.prewarmHelper()
}
}
}
}

static func generatePairingCode() -> String {
Expand Down
137 changes: 106 additions & 31 deletions macOS/Permissions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,69 +7,144 @@

import AppKit

// Not really a list of permissions, and not really worth making an enum either.
// This really just exists to make some UI code a bit easier to write.
enum Permission: CaseIterable, Identifiable {
case screenRecording
case accesibility

case helper

var name: String {
switch self {
case .screenRecording:
return "Screen Recording"
case .accesibility:
return "Accessibility"
case .screenRecording:
return "Screen Recording"
case .accesibility:
return "Accessibility"
case .helper:
return "Helper"
}
}

var id: String {
name
}

var reason: String {
guard supported else {
return "This feature is not supported on your Mac."
}

switch self {
case .screenRecording:
return "Provides access to critical functionality such as the ability to list windows and stream them."
case .accesibility:
return "Used to track certain window elements and synthesize input events to control your Mac remotely."
case .screenRecording:
return "Provides access to critical functionality such as the ability to list windows and stream them."
case .accesibility:
return "Used to synthesize input events so your Mac can be controlled remotely from Apple Vision Pro."
case .helper:
return "Enables \(Bundle.main.name) to track certain window elements and interact with them reliably."
}
}

var enabled: Bool {
switch self {
case .screenRecording:
CGPreflightScreenCaptureAccess()
case .accesibility:
// CGPreflightPostEventAccess appears to be a cached value
AXIsProcessTrusted()
case .screenRecording:
CGPreflightScreenCaptureAccess()
case .accesibility:
// CGPreflightPostEventAccess appears to be a cached value
AXIsProcessTrusted()
case .helper:
(try? NSUserUnixTask(url: Self.helperScriptInstallLocation)) != nil
}
}

var requested: Bool {
false

var supported: Bool {
switch self {
case .screenRecording, .accesibility:
return true
case .helper:
return sandbox_extension_consume != nil
}
}

var prompt: String {
guard supported else {
return "Unsupported"
}

switch self {
case .screenRecording, .accesibility:
return enabled ? "Enabled!" : "Request…"
case .helper:
return enabled ? "Installed!" : "Install…"
}
}

func request() {
var result: Bool
switch self {
case .screenRecording:
result = CGRequestScreenCaptureAccess()
case .accesibility:
result = CGRequestPostEventAccess()
case .screenRecording:
result = CGRequestScreenCaptureAccess()
case .accesibility:
result = CGRequestPostEventAccess()
case .helper:
result = false
}

if !result {
backupRequest()
}
}

func backupRequest() {
let parameter: String
switch self {
case .screenRecording:
parameter = "Privacy_ScreenCapture"
case .accesibility:
parameter = "Privacy_Accessibility"
case .screenRecording:
parameter = "Privacy_ScreenCapture"
case .accesibility:
parameter = "Privacy_Accessibility"
case .helper:
NSWorkspace.shared.open(Permission.helperScriptInstallLocation.deletingLastPathComponent())
NSWorkspace.shared.selectFile(Permission.helperScriptLocation.path, inFileViewerRootedAtPath: Self.helperScriptLocation.deletingLastPathComponent().path)
return
}
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?\(parameter)")!)
}

static func prewarmHelper() async -> Bool {
guard let sandbox_extension_consume,
let task = try? NSUserUnixTask(url: Self.helperScriptInstallLocation)
else {
return false
}

let pipe = Pipe()
task.standardOutput = pipe.fileHandleForWriting
guard (try? await task.execute(withArguments: nil)) != nil,
let data = try? pipe.fileHandleForReading.readToEnd(),
let sandboxExtension = String(data: data, encoding: .ascii),
// Strip off the newline
sandbox_extension_consume(String(sandboxExtension.dropLast())) >= 0
else {
return false
}

return true
}

static var helperScriptName: String {
"\(Bundle.main.name.lowercased())_helper_v1.sh"
}

static var helperScriptLocation: URL {
Bundle.main.sharedSupportURL!.appendingPathComponent(helperScriptName)
}

// This logs an annoying warning about being called on the main thread
// (because it goes through SecTrustEvaluateIfNecessary) so only do it once
static var applicationScripts = {
try! FileManager.default.url(for: .applicationScriptsDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
}()

static var helperScriptInstallLocation: URL {
applicationScripts.appendingPathComponent(helperScriptName)
}
}
Loading

0 comments on commit e90536e

Please sign in to comment.