Search code examples
macoscocoamultiple-monitors

NSScreen visibleFrame only accounting for menu bar area on main screen


I noticed that NSScreen's visibleFrame method isn't subtracting the menu bar dimensions on my non-main screens. Say I have the following code:

DB("Cocoa NSScreen rects:");
NSArray *screens = [NSScreen screens];
for(NSUInteger i = 0; i < [screens count]; ++i) {
    NSScreen *screen = [screens objectAtIndex:i];
    CGRect r = [screen visibleFrame];

    const char *suffix = "";
    if(screen == [NSScreen mainScreen])
        suffix = " (main screen)";

    DB("    %lu. (%.2f, %.2f) + (%.2f x %.2f)%s", (unsigned long)i, r.origin.x, r.origin.y, r.size.width, r.size.height, suffix);
}

I run it on my Mac, which has a menu bar on every monitor. I then get the following output:

Cocoa NSScreen rects:
    0. (4.00, 0.00) + (1276.00 x 777.00) (main screen)
    1. (3200.00, 9.00) + (1200.00 x 1920.00)
    2. (1280.00, 800.00) + (1920.00 x 1200.00)

The size of the menu bar and (hidden) dock appears to have been correctly subtracted from the main screen's visible area - but the menu bars on my external monitors have not been accounted for! (Assuming the menu bar is 23 pixels high on every screen - so I would expect screen 1 to be something like 1200x1897 and screen 2 to be around 1920x1877.)

Aside from wondering how big the screen is - and there you'll just have to trust me, I'm afraid! - what am I doing wrong? How do I get accurate screen bounds?

(OS X Yosemite 10.10.3)


Solution

  • Until the program creates an NSWindow - which this program, as far as I can tell, never does - the reported screen bounds appear to be inaccurate. So, before the program fetches the screen bounds, it now runs this bit of code:

    if(!ever_created_hack_window) {
        NSWindow *window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0,0,100,100)
                                                       styleMask:NSTitledWindowMask
                                                         backing:NSBackingStoreBuffered
                                                           defer:YES
                                                          screen:nil];
        [window release];
        window = nil;
    
        ever_created_hack_window = YES;
    }
    

    (ever_created_hack_window is just a global BOOL.)

    Once this has been done, I get the screen dimensions I expect:

    0. (4.00, 0.00) + (1276.00 x 777.00) (main screen)
    1. (3200.00, 9.00) + (1200.00 x 1897.00)
    2. (1280.00, 800.00) + (1920.00 x 1177.00)
    

    Additionally, it now correctly picks up changes in main screen.

    (This could be stuff that is set up by calling UIApplicationMain. This program doesn't do that, either.)