mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: the approval dialog is now forever
This commit is contained in:
@ -46,8 +46,9 @@ func requestIntentPermission() async -> Bool {
|
||||
|
||||
PermissionRequest.show(
|
||||
"org.mitchellh.ghostty.shortcutsPermission",
|
||||
message: "Allow Shortcuts to interact with Ghostty for the next 10 minutes?",
|
||||
allowDuration: .seconds(600),
|
||||
message: "Allow Shortcuts to interact with Ghostty?",
|
||||
allowDuration: .forever,
|
||||
rememberDuration: nil,
|
||||
) { response in
|
||||
continuation.resume(returning: response)
|
||||
}
|
||||
|
@ -3,17 +3,25 @@ import Foundation
|
||||
|
||||
/// Displays a permission request dialog with optional caching of user decisions
|
||||
class PermissionRequest {
|
||||
/// Specifies how long a permission decision should be cached
|
||||
enum AllowDuration {
|
||||
case once
|
||||
case forever
|
||||
case duration(Duration)
|
||||
}
|
||||
|
||||
/// Shows a permission request dialog with customizable caching behavior
|
||||
/// - Parameters:
|
||||
/// - key: Unique identifier for storing/retrieving cached decisions in UserDefaults
|
||||
/// - message: The message to display in the alert dialog
|
||||
/// - allowText: Custom text for the allow button (defaults to "Allow")
|
||||
/// - allowDuration: If provided, automatically cache "Allow" responses for this duration
|
||||
/// - rememberDuration: If provided, shows a checkbox to remember the decision for this duration
|
||||
/// - window: If provided, shows the alert as a sheet attached to this window
|
||||
/// - completion: Called with the user's decision (true for allow, false for deny)
|
||||
///
|
||||
/// Caching behavior:
|
||||
/// - If user checks "Remember my decision for one day", both allow/deny are cached for 24 hours
|
||||
/// - If rememberDuration is provided and user checks "Remember my decision", both allow/deny are cached for that duration
|
||||
/// - If allowDuration is provided and user selects allow (without checkbox), decision is cached for that duration
|
||||
/// - Cached decisions are automatically returned without showing the dialog
|
||||
@MainActor
|
||||
@ -22,7 +30,8 @@ class PermissionRequest {
|
||||
message: String,
|
||||
informative: String = "",
|
||||
allowText: String = "Allow",
|
||||
allowDuration: Duration? = nil,
|
||||
allowDuration: AllowDuration = .once,
|
||||
rememberDuration: Duration? = .seconds(86400),
|
||||
window: NSWindow? = nil,
|
||||
completion: @escaping (Bool) -> Void
|
||||
) {
|
||||
@ -41,24 +50,28 @@ class PermissionRequest {
|
||||
alert.addButton(withTitle: allowText)
|
||||
alert.addButton(withTitle: "Don't Allow")
|
||||
|
||||
// Create checkbox for remembering
|
||||
let checkbox = NSButton(
|
||||
checkboxWithTitle: "Remember my decision for one day",
|
||||
// Create checkbox for remembering if duration is provided
|
||||
var checkbox: NSButton?
|
||||
if let rememberDuration = rememberDuration {
|
||||
let checkboxTitle = formatRememberText(for: rememberDuration)
|
||||
checkbox = NSButton(
|
||||
checkboxWithTitle: checkboxTitle,
|
||||
target: nil,
|
||||
action: nil)
|
||||
checkbox.state = .off
|
||||
checkbox!.state = .off
|
||||
|
||||
// Set checkbox as accessory view
|
||||
alert.accessoryView = checkbox
|
||||
}
|
||||
|
||||
// Show the alert
|
||||
if let window = window {
|
||||
alert.beginSheetModal(for: window) { response in
|
||||
handleResponse(response, rememberDecision: checkbox.state == .on, key: key, allowDuration: allowDuration, completion: completion)
|
||||
handleResponse(response, rememberDecision: checkbox?.state == .on, key: key, allowDuration: allowDuration, rememberDuration: rememberDuration, completion: completion)
|
||||
}
|
||||
} else {
|
||||
let response = alert.runModal()
|
||||
handleResponse(response, rememberDecision: checkbox.state == .on, key: key, allowDuration: allowDuration, completion: completion)
|
||||
handleResponse(response, rememberDecision: checkbox?.state == .on, key: key, allowDuration: allowDuration, rememberDuration: rememberDuration, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,12 +81,14 @@ class PermissionRequest {
|
||||
/// - rememberDecision: Whether the remember checkbox was checked
|
||||
/// - key: The UserDefaults key for caching
|
||||
/// - allowDuration: Optional duration for auto-caching allow responses
|
||||
/// - rememberDuration: Optional duration for the remember checkbox
|
||||
/// - completion: Completion handler to call with the result
|
||||
private static func handleResponse(
|
||||
_ response: NSApplication.ModalResponse,
|
||||
rememberDecision: Bool,
|
||||
key: String,
|
||||
allowDuration: Duration?,
|
||||
allowDuration: AllowDuration,
|
||||
rememberDuration: Duration?,
|
||||
completion: @escaping (Bool) -> Void) {
|
||||
|
||||
let result: Bool
|
||||
@ -87,10 +102,21 @@ class PermissionRequest {
|
||||
}
|
||||
|
||||
// Store the result if checkbox is checked or if "Allow" was selected and allowDuration is set
|
||||
if rememberDecision {
|
||||
storeResult(result, for: key, duration: .seconds(86400))
|
||||
} else if result, let allowDuration {
|
||||
storeResult(result, for: key, duration: allowDuration)
|
||||
if rememberDecision, let rememberDuration = rememberDuration {
|
||||
storeResult(result, for: key, duration: rememberDuration)
|
||||
} else if result {
|
||||
switch allowDuration {
|
||||
case .once:
|
||||
// Don't store anything for once
|
||||
break
|
||||
case .forever:
|
||||
// Store for a very long time (100 years). When the bug comes in that
|
||||
// 100 years has passed and their forever permission expired I'll be
|
||||
// dead so it won't be my problem.
|
||||
storeResult(result, for: key, duration: .seconds(3153600000))
|
||||
case .duration(let duration):
|
||||
storeResult(result, for: key, duration: duration)
|
||||
}
|
||||
}
|
||||
|
||||
completion(result)
|
||||
@ -130,6 +156,31 @@ class PermissionRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Formats the remember checkbox text based on the duration
|
||||
/// - Parameter duration: The duration to format
|
||||
/// - Returns: A human-readable string for the checkbox
|
||||
private static func formatRememberText(for duration: Duration) -> String {
|
||||
let seconds = duration.timeInterval
|
||||
|
||||
// Warning: this probably isn't localization friendly at all so we're
|
||||
// going to have to redo this for that.
|
||||
switch seconds {
|
||||
case 0..<60:
|
||||
return "Remember my decision for \(Int(seconds)) seconds"
|
||||
case 60..<3600:
|
||||
let minutes = Int(seconds / 60)
|
||||
return "Remember my decision for \(minutes) minute\(minutes == 1 ? "" : "s")"
|
||||
case 3600..<86400:
|
||||
let hours = Int(seconds / 3600)
|
||||
return "Remember my decision for \(hours) hour\(hours == 1 ? "" : "s")"
|
||||
case 86400:
|
||||
return "Remember my decision for one day"
|
||||
default:
|
||||
let days = Int(seconds / 86400)
|
||||
return "Remember my decision for \(days) day\(days == 1 ? "" : "s")"
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal class for storing permission decisions with expiration dates in UserDefaults
|
||||
/// Conforms to NSSecureCoding for safe archiving/unarchiving
|
||||
@objc(StoredPermission)
|
||||
|
@ -2368,10 +2368,9 @@ keybind: Keybinds = .{},
|
||||
///
|
||||
/// Valid values are:
|
||||
///
|
||||
/// * `ask` - Ask the user whether for permission. Ghostty will by default
|
||||
/// cache the user's choice for 10 minutes since we can't determine
|
||||
/// when a single workflow begins or ends. The user also has an option
|
||||
/// in the GUI to allow for the remainder of the day.
|
||||
/// * `ask` - Ask the user whether for permission. Ghostty will remember
|
||||
/// this choice and never ask again. This is similar to other macOS
|
||||
/// permissions such as microphone access, camera access, etc.
|
||||
///
|
||||
/// * `allow` - Allow Shortcuts to control Ghostty without asking.
|
||||
///
|
||||
|
Reference in New Issue
Block a user