117 lines
4.0 KiB
Swift
117 lines
4.0 KiB
Swift
import Cocoa
|
|
import Foundation
|
|
|
|
class TextSelectionObserver: NSObject {
|
|
let workspace = NSWorkspace.shared
|
|
var lastSelectedText: String?
|
|
|
|
override init() {
|
|
super.init()
|
|
|
|
// 注册通知观察者
|
|
let observer = NSWorkspace.shared.notificationCenter
|
|
observer.addObserver(
|
|
self,
|
|
selector: #selector(handleSelectionChange),
|
|
name: NSWorkspace.didActivateApplicationNotification,
|
|
object: nil
|
|
)
|
|
|
|
// 监听选择变化通知
|
|
var axObserver: AXObserver?
|
|
let error = AXObserverCreate(getpid(), { observer, element, notification, userData in
|
|
let selfPointer = userData!.load(as: TextSelectionObserver.self)
|
|
selfPointer.checkSelectedText()
|
|
}, &axObserver)
|
|
|
|
if error == .success, let axObserver = axObserver {
|
|
CFRunLoopAddSource(
|
|
RunLoop.main.getCFRunLoop(),
|
|
AXObserverGetRunLoopSource(axObserver),
|
|
.defaultMode
|
|
)
|
|
|
|
// 当前活动应用添加监听
|
|
updateActiveAppObserver(axObserver)
|
|
}
|
|
}
|
|
|
|
@objc func handleSelectionChange(_ notification: Notification) {
|
|
// 应用切换时更新监听
|
|
var axObserver: AXObserver?
|
|
let error = AXObserverCreate(getpid(), { _, _, _, _ in }, &axObserver)
|
|
if error == .success, let axObserver = axObserver {
|
|
updateActiveAppObserver(axObserver)
|
|
}
|
|
}
|
|
|
|
func updateActiveAppObserver(_ axObserver: AXObserver) {
|
|
guard let app = workspace.frontmostApplication else { return }
|
|
let pid = app.processIdentifier
|
|
let element = AXUIElementCreateApplication(pid)
|
|
|
|
// 添加选择变化通知监听
|
|
AXObserverAddNotification(
|
|
axObserver,
|
|
element,
|
|
kAXSelectedTextChangedNotification as CFString,
|
|
UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
|
|
)
|
|
}
|
|
|
|
func checkSelectedText() {
|
|
if let text = getSelectedText() {
|
|
if text.count > 0 && text != lastSelectedText {
|
|
print(text)
|
|
fflush(stdout)
|
|
lastSelectedText = text
|
|
}
|
|
}
|
|
}
|
|
|
|
func getSelectedText() -> String? {
|
|
guard let app = NSWorkspace.shared.frontmostApplication else { return nil }
|
|
let pid = app.processIdentifier
|
|
|
|
let axApp = AXUIElementCreateApplication(pid)
|
|
var focusedElement: AnyObject?
|
|
|
|
// Get focused element
|
|
let result = AXUIElementCopyAttributeValue(axApp, kAXFocusedUIElementAttribute as CFString, &focusedElement)
|
|
guard result == .success else { return nil }
|
|
|
|
// Try different approaches to get selected text
|
|
var selectedText: AnyObject?
|
|
|
|
// First try: Direct selected text
|
|
var textResult = AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXSelectedTextAttribute as CFString, &selectedText)
|
|
|
|
// Second try: Selected text in text area
|
|
if textResult != .success {
|
|
var selectedTextRange: AnyObject?
|
|
textResult = AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXSelectedTextRangeAttribute as CFString, &selectedTextRange)
|
|
if textResult == .success {
|
|
textResult = AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXValueAttribute as CFString, &selectedText)
|
|
}
|
|
}
|
|
|
|
// Third try: Get selected text from parent element
|
|
if textResult != .success {
|
|
var parent: AnyObject?
|
|
if AXUIElementCopyAttributeValue(focusedElement as! AXUIElement, kAXParentAttribute as CFString, &parent) == .success {
|
|
textResult = AXUIElementCopyAttributeValue(parent as! AXUIElement, kAXSelectedTextAttribute as CFString, &selectedText)
|
|
}
|
|
}
|
|
|
|
guard textResult == .success, let text = selectedText as? String else { return nil }
|
|
return text
|
|
}
|
|
}
|
|
|
|
let observer = TextSelectionObserver()
|
|
|
|
signal(SIGINT) { _ in
|
|
exit(0)
|
|
}
|
|
|
|
RunLoop.main.run() |