Mac 四修:Dock 不再重複/結束真的會結束/全螢幕可用/視窗 70%
leo 08-06 Mac 實測回報。四個都是**只在 Mac 現形**的病。
## ① Dock 與選單列兩邊都顯示(連帶:只有 Arcrun 列在強制結束清單)
真兇:Wails 在 AppDelegate.m:44 applicationWillFinishLaunching 硬設
`setActivationPolicy:Regular`,**runtime 蓋掉 Info.plist 的 LSUIElement**
(後者只決定啟動當下的初始 policy)。而 v2.13 的 mac.ActivationPolicy 是註解掉的。
🔴 **這件事 fyne 版 604704a(07-24)已經解過且真機驗證通過**,換 Wails 時整段掉了;
wiki mistakes.md:124 也早就記著同一條。**本次是取回原版,不是重新發明**——
dock_darwin.go/dock_other.go 逐字沿用,只更新註解說明 Wails 死法相同。
## ② 托盤右鍵「結束 Arcrun」點了不會結束
leo:「唯一結束法是強制結束…因為舊版沒結束導致無法覆蓋,
**如果不知道怎麼強制結束的人就放棄了**」
真兇:Wails 的 runtime.Quit() 與「按 ×」**共用同一個 OnBeforeClose**
(frontend.go:364 `if !OnBeforeClose(ctx) { mainWindow.Quit() }`),
我們無條件回 true ⇒ mainWindow.Quit() 永遠不執行,還順手 WindowHide
⇒ 看起來像關了、其實行程還活著。
(AppDelegate.m:41 的 applicationShouldTerminate 也一律回 NSTerminateCancel,
決定權整個在這個回呼 ⇒ 回 true 就沒有任何出口。)
解:quitting 旗標區分兩者,並抽出 closeMeansHide()/beginQuit() 讓它**可機械驗證**。
## ③ Mac 全螢幕(綠色)按鈕被停用
真兇同樣在 Wails:window.go:92 `zoomable` 只在 `frontendOptions.Mac != nil` 時賦值,
我們沒設 Mac ⇒ zoomable 維持零值 false ⇒ WailsContext.m:208
`if (!zoomable && resizable) { [zoomButton setEnabled:NO]; }`。Windows 無此段。
解:`Mac: &mac.Options{}`(空的即可,DisableZoom 預設 false)。
## ④ 視窗 50% 太小
leo:「Google Drive 大約螢幕長寬的 70%,改成 50% 後顯得太小」⇒ 常數 screenFraction=0.70。
並加 appLog 留痕(螢幕多大、算出多大),以後不必請人量像素。
## 驗(本機真機實跑,非推測)
· `lsappinfo` → **type="UIElement"**、Version="0.18.8"
= 604704a 當年用的同一個驗法,通過 ⇒ Dock 無 icon、不列強制結束
· 截圖放大號誌燈 → **綠燈是彩色的(enabled)**,停用時會是灰色
· app.log 自報 → **視窗依螢幕 1470x956 設為 1029x669(70%)**
· 首頁顯示「看守中·資料夾有變動就會自動整理/已整理 1 份」=判活正常
· go test TestCloseMeansHide 通過(釘住「按×要隱藏、按結束要放行」)
· go vet 全綠;mac build + windows 交叉編譯皆 OK
· **未驗**:托盤右鍵實際點擊(osascript 缺輔助取用權限,點不到選單列)
⇒ 邏輯已單元測試+原始碼追到底,但**實際點擊歸 leo 真機**
This commit is contained in:
@@ -521,6 +521,11 @@ func (a *App) ShowWindow() {
|
||||
//
|
||||
// 這裡先停掉 collector 子行程再關 App,否則子行程會變孤兒繼續跑。
|
||||
func (a *App) Quit() {
|
||||
// 🔴 2026-08-06:先舉旗再喊退。沒有這一步,OnBeforeClose 會把 runtime.Quit()
|
||||
// 當成「按 ×」攔下來(Wails frontend.go:364 兩者共用同一個回呼)
|
||||
// ⇒ leo 實撞:托盤右鍵「結束 Arcrun」點了沒反應,只能強制結束,
|
||||
// 導致下載的新版覆蓋不掉還活著的舊版。
|
||||
beginQuit()
|
||||
stopSupervisor()
|
||||
runtime.Quit(a.ctx)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
//go:build darwin
|
||||
|
||||
// dock_darwin.go — 托盤唯一指示器(leo 07-24 裁決)的 macOS 實作。
|
||||
//
|
||||
// 🔴 **這段是取回 `604704a`(07-24,fyne 版)的原版,不是重新發明。**
|
||||
// 當時已真機驗證通過(`lsappinfo` 實測 `type=UIElement`),換 Wails 時整段掉了
|
||||
// ⇒ leo 08-06 又撞同一個病:「托盤顯示 icon 時通常 Dock 就不顯示了,但現在是兩邊都顯示」。
|
||||
//
|
||||
// 原版註解說的是 fyne/glfw,**Wails 的死法一模一樣**(已查原始碼確認):
|
||||
//
|
||||
// wails/v2@v2.13.0 internal/frontend/desktop/darwin/AppDelegate.m:44
|
||||
// - (void)applicationWillFinishLaunching:… {
|
||||
// [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
//
|
||||
// ⇒ `Info.plist` 的 `LSUIElement=true` **只決定啟動當下的初始 policy**,
|
||||
// app 起來後自己呼叫 setActivationPolicy 就把它蓋掉了。
|
||||
// (我們的 plist 實測確實有 `LSUIElement => true`,但 Dock 照樣出 icon——就是這個原因。)
|
||||
// ⇒ 而 Wails v2.13 的 `mac.ActivationPolicy` 選項在原始碼裡是**註解掉的**,設不了。
|
||||
// ⇒ 唯一可靠解=app 起來後 runtime 直接把 policy 切成 Accessory(就是本檔)。
|
||||
//
|
||||
// 附帶解掉的第三個症狀:leo「別的托盤 daemon 都不列在強制結束列表裡,只有 Arcrun 有」——
|
||||
// 因為 Regular policy 的 app 才會被列進「強制結束」;切成 Accessory 後就不列了。
|
||||
package main
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework AppKit
|
||||
#import <AppKit/AppKit.h>
|
||||
static void arcrunHideDock(void) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
});
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// hideDockIcon 把 app 切成 Accessory(選單列有、Dock 無)。
|
||||
//
|
||||
// 須在 NSApp 起來**之後**呼叫——fyne 版掛在 `Lifecycle().SetOnStarted`,
|
||||
// Wails 版掛在 `OnStartup`(見 main.go)。裡面已經 dispatch 回主佇列,
|
||||
// 呼叫端不必自己處理執行緒。
|
||||
func hideDockIcon() { C.arcrunHideDock() }
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build !darwin
|
||||
|
||||
package main
|
||||
|
||||
// hideDockIcon 在非 macOS 平台是 no-op(Windows 系統匣本來就不佔工作列)。
|
||||
// 取自 604704a(fyne 版)原版,語意不變。
|
||||
func hideDockIcon() {}
|
||||
+66
-12
@@ -3,10 +3,12 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"embed"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/wailsapp/wails/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/options"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/assetserver"
|
||||
"github.com/wailsapp/wails/v2/pkg/options/mac"
|
||||
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
@@ -21,6 +23,19 @@ type wailsCtx = context.Context
|
||||
// 版本號由 build 時 -ldflags 注入(同 arcrun-tray 的規矩)。
|
||||
var version = "dev"
|
||||
|
||||
// quitting 區分「按 ×(收起來)」與「按托盤的結束(真的退)」。
|
||||
// 兩者都會走 OnBeforeClose,沒有這個旗標就無法分辨——leo 08-06 實撞:
|
||||
// 「結束 Arcrun」點了沒反應,只能強制結束,導致新版覆蓋不了舊版。
|
||||
var quitting atomic.Bool
|
||||
|
||||
// beginQuit 舉旗表示「這次是真的要退出」,由 App.Quit()(托盤的結束)呼叫。
|
||||
func beginQuit() { quitting.Store(true) }
|
||||
|
||||
// closeMeansHide 回答「這次的關閉要不要攔下來改成隱藏」。
|
||||
// 抽成獨立函式是為了**可機械驗證**——OnBeforeClose 需要 Wails 的 ctx,測不到;
|
||||
// 這個判斷才是 leo 08-06 那個 bug 的核心(結束與按 × 共用同一個回呼)。
|
||||
func closeMeansHide() bool { return !quitting.Load() }
|
||||
|
||||
func main() {
|
||||
app := NewApp()
|
||||
|
||||
@@ -41,7 +56,7 @@ func main() {
|
||||
err := wails.Run(&options.App{
|
||||
Title: "Arcrun",
|
||||
// 這裡的 Width/Height 只是**開得起來的保底值**;真正的尺寸在 OnStartup
|
||||
// 依螢幕算成 50%(見下方 resizeToHalfScreen)。保底值不能大於常見筆電
|
||||
// 依螢幕算(見下方 resizeToScreenFraction)。保底值不能大於常見筆電
|
||||
// 的可視高度,否則 ScreenGetAll 失敗時會沿用它而擠出畫面外
|
||||
// (leo 08-06 實撞:1000x700 在他的 Windows 上「高度太高快擠到外面了」)。
|
||||
Width: 960, Height: 600,
|
||||
@@ -58,10 +73,23 @@ func main() {
|
||||
app.ShowWindow()
|
||||
},
|
||||
},
|
||||
// 🔴 2026-08-06(leo 實測):「在 Mac 似乎 disable 了全螢幕的按鈕,
|
||||
// 應該要讓使用者需要時可以全螢幕。查看 Windows 時全螢幕是可以用的」。
|
||||
// 真兇在 Wails 原始碼,不在我們的視窗設定:
|
||||
// window.go:92 `if frontendOptions.Mac != nil { … zoomable = !mac.DisableZoom … }`
|
||||
// ⇒ **沒設 Mac 時 zoomable 維持零值 false**
|
||||
// WailsContext.m:208 `if (!zoomable && resizable) { [zoomButton setEnabled:NO]; }`
|
||||
// ⇒ 綠色按鈕被停用。Windows 沒有這段,所以只有 Mac 壞。
|
||||
// 解=給一個**空的** Mac options(DisableZoom 預設 false ⇒ zoomable=true)。
|
||||
Mac: &mac.Options{},
|
||||
OnStartup: func(ctx wailsCtx) {
|
||||
app.startup(ctx)
|
||||
resizeToHalfScreen(ctx) // 視窗=螢幕的一半,置中(leo 08-06)
|
||||
startSupervisor() // 🔴 daemon 本體:不啟動就不會同步
|
||||
// 🔴 必須在 NSApp 起來之後才切 Accessory——Wails 在
|
||||
// applicationWillFinishLaunching 硬設 Regular,會蓋掉 Info.plist 的
|
||||
// LSUIElement。詳見 dock_darwin.go(取回 604704a 的原版)。
|
||||
hideDockIcon()
|
||||
resizeToScreenFraction(ctx) // 視窗=螢幕 70%,置中(leo 08-06 照 Google Drive)
|
||||
startSupervisor() // 🔴 daemon 本體:不啟動就不會同步
|
||||
// 🔴 托盤必須在**主執行緒**建立(NSStatusItem 會 new NSWindow)。
|
||||
// OnStartup 不是主執行緒 ⇒ 直接呼叫會炸
|
||||
// 「NSWindow should only be instantiated on the main thread!」(實測)。
|
||||
@@ -70,13 +98,29 @@ func main() {
|
||||
},
|
||||
// 🔴 daemon 是常駐程式:關窗只是收起來,**不能真的結束**
|
||||
// (結束=停止看守資料夾,使用者不會預期按 × 就不再同步)。
|
||||
//
|
||||
// 🔴 2026-08-06(leo 實測):「在托盤按右鍵結束 Arcrun 但實際上 Dock 上的沒結束…
|
||||
// 唯一結束法是強制結束…因為舊版沒結束導致無法覆蓋,**如果不知道怎麼強制結束的人
|
||||
// 就放棄了**」。
|
||||
// 真兇:Wails 的 `runtime.Quit()` **也走這個 OnBeforeClose**——
|
||||
// frontend.go:364 `func (f *Frontend) Quit() { … if !OnBeforeClose(ctx) { mainWindow.Quit() } }`
|
||||
// 我們無條件回 true ⇒ `mainWindow.Quit()` 永遠不執行 ⇒ 結束選單點了沒用,
|
||||
// 而且還順手 WindowHide ⇒ 看起來像關掉了、其實行程還活著。
|
||||
// (AppDelegate.m:41 的 applicationShouldTerminate 也一律回 NSTerminateCancel,
|
||||
// 把決定權整個交給這個回呼——所以這裡回 true 就真的沒有任何出口。)
|
||||
// 解=用 quitting 旗標區分「按 ×(收起來)」與「按結束(真的退)」。
|
||||
OnBeforeClose: func(ctx wailsCtx) bool {
|
||||
if !closeMeansHide() {
|
||||
return false // false=放行,Wails 才會真的呼叫 mainWindow.Quit()
|
||||
}
|
||||
wruntime.WindowHide(ctx)
|
||||
return true // true=攔下關閉
|
||||
return true // true=攔下關閉(按 × 只是收起來)
|
||||
},
|
||||
HideWindowOnClose: true,
|
||||
// Dock 不出現 icon:Wails v2.13 的 mac.ActivationPolicy 還沒開放(原始碼裡是註解掉的),
|
||||
// 改走 Info.plist 的 LSUIElement(與 fyne 版同一個做法,見 build/darwin/Info.plist)。
|
||||
// 而 Info.plist 的 LSUIElement **會被 Wails 啟動時的 Regular policy 蓋掉**
|
||||
// (leo 08-06 實撞:Dock 與托盤兩邊都顯示)⇒ 改在 OnStartup 呼叫 hideDockIcon()
|
||||
// runtime 切 Accessory。plist 的 LSUIElement 保留(決定啟動當下的初始 policy)。
|
||||
Bind: []interface{}{app},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -84,18 +128,24 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// resizeToHalfScreen 把視窗調成螢幕長寬的一半並置中。
|
||||
// screenFraction 是預設視窗佔螢幕長寬的比例。
|
||||
//
|
||||
// 🔴 leo 2026-08-06(Windows 封測,圖三標註):
|
||||
// 🔴 leo 2026-08-06 兩次調整,**以第二次為準**:
|
||||
// - 先說「default 50% 螢幕長寬」(因為寫死的 1000x700「高度太高快擠到外面了」)
|
||||
// - 實際看到 50% 後改口:「**我查看 Google Drive 的視窗,它應該是大約螢幕長寬的 70%,
|
||||
// 因為你改成 50% 後顯得太小**」
|
||||
//
|
||||
// 「視窗尺寸應該是 default 50% 螢幕長寬,**高度太高快擠到外面了**」
|
||||
// ⇒ 70%。要再調就改這一個常數,不要散在各處。
|
||||
const screenFraction = 0.70
|
||||
|
||||
// resizeToScreenFraction 把視窗調成螢幕長寬的 screenFraction 並置中。
|
||||
//
|
||||
// 寫死 1000x700 在 1366x768 這種常見筆電上,扣掉工作列就幾乎頂到底。
|
||||
// 寫死尺寸在 1366x768 這種常見筆電上,扣掉工作列就幾乎頂到底。
|
||||
// 依螢幕算才不會在別人機器上爆版——我們沒有全世界的螢幕可以試。
|
||||
//
|
||||
// 拿不到螢幕資訊時**什麼都不做**(沿用 options 的保底值),不要瞎猜一個數字:
|
||||
// 猜錯的後果就是現在這個病。
|
||||
func resizeToHalfScreen(ctx wailsCtx) {
|
||||
// 猜錯的後果就是原本那個病。
|
||||
func resizeToScreenFraction(ctx wailsCtx) {
|
||||
screens, err := wruntime.ScreenGetAll(ctx)
|
||||
if err != nil || len(screens) == 0 {
|
||||
return
|
||||
@@ -120,8 +170,12 @@ func resizeToHalfScreen(ctx wailsCtx) {
|
||||
if w == 0 || h == 0 {
|
||||
return
|
||||
}
|
||||
wruntime.WindowSetSize(ctx, w/2, h/2)
|
||||
nw, nh := int(float64(w)*screenFraction), int(float64(h)*screenFraction)
|
||||
wruntime.WindowSetSize(ctx, nw, nh)
|
||||
wruntime.WindowCenter(ctx)
|
||||
// 留痕:使用者回報「視窗太大/太小」時,這一行直接說出當時螢幕多大、算出多大。
|
||||
// 沒有它就只能請人量像素(08-06 我自己就卡在這)。
|
||||
appLog("視窗依螢幕 %dx%d 設為 %dx%d(%.0f%%)", w, h, nw, nh, screenFraction*100)
|
||||
}
|
||||
|
||||
// setupTray 建立選單列 icon。
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestCloseMeansHide 守住 leo 2026-08-06 撞到的 bug:
|
||||
//
|
||||
// 「在托盤按右鍵結束 Arcrun 但實際上 Dock 上的沒結束…唯一結束法是強制結束…
|
||||
// 因為舊版沒結束導致無法覆蓋,如果不知道怎麼強制結束的人就放棄了」
|
||||
//
|
||||
// 真兇:Wails 的 runtime.Quit() 與「按 ×」**共用同一個 OnBeforeClose**
|
||||
// (frontend.go:364 `if !OnBeforeClose(ctx) { mainWindow.Quit() }`)。
|
||||
// 我們原本無條件回 true ⇒ mainWindow.Quit() 永遠不執行 ⇒ 結束選單失效。
|
||||
//
|
||||
// 這支測試釘住兩件事:按 × 要隱藏、按結束要放行。
|
||||
func TestCloseMeansHide(t *testing.T) {
|
||||
quitting.Store(false)
|
||||
if !closeMeansHide() {
|
||||
t.Fatal("預設(按 ×)應該攔下來改成隱藏——常駐程式不該被關窗結束")
|
||||
}
|
||||
|
||||
beginQuit() // =托盤右鍵「結束 Arcrun」會做的第一件事
|
||||
if closeMeansHide() {
|
||||
t.Fatal("按了結束卻仍要隱藏=程式永遠退不掉(就是 leo 撞到的那個 bug)")
|
||||
}
|
||||
|
||||
quitting.Store(false) // 還原,避免影響其他測試
|
||||
}
|
||||
Reference in New Issue
Block a user