Files
traccar-client-macos/TraccarClient/Interface/WindowFocusController.swift
T
jayanezj 026cd6aa40 feat: persist local configuration and device identity (#3)
Store receiver and identity together in Keychain, with versioned tracking
preferences, explicit authorization retry and stable Settings focus.
Adopt free Apple Development locally and credential-free ad hoc CI.
Run configuration and focus tests through both CI toolchains.

Final decisions: https://github.com/jayanezj/traccar-client-macos/issues/3#issuecomment-6396087703
2026-10-25 10:43:43 +01:00

64 lines
2.4 KiB
Swift

import AppKit
@MainActor
protocol FocusRestorationWindow: AnyObject {
var isVisible: Bool { get }
var isMiniaturized: Bool { get }
func restoreKeyWindow()
}
extension NSWindow: FocusRestorationWindow {
func restoreKeyWindow() { makeKeyAndOrderFront(nil) }
}
@MainActor
final class WindowFocusController {
// A workspace activation sends a reopen event. Its window is selected only after activation finishes.
private(set) static var pendingActivations = 0
weak var window: (any FocusRestorationWindow)?
private var restoration: Task<Void, Never>?
private let activate: @MainActor () async -> Bool
init(activate: @escaping @MainActor () async -> Bool = activateCurrentApplication) {
self.activate = activate
}
@discardableResult
func perform(_ action: () -> Void) -> Task<Void, Never>? {
restoration?.cancel()
let initiatingWindow = window
action()
guard let initiatingWindow, initiatingWindow.isVisible, !initiatingWindow.isMiniaturized else { return nil }
let activate = activate
restoration = Task { [weak initiatingWindow] in
guard !Task.isCancelled, let initiatingWindow,
initiatingWindow.isVisible, !initiatingWindow.isMiniaturized else { return }
Self.pendingActivations += 1
defer { Self.pendingActivations -= 1 }
guard await activate(), !Task.isCancelled,
initiatingWindow.isVisible, !initiatingWindow.isMiniaturized else { return }
initiatingWindow.restoreKeyWindow()
}
return restoration
}
private static func activateCurrentApplication() async -> Bool {
let configuration = NSWorkspace.OpenConfiguration()
configuration.activates = true
configuration.createsNewApplicationInstance = false
configuration.allowsRunningApplicationSubstitution = false
configuration.addsToRecentItems = false
configuration.promptsUserIfNeeded = false
do {
// Launch Services supplies activation context that a plain NSApp.activate() cannot guarantee.
let application = try await NSWorkspace.shared.openApplication(
at: Bundle.main.bundleURL, configuration: configuration
)
return application.processIdentifier == ProcessInfo.processInfo.processIdentifier
} catch {
return false
}
}
}