如何从另一个应用程序中获取选定的文本?

For*_*orm 5 cocoa objective-c

我很快就会开发一个应用程序,它需要在最前面的应用程序窗口中获取当前选定的文本,无论是 Safari、Pages、TextEdit、Word 等,然后对这些文本做一些事情。

我的目标是找到一个可以与尽可能多的应用程序配合使用的解决方案。到目前为止,我考虑过使用AppleScript,但这会限制可以与我的服务一起使用的应用程序数量。至少必须支持这些常见的应用程序:Safari、Firefox(没有 AppleScript?)、Word、Pages、Excel、TextEdit,...

我还考虑将剪贴板的内容保存在一个临时变量中,然后模拟文本复制操作(Cmd-C),获取文本,然后将原始内容放回去。这可能会在复制操作时突出显示 Edit 菜单项是模拟的,对我来说似乎有点hacky。IMO 这个解决方案对于商业产品来说似乎不够好。

我还希望获得更多的选择(即:Safari 或 Word 中页面的完整内容等),以便在未来添加一些附加功能。

关于如何实现这种行为的任何想法/细节?

提前感谢您的任何提示!

注意:我需要至少支持 10.4 及更高版本,但最好也早于 10.4。

更新:

我选择的解决方案:使用“责任链”设计模式 (GOF) 结合 3 种不同的输入方法(粘贴板、AppleScript 和辅助功能),自动使用最佳可用输入源。

请注意,当使用 NSAppleScript 的 executeAndReturnError: 方法返回一个 NSAppleEventDescriptor(让我们说一个“描述符”实例)时,[descriptor stringValue] 方法返回一些东西,在你的 AppleScript 中,你必须使用“return someString” OUTSIDE“tell”块否则什么都不会返回。

Imt*_*ath 7

这是已接受答案中描述的 Swift 5.5 实现。

extension AXUIElement {
  static var focusedElement: AXUIElement? {
    systemWide.element(for: kAXFocusedUIElementAttribute)
  }
  
  var selectedText: String? {
    rawValue(for: kAXSelectedTextAttribute) as? String
  }
  
  private static var systemWide = AXUIElementCreateSystemWide()
  
  private func element(for attribute: String) -> AXUIElement? {
    guard let rawValue = rawValue(for: attribute), CFGetTypeID(rawValue) == AXUIElementGetTypeID() else { return nil }
    return (rawValue as! AXUIElement)
  }
  
  private func rawValue(for attribute: String) -> AnyObject? {
    var rawValue: AnyObject?
    let error = AXUIElementCopyAttributeValue(self, attribute as CFString, &rawValue)
    return error == .success ? rawValue : nil
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,无论您需要从最前面的应用程序获取选定的文本,您都可以使用AXUIElement.focusedElement?.selectedText.

正如答案中提到的,这并不是 100% 可靠。因此,我们还实现了另一个答案,它模拟 Command + C 并从剪贴板复制。另外,如果不需要,请确保从剪贴板中删除新项目。


Pet*_*sey 2

辅助功能将起作用,但前提是辅助设备的访问权限已打开。

您需要获取当前应用程序,然后获取其焦点 UI 元素,然后获取其选定的文本范围及其值(整个文本)和选定的文本范围。您可以只获取其选定的文本,但这会连接或忽略多个选择。

为这些步骤中的任何一个失败做好准备:应用程序可能没有任何窗口,可能没有具有焦点的 UI 元素,聚焦的 UI 元素可能没有文本,并且聚焦的 UI 元素可能只有空的选定文本范围。