(OS X)检测前端应用程序何时进入全屏模式

时间:2014-05-27 18:47:43

标签: macos

我正在编写一个“UIElement”应用程序,它在屏幕一侧显示一个状态窗口,类似于Dock。

现在,当一个程序占据整个屏幕时,我需要隐藏我的状态窗口,就像Dock一样。

我可以选择检测此事件和反向事件吗?

我喜欢通过定时事件避免轮询,也不能使用未记录的技巧(例如建议的here

什么行不通:

  • kEventAppSystemUIModeChanged事件注册Carbon事件处理程序是不够的 - 它可以检测VLC的全屏模式,但不适用于使用右上角新的全屏小部件的现代Cocoa应用程序他们的窗户。

  • 同样地,通过观察对currentSystemPresentationOptions属性的更改来遵循Apple关于NSApplication presentationOptions API的说明也没有帮助 - 再次,它只通知VLC的全屏模式,而不是关于使用窗口的应用程序'top正确的全屏小部件。

  • 使用CGDisplayRegisterReconfigurationCallback监控对屏幕配置的更改无效,因为这些全屏模式没有回调。

3 个答案:

答案 0 :(得分:1)

由于我之前的回答不适用于检测应用程序之间的全屏模式,因此我做了一些实验。从 Thomas Tempelmann 提出的检查菜单栏是否存在的解决方案开始,我发现了一个我认为可能更可靠的变体。

检查菜单栏的问题在于,在全屏模式下,您可以将鼠标光标移动到屏幕顶部以显示菜单栏,但您仍处于全屏模式。我在CGWindow信息中做了一些爬行,发现当我进入全屏时,"Fullscreen Backdrop"拥有一个名为"Dock"的窗口,而不是全屏模式时它不存在.

这是在 Xcode Playground 中的 Catalina (10.15.6) 上,因此应该在真实应用和 Big Sur(或任何当前操作系统,当您阅读本文时)上进行测试。

这是代码(在 Swift 中......更容易快速测试某些东西)

func isFullScreen() -> Bool
{
    guard let windows = CGWindowListCopyWindowInfo(.optionOnScreenOnly, kCGNullWindowID) else {
        return false
    }

    for window in windows as NSArray
    {
        guard let winInfo = window as? NSDictionary else { continue }
        
        if winInfo["kCGWindowOwnerName"] as? String == "Dock",
           winInfo["kCGWindowName"] as? String == "Fullscreen Backdrop"
        {
            return true
        }
    }
    
    return false
}

答案 1 :(得分:0)

根据@Cucker的建议,我提出了一个有效的解决方案,但可能不是万无一失。

该解决方案基于以下假设:10.7'} new fullscreen mode for windows将这些窗口移动到新的屏幕空间。因此,我们订阅了有关活动空间更改的通知。在该通知处理程序中,我们检查窗口列表以检测是否包含菜单栏。如果不是,则可能意味着我们处于全屏空间。

检查"菜单栏"是否存在窗口是我根据Chuck的想法得出的最佳测试。不过,我不太喜欢它,因为它对内部管理窗口的命名和存在做出了假设。

这里是AppDelegate.m中的测试代码,其中还包括其他应用程序范围的全屏模式的测试:

- (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
    }
}

答案 2 :(得分:0)

编辑注意:不幸的是,这个答案没有提供在不同的应用程序中检测全屏的解决方案,这正是 OP 所要求的。我离开它是因为它确实回答了在同一个应用程序中进行全屏检测的问题 - 例如在需要知道自动更新 keyEquivalents 和标题的通用库中,而不是明确添加的“进入全屏”菜单项比 Apple 自动添加的菜单项要好。

虽然这个问题现在已经很老了,但我最近不得不在 Swift 中检测到全屏模式。虽然它不像在 NSWindow 中查询某个标志那么简单,但正如我们所希望的那样,自 macOS 10.7 起就有了一个简单可靠的解决方案。

每当一个窗口即将进入全屏模式时,NSWindow 发送一个 willEnterFullScreenNotification 通知,当它即将退出全屏模式时,它发送 willExitFullScreenNotification。因此,您为这些通知添加了一个观察者。在以下代码中,我使用它们来设置全局布尔标志。

import Cocoa

/*
 Since notification closures can be run concurrently, we need to guard against
 races on the Boolean flag.  We could use DispatchSemaphore, but it's kind
 over-kill for guarding a simple read/write to a boolean variable.
 os_unfair_lock is appropriate for nanosecond-level contention.  If the wait
 could be milliseconds or longer, DispatchSemaphore is the thing to use.
 
 This extension is just to make using it easier and safer to use.
 */
extension os_unfair_lock
{
    mutating func withLock<R>(block: () throws -> R) rethrows -> R
    {
        os_unfair_lock_lock(&self)
        defer { os_unfair_lock_unlock(&self) }
        
        return try block()
    }
}

fileprivate var fullScreenLock = os_unfair_lock()
public fileprivate(set) var isFullScreen: Bool = false

// Call this function in the app delegate's applicationDidFinishLaunching method
func initializeFullScreenDetection()
{
    _ = NotificationCenter.default.addObserver(
        forName: NSWindow.willEnterFullScreenNotification,
        object: nil,
        queue: nil)
    { _ in
        fullScreenLock.withLock  { isFullScreen = true }
    }
    _ = NotificationCenter.default.addObserver(
        forName: NSWindow.willExitFullScreenNotification,
        object: nil,
        queue: nil)
    { _ in
        fullScreenLock.withLock  { isFullScreen = false }
    }
}

由于观察者闭包可以同时运行,我使用 os_unfair_lock 来保护 _isFullScreen 属性上的竞争。您可以使用 DispatchSemaphore,尽管它只是保护一个布尔标志有点重。回到第一次问这个问题时,OSSpinLock 本来是等价的,但它自 10.12 以来已被弃用。

只需确保在应用程序委托的 initializeFullScreenDetection() 方法中调用 applicationDidFinishLaunching()