【问题标题】:How to obtain the selected text from the frontmost application in macOS?如何从 macOS 中最前面的应用程序中获取选定的文本?
【发布时间】:2010-12-02 00:16:40
【问题描述】:

我将很快开发一个应用程序,该应用程序需要在最前面的应用程序窗口中获取当前选定的文本,无论是 Safari、Pages、TextEdit、Word 等,然后对这些文本执行一些操作。

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

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

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

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

提前感谢您的任何提示!

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

更新:

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

注意,当使用 NSAppleScript 的 executeAndReturnError: 方法返回一个 NSAppleEventDescriptor(假设是一个“descriptor”实例)时,为了让 [descriptor stringValue] 方法返回一些东西,在你的 AppleScript 中你必须使用“return someString” OUTSIDE of a “ tell" 块,否则不会返回任何内容。

【问题讨论】:

  • 您是否有机会发布一些关于如何从最前面的应用程序获取所选文本的代码?
  • 你最终解决了这个问题吗?您是如何通过 ApplicationServices 获得重点突出的 UI 元素的?
  • 不幸的是,这是很久以前的事了(在以前的工作中),我完全不记得我最终是如何做到的,而且我再也无法访问源代码了。我记得那不是很难。您可能会在其他地方找到示例。对不起:(

标签: swift objective-c cocoa appkit


【解决方案1】:

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

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

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

【讨论】:

  • 辅助功能看起来很有趣,但是在 Firefox 和 Word 中使用 Accessibility Inspector(来自开发工具)不会产生有趣的值,尽管在 Safari 中使用它时会产生大量信息。这是否意味着在这些应用程序中根本无法使用辅助功能,或者仍然有一种“通用”方式来获取您所描述的选定文本?你有代码示例或一些示例的链接吗?
  • 辅助功能适用于任何可访问的应用程序。某些应用程序无法访问。最糟糕的例子,包括这两个,也不太可能支持任何其他检查其 UI 的通用方法——如果你想支持它们,你必须使用 AppleScript 对它们进行特殊处理。
  • 例如:不,似乎没有了。也就是说,如果您能够理解和使用基于 Core-Foundation 的 API,您可以从 Accessibility 标头中的函数名称中找出您需要做什么。 (顺便说一句,您需要的框架是 ApplicationServices。)
  • 谢谢,我会采纳你的建议的。由于似乎没有完美的解决方案,我要做的是为每种输入法(粘贴板、AppleScript、可访问性)创建一些“输入”类,并使用另一个抽象类来选择最佳输入法活动应用程序或输入收集结果。
【解决方案2】:

如果您不需要非常频繁地选择文本,您可以通过编程方式按 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)
    }

【讨论】:

    【解决方案3】:

    这是已接受答案中描述的 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 并从剪贴板复制的另一个答案。此外,如果不需要,请确保从剪贴板中删除新项目。

    【讨论】:

      猜你喜欢
      • 2016-04-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-27
      • 2014-02-11
      • 2020-11-11
      • 2016-12-26
      • 1970-01-01
      相关资源
      最近更新 更多