From 8058eb48fe3fd1b37be974b5183df6ffc69bfb2c Mon Sep 17 00:00:00 2001
From: Ariver <shanghai3168@gmail.com>
Date: Mon, 18 May 2026 16:33:56 +0800
Subject: [PATCH] fix: show overlay in fullscreen spaces

---
 VoiceSnapGo/build/config.yml                                  |    2 
 TODO.md                                                       |    9 ++++
 VoiceSnapGo/frontend/package-lock.json                        |    4 +-
 VoiceSnapGo/frontend/src/components/settings/AboutPage.svelte |    2 
 VoiceSnapGo/internal/overlay/overlay_darwin.go                |   40 +++++++++++++++----
 VoiceSnapGo/app.go                                            |    4 +-
 VoiceSnapGo/frontend/package.json                             |    2 
 VoiceSnapGo/build/darwin/Info.plist                           |    4 +-
 VoiceSnapGo/build/windows/wails.exe.manifest                  |    2 
 CHANGELOG.md                                                  |   12 ++++++
 10 files changed, 62 insertions(+), 19 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0c6918a..886faf2 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,17 @@
 # Changelog
 
+## v2.1.2 (2026-05-18)
+
+### Bug 修复
+
+- **全屏窗口下悬浮指示器不显示**:macOS 悬浮指示器从普通 `NSWindow` 调整为非激活 `NSPanel`,窗口层级提升到状态栏级别,并补充 `Transient` / `IgnoresCycle` / `FullScreenAuxiliary` / `CanJoinAllSpaces` 行为,确保任意 App 全屏 Space 下也能显示录音胶囊。
+
+### 构建
+
+- build: `20260518.1623`
+
+---
+
 ## v2.1.1 (2026-02-27)
 
 ### Bug 修复
diff --git a/TODO.md b/TODO.md
index ad52f96..3c44637 100644
--- a/TODO.md
+++ b/TODO.md
@@ -21,6 +21,15 @@
 
 ## Done
 
+- [2026-05-18] 修复全屏窗口下语音输入悬浮指示器不显示。
+  - 根因: macOS 全屏 Space 下普通 `NSWindow` + `NSFloatingWindowLevel` 不稳定,容易被全屏 App 压在下层。
+  - 修复: 悬浮指示器改为非激活 `NSPanel`,窗口层级提升到 `kCGStatusWindowLevelKey`,并补充 `Transient`、`IgnoresCycle`、`FullScreenAuxiliary`、`CanJoinAllSpaces` 行为。
+  - 版本: `2.1.2`
+  - build: `20260518.1623`
+  - 输出: `VoiceSnapGo/build/local/arm64/VoiceSnap.app`
+  - 输出: `VoiceSnapGo/build/local/arm64/VoiceSnap-2.1.2-build20260518.1623-arm64-local.dmg`
+  - 兼容输出: `VoiceSnapGo/build/local/arm64/VoiceSnap-2.1.2-arm64-local.dmg`
+  - 已安装到 `/Applications/VoiceSnap.app`;签名验证为 `VoiceSnap Local Code Signing`。
 - [2026-05-18] 将 macOS 后台活动/登录项纳入权限检查面板。
   - 说明: 系统设置里的 “VoiceSnap Local” 是本地稳定签名证书的开发者名,下面挂着 VoiceSnap 的登录项/后台活动。
   - 它不是右 Alt、录音、自动上屏的必需隐私权限,但会影响登录后是否自动在后台运行。
diff --git a/VoiceSnapGo/app.go b/VoiceSnapGo/app.go
index e33d529..2805702 100755
--- a/VoiceSnapGo/app.go
+++ b/VoiceSnapGo/app.go
@@ -26,8 +26,8 @@
 )
 
 const (
-	appVersion        = "2.1.1"
-	appBuild          = "20260518.1604"
+	appVersion        = "2.1.2"
+	appBuild          = "20260518.1623"
 	appDisplayVersion = appVersion + " (build " + appBuild + ")"
 	appName           = "VoiceSnap"
 
diff --git a/VoiceSnapGo/build/config.yml b/VoiceSnapGo/build/config.yml
index b535e1b..4e1cbbe 100755
--- a/VoiceSnapGo/build/config.yml
+++ b/VoiceSnapGo/build/config.yml
@@ -7,7 +7,7 @@
   description: "Offline voice to text tool"
   copyright: "(c) 2025, VoiceSnap"
   comments: "Press and hold hotkey to record, release to transcribe"
-  version: "2.1.1"
+  version: "2.1.2"
 
 dev_mode:
   root_path: .
diff --git a/VoiceSnapGo/build/darwin/Info.plist b/VoiceSnapGo/build/darwin/Info.plist
index e55ed86..926b27b 100755
--- a/VoiceSnapGo/build/darwin/Info.plist
+++ b/VoiceSnapGo/build/darwin/Info.plist
@@ -15,9 +15,9 @@
     <key>CFBundlePackageType</key>
     <string>APPL</string>
     <key>CFBundleShortVersionString</key>
-    <string>2.1.1</string>
+    <string>2.1.2</string>
     <key>CFBundleVersion</key>
-    <string>20260518.1604</string>
+    <string>20260518.1623</string>
     <key>LSMinimumSystemVersion</key>
     <string>11.0</string>
     <key>NSMicrophoneUsageDescription</key>
diff --git a/VoiceSnapGo/build/windows/wails.exe.manifest b/VoiceSnapGo/build/windows/wails.exe.manifest
index 6e4c716..b4dec20 100755
--- a/VoiceSnapGo/build/windows/wails.exe.manifest
+++ b/VoiceSnapGo/build/windows/wails.exe.manifest
@@ -3,7 +3,7 @@
   <assemblyIdentity
     type="win32"
     name="VoiceSnap"
-    version="2.1.1.0"
+    version="2.1.2.0"
     processorArchitecture="*"/>
   <dependency>
     <dependentAssembly>
diff --git a/VoiceSnapGo/frontend/package-lock.json b/VoiceSnapGo/frontend/package-lock.json
index c611880..c3b8e4a 100755
--- a/VoiceSnapGo/frontend/package-lock.json
+++ b/VoiceSnapGo/frontend/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "voicesnap-frontend",
-  "version": "2.1.1",
+  "version": "2.1.2",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "voicesnap-frontend",
-      "version": "2.1.1",
+      "version": "2.1.2",
       "dependencies": {
         "@wailsio/runtime": "latest"
       },
diff --git a/VoiceSnapGo/frontend/package.json b/VoiceSnapGo/frontend/package.json
index d324c00..824edf4 100755
--- a/VoiceSnapGo/frontend/package.json
+++ b/VoiceSnapGo/frontend/package.json
@@ -1,7 +1,7 @@
 {
   "name": "voicesnap-frontend",
   "private": true,
-  "version": "2.1.1",
+  "version": "2.1.2",
   "type": "module",
   "scripts": {
     "dev": "vite dev",
diff --git a/VoiceSnapGo/frontend/src/components/settings/AboutPage.svelte b/VoiceSnapGo/frontend/src/components/settings/AboutPage.svelte
index 530ee30..c4ecfb7 100755
--- a/VoiceSnapGo/frontend/src/components/settings/AboutPage.svelte
+++ b/VoiceSnapGo/frontend/src/components/settings/AboutPage.svelte
@@ -2,7 +2,7 @@
   import { Call } from '@wailsio/runtime'
   import { t } from '../../lib/i18n'
 
-  let version = $state('2.1.1')
+  let version = $state('2.1.2')
   let updateStatus = $state('')
   let updateColor = $state('')
   let checking = $state(false)
diff --git a/VoiceSnapGo/internal/overlay/overlay_darwin.go b/VoiceSnapGo/internal/overlay/overlay_darwin.go
index 25ca770..423cb72 100755
--- a/VoiceSnapGo/internal/overlay/overlay_darwin.go
+++ b/VoiceSnapGo/internal/overlay/overlay_darwin.go
@@ -4,8 +4,9 @@
 
 /*
 #cgo CFLAGS: -x objective-c
-#cgo LDFLAGS: -framework Cocoa
+#cgo LDFLAGS: -framework Cocoa -framework ApplicationServices
 #import <Cocoa/Cocoa.h>
+#import <ApplicationServices/ApplicationServices.h>
 #include <dispatch/dispatch.h>
 #include <string.h>
 
@@ -118,8 +119,8 @@
 
 @end
 
-// ---- Custom NSWindow for drag tracking ----
-@interface VoiceSnapOverlayWindow : NSWindow {
+// ---- Custom NSPanel for drag tracking ----
+@interface VoiceSnapOverlayWindow : NSPanel {
 	NSPoint _dragStartOrigin;
 }
 @end
@@ -148,6 +149,7 @@
 }
 
 - (BOOL)canBecomeKeyWindow { return NO; }
+- (BOOL)canBecomeMainWindow { return NO; }
 
 @end
 
@@ -155,6 +157,21 @@
 static NSWindow* g_window = nil;
 static VoiceSnapOverlayView* g_view = nil;
 static NSTimer* g_timer = nil;
+
+static void applyOverlayWindowPolicy(NSWindow* window) {
+	if (!window) return;
+	[window setLevel:CGWindowLevelForKey(kCGStatusWindowLevelKey)];
+	[window setCollectionBehavior:
+		NSWindowCollectionBehaviorCanJoinAllSpaces |
+		NSWindowCollectionBehaviorStationary |
+		NSWindowCollectionBehaviorFullScreenAuxiliary |
+		NSWindowCollectionBehaviorTransient |
+		NSWindowCollectionBehaviorIgnoresCycle];
+	[window setCanHide:NO];
+	if ([window isKindOfClass:[NSPanel class]]) {
+		[(NSPanel*)window setHidesOnDeactivate:NO];
+	}
+}
 
 static void animateBars(void) {
 	g_state.animTime += 0.033f;
@@ -230,6 +247,7 @@
 	}
 
 	if (g_state.fadeTarget > 0 && ![g_window isVisible]) {
+		applyOverlayWindowPolicy(g_window);
 		[g_window orderFrontRegardless];
 	}
 
@@ -259,17 +277,13 @@
 
 	VoiceSnapOverlayWindow* window = [[VoiceSnapOverlayWindow alloc]
 		initWithContentRect:NSMakeRect(0, 0, CAP_W, CAP_H)
-		styleMask:NSWindowStyleMaskBorderless
+		styleMask:NSWindowStyleMaskBorderless | NSWindowStyleMaskNonactivatingPanel
 		backing:NSBackingStoreBuffered
 		defer:NO];
 
 	[window setOpaque:NO];
 	[window setBackgroundColor:[NSColor clearColor]];
-	[window setLevel:NSFloatingWindowLevel];
-	[window setCollectionBehavior:
-		NSWindowCollectionBehaviorCanJoinAllSpaces |
-		NSWindowCollectionBehaviorStationary |
-		NSWindowCollectionBehaviorFullScreenAuxiliary];
+	applyOverlayWindowPolicy(window);
 	[window setIgnoresMouseEvents:NO];
 	[window setMovableByWindowBackground:YES];
 	[window setHasShadow:NO];
@@ -307,11 +321,19 @@
 	g_state.hasPosition = 1;
 }
 
+static void doBringOverlayForward(void* ctx) {
+	(void)ctx;
+	if (!g_window) return;
+	applyOverlayWindowPolicy(g_window);
+	[g_window orderFrontRegardless];
+}
+
 static void overlayShow(void) {
 	g_state.fadeTarget = 1.0f;
 	if (!g_state.hasPosition) {
 		dispatch_async_f(dispatch_get_main_queue(), NULL, doAutoPosition);
 	}
+	dispatch_async_f(dispatch_get_main_queue(), NULL, doBringOverlayForward);
 }
 
 static void overlayHide(void) {

--
Gitblit v1.9.3