6

我将很快开发一个应用程序,该应用程序需要在最前面的应用程序窗口中获取当前选定的文本,无论是 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”实例)时,为了让 [descriptor stringValue] 方法返回某些内容,在您的 AppleScript 中,您必须在“tell”块之外使用“return someString”否则什么都不会返回。

4

3 回答 3

1

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

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

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

于 2009-09-28T14:52:52.763 回答
1

如果您不需要非常频繁地选择文本,您可以通过编程方式按 Command+C,然后从剪贴板中获取所选文本。但是在我的测试过程中,这仅在您关闭 App Sandbox 时才有效(无法提交到 Mac App Store)。

这是 Swift 3 代码:

     func performGlobalCopyShortcut() {

        func keyEvents(forPressAndReleaseVirtualKey virtualKey: Int) -> [CGEvent] {
            let eventSource = CGEventSource(stateID: .hidSystemState)
            return [
                CGEvent(keyboardEventSource: eventSource, virtualKey: CGKeyCode(virtualKey), keyDown: true)!,
                CGEvent(keyboardEventSource: eventSource, virtualKey: CGKeyCode(virtualKey), keyDown: false)!,
            ]
        }

        let tapLocation = CGEventTapLocation.cghidEventTap
        let events = keyEvents(forPressAndReleaseVirtualKey: kVK_ANSI_C)

        events.forEach {
            $0.flags = .maskCommand
            $0.post(tap: tapLocation)
        }
    }

    performGlobalCopyShortcut()

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { // wait 0.05s for copy.
        let clipboardText = NSPasteboard.general().readObjects(forClasses: [NSString.self], options: nil)?.first as? String ?? ""
        print(clipboardText)
    }
于 2017-07-13T06:11:12.480 回答
0

这是已接受答案中描述的 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
  }
}

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

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

于 2022-01-11T16:43:22.780 回答