-
Notifications
You must be signed in to change notification settings - Fork 9
/
TouchInjector.cpp
312 lines (277 loc) · 12.1 KB
/
TouchInjector.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// Copyright(c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma comment(lib, "winmm.lib")
#include <Windows.h>
#include <iostream>
// Milliseconds per second.
constexpr uint32_t MS_PER_SEC = 1000;
// Microseconds per second.
constexpr uint32_t US_PER_SEC = MS_PER_SEC * MS_PER_SEC;
// Time interval in QPC units between the injected touch inputs.
uint64_t injection_interval_in_qpc_units;
long Interpolate(long start, long end, double ratio) {
return static_cast<long>(start + (end - start) * ratio);
}
double InjectionIntervalInMsFromFrequency(int frequency) {
return (static_cast<double>(1.0) / static_cast<double>(frequency)) *
MS_PER_SEC;
}
// Calculates the number of packets to inject based on given pan
// duration and injection interval.
uint32_t CalculatePacketsNeeded(long duration, double interval) {
uint32_t packet_count = static_cast<uint32_t>(duration / interval);
// Rounding up the number of packets.
// Why rounding up? Let say we want to pan 500 pixels in 1 sec at frame
// frequency of 60. In this case, each frame/input will be injected at an
// interval of 16ms. As per the above code, |packet_count| = 62. Now, 62*16ms
// equals 992ms. This means that we are injecting pan with a velocity of 504
// (500/0.992) instead of 500. That's why if |duration| is not exactly
// divisible by interval, we need to round up the |packet_count| to inject at
// intended rate.
if (duration % static_cast<long>(interval))
packet_count++;
return packet_count;
}
// Injects the touch pointer onto primary monitor.
void InjectPointer(POINTER_TOUCH_INFO* touch_info) {
// Time when touch input packet is injected.
static uint64_t injection_time = 0;
LARGE_INTEGER perf_counter_now = {};
::QueryPerformanceCounter(&perf_counter_now);
if (touch_info->pointerInfo.pointerFlags & POINTER_FLAG_DOWN) {
injection_time = perf_counter_now.QuadPart;
} else {
while (perf_counter_now.QuadPart - injection_time <
injection_interval_in_qpc_units) {
::QueryPerformanceCounter(&perf_counter_now);
}
injection_time += injection_interval_in_qpc_units;
}
touch_info->pointerInfo.PerformanceCount = injection_time;
InjectTouchInput(1, touch_info);
}
// Sends a pointer down event to primary monitor.
void SendPointerDown(POINTER_TOUCH_INFO* touch_info) {
touch_info->pointerInfo.pointerFlags =
(POINTER_FLAG_DOWN | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT);
InjectPointer(touch_info);
}
// Sends a pointer update event to primary monitor.
void SendPointerMove(POINTER_TOUCH_INFO* touch_info) {
touch_info->pointerInfo.pointerFlags =
(POINTER_FLAG_UPDATE | POINTER_FLAG_INRANGE | POINTER_FLAG_INCONTACT);
InjectPointer(touch_info);
}
// Sends a pointer up event to primary monitor.
void SendPointerUp(POINTER_TOUCH_INFO* touch_info) {
touch_info->pointerInfo.pointerFlags = POINTER_FLAG_UP;
InjectPointer(touch_info);
}
// Injects the requested input from |start| to |end| in vertical direction.
bool ExecuteInjection(POINTER_TOUCH_INFO* contact,
uint32_t packets,
long start,
long end,
HANDLE hTimer,
bool accelerate) {
contact->pointerInfo.ptPixelLocation.y = start;
contact->rcContact.top = contact->pointerInfo.ptPixelLocation.y - 2;
contact->rcContact.bottom = contact->pointerInfo.ptPixelLocation.y + 2;
contact->rcContact.left = contact->pointerInfo.ptPixelLocation.x - 2;
contact->rcContact.right = contact->pointerInfo.ptPixelLocation.x + 2;
SendPointerDown(contact);
for (uint32_t i = 1; i <= packets; i++) {
if (WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0) {
printf("WaitForSingleObject failed (%d)\n", GetLastError());
CancelWaitableTimer(hTimer);
CloseHandle(hTimer);
return false;
}
double ratio = static_cast<double>(i) / static_cast<double>(packets);
if (accelerate)
ratio *= (1.0f * (ratio - 1) + 1);
// Updating the Y Co-ordinate
contact->pointerInfo.ptPixelLocation.y = Interpolate(start, end, ratio);
contact->rcContact.right = contact->pointerInfo.ptPixelLocation.y + 2;
contact->rcContact.bottom = contact->pointerInfo.ptPixelLocation.y + 2;
SendPointerMove(contact);
}
SendPointerUp(contact);
return true;
}
// Entry point for the application.
int __cdecl main(int argc, const char** argv) {
// Default values
// Total number of pixels to move in a direction
int distance = 500;
// Number of second to take to perform the pan gesture.
float duration = 1.0f;
// Number of times to repeat the input sequence.
int repeat = 1;
// Number of seconds to wait before starting any input.
float start_delay = 1;
// Number of seconds to wait between each repetition of the input segment.
float segment_delay = 3;
// One segment can contain upto two input sequences, one in forward and one in
// reverse direction. |sequence_delay| specifies the number of seconds to wait
// between each input sequence.
float sequence_delay = 3;
// Frequency/frame rate at which input needs to be injected.
int frequency = 100;
// True if pan gesture needs to accelerate.
bool acceleration = false;
// True if pan injection is uni-directional.
bool onedir = false;
// Parse command line arguments.
for (int i = 1; i < argc; i++) {
if (_stricmp(argv[i], "duration") == 0) {
duration = static_cast<float>(atof(argv[++i]));
} else if (_stricmp(argv[i], "repeat") == 0) {
repeat = atoi(argv[++i]);
} else if (_stricmp(argv[i], "segmentdelay") == 0) {
segment_delay = static_cast<float>(atof(argv[++i]));
} else if (_stricmp(argv[i], "startdelay") == 0) {
start_delay = static_cast<float>(atof(argv[++i]));
} else if (_stricmp(argv[i], "sequencedelay") == 0) {
sequence_delay = static_cast<float>(atof(argv[++i]));
} else if (_stricmp(argv[i], "distance") == 0) {
distance = atoi(argv[++i]);
} else if (_stricmp(argv[i], "frequency") == 0) {
frequency = atoi(argv[++i]);
} else if (_stricmp(argv[i], "accelerate") == 0) {
acceleration = true;
} else if (_stricmp(argv[i], "onedir") == 0) {
onedir = true;
} else {
printf(
"pan.exe [repeat n] [startdelay n] [segmentdelay n] [sequencedelay "
"n] [distance n] "
"[duration n] [frequency n] [accelerate] [onedir]\r\n");
printf("\r\n");
printf(
" Note that touch input is injected at 100, 100 + "
"distance\r\n");
printf("\r\n");
printf(
" Example: pan repeat 3 segmentdelay 0.5 distance 100 duration "
"0.75\r\n");
printf("\r\n");
printf(
" repeat - number of times to repeat the input sequence. The "
"default value is 1.\r\n");
printf(
" startdelay - number of seconds to wait before starting any "
"input. The default value is 1 second.\r\n");
printf(
" segmentdelay - number of seconds to wait between each "
"repetition of the input segment. The default value is 3 "
"seconds.\r\n");
printf(
" sequencedelay - number of seconds to wait between each input "
"sequence of a segment. The default value is 3 seconds.\r\n");
printf(
" distance - total number of pixels to move in a direction. The "
"default value is 500 pixels.\r\n");
printf(
" duration - number of second to take to perform the pan gesture. "
"The default value is 1 second.\r\n");
printf(
" frequency - frequency/frame rate at which input needs to be "
"injected. The default value is 100 frames per second.\r\n");
printf(
" accelerate - accelerate injection tool instead of linear "
"movement. The default value is false. \r\n");
printf(
" onedir - do not reverse the pan direction after completing a "
"input sequence. The default value is false. \r\n");
return -1;
}
}
// Increase the thread priority to help ensure we're getting
// input delivered in a timely manner.
if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {
printf("Error calling SetPriorityClass: %d\r\n", GetLastError());
return -1;
}
if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) {
printf("Error calling SetThreadPriority: %d\r\n", GetLastError());
return -1;
}
// Starting point for a pan gesture.
long startx = 100;
long starty = 100 + distance;
// End point for a pan gesture.
long endy = 100;
long duration_in_ms = static_cast<long>(duration * MS_PER_SEC);
double injection_interval_in_ms_from_frequency =
InjectionIntervalInMsFromFrequency(frequency);
// Request minimum resolution of |injection_interval_in_ms_from_frequency| ms
// for timer. Timer can fire either early or late relative to
// |injection_interval_in_ms_from_frequency|. Without |timeBeginPeriod| being
// called, this range varies from -2 to 2 ms. |timeBeginPeriod| helps improve
// timer firing accuracy and reduces the range from -0.7 to 0.6ms.
timeBeginPeriod(
static_cast<uint32_t>(injection_interval_in_ms_from_frequency));
HANDLE hTimer = CreateWaitableTimer(nullptr, FALSE, nullptr);
if (hTimer == nullptr) {
printf("Error calling CreateWaitableTimer: %d\r\n", GetLastError());
return -1;
}
LARGE_INTEGER qpc_frequency = {};
::QueryPerformanceFrequency(&qpc_frequency);
injection_interval_in_qpc_units = static_cast<uint64_t>(
injection_interval_in_ms_from_frequency *
(MS_PER_SEC * (qpc_frequency.QuadPart / US_PER_SEC)));
// Create a negative 64-bit integer that will be used to signal the timer
// |injection_interval_in_ms_from_frequency| milliseconds from now. Negative
// values allows relative time offset instead of absolute times.
LARGE_INTEGER li_due_time;
li_due_time.QuadPart =
-(static_cast<LONGLONG>(injection_interval_in_ms_from_frequency * 10000));
if (!SetWaitableTimer(
hTimer, &li_due_time,
static_cast<long>(injection_interval_in_ms_from_frequency), nullptr,
nullptr, 0)) {
printf("Error calling SetWaitableTimer: %d\r\n", GetLastError());
CloseHandle(hTimer);
return -1;
}
POINTER_TOUCH_INFO contact;
memset(&contact, 0, sizeof(POINTER_TOUCH_INFO));
// Initialize touch injection with max of 1 contacts.
InitializeTouchInjection(1, TOUCH_FEEDBACK_DEFAULT);
contact.pointerInfo.pointerType = PT_TOUCH; // we're sending touch input
contact.pointerInfo.pointerId = 0; // contact 0
contact.pointerInfo.ptPixelLocation.x = startx;
contact.touchFlags = TOUCH_FLAG_NONE;
contact.touchMask =
TOUCH_MASK_CONTACTAREA | TOUCH_MASK_ORIENTATION | TOUCH_MASK_PRESSURE;
contact.orientation = 0;
contact.pressure = 0;
// Delay the start of injection by |start_delay| seconds.
Sleep(static_cast<DWORD>(start_delay * MS_PER_SEC));
// Number of pointers to be injected for desired gesture.
uint32_t packets = CalculatePacketsNeeded(
duration_in_ms, injection_interval_in_ms_from_frequency);
for (int c = 0; c < repeat; c++) {
if (!ExecuteInjection(&contact, packets, starty, endy, hTimer,
acceleration)) {
return -1;
}
// Wait for |sequence_delay| seconds before injecting gesture in reverse
// direction.
Sleep(static_cast<DWORD>(sequence_delay * MS_PER_SEC));
if (!onedir) {
if (!ExecuteInjection(&contact, packets, endy, starty, hTimer,
acceleration))
return -1;
}
// Delay the next sequence of injection by |segment_delay| seconds.
if (repeat != 1)
Sleep(static_cast<DWORD>(segment_delay * MS_PER_SEC));
}
CancelWaitableTimer(hTimer);
CloseHandle(hTimer);
timeEndPeriod(static_cast<uint32_t>(injection_interval_in_ms_from_frequency));
return 0;
}