From ffa76a42b2ffddb9271065af794281a170a4bd6f Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Sat, 23 May 2026 23:42:00 +0800
Subject: [PATCH] Fix fullscreen Space overlay presentation
---
Release/AppStore-7.6.0-20260523.2335/BUILD_SIGN_UPLOAD.md | 2
Apptag/ApptagApp.swift | 71 +++++++++++++++++++++--
Scripts/window_logic_qa.sh | 54 ++++++++++++++---
Release/AppStore-7.6.0-20260523.2335/README.md | 6 +
Release/AppStore-7.6.0-20260523.2335/APP_STORE_CONNECT_METADATA.md | 0
Release/AppStore-7.6.0-20260523.2335/QA_RELEASE_EVIDENCE.md | 8 +-
Release/AppStore-7.6.0-20260523.2335/FIRST_TIME_MAC_APP_STORE_TODO.md | 0
7 files changed, 118 insertions(+), 23 deletions(-)
diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index e11a479..075c7fc 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -258,13 +258,18 @@
}
}
- private func refreshLauncherChromeState(activate: Bool = false) {
+ private func refreshLauncherChromeState(activate: Bool = false, avoidSpaceSwitch: Bool = false) {
let showDock = UserDefaults.standard.bool(forKey: Self.showDockIconKey)
lastShowDockIcon = showDock
- let desiredPolicy: NSApplication.ActivationPolicy = requiresForegroundOwnership
+ let shouldStayAccessoryForCurrentFullscreenSpace = avoidSpaceSwitch
+ && isOverlayVisible
+ && !isSettingsVisible
+ let desiredPolicy: NSApplication.ActivationPolicy = shouldStayAccessoryForCurrentFullscreenSpace
+ ? .accessory
+ : (requiresForegroundOwnership
? .regular
- : (showDock ? .regular : .accessory)
+ : (showDock ? .regular : .accessory))
if NSApp.activationPolicy() != desiredPolicy {
NSApp.setActivationPolicy(desiredPolicy)
}
@@ -274,7 +279,7 @@
NSApp.presentationOptions = desiredPresentation
}
- if activate && requiresForegroundOwnership {
+ if activate && requiresForegroundOwnership && !shouldStayAccessoryForCurrentFullscreenSpace {
let keyWindow = isSettingsVisible ? settingsWindow : (isOverlayVisible ? overlayWindow : nil)
claimLauncherForeground(
keyWindow: keyWindow,
@@ -749,10 +754,12 @@
}
private func showOrFocusOverlay(preferredScreen: NSScreen? = nil) {
+ let placement = overlayPlacementContextForNextOverlay(preferredScreen: preferredScreen)
+ let shouldAvoidSpaceSwitch = placement.map { hasFullscreenWindowOnScreen($0.screen) } ?? false
if let overlayWindow, overlayWindow.isVisible {
moveOverlayToCurrentPlacement(preferredScreen: preferredScreen)
overlayWindow.level = currentOverlayLevel
- refreshLauncherChromeState(activate: true)
+ refreshLauncherChromeState(activate: !shouldAvoidSpaceSwitch, avoidSpaceSwitch: shouldAvoidSpaceSwitch)
overlayWindow.makeKeyAndOrderFront(nil)
overlayWindow.orderFrontRegardless()
if let settingsWindow, settingsWindow.isVisible {
@@ -778,7 +785,8 @@
) {
guard let placement = overlayPlacementContextForNextOverlay(preferredScreen: preferredScreen) else { return }
- let shouldStageAsAccessory = shouldStageOverlayAsAccessory
+ let shouldAvoidSpaceSwitch = hasFullscreenWindowOnScreen(placement.screen)
+ let shouldStageAsAccessory = shouldStageOverlayAsAccessory || shouldAvoidSpaceSwitch
if !stagedForAllSpaces && shouldStageAsAccessory {
// Let the all-spaces panel attach to the pointer's display before the app reclaims focus.
@@ -831,7 +839,7 @@
let placementFrame = placement.frame
let finishForegroundClaim: () -> Void = { [weak self, weak window] in
guard let self, let window, self.overlayWindow === window else { return }
- self.refreshLauncherChromeState(activate: true)
+ self.refreshLauncherChromeState(activate: !shouldAvoidSpaceSwitch, avoidSpaceSwitch: shouldAvoidSpaceSwitch)
if window.frame != placementFrame {
window.setFrame(placementFrame, display: true)
window.makeKeyAndOrderFront(nil)
@@ -848,6 +856,42 @@
} else {
finishForegroundClaim()
}
+ }
+
+ private func hasFullscreenWindowOnScreen(_ screen: NSScreen) -> Bool {
+ let screenFrame = screen.frame
+ let windows = CGWindowListCopyWindowInfo([.optionOnScreenOnly, .excludeDesktopElements], kCGNullWindowID) as? [[String: Any]] ?? []
+ return windows.contains { info in
+ guard let owner = info[kCGWindowOwnerName as String] as? String,
+ owner != AppIdentity.displayName,
+ owner != "Window Server",
+ owner != "Dock",
+ owner != "loginwindow",
+ let layer = info[kCGWindowLayer as String] as? Int,
+ layer == 0,
+ let bounds = info[kCGWindowBounds as String] as? NSDictionary
+ else { return false }
+
+ let x = cgWindowDimension(bounds, "X")
+ let y = cgWindowDimension(bounds, "Y")
+ let width = cgWindowDimension(bounds, "Width")
+ let height = cgWindowDimension(bounds, "Height")
+ let windowFrame = NSRect(x: x, y: y, width: width, height: height)
+ let widthMatches = abs(windowFrame.width - screenFrame.width) <= 12
+ let heightMatches = windowFrame.height >= screenFrame.height * 0.88
+ let horizontallyAligned = abs(windowFrame.midX - screenFrame.midX) <= 12
+ return widthMatches && heightMatches && horizontallyAligned
+ }
+ }
+
+ private func cgWindowDimension(_ bounds: NSDictionary, _ key: String) -> CGFloat {
+ if let value = bounds[key] as? CGFloat {
+ return value
+ }
+ if let value = bounds[key] as? NSNumber {
+ return CGFloat(truncating: value)
+ }
+ return 0
}
private func installOverlayKeyMonitor() {
@@ -1119,6 +1163,11 @@
return
}
+ if self.isAppOwnedDocumentWindow(keyWindow) {
+ self.prepareSettingsWindow(keyWindow)
+ return
+ }
+
// Settings/Preferences window -> float it above overlay for real-time preview.
if self.isSettingsWindowCandidate(keyWindow) {
self.prepareSettingsWindow(keyWindow)
@@ -1176,6 +1225,7 @@
private func isSettingsWindowCandidate(_ window: NSWindow) -> Bool {
if window == settingsWindow { return true }
if window.identifier?.rawValue == "TagLauncherPreferencesWindow" { return true }
+ if isAppOwnedDocumentWindow(window) { return true }
guard NSApp.windows.contains(window),
window != overlayWindow,
window.isVisible,
@@ -1184,6 +1234,13 @@
return settingsWindowTitleCandidates().contains(normalizedWindowTitle(window.title))
}
+ private func isAppOwnedDocumentWindow(_ window: NSWindow) -> Bool {
+ NSApp.windows.contains(window)
+ && window != overlayWindow
+ && window.isVisible
+ && !(window is NSPanel)
+ }
+
private func settingsWindowTitleCandidates() -> Set<String> {
let keys = [
"menu.preferences",
diff --git a/Release/AppStore-7.6.0-20260523.1803/APP_STORE_CONNECT_METADATA.md b/Release/AppStore-7.6.0-20260523.2335/APP_STORE_CONNECT_METADATA.md
similarity index 100%
rename from Release/AppStore-7.6.0-20260523.1803/APP_STORE_CONNECT_METADATA.md
rename to Release/AppStore-7.6.0-20260523.2335/APP_STORE_CONNECT_METADATA.md
diff --git a/Release/AppStore-7.6.0-20260523.1803/BUILD_SIGN_UPLOAD.md b/Release/AppStore-7.6.0-20260523.2335/BUILD_SIGN_UPLOAD.md
similarity index 98%
rename from Release/AppStore-7.6.0-20260523.1803/BUILD_SIGN_UPLOAD.md
rename to Release/AppStore-7.6.0-20260523.2335/BUILD_SIGN_UPLOAD.md
index 5cdb9b3..7bc5a21 100644
--- a/Release/AppStore-7.6.0-20260523.1803/BUILD_SIGN_UPLOAD.md
+++ b/Release/AppStore-7.6.0-20260523.2335/BUILD_SIGN_UPLOAD.md
@@ -50,7 +50,7 @@
APP_STORE=1 \
CODESIGN_IDENTITY="3rd Party Mac Developer Application: <TEAM NAME> (<TEAMID>)" \
-APP_BUILD=20260523.1803 \
+APP_BUILD=20260523.2335 \
bash build.sh
productbuild \
diff --git a/Release/AppStore-7.6.0-20260523.1803/FIRST_TIME_MAC_APP_STORE_TODO.md b/Release/AppStore-7.6.0-20260523.2335/FIRST_TIME_MAC_APP_STORE_TODO.md
similarity index 100%
rename from Release/AppStore-7.6.0-20260523.1803/FIRST_TIME_MAC_APP_STORE_TODO.md
rename to Release/AppStore-7.6.0-20260523.2335/FIRST_TIME_MAC_APP_STORE_TODO.md
diff --git a/Release/AppStore-7.6.0-20260523.1803/QA_RELEASE_EVIDENCE.md b/Release/AppStore-7.6.0-20260523.2335/QA_RELEASE_EVIDENCE.md
similarity index 87%
rename from Release/AppStore-7.6.0-20260523.1803/QA_RELEASE_EVIDENCE.md
rename to Release/AppStore-7.6.0-20260523.2335/QA_RELEASE_EVIDENCE.md
index e5745c5..0aa6f5b 100644
--- a/Release/AppStore-7.6.0-20260523.1803/QA_RELEASE_EVIDENCE.md
+++ b/Release/AppStore-7.6.0-20260523.2335/QA_RELEASE_EVIDENCE.md
@@ -3,7 +3,7 @@
## Local Build
- Version: `7.6.0`
-- Build: `20260523.1803`
+- Build: `20260523.2335`
- App path: `/Users/ar/Projects/Taglauncher/build/TagLauncher.app`
- DMG path: `/Users/ar/Projects/Taglauncher/build/TagLauncher.dmg`
- App executable size: `1.4M`
@@ -12,8 +12,8 @@
## Hashes
```text
-c848f902a8219547b59767b172d5b26453748e7f34ee1ce7d10c421fc61005de build/TagLauncher.dmg
-8a541e00734f0bd5f461b13881e194b29cb9c33b79fc0203cd59864680928d4e build/TagLauncher.app/Contents/MacOS/TagLauncher
+370adb4ad671ec910cc0edf7e6f1ae3ebdd59cbd063f5d8439f2c7e8b05fd0e1 build/TagLauncher.dmg
+9f808b6fc81b587a1508aa31c261cdbe187a3b7727daf8bf163788ba49eb2333 build/TagLauncher.app/Contents/MacOS/TagLauncher
```
## Commands Run
@@ -40,6 +40,8 @@
- Repeated self-launch keeps exactly one process.
- Repeated self-launch keeps exactly one Dock tile.
- Fullscreen Space overlay appears above the current fullscreen app.
+- Fullscreen Space overlay is checked with `showDockIcon=true` and `showDockIcon=false`.
+- Fullscreen Space overlay bounds must match the fullscreen target display and remain stable across repeated samples.
- App grid claims foreground, hides Dock, and keeps menu bar visible.
- Settings floats above app grid.
- File panel floats above Settings.
diff --git a/Release/AppStore-7.6.0-20260523.1803/README.md b/Release/AppStore-7.6.0-20260523.2335/README.md
similarity index 89%
rename from Release/AppStore-7.6.0-20260523.1803/README.md
rename to Release/AppStore-7.6.0-20260523.2335/README.md
index 52b8f4d..0d5ddbf 100644
--- a/Release/AppStore-7.6.0-20260523.1803/README.md
+++ b/Release/AppStore-7.6.0-20260523.2335/README.md
@@ -8,9 +8,9 @@
- Platform: macOS / Mac App Store
- Bundle ID: `com.taglauncher.app`
- Version: `7.6.0`
-- Local QA build: `20260523.1803`
+- Local QA build: `20260523.2335`
- Source branch at preparation time: `main`
-- Release folder: `Release/AppStore-7.6.0-20260523.1803`
+- Release folder: `Release/AppStore-7.6.0-20260523.2335`
## What Changed For This Release
@@ -21,6 +21,8 @@
- Removed the invalid all-spaces + move-to-active-space window behavior combination that caused the 07:23 crash.
- Removed the broken macOS system Help menu entry.
- Added an About-page Help PDF button that opens the online PDF matching the current app language, plus a copy-link button.
+- Hardened fullscreen Space presentation so the app grid stays above the current fullscreen app instead of moving to another Space.
+- Expanded window QA to cover fullscreen Space behavior with Dock icon both enabled and disabled, including stability checks.
- Strengthened the window logic QA script to cover fullscreen Space, duplicate Dock tiles, repeated self-launch, force quit layering, Settings/file panel layering, Quick Search, and two-display pointer-following behavior.
## Folder Contents
diff --git a/Scripts/window_logic_qa.sh b/Scripts/window_logic_qa.sh
index 04524c3..bb6877c 100755
--- a/Scripts/window_logic_qa.sh
+++ b/Scripts/window_logic_qa.sh
@@ -417,6 +417,17 @@
guard let target = windows.first(where: { $0.name == "TagLauncherFullscreenQATargetFullscreen" }) else {
fail("fullscreen target disappeared; TagLauncher likely switched to another Space")
}
+ let overlayWidth = dimension(tag[0].bounds, "Width")
+ let overlayHeight = dimension(tag[0].bounds, "Height")
+ let overlayMidX = dimension(tag[0].bounds, "X") + overlayWidth / 2
+ let targetWidth = dimension(target.bounds, "Width")
+ let targetHeight = dimension(target.bounds, "Height")
+ let targetMidX = dimension(target.bounds, "X") + targetWidth / 2
+ guard abs(overlayWidth - targetWidth) <= 12,
+ overlayHeight >= targetHeight * 0.88,
+ abs(overlayMidX - targetMidX) <= 12 else {
+ fail("fullscreen overlay is not on the target fullscreen display: overlay=\(tag[0].bounds) target=\(target.bounds)")
+ }
guard tag[0].layer > target.layer else {
fail("TagLauncher layer \(tag[0].layer) is not above fullscreen target layer \(target.layer)")
}
@@ -562,6 +573,18 @@
return 1
}
+assert_fullscreen_overlay_stable() {
+ local output=""
+ for _ in {1..20}; do
+ if ! output="$(swift "$assert_swift" fullscreen-overlay 2>&1)"; then
+ printf '%s\n' "$output" >&2
+ return 1
+ fi
+ sleep 0.1
+ done
+ printf '%s\n' "$output"
+}
+
show_overlay() {
send_main_hotkey
if wait_swift_assert overlay >/dev/null 2>&1; then
@@ -650,16 +673,27 @@
sleep 0.4
wait_swift_assert no-overlay
-log "==> QA fullscreen Space: overlay stays above the current fullscreen app"
-start_fullscreen_qa_target
-send_main_hotkey
-wait_swift_assert fullscreen-overlay
-frontmost="$(osascript -e 'tell application "System Events" to get name of first application process whose frontmost is true')"
-[[ "$frontmost" == "TagLauncher" ]] || { echo "FAIL: fullscreen overlay frontmost app is $frontmost, expected TagLauncher" >&2; exit 1; }
-send_keycode 53
-sleep 0.4
-wait_swift_assert no-overlay
-kill_fullscreen_qa_target
+run_fullscreen_space_case() {
+ local dock_value="$1"
+ log "==> QA fullscreen Space: overlay stays above the current fullscreen app (showDockIcon=$dock_value)"
+ defaults write "$DEFAULTS_DOMAIN" showDockIcon -bool "$dock_value"
+ prepare_isolated_app_instance
+ start_fullscreen_qa_target
+ send_main_hotkey
+ wait_swift_assert fullscreen-overlay
+ assert_fullscreen_overlay_stable
+ send_keycode 53
+ sleep 0.4
+ wait_swift_assert no-overlay
+ kill_fullscreen_qa_target
+}
+
+run_fullscreen_space_case true
+run_fullscreen_space_case false
+
+log "==> Restoring dock-visible QA app instance for remaining checks"
+defaults write "$DEFAULTS_DOMAIN" showDockIcon -bool true
+prepare_isolated_app_instance
log "==> QA 1/7: overlay claims foreground, hides Dock, keeps menu bar visible"
show_overlay
--
Gitblit v1.9.3