mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Make permission monitoring dependent on views
This commit is contained in:
parent
a121d09fee
commit
41d602d6d9
3 changed files with 103 additions and 15 deletions
|
|
@ -5,6 +5,10 @@ import Foundation
|
||||||
import Observation
|
import Observation
|
||||||
import OSLog
|
import OSLog
|
||||||
|
|
||||||
|
extension Notification.Name {
|
||||||
|
static let permissionsUpdated = Notification.Name("sh.vibetunnel.permissionsUpdated")
|
||||||
|
}
|
||||||
|
|
||||||
/// Types of system permissions that VibeTunnel requires
|
/// Types of system permissions that VibeTunnel requires
|
||||||
enum SystemPermission {
|
enum SystemPermission {
|
||||||
case appleScript
|
case appleScript
|
||||||
|
|
@ -63,8 +67,14 @@ final class SystemPermissionManager {
|
||||||
category: "SystemPermissions"
|
category: "SystemPermissions"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/// Timer for monitoring permission changes
|
||||||
|
private var monitorTimer: Timer?
|
||||||
|
|
||||||
|
/// Count of views that have registered for monitoring
|
||||||
|
private var monitorRegistrationCount = 0
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
// No automatic monitoring - UI components will check when visible
|
// No automatic monitoring - UI components will register when visible
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public API
|
// MARK: - Public API
|
||||||
|
|
@ -127,13 +137,67 @@ final class SystemPermissionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Permission Monitoring
|
||||||
|
|
||||||
|
/// Register for permission monitoring (call when a view appears)
|
||||||
|
func registerForMonitoring() {
|
||||||
|
monitorRegistrationCount += 1
|
||||||
|
logger.debug("Registered for monitoring, count: \(self.monitorRegistrationCount)")
|
||||||
|
|
||||||
|
if monitorRegistrationCount == 1 {
|
||||||
|
// First registration, start monitoring
|
||||||
|
startMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unregister from permission monitoring (call when a view disappears)
|
||||||
|
func unregisterFromMonitoring() {
|
||||||
|
monitorRegistrationCount = max(0, monitorRegistrationCount - 1)
|
||||||
|
logger.debug("Unregistered from monitoring, count: \(self.monitorRegistrationCount)")
|
||||||
|
|
||||||
|
if monitorRegistrationCount == 0 {
|
||||||
|
// No more registrations, stop monitoring
|
||||||
|
stopMonitoring()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startMonitoring() {
|
||||||
|
logger.info("Starting permission monitoring")
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
Task {
|
||||||
|
await checkAllPermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start timer for periodic checks
|
||||||
|
monitorTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
||||||
|
guard let self else { return }
|
||||||
|
Task { @MainActor in
|
||||||
|
await self.checkAllPermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopMonitoring() {
|
||||||
|
logger.info("Stopping permission monitoring")
|
||||||
|
monitorTimer?.invalidate()
|
||||||
|
monitorTimer = nil
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Permission Checking
|
// MARK: - Permission Checking
|
||||||
|
|
||||||
func checkAllPermissions() async {
|
func checkAllPermissions() async {
|
||||||
|
let oldPermissions = permissions
|
||||||
|
|
||||||
// Check each permission type
|
// Check each permission type
|
||||||
permissions[.appleScript] = await checkAppleScriptPermission()
|
permissions[.appleScript] = await checkAppleScriptPermission()
|
||||||
permissions[.screenRecording] = checkScreenRecordingPermission()
|
permissions[.screenRecording] = checkScreenRecordingPermission()
|
||||||
permissions[.accessibility] = checkAccessibilityPermission()
|
permissions[.accessibility] = checkAccessibilityPermission()
|
||||||
|
|
||||||
|
// Post notification if any permissions changed
|
||||||
|
if oldPermissions != permissions {
|
||||||
|
NotificationCenter.default.post(name: .permissionsUpdated, object: nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - AppleScript Permission
|
// MARK: - AppleScript Permission
|
||||||
|
|
|
||||||
|
|
@ -130,14 +130,20 @@ private struct PermissionsSection: View {
|
||||||
@State private var permissionManager = SystemPermissionManager.shared
|
@State private var permissionManager = SystemPermissionManager.shared
|
||||||
@State private var permissionUpdateTrigger = 0
|
@State private var permissionUpdateTrigger = 0
|
||||||
|
|
||||||
|
// IMPORTANT: These computed properties ensure the UI always shows current permission state.
|
||||||
|
// The permissionUpdateTrigger dependency forces SwiftUI to re-evaluate these properties
|
||||||
|
// when permissions change. Without this, the UI would not update when permissions are
|
||||||
|
// granted in System Settings while this view is visible.
|
||||||
|
//
|
||||||
|
// We use computed properties instead of @State to avoid UI flashing - the initial
|
||||||
|
// permission check in .task happens before the first render, ensuring correct state
|
||||||
|
// from the start.
|
||||||
private var hasAppleScriptPermission: Bool {
|
private var hasAppleScriptPermission: Bool {
|
||||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
|
||||||
_ = permissionUpdateTrigger
|
_ = permissionUpdateTrigger
|
||||||
return permissionManager.hasPermission(.appleScript)
|
return permissionManager.hasPermission(.appleScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var hasAccessibilityPermission: Bool {
|
private var hasAccessibilityPermission: Bool {
|
||||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
|
||||||
_ = permissionUpdateTrigger
|
_ = permissionUpdateTrigger
|
||||||
return permissionManager.hasPermission(.accessibility)
|
return permissionManager.hasPermission(.accessibility)
|
||||||
}
|
}
|
||||||
|
|
@ -232,13 +238,19 @@ private struct PermissionsSection: View {
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
|
|
||||||
// Force a re-render to check permissions
|
|
||||||
permissionUpdateTrigger += 1
|
|
||||||
}
|
|
||||||
.task {
|
.task {
|
||||||
// Check all permissions
|
// Check permissions before first render to avoid UI flashing
|
||||||
await permissionManager.checkAllPermissions()
|
await permissionManager.checkAllPermissions()
|
||||||
|
|
||||||
|
// Register for continuous monitoring
|
||||||
|
permissionManager.registerForMonitoring()
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
permissionManager.unregisterFromMonitoring()
|
||||||
|
}
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .permissionsUpdated)) { _ in
|
||||||
|
// Increment trigger to force computed property re-evaluation
|
||||||
|
permissionUpdateTrigger += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,20 @@ struct RequestPermissionsPageView: View {
|
||||||
@State private var permissionManager = SystemPermissionManager.shared
|
@State private var permissionManager = SystemPermissionManager.shared
|
||||||
@State private var permissionUpdateTrigger = 0
|
@State private var permissionUpdateTrigger = 0
|
||||||
|
|
||||||
|
// IMPORTANT: These computed properties ensure the UI always shows current permission state.
|
||||||
|
// The permissionUpdateTrigger dependency forces SwiftUI to re-evaluate these properties
|
||||||
|
// when permissions change. Without this, the UI would not update when permissions are
|
||||||
|
// granted in System Settings while this view is visible.
|
||||||
|
//
|
||||||
|
// We use computed properties instead of @State to avoid UI flashing - the initial
|
||||||
|
// permission check in .task happens before the first render, ensuring correct state
|
||||||
|
// from the start.
|
||||||
private var hasAppleScriptPermission: Bool {
|
private var hasAppleScriptPermission: Bool {
|
||||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
|
||||||
_ = permissionUpdateTrigger
|
_ = permissionUpdateTrigger
|
||||||
return permissionManager.hasPermission(.appleScript)
|
return permissionManager.hasPermission(.appleScript)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var hasAccessibilityPermission: Bool {
|
private var hasAccessibilityPermission: Bool {
|
||||||
// This will cause a re-read whenever permissionUpdateTrigger changes
|
|
||||||
_ = permissionUpdateTrigger
|
_ = permissionUpdateTrigger
|
||||||
return permissionManager.hasPermission(.accessibility)
|
return permissionManager.hasPermission(.accessibility)
|
||||||
}
|
}
|
||||||
|
|
@ -102,13 +108,19 @@ struct RequestPermissionsPageView: View {
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.padding()
|
.padding()
|
||||||
.onReceive(Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()) { _ in
|
|
||||||
// Force a re-render to check permissions
|
|
||||||
permissionUpdateTrigger += 1
|
|
||||||
}
|
|
||||||
.task {
|
.task {
|
||||||
// Check all permissions
|
// Check permissions before first render to avoid UI flashing
|
||||||
await permissionManager.checkAllPermissions()
|
await permissionManager.checkAllPermissions()
|
||||||
|
|
||||||
|
// Register for continuous monitoring
|
||||||
|
permissionManager.registerForMonitoring()
|
||||||
|
}
|
||||||
|
.onDisappear {
|
||||||
|
permissionManager.unregisterFromMonitoring()
|
||||||
|
}
|
||||||
|
.onReceive(NotificationCenter.default.publisher(for: .permissionsUpdated)) { _ in
|
||||||
|
// Increment trigger to force computed property re-evaluation
|
||||||
|
permissionUpdateTrigger += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue