mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Update BetterDisplay
This commit is contained in:
parent
dd890bd5e5
commit
d8ca8ceb02
3 changed files with 111 additions and 22 deletions
|
|
@ -4795,6 +4795,9 @@
|
|||
"BetterDisplay Integration" : {
|
||||
"comment" : "The header text for the \"BetterDisplay Integration\" section in the settings view.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"BetterDisplay is installed but not currently running. Launch BetterDisplay to enable integration." : {
|
||||
|
||||
},
|
||||
"BetterDisplay OSD events will be routed through Atoll's active HUD style. Make sure BetterDisplay's OSD integration is enabled in its Settings › Application › Integration." : {
|
||||
|
||||
|
|
@ -8517,10 +8520,6 @@
|
|||
"comment" : "Name of a user profile for a designer.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Detected" : {
|
||||
"comment" : "A label indicating that BetterDisplay is detected on the system.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Detection Status" : {
|
||||
"comment" : "The header for a section that displays whether the screen recording detection feature is currently active or inactive.",
|
||||
"isCommentAutoGenerated" : true,
|
||||
|
|
@ -17410,9 +17409,6 @@
|
|||
"Not charging" : {
|
||||
"comment" : "Text displayed in the status text when the battery is not charging.",
|
||||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Not detected" : {
|
||||
|
||||
},
|
||||
"Not Now" : {
|
||||
"localizations" : {
|
||||
|
|
|
|||
|
|
@ -1934,6 +1934,26 @@ private struct BetterDisplayIntegrationSection: View {
|
|||
SettingsTab.hudAndOSD.highlightID(for: title)
|
||||
}
|
||||
|
||||
private var statusText: String {
|
||||
if betterDisplayManager.isRunning {
|
||||
return "Running"
|
||||
} else if betterDisplayManager.isDetected {
|
||||
return "Not running"
|
||||
} else {
|
||||
return "Not detected"
|
||||
}
|
||||
}
|
||||
|
||||
private var statusColor: Color {
|
||||
if betterDisplayManager.isRunning {
|
||||
return .green
|
||||
} else if betterDisplayManager.isDetected {
|
||||
return .orange
|
||||
} else {
|
||||
return .secondary
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
|
|
@ -1941,9 +1961,9 @@ private struct BetterDisplayIntegrationSection: View {
|
|||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("BetterDisplay")
|
||||
.font(.system(size: 13, weight: .medium))
|
||||
Text(betterDisplayManager.isDetected ? "Detected" : "Not detected")
|
||||
Text(statusText)
|
||||
.font(.caption)
|
||||
.foregroundStyle(betterDisplayManager.isDetected ? .green : .secondary)
|
||||
.foregroundStyle(statusColor)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
|
@ -1959,6 +1979,10 @@ private struct BetterDisplayIntegrationSection: View {
|
|||
Text("Install [BetterDisplay](https://betterdisplay.pro) to control external display brightness and volume through Atoll's HUD.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
} else if !betterDisplayManager.isRunning {
|
||||
Text("BetterDisplay is installed but not currently running. Launch BetterDisplay to enable integration.")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
} else if enableBetterDisplayIntegration {
|
||||
Text("BetterDisplay OSD events will be routed through Atoll's active HUD style. Make sure BetterDisplay's OSD integration is enabled in its Settings › Application › Integration.")
|
||||
.font(.caption)
|
||||
|
|
|
|||
|
|
@ -93,23 +93,33 @@ final class BetterDisplayManager: ObservableObject {
|
|||
private static let osdNotificationName = NSNotification.Name("com.betterdisplay.BetterDisplay.osd")
|
||||
private static let requestNotificationName = NSNotification.Name("com.betterdisplay.BetterDisplay.request")
|
||||
private static let responseNotificationName = NSNotification.Name("com.betterdisplay.BetterDisplay.response")
|
||||
private static let launchedNotificationName = NSNotification.Name("pro.betterdisplay.BetterDisplay.launched")
|
||||
private static let terminatedNotificationName = NSNotification.Name("pro.betterdisplay.BetterDisplay.terminated")
|
||||
|
||||
// MARK: Published state
|
||||
|
||||
/// Whether BetterDisplay is currently detected on this machine.
|
||||
/// Whether BetterDisplay is currently detected (installed) on this machine.
|
||||
@Published private(set) var isDetected: Bool = false
|
||||
|
||||
/// Whether BetterDisplay is currently running and ready for communication.
|
||||
@Published private(set) var isRunning: Bool = false
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var osdObserver: NSObjectProtocol?
|
||||
private var responseObserver: NSObjectProtocol?
|
||||
private var workspaceObserver: NSObjectProtocol?
|
||||
private var workspaceTermObserver: NSObjectProtocol?
|
||||
private var launchedObserver: NSObjectProtocol?
|
||||
private var terminatedObserver: NSObjectProtocol?
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
private weak var coordinator: DynamicIslandViewCoordinator?
|
||||
|
||||
private init() {
|
||||
isDetected = Self.checkInstallation()
|
||||
isRunning = Self.checkRunning()
|
||||
setupWorkspaceObserver()
|
||||
setupLifecycleObservers()
|
||||
setupSettingsObserver()
|
||||
}
|
||||
|
||||
|
|
@ -119,8 +129,8 @@ final class BetterDisplayManager: ObservableObject {
|
|||
func configure(coordinator: DynamicIslandViewCoordinator) {
|
||||
self.coordinator = coordinator
|
||||
|
||||
// Start listening if integration is enabled and BetterDisplay is detected
|
||||
if Defaults[.enableBetterDisplayIntegration] && isDetected {
|
||||
// Start listening if integration is enabled and BetterDisplay is running
|
||||
if Defaults[.enableBetterDisplayIntegration] && isRunning {
|
||||
startListening()
|
||||
}
|
||||
}
|
||||
|
|
@ -128,18 +138,19 @@ final class BetterDisplayManager: ObservableObject {
|
|||
/// Refresh detection status (e.g. after app install/uninstall).
|
||||
func refreshDetectionStatus() {
|
||||
let wasDetected = isDetected
|
||||
let wasRunning = isRunning
|
||||
isDetected = Self.checkInstallation()
|
||||
if wasDetected && !isDetected {
|
||||
isRunning = Self.checkRunning()
|
||||
if wasRunning && !isRunning {
|
||||
stopListening()
|
||||
} else if !wasDetected && isDetected && Defaults[.enableBetterDisplayIntegration] {
|
||||
} else if !wasRunning && isRunning && Defaults[.enableBetterDisplayIntegration] {
|
||||
startListening()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Detection
|
||||
|
||||
/// Check if BetterDisplay is installed by looking for its bundle ID in running apps
|
||||
/// and falling back to NSWorkspace URL lookup.
|
||||
/// Check if BetterDisplay is installed by looking for its bundle ID.
|
||||
static func checkInstallation() -> Bool {
|
||||
// Check running apps first (fast path)
|
||||
if NSWorkspace.shared.runningApplications.contains(where: { $0.bundleIdentifier == bundleID }) {
|
||||
|
|
@ -154,6 +165,11 @@ final class BetterDisplayManager: ObservableObject {
|
|||
return false
|
||||
}
|
||||
|
||||
/// Check if BetterDisplay is currently running.
|
||||
static func checkRunning() -> Bool {
|
||||
NSWorkspace.shared.runningApplications.contains(where: { $0.bundleIdentifier == bundleID })
|
||||
}
|
||||
|
||||
// MARK: - OSD Listening
|
||||
|
||||
private func startListening() {
|
||||
|
|
@ -183,7 +199,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
// MARK: - OSD Handling
|
||||
|
||||
private func handleOSDNotification(_ notification: Notification) {
|
||||
guard Defaults[.enableBetterDisplayIntegration] else { return }
|
||||
guard Defaults[.enableBetterDisplayIntegration], isRunning else { return }
|
||||
|
||||
guard let notificationString = notification.object as? String else {
|
||||
NSLog("⚠️ BetterDisplay OSD: unexpected notification format")
|
||||
|
|
@ -345,6 +361,12 @@ final class BetterDisplayManager: ObservableObject {
|
|||
parameters: [String: String?],
|
||||
completion: (@MainActor @Sendable (BetterDisplayResponseData?) -> Void)? = nil
|
||||
) {
|
||||
guard isRunning else {
|
||||
NSLog("⚠️ BetterDisplay sendRequest skipped — app is not running")
|
||||
completion?(nil)
|
||||
return
|
||||
}
|
||||
|
||||
let uuid = UUID().uuidString
|
||||
let request = BetterDisplayRequestData(uuid: uuid, commands: commands, parameters: parameters)
|
||||
|
||||
|
|
@ -429,16 +451,54 @@ final class BetterDisplayManager: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
// Also observe app termination
|
||||
NSWorkspace.shared.notificationCenter.addObserver(
|
||||
// Also observe app termination — handles crashes and force-quits
|
||||
// (orderly quits are caught by the lifecycle observer below)
|
||||
workspaceTermObserver = NSWorkspace.shared.notificationCenter.addObserver(
|
||||
forName: NSWorkspace.didTerminateApplicationNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { notification in
|
||||
) { [weak self] notification in
|
||||
guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,
|
||||
app.bundleIdentifier == betterDisplayBundleID
|
||||
else { return }
|
||||
// Don't clear isDetected on termination — app is still installed, just not running.
|
||||
Task { @MainActor in
|
||||
NSLog("🔴 BetterDisplay terminated (workspace notification)")
|
||||
self?.isRunning = false
|
||||
self?.stopListening()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Lifecycle Observers (BetterDisplay launched/terminated notifications)
|
||||
|
||||
/// Listen for distributed notifications sent by BetterDisplay itself
|
||||
/// to know when it becomes ready and when it shuts down cleanly.
|
||||
private func setupLifecycleObservers() {
|
||||
launchedObserver = DistributedNotificationCenter.default().addObserver(
|
||||
forName: Self.launchedNotificationName,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
NSLog("🟢 BetterDisplay launched notification received")
|
||||
self?.isDetected = true
|
||||
self?.isRunning = true
|
||||
if Defaults[.enableBetterDisplayIntegration] {
|
||||
self?.startListening()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
terminatedObserver = DistributedNotificationCenter.default().addObserver(
|
||||
forName: Self.terminatedNotificationName,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
Task { @MainActor in
|
||||
NSLog("🔴 BetterDisplay terminated notification received")
|
||||
self?.isRunning = false
|
||||
self?.stopListening()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -449,7 +509,7 @@ final class BetterDisplayManager: ObservableObject {
|
|||
.sink { [weak self] change in
|
||||
Task { @MainActor in
|
||||
guard let self else { return }
|
||||
if change.newValue && self.isDetected {
|
||||
if change.newValue && self.isRunning {
|
||||
self.startListening()
|
||||
} else {
|
||||
self.stopListening()
|
||||
|
|
@ -463,9 +523,18 @@ final class BetterDisplayManager: ObservableObject {
|
|||
if let osdObserver {
|
||||
DistributedNotificationCenter.default().removeObserver(osdObserver)
|
||||
}
|
||||
if let launchedObserver {
|
||||
DistributedNotificationCenter.default().removeObserver(launchedObserver)
|
||||
}
|
||||
if let terminatedObserver {
|
||||
DistributedNotificationCenter.default().removeObserver(terminatedObserver)
|
||||
}
|
||||
if let workspaceObserver {
|
||||
NSWorkspace.shared.notificationCenter.removeObserver(workspaceObserver)
|
||||
}
|
||||
if let workspaceTermObserver {
|
||||
NSWorkspace.shared.notificationCenter.removeObserver(workspaceTermObserver)
|
||||
}
|
||||
cancellables.removeAll()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue