Skip to content

Commit 23650d7

Browse files
committed
Add calibration mode
1 parent 5180842 commit 23650d7

File tree

5 files changed

+145
-38
lines changed

5 files changed

+145
-38
lines changed

README.md

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,11 @@ version with the same bitness as the program that you are injecting it
4343
into, NOT the bitness of your operating system! x86 is for 32-bit programs
4444
and x64 is for 64-bit programs.
4545

46-
Make sure you have the Visual C++ 2017 Redistributable installed (again,
46+
Make sure you have the Visual C++ 2019 Redistributable installed (again,
4747
for the bitness version that you intend to run, not for the bitness of
4848
your operating system). You can download the 32-bit version
49-
[here](https://aka.ms/vs/15/release/vc_redist.x86.exe) and the 64-bit
50-
version [here](https://aka.ms/vs/15/release/vc_redist.x64.exe).
49+
[here](https://aka.ms/vs/16/release/vc_redist.x86.exe) and the 64-bit
50+
version [here](https://aka.ms/vs/16/release/vc_redist.x64.exe).
5151

5252
Extract `atloader.exe` and `atdll.dll` to the same directory, then
5353
run the following command:
@@ -64,15 +64,23 @@ Initially at program startup, absolute touch mode will be disabled.
6464
You can toggle it on and off by pressing `SHIFT + F6`. Make sure to enable
6565
raw input mode; AbsoluteTouchEx will not work without it.
6666

67+
To adjust the area of your touchpad that gets mapped to the screen, press
68+
`SHIFT + F7` to enter calibration mode. Draw a rectangle on your touchpad
69+
around the area that you wish to use (simply touching the top-left and
70+
bottom-right corners is also sufficient), then press `SHIFT + F7` again to
71+
save. Note that this must be done every time AbsoluteTouchEx is run; your
72+
settings are not saved to disk. While in calibration mode, your cursor
73+
will not move; that is normal.
74+
6775
## Building the project
6876

6977
Requirements:
7078

7179
- Visual Studio 2019
7280
- Windows 10 SDK and WDK (for HID libraries)
73-
- [Detours](https://github.com/Microsoft/Detours)
7481

7582
The project should open and build with no configuration necessary, assuming
76-
you correctly installed the dependencies above. A prebuilt version of Detours
77-
is included in the source directory; if you wish to update it you are
78-
responsible for building it yourself.
83+
you correctly installed the dependencies above. A prebuilt version of
84+
[Detours](https://github.com/Microsoft/Detours) is included in the source
85+
directory; if you wish to update it you are responsible for building it
86+
yourself.

atdll/atdll.cpp

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <cstdlib>
22
#include <exception>
33
#include <memory>
4+
#include <optional>
45
#include <stdexcept>
56
#include <unordered_map>
67
#include <vector>
@@ -21,10 +22,15 @@
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
3036
class 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
124131
static 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()
127140
static 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
146160
static void
147161
debugf(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.
159174
static RAWINPUTHEADER
@@ -376,8 +391,12 @@ AT_RegisterTouchpadInput(HWND hWnd)
376391
static POINT
377392
AT_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 &
400419
AT_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
}

atdll/atdll.vcxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@
100100
<SDLCheck>true</SDLCheck>
101101
<PreprocessorDefinitions>WIN32;_DEBUG;ATDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
102102
<ConformanceMode>true</ConformanceMode>
103+
<LanguageStandard>stdcpp17</LanguageStandard>
103104
</ClCompile>
104105
<Link>
105106
<SubSystem>Windows</SubSystem>
@@ -116,6 +117,7 @@
116117
<SDLCheck>true</SDLCheck>
117118
<PreprocessorDefinitions>_DEBUG;ATDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
118119
<ConformanceMode>true</ConformanceMode>
120+
<LanguageStandard>stdcpp17</LanguageStandard>
119121
</ClCompile>
120122
<Link>
121123
<SubSystem>Windows</SubSystem>
@@ -134,6 +136,7 @@
134136
<SDLCheck>true</SDLCheck>
135137
<PreprocessorDefinitions>WIN32;NDEBUG;ATDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
136138
<ConformanceMode>true</ConformanceMode>
139+
<LanguageStandard>stdcpp17</LanguageStandard>
137140
</ClCompile>
138141
<Link>
139142
<SubSystem>Windows</SubSystem>
@@ -154,6 +157,7 @@
154157
<SDLCheck>true</SDLCheck>
155158
<PreprocessorDefinitions>NDEBUG;ATDLL_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
156159
<ConformanceMode>true</ConformanceMode>
160+
<LanguageStandard>stdcpp17</LanguageStandard>
157161
</ClCompile>
158162
<Link>
159163
<SubSystem>Windows</SubSystem>

0 commit comments

Comments
 (0)