Skip to content
This repository has been archived by the owner on Sep 7, 2021. It is now read-only.

Added multi-touch support for Windows #51

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/include/gainput/GainputInputManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ class GAINPUT_LIBEXPORT InputManager
*/
InputManager(bool useSystemTime = true, Allocator& allocator = GetDefaultAllocator());

#if defined(GAINPUT_PLATFORM_WIN)
/**
* \param useSystemTime Specifies if the GetTime() function uses system time or the time
* supplied to Update(uint64_t).
* \param hWnd Pass a pointer (HWND) to the window handle to hook the message pump to have HandleMessage() automatically invoked.
* \param allocator The memory allocator to be used for all allocations.
* \see SetDisplaySize
* \see GetTime
*/
InputManager(void* hWnd, bool useSystemTime = true, Allocator& allocator = GetDefaultAllocator());
#endif

/// Destructs the manager.
~InputManager();

Expand Down
161 changes: 160 additions & 1 deletion lib/source/gainput/GainputInputManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
#include "keyboard/GainputInputDeviceKeyboardWinRaw.h"
#include "mouse/GainputInputDeviceMouseWin.h"
#include "mouse/GainputInputDeviceMouseWinRaw.h"
#include "touch/GainputInputDeviceTouchWin.h"
#include "GainputWindows.h"
#elif defined(GAINPUT_PLATFORM_ANDROID)
#include <time.h>
#include <jni.h>
Expand Down Expand Up @@ -58,6 +60,146 @@ InputManager::InputManager(bool useSystemTime, Allocator& allocator) :
#endif
}

//==========================================================================
#if defined (GAINPUT_PLATFORM_WIN)
// Capped at 128 instances per process
#define MAX_INSTANCES 128
// Container of our manager pointers and old WNDPROC callbacks to get provide context to new static WNDPROC callback.
static InputManager* sMgrMap[MAX_INSTANCES];
static WNDPROC sMgrMapOldWndProcs[MAX_INSTANCES];
// Corresponding Hwnds
static HWND sMgrMapHwnds[MAX_INSTANCES];
static int sMgrMapIdx = 0;

// Simple mutex, Windows-only, which is okay as this is a Windows-only segment
class SimpleSemaphore
{
LONG m_counter;
HANDLE m_semaphore;

public:
SimpleSemaphore() { m_counter = 0; m_semaphore = CreateSemaphore(NULL, 0, 1, NULL); }
~SimpleSemaphore() { CloseHandle(m_semaphore); }

void Lock() {
if (_InterlockedIncrement(&m_counter) > 1) // x86/64 guarantees acquire semantics
WaitForSingleObject(m_semaphore, INFINITE);
}

void Unlock() {
if (_InterlockedDecrement(&m_counter) > 0) // x86/64 guarantees release semantics
ReleaseSemaphore(m_semaphore, 1, NULL);
}
};

// Make it thread safe
static SimpleSemaphore sWindProcHookMtx;

// Retrieves InputManager ptr from table using HWND ptr as key
static InputManager* HwndToInputManager(HWND hwnd) {
for (int i = 0; i < MAX_INSTANCES; i++) {
if (sMgrMapHwnds[i] == hwnd)
return sMgrMap[i];
}
return NULL;
}

// Retrieves WNDPROC ptr from table using HWND ptr as key
static WNDPROC HwndToOldWndProc(HWND hwnd) {
for (int i = 0; i < MAX_INSTANCES; i++) {
if (sMgrMapHwnds[i] == hwnd)
return sMgrMapOldWndProcs[i];
}
return NULL;
}

static LRESULT WindowProcHook(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Get old WNDPROC
WNDPROC fptr = HwndToOldWndProc(hwnd);
if (!fptr) {
GAINPUT_LOG("%s (%d): Old WNDPROC ptr == NULL", __FILE__, __LINE__);
return LRESULT();
}

// Get corresponding input manager
InputManager* mgr = HwndToInputManager(hwnd);

// If none exists, just pass to old WNDPROC
if (!mgr) {
GAINPUT_LOG("%s (%d): InputManager ptr == NULL", __FILE__, __LINE__);
return CallWindowProc(fptr, hwnd, uMsg, wParam, lParam);
}

// Construct MSG, timestamp not needed
MSG msg;
msg.hwnd = hwnd;
msg.message = uMsg;
msg.wParam = wParam;
msg.lParam = lParam;

// Pass to manager
mgr->HandleMessage(msg);

// Pass back to old WNDPROC
return CallWindowProc(fptr, hwnd, uMsg, wParam, lParam);
}

static void InputManagerWinHook(HWND hwnd, InputManager* input_manager)
{
// Enter critical section
sWindProcHookMtx.Lock();

if (!hwnd) { GAINPUT_LOG("%s (%d): Window handle == NULL", __FILE__, __LINE__); sWindProcHookMtx.Unlock(); return; }

// Get old WNDPROC, assign new callback
WNDPROC old_wndproc = (WNDPROC)SetWindowLongPtr((HWND)hwnd, GWLP_WNDPROC, (LONG_PTR)&WindowProcHook);

if (sMgrMapIdx == 0) {
memset(sMgrMap, NULL, sizeof(sMgrMap));
memset(sMgrMapHwnds, NULL, sizeof(sMgrMapHwnds));
memset(sMgrMapOldWndProcs, NULL, sizeof(sMgrMapOldWndProcs));
}

sMgrMap[sMgrMapIdx] = input_manager;
sMgrMapHwnds[sMgrMapIdx] = hwnd;
sMgrMapOldWndProcs[sMgrMapIdx] = old_wndproc;

++sMgrMapIdx;

// Exit critical section
sWindProcHookMtx.Unlock();
}


InputManager::InputManager(void* hWnd, bool useSystemTime/* = true*/, Allocator& allocator/* = GetDefaultAllocator()*/) :
allocator_(allocator),
devices_(allocator_),
nextDeviceId_(0),
listeners_(allocator_),
nextListenerId_(0),
sortedListeners_(allocator_),
modifiers_(allocator_),
nextModifierId_(0),
deltaState_(allocator_.New<InputDeltaState>(allocator_)),
currentTime_(0),
GAINPUT_CONC_CONSTRUCT(concurrentInputs_),
displayWidth_(-1),
displayHeight_(-1),
useSystemTime_(useSystemTime),
debugRenderingEnabled_(false),
debugRenderer_(0)
{
GAINPUT_DEV_INIT(this);

// Hook our custom WNDPROC to dispatch messages automatically to InputManager::HandleMessage()
if (hWnd && (IsWindow((HWND)hWnd) == TRUE) ? true : false) {
InputManagerWinHook((HWND)hWnd, this);
}

}
#endif

InputManager::~InputManager()
{
allocator_.Delete(deltaState_);
Expand All @@ -68,7 +210,6 @@ InputManager::~InputManager()
{
allocator_.Delete(it->second);
}

GAINPUT_DEV_SHUTDOWN(this);
}

Expand Down Expand Up @@ -384,6 +525,24 @@ InputManager::HandleMessage(const MSG& msg)
mouseImpl->HandleMessage(msg);
}
}
#if defined(GAINPUT_PLATFORM_WIN)
// Process touch messages for Windows for multi-touch support.
//
// Either pass messages directly from your own message handler, or pass an HWND window handle on the construction of
// the InputManager to automatically hook a message pump. This latter solution is especially helpful for situations
// where you (a) don't feel like cluttering your native-side code with extra hooks to Gainput, or (b) instances where
// you may not have access to the native message handler but _do_ have access to the window handle, e.g., with GLFW.
else if (it->second->GetType() == InputDevice::DT_TOUCH)
{
if (it->second->GetVariant() == InputDevice::DV_STANDARD)
{
InputDeviceTouch* touch = static_cast<InputDeviceTouch*>(it->second);
InputDeviceTouchImplWin* touchImpl = static_cast<InputDeviceTouchImplWin*>(touch->GetPimpl());
GAINPUT_ASSERT(touchImpl);
touchImpl->HandleMessage(msg);
}
}
#endif
}
}
#endif
Expand Down
9 changes: 8 additions & 1 deletion lib/source/gainput/touch/GainputInputDeviceTouch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@
#include "GainputInputDeviceTouchAndroid.h"
#elif defined(GAINPUT_PLATFORM_IOS) || defined(GAINPUT_PLATFORM_TVOS)
#include "GainputInputDeviceTouchIos.h"
#elif defined (GAINPUT_PLATFORM_WIN)
#include "GainputInputDeviceTouchWin.h"
#endif


namespace gainput
{

Expand All @@ -38,8 +41,12 @@ InputDeviceTouch::InputDeviceTouch(InputManager& manager, DeviceId device, unsig
{
impl_ = manager.GetAllocator().New<InputDeviceTouchImplIos>(manager, *this, *state_, *previousState_);
}
#elif defined (GAINPUT_PLATFORM_WIN)
if (variant != DV_NULL)
{
impl_ = manager.GetAllocator().New<InputDeviceTouchImplWin>(manager, *this, *state_, *previousState_);
}
#endif

if (!impl_)
{
impl_ = manager.GetAllocator().New<InputDeviceTouchImplNull>(manager, *this);
Expand Down
16 changes: 8 additions & 8 deletions lib/source/gainput/touch/GainputInputDeviceTouchIos.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ class InputDeviceTouchImplIos : public InputDeviceTouchImpl
touches_.push_back(static_cast<void*>(id));
}

HandleBool(gainput::Touch0Down + touchIdx*4, true);
HandleFloat(gainput::Touch0X + touchIdx*4, x);
HandleFloat(gainput::Touch0Y + touchIdx*4, y);
HandleFloat(gainput::Touch0Pressure + touchIdx*4, z);
HandleBool(gainput::Touch0Down + touchIdx*TouchDataElems, true);
HandleFloat(gainput::Touch0X + touchIdx*TouchDataElems, x);
HandleFloat(gainput::Touch0Y + touchIdx*TouchDataElems, y);
HandleFloat(gainput::Touch0Pressure + touchIdx*TouchDataElems, z);
}

void HandleTouchEnd(void* id, float x, float y, float z = 0.f)
Expand All @@ -113,10 +113,10 @@ class InputDeviceTouchImplIos : public InputDeviceTouchImpl

touches_[touchIdx] = 0;

HandleBool(gainput::Touch0Down + touchIdx*4, false);
HandleFloat(gainput::Touch0X + touchIdx*4, x);
HandleFloat(gainput::Touch0Y + touchIdx*4, y);
HandleFloat(gainput::Touch0Pressure + touchIdx*4, z);
HandleBool(gainput::Touch0Down + touchIdx*TouchDataElems, false);
HandleFloat(gainput::Touch0X + touchIdx*TouchDataElems, x);
HandleFloat(gainput::Touch0Y + touchIdx*TouchDataElems, y);
HandleFloat(gainput::Touch0Pressure + touchIdx*TouchDataElems, z);
}

private:
Expand Down
Loading