diff --git a/CHANGELOG.md b/CHANGELOG.md index 6895f5d..230e257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.5] - 2026-09-06 + +### Fixed + +- Dictation still not pasting into terminals after 1.0.4: the system-wide `AXFocusedUIElement` lookup returns nothing for some apps (terminals), so detection saw "no focused element" and diverted to the clipboard. It now falls back to the frontmost application's own focused element, which those apps do expose. + ## [1.0.4] - 2026-09-06 ### Fixed diff --git a/Resources/Info.plist b/Resources/Info.plist index b7fc9d7..7755bd5 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -15,9 +15,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0.4 + 1.0.5 CFBundleVersion - 1.0.4 + 1.0.5 LSMinimumSystemVersion 14.0 LSUIElement diff --git a/Sources/EchoType/Paster.swift b/Sources/EchoType/Paster.swift index c78bd2b..33725bc 100644 --- a/Sources/EchoType/Paster.swift +++ b/Sources/EchoType/Paster.swift @@ -76,14 +76,10 @@ enum Paster { /// Warp/Ghostty) expose an opaque focused view — treat that as a text target /// rather than losing the paste, matching pre-1.0.3 behavior. private static func hasTextTarget() -> Bool { - let system = AXUIElementCreateSystemWide() - var focused: AnyObject? - guard AXUIElementCopyAttributeValue(system, kAXFocusedUIElementAttribute as CFString, &focused) == .success, - let element = focused else { + guard let axElement = focusedElement() else { Log.write("paste: no focused element — copying to clipboard") return false } - let axElement = element as! AXUIElement var role = "" var roleValue: AnyObject? @@ -104,6 +100,26 @@ enum Paster { return true } + /// The focused UI element. The system-wide `AXFocusedUIElement` is empty for + /// some apps (terminals), so fall back to the frontmost application's own + /// focused element. + private static func focusedElement() -> AXUIElement? { + let system = AXUIElementCreateSystemWide() + var focused: AnyObject? + if AXUIElementCopyAttributeValue(system, kAXFocusedUIElementAttribute as CFString, &focused) == .success, + let element = focused { + return (element as! AXUIElement) + } + guard let pid = NSWorkspace.shared.frontmostApplication?.processIdentifier else { return nil } + let appElement = AXUIElementCreateApplication(pid) + var appFocused: AnyObject? + if AXUIElementCopyAttributeValue(appElement, kAXFocusedUIElementAttribute as CFString, &appFocused) == .success, + let element = appFocused { + return (element as! AXUIElement) + } + return nil + } + private static func isValueSettable(_ element: AXUIElement) -> Bool { var settable: DarwinBoolean = false guard AXUIElementIsAttributeSettable(element, kAXValueAttribute as CFString, &settable) == .success else {