Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Sources/DefiDaemon/DaemonCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ extension Daemon {
}

func enqueueHotKey(_ invocation: HotKeyInvocation) {
guard desktopSessionActive else { return }
guard pendingHotKeyCommands.count < 64 else { return }
pendingHotKeyCommands.append(invocation)
processPendingHotKeys()
Expand Down
19 changes: 18 additions & 1 deletion Sources/DefiDaemon/DaemonDesktopSynchronization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ extension Daemon {
forceApplicationInventoryRefresh: Bool = false,
consumePeriodicWindowRefresh: Bool = false
) {
guard desktopSessionActive else { return }
let sessionGeneration = desktopSessionGeneration
let nativeFocusWasPending = platform.hasPendingNativeFocusEvent
if desktopSnapshotInFlight {
supersededDesktopSnapshotRequest = (
Expand All @@ -56,7 +58,22 @@ extension Daemon {
forceWindowListRefresh: forceWindowListRefresh,
forceApplicationInventoryRefresh: forceApplicationInventoryRefresh
) { [weak self] snapshot in
self?.applyDesktopSnapshot(
guard let self else { return }
guard desktopSessionActive,
desktopSessionGeneration == sessionGeneration
else {
desktopSnapshotInFlight = false
supersededDesktopSnapshotRequest = nil
if desktopSessionActive {
synchronizeDesktop(
forceFullWindowRefresh: true,
forceWindowListRefresh: true,
forceApplicationInventoryRefresh: true
)
}
return
}
applyDesktopSnapshot(
snapshot,
nativeFocusWasPending: nativeFocusWasPending,
forceFullWindowRefresh: forceFullWindowRefresh,
Expand Down
2 changes: 1 addition & 1 deletion Sources/DefiDaemon/DaemonStatusLifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ extension Daemon {
"\($0.id.rawValue):\(Int($0.frame.width))x\(Int($0.frame.height))"
}.joined(separator: ",")
return
"running monitors=\(state.monitors.count)[\(displaySizes)] windows=\(managedCount) floating=\(floatingCount) workspace=\(workspace) focused=\(focused) fullscreenSuspended=\(state.nativeFullscreenWindowIDs.count)[\(fullscreenWindowIDs)] columnWidth=\(focusedColumnState) menuBar=\(menuBar == nil ? "missing" : "installed") hotkeys=\(hotKeyState) bindings=\(bindingCount) captured=\(capturedHotKeyCount) processed=\(processedHotKeyCount) queued=\(pendingHotKeyCommands.count) tapReenables=\(tapReenableCount) overview=\(overviewOpen ? "open" : "closed") overviewPanels=\(overviewPanels) overviewPreviewConfig=\(config.overview.windowPreviews) overviewPermission=\(overviewPermission) overviewCaptures=\(overviewCaptures) overviewPreviews=\(overviewPreviews) overviewPreviewCacheMiB=\(overviewPreviewCacheMiB) overviewPreviewFailures=\(overviewPreviewFailures) events=\(observedPlatformEventCount) focusDedup=\(ignoredRedundantNativeFocusCount) closeFocusPreserved=\(preservedWindowRemovalFocusCount) displayEvents=\(displayConfigurationEventCount) displayRetries=\(pendingDisplaySyncDeadlines.count) drift=\(targetMismatchCount)[\(driftDetails)] resize=\(resize) visibility=\(visibility) hidden=\(platform.hiddenWindowCount) borders=\(borders.visible) borderNodes=\(borders.allocated) borderDormant=\(borders.dormant) borderOpacity=\(borderOpacity) borderSurfaceMiB=\(borderSurfaceMiB) borderCapture=\(borders.captureEnabled) borderPlans=\(borders.appliedPlans) borderSkips=\(borders.skippedPlans) borderGeometry=\(borders.geometryUpdates) snapshots=\(snapshotPerformance.full)/\(snapshotPerformance.incremental)/\(snapshotPerformance.cached) appInventories=\(snapshotPerformance.applicationInventories) snapshotMs=\(snapshotMS) snapshotMaxMs=\(snapshotMaxMS) snapshotCG=\(snapshotPerformance.cgCopies)/\(snapshotCGMS)/\(snapshotCGMaxMS) axReads=\(attributeReads.batched)/\(attributeReads.fallback) parkingChecks=\(parking.checks) parkingRepairs=\(parking.repairs) initialChecks=\(initialSettlement.checks) initialRepairs=\(initialSettlement.repairs) settling=\(frameCommit.settling) deferredCommits=\(frameCommit.deferred) observedCommits=\(frameCommit.observed) observedCommitMaxMs=\(observedCommitMaxMS) slowApps=\(platform.latencySensitiveProcessCount) slowAppDetails=[\(platform.latencySensitiveProcessDescription)] axAppDetails=[\(platform.processLatencyDescription)] posWrites=\(platform.successfulPositionWriteCount) stalePos=\(platform.skippedStalePositionWriteCount) droppedFrames=\(platform.droppedPositionFrameCount) displayedRebases=\(displayedFrameRebaseCount) displayedDelta=\(displayedRebaseDelta) sizeWrites=\(platform.successfulSizeWriteCount) displayHz=\(displayHz) timerHz=\(timerHz) axPending=\(platform.hasPendingAnimatedFrameWrites) axFrameMs=\(axFrameMS) axFrameMaxMs=\(axFrameMaxMS) axSlowFrames=\(axFramePerformance.slowFrames) focusPending=\(platform.hasPendingFocusWrite) focusFast=\(focusPerformance.fastPaths) focusCancelled=\(focusPerformance.cancelled) focusRetries=\(focusPerformance.retries) focusMainMs=\(focusMainMS) focusRaiseMs=\(focusRaiseMS) focusActivateMs=\(focusActivateMS) animating=\(platform.hasPendingAnimatedFrameWrites) animationFrames=\(axFramePerformance.animationFrames) animationMs=\(coordinatorAnimationMS) commandMs=\(commandMS) frameMs=\(frameMS) focusMs=\(focusMS)"
"running session=\(desktopSessionActive ? "active" : "inactive") monitors=\(state.monitors.count)[\(displaySizes)] windows=\(managedCount) floating=\(floatingCount) workspace=\(workspace) focused=\(focused) fullscreenSuspended=\(state.nativeFullscreenWindowIDs.count)[\(fullscreenWindowIDs)] columnWidth=\(focusedColumnState) menuBar=\(menuBar == nil ? "missing" : "installed") hotkeys=\(hotKeyState) bindings=\(bindingCount) captured=\(capturedHotKeyCount) processed=\(processedHotKeyCount) queued=\(pendingHotKeyCommands.count) tapReenables=\(tapReenableCount) overview=\(overviewOpen ? "open" : "closed") overviewPanels=\(overviewPanels) overviewPreviewConfig=\(config.overview.windowPreviews) overviewPermission=\(overviewPermission) overviewCaptures=\(overviewCaptures) overviewPreviews=\(overviewPreviews) overviewPreviewCacheMiB=\(overviewPreviewCacheMiB) overviewPreviewFailures=\(overviewPreviewFailures) events=\(observedPlatformEventCount) focusDedup=\(ignoredRedundantNativeFocusCount) closeFocusPreserved=\(preservedWindowRemovalFocusCount) displayEvents=\(displayConfigurationEventCount) displayRetries=\(pendingDisplaySyncDeadlines.count) drift=\(targetMismatchCount)[\(driftDetails)] resize=\(resize) visibility=\(visibility) hidden=\(platform.hiddenWindowCount) borders=\(borders.visible) borderNodes=\(borders.allocated) borderDormant=\(borders.dormant) borderOpacity=\(borderOpacity) borderSurfaceMiB=\(borderSurfaceMiB) borderCapture=\(borders.captureEnabled) borderPlans=\(borders.appliedPlans) borderSkips=\(borders.skippedPlans) borderGeometry=\(borders.geometryUpdates) snapshots=\(snapshotPerformance.full)/\(snapshotPerformance.incremental)/\(snapshotPerformance.cached) appInventories=\(snapshotPerformance.applicationInventories) snapshotMs=\(snapshotMS) snapshotMaxMs=\(snapshotMaxMS) snapshotCG=\(snapshotPerformance.cgCopies)/\(snapshotCGMS)/\(snapshotCGMaxMS) axReads=\(attributeReads.batched)/\(attributeReads.fallback) parkingChecks=\(parking.checks) parkingRepairs=\(parking.repairs) initialChecks=\(initialSettlement.checks) initialRepairs=\(initialSettlement.repairs) settling=\(frameCommit.settling) deferredCommits=\(frameCommit.deferred) observedCommits=\(frameCommit.observed) observedCommitMaxMs=\(observedCommitMaxMS) slowApps=\(platform.latencySensitiveProcessCount) slowAppDetails=[\(platform.latencySensitiveProcessDescription)] axAppDetails=[\(platform.processLatencyDescription)] posWrites=\(platform.successfulPositionWriteCount) stalePos=\(platform.skippedStalePositionWriteCount) droppedFrames=\(platform.droppedPositionFrameCount) displayedRebases=\(displayedFrameRebaseCount) displayedDelta=\(displayedRebaseDelta) sizeWrites=\(platform.successfulSizeWriteCount) displayHz=\(displayHz) timerHz=\(timerHz) axPending=\(platform.hasPendingAnimatedFrameWrites) axFrameMs=\(axFrameMS) axFrameMaxMs=\(axFrameMaxMS) axSlowFrames=\(axFramePerformance.slowFrames) focusPending=\(platform.hasPendingFocusWrite) focusFast=\(focusPerformance.fastPaths) focusCancelled=\(focusPerformance.cancelled) focusRetries=\(focusPerformance.retries) focusMainMs=\(focusMainMS) focusRaiseMs=\(focusRaiseMS) focusActivateMs=\(focusActivateMS) animating=\(platform.hasPendingAnimatedFrameWrites) animationFrames=\(axFramePerformance.animationFrames) animationMs=\(coordinatorAnimationMS) commandMs=\(commandMS) frameMs=\(frameMS) focusMs=\(focusMS)"
+ " topologyObservers=\(platform.hasReliableWindowTopologyObservation) appWindowLists=\(snapshotPerformance.applicationWindowListReads)"
+ " appLifecycleObservers=\(platform.hasReliableApplicationLifecycleObservation) appInventoryInterval=\(Int(platform.recommendedApplicationInventoryRefreshInterval))"
+ " desktopObservers=\(platform.hasReliableDesktopObservation)"
Expand Down
57 changes: 54 additions & 3 deletions Sources/DefiDaemon/DefiDaemon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ final class Daemon: NSObject {
var processingHotKeyCommands = false
var processedHotKeyCount = 0
var needsDesktopSync = true
var desktopSessionActive = true
var desktopSessionGeneration: UInt64 = 0
var desktopSnapshotInFlight = false
var supersededDesktopSnapshotRequest: (
forceFullWindowRefresh: Bool,
Expand Down Expand Up @@ -309,17 +311,24 @@ final class Daemon: NSObject {
}
platform.startObserving(
{ [weak self] in
self?.observedPlatformEventCount += 1
self?.needsDesktopSync = true
self?.scheduleTick()
guard let self, desktopSessionActive else { return }
observedPlatformEventCount += 1
needsDesktopSync = true
scheduleTick()
},
desktopSessionHandler: { [weak self] active in
self?.handleDesktopSessionActivity(active)
},
displayConfigurationHandler: { [weak self] in
guard self?.desktopSessionActive == true else { return }
self?.scheduleDisplayReconciliation()
},
mouseGestureStartedHandler: { [weak self] in
guard self?.desktopSessionActive == true else { return }
self?.beginMouseGesture()
},
mouseGestureHandler: { [weak self] in
guard self?.desktopSessionActive == true else { return }
self?.cancelAnimationForMouseGesture()
}
)
Expand All @@ -329,12 +338,14 @@ final class Daemon: NSObject {
userInputTracker: platform.userInputTracker,
pointerMotionTracker: platform.pointerMotionTracker,
pointerMotionHandler: { [weak self] invocation in
guard self?.desktopSessionActive == true else { return }
self?.handlePointerMotion(invocation)
},
tapReenabledHandler: { [weak self] timestamp in
self?.handleEventTapReenabled(at: timestamp)
},
overviewHandler: { [weak self] action in
guard self?.desktopSessionActive == true else { return }
self?.overviewController?.handleKey(action)
}
) { [weak self] invocation in
Expand Down Expand Up @@ -374,7 +385,47 @@ final class Daemon: NSObject {
NSApplication.shared.run()
}

func handleDesktopSessionActivity(_ active: Bool) {
guard desktopSessionActive != active else { return }
desktopSessionActive = active
desktopSessionGeneration &+= 1
guard active else {
commandGeneration &+= 1
mouseGestureGeneration &+= 1
pendingHotKeyCommands.removeAll(keepingCapacity: true)
pendingAnimatedFocus = nil
invalidateSubmittedCommandFocus()
pendingWorkspaceFocus = nil
invalidateSubmittedWorkspaceFocus()
displacedPointerFocusRecovery = nil
invalidatePointerFocusIntent()
scrollAnimations.removeAll(keepingCapacity: true)
finishMouseGestureTracking()
mouseReorderAnimationActive = false
frameNotificationsSuspended = false
pendingDisplaySyncDeadlines.removeAll(keepingCapacity: true)
supersededDesktopSnapshotRequest = nil
needsDesktopSync = false
followUpTickSignature = nil
followUpBackoffSteps = 0
platform.invalidateStateForDesktopSessionChange()
Comment thread
qeude marked this conversation as resolved.
setTimerFrequency(2)
log("desktop session inactive")
return
}

needsDesktopSync = true
synchronizeDesktop(
forceFullWindowRefresh: true,
forceWindowListRefresh: true,
forceApplicationInventoryRefresh: true
)
scheduleTick()
log("desktop session active; refreshing desktop")
}

func tick() {
guard desktopSessionActive else { return }
processPendingHotKeys()
finishPendingAnimatedFocusIfReady()
finishPendingWorkspaceFocusIfReady()
Expand Down
9 changes: 7 additions & 2 deletions Sources/DefiMacOS/HotKeys.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import DefiConfig
import DefiModel
import Foundation

let hotKeyEventTapPlacement = CGEventTapPlacement.tailAppendEventTap

@MainActor
public final class HotKeyManager {
public typealias Handler = @MainActor @Sendable (HotKeyInvocation) -> Void
Expand Down Expand Up @@ -148,7 +150,7 @@ public final class HotKeyManager {
guard
let tap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
place: hotKeyEventTapPlacement,
options: .defaultTap,
eventsOfInterest: mask,
callback: callback,
Expand All @@ -160,6 +162,7 @@ public final class HotKeyManager {
guard let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
else {
CGEvent.tapEnable(tap: tap, enable: false)
CFMachPortInvalidate(tap)
throw HotKeyError.eventTapUnavailable
}
context.install(tap: tap, source: source)
Expand Down Expand Up @@ -190,7 +193,7 @@ public final class HotKeyManager {
}
}

private final class HotKeyTapContext: @unchecked Sendable {
final class HotKeyTapContext: @unchecked Sendable {
private static let commandTabKeyCode = CGKeyCode(48)
private static let closeWindowKeyCodes: Set<CGKeyCode> = [12, 13]

Expand Down Expand Up @@ -495,6 +498,7 @@ private final class HotKeyTapContext: @unchecked Sendable {
guard let runLoop else {
if let tap {
CGEvent.tapEnable(tap: tap, enable: false)
CFMachPortInvalidate(tap)
}
return
}
Expand All @@ -504,6 +508,7 @@ private final class HotKeyTapContext: @unchecked Sendable {
}
if let tap {
CGEvent.tapEnable(tap: tap, enable: false)
CFMachPortInvalidate(tap)
}
CFRunLoopStop(runLoop)
}
Expand Down
7 changes: 7 additions & 0 deletions Sources/DefiMacOS/MacOSPlatform+EventObservation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ extension MacOSPlatform {
clearFrameState()
}

public func invalidateStateForDesktopSessionChange() {
invalidatePreparedAXWindowAttributes()
frameCoordinator.invalidate(reason: "desktop-session-change")
clearFrameState()
invalidateFocusStateForDisplayChange()
}

public func cancelPendingFrameWrites() {
frameCoordinator.invalidate(reason: "mouse-gesture")
}
Expand Down
4 changes: 4 additions & 0 deletions Sources/DefiMacOS/MacOSPlatform+WindowBorderObservation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extension MacOSPlatform {

public func startObserving(
_ handler: @escaping () -> Void,
desktopSessionHandler: @escaping (Bool) -> Void = { _ in },
displayConfigurationHandler: @escaping () -> Void = {},
mouseGestureStartedHandler: @escaping () -> Void = {},
mouseGestureHandler: @escaping () -> Void = {}
Expand Down Expand Up @@ -139,6 +140,9 @@ extension MacOSPlatform {
handler()
},
userInputTracker: userInputTracker,
desktopSessionHandler: { change in
desktopSessionHandler(change == .becameActive)
},
frameHandler: { [weak self] element in
guard let self else { return }
self.refreshWindowBorderGeometry(for: element)
Expand Down
Loading