feat(v2): T-309 no startup foreground grab, auto-close popup, rename buttons

- Comment out Gio's startup SetForegroundWindow/SetFocus in the vendored
  os_windows.go (marked SILVER-POSE PATCH; reverts on re-vendor) so the
  window no longer steals foreground on launch. Gio has no option for this.
- The fall confirm popup auto-closes 5s after opening (op.InvalidateCmd
  schedules the redraw even with no live frames); '我已知晓' still closes early.
- Rename save buttons to 保存摄像头参数 / 保存常用检测参数. Cross-compiles for Windows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-23 22:00:44 +08:00
co-authored by Claude Opus 4.8
parent d92b53d7a3
commit aced262f6b
3 changed files with 29 additions and 4 deletions
+9
View File
@@ -639,3 +639,12 @@
- 验证:UI 用 `GOOS=windows CGO_ENABLED=0` 交叉编译;store(sqlite,cgo) 本地 `CGO_ENABLED=1` gcc 编译;窗口/弹窗/DB 目视需 Windows。 - 验证:UI 用 `GOOS=windows CGO_ENABLED=0` 交叉编译;store(sqlite,cgo) 本地 `CGO_ENABLED=1` gcc 编译;窗口/弹窗/DB 目视需 Windows。
- 决策:SQLite 用 cgo mattn(用户选,vendor 更小;V2 本就 cgo)。DB 只存内网 host/端口/通道,不存密码;DB 放 artifacts/ 下随 gitignore。先做 ①(取消置顶/抢前台)。 - 决策:SQLite 用 cgo mattn(用户选,vendor 更小;V2 本就 cgo)。DB 只存内网 host/端口/通道,不存密码;DB 放 artifacts/ 下随 gitignore。先做 ①(取消置顶/抢前台)。
- 下一步:定位并取消窗口置顶(Gio ViewEvent 拿 HWND + SetWindowPos NOTOPMOST/NOACTIVATE),交叉编译验证。 - 下一步:定位并取消窗口置顶(Gio ViewEvent 拿 HWND + SetWindowPos NOTOPMOST/NOACTIVATE),交叉编译验证。
## 【2026-07-23】T-309 A:取消抢前台 + 弹窗自动关 + 按钮改名
- 状态:DOING
- 变更:①**vendored patch**——注释掉 `v2/vendor/gioui.org/app/os_windows.go` 启动时的 `SetForegroundWindow`/`SetFocus`(带 `SILVER-POSE PATCH (T-309)` 标记),取消"启动抢前台"。**注意:Gio 无 API 可关此行为;重跑 `go mod vendor` 会还原此补丁,需按标记重打。** 窗口默认非置顶(TopMost=false),故不需另改置顶。②④ 保存按钮改名为"保存摄像头参数"/"保存常用检测参数"。⑤ 摔倒确认弹窗记录打开时间,`layout` 中超 5 秒自动清除,并用 `op.InvalidateCmd{At:}` 调度到期重绘(停流也能自动关);"我已知晓"仍可提前关。
- 验证:`GOOS=windows CGO_ENABLED=0 go build -mod=vendor ./internal/ui/... ./cmd/ui-spike/...` 退出码 0(含 patched os_windows.go);gofmt 干净。窗口不抢前台/弹窗自动关的实际行为需 Windows 目视。
- 阻塞:③测试连接、⑥SQLite 未做。
- 决策:抢前台只能靠 patch vendored Gio(无 API);已加显著标记并在此记录,便于 re-vendor 后重打。
- 下一步:③测试连接摄像头按钮+弹窗;⑥SQLite(mattn) 记录(config 保留 host/端口/通道 + internal/store + 接线)。
+16 -2
View File
@@ -115,6 +115,7 @@ type Window struct {
latestImage *image.RGBA latestImage *image.RGBA
imageGen uint64 imageGen uint64
pending *alert.Record pending *alert.Record
pendingAt time.Time
seenAlerts map[string]struct{} seenAlerts map[string]struct{}
incoming []galleryIncoming incoming []galleryIncoming
seenEvents map[string]struct{} seenEvents map[string]struct{}
@@ -335,6 +336,7 @@ func (window *Window) PresentAlert(record alert.Record) {
window.seenAlerts[record.Event.EventID] = struct{}{} window.seenAlerts[record.Event.EventID] = struct{}{}
queued := record queued := record
window.pending = &queued window.pending = &queued
window.pendingAt = time.Now()
window.mu.Unlock() window.mu.Unlock()
playAlertSound() playAlertSound()
window.appWindow.Invalidate() window.appWindow.Invalidate()
@@ -360,6 +362,7 @@ func (window *Window) layout(gtx layout.Context) layout.Dimensions {
frame := window.latestImage frame := window.latestImage
gen := window.imageGen gen := window.imageGen
pending := window.pending pending := window.pending
pendingAt := window.pendingAt
incoming := window.incoming incoming := window.incoming
running := window.running running := window.running
window.incoming = nil window.incoming = nil
@@ -410,6 +413,17 @@ func (window *Window) layout(gtx layout.Context) layout.Dimensions {
window.mu.Unlock() window.mu.Unlock()
pending = nil pending = nil
} }
// The confirm popup auto-closes 5 seconds after it opens.
if pending != nil {
if time.Since(pendingAt) >= 5*time.Second {
window.mu.Lock()
window.pending = nil
window.mu.Unlock()
pending = nil
} else {
gtx.Execute(op.InvalidateCmd{At: pendingAt.Add(5 * time.Second)})
}
}
content := func(gtx layout.Context) layout.Dimensions { content := func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx, return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
@@ -701,7 +715,7 @@ func (window *Window) layoutSettings(gtx layout.Context, model ViewModel) layout
window.editorRow("传输 (tcp/udp)", &window.editTransport), window.editorRow("传输 (tcp/udp)", &window.editTransport),
window.editorRow("连接超时 (秒)", &window.editTimeout), window.editorRow("连接超时 (秒)", &window.editTimeout),
window.checkRow("低延迟", &window.lowLatency), window.checkRow("低延迟", &window.lowLatency),
window.buttonRow(&window.saveCamera, "保存摄像头"), window.buttonRow(&window.saveCamera, "保存摄像头参数"),
) )
paramsCard := verticalRows( paramsCard := verticalRows(
window.heading("常用检测参数(下次启动生效)"), window.heading("常用检测参数(下次启动生效)"),
@@ -711,7 +725,7 @@ func (window *Window) layoutSettings(gtx layout.Context, model ViewModel) layout
window.editorRow("模型置信度", &window.editModelConf), window.editorRow("模型置信度", &window.editModelConf),
window.checkRow("要求先快速下移", &window.requireRapid), window.checkRow("要求先快速下移", &window.requireRapid),
window.checkRow("要求膝踝清晰", &window.requireLower), window.checkRow("要求膝踝清晰", &window.requireLower),
window.buttonRow(&window.saveParams, "保存参数"), window.buttonRow(&window.saveParams, "保存常用检测参数"),
window.labelRow(window.settingsStatus), window.labelRow(window.settingsStatus),
) )
depsCard := verticalRows( depsCard := verticalRows(
+4 -2
View File
@@ -112,8 +112,10 @@ func newWindow(win *callbacks, options []Option) {
defer winMap.Delete(w.hwnd) defer winMap.Delete(w.hwnd)
w.Configure(options) w.Configure(options)
w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)}) w.ProcessEvent(Win32ViewEvent{HWND: uintptr(w.hwnd)})
windows.SetForegroundWindow(w.hwnd) // SILVER-POSE PATCH (T-309): do not steal foreground/focus on startup.
windows.SetFocus(w.hwnd) // Re-applying `go mod vendor` reverts this; see progress.md.
// windows.SetForegroundWindow(w.hwnd)
// windows.SetFocus(w.hwnd)
// Since the window class for the cursor is null, // Since the window class for the cursor is null,
// set it here to show the cursor. // set it here to show the cursor.
w.SetCursor(pointer.CursorDefault) w.SetCursor(pointer.CursorDefault)