Skip to content

Commit ee2173f

Browse files
leaanthonyWails Documentation Agenttaliesin-ai
authored
fix(windows): prevent Center() from stealing focus when StartHidden:true (#5249)
* fix(windows): prevent Center() from stealing focus when StartHidden is true When StartHidden:true is set, the Center() method's SetWindowPos call would activate the window and steal focus from the foreground application. Adding SWP_NOACTIVATE to the flags prevents this behavior. Fixes #4882 * test(4882): make Center() focus test a real regression guard The previous test only OR'd together locally-redefined SWP_* constants and never referenced Center(), so it passed even if form.go reverted to SWP_NOSIZE only (per PR review). Replace it with an AST-based assertion that parses form.go and verifies Center()'s SetWindowPos call still includes SWP_NOACTIVATE. Runs on any OS (winc is Windows-only), and fails if the flag is dropped. --------- Co-authored-by: Wails Documentation Agent <agent@wails.local> Co-authored-by: taliesin-ai <bot@taliesin.ai>
1 parent aad04de commit ee2173f

2 files changed

Lines changed: 88 additions & 1 deletion

File tree

v2/internal/frontend/desktop/windows/winc/form.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func (fm *Form) Center() {
168168
winHeight := winRect.Bottom - winRect.Top
169169
windowX := screenMiddleW - (winWidth / 2)
170170
windowY := screenMiddleH - (winHeight / 2)
171-
w32.SetWindowPos(fm.hwnd, w32.HWND_TOP, int(windowX), int(windowY), int(winWidth), int(winHeight), w32.SWP_NOSIZE)
171+
w32.SetWindowPos(fm.hwnd, w32.HWND_TOP, int(windowX), int(windowY), int(winWidth), int(winHeight), w32.SWP_NOSIZE|w32.SWP_NOACTIVATE)
172172
}
173173

174174
func (fm *Form) Fullscreen() {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package test
2+
3+
import (
4+
"go/ast"
5+
"go/parser"
6+
"go/token"
7+
"path/filepath"
8+
"testing"
9+
)
10+
11+
// formSource is the winc Form implementation that defines Center().
12+
const formSource = "../../internal/frontend/desktop/windows/winc/form.go"
13+
14+
// TestCenterSetsNoActivate is the regression guard for #4882: launching an app
15+
// with StartHidden:true must not steal focus from the foreground window.
16+
// Form.Center() prevents that by passing SWP_NOACTIVATE to SetWindowPos.
17+
//
18+
// winc is Windows-only (build-tagged), so instead of linking it we assert
19+
// against the source of Center() directly: this fails if the flag is ever
20+
// dropped from that call, which is the exact regression that reintroduces the
21+
// bug. (The previous version of this test only OR'd together local copies of
22+
// the constants and would pass even if form.go reverted — see PR review.)
23+
func TestCenterSetsNoActivate(t *testing.T) {
24+
fset := token.NewFileSet()
25+
file, err := parser.ParseFile(fset, filepath.FromSlash(formSource), nil, 0)
26+
if err != nil {
27+
t.Fatalf("parsing %s: %v", formSource, err)
28+
}
29+
30+
var center *ast.FuncDecl
31+
for _, decl := range file.Decls {
32+
fn, ok := decl.(*ast.FuncDecl)
33+
if ok && fn.Recv != nil && fn.Name.Name == "Center" {
34+
center = fn
35+
break
36+
}
37+
}
38+
if center == nil {
39+
t.Fatalf("could not find Center() in %s", formSource)
40+
}
41+
42+
var foundSetWindowPos, foundNoActivate bool
43+
ast.Inspect(center, func(n ast.Node) bool {
44+
call, ok := n.(*ast.CallExpr)
45+
if !ok {
46+
return true
47+
}
48+
sel, ok := call.Fun.(*ast.SelectorExpr)
49+
if !ok || sel.Sel.Name != "SetWindowPos" {
50+
return true
51+
}
52+
foundSetWindowPos = true
53+
for _, arg := range call.Args {
54+
if exprReferences(arg, "SWP_NOACTIVATE") {
55+
foundNoActivate = true
56+
}
57+
}
58+
return true
59+
})
60+
61+
if !foundSetWindowPos {
62+
t.Fatal("Center() no longer calls SetWindowPos; update this regression guard")
63+
}
64+
if !foundNoActivate {
65+
t.Error("Center()'s SetWindowPos call is missing SWP_NOACTIVATE — this reintroduces the StartHidden focus-steal bug (#4882)")
66+
}
67+
}
68+
69+
// exprReferences reports whether expr references an identifier with the given
70+
// name, e.g. the w32.SWP_NOACTIVATE term inside a bitwise-or of flags.
71+
func exprReferences(expr ast.Expr, name string) bool {
72+
found := false
73+
ast.Inspect(expr, func(n ast.Node) bool {
74+
switch v := n.(type) {
75+
case *ast.SelectorExpr:
76+
if v.Sel.Name == name {
77+
found = true
78+
}
79+
case *ast.Ident:
80+
if v.Name == name {
81+
found = true
82+
}
83+
}
84+
return !found
85+
})
86+
return found
87+
}

0 commit comments

Comments
 (0)