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.)
This commit is contained in:
David Capello 2017-11-01 22:42:30 -03:00
parent 7c4f811fc7
commit 8c9b8910c1
8 changed files with 437 additions and 47 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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;
};

42
src/she/win/winapi.cpp Normal file
View File

@ -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<name##_Func>(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

39
src/she/win/winapi.h Normal file
View File

@ -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 <windows.h>
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

View File

@ -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()
{

View File

@ -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;