Panos Karampis
Panos Karampis

Reputation: 586

Get window values under mouse

I am playing with Cocoa/Objective-C and I would like to ask you if it is possible to get window information such as pid, window name from a window that is inactive. What I precisely mean is, in the case there are two non full screen (nor maximized) windows A and B of different tasks, lets say Chrome(A) and Firefox(B), with active window A and mouse cursor above window B, can I get such info without having to click into window B and bring it in foreground?

I have noticed that for example scrolling in an inactive window scrolls the context as if it was in foreground, so my guess is that this is doable.

Any tips would be really welcome.

Thank you

Upvotes: 6

Views: 1877

Answers (2)

Noah Nuebling
Noah Nuebling

Reputation: 309

You can get the ID of the window under the mouse pointer using [NSWindow windowNumberAtPoint:belowWindowWithWindowNumber:]. Then you can use CGWindowListCopyWindowInfo() to get any info on the window. This is significantly faster than using the AXUI API mentioned in the other answer. I was using the AXUI API for my own project but it was so slow that it was sometimes causing hangs and other problems, so I don't recommend using it.

Here's my implementation for getting the app under the mouse pointer without using the slow AXUI API:

+ (NSRunningApplication * _Nullable)appUnderMousePointer:(NSPoint)pointerLocation {

    /// Get PID under mouse pointer
    
    pid_t pidUnderPointer = -1; /// I hope -1 is actually unused?
    
    CGWindowID windowNumber = (CGWindowID)[NSWindow windowNumberAtPoint:pointerLocation belowWindowWithWindowNumber:0];
    NSArray *windowInfo = (__bridge_transfer NSArray *)CGWindowListCopyWindowInfo(kCGWindowListOptionIncludingWindow, windowNumber);
    if (windowInfo.count > 0) {
        pidUnderPointer = [windowInfo[0][(__bridge NSString *)kCGWindowOwnerPID] intValue];
    }
    
    /// Get runningApplication

    NSRunningApplication *appUnderMousePointer = [NSRunningApplication runningApplicationWithProcessIdentifier:pidUnderPointer];
    
    return appUnderMousePointer;
}

You can get the pointerLocation easily using [NSEvent mouseLocation]. If you already have a CGEvent you can also get the pointerLocation using CGEventGetLocation(). Obtaining the location from the latest CGEvent instead of using [NSEvent mouseLocation] ensures that the location is up-to-date and should be more efficient. However, if you get the pointerLocation from a CGEvent, you need to flip the coordinates before plugging them into [NSWindow windowNumberAtPoint:belowWindowWithWindowNumber:]. That's because [NSWindow windowNumberAtPoint:belowWindowWithWindowNumber:] expects the pointerLocation to be given in Cocoa screen coordinates, while CGEventGetLocation() provides the pointer location in Quartz screen coordinates. Learn how to flip the coordinates here.

Upvotes: 0

Panos Karampis
Panos Karampis

Reputation: 586

I will answer my own question: It possible with accessibility api & carbon a) register a wide event:

AXUIElementRef _systemWideElement = AXUIElementCreateSystemWide();

b)convert carbon to screen point

- (CGPoint)carbonScreenPointFromCocoaScreenPoint:(NSPoint)cocoaPoint {
    NSScreen *foundScreen = nil;
    CGPoint thePoint;

    for (NSScreen *screen in [NSScreen screens]) {
        if (NSPointInRect(cocoaPoint, [screen frame])) {
            foundScreen = screen;
        }
    }

    if (foundScreen) {
        CGFloat screenMaxY = NSMaxY([foundScreen frame]);

        thePoint = CGPointMake(cocoaPoint.x, screenMaxY - cocoaPoint.y - 1);
    } else {
        thePoint = CGPointMake(0.0, 0.0);
    }

    return thePoint;
}

c) get process under mouse

NSPoint cocoaPoint = [NSEvent mouseLocation];
if (!NSEqualPoints(cocoaPoint, _lastMousePoint)) {
    CGPoint pointAsCGPoint = [self carbonScreenPointFromCocoaScreenPoint:cocoaPoint];

    AXUIElementRef newElement = NULL;
    if (AXUIElementCopyElementAtPosition( _systemWideElement, pointAsCGPoint.x, pointAsCGPoint.y, &newElement ) == kAXErrorSuccess) {

        NSLog(@"%@",newElement);

    }
    _lastMousePoint = cocoaPoint;
}

Credits to https://developer.apple.com/library/mac/samplecode/UIElementInspector/Introduction/Intro.html

nslog gives something like <AXUIElement 0x6000000583c0> {pid=39429}

ps aux | grep 39429

:39429   0.2  5.5  5109480 916500   ??  U     1:57PM   3:34.67 /Applications/Xcode.app/Contents/MacOS/Xcode

Upvotes: 8

Related Questions