a02eed539a
leo 實測:右鍵仍然沒有 quit 選單。
## 真兇:我把函式庫本來就會做對的事,換成一個更脆弱的版本
讀 systray_darwin.go 的 systray_on_rclick():
func systray_on_rclick() {
if onRClick != nil { onRClick(st) } else { C.show_menu() }
}
⇒ **沒註冊 SetOnRClick 時,函式庫自己就會把選單叫出來**;
而且 C 層的 show_menu 內部已經處理好整套:
create_menu() → [statusItem.button performClick:nil] → set_menu_nil()
我卻註冊了自己的 handler、還加 `if menu != nil` 防呆
⇒ 等於用一個判斷把「本來會動的預設行為」擋掉。
修:**刪掉 SetOnRClick 註冊**。左鍵仍自訂(要開視窗),右鍵交還函式庫。
## 判準(已寫進 mistakes)
用第三方函式庫時,**先讀它的預設行為再決定要不要覆寫**。
「多註冊一個 handler」看起來是加功能,實際上常是**關掉**原本正確的行為。
## 閘也跟著改
check-tray.sh 原本驗「SetOnRClick 有註冊且順序對」——**那是在守一個錯的寫法**。
改成反向:**出現 systray.SetOnRClick 就擋下**,並說明原因。
實跑 7 項全過;App 活著 pid 14081、crash 0。
79 lines
3.7 KiB
Bash
Executable File
79 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# check-tray.sh — 托盤行為的機械閘(t194,leo 真機測出四個 bug 後補)
|
||
#
|
||
# 🔴 為什麼要有:這四個 bug **編得過、也跑得起來**,只有真的去點才會發現。
|
||
# 我改完只驗「build ok」就想交,被交付警察攔下。
|
||
# ⇒ 把「點不下去的部分」用可機械檢查的**前置條件**守住。
|
||
set -uo pipefail
|
||
cd "$(dirname "$0")"
|
||
FAIL=0
|
||
ok(){ printf " ✅ %s\n" "$1"; }
|
||
ng(){ printf " ❌ %s\n" "$1"; FAIL=1; }
|
||
|
||
echo "━━━ 托盤行為前置條件 ━━━"
|
||
|
||
# ① template icon:純黑+大量透明、尺寸 22/44
|
||
python3 - <<'PY' || exit 1
|
||
import zlib,struct,sys
|
||
d=open('build/trayicon.png','rb').read()
|
||
pos=8; idat=b''; w=h=ct=0
|
||
while pos<len(d):
|
||
ln=struct.unpack('>I',d[pos:pos+4])[0]; t=d[pos+4:pos+8]
|
||
if t==b'IHDR': w,h,_,ct=struct.unpack('>IIBB',d[pos+8:pos+18])
|
||
if t==b'IDAT': idat+=d[pos+8:pos+8+ln]
|
||
pos+=12+ln
|
||
if w not in (22,44) or ct!=6:
|
||
print(f" ❌ tray icon 應為 22/44 的 RGBA,got {w}x{h} ct={ct}"); sys.exit(1)
|
||
raw=zlib.decompress(idat); ch=4; stride=w*ch+1
|
||
rows=[];prev=bytearray(w*ch)
|
||
for y in range(h):
|
||
f=raw[y*stride];line=bytearray(raw[y*stride+1:(y+1)*stride])
|
||
for i in range(len(line)):
|
||
a=line[i-ch] if i>=ch else 0;b=prev[i];c=prev[i-ch] if i>=ch else 0
|
||
if f==1: line[i]=(line[i]+a)&255
|
||
elif f==2: line[i]=(line[i]+b)&255
|
||
elif f==3: line[i]=(line[i]+(a+b)//2)&255
|
||
elif f==4:
|
||
pp=a+b-c;pa,pb,pc=abs(pp-a),abs(pp-b),abs(pp-c)
|
||
pr=a if(pa<=pb and pa<=pc) else (b if pb<=pc else c)
|
||
line[i]=(line[i]+pr)&255
|
||
rows.append(bytes(line));prev=line
|
||
al=[rows[y][x*4+3] for y in range(h) for x in range(w)]
|
||
opq=sum(1 for a in al if a>200)
|
||
if opq > len(al)*0.5:
|
||
print(f" ❌ tray icon 幾乎全不透明({100*opq//len(al)}%)⇒ 會顯示成方塊"); sys.exit(1)
|
||
cols={tuple(rows[y][x*4:x*4+3]) for y in range(h) for x in range(w) if rows[y][x*4+3]>200}
|
||
print(f" ✅ template icon:{w}x{h}、不透明 {100*opq//len(al)}%、顏色 {len(cols)} 種 {list(cols)[:1]}")
|
||
PY
|
||
|
||
# ② 呼叫順序:AddMenuItem → SetMenuNil → SetOnClick/SetOnRClick
|
||
python3 - <<'PY' || exit 1
|
||
import sys
|
||
s=open('main.go',encoding='utf-8').read()
|
||
try:
|
||
a=s.index('systray.AddMenuItem'); n=s.index('systray.SetMenuNil')
|
||
c=s.index('systray.SetOnClick')
|
||
except ValueError as e:
|
||
print(f" ❌ 找不到必要呼叫:{e}"); sys.exit(1)
|
||
if not (a < n < c):
|
||
print(" ❌ 順序錯:必須 AddMenuItem → SetMenuNil → SetOnClick"); sys.exit(1)
|
||
print(" ✅ 順序正確(建選單→清掉→註冊左鍵)")
|
||
# 🔴 右鍵**不可以**註冊 handler:systray_on_rclick() 在沒註冊時會自己 show_menu(),
|
||
# 註冊了反而要自己重做一遍(leo 實測「右鍵沒跳出 quit」就是這樣來的)。
|
||
if 'systray.SetOnRClick' in s:
|
||
print(" ❌ 註冊了 SetOnRClick ⇒ 蓋掉函式庫預設的『右鍵開選單』,右鍵會沒反應"); sys.exit(1)
|
||
print(" ✅ 右鍵交給函式庫預設行為(不自己註冊)")
|
||
PY
|
||
|
||
# ③ ShowWindow 要能叫回「已開但被蓋住」的視窗
|
||
for f in WindowUnminimise WindowShow WindowSetAlwaysOnTop; do
|
||
grep -q "runtime.$f" app.go && ok "ShowWindow 有 $f" || ng "ShowWindow 缺 $f(叫不回被蓋住的視窗)"
|
||
done
|
||
|
||
# ④ 結束路徑:收訊號 → 停子行程 → 等它真的不見
|
||
grep -q "signal.Notify" supervise.go && ok "有處理 SIGTERM/SIGINT" || ng "沒有訊號處理(只能強制結束)"
|
||
grep -q "waitCollectorGone" supervise.go && ok "結束前會等 collector 真的不見" || ng "沒等子行程 ⇒ 會留孤兒"
|
||
|
||
echo
|
||
[ $FAIL -eq 0 ] && echo "✅ 托盤前置條件全過" || { echo "❌ 未過——不准交貨"; exit 1; }
|