diff --git a/src/InputBoxManager.swift b/src/InputBoxManager.swift index ce10f77..53d881d 100644 --- a/src/InputBoxManager.swift +++ b/src/InputBoxManager.swift @@ -37,12 +37,19 @@ public final class InputBoxManager { public func ensureFocusedInputBox() { resetSession() - guard AXIsProcessTrusted() else { + let promptKey = kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String + let isTrusted = AXIsProcessTrustedWithOptions([promptKey: true] as CFDictionary) + guard isTrusted else { print("InputBoxManager: Accessibility not trusted.") return } guard let frontApp = NSWorkspace.shared.frontmostApplication else { return } + if let bid = frontApp.bundleIdentifier?.lowercased(), + bid.contains("siri") || bid.contains("shortcuts") { + print("InputBoxManager: Frontmost app is Siri/Shortcuts (\(bid)), preserving user app focus.") + return + } let appElem = AXUIElementCreateApplication(frontApp.processIdentifier) // 1. Check if an element is already focused in the frontmost application @@ -292,6 +299,14 @@ public final class InputBoxManager { let normStreamed = streamedWords.map(cleanWord).filter { !$0.isEmpty } let normFinal = finalWords.map(cleanWord).filter { !$0.isEmpty } + let wordsMatch: (String, String) -> Bool = { w1, w2 in + if w1 == w2 { return true } + if w1.count >= 2 && w2.count >= 2 { + if w1.hasPrefix(w2) || w2.hasPrefix(w1) { return true } + } + return false + } + var unstreamedStartIndex: Int? = nil // Strategy 4A: Search for 3-word tail anchor from streamed text in final words @@ -300,7 +315,7 @@ public final class InputBoxManager { let a2 = normStreamed[normStreamed.count - 2] let a3 = normStreamed[normStreamed.count - 1] for j in stride(from: normFinal.count - 1, through: 2, by: -1) { - if normFinal[j] == a3 && normFinal[j - 1] == a2 && normFinal[j - 2] == a1 { + if wordsMatch(normFinal[j], a3) && wordsMatch(normFinal[j - 1], a2) && wordsMatch(normFinal[j - 2], a1) { unstreamedStartIndex = j + 1 break } @@ -312,7 +327,7 @@ public final class InputBoxManager { let a1 = normStreamed[normStreamed.count - 2] let a2 = normStreamed[normStreamed.count - 1] for j in stride(from: normFinal.count - 1, through: 1, by: -1) { - if normFinal[j] == a2 && normFinal[j - 1] == a1 { + if wordsMatch(normFinal[j], a2) && wordsMatch(normFinal[j - 1], a1) { unstreamedStartIndex = j + 1 break } @@ -324,7 +339,7 @@ public final class InputBoxManager { let a1 = normStreamed[normStreamed.count - 1] let searchStart = max(0, normFinal.count - max(15, normStreamed.count)) for j in stride(from: normFinal.count - 1, through: searchStart, by: -1) { - if normFinal[j] == a1 { + if wordsMatch(normFinal[j], a1) { unstreamedStartIndex = j + 1 break } @@ -335,7 +350,7 @@ public final class InputBoxManager { if unstreamedStartIndex == nil { var prefixMatchCount = 0 for i in 0..= (finalWords.count / 2) { - print("InputBoxManager: Most words already streamed (\(streamedWords.count) vs \(finalWords.count)). Suppressing full paste to prevent duplication.") - if addTrailingSpace { - let pb = NSPasteboard.general - pb.clearContents() - pb.setString(" ", forType: .string) - self.postCleanCmdV() - } - restoreClipboardFullText() - return - } - - // Case 6: Fallback when only very few words were streamed (<50%) and no anchor was found - if finalWords.count > streamedWords.count { - let remainingWords = Array(finalWords.dropFirst(streamedWords.count)) - let suffix = remainingWords.joined(separator: " ") - let suffixToPaste = addTrailingSpace ? (" " + suffix + " ") : (" " + suffix) - let pb = NSPasteboard.general - pb.clearContents() - pb.setString(suffixToPaste, forType: .string) - self.postCleanCmdV() - } restoreClipboardFullText() } }