From 8c9b8910c1716684080f1ab6058fac1ce7effdad Mon Sep 17 00:00:00 2001 From: David Capello Date: Wed, 1 Nov 2017 22:42:30 -0300 Subject: [PATCH 1/2] Use WM_POINTER on Windows 8 platform First version. Still touch support is not working as expected. The pen, mouse, and trackpad are working correctly. (Even the eraser tip of the pen is recognized.) --- CMakeLists.txt | 5 +- src/she/CMakeLists.txt | 1 + src/she/README.md | 1 + src/she/win/system.h | 9 +- src/she/win/winapi.cpp | 42 +++++ src/she/win/winapi.h | 39 +++++ src/she/win/window.cpp | 378 ++++++++++++++++++++++++++++++++++++----- src/she/win/window.h | 9 + 8 files changed, 437 insertions(+), 47 deletions(-) create mode 100644 src/she/win/winapi.cpp create mode 100644 src/she/win/winapi.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dae638be8..eff20c31d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -405,8 +405,9 @@ if(WIN32) kernel32 user32 gdi32 comdlg32 ole32 winmm shlwapi psapi wininet comctl32 dbghelp) - # Windows XP is the minimum supported platform. - add_definitions(-D_WIN32_WINNT=0x0501 -DWINVER=0x0501) + # Windows Vista is the minimum supported platform but we're defining + # Windows 10 to get the all constant/structure definitions. + add_definitions(-D_WIN32_WINNT=0x0A00 -DWINVER=0x0A00) # We need Unicode support add_definitions(-DUNICODE -D_UNICODE) diff --git a/src/she/CMakeLists.txt b/src/she/CMakeLists.txt index d6b59088b..7328a26ca 100644 --- a/src/she/CMakeLists.txt +++ b/src/she/CMakeLists.txt @@ -122,6 +122,7 @@ if(USE_SKIA_BACKEND) skia/skia_window_win.cpp win/pen.cpp win/vk.cpp + win/winapi.cpp win/window.cpp win/window_dde.cpp) elseif(APPLE) diff --git a/src/she/README.md b/src/she/README.md index f06437852..602bf4205 100644 --- a/src/she/README.md +++ b/src/she/README.md @@ -7,3 +7,4 @@ back-ends: * Previous version were using Allegro 4 (it still uses Allegro 4 on Linux) * Now we use our own implementation on Windows and macOS to handle events, and [Skia](https://skia.org/) to render graphics. +* Minimum Windows platform: Windows Vista diff --git a/src/she/win/system.h b/src/she/win/system.h index 70e8a922c..7b7361f1e 100644 --- a/src/she/win/system.h +++ b/src/she/win/system.h @@ -1,5 +1,5 @@ // SHE library -// Copyright (C) 2012-2016 David Capello +// Copyright (C) 2012-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -10,6 +10,7 @@ #include "she/common/system.h" #include "she/win/pen.h" +#include "she/win/winapi.h" namespace she { @@ -21,9 +22,8 @@ public: WindowSystem() { } ~WindowSystem() { } - PenAPI& penApi() { - return m_penApi; - } + WinAPI& winApi() { return m_winApi; } + PenAPI& penApi() { return m_penApi; } bool isKeyPressed(KeyScancode scancode) override { return win_is_key_pressed(scancode); @@ -34,6 +34,7 @@ public: } private: + WinAPI m_winApi; PenAPI m_penApi; }; diff --git a/src/she/win/winapi.cpp b/src/she/win/winapi.cpp new file mode 100644 index 000000000..8738afaf9 --- /dev/null +++ b/src/she/win/winapi.cpp @@ -0,0 +1,42 @@ +// SHE library +// Copyright (C) 2017 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "she/win/winapi.h" + +namespace she { + +#define GET_PROC(dll, name) \ + name = base::get_dll_proc(dll, #name) + +WinAPI::WinAPI() + : EnableMouseInPointer(nullptr) + , IsMouseInPointerEnabled(nullptr) + , GetPointerInfo(nullptr) + , GetPointerPenInfo(nullptr) + , m_user32(nullptr) +{ + m_user32 = base::load_dll("user32.dll"); + if (m_user32) { + GET_PROC(m_user32, EnableMouseInPointer); + GET_PROC(m_user32, IsMouseInPointerEnabled); + GET_PROC(m_user32, GetPointerInfo); + GET_PROC(m_user32, GetPointerPenInfo); + } +} + +WinAPI::~WinAPI() +{ + if (m_user32) { + base::unload_dll(m_user32); + m_user32 = nullptr; + } +} + +} // namespace she diff --git a/src/she/win/winapi.h b/src/she/win/winapi.h new file mode 100644 index 000000000..787fd8ee9 --- /dev/null +++ b/src/she/win/winapi.h @@ -0,0 +1,39 @@ +// SHE library +// Copyright (C) 2017 David Capello +// +// This file is released under the terms of the MIT license. +// Read LICENSE.txt for more information. + +#ifndef SHE_WIN_WINAPI_H_INCLUDED +#define SHE_WIN_WINAPI_H_INCLUDED +#pragma once + +#include "base/dll.h" + +#include + +namespace she { + + typedef BOOL (WINAPI* EnableMouseInPointer_Func)(BOOL fEnable); + typedef BOOL (WINAPI* IsMouseInPointerEnabled_Func)(void); + typedef BOOL (WINAPI* GetPointerInfo_Func)(UINT32 pointerId, POINTER_INFO* pointerInfo); + typedef BOOL (WINAPI* GetPointerPenInfo_Func)(UINT32 pointerId, POINTER_PEN_INFO* penInfo); + + class WinAPI { + public: + WinAPI(); + ~WinAPI(); + + // These functions are availble only since Windows 8 + EnableMouseInPointer_Func EnableMouseInPointer; + IsMouseInPointerEnabled_Func IsMouseInPointerEnabled; + GetPointerInfo_Func GetPointerInfo; + GetPointerPenInfo_Func GetPointerPenInfo; + + private: + base::dll m_user32; + }; + +} // namespace she + +#endif diff --git a/src/she/win/window.cpp b/src/she/win/window.cpp index 489bb111d..141542771 100644 --- a/src/she/win/window.cpp +++ b/src/she/win/window.cpp @@ -25,29 +25,57 @@ #include "she/win/vk.h" #include "she/win/window_dde.h" -#ifndef WM_MOUSEHWHEEL - #define WM_MOUSEHWHEEL 0x020E -#endif - #define SHE_WND_CLASS_NAME L"Aseprite.Window" +#define MOUSE_TRACE(...) + +// Gets the window client are in absolute/screen coordinates +#define ABS_CLIENT_RC(rc) \ + RECT rc; \ + GetClientRect(m_hwnd, &rc); \ + MapWindowPoints(m_hwnd, NULL, (POINT*)&rc, 2) + +// Not yet ready because if we start receiving WM_POINTERDOWN messages +// instead of WM_LBUTTONDBLCLK we lost the automatic double-click +// messages. +#define USE_EnableMouseInPointer 0 + namespace she { WinWindow::WinWindow(int width, int height, int scale) : m_hwnd(nullptr) + , m_hcursor(nullptr) , m_clientSize(1, 1) , m_restoredSize(0, 0) + , m_scale(scale) , m_isCreated(false) , m_translateDeadKeys(false) , m_hasMouse(false) , m_captureMouse(false) + , m_customHcursor(false) + , m_usePointerApi(false) + , m_ignoreMouseMessages(false) + , m_lastPointerId(0) + , m_capturePointerId(0) , m_hpenctx(nullptr) , m_pointerType(PointerType::Unknown) , m_pressure(0.0) { - m_hcursor = nullptr; - m_customHcursor = false; - m_scale = scale; + auto& winApi = system()->winApi(); + if (winApi.EnableMouseInPointer && + winApi.IsMouseInPointerEnabled && + winApi.GetPointerInfo && + winApi.GetPointerPenInfo) { +#if USE_EnableMouseInPointer == 1 + if (!winApi.IsMouseInPointerEnabled()) { + // Prefer pointer messages (WM_POINTER*) since Windows 8 instead + // of mouse messages (WM_MOUSE*) + winApi.EnableMouseInPointer(TRUE); + m_ignoreMouseMessages = (winApi.IsMouseInPointerEnabled() ? true: false); + } +#endif + m_usePointerApi = true; + } registerClass(); @@ -127,11 +155,23 @@ void WinWindow::setTitle(const std::string& title) void WinWindow::captureMouse() { m_captureMouse = true; + m_capturePointerId = m_lastPointerId; + + if (GetCapture() != m_hwnd) { + MOUSE_TRACE("SetCapture\n"); + SetCapture(m_hwnd); + } } void WinWindow::releaseMouse() { m_captureMouse = false; + m_capturePointerId = 0; + + if (GetCapture() == m_hwnd) { + MOUSE_TRACE("ReleaseCapture\n"); + ReleaseCapture(); + } } void WinWindow::setMousePosition(const gfx::Point& position) @@ -436,22 +476,23 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) } break; + // Mouse and Trackpad Messages + case WM_MOUSEMOVE: { - // Adjust capture - if (m_captureMouse) { - if (GetCapture() != m_hwnd) - SetCapture(m_hwnd); - } - else { - if (GetCapture() == m_hwnd) - ReleaseCapture(); - } + // If the pointer API is enable, we use WM_POINTERUPDATE instead + // of WM_MOUSEMOVE. This check is here because Windows keeps + // sending us WM_MOUSEMOVE messages even when we call + // EnableMouseInPointer() (mainly when we use Alt+stylus we + // receive WM_MOUSEMOVE with the position of the mouse/trackpad + // + WM_POINTERUPDATE with the position of the pen) + if (m_ignoreMouseMessages) + break; Event ev; - ev.setModifiers(get_modifiers_from_last_win32_message()); - ev.setPosition(gfx::Point( - GET_X_LPARAM(lparam) / m_scale, - GET_Y_LPARAM(lparam) / m_scale)); + mouseEvent(lparam, ev); + + MOUSE_TRACE("MOUSEMOVE xy=%d,%d\n", + ev.position().x, ev.position().y); if (!m_hasMouse) { m_hasMouse = true; @@ -459,6 +500,8 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) ev.setType(Event::MouseEnter); queueEvent(ev); + MOUSE_TRACE("-> Event::MouseEnter\n"); + // Track mouse to receive WM_MOUSELEAVE message. TRACKMOUSEEVENT tme; tme.cbSize = sizeof(TRACKMOUSEEVENT); @@ -486,6 +529,8 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) ev.setType(Event::MouseLeave); ev.setModifiers(get_modifiers_from_last_win32_message()); queueEvent(ev); + + MOUSE_TRACE("-> Event::MouseLeave\n"); } break; @@ -494,11 +539,8 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_MBUTTONDOWN: case WM_XBUTTONDOWN: { Event ev; + mouseEvent(lparam, ev); ev.setType(Event::MouseDown); - ev.setModifiers(get_modifiers_from_last_win32_message()); - ev.setPosition(gfx::Point( - GET_X_LPARAM(lparam) / m_scale, - GET_Y_LPARAM(lparam) / m_scale)); ev.setButton( msg == WM_LBUTTONDOWN ? Event::LeftButton: msg == WM_RBUTTONDOWN ? Event::RightButton: @@ -511,8 +553,11 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) ev.setPointerType(m_pointerType); ev.setPressure(m_pressure); } - queueEvent(ev); + + MOUSE_TRACE("BUTTONDOWN xy=%d,%d button=%d\n", + ev.position().x, ev.position().y, + ev.button()); break; } @@ -521,11 +566,8 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_MBUTTONUP: case WM_XBUTTONUP: { Event ev; + mouseEvent(lparam, ev); ev.setType(Event::MouseUp); - ev.setModifiers(get_modifiers_from_last_win32_message()); - ev.setPosition(gfx::Point( - GET_X_LPARAM(lparam) / m_scale, - GET_Y_LPARAM(lparam) / m_scale)); ev.setButton( msg == WM_LBUTTONUP ? Event::LeftButton: msg == WM_RBUTTONUP ? Event::RightButton: @@ -538,9 +580,12 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) ev.setPointerType(m_pointerType); ev.setPressure(m_pressure); } - queueEvent(ev); + MOUSE_TRACE("BUTTONUP xy=%d,%d button=%d\n", + ev.position().x, ev.position().y, + ev.button()); + // Avoid popup menu for scrollbars if (msg == WM_RBUTTONUP) return 0; @@ -553,11 +598,8 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_RBUTTONDBLCLK: case WM_XBUTTONDBLCLK: { Event ev; + mouseEvent(lparam, ev); ev.setType(Event::MouseDoubleClick); - ev.setModifiers(get_modifiers_from_last_win32_message()); - ev.setPosition(gfx::Point( - GET_X_LPARAM(lparam) / m_scale, - GET_Y_LPARAM(lparam) / m_scale)); ev.setButton( msg == WM_LBUTTONDBLCLK ? Event::LeftButton: msg == WM_RBUTTONDBLCLK ? Event::RightButton: @@ -570,8 +612,11 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) ev.setPointerType(m_pointerType); ev.setPressure(m_pressure); } - queueEvent(ev); + + MOUSE_TRACE("BUTTONDBLCLK xy=%d,%d button=%d\n", + ev.position().x, ev.position().y, + ev.button()); break; } @@ -599,8 +644,11 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) (msg == WM_MOUSEHWHEEL ? z: 0), (msg == WM_MOUSEWHEEL ? -z: 0)); ev.setWheelDelta(delta); - queueEvent(ev); + + MOUSE_TRACE("MOUSEWHEEL xy=%d,%d delta=%d,%d\n", + ev.position().x, ev.position().y, + ev.wheelDelta().x, ev.wheelDelta().y); break; } @@ -646,11 +694,208 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) ev.setWheelDelta(delta); SetScrollPos(m_hwnd, bar, 50, FALSE); - queueEvent(ev); + + MOUSE_TRACE("HVSCROLL xy=%d,%d delta=%d,%d\n", + ev.position().x, ev.position().y, + ev.wheelDelta().x, ev.wheelDelta().y); break; } + // Pointer API (since Windows 8.0) + + case WM_POINTERCAPTURECHANGED: { + MOUSE_TRACE("POINTERCAPTURECHANGED\n"); + m_capturePointerId = 0; + break; + } + + case WM_POINTERENTER: { + POINTER_INFO pi; + Event ev; + if (!pointerEvent(wparam, lparam, ev, pi)) + break; + + // Ignore this message because we have captured other pointerId. + if (m_capturePointerId && + m_capturePointerId != pi.pointerId) { + return 0; + } + + MOUSE_TRACE("POINTERENTER id=%d xy=%d,%d\n", + pi.pointerId, ev.position().x, ev.position().y); + +#if USE_EnableMouseInPointer == 0 + // This is necessary to avoid receiving random WM_MOUSEMOVE from + // the mouse position when we use Alt+pen tip. + // TODO Remove this line when we enable EnableMouseInPointer(TRUE); + m_ignoreMouseMessages = true; +#endif + + if (!m_hasMouse) { + m_hasMouse = true; + + ev.setType(Event::MouseEnter); + queueEvent(ev); + + MOUSE_TRACE("-> Event::MouseEnter\n"); + } + return 0; + } + + case WM_POINTERLEAVE: { + UINT32 pointerId = GET_POINTERID_WPARAM(wparam); + + // Ignore this message because we have captured other pointerId. + if (m_capturePointerId && + m_capturePointerId != pointerId) { + return 0; + } + + MOUSE_TRACE("POINTERLEAVE id=%d\n", pointerId); + +#if USE_EnableMouseInPointer == 0 + m_ignoreMouseMessages = false; +#endif + + if (m_hasMouse) { + m_hasMouse = false; + + Event ev; + ev.setType(Event::MouseLeave); + ev.setModifiers(get_modifiers_from_last_win32_message()); + queueEvent(ev); + + MOUSE_TRACE("-> Event::MouseLeave\n"); + return 0; + } + break; + } + + case WM_POINTERDOWN: { + POINTER_INFO pi; + Event ev; + if (!pointerEvent(wparam, lparam, ev, pi)) + break; + + // Ignore this message because we have captured other pointerId. + if (m_capturePointerId && + m_capturePointerId != pi.pointerId) { + return 0; + } + + ev.setType(Event::MouseDown); + ev.setButton( + pi.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_DOWN ? Event::LeftButton: + pi.ButtonChangeType == POINTER_CHANGE_SECONDBUTTON_DOWN ? Event::RightButton: + pi.ButtonChangeType == POINTER_CHANGE_THIRDBUTTON_DOWN ? Event::MiddleButton: + pi.ButtonChangeType == POINTER_CHANGE_FOURTHBUTTON_DOWN ? Event::X1Button: + pi.ButtonChangeType == POINTER_CHANGE_FIFTHBUTTON_DOWN ? Event::X2Button: + Event::NoneButton); + queueEvent(ev); + + MOUSE_TRACE("POINTERDOWN id=%d xy=%d,%d button=%d\n", + pi.pointerId, ev.position().x, ev.position().y, + ev.button()); + return 0; + } + + case WM_POINTERUP: { + POINTER_INFO pi; + Event ev; + if (!pointerEvent(wparam, lparam, ev, pi)) + break; + + // Ignore this message because we have captured other pointerId. + if (m_capturePointerId && + m_capturePointerId != pi.pointerId) { + return 0; + } + + ev.setType(Event::MouseUp); + ev.setButton( + pi.ButtonChangeType == POINTER_CHANGE_FIRSTBUTTON_UP ? Event::LeftButton: + pi.ButtonChangeType == POINTER_CHANGE_SECONDBUTTON_UP ? Event::RightButton: + pi.ButtonChangeType == POINTER_CHANGE_THIRDBUTTON_UP ? Event::MiddleButton: + pi.ButtonChangeType == POINTER_CHANGE_FOURTHBUTTON_UP ? Event::X1Button: + pi.ButtonChangeType == POINTER_CHANGE_FIFTHBUTTON_UP ? Event::X2Button: + Event::NoneButton); + queueEvent(ev); + + MOUSE_TRACE("POINTERUP id=%d xy=%d,%d button=%d\n", + pi.pointerId, ev.position().x, ev.position().y, + ev.button()); + return 0; + } + + case WM_POINTERUPDATE: { + POINTER_INFO pi; + Event ev; + if (!pointerEvent(wparam, lparam, ev, pi)) + break; + + // Ignore this message because we have captured other pointerId. + if (m_capturePointerId && + m_capturePointerId != pi.pointerId) { + return 0; + } + + if (!m_hasMouse) { + m_hasMouse = true; + + ev.setType(Event::MouseEnter); + queueEvent(ev); + + MOUSE_TRACE("-> Event::MouseEnter\n"); + } + + ev.setType(Event::MouseMove); + queueEvent(ev); + + MOUSE_TRACE("POINTERUPDATE id=%d xy=%d,%d\n", + pi.pointerId, ev.position().x, ev.position().y); + return 0; + } + + case WM_POINTERWHEEL: + case WM_POINTERHWHEEL: { + POINTER_INFO pi; + Event ev; + if (!pointerEvent(wparam, lparam, ev, pi)) + break; + + // Ignore this message because we have captured other pointerId. + if (m_capturePointerId && + m_capturePointerId != pi.pointerId) { + return 0; + } + + ev.setType(Event::MouseWheel); + + int z = GET_WHEEL_DELTA_WPARAM(wparam); + if (ABS(z) >= WHEEL_DELTA) + z /= WHEEL_DELTA; + else { + // TODO use floating point numbers or something similar + // (so we could use: z /= double(WHEEL_DELTA)) + z = SGN(z); + } + + gfx::Point delta( + (msg == WM_POINTERHWHEEL ? z: 0), + (msg == WM_POINTERWHEEL ? -z: 0)); + ev.setWheelDelta(delta); + queueEvent(ev); + + MOUSE_TRACE("POINTERWHEEL xy=%d,%d delta=%d,%d\n", + ev.position().x, ev.position().y, + ev.wheelDelta().x, ev.wheelDelta().y); + + return 0; + } + + // Keyboard Messages + case WM_SYSKEYDOWN: case WM_KEYDOWN: { int vk = wparam; @@ -749,11 +994,10 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_NCHITTEST: { LRESULT result = CallWindowProc(DefWindowProc, m_hwnd, msg, wparam, lparam); - gfx::Point pt(GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)); + gfx::Point pt(GET_X_LPARAM(lparam), + GET_Y_LPARAM(lparam)); - RECT rc; - GetClientRect(m_hwnd, &rc); - MapWindowPoints(m_hwnd, NULL, (POINT*)&rc, 2); + ABS_CLIENT_RC(rc); gfx::Rect area(rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top); //LOG("NCHITTEST: %d %d - %d %d %d %d - %s\n", pt.x, pt.y, area.x, area.y, area.w, area.h, area.contains(pt) ? "true": "false"); @@ -772,6 +1016,8 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) return result; } + // Wintab API Messages + case WT_PROXIMITY: { bool entering_ctx = (LOWORD(lparam) ? true: false); if (!entering_ctx) @@ -836,6 +1082,56 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) return DefWindowProc(m_hwnd, msg, wparam, lparam); } +void WinWindow::mouseEvent(LPARAM lparam, Event& ev) +{ + ev.setModifiers(get_modifiers_from_last_win32_message()); + ev.setPosition(gfx::Point( + GET_X_LPARAM(lparam) / m_scale, + GET_Y_LPARAM(lparam) / m_scale)); +} + +bool WinWindow::pointerEvent(WPARAM wparam, LPARAM lparam, + Event& ev, POINTER_INFO& pi) +{ + if (!m_usePointerApi) + return false; + + auto& winApi = system()->winApi(); + if (!winApi.GetPointerInfo(GET_POINTERID_WPARAM(wparam), &pi)) + return false; + + ABS_CLIENT_RC(rc); + + ev.setModifiers(get_modifiers_from_last_win32_message()); + ev.setPosition(gfx::Point((pi.ptPixelLocation.x - rc.left) / m_scale, + (pi.ptPixelLocation.y - rc.top) / m_scale)); + + switch (pi.pointerType) { + case PT_MOUSE: { + ev.setPointerType(PointerType::Mouse); + break; + } + case PT_TOUCH: + case PT_TOUCHPAD: { + ev.setPointerType(PointerType::Multitouch); + break; + } + case PT_PEN: { + ev.setPointerType(PointerType::Pen); + + POINTER_PEN_INFO ppi; + if (winApi.GetPointerPenInfo(pi.pointerId, &ppi)) { + if (ppi.penFlags & PEN_FLAG_ERASER) + ev.setPointerType(PointerType::Eraser); + } + break; + } + } + + m_lastPointerId = pi.pointerId; + return true; +} + //static void WinWindow::registerClass() { diff --git a/src/she/win/window.h b/src/she/win/window.h index 8a2aabee3..3332acd78 100644 --- a/src/she/win/window.h +++ b/src/she/win/window.h @@ -54,6 +54,9 @@ namespace she { private: bool setCursor(HCURSOR hcursor, bool custom); LRESULT wndProc(UINT msg, WPARAM wparam, LPARAM lparam); + void mouseEvent(LPARAM lparam, Event& ev); + bool pointerEvent(WPARAM wparam, LPARAM lparam, + Event& ev, POINTER_INFO& pi); virtual void onQueueEvent(Event& ev) { } virtual void onResize(const gfx::Size& sz) { } @@ -76,6 +79,12 @@ namespace she { bool m_captureMouse; bool m_customHcursor; + // Windows 8 pointer API + bool m_usePointerApi; + bool m_ignoreMouseMessages; + UINT32 m_lastPointerId; + UINT32 m_capturePointerId; + // Wintab API data HCTX m_hpenctx; PointerType m_pointerType; From 6cb0c2c315817e39f47551580710e664152fe21e Mon Sep 17 00:00:00 2001 From: David Capello Date: Thu, 2 Nov 2017 21:19:28 -0300 Subject: [PATCH 2/2] Use an InteractionContext to interpret touch gestures --- src/she/event.h | 1 - src/she/osx/view.mm | 4 +- src/she/pointer_type.h | 5 +- src/she/win/winapi.cpp | 25 +++++ src/she/win/winapi.h | 40 ++++++++ src/she/win/window.cpp | 228 ++++++++++++++++++++++++++++++++--------- src/she/win/window.h | 13 ++- 7 files changed, 261 insertions(+), 55 deletions(-) diff --git a/src/she/event.h b/src/she/event.h index 3131c6493..64d049932 100644 --- a/src/she/event.h +++ b/src/she/event.h @@ -86,7 +86,6 @@ namespace she { // We suppose that if we are receiving precise scrolling deltas, // it means that the user is using a touch-like surface (trackpad, // magic mouse scrolling, touch wacom tablet, etc.) - // TODO change this with the new PointerType::Multitouch bool preciseWheel() const { return m_preciseWheel; } PointerType pointerType() const { return m_pointerType; } diff --git a/src/she/osx/view.mm b/src/she/osx/view.mm index 27012fa48..be47edf59 100644 --- a/src/she/osx/view.mm +++ b/src/she/osx/view.mm @@ -454,7 +454,7 @@ using namespace she; scale = [(OSXWindow*)self.window scale]; if (event.hasPreciseScrollingDeltas) { - ev.setPointerType(she::PointerType::Multitouch); + ev.setPointerType(she::PointerType::Touchpad); ev.setWheelDelta(gfx::Point(-event.scrollingDeltaX / scale, -event.scrollingDeltaY / scale)); ev.setPreciseWheel(true); @@ -485,7 +485,7 @@ using namespace she; ev.setMagnification(event.magnification); ev.setPosition(get_local_mouse_pos(self, event)); ev.setModifiers(get_modifiers_from_nsevent(event)); - ev.setPointerType(she::PointerType::Multitouch); + ev.setPointerType(she::PointerType::Touchpad); queue_event(ev); } diff --git a/src/she/pointer_type.h b/src/she/pointer_type.h index ad42d00ff..6ed61426e 100644 --- a/src/she/pointer_type.h +++ b/src/she/pointer_type.h @@ -1,5 +1,5 @@ // SHE library -// Copyright (C) 2016 David Capello +// Copyright (C) 2016-2017 David Capello // // This file is released under the terms of the MIT license. // Read LICENSE.txt for more information. @@ -14,7 +14,8 @@ namespace she { enum class PointerType { Unknown, Mouse, // A regular mouse - Multitouch, // Trackpad/multitouch surface + Touchpad, // Touchpad/trackpad + Touch, // Touch screen Pen, // Stylus pen Cursor, // Puck like device Eraser // Eraser end of a stylus pen diff --git a/src/she/win/winapi.cpp b/src/she/win/winapi.cpp index 8738afaf9..659fb5a69 100644 --- a/src/she/win/winapi.cpp +++ b/src/she/win/winapi.cpp @@ -20,15 +20,36 @@ WinAPI::WinAPI() , IsMouseInPointerEnabled(nullptr) , GetPointerInfo(nullptr) , GetPointerPenInfo(nullptr) + , CreateInteractionContext(nullptr) + , DestroyInteractionContext(nullptr) + , StopInteractionContext(nullptr) + , RegisterOutputCallbackInteractionContext(nullptr) + , AddPointerInteractionContext(nullptr) + , RemovePointerInteractionContext(nullptr) + , SetInteractionConfigurationInteractionContext(nullptr) + , ProcessPointerFramesInteractionContext(nullptr) , m_user32(nullptr) + , m_ninput(nullptr) { m_user32 = base::load_dll("user32.dll"); + m_ninput = base::load_dll("ninput.dll"); if (m_user32) { GET_PROC(m_user32, EnableMouseInPointer); GET_PROC(m_user32, IsMouseInPointerEnabled); GET_PROC(m_user32, GetPointerInfo); GET_PROC(m_user32, GetPointerPenInfo); } + if (m_ninput) { + GET_PROC(m_ninput, CreateInteractionContext); + GET_PROC(m_ninput, DestroyInteractionContext); + GET_PROC(m_ninput, StopInteractionContext); + GET_PROC(m_ninput, RegisterOutputCallbackInteractionContext); + GET_PROC(m_ninput, AddPointerInteractionContext); + GET_PROC(m_ninput, RemovePointerInteractionContext); + GET_PROC(m_ninput, SetInteractionConfigurationInteractionContext); + GET_PROC(m_ninput, SetPropertyInteractionContext); + GET_PROC(m_ninput, ProcessPointerFramesInteractionContext); + } } WinAPI::~WinAPI() @@ -37,6 +58,10 @@ WinAPI::~WinAPI() base::unload_dll(m_user32); m_user32 = nullptr; } + if (m_ninput) { + base::unload_dll(m_ninput); + m_ninput = nullptr; + } } } // namespace she diff --git a/src/she/win/winapi.h b/src/she/win/winapi.h index 787fd8ee9..065447c08 100644 --- a/src/she/win/winapi.h +++ b/src/she/win/winapi.h @@ -11,6 +11,7 @@ #include "base/dll.h" #include +#include namespace she { @@ -19,6 +20,33 @@ namespace she { typedef BOOL (WINAPI* GetPointerInfo_Func)(UINT32 pointerId, POINTER_INFO* pointerInfo); typedef BOOL (WINAPI* GetPointerPenInfo_Func)(UINT32 pointerId, POINTER_PEN_INFO* penInfo); + typedef HRESULT (WINAPI* CreateInteractionContext_Func)(HINTERACTIONCONTEXT* interactionContext); + typedef HRESULT (WINAPI* DestroyInteractionContext_Func)(HINTERACTIONCONTEXT interactionContext); + typedef HRESULT (WINAPI* StopInteractionContext_Func)(HINTERACTIONCONTEXT interactionContext); + typedef HRESULT (WINAPI* RegisterOutputCallbackInteractionContext_Func)( + HINTERACTIONCONTEXT interactionContext, + INTERACTION_CONTEXT_OUTPUT_CALLBACK outputCallback, + void* clientData); + typedef HRESULT (WINAPI* AddPointerInteractionContext_Func)( + HINTERACTIONCONTEXT interactionContext, + UINT32 pointerId); + typedef HRESULT (WINAPI* RemovePointerInteractionContext_Func)( + HINTERACTIONCONTEXT interactionContext, + UINT32 pointerId); + typedef HRESULT (WINAPI* SetInteractionConfigurationInteractionContext_Func)( + HINTERACTIONCONTEXT interactionContext, + UINT32 configurationCount, + const INTERACTION_CONTEXT_CONFIGURATION* configuration); + typedef HRESULT (WINAPI* SetPropertyInteractionContext_Func)( + HINTERACTIONCONTEXT interactionContext, + INTERACTION_CONTEXT_PROPERTY contextProperty, + UINT32 value); + typedef HRESULT (WINAPI* ProcessPointerFramesInteractionContext_Func)( + HINTERACTIONCONTEXT interactionContext, + UINT32 entriesCount, + UINT32 pointerCount, + const POINTER_INFO* pointerInfo); + class WinAPI { public: WinAPI(); @@ -30,8 +58,20 @@ namespace she { GetPointerInfo_Func GetPointerInfo; GetPointerPenInfo_Func GetPointerPenInfo; + // InteractionContext introduced on Windows 8 + CreateInteractionContext_Func CreateInteractionContext; + DestroyInteractionContext_Func DestroyInteractionContext; + StopInteractionContext_Func StopInteractionContext; + RegisterOutputCallbackInteractionContext_Func RegisterOutputCallbackInteractionContext; + AddPointerInteractionContext_Func AddPointerInteractionContext; + RemovePointerInteractionContext_Func RemovePointerInteractionContext; + SetInteractionConfigurationInteractionContext_Func SetInteractionConfigurationInteractionContext; + SetPropertyInteractionContext_Func SetPropertyInteractionContext; + ProcessPointerFramesInteractionContext_Func ProcessPointerFramesInteractionContext; + private: base::dll m_user32; + base::dll m_ninput; }; } // namespace she diff --git a/src/she/win/window.cpp b/src/she/win/window.cpp index 141542771..04afb2546 100644 --- a/src/she/win/window.cpp +++ b/src/she/win/window.cpp @@ -40,6 +40,10 @@ // messages. #define USE_EnableMouseInPointer 0 +#ifndef INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN +#define INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN 1 +#endif + namespace she { WinWindow::WinWindow(int width, int height, int scale) @@ -56,7 +60,7 @@ WinWindow::WinWindow(int width, int height, int scale) , m_usePointerApi(false) , m_ignoreMouseMessages(false) , m_lastPointerId(0) - , m_capturePointerId(0) + , m_ictx(nullptr) , m_hpenctx(nullptr) , m_pointerType(PointerType::Unknown) , m_pressure(0.0) @@ -74,6 +78,49 @@ WinWindow::WinWindow(int width, int height, int scale) m_ignoreMouseMessages = (winApi.IsMouseInPointerEnabled() ? true: false); } #endif + + // Initialize a Interaction Context to convert WM_POINTER messages + // into gestures processed by handleInteractionContextOutput(). + if (winApi.CreateInteractionContext && + winApi.RegisterOutputCallbackInteractionContext && + winApi.SetInteractionConfigurationInteractionContext) { + HRESULT hr = winApi.CreateInteractionContext(&m_ictx); + if (SUCCEEDED(hr)) { + hr = winApi.RegisterOutputCallbackInteractionContext( + m_ictx, &WinWindow::staticInteractionContextCallback, this); + } + if (SUCCEEDED(hr)) { + INTERACTION_CONTEXT_CONFIGURATION cfg[] = { + { INTERACTION_ID_MANIPULATION, + INTERACTION_CONFIGURATION_FLAG_MANIPULATION | + INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_X | + INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_Y | + INTERACTION_CONFIGURATION_FLAG_MANIPULATION_SCALING | + INTERACTION_CONFIGURATION_FLAG_MANIPULATION_TRANSLATION_INERTIA | + INTERACTION_CONFIGURATION_FLAG_MANIPULATION_SCALING_INERTIA }, + { INTERACTION_ID_TAP, + INTERACTION_CONFIGURATION_FLAG_TAP | + INTERACTION_CONFIGURATION_FLAG_TAP_DOUBLE }, + { INTERACTION_ID_SECONDARY_TAP, + INTERACTION_CONFIGURATION_FLAG_SECONDARY_TAP }, + { INTERACTION_ID_HOLD, + INTERACTION_CONFIGURATION_FLAG_NONE }, + { INTERACTION_ID_DRAG, + INTERACTION_CONFIGURATION_FLAG_NONE }, + { INTERACTION_ID_CROSS_SLIDE, + INTERACTION_CONFIGURATION_FLAG_NONE } + }; + hr = winApi.SetInteractionConfigurationInteractionContext( + m_ictx, sizeof(cfg) / sizeof(INTERACTION_CONTEXT_CONFIGURATION), cfg); + } + if (SUCCEEDED(hr)) { + hr = winApi.SetPropertyInteractionContext( + m_ictx, + INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS, + INTERACTION_CONTEXT_PROPERTY_MEASUREMENT_UNITS_SCREEN); + } + } + m_usePointerApi = true; } @@ -96,6 +143,10 @@ WinWindow::WinWindow(int width, int height, int scale) WinWindow::~WinWindow() { + auto& winApi = system()->winApi(); + if (m_ictx && winApi.DestroyInteractionContext) + winApi.DestroyInteractionContext(m_ictx); + if (m_hwnd) DestroyWindow(m_hwnd); } @@ -155,7 +206,6 @@ void WinWindow::setTitle(const std::string& title) void WinWindow::captureMouse() { m_captureMouse = true; - m_capturePointerId = m_lastPointerId; if (GetCapture() != m_hwnd) { MOUSE_TRACE("SetCapture\n"); @@ -166,7 +216,6 @@ void WinWindow::captureMouse() void WinWindow::releaseMouse() { m_captureMouse = false; - m_capturePointerId = 0; if (GetCapture() == m_hwnd) { MOUSE_TRACE("ReleaseCapture\n"); @@ -706,22 +755,16 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_POINTERCAPTURECHANGED: { MOUSE_TRACE("POINTERCAPTURECHANGED\n"); - m_capturePointerId = 0; + releaseMouse(); break; } case WM_POINTERENTER: { POINTER_INFO pi; Event ev; - if (!pointerEvent(wparam, lparam, ev, pi)) + if (!pointerEvent(wparam, ev, pi)) break; - // Ignore this message because we have captured other pointerId. - if (m_capturePointerId && - m_capturePointerId != pi.pointerId) { - return 0; - } - MOUSE_TRACE("POINTERENTER id=%d xy=%d,%d\n", pi.pointerId, ev.position().x, ev.position().y); @@ -732,6 +775,12 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) m_ignoreMouseMessages = true; #endif + if (pi.pointerType == PT_TOUCH) { + auto& winApi = system()->winApi(); + if (m_ictx && winApi.AddPointerInteractionContext) + winApi.AddPointerInteractionContext(m_ictx, pi.pointerId); + } + if (!m_hasMouse) { m_hasMouse = true; @@ -744,44 +793,51 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) } case WM_POINTERLEAVE: { - UINT32 pointerId = GET_POINTERID_WPARAM(wparam); + POINTER_INFO pi; + Event ev; + if (!pointerEvent(wparam, ev, pi)) + break; - // Ignore this message because we have captured other pointerId. - if (m_capturePointerId && - m_capturePointerId != pointerId) { - return 0; - } - - MOUSE_TRACE("POINTERLEAVE id=%d\n", pointerId); + MOUSE_TRACE("POINTERLEAVE id=%d\n", pi.pointerId); #if USE_EnableMouseInPointer == 0 m_ignoreMouseMessages = false; #endif + if (pi.pointerType == PT_TOUCH) { + auto& winApi = system()->winApi(); + if (m_ictx && winApi.RemovePointerInteractionContext) + winApi.RemovePointerInteractionContext(m_ictx, pi.pointerId); + } + +#if 0 // Don't generate MouseLeave from pen/touch messages + // TODO we should generate this message, but after this touch + // messages don't work anymore, so we have to fix that problem. if (m_hasMouse) { m_hasMouse = false; - Event ev; ev.setType(Event::MouseLeave); - ev.setModifiers(get_modifiers_from_last_win32_message()); queueEvent(ev); MOUSE_TRACE("-> Event::MouseLeave\n"); return 0; } +#endif break; } case WM_POINTERDOWN: { POINTER_INFO pi; Event ev; - if (!pointerEvent(wparam, lparam, ev, pi)) + if (!pointerEvent(wparam, ev, pi)) break; - // Ignore this message because we have captured other pointerId. - if (m_capturePointerId && - m_capturePointerId != pi.pointerId) { - return 0; + if (pi.pointerType == PT_TOUCH) { + auto& winApi = system()->winApi(); + if (m_ictx && winApi.ProcessPointerFramesInteractionContext) { + winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi); + return 0; + } } ev.setType(Event::MouseDown); @@ -803,13 +859,15 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_POINTERUP: { POINTER_INFO pi; Event ev; - if (!pointerEvent(wparam, lparam, ev, pi)) + if (!pointerEvent(wparam, ev, pi)) break; - // Ignore this message because we have captured other pointerId. - if (m_capturePointerId && - m_capturePointerId != pi.pointerId) { - return 0; + if (pi.pointerType == PT_TOUCH) { + auto& winApi = system()->winApi(); + if (m_ictx && winApi.ProcessPointerFramesInteractionContext) { + winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi); + return 0; + } } ev.setType(Event::MouseUp); @@ -831,13 +889,15 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_POINTERUPDATE: { POINTER_INFO pi; Event ev; - if (!pointerEvent(wparam, lparam, ev, pi)) + if (!pointerEvent(wparam, ev, pi)) break; - // Ignore this message because we have captured other pointerId. - if (m_capturePointerId && - m_capturePointerId != pi.pointerId) { - return 0; + if (pi.pointerType == PT_TOUCH) { + auto& winApi = system()->winApi(); + if (m_ictx && winApi.ProcessPointerFramesInteractionContext) { + winApi.ProcessPointerFramesInteractionContext(m_ictx, 1, 1, &pi); + return 0; + } } if (!m_hasMouse) { @@ -861,15 +921,9 @@ LRESULT WinWindow::wndProc(UINT msg, WPARAM wparam, LPARAM lparam) case WM_POINTERHWHEEL: { POINTER_INFO pi; Event ev; - if (!pointerEvent(wparam, lparam, ev, pi)) + if (!pointerEvent(wparam, ev, pi)) break; - // Ignore this message because we have captured other pointerId. - if (m_capturePointerId && - m_capturePointerId != pi.pointerId) { - return 0; - } - ev.setType(Event::MouseWheel); int z = GET_WHEEL_DELTA_WPARAM(wparam); @@ -1090,8 +1144,7 @@ void WinWindow::mouseEvent(LPARAM lparam, Event& ev) GET_Y_LPARAM(lparam) / m_scale)); } -bool WinWindow::pointerEvent(WPARAM wparam, LPARAM lparam, - Event& ev, POINTER_INFO& pi) +bool WinWindow::pointerEvent(WPARAM wparam, Event& ev, POINTER_INFO& pi) { if (!m_usePointerApi) return false; @@ -1111,9 +1164,12 @@ bool WinWindow::pointerEvent(WPARAM wparam, LPARAM lparam, ev.setPointerType(PointerType::Mouse); break; } - case PT_TOUCH: + case PT_TOUCH: { + ev.setPointerType(PointerType::Touch); + break; + } case PT_TOUCHPAD: { - ev.setPointerType(PointerType::Multitouch); + ev.setPointerType(PointerType::Touchpad); break; } case PT_PEN: { @@ -1132,6 +1188,75 @@ bool WinWindow::pointerEvent(WPARAM wparam, LPARAM lparam, return true; } +void WinWindow::handleInteractionContextOutput( + const INTERACTION_CONTEXT_OUTPUT* output) +{ + MOUSE_TRACE("%s (%d) xy=%.16g %.16g flags=%d type=%d\n", + output->interactionId == INTERACTION_ID_MANIPULATION ? "INTERACTION_ID_MANIPULATION": + output->interactionId == INTERACTION_ID_TAP ? "INTERACTION_ID_TAP": + output->interactionId == INTERACTION_ID_SECONDARY_TAP ? "INTERACTION_ID_SECONDARY_TAP": + output->interactionId == INTERACTION_ID_HOLD ? "INTERACTION_ID_HOLD": "INTERACTION_ID_???", + output->interactionId, + output->x, output->y, + output->interactionFlags, + output->inputType); + + // We use the InteractionContext to interpret touch gestures only. + if (output->inputType == PT_TOUCH) { + ABS_CLIENT_RC(rc); + + gfx::Point pos(int((output->x - rc.left) / m_scale), + int((output->y - rc.top) / m_scale)); + + Event ev; + ev.setModifiers(get_modifiers_from_last_win32_message()); + ev.setPosition(pos); + + switch (output->interactionId) { + case INTERACTION_ID_MANIPULATION: { + MOUSE_TRACE(" - delta xy=%.16g %.16g scale=%.16g expansion=%.16g rotation=%.16g\n", + output->arguments.manipulation.delta.translationX, + output->arguments.manipulation.delta.translationY, + output->arguments.manipulation.delta.scale, + output->arguments.manipulation.delta.expansion, + output->arguments.manipulation.delta.rotation); + + gfx::Point delta(-int(output->arguments.manipulation.delta.translationX) / m_scale, + -int(output->arguments.manipulation.delta.translationY) / m_scale); + + ev.setType(Event::MouseWheel); + ev.setWheelDelta(delta); + ev.setPreciseWheel(true); + queueEvent(ev); + + ev.setType(Event::TouchMagnify); + ev.setMagnification(output->arguments.manipulation.delta.scale - 1.0); + queueEvent(ev); + break; + } + + case INTERACTION_ID_TAP: + MOUSE_TRACE(" - count=%d\n", output->arguments.tap.count); + ev.setButton(Event::LeftButton); + if (output->arguments.tap.count == 2) { + ev.setType(Event::MouseDoubleClick); queueEvent(ev); + } + else { + ev.setType(Event::MouseDown); queueEvent(ev); + ev.setType(Event::MouseUp); queueEvent(ev); + } + break; + + case INTERACTION_ID_SECONDARY_TAP: + case INTERACTION_ID_HOLD: + ev.setButton(Event::RightButton); + ev.setType(Event::MouseDown); queueEvent(ev); + ev.setType(Event::MouseUp); queueEvent(ev); + break; + } + } +} + //static void WinWindow::registerClass() { @@ -1233,6 +1358,15 @@ LRESULT CALLBACK WinWindow::staticWndProc(HWND hwnd, UINT msg, WPARAM wparam, LP } } +//static +void CALLBACK WinWindow::staticInteractionContextCallback( + void* clientData, + const INTERACTION_CONTEXT_OUTPUT* output) +{ + WinWindow* self = reinterpret_cast(clientData); + self->handleInteractionContextOutput(output); +} + // static WindowSystem* WinWindow::system() { diff --git a/src/she/win/window.h b/src/she/win/window.h index 3332acd78..076c93483 100644 --- a/src/she/win/window.h +++ b/src/she/win/window.h @@ -15,6 +15,7 @@ #include #include +#include namespace she { class Event; @@ -55,8 +56,9 @@ namespace she { bool setCursor(HCURSOR hcursor, bool custom); LRESULT wndProc(UINT msg, WPARAM wparam, LPARAM lparam); void mouseEvent(LPARAM lparam, Event& ev); - bool pointerEvent(WPARAM wparam, LPARAM lparam, - Event& ev, POINTER_INFO& pi); + bool pointerEvent(WPARAM wparam, Event& ev, POINTER_INFO& pi); + void handleInteractionContextOutput( + const INTERACTION_CONTEXT_OUTPUT* output); virtual void onQueueEvent(Event& ev) { } virtual void onResize(const gfx::Size& sz) { } @@ -64,7 +66,11 @@ namespace she { static void registerClass(); static HWND createHwnd(WinWindow* self, int width, int height); - static LRESULT CALLBACK staticWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + static LRESULT CALLBACK staticWndProc( + HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); + static void CALLBACK staticInteractionContextCallback( + void* clientData, + const INTERACTION_CONTEXT_OUTPUT* output); static WindowSystem* system(); @@ -84,6 +90,7 @@ namespace she { bool m_ignoreMouseMessages; UINT32 m_lastPointerId; UINT32 m_capturePointerId; + HINTERACTIONCONTEXT m_ictx; // Wintab API data HCTX m_hpenctx;