From cb2f97f11d4a769c86e78818f91e45639dd40016 Mon Sep 17 00:00:00 2001 From: WarmUpTill <19472752+WarmUpTill@users.noreply.github.com> Date: Wed, 20 May 2026 15:17:56 +0200 Subject: [PATCH] Adapt macOS fullscreen window detection to be more reliable --- deps/cpp-httplib | 2 +- lib/osx/advanced-scene-switcher-osx.mm | 151 ++++++++++++++++--------- 2 files changed, 100 insertions(+), 53 deletions(-) diff --git a/deps/cpp-httplib b/deps/cpp-httplib index c7ed1796..811dd0b6 160000 --- a/deps/cpp-httplib +++ b/deps/cpp-httplib @@ -1 +1 @@ -Subproject commit c7ed1796a778592ae5a122287a16b1dd4770858a +Subproject commit 811dd0b6f2382f50f801f3cfdb5ba349d08bb575 diff --git a/lib/osx/advanced-scene-switcher-osx.mm b/lib/osx/advanced-scene-switcher-osx.mm index 27d5a898..84ef3a15 100644 --- a/lib/osx/advanced-scene-switcher-osx.mm +++ b/lib/osx/advanced-scene-switcher-osx.mm @@ -73,52 +73,49 @@ std::string GetCurrentWindowTitle() return title; } -bool isWindowOriginOnScreen(NSDictionary *app, NSScreen *screen, - bool fullscreen = false) -{ - NSArray *screens = [NSScreen screens]; - NSRect mainScreenFrame = [screens[0] frame]; - NSRect screenFrame; - if (fullscreen) { - screenFrame = [screen frame]; - } else { - screenFrame = [screen visibleFrame]; - } - NSRect windowBounds; - CGRectMakeWithDictionaryRepresentation( - (CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"], - &windowBounds); - - return (windowBounds.origin.x == screenFrame.origin.x && - (mainScreenFrame.size.height - screenFrame.size.height - - windowBounds.origin.y == - screenFrame.origin.y)); -} bool isWindowMaximizedOnScreen(NSDictionary *app, NSScreen *screen) { - double maximizedTolerance = 0.99; - NSRect screenFrame = [screen frame]; NSRect windowBounds; CGRectMakeWithDictionaryRepresentation( (CFDictionaryRef)[app objectForKey:@"kCGWindowBounds"], &windowBounds); - int sumX = windowBounds.origin.x + windowBounds.size.width; - int sumY = windowBounds.origin.y + windowBounds.size.height; + // CGWindowList: flipped coords, origin at top-left of main display. + // NSScreen: Cocoa coords, origin at bottom-left of main display. + NSRect mainFrame = [[NSScreen screens][0] frame]; + NSRect visFrame = [screen visibleFrame]; - // Return false if window spans over multiple screens - if (sumX > screenFrame.size.width) { + // Convert visible area to CGWindow coordinates. + // screenTop is just below the menubar; screenBottom is just above the + // dock. + CGFloat screenLeft = visFrame.origin.x; + CGFloat screenTop = + mainFrame.size.height - visFrame.origin.y - visFrame.size.height; + CGFloat screenRight = screenLeft + visFrame.size.width; + CGFloat screenBottom = screenTop + visFrame.size.height; + + const double tolerance = 4.0; + + // Width must span the full visible width. + if (fabs(windowBounds.origin.x - screenLeft) > tolerance) { return false; } - if (sumY > screenFrame.size.height) { + if (fabs((windowBounds.origin.x + windowBounds.size.width) - + screenRight) > tolerance) { return false; } - - return ((double)sumX / (double)screenFrame.size.width) > - maximizedTolerance && - ((double)sumY / (double)screenFrame.size.height) > - maximizedTolerance; + // Bottom edge must align with the visible bottom (above dock). + if (fabs((windowBounds.origin.y + windowBounds.size.height) - + screenBottom) > tolerance) { + return false; + } + // Top edge must be at or above the menubar line (some apps extend + // their window frame behind the menubar). + if (windowBounds.origin.y > screenTop + tolerance) { + return false; + } + return true; } bool isWindowFullscreenOnScreen(NSDictionary *app, NSScreen *screen) @@ -169,6 +166,56 @@ std::vector GetWindows(const WindowQueryOptions &options) } } + std::set maximizedPIDs; + if (options.maximized) { + for (NSDictionary *app in apps) { + int layer = [[app objectForKey:@"kCGWindowLayer"] + intValue]; + if (layer != 0) { + continue; + } + for (NSScreen *screen in screens) { + if (isWindowMaximizedOnScreen(app, + screen)) { + int pid = [[app objectForKey: + @"kCGWindowOwnerPID"] + intValue]; + maximizedPIDs.insert(pid); + break; + } + } + } + } + + // Pre-compute the largest-area window bounds per PID. + // CGWindowList often lists small auxiliary windows (toolbars, + // chrome) before the main content window; using the largest area + // avoids reporting those wrong bounds for geometry. + std::map largestBoundsPerPID; + if (options.geometry || options.maximized) { + for (NSDictionary *app in apps) { + int layer = [[app objectForKey:@"kCGWindowLayer"] + intValue]; + if (layer != 0) { + continue; + } + NSRect b = NSZeroRect; + CGRectMakeWithDictionaryRepresentation( + (CFDictionaryRef)[app + objectForKey:@"kCGWindowBounds"], + &b); + int pid = [[app objectForKey:@"kCGWindowOwnerPID"] + intValue]; + auto it = largestBoundsPerPID.find(pid); + if (it == largestBoundsPerPID.end() || + b.size.width * b.size.height > + it->second.size.width * + it->second.size.height) { + largestBoundsPerPID[pid] = b; + } + } + } + // Track titles already added to avoid duplicates (name + owner) std::vector seen; @@ -218,29 +265,29 @@ std::vector GetWindows(const WindowQueryOptions &options) if (options.geometry || options.fullscreen || options.maximized) { - info.x = (int)bounds.origin.x; - info.y = (int)bounds.origin.y; - info.width = (int)bounds.size.width; - info.height = (int)bounds.size.height; - } - - if (options.fullscreen) { int pid = [[app objectForKey: @"kCGWindowOwnerPID"] intValue]; - info.fullscreen = - fullscreenPIDs.count(pid) > 0; - } + auto it = largestBoundsPerPID.find(pid); + const NSRect &geoBounds = + (it != largestBoundsPerPID.end()) + ? it->second + : bounds; + info.x = (int)geoBounds.origin.x; + info.y = (int)geoBounds.origin.y; + info.width = (int)geoBounds.size.width; + info.height = (int)geoBounds.size.height; - if (options.maximized) { - for (NSScreen *screen in screens) { - if (isWindowOriginOnScreen( - app, screen) && - isWindowMaximizedOnScreen( - app, screen)) { - info.maximized = true; - break; - } + if (options.fullscreen) { + info.fullscreen = + fullscreenPIDs.count(pid) > + 0; + } + + if (options.maximized) { + info.maximized = + maximizedPIDs.count(pid) > + 0; } }