Skip to content

Commit

Permalink
Add a screen for opting into permissions
Browse files Browse the repository at this point in the history
Also add a NSUserDefaults wrapper
  • Loading branch information
saagarjha committed Feb 8, 2024
1 parent a5d12c6 commit f54aefc
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 0 deletions.
14 changes: 14 additions & 0 deletions Ensemble.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
4901A1482B721BEF0040D2EE /* Permissions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4901A1472B721BEF0040D2EE /* Permissions.swift */; };
4901A14A2B721EAB0040D2EE /* PermissionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4901A1492B721EAB0040D2EE /* PermissionsView.swift */; };
4901A14C2B7246760040D2EE /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4901A14B2B7246760040D2EE /* Preference.swift */; };
4901A14D2B7246760040D2EE /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4901A14B2B7246760040D2EE /* Preference.swift */; };
49226A2F2AE43EF50044CFC9 /* SPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49226A2E2AE43EF50044CFC9 /* SPI.swift */; };
49226A312AE447C10044CFC9 /* ScreenRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49226A302AE447C10044CFC9 /* ScreenRecorder.swift */; };
49226A332AE45D710044CFC9 /* RootWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49226A322AE45D710044CFC9 /* RootWindowView.swift */; };
Expand Down Expand Up @@ -59,6 +63,9 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
4901A1472B721BEF0040D2EE /* Permissions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Permissions.swift; sourceTree = "<group>"; };
4901A1492B721EAB0040D2EE /* PermissionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsView.swift; sourceTree = "<group>"; };
4901A14B2B7246760040D2EE /* Preference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preference.swift; sourceTree = "<group>"; };
49226A2E2AE43EF50044CFC9 /* SPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPI.swift; sourceTree = "<group>"; };
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>"; };
Expand Down Expand Up @@ -158,6 +165,8 @@
49E09B562AD2EE5000B56CD3 /* EnsembleApp.swift */,
49EDAA6D2B28E58A00546EAB /* Events.swift */,
49E09BC62AD52CCE00B56CD3 /* Local.swift */,
4901A1472B721BEF0040D2EE /* Permissions.swift */,
4901A1492B721EAB0040D2EE /* PermissionsView.swift */,
49E09BC42AD52CC900B56CD3 /* Remote.swift */,
49226A302AE447C10044CFC9 /* ScreenRecorder.swift */,
49226A2E2AE43EF50044CFC9 /* SPI.swift */,
Expand Down Expand Up @@ -228,6 +237,7 @@
49E09BC12AD52C7800B56CD3 /* macOSInterface.swift */,
49E09BB22AD419CF00B56CD3 /* Messages.swift */,
49E09BCC2AD52D5900B56CD3 /* Multiplexer.swift */,
4901A14B2B7246760040D2EE /* Preference.swift */,
49E09BB52AD4F84500B56CD3 /* Serializable.swift */,
495E8E3C2AD5E6C500946419 /* SerializableConformances.swift */,
4978BAAB2AD54EDB000C549C /* Video.swift */,
Expand Down Expand Up @@ -357,6 +367,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
4901A1482B721BEF0040D2EE /* Permissions.swift in Sources */,
49226A2F2AE43EF50044CFC9 /* SPI.swift in Sources */,
494DFBD12B29111C00205CAC /* Keys.swift in Sources */,
49E09BC72AD52CCE00B56CD3 /* Local.swift in Sources */,
Expand All @@ -372,8 +383,10 @@
4978BAAC2AD54EDB000C549C /* Video.swift in Sources */,
49E09BCD2AD52D5900B56CD3 /* Multiplexer.swift in Sources */,
49EDAA6E2B28E58A00546EAB /* Events.swift in Sources */,
4901A14C2B7246760040D2EE /* Preference.swift in Sources */,
49E09BC22AD52C7800B56CD3 /* macOSInterface.swift in Sources */,
49E09BB32AD419CF00B56CD3 /* Messages.swift in Sources */,
4901A14A2B721EAB0040D2EE /* PermissionsView.swift in Sources */,
49226A312AE447C10044CFC9 /* ScreenRecorder.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -392,6 +405,7 @@
495E8E3B2AD5CE2400946419 /* ImageBufferView.swift in Sources */,
49E09B9B2AD3237D00B56CD3 /* ContentView.swift in Sources */,
49E09BB12AD3FDCC00B56CD3 /* ConnectionView.swift in Sources */,
4901A14D2B7246760040D2EE /* Preference.swift in Sources */,
49E09B992AD3237D00B56CD3 /* EnsembleApp.swift in Sources */,
4978BAAF2AD55D71000C549C /* WindowPreviewView.swift in Sources */,
49E09BC92AD52D1600B56CD3 /* Remote.swift in Sources */,
Expand Down
109 changes: 109 additions & 0 deletions Shared/Preference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// Preference.swift
// Ensemble
//
// Created by Saagar Jha on 2/6/24.
//

import SwiftUI

@propertyWrapper
struct Preference<T>: DynamicProperty {
// Forces an update
@State
var value = false

let key: String
let defaultValue: T

init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}

init(previewing value: T) {
self.init("", defaultValue: value)
}

var wrappedValue: T {
get {
_ = value
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
nonmutating set {
value.toggle()
UserDefaults.standard.setValue(newValue, forKey: key)
}
}

var projectedValue: Binding<T> {
Binding(
get: {
wrappedValue
},
set: {
wrappedValue = $0
}
)
}

var isSet: Bool {
return UserDefaults.standard.value(forKey: key) == nil
}

func reset() {
UserDefaults.standard.removeObject(forKey: key)
}
}

@propertyWrapper
struct CodablePreference<T: Codable> {
// Forces an update
@State
var value = false

var key: String
var defaultValue: T

init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}

init(previewing value: T) {
self.init("", defaultValue: value)
}

var wrappedValue: T {
get {
_ = value
guard let data = UserDefaults.standard.object(forKey: key) as? Data else {
return defaultValue
}
return (try? JSONDecoder().decode(T.self, from: data)) ?? defaultValue
}
nonmutating set {
value.toggle()
UserDefaults.standard.setValue(try! JSONEncoder().encode(newValue), forKey: key)
}
}

var projectedValue: Binding<T> {
Binding(
get: {
wrappedValue
},
set: {
wrappedValue = $0
}
)
}

var isSet: Bool {
UserDefaults.standard.value(forKey: key) == nil
}

func reset() {
UserDefaults.standard.removeObject(forKey: key)
}
}
12 changes: 12 additions & 0 deletions macOS/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ struct ContentView: View {

@State
var remote: Remote?
@State
var askForPermissions = false

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

init() {
_askForPermissions = State(initialValue: !Permission.allCases.allSatisfy(\.enabled) && !suppressPermissionsView)
}

var body: some View {
HStack(spacing: 50) {
Expand Down Expand Up @@ -73,6 +82,9 @@ struct ContentView: View {
}
.padding(40)
.frame(width: 800, height: 400)
.sheet(isPresented: $askForPermissions) {
PermissionsView(askForPermissions: $askForPermissions, suppressPermissionsView: _suppressPermissionsView)
}
}

static func generatePairingCode() -> String {
Expand Down
75 changes: 75 additions & 0 deletions macOS/Permissions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// Permissions.swift
// macOS
//
// Created by Saagar Jha on 2/5/24.
//

import AppKit

enum Permission: CaseIterable, Identifiable {
case screenRecording
case accesibility

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

var id: String {
name
}

var reason: String {
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."
}
}

var enabled: Bool {
switch self {
case .screenRecording:
CGPreflightScreenCaptureAccess()
case .accesibility:
// CGPreflightPostEventAccess appears to be a cached value
AXIsProcessTrusted()
}
}

var requested: Bool {
false
}

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

if !result {
backupRequest()
}
}

func backupRequest() {
let parameter: String
switch self {
case .screenRecording:
parameter = "Privacy_ScreenCapture"
case .accesibility:
parameter = "Privacy_Accessibility"
}
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?\(parameter)")!)

}
}
Loading

0 comments on commit f54aefc

Please sign in to comment.