11#include < cstdlib>
22#include < exception>
33#include < memory>
4+ #include < optional>
45#include < stdexcept>
56#include < unordered_map>
67#include < vector>
2122// that they should read our injected input.
2223#define MAGIC_HANDLE ((HRAWINPUT)0 )
2324
24- // Hotkey for the global toggle
25- #define HOTKEY_ID 0xCAFE
26- #define HOTKEY_MOD (MOD_SHIFT)
27- #define HOTKEY_VK VK_F6
25+ // Hotkey for enable/disable toggle
26+ #define HOTKEY_ENABLE_ID 0xCAFE
27+ #define HOTKEY_ENABLE_MOD (MOD_SHIFT)
28+ #define HOTKEY_ENABLE_VK VK_F6
29+
30+ // Hotkey for calibration mode toggle
31+ #define HOTKEY_CALIBRATION_ID 0xCAFF
32+ #define HOTKEY_CALIBRATION_MOD (MOD_SHIFT)
33+ #define HOTKEY_CALIBRATION_VK VK_F7
2834
2935// C++ exception wrapping the Win32 GetLastError() status
3036class win32_error : std::exception
@@ -98,6 +104,7 @@ struct at_device_info
98104 malloc_ptr<_HIDP_PREPARSED_DATA> preparsedData; // HID internal data
99105 USHORT linkContactCount; // Link collection for number of contacts present
100106 std::vector<at_contact_info> contactInfo; // Link collection and touch area for each contact
107+ std::optional<RECT> touchAreaOverride; // Override touch area for all points if set
101108};
102109
103110// Hook trampolines
@@ -123,6 +130,12 @@ static std::unordered_map<HANDLE, at_device_info> g_devices;
123130// Whether absolute input mode is enabled
124131static bool g_enabled;
125132
133+ // Whether currently in calibration mode
134+ static bool g_inCalibrationMode;
135+
136+ // Current bounds for calibration mode
137+ static thread_local std::unordered_map<HANDLE, RECT> t_calibrationArea;
138+
126139// Holds the injected mouse input to be consumed by the real WndProc()
127140static thread_local RAWINPUT t_injectedInput;
128141
@@ -143,17 +156,19 @@ make_malloc(size_t size)
143156}
144157
145158// C-style printf for debug output.
159+ #if DEBUG_MODE
146160static void
147161debugf (const char *fmt, ...)
148162{
149- #if DEBUG_MODE
150163 va_list args;
151164 va_start (args, fmt);
152165 vfprintf (stderr, fmt, args);
153166 va_end (args);
154167 putc (' \n ' , stderr);
155- #endif
156168}
169+ #else
170+ #define debugf (...) ((void )0 )
171+ #endif
157172
158173// Reads the raw input header for the given raw input handle.
159174static RAWINPUTHEADER
@@ -376,8 +391,12 @@ AT_RegisterTouchpadInput(HWND hWnd)
376391static POINT
377392AT_TouchpadToScreen (RECT touchpadRect, POINT touchpadPoint)
378393{
379- LONG tpDeltaX = touchpadPoint.x - touchpadRect.left ;
380- LONG tpDeltaY = touchpadPoint.y - touchpadRect.top ;
394+ // Clamp point within touch bounds
395+ LONG tpX = max (touchpadRect.left , min (touchpadRect.right , touchpadPoint.x ));
396+ LONG tpY = max (touchpadRect.top , min (touchpadRect.bottom , touchpadPoint.y ));
397+
398+ LONG tpDeltaX = tpX - touchpadRect.left ;
399+ LONG tpDeltaY = tpY - touchpadRect.top ;
381400
382401 // As per HID spec, maximum is inclusive, so we need to add 1 here
383402 LONG tpWidth = touchpadRect.right + 1 - touchpadRect.left ;
@@ -400,10 +419,11 @@ static at_device_info &
400419AT_GetDeviceInfo (HANDLE hDevice)
401420{
402421 if (g_devices.count (hDevice)) {
403- return g_devices[ hDevice] ;
422+ return g_devices. at ( hDevice) ;
404423 }
405424
406425 at_device_info dev;
426+ std::optional<USHORT> linkContactCount;
407427 dev.preparsedData = AT_GetHidPreparsedData (hDevice);
408428
409429 // Struct to hold our parser state
@@ -437,7 +457,7 @@ AT_GetDeviceInfo(HANDLE hDevice)
437457 }
438458 } else if (cap.UsagePage == HID_USAGE_PAGE_DIGITIZER) {
439459 if (cap.NotRange .Usage == HID_USAGE_DIGITIZER_CONTACT_COUNT) {
440- dev. linkContactCount = cap.LinkCollection ;
460+ linkContactCount = cap.LinkCollection ;
441461 } else if (cap.NotRange .Usage == HID_USAGE_DIGITIZER_CONTACT_ID) {
442462 contacts[cap.LinkCollection ].hasContactID = true ;
443463 }
@@ -452,6 +472,11 @@ AT_GetDeviceInfo(HANDLE hDevice)
452472 }
453473 }
454474
475+ if (!linkContactCount.has_value ()) {
476+ throw std::runtime_error (" No contact count usage found" );
477+ }
478+ dev.linkContactCount = linkContactCount.value ();
479+
455480 for (const auto &kvp : contacts) {
456481 USHORT link = kvp.first ;
457482 const at_contact_info_tmp &info = kvp.second ;
@@ -563,6 +588,40 @@ AT_GetPrimaryContact(const std::vector<at_contact> &contacts)
563588 return contacts[0 ];
564589}
565590
591+ // Expands the calibration touch area to include all given contacts.
592+ static void
593+ AT_ExtendCalibrationArea (HANDLE hDevice, const std::vector<at_contact> &contacts)
594+ {
595+ if (!t_calibrationArea.count (hDevice)) {
596+ RECT &touchArea = t_calibrationArea[hDevice];
597+ touchArea.left = LONG_MAX;
598+ touchArea.top = LONG_MAX;
599+ touchArea.right = LONG_MIN;
600+ touchArea.bottom = LONG_MIN;
601+ }
602+
603+ RECT &touchArea = t_calibrationArea.at (hDevice);
604+ for (at_contact contact : contacts) {
605+ touchArea.left = min (touchArea.left , contact.point .x );
606+ touchArea.top = min (touchArea.top , contact.point .y );
607+ touchArea.right = max (touchArea.right , contact.point .x );
608+ touchArea.bottom = max (touchArea.bottom , contact.point .y );
609+ }
610+ }
611+
612+ // Returns the touch area for a given contact. If the device touch
613+ // area was overridden (by calibration mode), uses that; otherwise uses
614+ // the per-contact touch area.
615+ static RECT
616+ AT_GetTouchArea (at_device_info &dev, at_contact &contact)
617+ {
618+ if (dev.touchAreaOverride .has_value ()) {
619+ return dev.touchAreaOverride .value ();
620+ } else {
621+ return contact.info .touchArea ;
622+ }
623+ }
624+
566625// Handles a WM_INPUT event. May update wParam/lParam to be delivered
567626// to the real WndProc. Returns true if the event is handled entirely
568627// at the hook layer and should not be delivered to the real WndProc.
@@ -588,23 +647,32 @@ AT_HandleRawInput(WPARAM *wParam, LPARAM *lParam)
588647 at_device_info &dev = AT_GetDeviceInfo (hdr.hDevice );
589648 malloc_ptr<RAWINPUT> input = AT_GetRawInput (hInput, hdr);
590649 std::vector<at_contact> contacts = AT_GetContacts (dev, input.get ());
591- if (contacts.size () == 0 ) {
650+ if (contacts.empty ()) {
651+ return true ;
652+ }
653+
654+ // If we're in calibration mode, swallow input and extend the
655+ // touch bounding area.
656+ if (g_inCalibrationMode) {
657+ AT_ExtendCalibrationArea (hdr.hDevice , contacts);
592658 return true ;
593659 }
594660
595661 at_contact contact = AT_GetPrimaryContact (contacts);
596- POINT screenPoint = AT_TouchpadToScreen (contact.info .touchArea , contact.point );
597-
598- t_injectedInput.header .dwType = RIM_TYPEMOUSE;
599- t_injectedInput.header .dwSize = sizeof (RAWINPUT);
600- t_injectedInput.header .wParam = *wParam;
601- t_injectedInput.header .hDevice = input->header .hDevice ;
602- t_injectedInput.data .mouse .usFlags = MOUSE_MOVE_ABSOLUTE;
603- t_injectedInput.data .mouse .ulExtraInformation = 0 ;
604- t_injectedInput.data .mouse .usButtonFlags = 0 ;
605- t_injectedInput.data .mouse .usButtonData = 0 ;
606- t_injectedInput.data .mouse .lLastX = screenPoint.x ;
607- t_injectedInput.data .mouse .lLastY = screenPoint.y ;
662+ RECT touchArea = AT_GetTouchArea (dev, contact);
663+ POINT screenPoint = AT_TouchpadToScreen (touchArea, contact.point );
664+
665+ RAWINPUT *injectedInput = &t_injectedInput;
666+ injectedInput->header .dwType = RIM_TYPEMOUSE;
667+ injectedInput->header .dwSize = sizeof (RAWINPUT);
668+ injectedInput->header .wParam = *wParam;
669+ injectedInput->header .hDevice = input->header .hDevice ;
670+ injectedInput->data .mouse .usFlags = MOUSE_MOVE_ABSOLUTE;
671+ injectedInput->data .mouse .ulExtraInformation = 0 ;
672+ injectedInput->data .mouse .usButtonFlags = 0 ;
673+ injectedInput->data .mouse .usButtonData = 0 ;
674+ injectedInput->data .mouse .lLastX = screenPoint.x ;
675+ injectedInput->data .mouse .lLastY = screenPoint.y ;
608676
609677 *lParam = (LPARAM)MAGIC_HANDLE;
610678 return false ;
@@ -656,6 +724,20 @@ AT_GetRawInputDataHook(
656724 }
657725}
658726
727+ // Toggles on/off calibration mode, committing changes made to the
728+ // touch area of any devices.
729+ static void
730+ AT_ToggleCalibrationMode ()
731+ {
732+ if (g_inCalibrationMode) {
733+ for (const auto &entry : t_calibrationArea) {
734+ g_devices.at (entry.first ).touchAreaOverride = entry.second ;
735+ }
736+ t_calibrationArea.clear ();
737+ }
738+ g_inCalibrationMode = !g_inCalibrationMode;
739+ }
740+
659741// Our fake WndProc that intercepts any WM_INPUT messages.
660742// Any non-WM_INPUT messages and unhandled WM_INPUT messages
661743// are delivered to the real WndProc.
@@ -666,13 +748,17 @@ AT_WndProcHook(
666748 WPARAM wParam,
667749 LPARAM lParam)
668750{
669- if (message == WM_HOTKEY && wParam == HOTKEY_ID ) {
751+ if (message == WM_HOTKEY && wParam == HOTKEY_ENABLE_ID ) {
670752 g_enabled = !g_enabled;
671753 debugf (" Absolute touch mode -> %s" , g_enabled ? " ON" : " OFF" );
672754 return 0 ;
755+ } else if (message == WM_HOTKEY && wParam == HOTKEY_CALIBRATION_ID) {
756+ AT_ToggleCalibrationMode ();
757+ debugf (" Calibration mode -> %s" , g_inCalibrationMode ? " ON" : " OFF" );
758+ return 0 ;
673759 }
674760
675- if (g_enabled) {
761+ if (g_enabled || g_inCalibrationMode ) {
676762 if (message == WM_MOUSEMOVE) {
677763 return 0 ;
678764 }
@@ -694,7 +780,7 @@ AT_WndProcHook(
694780 }
695781 }
696782
697- return CallWindowProcW (g_originalWndProcs[ hWnd] , hWnd, message, wParam, lParam);
783+ return CallWindowProcW (g_originalWndProcs. at ( hWnd) , hWnd, message, wParam, lParam);
698784}
699785
700786// Hook for RegisterRawInputDevices(). Any windows that register for
@@ -717,7 +803,8 @@ AT_RegisterRawInputDevicesHook(
717803 // Register hotkey here since we only want to do this once, and generally
718804 // chances are that only one window will be receiving raw input. Ignore errors.
719805 // This is a bit ugly, but it works well enough for our purposes.
720- RegisterHotKey (hWnd, HOTKEY_ID, HOTKEY_MOD, HOTKEY_VK);
806+ RegisterHotKey (hWnd, HOTKEY_ENABLE_ID, HOTKEY_ENABLE_MOD, HOTKEY_ENABLE_VK);
807+ RegisterHotKey (hWnd, HOTKEY_CALIBRATION_ID, HOTKEY_CALIBRATION_MOD, HOTKEY_CALIBRATION_VK);
721808
722809 try {
723810 AT_RegisterTouchpadInput (hWnd);
@@ -740,7 +827,7 @@ AT_GetWindowLongPtrWHook(
740827 int nIndex)
741828{
742829 if (nIndex == GWLP_WNDPROC && g_originalWndProcs.count (hWnd)) {
743- return (LONG_PTR)g_originalWndProcs[ hWnd] ;
830+ return (LONG_PTR)g_originalWndProcs. at ( hWnd) ;
744831 }
745832 return g_originalGetWindowLongPtrW (hWnd, nIndex);
746833}
@@ -754,7 +841,7 @@ AT_SetWindowLongPtrWHook(
754841 LONG_PTR dwNewLong)
755842{
756843 if (nIndex == GWLP_WNDPROC && g_originalWndProcs.count (hWnd)) {
757- WNDPROC origWndProc = g_originalWndProcs[ hWnd] ;
844+ WNDPROC origWndProc = g_originalWndProcs. at ( hWnd) ;
758845 g_originalWndProcs[hWnd] = (WNDPROC)dwNewLong;
759846 return (LONG_PTR)origWndProc;
760847 }
@@ -771,7 +858,7 @@ AT_GetWindowLongWHook(
771858 int nIndex)
772859{
773860 if (nIndex == GWL_WNDPROC && g_originalWndProcs.count (hWnd)) {
774- return (LONG)g_originalWndProcs[ hWnd] ;
861+ return (LONG)g_originalWndProcs. at ( hWnd) ;
775862 }
776863 return g_originalGetWindowLongW (hWnd, nIndex);
777864}
@@ -785,7 +872,7 @@ AT_SetWindowLongWHook(
785872 LONG dwNewLong)
786873{
787874 if (nIndex == GWL_WNDPROC && g_originalWndProcs.count (hWnd)) {
788- WNDPROC origWndProc = g_originalWndProcs[ hWnd] ;
875+ WNDPROC origWndProc = g_originalWndProcs. at ( hWnd) ;
789876 g_originalWndProcs[hWnd] = (WNDPROC)dwNewLong;
790877 return (LONG)origWndProc;
791878 }
0 commit comments