Fix selected text detection and Power Mode handling

This commit is contained in:
Beingpax 2025-06-30 20:11:20 +05:45
parent 05806f4248
commit d9165e1e4d
2 changed files with 47 additions and 35 deletions

View File

@ -19,7 +19,18 @@ class PromptDetectionService {
func analyzeText(_ text: String, with enhancementService: AIEnhancementService) -> PromptDetectionResult {
let originalEnhancementState = enhancementService.isEnhancementEnabled
let originalPromptId = enhancementService.selectedPromptId
if let selectedText = SelectedTextService.fetchSelectedText(), !selectedText.isEmpty {
return PromptDetectionResult(
shouldEnableAI: true,
selectedPromptId: PredefinedPrompts.assistantPromptId,
processedText: text, // The user's speech is the prompt for the selected text
detectedTriggerWord: nil,
originalEnhancementState: originalEnhancementState,
originalPromptId: originalPromptId
)
}
for prompt in enhancementService.allPrompts {
if !prompt.triggerWords.isEmpty {
if let (detectedWord, processedText) = findMatchingTriggerWord(from: text, triggerWords: prompt.triggerWords) {
@ -34,7 +45,7 @@ class PromptDetectionService {
}
}
}
return PromptDetectionResult(
shouldEnableAI: false,
selectedPromptId: nil,

View File

@ -3,49 +3,50 @@ import AppKit
class SelectedTextService {
static func fetchSelectedText() -> String? {
// Do not check for selected text within VoiceInk itself.
guard let frontmostApp = NSWorkspace.shared.frontmostApplication,
frontmostApp.bundleIdentifier != "com.prakashjoshipax.VoiceInk" else {
return nil
}
// Get the currently focused UI element system-wide.
let systemWideElement = AXUIElementCreateSystemWide()
var focusedElement: CFTypeRef?
guard AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute as CFString, &focusedElement) == .success,
let element = focusedElement else {
guard AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute as CFString, &focusedElement) == .success, focusedElement != nil else {
return nil
}
let element = focusedElement as! AXUIElement
// First, try the standard attribute, which is the most reliable method.
var selectedTextValue: CFTypeRef?
if AXUIElementCopyAttributeValue(element, kAXSelectedTextAttribute as CFString, &selectedTextValue) == .success,
let selectedText = selectedTextValue as? String, !selectedText.isEmpty {
return selectedText
}
// If the standard attribute fails, fall back to checking the selection range.
// This correctly handles apps that don't support the standard attribute.
var selectedRangeValue: CFTypeRef?
guard AXUIElementCopyAttributeValue(element, kAXSelectedTextRangeAttribute as CFString, &selectedRangeValue) == .success, selectedRangeValue != nil else {
return nil
}
let axRangeValue = selectedRangeValue as! AXValue
var selectedRange: CFRange = .init()
AXValueGetValue(axRangeValue, .cfRange, &selectedRange)
// An actual selection must have a length greater than zero.
guard selectedRange.length > 0 else {
return nil
}
return findSelectedText(in: element as! AXUIElement)
}
private static func findSelectedText(in element: AXUIElement) -> String? {
var selectedTextValue: CFTypeRef?
if AXUIElementCopyAttributeValue(element, kAXSelectedTextAttribute as CFString, &selectedTextValue) == .success {
if let selectedText = selectedTextValue as? String, !selectedText.isEmpty {
return selectedText
}
var fullTextValue: CFTypeRef?
guard AXUIElementCopyAttributeValue(element, kAXValueAttribute as CFString, &fullTextValue) == .success,
let fullText = fullTextValue as? String,
let subrange = Range(NSRange(location: selectedRange.location, length: selectedRange.length), in: fullText) else {
return nil
}
// Fallback for apps that use kAXValueAttribute for selected text (like some Electron apps)
var value: CFTypeRef?
if AXUIElementCopyAttributeValue(element, kAXValueAttribute as CFString, &value) == .success {
if let selectedText = value as? String, !selectedText.isEmpty {
return selectedText
}
}
var children: CFTypeRef?
if AXUIElementCopyAttributeValue(element, kAXChildrenAttribute as CFString, &children) == .success {
if let axChildren = children as? [AXUIElement] {
for child in axChildren {
if let foundText = findSelectedText(in: child) {
return foundText
}
}
}
}
return nil
return String(fullText[subrange])
}
}