This is an automated email from the ASF dual-hosted git repository. hsluoyz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
commit 5378983355409066acf2c9f0b328008f8008e146 Author: Yang Luo <[email protected]> AuthorDate: Mon Aug 24 22:42:21 2026 +0800 fix(desktop): hide the window on close so reopening is instant --- desktop/focus_windows.go | 83 +++++++++++++++++++++++++++++++++-------------- desktop/hide_windows.go | 43 +++++++++++------------- desktop/window_windows.go | 5 ++- 3 files changed, 82 insertions(+), 49 deletions(-) diff --git a/desktop/focus_windows.go b/desktop/focus_windows.go index 1cf4aaf..a249f9a 100644 --- a/desktop/focus_windows.go +++ b/desktop/focus_windows.go @@ -12,13 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. + package main import ( + "sync" "syscall" "unsafe" ) +// webviewClass is the window class go-webview2 registers. The window is looked +// up by class rather than by "is it visible", because the whole point is to +// find it again once closing has hidden it. +const webviewClass = "webview" + +const ( + swHide = 0 + swShow = 5 + swRestore = 9 +) + var ( shell32 = syscall.NewLazyDLL("shell32.dll") user32 = syscall.NewLazyDLL("user32.dll") @@ -26,13 +39,12 @@ var ( procSetAppId = shell32.NewProc("SetCurrentProcessExplicitAppUserModelID") procEnumWindows = user32.NewProc("EnumWindows") procGetWindowThreadPid = user32.NewProc("GetWindowThreadProcessId") - procIsWindowVisible = user32.NewProc("IsWindowVisible") + procGetClassNameW = user32.NewProc("GetClassNameW") + procIsIconic = user32.NewProc("IsIconic") procShowWindow = user32.NewProc("ShowWindow") procSetForegroundWindow = user32.NewProc("SetForegroundWindow") ) -const swRestore = 9 - func setAppUserModelId() { id, err := syscall.UTF16PtrFromString(appId) if err != nil { @@ -41,30 +53,53 @@ func setAppUserModelId() { _, _, _ = procSetAppId.Call(uintptr(unsafe.Pointer(id))) } -// focusWindow raises the window process's own window, so that clicking the tray -// while it is already open brings it to the front instead of doing nothing. +// focusWindow shows the window process's own window and raises it, so that +// clicking the tray brings back the window the user closed — the same one, with +// the page still loaded — rather than starting over. func focusWindow(pid int) { - var found uintptr + hwnd := findWindow(pid) + if hwnd == 0 { + return + } - callback := syscall.NewCallback(func(hwnd uintptr, _ uintptr) uintptr { - visible, _, _ := procIsWindowVisible.Call(hwnd) - if visible == 0 { - return 1 - } - var owner uint32 - _, _, _ = procGetWindowThreadPid.Call(hwnd, uintptr(unsafe.Pointer(&owner))) - if owner != uint32(pid) { - return 1 - } - found = hwnd - return 0 - }) + show := uintptr(swShow) + if iconic, _, _ := procIsIconic.Call(hwnd); iconic != 0 { + show = swRestore + } + _, _, _ = procShowWindow.Call(hwnd, show) + _, _, _ = procSetForegroundWindow.Call(hwnd) +} - _, _, _ = procEnumWindows.Call(callback, 0) - if found == 0 { - return +// search is package level because syscall.NewCallback allocates a callback that +// is never freed, so the enumeration cannot make one per click. +var search struct { + sync.Mutex + pid uint32 + found uintptr +} + +var enumWindowsCallback = syscall.NewCallback(func(hwnd, _ uintptr) uintptr { + var owner uint32 + _, _, _ = procGetWindowThreadPid.Call(hwnd, uintptr(unsafe.Pointer(&owner))) + if owner != search.pid || windowClass(hwnd) != webviewClass { + return 1 } + search.found = hwnd + return 0 +}) + +func findWindow(pid int) uintptr { + search.Lock() + defer search.Unlock() + + search.pid = uint32(pid) + search.found = 0 + _, _, _ = procEnumWindows.Call(enumWindowsCallback, 0) + return search.found +} - _, _, _ = procShowWindow.Call(found, swRestore) - _, _, _ = procSetForegroundWindow.Call(found) +func windowClass(hwnd uintptr) string { + var name [64]uint16 + n, _, _ := procGetClassNameW.Call(hwnd, uintptr(unsafe.Pointer(&name[0])), uintptr(len(name))) + return syscall.UTF16ToString(name[:n]) } diff --git a/desktop/hide_windows.go b/desktop/hide_windows.go index b42d365..cb20788 100644 --- a/desktop/hide_windows.go +++ b/desktop/hide_windows.go @@ -12,44 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. + package main -import "syscall" +import ( + "syscall" + "unsafe" +) const ( wmClose = 0x0010 - swHide = 0 - swShow = 5 - swRestore = 9 gwlpWndProc = ^uintptr(3) // GWLP_WNDPROC, which is -4 // DWMWA_TRANSITIONS_FORCEDISABLED, which turns off the animation the window - // manager plays when a window is shown, hidden or restored. + // manager plays when a window is shown, hidden, minimized or restored. dwmTransitionsForceDisabled = 3 ) var ( dwmapi = syscall.NewLazyDLL("dwmapi.dll") - procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW") - procSetWindowLongW = user32.NewProc("SetWindowLongW") - procCallWindowProcW = user32.NewProc("CallWindowProcW") - procDwmSetWindowAttrib = dwmapi.NewProc("DwmSetWindowAttribute") - procIsIconic = user32.NewProc("IsIconic") - procBringWindowToTop = user32.NewProc("BringWindowToTop") - procAttachThreadInput = user32.NewProc("AttachThreadInput") - procGetForegroundWindow = user32.NewProc("GetForegroundWindow") - procGetCurrentThreadId = kernel32.NewProc("GetCurrentThreadId") - kernel32 = syscall.NewLazyDLL("kernel32.dll") - previousWindowProc uintptr - hideOnCloseProcCallback = syscall.NewCallback(hideOnClose) + procSetWindowLongPtrW = user32.NewProc("SetWindowLongPtrW") + procSetWindowLongW = user32.NewProc("SetWindowLongW") + procCallWindowProcW = user32.NewProc("CallWindowProcW") + procDwmSetWindowAttrib = dwmapi.NewProc("DwmSetWindowAttribute") + + previousWindowProc uintptr + hideOnCloseCallback = syscall.NewCallback(hideOnClose) ) // keepWindowAlive makes closing the window hide it instead of destroying it. -// The webview's own window procedure answers WM_CLOSE with DestroyWindow, which -// ends the run loop and the process with it — which is why reopening from the -// tray used to reload everything from scratch. Hiding keeps the page loaded, so -// the window comes back instantly. +// The webview answers WM_CLOSE with DestroyWindow, which ends the run loop and +// the process with it — which is why reopening from the tray reloaded the whole +// UI. Hiding keeps the page loaded, so the window comes back instantly. func keepWindowAlive(hwnd uintptr) { if hwnd == 0 { return @@ -66,7 +61,7 @@ func keepWindowAlive(hwnd uintptr) { return } - previousWindowProc, _, _ = setLong.Call(hwnd, gwlpWndProc, hideOnCloseProcCallback) + previousWindowProc, _, _ = setLong.Call(hwnd, gwlpWndProc, hideOnCloseCallback) } func hideOnClose(hwnd, msg, wparam, lparam uintptr) uintptr { @@ -88,7 +83,7 @@ func disableWindowAnimations(hwnd uintptr) { _, _, _ = procDwmSetWindowAttrib.Call( hwnd, dwmTransitionsForceDisabled, - uintptr(unsafePointerTo(&disabled)), - 4, + uintptr(unsafe.Pointer(&disabled)), + unsafe.Sizeof(disabled), ) } diff --git a/desktop/window_windows.go b/desktop/window_windows.go index fe45551..8acefe4 100644 --- a/desktop/window_windows.go +++ b/desktop/window_windows.go @@ -70,7 +70,10 @@ func runWindow(url string) error { } defer view.Destroy() - setWindowIcon(uintptr(view.Window())) + hwnd := uintptr(view.Window()) + setWindowIcon(hwnd) + keepWindowAlive(hwnd) + view.Navigate(url) view.Run() return nil --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
