fix(windows): use WM_NCHITTEST instead of removing WS_EX_LAYERED in fullscreen

Removing WS_EX_LAYERED dynamically breaks the layered window rendering
because unfullscreen() cannot properly re-initialize the compositing
pipeline when re-adding the flag via SetWindowLong (the window was
originally created with WS_EX_LAYERED via CreateWindowEx which auto-
initializes, but dynamically adding it requires explicit calls to
SetLayeredWindowAttributes or UpdateLayeredWindow).

Instead, keep WS_EX_LAYERED set and handle the alpha-based hit-testing
issue in WndProc: return HTCLIENT for WM_NCHITTEST when fullscreen is
active, forcing all mouse events to be captured regardless of pixel
alpha values. WS_EX_TRANSPARENT is still removed in fullscreen() to
prevent explicit click-through.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Lea Anthony 2026-02-28 16:08:34 +11:00
commit 2a9cdc0e73

View file

@ -843,13 +843,15 @@ func (w *windowsWebviewWindow) fullscreen() {
w32.GWL_STYLE,
w.previousWindowStyle & ^uint32(w32.WS_OVERLAPPEDWINDOW) | (w32.WS_POPUP|w32.WS_VISIBLE),
)
// Remove WS_EX_TRANSPARENT and WS_EX_LAYERED to ensure mouse events are captured in fullscreen mode.
// Both flags must be removed to fix click-through issues when Frameless + BackgroundTypeTransparent are used.
// Remove WS_EX_DLGMODALFRAME and WS_EX_TRANSPARENT to prevent mouse click-through in fullscreen.
// WS_EX_LAYERED is intentionally kept: removing it dynamically breaks the layered window rendering
// and cannot be properly restored in unfullscreen() without re-initializing the compositing pipeline.
// Alpha-based hit-testing (from WS_EX_LAYERED) is handled separately via WM_NCHITTEST in WndProc.
// See: https://github.com/wailsapp/wails/issues/4408
w32.SetWindowLong(
w.hwnd,
w32.GWL_EXSTYLE,
w.previousWindowExStyle & ^uint32(w32.WS_EX_DLGMODALFRAME|w32.WS_EX_TRANSPARENT|w32.WS_EX_LAYERED),
w.previousWindowExStyle & ^uint32(w32.WS_EX_DLGMODALFRAME|w32.WS_EX_TRANSPARENT),
)
w.isCurrentlyFullscreen = true
w32.SetWindowPos(w.hwnd, w32.HWND_TOP,
@ -1384,6 +1386,14 @@ func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintp
return code
}
// Force mouse event capture in fullscreen mode for transparent/layered windows.
// WS_EX_LAYERED windows use alpha-based hit testing, so transparent pixels (alpha=0) would
// normally pass mouse events through. Returning HTCLIENT overrides this behavior.
// See: https://github.com/wailsapp/wails/issues/4408
if msg == w32.WM_NCHITTEST && w.isCurrentlyFullscreen {
return w32.HTCLIENT
}
switch msg {
case w32.WM_ACTIVATE:
if int(wparam&0xffff) == w32.WA_INACTIVE {