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/DaemonDesktop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ extension Daemon {
commandPerformance: commandPerformance,
source: source
)
if timerFrequencyHz == 0 { scheduleIdleTick() }
platform.updateWindowBorders(
frames: borderAssignments,
selectedWindowID: selectedWindowID,
Expand Down
6 changes: 3 additions & 3 deletions Sources/DefiDaemon/DaemonDesktopSynchronization.swift
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,11 @@ extension Daemon {
),
reset: forceFullWindowRefresh || consumePeriodicWindowRefresh
)
nextWindowListRefreshAt = boundedSnapshotRefreshDeadline(
nextWindowListRefreshAt = windowDiscoveryGlobalDeadline(
current: nextWindowListRefreshAt,
now: snapshotCompletedAt,
interval: platform.recommendedWindowListRefreshInterval,
reset: forceWindowListRefresh || targetedWindowRetryRefresh
interval: platform.recommendedGlobalWindowListRefreshInterval,
globalRefresh: forceWindowListRefresh, targetedRefresh: targetedWindowRetryRefresh
)
let applicationInventoryInterval =
platform.recommendedApplicationInventoryRefreshInterval
Expand Down
4 changes: 3 additions & 1 deletion Sources/DefiDaemon/DaemonStatusLifecycle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,9 @@ extension Daemon {
deadlinesAndIntervals: [
(nextPeriodicWindowRefreshAt, desktopSnapshotRefreshInterval(
reliableDesktopObservation: platform.hasReliableDesktopObservation)),
(nextWindowListRefreshAt, platform.recommendedWindowListRefreshInterval),
(nextWindowListRefreshAt, platform.recommendedGlobalWindowListRefreshInterval),
(platform.nextWindowDiscoveryRetryAt ?? .infinity, 0.1),
(platform.nextFrameCommitVerificationAt ?? .infinity, 0.1),
(nextApplicationInventoryRefreshAt,
platform.recommendedApplicationInventoryRefreshInterval),
]
Expand Down
20 changes: 12 additions & 8 deletions Sources/DefiDaemon/DefiDaemon.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ final class Daemon {
needsDesktopSync = true
}
let now = ProcessInfo.processInfo.systemUptime
if platform.requestDueFrameCommitVerification(now: now) {
needsDesktopSync = true
}
if let mouseGestureSettlement,
now >= mouseGestureSettlement.nextCheckAt
{
Expand Down Expand Up @@ -451,22 +454,23 @@ final class Daemon {
let desktopRefreshInterval = desktopSnapshotRefreshInterval(
reliableDesktopObservation: platform.hasReliableDesktopObservation
)
let windowListRefreshInterval = platform.recommendedWindowListRefreshInterval
let windowListRefreshInterval = platform.recommendedGlobalWindowListRefreshInterval
let applicationInventoryInterval =
platform.recommendedApplicationInventoryRefreshInterval
let periodicWindowRefreshDue = observationWatchdogRefreshIsReady(
due: now >= nextPeriodicWindowRefreshAt,
interval: desktopRefreshInterval,
userInputIdleDuration: userInputIdleDuration
)
let windowListRefreshDue = observationWatchdogRefreshIsReady(
due: now >= nextWindowListRefreshAt,
interval: windowListRefreshInterval,
userInputIdleDuration: userInputIdleDuration
let windowListRequest = windowDiscoveryRefreshRequest(
now: now, globalDeadline: nextWindowListRefreshAt, interval: windowListRefreshInterval,
retryDeadline: platform.nextWindowDiscoveryRetryAt,
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
userInputIdleDuration: userInputIdleDuration,
dueProcessIDs: platform.dueWindowDiscoveryRetryProcessIDs(now: now)
)
let targetedWindowRetryRefresh = windowListRefreshDue
&& !platform.dueWindowDiscoveryRetryProcessIDs(now: now).isEmpty
let globalWindowListRefreshDue = windowListRefreshDue && !targetedWindowRetryRefresh
let windowListRefreshDue = windowListRequest.due
let targetedWindowRetryRefresh = windowListRequest.targeted
let globalWindowListRefreshDue = windowListRequest.global
let applicationInventoryRefreshDue = observationWatchdogRefreshIsReady(
due: now >= nextApplicationInventoryRefreshAt,
interval: applicationInventoryInterval,
Expand Down
49 changes: 49 additions & 0 deletions Sources/DefiMacOS/AXFocusOperations.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import AppKit
import ApplicationServices

struct AXFocusOperations: @unchecked Sendable {
var targetIsFocused: (AXUIElement, AXUIElement) -> Bool = { element, application in
AXMessagingTimeoutAccess.shared.withTimeout(0.016, elements: [application, element]) {
// Main-window state alone does not establish the keyboard recipient.
targetWindowFocusIsConfirmed(readFocused(element)) {
var focusedWindow: CFTypeRef?
guard AXUIElementCopyAttributeValue(
application, kAXFocusedWindowAttribute as CFString, &focusedWindow
) == .success, let focusedWindow,
CFGetTypeID(focusedWindow) == AXUIElementGetTypeID()
else { return false }
return CFEqual(focusedWindow, element)
}
}
}
var setMain: (AXUIElement) -> AXError = {
AXUIElementSetAttributeValue($0, kAXMainAttribute as CFString, kCFBooleanTrue)
}
var raise: (AXUIElement) -> AXError = {
AXUIElementPerformAction($0, kAXRaiseAction as CFString)
}
var applicationIsActive: (pid_t) -> Bool = {
NSRunningApplication(processIdentifier: $0)?.isActive == true
}
var prepareActivation: () -> (AXUIElement) -> AXError = {
let system = AXUIElementCreateSystemWide()
return {
AXUIElementSetAttributeValue(system, kAXFocusedApplicationAttribute as CFString, $0)
}
}
var activateApplication: (pid_t) -> Bool = {
NSRunningApplication(processIdentifier: $0)?.activate() == true
}
var withTimeout: (Float, [AXUIElement], () -> Void) -> Void = { timeout, elements, perform in
AXMessagingTimeoutAccess.shared.withTimeout(timeout, elements: elements, perform: perform)
}

private static func readFocused(_ element: AXUIElement) -> Bool? {
var rawValue: CFTypeRef?
guard AXUIElementCopyAttributeValue(
element, kAXFocusedAttribute as CFString, &rawValue
) == .success, let rawValue, CFGetTypeID(rawValue) == CFBooleanGetTypeID()
else { return nil }
return CFBooleanGetValue(rawValue as! CFBoolean)
}
}
6 changes: 6 additions & 0 deletions Sources/DefiMacOS/AXFocusWriter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ struct QueuedFocusRequest: @unchecked Sendable {
}

final class AXFocusWriter: @unchecked Sendable {
let operations: AXFocusOperations

init(operations: AXFocusOperations = AXFocusOperations()) {
self.operations = operations
}

let queue = DispatchQueue(
label: "com.quentin.defi.ax-focus",
qos: .userInitiated
Expand Down
146 changes: 49 additions & 97 deletions Sources/DefiMacOS/AXFocusWriterExecution.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,35 +50,33 @@ extension AXFocusWriter {
selectsSpecificWindow = specificWindowFocusWriteIsRequired(
requested: selectsSpecificWindow,
validatesCurrentFocus: request.validatesSpecificWindowFocus,
targetIsFocused: isTargetFocused(
request.element,
application: request.application
)
targetIsFocused: operations.targetIsFocused(request.element, request.application)
)
}
if selectsSpecificWindow {
AXMessagingTimeoutAccess.shared.withTimeout(
operations.withTimeout(
0.016,
elements: [request.application, request.element]
[request.application, request.element]
) {
guard isCurrent(queued) else {
cancelled = true
return
}
let mainStartedAt = ProcessInfo.processInfo.systemUptime
var mainResult = AXUIElementSetAttributeValue(
request.element,
kAXMainAttribute as CFString,
kCFBooleanTrue
)
var mainResult = operations.setMain(request.element)
focusMutationApplied = mainResult == .success
windowSelectionSucceeded = mainResult == .success
mainDurationMS =
(ProcessInfo.processInfo.systemUptime - mainStartedAt) * 1_000
cancelled = !isCurrent(queued)
var raiseResult = AXError.cannotComplete
if !cancelled, mainResult != .success {
guard isCurrent(queued) else {
cancelled = true
return
}
let raiseStartedAt = ProcessInfo.processInfo.systemUptime
raiseResult = AXUIElementPerformAction(
request.element,
kAXRaiseAction as CFString
)
raiseResult = operations.raise(request.element)
focusMutationApplied =
focusMutationApplied
|| raiseResult == .success
Expand All @@ -93,16 +91,16 @@ extension AXFocusWriter {
mainResult != .success && raiseResult != .success
{
retried = true
AXMessagingTimeoutAccess.shared.withTimeout(
operations.withTimeout(
0.05,
elements: [request.application, request.element]
[request.application, request.element]
) {
guard isCurrent(queued) else {
cancelled = true
return
}
let retryMainStartedAt = ProcessInfo.processInfo.systemUptime
mainResult = AXUIElementSetAttributeValue(
request.element,
kAXMainAttribute as CFString,
kCFBooleanTrue
)
mainResult = operations.setMain(request.element)
focusMutationApplied =
focusMutationApplied
|| mainResult == .success
Expand All @@ -111,12 +109,13 @@ extension AXFocusWriter {
|| mainResult == .success
mainDurationMS +=
(ProcessInfo.processInfo.systemUptime - retryMainStartedAt) * 1_000
if isCurrent(queued), mainResult != .success {
if mainResult != .success {
guard isCurrent(queued) else {
cancelled = true
return
}
let retryRaiseStartedAt = ProcessInfo.processInfo.systemUptime
raiseResult = AXUIElementPerformAction(
request.element,
kAXRaiseAction as CFString
)
raiseResult = operations.raise(request.element)
focusMutationApplied =
focusMutationApplied
|| raiseResult == .success
Expand All @@ -141,33 +140,33 @@ extension AXFocusWriter {
? nil
: activationRequirement(
requested: request.activatesApplication
|| NSRunningApplication(processIdentifier: request.processID)?.isActive != true,
|| !operations.applicationIsActive(request.processID),
generation: queued.generation
)
if activationRequired == true {
if activationRequired == true, isCurrent(queued) {
activationAttempted = true
activationSucceeded = false
let activationStartedAt = ProcessInfo.processInfo.systemUptime
let system = AXUIElementCreateSystemWide()
let activationResult = AXUIElementSetAttributeValue(
system,
kAXFocusedApplicationAttribute as CFString,
request.application
)
if activationResult == .success {
activationSucceeded = true
} else if isCurrent(queued) {
activationSucceeded =
NSRunningApplication(processIdentifier: request.processID)?
.activate() == true
let activate = operations.prepareActivation()
if isCurrent(queued) {
let activationResult = activate(request.application)
if activationResult == .success {
activationSucceeded = true
} else if isCurrent(queued) {
activationSucceeded =
operations.activateApplication(request.processID)
}
} else {
cancelled = true
}
focusMutationApplied = focusMutationStateAfterActivation(
priorMutationApplied: focusMutationApplied,
activationSucceeded: activationSucceeded
)
activationDurationMS =
(ProcessInfo.processInfo.systemUptime - activationStartedAt) * 1_000
} else if activationRequired == nil {
} else if activationRequired == nil || !isCurrent(queued) {
if activationRequired == true { markRecoveryActivationNeeded() }
cancelled = true
}
if !cancelled,
Expand All @@ -187,15 +186,15 @@ extension AXFocusWriter {
isCurrent(queued) && inputGuardIsCurrent(request)
},
attempt: { timeout in
AXMessagingTimeoutAccess.shared.withTimeout(
timeout,
elements: [element]
) {
AXUIElementPerformAction(
element,
kAXRaiseAction as CFString
)
var result = AXError.cannotComplete
operations.withTimeout(timeout, [element]) {
guard isCurrent(queued) else {
cancelled = true
return
}
result = operations.raise(element)
}
return result
}
)
raiseDurationMS +=
Expand Down Expand Up @@ -338,51 +337,4 @@ extension AXFocusWriter {
lock.unlock()
}

private func isTargetFocused(
_ element: AXUIElement,
application: AXUIElement
) -> Bool {
AXMessagingTimeoutAccess.shared.withTimeout(
0.016,
elements: [application, element]
) {
// AXMain is structural app state, not proof of keyboard focus. Apps such as
// Kaku can leave a non-focused window main during rapid intra-app navigation.
targetWindowFocusIsConfirmed(
readBoolean(element, attribute: kAXFocusedAttribute)
) {
var focusedWindow: CFTypeRef?
guard AXUIElementCopyAttributeValue(
application,
kAXFocusedWindowAttribute as CFString,
&focusedWindow
) == .success,
let focusedWindow,
CFGetTypeID(focusedWindow) == AXUIElementGetTypeID()
else {
return false
}
return CFEqual(focusedWindow, element)
}
}
}

private func readBoolean(
_ element: AXUIElement,
attribute: String
) -> Bool? {
var rawValue: CFTypeRef?
guard
AXUIElementCopyAttributeValue(
element,
attribute as CFString,
&rawValue
) == .success,
let rawValue,
CFGetTypeID(rawValue) == CFBooleanGetTypeID()
else {
return nil
}
return CFBooleanGetValue((rawValue as! CFBoolean))
}
}
Loading
Loading