How do find out if there is a full-screen app running on a specific NSScreen
Asked Answered
V

4

9

In Cocoa/AppKit, given a screen from [NSScreen screens], how can I find out if there's a full-screen app running on that specific screen? I'm mostly interested in apps that use the Cocoa APIs for full-screen, but if there's a solution that also encompasses other types of full-screen apps, even better. The solution needs be able to pass Mac App Store approval.

My specific use case involves a menu bar app (NSStatusItem) and figuring out whether or not a menubar is shown at all on [NSScreen mainScreen] in order to allow a global keyboard shortcut to show either a popover positioning on the status item (if it's visible) or a floating window if there's no visible status item.

NSScreens themselves don't seem to expose any information about windows/apps, and NSRunningApplication doesn't expose this information either.

Are there perhaps Carbon APIs for finding this out? For example, if I have a list of windows, I could iterate through them and see if any window frames match the screens' frame exactly. On the other hand, there might be apps that have a frame like that but run underneath other apps (like the Backdrop app, https://itunes.apple.com/us/app/backdrop/id411461952?mt=12), so an approach like this would need to look at window levels.

Vaudevillian answered 24/5, 2014 at 10:56 Comment(0)
C
7

You can try the CGWindowList API, such as CGWindowListCopyWindowInfo().

If you just want to know if the menu bar is showing, you should be able to check -[NSApplication currentSystemPresentationOptions] for NSApplicationPresentationAutoHideMenuBar or NSApplicationPresentationHideMenuBar. That method can also tell you if the active app is in Cocoa full-screen mode (NSApplicationPresentationFullScreen).

Coverlet answered 24/5, 2014 at 11:6 Comment(4)
Thanks, I'll look into CGWindowListCopyWindowInfo(). (NSApplication is not helpful because I'm not looking for information about the currently active application, but about if any application is running in fullscreen on a screen.)Vaudevillian
You mean if an app is full-screen on another space? That NSApplication method will tell you if the menu is currently hidden, not just if the calling app asked for it to be hidden. By the way, an app can use Cocoa full-screen mode even if its window doesn't fill the screen. For example, if it has a maximum size. (Although, there may be a shielding window created by Cocoa on its behalf that's observable via CGWindowList.)Coverlet
Note: Using currentSystemPresentationOptions doesn't work—even when there's a full-screen app running on the only screen, I still get back 0 calling it inside my menubar-only application.Vaudevillian
currentSystemPresentationOptions doesn't work for me too. It has rawValue of 0 even when the system is in fullscreen presentation mode. Checking for a window with the name "Fullscreen Backdrop" in the windows returned by CGWindowListCopyWindowInfo is what worked for me.Lodging
I
4

Here's a solution based on CGWindowListCopyWindowInfo in Swift.

func fullScreenWindows(fullScreen: Bool) -> [CGWindowID] {
    var winList: [CGWindowID] = []
    // if you want to get the windows in full screen, you MUST make sure the option excluding 'optionOnScreenOnly'
    let option: CGWindowListOption = fullScreen ? .excludeDesktopElements : [.excludeDesktopElements, .optionOnScreenOnly]
    guard let winArray: CFArray = CGWindowListCopyWindowInfo(option, kCGNullWindowID) else {
        return winList
    }
    for i in 0..<CFArrayGetCount(winArray) {
        
        // current window's info
        let winInfo = unsafeBitCast(CFArrayGetValueAtIndex(winArray, i), to: CFDictionary.self)
        
        // current window's bounds
        guard let boundsDict = (winInfo as NSDictionary)[kCGWindowBounds],
            let bounds = CGRect.init(dictionaryRepresentation: boundsDict as! CFDictionary) else {
            continue
        }
        
        // to check the window is in full screen or not
        guard __CGSizeEqualToSize(NSScreen.main!.frame.size, bounds.size) else {
            continue
        }
        
        // current window's id
        guard let winId = (winInfo as NSDictionary)[kCGWindowNumber] as? CGWindowID,
            winId == kCGNullWindowID else {
            continue
        }
        
        winList.append(winId)
    }
    return winList
}
Index answered 13/3, 2020 at 10:25 Comment(0)
D
2

Here's a solution based on CGWindowListCopyWindowInfo, as Ken Thomases suggested in his answer:

- (BOOL)fullScreenAppPresentOn:(NSScreen *)screen
{
    // Get all of the visible windows (across all running applications)
    NSArray<NSDictionary*> *windowInfoList = (__bridge_transfer id)CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements, kCGNullWindowID);

    // For each window, see if the bounds are the same size as the screen's frame
    for (int windowInfoListIndex = 0; windowInfoListIndex < (int)windowsInfoList.count; windowInfoListIndex++)
    {    
        NSDictionary *windowInfo = windowInfoList[windowInfoListIndex];

        CFDictionaryRef windowInfoRef = (__bridge CFDictionaryRef) windowInfo[(__bridge NSString *)kCGWindowBounds];
        CGRect windowBounds;
        CGRectMakeWithDictionaryRepresentation(windowInfoRef, &windowBounds);

        if (CGRectEqualToRect([screen frame], windowBounds))
        {
            return YES;
        }
    }

    return NO;
}
Darbie answered 10/6, 2019 at 12:1 Comment(0)
J
0

Coming back to this old thread. The above solutions were not working for me, since the screen.frame is always bigger than the bounds of the window (menubar/notch offsets), I came up with a modified solution:

private func otherAppIsInFullScreen() -> Bool {
    let option: CGWindowListOption = [.excludeDesktopElements, .optionOnScreenOnly]
    guard let infos  = CGWindowListCopyWindowInfo(option, kCGNullWindowID) as? [[ String : Any]] else {
        return false
    }

    guard
        let visibleFrame = NSScreen.main?.visibleFrame,
        let screenFrame = NSScreen.main?.frame  else {
        return false
    }

    for info in infos {
        guard
            let boundsDict = info[kCGWindowBounds as String] as? NSDictionary,
            let bounds = CGRect(dictionaryRepresentation: boundsDict) else {
            continue
        }

        guard bounds.size.width >= visibleFrame.size.width else {
            continue
        }

        var isFullscreen = false

        if NSApp.presentationOptions.contains(.autoHideMenuBar) {
            isFullscreen = bounds.maxY >= visibleFrame.size.height
        } else {
            isFullscreen = bounds.size.height > visibleFrame.size.height
        }

        guard isFullscreen else {
            continue
        }

        return true
    }

    return false
}

So the height or the window must be larger than the visibleFrame (extends over the dock).

If the menu bar is auto hidden, this approach does not work, we have to take the maxY into account in order to detect, if the window is full screen.

While this approach works for most cases, it fails to detect (obviously), when the dock and menubar are auto hidden and the window is full size (but not full screen).

So I'm wondering, if there is a better approach than comparing frames…

Jose answered 31/1 at 8:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.