spicetools/touch/win8.cpp

317 lines
10 KiB
C++

// set version to Windows 8 to enable Windows 8 touch functions
#define _WIN32_WINNT 0x0602
#include <thread>
#include <mutex>
#include <initguid.h>
#include <windowsx.h>
#include <propsys.h>
#include "win8.h"
#include "util/libutils.h"
#include "util/logging.h"
#include "rawinput/touch.h"
// mingw does not seem to have this either
DEFINE_GUID(PKEY_EdgeGesture_DisableTouchWhenFullscreen_Format, 0x32CE38B2, 0x2C9A, 0x41B1, 0x9B, 0xC5, 0xB3, 0x78, 0x43, 0x94, 0xAA, 0x44);
// mingw issue #2205 workaround
// https://sourceforge.net/p/mingw/bugs/2205/
#undef TOUCHEVENTF_MOVE
#define TOUCHEVENTF_MOVE 0x0001
#undef TOUCHEVENTF_DOWN
#define TOUCHEVENTF_DOWN 0x0002
// mingw doesn't seem to have these
#ifndef WM_TABLET_DEFBASE
#define WM_TABLET_DEFBASE 0x02C0
#define WM_TABLET_QUERYSYSTEMGESTURESTATUS (WM_TABLET_DEFBASE + 12)
#define TABLET_DISABLE_PRESSANDHOLD 0x00000001
#define TABLET_DISABLE_PENTAPFEEDBACK 0x00000008
#define TABLET_DISABLE_PENBARRELFEEDBACK 0x00000010
#define TABLET_DISABLE_TOUCHUIFORCEON 0x00000100
#define TABLET_DISABLE_TOUCHUIFORCEOFF 0x00000200
#define TABLET_DISABLE_TOUCHSWITCH 0x00008000
#define TABLET_DISABLE_FLICKS 0x00010000
#define TABLET_DISABLE_SMOOTHSCROLLING 0x00080000
#define TABLET_DISABLE_FLICKFALLBACKKEYS 0x00100000
#define TABLET_ENABLE_MULTITOUCHDATA 0x01000000
#endif
// general states
static HINSTANCE USER32_INSTANCE = nullptr;
static HINSTANCE SHELL32_INSTANCE = nullptr;
const char *LOG_MODULE_NAME = "touch::win8";
/*
* dynamic touch functions
* to maintain compatibility with windows XP
*/
typedef BOOL (WINAPI *GetPointerFrameInfoHistory_t)(UINT32, UINT32 *, UINT32 *, POINTER_INFO *);
typedef BOOL (WINAPI *SkipPointerFrameMessages_t)(UINT32);
typedef HRESULT (WINAPI *SHGetPropertyStoreForWindow_t)(HWND, REFIID, void **);
static GetPointerFrameInfoHistory_t pGetPointerFrameInfoHistory = nullptr;
static SkipPointerFrameMessages_t pSkipPointerFrameMessages = nullptr;
static SHGetPropertyStoreForWindow_t pSHGetPropertyStoreForWindow = nullptr;
// other
static const char ATOM_NAME[] = "MicrosoftTabletPenServiceProperty";
static void load_functions() {
// check if already loaded
static bool functions_loaded = false;
if (functions_loaded) {
return;
}
// load libraries
if (USER32_INSTANCE == nullptr) {
USER32_INSTANCE = libutils::load_library("user32.dll");
}
if (SHELL32_INSTANCE == nullptr) {
SHELL32_INSTANCE = libutils::try_library("shell32.dll");
}
// load touch functions
pGetPointerFrameInfoHistory = libutils::try_proc<GetPointerFrameInfoHistory_t>(
USER32_INSTANCE, "GetPointerFrameInfoHistory");
pSkipPointerFrameMessages = libutils::try_proc<SkipPointerFrameMessages_t>(
USER32_INSTANCE, "SkipPointerFrameMessages");
if (SHELL32_INSTANCE != nullptr) {
pSHGetPropertyStoreForWindow = libutils::try_proc<SHGetPropertyStoreForWindow_t>(
SHELL32_INSTANCE, "SHGetPropertyStoreForWindow");
}
functions_loaded = true;
}
Win8Handler::Win8Handler() : TouchHandler("win8") {
load_functions();
}
bool Win8Handler::is_available() {
bool have_digitizers = (GetSystemMetrics(94) & 0x80) == 0x80;
load_functions();
if (pGetPointerFrameInfoHistory != nullptr) {
if (have_digitizers) {
log_info(LOG_MODULE_NAME, "Pointer API available");
} else {
log_info(LOG_MODULE_NAME, "Pointer API available, but no touch screens active");
}
} else {
log_info(LOG_MODULE_NAME, "Pointer API unavailable");
}
return have_digitizers && pGetPointerFrameInfoHistory != nullptr;
}
bool Win8Handler::window_register(HWND hWnd) {
// atom settings
DWORD dwHwndTabletProperty = TABLET_DISABLE_PRESSANDHOLD |
TABLET_DISABLE_PENTAPFEEDBACK |
TABLET_DISABLE_PENBARRELFEEDBACK |
TABLET_DISABLE_FLICKS;
// get atom ID
ATOM atomID = GlobalAddAtom(ATOM_NAME);
// disable gestures
if (atomID > 0) {
SetProp(hWnd, ATOM_NAME, (HANDLE) ((unsigned long long) dwHwndTabletProperty));
}
IPropertyStore *ps = nullptr;
HRESULT hr = pSHGetPropertyStoreForWindow(hWnd, IID_IPropertyStore, (void **) &ps);
if (SUCCEEDED(hr) && ps != nullptr) {
PROPERTYKEY PKEY_EdgeGesture_DisableTouchWhenFullscreen = {
PKEY_EdgeGesture_DisableTouchWhenFullscreen_Format,
2,
};
PROPVARIANT var {};
var.vt = VT_BOOL;
var.boolVal = VARIANT_TRUE;
hr = ps->SetValue(PKEY_EdgeGesture_DisableTouchWhenFullscreen, var);
ps->Release();
if (FAILED(hr)) {
log_warning(LOG_MODULE_NAME, "Failed to disable edge gestures on window");
}
}
return true;
}
bool Win8Handler::window_unregister(HWND hWnd) {
return true;
}
void Win8Handler::handle_message(msg_handler_result &result, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
// parse touch messages
switch (msg) {
case WM_POINTERACTIVATE: {
result.action = ACTION_RETURN_STORED;
result.return_value = PA_ACTIVATE;
break;
}
case WM_NCPOINTERUPDATE:
case WM_NCPOINTERDOWN:
case WM_NCPOINTERUP:
case WM_POINTERUPDATE:
case WM_POINTERDOWN:
case WM_POINTERUP: {
UINT entries_count = 0, pointer_count = 0;
if (pGetPointerFrameInfoHistory == nullptr) {
break;
}
if (!pGetPointerFrameInfoHistory(GET_POINTERID_WPARAM(wParam), &entries_count, &pointer_count, NULL)) {
break;
}
if (entries_count * pointer_count == 0) {
break;
}
auto pointer_info = std::make_unique<POINTER_INFO[]>(entries_count * pointer_count);
if (!pGetPointerFrameInfoHistory(GET_POINTERID_WPARAM(wParam), &entries_count, &pointer_count, pointer_info.get())) {
break;
}
// lock touch points
std::lock_guard<std::mutex> lock_points(TOUCH_POINTS_M);
std::lock_guard<std::mutex> lock_events(TOUCH_EVENTS_M);
// iterate all inputs
static long prev_x, prev_y;
for (size_t i = 0; i < entries_count * pointer_count; i++) {
auto &pi = pointer_info[i];
if (pi.pointerFlags & POINTER_FLAG_DOWN) {
// convert to window position
POINT point = pi.ptPixelLocation;
ScreenToClient(hWnd, &point);
// create new touch point
TouchPoint tp {
.id = pi.pointerId,
.x = point.x,
.y = point.y,
.mouse = false,
};
TOUCH_POINTS.push_back(tp);
// add touch down event
TouchEvent te {
.id = tp.id,
.x = tp.x,
.y = tp.y,
.type = TOUCH_DOWN,
.mouse = tp.mouse,
};
add_touch_event(&te);
// set prev coordinates
prev_x = point.x;
prev_y = point.y;
}
if ((pi.pointerFlags & POINTER_FLAG_UPDATE) != 0) {
// convert to window position
POINT point = pi.ptPixelLocation;
ScreenToClient(hWnd, &point);
// check prev coordinates
if (point.x != prev_x || point.y != prev_y) {
// update point
for (auto &tp : TOUCH_POINTS) {
if (tp.id == pi.pointerId) {
// update values
tp.x = point.x;
tp.y = point.y;
// add touch move event
TouchEvent te {
.id = tp.id,
.x = tp.x,
.y = tp.y,
.type = TOUCH_MOVE,
.mouse = tp.mouse,
};
add_touch_event(&te);
break;
}
}
}
// set prev coordinates
prev_x = point.x;
prev_y = point.y;
}
if ((pi.pointerFlags & POINTER_FLAG_UP) != 0) {
// remove point
for (size_t x = 0; x < TOUCH_POINTS.size(); x++) {
auto &tp = TOUCH_POINTS[x];
if (tp.id == pi.pointerId) {
// add touch up event
TouchEvent te {
.id = pi.pointerId,
.x = tp.x,
.y = tp.y,
.type = TOUCH_UP,
.mouse = tp.mouse,
};
add_touch_event(&te);
// remove from active touch points
TOUCH_POINTS.erase(TOUCH_POINTS.begin() + x);
break;
}
}
}
}
if (pSkipPointerFrameMessages != nullptr) {
pSkipPointerFrameMessages(GET_POINTERID_WPARAM(wParam));
}
// card button
update_card_button();
result.action = ACTION_RETURN_STORED;
result.return_value = 0;
break;
}
default:
break;
}
}