Search code examples
objective-ccocoascreen-resolutionhid

Determining Mouse Cursor X Position on NSScreen When Multiple NSScreens are Present in macOS Catalina


My Mac set up is like this: 5K display arranged on top of the Mac's built-in display.

Image of display arrangement in macOS System Preferences

If my mouse cursor is on the 5K display and I try to get the mouse position like this:

CGEventRef ourEvent = CGEventCreate(NULL);
NSPoint point = CGEventGetLocation(ourEvent);
CFRelease(ourEvent);

I'm getting an unexpected return value for point.x. For example if the cursor is in this position:

Image showing where mouse cursor is on screen

NSLog(@"MOUSE POSITION X: %.0f px", point.x);

Then I get back MOUSE POSITION X: 1950 px. An app like Xscope reports this x position as 3900 px, however.

Image showing mouse cursor X,Y position as reported by app Xscope

Neither of these values seem correct, as the screen is reported as being 2560 wide:

NSScreen *currentScreen = [NSScreen currentScreenForMouseLocation];
NSLog(@"SCREEN WIDTH: %f", currentScreen.frame.size.width);

Note: currentScreenForMouseLocation comes from NSScreen-PointConversion.

And if I move the arrangement 13" built-in display off center, but still underneath the 5K display, the point.x value is even more inaccurate. I can see there's some relational math going on here between the differences in screen size/resolution, but I'm having a hard time trying to figure out exactly what is going on and how to eventually turn this into code where I can get the real point.x value for the mouse cursor position.

The issue I'm trying to solve is that I need to programmatically show an NSStatusItem's menu, and I need to determine if the menu is too wide to show on the right side of the NSStatusItem. The code I have works fine generally, except in this specific situation of vertically arranged displays. In this situation, something like this can happen:

Image showing misplaced NSStatusItem menu


Solution

  • The built-in display is set as the primary display. You can tell in the screenshot of the Displays pane of System Preferences, because the symbolic menu bar is on it.

    That means that the upper-left of that is the origin of the Cocoa coordinate system. So, any point on the external display will be relative to that, so the relative positions of the displays affects the reported position of the cursor.

    I suggest, first, that you use Cocoa, not Core Graphics, to get the current mouse position. Then, you need to adjust it to be relative to the origin of the screen it's on (if that's what you're interested in):

    NSPoint pt = [NSEvent mouseLocation];
    NSScreen* currentScreen = [NSScreen currentScreenForMouseLocation];
    pt.x -= currentScreen.frame.origin.x;
    pt.y -= currentScreen.frame.origin.y;
    

    That said, it sounds like you're just going to compare the point to the screen's right edge, which would also be reported relative to the primary display's origin. So, why not just compare the original point to the screen's frame's MaxX()"

    NSPoint pt = [NSEvent mouseLocation];
    NSScreen* currentScreen = [NSScreen currentScreenForMouseLocation];
    if (pt.x + menuWidth >= NSMaxX(currentScreen.frame))
        ...;