Search code examples
macoscocoaserviceaccessibility

Get currently selected text in active application in Cocoa


I have a status-menu app that can be started using a system wide shortcut. When the app gets active, it would be great if I could somehow get the text that is selected in the currently running application.

So for example I type something in my text-editor, select the text, hit my global shortcut, my app comes up and I would now love to know the selected text from the text-editor.

What I have so far is the following (adopted code from How to get global screen coordinates of currently selected text via Accessibility APIs.)

AXUIElementRef systemWideElement = AXUIElementCreateSystemWide();
AXUIElementRef focussedElement = NULL;
AXError error = AXUIElementCopyAttributeValue(systemWideElement, kAXFocusedUIElementAttribute, (CFTypeRef *)&focussedElement);
if (error != kAXErrorSuccess) {
    NSLog(@"Could not get focussed element");
} else {
    AXValueRef selectedTextValue = NULL;
    AXError getSelectedTextError = AXUIElementCopyAttributeValue(focussedElement, kAXSelectedTextAttribute, (CFTypeRef *)&selectedTextValue);
    if (getSelectedTextError == kAXErrorSuccess) {

        selectedText = (__bridge NSString *)(selectedTextValue);
        NSLog(@"%@", selectedText);
    } else {
        NSLog(@"Could not get selected text");
    }
}
if (focussedElement != NULL) CFRelease(focussedElement);
CFRelease(systemWideElement);

The problem here is that it does not work with apps like Safari and Mail...

Thanks


Solution

  • This is actually very easy, kAXSelectedTextAttribute is your friend.

    extension AXUIElement {    
        var selectedText: String? {
            rawValue(for: kAXSelectedTextAttribute) as? String
        }
            
        func rawValue(for attribute: String) -> AnyObject? {
            var rawValue: AnyObject?
            let error = AXUIElementCopyAttributeValue(self, attribute as CFString, &rawValue)
            return error == .success ? rawValue : nil
        }
    }