Simplified the Windows touch implementation

This commit is contained in:
Mario Liebisch 2022-10-25 10:19:15 +02:00
parent 3e1031e3d4
commit d8dd4b9bf9
No known key found for this signature in database
GPG Key ID: 32B56AC1F87EFBF9
3 changed files with 123 additions and 226 deletions

View File

@ -226,7 +226,7 @@ void InputImpl::setMousePosition(const Vector2i& position)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
void InputImpl::setMousePosition(const Vector2i& position, const WindowBase& relativeTo) void InputImpl::setMousePosition(const Vector2i& position, const WindowBase& relativeTo)
{ {
WindowHandle handle = relativeTo.getSystemHandle(); const WindowHandle handle = relativeTo.getSystemHandle();
if (handle) if (handle)
{ {
POINT point = {position.x, position.y}; POINT point = {position.x, position.y};
@ -253,13 +253,13 @@ Vector2i InputImpl::getTouchPosition(unsigned int finger)
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
Vector2i InputImpl::getTouchPosition(unsigned int finger, const WindowBase& relativeTo) Vector2i InputImpl::getTouchPosition(unsigned int finger, const WindowBase& relativeTo)
{ {
WindowHandle handle = relativeTo.getSystemHandle(); const WindowHandle handle = relativeTo.getSystemHandle();
Vector2i pos; Vector2i pos;
if (handle && WindowImplWin32::isTouchDown(finger)) if (handle && WindowImplWin32::isTouchDown(finger))
{ {
pos = WindowImplWin32::getTouchPosition(finger); pos = WindowImplWin32::getTouchPosition(finger);
POINT point = { pos.x, pos.y }; POINT point = {pos.x, pos.y};
ScreenToClient(handle, &point); ScreenToClient(handle, &point);
pos.x = point.x; pos.x = point.x;
pos.y = point.y; pos.y = point.y;

View File

@ -56,68 +56,28 @@
namespace namespace
{ {
constexpr int maxTouchPoints = 10; // Current Windows API limitation
// Define constants for tablet API to avoid pulling in an extra Tablet PC header
const char* tabletServiceProperty = "MicrosoftTabletPenServiceProperty";
const unsigned long long tabletDisablePressAndHold = 0x00000001ULL;
unsigned int windowCount = 0; // Windows owned by SFML unsigned int windowCount = 0; // Windows owned by SFML
unsigned int handleCount = 0; // All window handles unsigned int handleCount = 0; // All window handles
const wchar_t* className = L"SFML_Window"; const wchar_t* className = L"SFML_Window";
sf::priv::WindowImplWin32* fullscreenWindow = nullptr; sf::priv::WindowImplWin32* fullscreenWindow = nullptr;
HINSTANCE user32Dll = nullptr; DWORD touchIDs[maxTouchPoints];
DWORD touchIDs[10]; POINT touches[maxTouchPoints];
POINT touches[10];
#if WINVER < 0x0601
// Define touch API that's available for more recent versions of Windows
#define WM_TOUCH 0x0240
DECLARE_HANDLE(HTOUCHINPUT);
typedef struct tagTOUCHINPUT
{
LONG x;
LONG y;
HANDLE hSource;
DWORD dwID;
DWORD dwFlags;
DWORD dwMask;
DWORD dwTime;
ULONG_PTR dwExtraInfo;
DWORD cxContact;
DWORD cyContact;
} TOUCHINPUT, *PTOUCHINPUT;
typedef TOUCHINPUT const * PCTOUCHINPUT;
#define TOUCH_COORD_TO_PIXEL(l) ((l) / 100)
#define TOUCHEVENTF_MOVE 0x0001
#define TOUCHEVENTF_DOWN 0x0002
#define TOUCHEVENTF_UP 0x0004
#define TOUCHEVENTF_INRANGE 0x0008
#define TOUCHEVENTF_PRIMARY 0x0010
#define TOUCHEVENTF_NOCOALESCE 0x0020
#define TOUCHEVENTF_PEN 0x0040
#define TOUCHEVENTF_PALM 0x0080
typedef BOOL(WINAPI* RegisterTouchWindowFuncType)(HWND, ULONG);
typedef BOOL(WINAPI* CloseTouchInputHandleFuncType)(HTOUCHINPUT);
typedef BOOL(WINAPI* GetTouchInputInfoFuncType)(HTOUCHINPUT, UINT, PTOUCHINPUT, int);
RegisterTouchWindowFuncType RegisterTouchWindow = nullptr;
CloseTouchInputHandleFuncType CloseTouchInputHandle = nullptr;
GetTouchInputInfoFuncType GetTouchInputInfo = nullptr;
bool touchEnabled = false;
#else
static const bool touchEnabled = true;
#endif
// Convert a hardware dependent ID to a 0 based index we can use // Convert a hardware dependent ID to a 0 based index we can use
int getTouchID(DWORD id) int getTouchID(DWORD id)
{ {
for (int i = 0; i < 10; ++i) for (int i = 0; i < maxTouchPoints; ++i)
{ {
if (touchIDs[i] == id) if (touchIDs[i] == id)
return i; return i;
} }
for (int i = 0; i < 10; ++i) for (int i = 0; i < maxTouchPoints; ++i)
{ {
if (touchIDs[i] == -1) if (touchIDs[i] == -1)
{ {
@ -142,7 +102,7 @@ std::string getErrorString(DWORD error)
nullptr) == 0) nullptr) == 0)
return "Unknown error."; return "Unknown error.";
sf::String message = buffer; const sf::String message = buffer;
LocalFree(buffer); LocalFree(buffer);
return message.toAnsiString(); return message.toAnsiString();
} }
@ -151,52 +111,17 @@ const GUID GUID_DEVINTERFACE_HID = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x0
void setProcessDpiAware() void setProcessDpiAware()
{ {
// Try SetProcessDpiAwareness first #if 0
HINSTANCE shCoreDll = LoadLibrary(L"Shcore.dll"); // Windows 8.1+
if (SetProcessDpiAwareness(PROCESS_SYSTEM_DPI_AWARE) == E_INVALIDARG)
if (shCoreDll)
{ {
enum ProcessDpiAwareness sf::err() << "Failed to set process DPI awareness" << std::endl;
{
ProcessDpiUnaware = 0,
ProcessSystemDpiAware = 1,
ProcessPerMonitorDpiAware = 2
};
using SetProcessDpiAwarenessFuncType = HRESULT(WINAPI*)(ProcessDpiAwareness);
auto SetProcessDpiAwarenessFunc = reinterpret_cast<SetProcessDpiAwarenessFuncType>(
reinterpret_cast<void*>(GetProcAddress(shCoreDll, "SetProcessDpiAwareness")));
if (SetProcessDpiAwarenessFunc)
{
// We only check for E_INVALIDARG because we would get
// E_ACCESSDENIED if the DPI was already set previously
// and S_OK means the call was successful
if (SetProcessDpiAwarenessFunc(ProcessSystemDpiAware) == E_INVALIDARG)
{
sf::err() << "Failed to set process DPI awareness" << std::endl;
}
else
{
FreeLibrary(shCoreDll);
return;
}
}
FreeLibrary(shCoreDll);
} }
#endif
if (user32Dll) if (!SetProcessDPIAware())
{ {
using SetProcessDPIAwareFuncType = BOOL(WINAPI*)(); sf::err() << "Failed to set process DPI awareness" << std::endl;
auto SetProcessDPIAwareFunc = reinterpret_cast<SetProcessDPIAwareFuncType>(
reinterpret_cast<void*>(GetProcAddress(user32Dll, "SetProcessDPIAware")));
if (SetProcessDPIAwareFunc)
{
if (!SetProcessDPIAwareFunc())
sf::err() << "Failed to set process DPI awareness" << std::endl;
}
} }
} }
} // namespace } // namespace
@ -221,21 +146,16 @@ m_fullscreen(false),
m_cursorGrabbed(false) m_cursorGrabbed(false)
{ {
// If we're the first window handle // If we're the first window handle
if (handleCount == 0)
{
// Ensure User32.dll is loaded
if (!user32Dll)
user32Dll = LoadLibraryA("User32.dll");
// Set that this process is DPI aware and can handle DPI scaling
setProcessDpiAware();
}
if (m_handle) if (m_handle)
{ {
// If we're the first window handle, we only need to poll for joysticks when WM_DEVICECHANGE message is received // If we're the first window handle, set DPI Awareness
// plus we only need to poll for joysticks when WM_DEVICECHANGE message is received
if (handleCount == 0) if (handleCount == 0)
{
setProcessDpiAware();
JoystickImpl::setLazyUpdates(true); JoystickImpl::setLazyUpdates(true);
}
++handleCount; ++handleCount;
@ -265,26 +185,20 @@ m_fullscreen((style & Style::Fullscreen) != 0),
m_cursorGrabbed(m_fullscreen) m_cursorGrabbed(m_fullscreen)
{ {
// If we're the first window handle // If we're the first window handle
// Set that this process is DPI aware and can handle DPI scaling
if (handleCount == 0) if (handleCount == 0)
{
// Ensure User32.dll is loaded
if (!user32Dll)
user32Dll = LoadLibraryA("User32.dll");
// Set that this process is DPI aware and can handle DPI scaling
setProcessDpiAware(); setProcessDpiAware();
}
// Register the window class at first call // Register the window class at first call
if (windowCount == 0) if (windowCount == 0)
registerWindowClass(); registerWindowClass();
// Compute position and size // Compute position and size
HDC screenDC = GetDC(nullptr); const HDC screenDC = GetDC(nullptr);
int left = (GetDeviceCaps(screenDC, HORZRES) - static_cast<int>(mode.size.x)) / 2; const int left = (GetDeviceCaps(screenDC, HORZRES) - static_cast<int>(mode.size.x)) / 2;
int top = (GetDeviceCaps(screenDC, VERTRES) - static_cast<int>(mode.size.y)) / 2; const int top = (GetDeviceCaps(screenDC, VERTRES) - static_cast<int>(mode.size.y)) / 2;
int width = static_cast<int>(mode.size.x); int width = static_cast<int>(mode.size.x);
int height = static_cast<int>(mode.size.y); int height = static_cast<int>(mode.size.y);
ReleaseDC(nullptr, screenDC); ReleaseDC(nullptr, screenDC);
// Choose the window style according to the Style parameter // Choose the window style according to the Style parameter
@ -337,21 +251,21 @@ m_cursorGrabbed(m_fullscreen)
JoystickImpl::setLazyUpdates(true); JoystickImpl::setLazyUpdates(true);
++handleCount; ++handleCount;
// By default, the OS limits the size of the window the the desktop size,
// we have to resize it after creation to apply the real size
setSize(mode.size);
// Switch to fullscreen if requested
if (m_fullscreen)
switchToFullscreen(mode);
// Try to prepare touch events, if necessary
prepareTouch();
// Increment window count
++windowCount;
} }
// By default, the OS limits the size of the window the the desktop size,
// we have to resize it after creation to apply the real size
setSize(mode.size);
// Switch to fullscreen if requested
if (m_fullscreen)
switchToFullscreen(mode);
// Try to prepare touch events, if necessary
prepareTouch();
// Increment window count
++windowCount;
} }
@ -370,17 +284,9 @@ WindowImplWin32::~WindowImplWin32()
--handleCount; --handleCount;
// This was the last handle // This was the last handle
// Reenable automatic joystick polling
if (handleCount == 0) if (handleCount == 0)
{
// Free User32.dll
if (user32Dll)
{
FreeLibrary(user32Dll);
user32Dll = nullptr;
}
// Reenable automatic joystick polling
JoystickImpl::setLazyUpdates(false); JoystickImpl::setLazyUpdates(false);
}
} }
if (!m_callback) if (!m_callback)
@ -895,7 +801,7 @@ void WindowImplWin32::processEvent(UINT message, WPARAM wParam, LPARAM lParam)
position.y = static_cast<std::int16_t>(HIWORD(lParam)); position.y = static_cast<std::int16_t>(HIWORD(lParam));
ScreenToClient(m_handle, &position); ScreenToClient(m_handle, &position);
auto delta = static_cast<std::int16_t>(HIWORD(wParam)); const auto delta = static_cast<std::int16_t>(HIWORD(wParam));
Event event; Event event;
@ -1045,8 +951,8 @@ void WindowImplWin32::processEvent(UINT message, WPARAM wParam, LPARAM lParam)
case WM_MOUSEMOVE: case WM_MOUSEMOVE:
{ {
// Extract the mouse local coordinates // Extract the mouse local coordinates
int x = static_cast<std::int16_t>(LOWORD(lParam)); const int x = static_cast<std::int16_t>(LOWORD(lParam));
int y = static_cast<std::int16_t>(HIWORD(lParam)); const int y = static_cast<std::int16_t>(HIWORD(lParam));
// Get the client area of the window // Get the client area of the window
RECT area; RECT area;
@ -1115,7 +1021,7 @@ void WindowImplWin32::processEvent(UINT message, WPARAM wParam, LPARAM lParam)
if ((wParam == DBT_DEVICEARRIVAL) || (wParam == DBT_DEVICEREMOVECOMPLETE)) if ((wParam == DBT_DEVICEARRIVAL) || (wParam == DBT_DEVICEREMOVECOMPLETE))
{ {
// Some sort of device change has happened, update joystick connections if it is a device interface // Some sort of device change has happened, update joystick connections if it is a device interface
auto* deviceBroadcastHeader = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam); const auto* deviceBroadcastHeader = reinterpret_cast<DEV_BROADCAST_HDR*>(lParam);
if (deviceBroadcastHeader && (deviceBroadcastHeader->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE)) if (deviceBroadcastHeader && (deviceBroadcastHeader->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE))
JoystickImpl::updateConnections(); JoystickImpl::updateConnections();
@ -1126,67 +1032,64 @@ void WindowImplWin32::processEvent(UINT message, WPARAM wParam, LPARAM lParam)
case WM_TOUCH: case WM_TOUCH:
{ {
// Get the number of events // Get the number of events
std::int16_t num = LOWORD(wParam); const unsigned int count = LOWORD(wParam);
// Reserve memory // Reserve memory
PTOUCHINPUT events = new TOUCHINPUT[num]; std::vector<TOUCHINPUT> events(count);
if (events) if (GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(lParam), count, events.data(), sizeof(TOUCHINPUT)))
{ {
if (GetTouchInputInfo(reinterpret_cast<HTOUCHINPUT>(lParam), num, events, sizeof(TOUCHINPUT))) POINT point;
for (unsigned int i = 0; i < count; ++i)
{ {
POINT point; Event event;
for (int i = 0; i < num; ++i) int index = getTouchID(events[i].dwID);
// Out of IDs? Should never happen
if (index == -1)
continue;
event.touch.finger = static_cast<unsigned int>(index);
point.x = TOUCH_COORD_TO_PIXEL(events[i].x);
point.y = TOUCH_COORD_TO_PIXEL(events[i].y);
POINT cpoint = point;
ScreenToClient(m_handle, &cpoint);
event.touch.x = cpoint.x;
event.touch.y = cpoint.y;
if (events[i].dwFlags & TOUCHEVENTF_DOWN)
{ {
Event event; event.type = Event::TouchBegan;
int index = getTouchID(events[i].dwID); pushEvent(event);
// Out of Ids? Should never happen // Prevent initial move event
if (index == -1) touches[index] = point;
continue; }
if (events[i].dwFlags & TOUCHEVENTF_UP)
{
event.type = Event::TouchEnded;
pushEvent(event);
event.touch.finger = static_cast<unsigned int>(index); // Remove the stored ID
point.x = TOUCH_COORD_TO_PIXEL(events[i].x); touchIDs[index] = static_cast<DWORD>(-1);
point.y = TOUCH_COORD_TO_PIXEL(events[i].y); }
if (events[i].dwFlags & TOUCHEVENTF_MOVE)
POINT cpoint = point; {
ScreenToClient(m_handle, &cpoint); // Only handle real movement
event.touch.x = cpoint.x; if (touches[index].x != point.x || touches[index].y != point.y)
event.touch.y = cpoint.y;
if (events[i].dwFlags & TOUCHEVENTF_DOWN)
{ {
event.type = Event::TouchBegan;
pushEvent(event);
// Prevent initial move event
touches[index] = point; touches[index] = point;
} event.type = Event::TouchMoved;
if (events[i].dwFlags & TOUCHEVENTF_UP)
{
event.type = Event::TouchEnded;
pushEvent(event); pushEvent(event);
// Remove the stored ID
touchIDs[index] = static_cast<DWORD>(-1);
}
if (events[i].dwFlags & TOUCHEVENTF_MOVE) {
// Only handle real movement
if (touches[index].x != point.x || touches[index].y != point.y)
{
touches[index] = point;
event.type = Event::TouchMoved;
pushEvent(event);
}
} }
} }
CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(lParam));
} }
else CloseTouchInputHandle(reinterpret_cast<HTOUCHINPUT>(lParam));
{ }
err() << "Failed to get touch input info: " << getErrorString(GetLastError()) << std::endl; else
} {
delete[] events; err() << "Failed to get touch input info: " << getErrorString(GetLastError()) << std::endl;
} }
break; break;
} }
@ -1361,26 +1264,20 @@ void WindowImplWin32::prepareTouch()
{ {
prepared = true; prepared = true;
#if WINVER < 0x0601 // Reset touch IDs
RegisterTouchWindow = reinterpret_cast<RegisterTouchWindowFuncType>(GetProcAddress(user32Dll, "RegisterTouchWindow")); std::fill_n(touchIDs, maxTouchPoints, static_cast<DWORD>(-1));
touchEnabled = RegisterTouchWindow != nullptr;
// If we've got touch support, load the other procs
if (touchEnabled)
{
CloseTouchInputHandle = reinterpret_cast<CloseTouchInputHandleFuncType>(GetProcAddress(user32Dll, "CloseTouchInputHandle"));
GetTouchInputInfo = reinterpret_cast<GetTouchInputInfoFuncType>(GetProcAddress(user32Dll, "GetTouchInputInfo"));
// Reset touch IDs
for (int i = 0; i < 10; ++i)
touchIDs[i] = static_cast<DWORD>(-1);
}
#endif
} }
if (touchEnabled && m_handle) // Tell Windows this window is touch aware
RegisterTouchWindow(m_handle, 0); RegisterTouchWindow(m_handle, 0);
// Disable right mouse button emulation by long touching
const ATOM atom = GlobalAddAtomA(tabletServiceProperty);
if (atom)
{
SetPropA(m_handle, tabletServiceProperty, reinterpret_cast<HANDLE>(tabletDisablePressAndHold));
GlobalDeleteAtom(atom);
}
} }
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////