app: [Windows] add minimal support for devices with touch screens

The Windows Pointer API
(https://learn.microsoft.com/en-us/windows/win32/api/_inputmsg/) was used
to allow the detection of events when interacting with touch screens. This
also opens the gates for supporting other types of input devices (e.g. pens
and touchpads). Mouse events are now part of the pointer events (primary
events trigger WM_POINTER<DOWN|UP>, whereas secondary ones vanilla
WM_POINTERUPDATE, and cancellations WM_POINTERCAPTURECHANGED).
A fourth and fifth button (usually found in modern mice) has also been added
for completeness, though their integration in other OS-es shall be the
objective of future patches.

Signed-off-by: Marc <marc.leroy@samantree.com>
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
Marc
2025-05-27 10:33:22 +02:00
committed by Elias Naur
parent 0eac4f2c6a
commit 809a6d0dc7
2 changed files with 239 additions and 87 deletions
+104 -2
View File
@@ -108,6 +108,74 @@ type MonitorInfo struct {
Flags uint32 Flags uint32
} }
type POINTER_INPUT_TYPE int32
const (
PT_POINTER POINTER_INPUT_TYPE = 1
PT_TOUCH POINTER_INPUT_TYPE = 2
PT_PEN POINTER_INPUT_TYPE = 3
PT_MOUSE POINTER_INPUT_TYPE = 4
PT_TOUCHPAD POINTER_INPUT_TYPE = 5
)
type POINTER_INFO_POINTER_FLAGS int32
const (
POINTER_FLAG_NEW POINTER_INFO_POINTER_FLAGS = 0x00000001
POINTER_FLAG_INRANGE POINTER_INFO_POINTER_FLAGS = 0x00000002
POINTER_FLAG_INCONTACT POINTER_INFO_POINTER_FLAGS = 0x00000004
POINTER_FLAG_FIRSTBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000010
POINTER_FLAG_SECONDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000020
POINTER_FLAG_THIRDBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000040
POINTER_FLAG_FOURTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000080
POINTER_FLAG_FIFTHBUTTON POINTER_INFO_POINTER_FLAGS = 0x00000100
POINTER_FLAG_PRIMARY POINTER_INFO_POINTER_FLAGS = 0x00002000
POINTER_FLAG_CONFIDENCE POINTER_INFO_POINTER_FLAGS = 0x00004000
POINTER_FLAG_CANCELED POINTER_INFO_POINTER_FLAGS = 0x00008000
POINTER_FLAG_DOWN POINTER_INFO_POINTER_FLAGS = 0x00010000
POINTER_FLAG_UPDATE POINTER_INFO_POINTER_FLAGS = 0x00020000
POINTER_FLAG_UP POINTER_INFO_POINTER_FLAGS = 0x00040000
POINTER_FLAG_WHEEL POINTER_INFO_POINTER_FLAGS = 0x00080000
POINTER_FLAG_HWHEEL POINTER_INFO_POINTER_FLAGS = 0x00100000
POINTER_FLAG_CAPTURECHANGED POINTER_INFO_POINTER_FLAGS = 0x00200000
POINTER_FLAG_HASTRANSFORM POINTER_INFO_POINTER_FLAGS = 0x00400000
)
type POINTER_BUTTON_CHANGE_TYPE int32
const (
POINTER_CHANGE_NONE POINTER_BUTTON_CHANGE_TYPE = 0
POINTER_CHANGE_FIRSTBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 1
POINTER_CHANGE_FIRSTBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 2
POINTER_CHANGE_SECONDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 3
POINTER_CHANGE_SECONDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 4
POINTER_CHANGE_THIRDBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 5
POINTER_CHANGE_THIRDBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 6
POINTER_CHANGE_FOURTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 7
POINTER_CHANGE_FOURTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 8
POINTER_CHANGE_FIFTHBUTTON_DOWN POINTER_BUTTON_CHANGE_TYPE = 9
POINTER_CHANGE_FIFTHBUTTON_UP POINTER_BUTTON_CHANGE_TYPE = 10
)
type PointerInfo struct {
PointerType POINTER_INPUT_TYPE
PointerId uint32
FrameId uint32
PointerFlags POINTER_INFO_POINTER_FLAGS
SourceDevice syscall.Handle
HwndTarget syscall.Handle
PtPixelLocation Point
PtHimetricLocation Point
PtPixelLocationRaw Point
PtHimetricLocationRaw Point
DwTime uint32
HistoryCount uint32
InputData int32
DwKeyStates uint32
PerformanceCount uint64
ButtonChangeType POINTER_BUTTON_CHANGE_TYPE
}
const ( const (
TRUE = 1 TRUE = 1
@@ -269,7 +337,15 @@ const (
WM_NCHITTEST = 0x0084 WM_NCHITTEST = 0x0084
WM_NCCALCSIZE = 0x0083 WM_NCCALCSIZE = 0x0083
WM_PAINT = 0x000F WM_PAINT = 0x000F
WM_POINTERCAPTURECHANGED = 0x024C
WM_POINTERDOWN = 0x0246
WM_POINTERUP = 0x0247
WM_POINTERUPDATE = 0x0245
WM_POINTERWHEEL = 0x024E
WM_POINTERHWHEEL = 0x024F
WM_QUIT = 0x0012 WM_QUIT = 0x0012
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_SETCURSOR = 0x0020 WM_SETCURSOR = 0x0020
WM_SETFOCUS = 0x0007 WM_SETFOCUS = 0x0007
WM_SHOWWINDOW = 0x0018 WM_SHOWWINDOW = 0x0018
@@ -277,8 +353,6 @@ const (
WM_STYLECHANGED = 0x007D WM_STYLECHANGED = 0x007D
WM_SYSKEYDOWN = 0x0104 WM_SYSKEYDOWN = 0x0104
WM_SYSKEYUP = 0x0105 WM_SYSKEYUP = 0x0105
WM_RBUTTONDOWN = 0x0204
WM_RBUTTONUP = 0x0205
WM_TIMER = 0x0113 WM_TIMER = 0x0113
WM_UNICHAR = 0x0109 WM_UNICHAR = 0x0109
WM_USER = 0x0400 WM_USER = 0x0400
@@ -346,6 +420,7 @@ var (
_DestroyWindow = user32.NewProc("DestroyWindow") _DestroyWindow = user32.NewProc("DestroyWindow")
_DispatchMessage = user32.NewProc("DispatchMessageW") _DispatchMessage = user32.NewProc("DispatchMessageW")
_EmptyClipboard = user32.NewProc("EmptyClipboard") _EmptyClipboard = user32.NewProc("EmptyClipboard")
_EnableMouseInPointer = user32.NewProc("EnableMouseInPointer")
_GetWindowRect = user32.NewProc("GetWindowRect") _GetWindowRect = user32.NewProc("GetWindowRect")
_GetClientRect = user32.NewProc("GetClientRect") _GetClientRect = user32.NewProc("GetClientRect")
_GetClipboardData = user32.NewProc("GetClipboardData") _GetClipboardData = user32.NewProc("GetClipboardData")
@@ -355,6 +430,7 @@ var (
_GetMessage = user32.NewProc("GetMessageW") _GetMessage = user32.NewProc("GetMessageW")
_GetMessageTime = user32.NewProc("GetMessageTime") _GetMessageTime = user32.NewProc("GetMessageTime")
_GetMonitorInfo = user32.NewProc("GetMonitorInfoW") _GetMonitorInfo = user32.NewProc("GetMonitorInfoW")
_GetPointerInfo = user32.NewProc("GetPointerInfo")
_GetSystemMetrics = user32.NewProc("GetSystemMetrics") _GetSystemMetrics = user32.NewProc("GetSystemMetrics")
_GetWindowLong = user32.NewProc("GetWindowLongPtrW") _GetWindowLong = user32.NewProc("GetWindowLongPtrW")
_GetWindowLong32 = user32.NewProc("GetWindowLongW") _GetWindowLong32 = user32.NewProc("GetWindowLongW")
@@ -372,6 +448,7 @@ var (
_PostQuitMessage = user32.NewProc("PostQuitMessage") _PostQuitMessage = user32.NewProc("PostQuitMessage")
_ReleaseCapture = user32.NewProc("ReleaseCapture") _ReleaseCapture = user32.NewProc("ReleaseCapture")
_RegisterClassExW = user32.NewProc("RegisterClassExW") _RegisterClassExW = user32.NewProc("RegisterClassExW")
_RegisterTouchWindow = user32.NewProc("RegisterTouchWindow")
_ReleaseDC = user32.NewProc("ReleaseDC") _ReleaseDC = user32.NewProc("ReleaseDC")
_ScreenToClient = user32.NewProc("ScreenToClient") _ScreenToClient = user32.NewProc("ScreenToClient")
_ShowWindow = user32.NewProc("ShowWindow") _ShowWindow = user32.NewProc("ShowWindow")
@@ -445,6 +522,31 @@ func CreateWindowEx(dwExStyle uint32, lpClassName uint16, lpWindowName string, d
return syscall.Handle(hwnd), nil return syscall.Handle(hwnd), nil
} }
func GetPointerInfo(pointerId uint32) (PointerInfo, error) {
var info PointerInfo
r1, _, err := _GetPointerInfo.Call(uintptr(pointerId), uintptr(unsafe.Pointer(&info)))
if r1 == 0 {
return PointerInfo{}, fmt.Errorf("GetPointerInfo failed: %v", err)
}
return info, nil
}
func RegisterTouchWindow(hwnd syscall.Handle, flags uint32) error {
r1, _, err := _RegisterTouchWindow.Call(uintptr(hwnd), uintptr(flags))
if r1 == 0 {
return fmt.Errorf("RegisterTouchWindow failed: %v", err)
}
return nil
}
func EnableMouseInPointer(enable uint) error {
r1, _, err := _EnableMouseInPointer.Call(uintptr(enable))
if r1 == 0 {
return fmt.Errorf("EnableMouseInPointer failed: %v", err)
}
return nil
}
func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr { func DefWindowProc(hwnd syscall.Handle, msg uint32, wparam, lparam uintptr) uintptr {
r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam) r, _, _ := _DefWindowProc.Call(uintptr(hwnd), uintptr(msg), wparam, lparam)
return r return r
+95 -45
View File
@@ -39,7 +39,6 @@ type window struct {
hwnd syscall.Handle hwnd syscall.Handle
hdc syscall.Handle hdc syscall.Handle
w *callbacks w *callbacks
pointerBtns pointer.Buttons
// cursorIn tracks whether the cursor was inside the window according // cursorIn tracks whether the cursor was inside the window according
// to the most recent WM_SETCURSOR. // to the most recent WM_SETCURSOR.
@@ -175,6 +174,12 @@ func (w *window) init() error {
if err != nil { if err != nil {
return err return err
} }
if err := windows.RegisterTouchWindow(hwnd, 0); err != nil {
return err
}
if err := windows.EnableMouseInPointer(1); err != nil {
return err
}
w.hdc, err = windows.GetDC(hwnd) w.hdc, err = windows.GetDC(hwnd)
if err != nil { if err != nil {
windows.DestroyWindow(hwnd) windows.DestroyWindow(hwnd)
@@ -265,18 +270,32 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
return 0 return 0
} }
} }
case windows.WM_LBUTTONDOWN: case windows.WM_POINTERDOWN, windows.WM_POINTERUP, windows.WM_POINTERUPDATE, windows.WM_POINTERCAPTURECHANGED:
w.pointerButton(pointer.ButtonPrimary, true, lParam, getModifiers()) pid := getPointerIDwParam(wParam)
case windows.WM_LBUTTONUP: pi, err := windows.GetPointerInfo(uint32(pid))
w.pointerButton(pointer.ButtonPrimary, false, lParam, getModifiers()) if err != nil {
case windows.WM_RBUTTONDOWN: panic(err)
w.pointerButton(pointer.ButtonSecondary, true, lParam, getModifiers()) }
case windows.WM_RBUTTONUP: switch msg {
w.pointerButton(pointer.ButtonSecondary, false, lParam, getModifiers()) case windows.WM_POINTERDOWN:
case windows.WM_MBUTTONDOWN: windows.SetCapture(w.hwnd)
w.pointerButton(pointer.ButtonTertiary, true, lParam, getModifiers()) case windows.WM_POINTERUP:
case windows.WM_MBUTTONUP: windows.ReleaseCapture()
w.pointerButton(pointer.ButtonTertiary, false, lParam, getModifiers()) }
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)
case windows.WM_CANCELMODE: case windows.WM_CANCELMODE:
w.ProcessEvent(pointer.Event{ w.ProcessEvent(pointer.Event{
Kind: pointer.Cancel, Kind: pointer.Cancel,
@@ -296,20 +315,9 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
np := windows.Point{X: int32(x), Y: int32(y)} np := windows.Point{X: int32(x), Y: int32(y)}
windows.ScreenToClient(w.hwnd, &np) windows.ScreenToClient(w.hwnd, &np)
return w.hitTest(int(np.X), int(np.Y)) return w.hitTest(int(np.X), int(np.Y))
case windows.WM_MOUSEMOVE: case windows.WM_POINTERWHEEL:
x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)}
w.ProcessEvent(pointer.Event{
Kind: pointer.Move,
Source: pointer.Mouse,
Position: p,
Buttons: w.pointerBtns,
Time: windows.GetMessageTime(),
Modifiers: getModifiers(),
})
case windows.WM_MOUSEWHEEL:
w.scrollEvent(wParam, lParam, false, getModifiers()) w.scrollEvent(wParam, lParam, false, getModifiers())
case windows.WM_MOUSEHWHEEL: case windows.WM_POINTERHWHEEL:
w.scrollEvent(wParam, lParam, true, getModifiers()) w.scrollEvent(wParam, lParam, true, getModifiers())
case windows.WM_DESTROY: case windows.WM_DESTROY:
w.ProcessEvent(Win32ViewEvent{}) w.ProcessEvent(Win32ViewEvent{})
@@ -499,34 +507,28 @@ func (w *window) hitTest(x, y int) uintptr {
return windows.HTCLIENT return windows.HTCLIENT
} }
func (w *window) pointerButton(btn pointer.Buttons, press bool, lParam uintptr, kmods key.Modifiers) { func (w *window) pointerUpdate(pi windows.PointerInfo, pid pointer.ID, kind pointer.Kind, lParam uintptr) {
if !w.config.Focused { if !w.config.Focused {
windows.SetFocus(w.hwnd) windows.SetFocus(w.hwnd)
} }
var kind pointer.Kind src := pointer.Touch
if press { if pi.PointerType == windows.PT_MOUSE {
kind = pointer.Press src = pointer.Mouse
if w.pointerBtns == 0 {
windows.SetCapture(w.hwnd)
}
w.pointerBtns |= btn
} else {
kind = pointer.Release
w.pointerBtns &^= btn
if w.pointerBtns == 0 {
windows.ReleaseCapture()
}
} }
x, y := coordsFromlParam(lParam) x, y := coordsFromlParam(lParam)
p := f32.Point{X: float32(x), Y: float32(y)} np := windows.Point{X: int32(x), Y: int32(y)}
windows.ScreenToClient(w.hwnd, &np)
p := f32.Point{X: float32(np.X), Y: float32(np.Y)}
w.ProcessEvent(pointer.Event{ w.ProcessEvent(pointer.Event{
Kind: kind, Kind: kind,
Source: pointer.Mouse, Source: src,
Position: p, Position: p,
Buttons: w.pointerBtns, PointerID: pid,
Buttons: getPointerButtons(pi),
Time: windows.GetMessageTime(), Time: windows.GetMessageTime(),
Modifiers: kmods, Modifiers: getModifiers(),
}) })
} }
@@ -537,6 +539,12 @@ func coordsFromlParam(lParam uintptr) (int, int) {
} }
func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) { func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.Modifiers) {
pid := getPointerIDwParam(wParam)
pi, err := windows.GetPointerInfo(uint32(pid))
if err != nil {
panic(err)
}
x, y := coordsFromlParam(lParam) x, y := coordsFromlParam(lParam)
// The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast // The WM_MOUSEWHEEL coordinates are in screen coordinates, in contrast
// to other mouse events. // to other mouse events.
@@ -559,7 +567,7 @@ func (w *window) scrollEvent(wParam, lParam uintptr, horizontal bool, kmods key.
Kind: pointer.Scroll, Kind: pointer.Scroll,
Source: pointer.Mouse, Source: pointer.Mouse,
Position: p, Position: p,
Buttons: w.pointerBtns, Buttons: getPointerButtons(pi),
Scroll: sp, Scroll: sp,
Modifiers: kmods, Modifiers: kmods,
Time: windows.GetMessageTime(), Time: windows.GetMessageTime(),
@@ -1005,3 +1013,45 @@ func (Win32ViewEvent) ImplementsEvent() {}
func (w Win32ViewEvent) Valid() bool { func (w Win32ViewEvent) Valid() bool {
return w != (Win32ViewEvent{}) return w != (Win32ViewEvent{})
} }
// LOWORD (minwindef.h)
func loWord(val uint32) uint16 {
return uint16(val & 0xFFFF)
}
// GET_POINTERID_WPARAM (winuser.h)
func getPointerIDwParam(wParam uintptr) pointer.ID {
return pointer.ID(loWord(uint32(wParam)))
}
func getPointerButtons(pi windows.PointerInfo) pointer.Buttons {
var btns pointer.Buttons
if pi.PointerFlags&windows.POINTER_FLAG_FIRSTBUTTON != 0 {
btns |= pointer.ButtonPrimary
} else {
btns &^= pointer.ButtonPrimary
}
if pi.PointerFlags&windows.POINTER_FLAG_SECONDBUTTON != 0 {
btns |= pointer.ButtonSecondary
} else {
btns &^= pointer.ButtonSecondary
}
if pi.PointerFlags&windows.POINTER_FLAG_THIRDBUTTON != 0 {
btns |= pointer.ButtonTertiary
} else {
btns &^= pointer.ButtonTertiary
}
if pi.PointerFlags&windows.POINTER_FLAG_FOURTHBUTTON != 0 {
btns |= pointer.ButtonQuaternary
} else {
btns &^= pointer.ButtonQuaternary
}
if pi.PointerFlags&windows.POINTER_FLAG_FIFTHBUTTON != 0 {
btns |= pointer.ButtonQuinary
} else {
btns &^= pointer.ButtonQuinary
}
return btns
}