From 1555e7bd10eeade3cf392b5cc3816431b3842ad2 Mon Sep 17 00:00:00 2001 From: qiannian <40210590@qq.com> Date: Fri, 6 Nov 2026 22:40:33 +0800 Subject: [PATCH 1/3] app: [Windows] clear hover when the pointer leaves the client area Resizing a window could leave title bar buttons hovered. Send pointer.Leave for pointer leave and non-client messages so widgets clear their hover state. Leave events must not take focus. Signed-off-by: qiannian Signed-off-by: Elias Naur --- app/internal/windows/windows.go | 4 +++ app/os_windows.go | 43 +++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/app/internal/windows/windows.go b/app/internal/windows/windows.go index ba1f330f..54f18da3 100644 --- a/app/internal/windows/windows.go +++ b/app/internal/windows/windows.go @@ -368,9 +368,13 @@ const ( WM_NCACTIVATE = 0x0086 WM_NCHITTEST = 0x0084 WM_NCCALCSIZE = 0x0083 + WM_NCPOINTERUPDATE = 0x0241 + WM_NCPOINTERDOWN = 0x0242 + WM_NCPOINTERUP = 0x0243 WM_PAINT = 0x000F WM_POINTERCAPTURECHANGED = 0x024C WM_POINTERDOWN = 0x0246 + WM_POINTERLEAVE = 0x024A WM_POINTERUP = 0x0247 WM_POINTERUPDATE = 0x0245 WM_POINTERWHEEL = 0x024E diff --git a/app/os_windows.go b/app/os_windows.go index 11766227..ad1169f9 100644 --- a/app/os_windows.go +++ b/app/os_windows.go @@ -307,26 +307,16 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr return 0 } } - case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED: + case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, + windows.WM_POINTERLEAVE, windows.WM_POINTERCAPTURECHANGED, + windows.WM_NCPOINTERUPDATE, windows.WM_NCPOINTERDOWN, windows.WM_NCPOINTERUP: pid := getPointerIDwParam(wParam) pi, err := windows.GetPointerInfo(uint32(pid)) if err != nil { panic(err) } - kind := pointer.Move - switch pi.ButtonChangeType { - case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, windows.POINTER_CHANGE_FIFTHBUTTON_DOWN: - kind = pointer.Press - case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, windows.POINTER_CHANGE_FIFTHBUTTON_UP: - kind = pointer.Release - } - - if (pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0) || (msg == windows.WM_POINTERCAPTURECHANGED) { - kind = pointer.Cancel - } - - w.pointerUpdate(pi, pid, kind, lParam) + w.pointerUpdate(pi, pid, pointerKind(msg, pi), lParam) case windows.WM_CANCELMODE: w.ProcessEvent(pointer.Event{ Kind: pointer.Cancel, @@ -641,8 +631,31 @@ func (w *window) hitTest(x, y int) uintptr { return windows.HTCLIENT } +func pointerKind(msg uint32, pi windows.PointerInfo) pointer.Kind { + if pi.PointerFlags&windows.POINTER_FLAG_CANCELED != 0 || msg == windows.WM_POINTERCAPTURECHANGED { + return pointer.Cancel + } + switch msg { + case windows.WM_POINTERLEAVE, windows.WM_NCPOINTERUPDATE, windows.WM_NCPOINTERDOWN, windows.WM_NCPOINTERUP: + // Treat non-client input as leaving the client area. + return pointer.Leave + } + switch pi.ButtonChangeType { + case windows.POINTER_CHANGE_FIRSTBUTTON_DOWN, windows.POINTER_CHANGE_SECONDBUTTON_DOWN, + windows.POINTER_CHANGE_THIRDBUTTON_DOWN, windows.POINTER_CHANGE_FOURTHBUTTON_DOWN, + windows.POINTER_CHANGE_FIFTHBUTTON_DOWN: + return pointer.Press + case windows.POINTER_CHANGE_FIRSTBUTTON_UP, windows.POINTER_CHANGE_SECONDBUTTON_UP, + windows.POINTER_CHANGE_THIRDBUTTON_UP, windows.POINTER_CHANGE_FOURTHBUTTON_UP, + windows.POINTER_CHANGE_FIFTHBUTTON_UP: + return pointer.Release + default: + return pointer.Move + } +} + func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) { - if !w.config.Focused { + if !w.config.Focused && kind != pointer.Leave { windows.SetFocus(w.hwnd) } From aec3b91f3396a8752423be7458d7b0f67f8068f0 Mon Sep 17 00:00:00 2001 From: John Englar Date: Fri, 6 Nov 2026 14:57:05 +0000 Subject: [PATCH 2/3] app: [X11] scroll once per wheel notch, not twice The X server reports a wheel notch as a press and a release of button 4, 5, 6 or 7. Both were turned into pointer.Scroll events, so every notch scrolled twice the intended distance. The attached patch ignores the release. Signed-off-by: John Englar Signed-off-by: Elias Naur --- app/os_x11.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/os_x11.go b/app/os_x11.go index 03041ae2..ccfec3f7 100644 --- a/app/os_x11.go +++ b/app/os_x11.go @@ -701,6 +701,11 @@ func (h *x11EventHandler) handleEvents() bool { default: continue } + if ev.Kind == pointer.Scroll && _type == C.ButtonRelease { + // The X server sends a press and a release for every wheel + // notch; only the press scrolls, or each notch counts twice. + continue + } switch _type { case C.ButtonPress: w.pointerBtns |= btn From 2919cda56e42755a93859433906ecf3623579a44 Mon Sep 17 00:00:00 2001 From: John Englar Date: Fri, 6 Nov 2026 14:55:42 +0000 Subject: [PATCH 3/3] app: [X11] send pointer.Cancel when the pointer leaves the window Signed-off-by: John Englar Signed-off-by: Elias Naur --- app/os_x11.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/os_x11.go b/app/os_x11.go index ccfec3f7..f0a0276d 100644 --- a/app/os_x11.go +++ b/app/os_x11.go @@ -727,6 +727,11 @@ func (h *x11EventHandler) handleEvents() bool { Time: time.Duration(mevt.time) * time.Millisecond, Modifiers: w.xkb.Modifiers(), }) + case C.LeaveNotify: + cevt := (*C.XCrossingEvent)(unsafe.Pointer(xev)) + if w.pointerBtns == 0 && cevt.detail != C.NotifyInferior { + w.ProcessEvent(pointer.Event{Kind: pointer.Cancel}) + } case C.Expose: // update // redraw only on the last expose event redraw = (*C.XExposeEvent)(unsafe.Pointer(xev)).count == 0 @@ -903,6 +908,7 @@ func newX11Window(gioWin *callbacks, options []Option) error { C.KeyPressMask | C.KeyReleaseMask | // keyboard C.ButtonPressMask | C.ButtonReleaseMask | // mouse clicks C.PointerMotionMask | // mouse movement + C.LeaveWindowMask | // pointer leaving the window C.StructureNotifyMask, // resize background_pixmap: C.None, override_redirect: C.False,