Files
u-desk/app_window_fix.go
T
lxy e28ae653b0 修复: webview尺寸收窄卡住与兜底失效闪退
- 窗口尺寸变化后webview收窄卡住根修
- 尺寸兜底失效致闪退与修复空转加固
- 跨服务器预览残留清理
2026-10-01 01:02:39 +08:00

134 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build windows
package main
import (
"strings"
"sync/atomic"
"syscall"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
// Chrome_WidgetWin 系列类名是 WebView2 的宿主窗口层级
const webviewHostClassFragment = "Chrome_WidgetWin"
// wmSize Win32 窗口尺寸变化消息
const wmSizeMsg uint32 = 0x0005
var (
// user32 对这三个 API 只导出无后缀名(无 A/W 变体,带 W 后缀找不到 proc 会 panic)
procGetClientRectFix = windows.NewLazySystemDLL("user32.dll").NewProc("GetClientRect")
procSetWindowPosFix = windows.NewLazySystemDLL("user32.dll").NewProc("SetWindowPos")
procGetAncestorFix = windows.NewLazySystemDLL("user32.dll").NewProc("GetAncestor")
)
// 修复去重:同一时刻只允许一个校验在跑(回调写共享切片要求互斥)
var webviewFixRunning atomic.Bool
// 当前校验的目标主窗口(枚举回调过滤直属子窗口用)
var webviewTargetHwnd uintptr
// webviewHosts 收集结果容器;enumChildWebviewHosts 回调写入
var webviewHosts []windows.HWND
// 包级变量:syscall.NewCallback 指向的 Go 闭包必须存活于整个进程生命周期
var enumChildWebviewHosts = syscall.NewCallback(func(h uintptr, _ uintptr) uintptr {
// GA_PARENT(1):只收直属子窗口,避免动到更深层的 WebView2 内部窗口
parent, _, _ := procGetAncestorFix.Call(h, 1)
if parent != webviewTargetHwnd {
return 1
}
var buf [256]uint16
if _, err := windows.GetClassName(windows.HWND(h), &buf[0], int32(len(buf))); err != nil {
return 1
}
if strings.Contains(syscall.UTF16ToString(buf[:]), webviewHostClassFragment) {
webviewHosts = append(webviewHosts, windows.HWND(h))
}
return 1
})
// checkAndFixFill 校验 WebView2 宿主是否铺满窗口客户区,未铺满则强制铺满。
// wails alpha 的 WM_SIZE 处理存在竞态:主线程忙时消息被 resizePending 标志丢弃,
// 迟到执行的重铺又使用闭包捕获的过期 bounds,webview 会停在中间尺寸且不再有
// 新消息来纠正,表现为整个应用内容收窄、四周露出窗口底色。
func checkAndFixFill(hwnd uintptr) {
if hwnd == 0 {
return
}
var rc struct{ Left, Top, Right, Bottom int32 }
if r, _, _ := procGetClientRectFix.Call(hwnd, uintptr(unsafe.Pointer(&rc))); r == 0 {
return
}
cw, ch := int(rc.Right-rc.Left), int(rc.Bottom-rc.Top)
if cw <= 0 || ch <= 0 {
return
}
webviewTargetHwnd = hwnd
webviewHosts = webviewHosts[:0]
windows.EnumChildWindows(windows.HWND(hwnd), enumChildWebviewHosts, nil)
for _, h := range webviewHosts {
// SWP_NOZORDER(0x0004):只改尺寸位置,不干预 wails 管理的层级
procSetWindowPosFix.Call(uintptr(h), 0, 0, 0, uintptr(cw), uintptr(ch), 0x0004)
}
}
// runGuarded 统一 panic 兜底:windowsgui 下 goroutine panic 会静默杀进程
func runGuarded(fn func()) {
defer func() { recover() }()
fn()
}
// fixWebviewFill WM_SIZE 路径:等 resize 序列结束、wails 内部去抖落定后校验一次
func fixWebviewFill(hwnd uintptr) {
if !webviewFixRunning.CompareAndSwap(false, true) {
return
}
go func() {
defer webviewFixRunning.Store(false)
runGuarded(func() {
time.Sleep(150 * time.Millisecond)
checkAndFixFill(hwnd)
})
}()
}
// StartWebviewFillWatch 周期兜底:覆盖"无新 WM_SIZE 的迟到重放"场景
// (主线程忙时排队的过期重绘在窗口静止时才执行,此时不会产生任何 WM_SIZE)
func StartWebviewFillWatch() {
go runGuarded(func() {
for {
time.Sleep(3 * time.Second)
if webviewFixRunning.CompareAndSwap(false, true) {
runGuarded(func() {
checkAndFixFill(findMainWindowHwnd())
})
webviewFixRunning.Store(false)
}
}
})
}
// findMainWindowHwnd 找本进程的可见主窗口
func findMainWindowHwnd() uintptr {
var found uintptr
pid := windows.GetCurrentProcessId()
cb := syscall.NewCallback(func(h uintptr, _ uintptr) uintptr {
var winPid uint32
windows.GetWindowThreadProcessId(windows.HWND(h), &winPid)
if winPid == pid && windows.IsWindowVisible(windows.HWND(h)) {
found = h
return 0
}
return 1
})
runGuarded(func() {
windows.EnumWindows(cb, nil)
})
return found
}