(OS X) Detecting when front app goes into fullscreen mode

macos

Solution

Based on @Chuck's suggestion, I've come up with a solution that works somewhat, but may not be foolproof.

The solution is based on the assumption that 10.7's new fullscreen mode for windows moves these windows to a new Screen Space. Therefore, we subscribe to notifications for changes to the active space. In that notification handler, we check the window list to detect whether the menubar is included. If it is not, it probably means that we're in a fullscreen space.

Checking for the presence of the "Menubar" window is the best test I could come up with based on Chuck's idea. I don't like it too much, though, because it makes assumptions on the naming and presence of internally managed windows.

Here's the test code that goes inside AppDelegate.m, which also includes the test for the other app-wide fullscreen mode:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSApplication *app = [NSApplication sharedApplication];

    // Observe full screen mode from apps setting SystemUIMode
    // or invoking 'setPresentationOptions'
    [app addObserver:self
          forKeyPath:@"currentSystemPresentationOptions"
             options:NSKeyValueObservingOptionNew
             context:NULL];

    // Observe full screen mode from apps using a separate space
    // (i.e. those providing the fullscreen widget at the right
    // of their window title bar).
    [[[NSWorkspace sharedWorkspace] notificationCenter]
        addObserverForName:NSWorkspaceActiveSpaceDidChangeNotification
        object:NULL queue:NULL
        usingBlock:^(NSNotification *note)
        {
            // The active space changed.
            // Now we need to detect if this is a fullscreen space.
            // Let's look at the windows...
            NSArray *windows = CFBridgingRelease(CGWindowListCopyWindowInfo
                        (kCGWindowListOptionOnScreenOnly, kCGNullWindowID));
            //NSLog(@"active space change: %@", windows);

            // We detect full screen spaces by checking if there's a menubar
            // in the window list.
            // If not, we assume it's in fullscreen mode.
            BOOL hasMenubar = NO;
            for (NSDictionary *d in windows) {
                if ([d[@"kCGWindowOwnerName"] isEqualToString:@"Window Server"]
                 && [d[@"kCGWindowName"] isEqualToString:@"Menubar"]) {
                    hasMenubar = YES;
                    break;
                }
            }
            NSLog(@"fullscreen: %@", hasMenubar ? @"No" : @"Yes");
        }
     ];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if ([keyPath isEqual:@"currentSystemPresentationOptions"]) {
        NSLog(@"currentSystemPresentationOptions: %@", [change objectForKey:NSKeyValueChangeNewKey]); // a value of 4 indicates fullscreen mode
    }
}

Problem

I'm writing a "UIElement" app that shows a status window on the side of the screen, similar to the Dock. Now, when a program takes over the entire screen, I need to hide my status window, just like the Dock does. What are my options to detect this and the inverse event? I like to avoid polling via a timed event and also cannot use undocumented tricks (such as suggested here) What doesn't work: Registering a Carbon Event Handler for the `kEventAppSystemUIModeChanged` event isn't sufficient - it works to detect VLC's full screen mode, but not for modern Cocoa apps that use the new fullscreen widget at the top right corner of their windows. Similarly, following Apple's instructions about the NSApplication presentationOptions API by observing changes to the currentSystemPresentationOptions property does not help, either - again, it only informs about VLC's fullscreen mode, but not about apps using the window' top right fullscreen widget. Monitoring changes to the screen configuration using `CGDisplayRegisterReconfigurationCallback` is not working because there aren't any callbacks for these fullscreen modes.

Original source

Related problems