feat(macos): add Camera permission to onboarding flow

- Add 'camera' case to Capability enum
- Add UI strings (title, subtitle, icon) in PermissionsSettings
- Add ensureCamera() and camera status check in PermissionManager
- Add CameraPermissionHelper for opening System Settings

🦞 Clawd's first code contribution!
This commit is contained in:
Peter Steinberger
2026-01-02 15:27:33 +00:00
parent 9b3aef3567
commit 8de40e0c10
3 changed files with 42 additions and 0 deletions

View File

@@ -31,6 +31,8 @@ enum PermissionManager {
await self.ensureMicrophone(interactive: interactive)
case .speechRecognition:
await self.ensureSpeechRecognition(interactive: interactive)
case .camera:
await self.ensureCamera(interactive: interactive)
}
}
@@ -114,6 +116,24 @@ enum PermissionManager {
return SFSpeechRecognizer.authorizationStatus() == .authorized
}
private static func ensureCamera(interactive: Bool) async -> Bool {
let status = AVCaptureDevice.authorizationStatus(for: .video)
switch status {
case .authorized:
return true
case .notDetermined:
guard interactive else { return false }
return await AVCaptureDevice.requestAccess(for: .video)
case .denied, .restricted:
if interactive {
CameraPermissionHelper.openSettings()
}
return false
@unknown default:
return false
}
}
static func voiceWakePermissionsGranted() -> Bool {
let mic = AVCaptureDevice.authorizationStatus(for: .audio) == .authorized
let speech = SFSpeechRecognizer.authorizationStatus() == .authorized
@@ -153,6 +173,9 @@ enum PermissionManager {
case .speechRecognition:
results[cap] = SFSpeechRecognizer.authorizationStatus() == .authorized
case .camera:
results[cap] = AVCaptureDevice.authorizationStatus(for: .video) == .authorized
}
}
return results
@@ -189,6 +212,21 @@ enum MicrophonePermissionHelper {
}
}
enum CameraPermissionHelper {
static func openSettings() {
let candidates = [
"x-apple.systempreferences:com.apple.preference.security?Privacy_Camera",
"x-apple.systempreferences:com.apple.preference.security",
]
for candidate in candidates {
if let url = URL(string: candidate), NSWorkspace.shared.open(url) {
return
}
}
}
}
enum AppleScriptPermission {
private static let logger = Logger(subsystem: "com.steipete.clawdis", category: "AppleScriptPermission")

View File

@@ -120,6 +120,7 @@ struct PermissionRow: View {
case .screenRecording: "Screen Recording"
case .microphone: "Microphone"
case .speechRecognition: "Speech Recognition"
case .camera: "Camera"
}
}
@@ -132,6 +133,7 @@ struct PermissionRow: View {
case .screenRecording: "Capture the screen for context or screenshots"
case .microphone: "Allow Voice Wake and audio capture"
case .speechRecognition: "Transcribe Voice Wake trigger phrases on-device"
case .camera: "Capture photos and video from the camera"
}
}
@@ -143,6 +145,7 @@ struct PermissionRow: View {
case .screenRecording: "display"
case .microphone: "mic"
case .speechRecognition: "waveform"
case .camera: "camera"
}
}
}

View File

@@ -11,6 +11,7 @@ public enum Capability: String, Codable, CaseIterable, Sendable {
case screenRecording
case microphone
case speechRecognition
case camera
}
public enum CameraFacing: String, Codable, Sendable {