1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23// General touch handling code for SDL
24
25#include "SDL_events_c.h"
26#include "../video/SDL_sysvideo.h"
27
28static int SDL_num_touch = 0;
29static SDL_Touch **SDL_touchDevices = NULL;
30
31// for mapping touch events to mice
32static bool finger_touching = false;
33static SDL_FingerID track_fingerid;
34static SDL_TouchID track_touchid;
35
36// Public functions
37bool SDL_InitTouch(void)
38{
39 return true;
40}
41
42bool SDL_TouchDevicesAvailable(void)
43{
44 return SDL_num_touch > 0;
45}
46
47SDL_TouchID *SDL_GetTouchDevices(int *count)
48{
49 if (count) {
50 *count = 0;
51 }
52
53 const int total = SDL_num_touch;
54 SDL_TouchID *result = (SDL_TouchID *) SDL_malloc(sizeof (SDL_TouchID) * (total + 1));
55 if (result) {
56 for (int i = 0; i < total; i++) {
57 result[i] = SDL_touchDevices[i]->id;
58 }
59 result[total] = 0;
60 if (count) {
61 *count = SDL_num_touch;
62 }
63 }
64
65 return result;
66}
67
68static int SDL_GetTouchIndex(SDL_TouchID id)
69{
70 int index;
71 SDL_Touch *touch;
72
73 for (index = 0; index < SDL_num_touch; ++index) {
74 touch = SDL_touchDevices[index];
75 if (touch->id == id) {
76 return index;
77 }
78 }
79 return -1;
80}
81
82SDL_Touch *SDL_GetTouch(SDL_TouchID id)
83{
84 int index = SDL_GetTouchIndex(id);
85 if (index < 0 || index >= SDL_num_touch) {
86 if (SDL_GetVideoDevice()->ResetTouch != NULL) {
87 SDL_SetError("Unknown touch id %d, resetting", (int)id);
88 (SDL_GetVideoDevice()->ResetTouch)(SDL_GetVideoDevice());
89 } else {
90 SDL_SetError("Unknown touch device id %d, cannot reset", (int)id);
91 }
92 return NULL;
93 }
94 return SDL_touchDevices[index];
95}
96
97const char *SDL_GetTouchDeviceName(SDL_TouchID id)
98{
99 SDL_Touch *touch = SDL_GetTouch(id);
100 if (!touch) {
101 return NULL;
102 }
103 return SDL_GetPersistentString(touch->name);
104}
105
106SDL_TouchDeviceType SDL_GetTouchDeviceType(SDL_TouchID id)
107{
108 SDL_Touch *touch = SDL_GetTouch(id);
109 return touch ? touch->type : SDL_TOUCH_DEVICE_INVALID;
110}
111
112static int SDL_GetFingerIndex(const SDL_Touch *touch, SDL_FingerID fingerid)
113{
114 int index;
115 for (index = 0; index < touch->num_fingers; ++index) {
116 if (touch->fingers[index]->id == fingerid) {
117 return index;
118 }
119 }
120 return -1;
121}
122
123static SDL_Finger *SDL_GetFinger(const SDL_Touch *touch, SDL_FingerID id)
124{
125 int index = SDL_GetFingerIndex(touch, id);
126 if (index < 0 || index >= touch->num_fingers) {
127 return NULL;
128 }
129 return touch->fingers[index];
130}
131
132SDL_Finger **SDL_GetTouchFingers(SDL_TouchID touchID, int *count)
133{
134 SDL_Finger **fingers;
135 SDL_Finger *finger_data;
136
137 if (count) {
138 *count = 0;
139 }
140
141 SDL_Touch *touch = SDL_GetTouch(touchID);
142 if (!touch) {
143 return NULL;
144 }
145
146 // Create a snapshot of the current finger state
147 fingers = (SDL_Finger **)SDL_malloc((touch->num_fingers + 1) * sizeof(*fingers) + touch->num_fingers * sizeof(**fingers));
148 if (!fingers) {
149 return NULL;
150 }
151 finger_data = (SDL_Finger *)(fingers + (touch->num_fingers + 1));
152
153 for (int i = 0; i < touch->num_fingers; ++i) {
154 fingers[i] = &finger_data[i];
155 SDL_copyp(fingers[i], touch->fingers[i]);
156 }
157 fingers[touch->num_fingers] = NULL;
158
159 if (count) {
160 *count = touch->num_fingers;
161 }
162 return fingers;
163}
164
165int SDL_AddTouch(SDL_TouchID touchID, SDL_TouchDeviceType type, const char *name)
166{
167 SDL_Touch **touchDevices;
168 int index;
169
170 SDL_assert(touchID != 0);
171
172 index = SDL_GetTouchIndex(touchID);
173 if (index >= 0) {
174 return index;
175 }
176
177 // Add the touch to the list of touch
178 touchDevices = (SDL_Touch **)SDL_realloc(SDL_touchDevices,
179 (SDL_num_touch + 1) * sizeof(*touchDevices));
180 if (!touchDevices) {
181 return -1;
182 }
183
184 SDL_touchDevices = touchDevices;
185 index = SDL_num_touch;
186
187 SDL_touchDevices[index] = (SDL_Touch *)SDL_malloc(sizeof(*SDL_touchDevices[index]));
188 if (!SDL_touchDevices[index]) {
189 return -1;
190 }
191
192 // Added touch to list
193 ++SDL_num_touch;
194
195 // we're setting the touch properties
196 SDL_touchDevices[index]->id = touchID;
197 SDL_touchDevices[index]->type = type;
198 SDL_touchDevices[index]->num_fingers = 0;
199 SDL_touchDevices[index]->max_fingers = 0;
200 SDL_touchDevices[index]->fingers = NULL;
201 SDL_touchDevices[index]->name = SDL_strdup(name ? name : "");
202
203 return index;
204}
205
206static bool SDL_AddFinger(SDL_Touch *touch, SDL_FingerID fingerid, float x, float y, float pressure)
207{
208 SDL_Finger *finger;
209
210 SDL_assert(fingerid != 0);
211
212 if (touch->num_fingers == touch->max_fingers) {
213 SDL_Finger **new_fingers;
214 new_fingers = (SDL_Finger **)SDL_realloc(touch->fingers, (touch->max_fingers + 1) * sizeof(*touch->fingers));
215 if (!new_fingers) {
216 return false;
217 }
218 touch->fingers = new_fingers;
219 touch->fingers[touch->max_fingers] = (SDL_Finger *)SDL_malloc(sizeof(*finger));
220 if (!touch->fingers[touch->max_fingers]) {
221 return false;
222 }
223 touch->max_fingers++;
224 }
225
226 finger = touch->fingers[touch->num_fingers++];
227 finger->id = fingerid;
228 finger->x = x;
229 finger->y = y;
230 finger->pressure = pressure;
231 return true;
232}
233
234static void SDL_DelFinger(SDL_Touch *touch, SDL_FingerID fingerid)
235{
236 int index = SDL_GetFingerIndex(touch, fingerid);
237 if (index < 0) {
238 return;
239 }
240
241 --touch->num_fingers;
242 if (index < (touch->num_fingers)) {
243 // Move the deleted finger to just past the end of the active fingers array and shift the active fingers by one.
244 // This ensures that the descriptor for the now-deleted finger is located at `touch->fingers[touch->num_fingers]`
245 // and is ready for use in SDL_AddFinger.
246 SDL_Finger *deleted_finger = touch->fingers[index];
247 SDL_memmove(&touch->fingers[index], &touch->fingers[index + 1], (touch->num_fingers - index) * sizeof(touch->fingers[index]));
248 touch->fingers[touch->num_fingers] = deleted_finger;
249 }
250}
251
252void SDL_SendTouch(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window, SDL_EventType type, float x, float y, float pressure)
253{
254 SDL_Finger *finger;
255 bool down = (type == SDL_EVENT_FINGER_DOWN);
256
257 SDL_Touch *touch = SDL_GetTouch(id);
258 if (!touch) {
259 return;
260 }
261
262 SDL_Mouse *mouse = SDL_GetMouse();
263
264 // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events
265 // SDL_HINT_VITA_TOUCH_MOUSE_DEVICE: controlling which touchpad should generate synthetic mouse events, PSVita-only
266 {
267 // FIXME: maybe we should only restrict to a few SDL_TouchDeviceType
268 if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) {
269#ifdef SDL_PLATFORM_VITA
270 if (mouse->touch_mouse_events && ((mouse->vita_touch_mouse_device == id) || (mouse->vita_touch_mouse_device == 3))) {
271#else
272 if (mouse->touch_mouse_events) {
273#endif
274 if (window) {
275 if (down) {
276 if (finger_touching == false) {
277 float pos_x = (x * (float)window->w);
278 float pos_y = (y * (float)window->h);
279 if (pos_x < 0) {
280 pos_x = 0;
281 }
282 if (pos_x > (float)(window->w - 1)) {
283 pos_x = (float)(window->w - 1);
284 }
285 if (pos_y < 0.0f) {
286 pos_y = 0.0f;
287 }
288 if (pos_y > (float)(window->h - 1)) {
289 pos_y = (float)(window->h - 1);
290 }
291 SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y);
292 SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, true);
293 }
294 } else {
295 if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) {
296 SDL_SendMouseButton(timestamp, window, SDL_TOUCH_MOUSEID, SDL_BUTTON_LEFT, false);
297 }
298 }
299 }
300 if (down) {
301 if (finger_touching == false) {
302 finger_touching = true;
303 track_touchid = id;
304 track_fingerid = fingerid;
305 }
306 } else {
307 if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) {
308 finger_touching = false;
309 }
310 }
311 }
312 }
313 }
314
315 // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer
316 if (!mouse->mouse_touch_events && (id == SDL_MOUSE_TOUCHID)) {
317 return;
318 } else if (!mouse->pen_touch_events && (id == SDL_PEN_TOUCHID)) {
319 return;
320 }
321
322 finger = SDL_GetFinger(touch, fingerid);
323 if (down) {
324 if (finger) {
325 /* This finger is already down.
326 Assume the finger-up for the previous touch was lost, and send it. */
327 SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_CANCELED, x, y, pressure);
328 }
329
330 if (!SDL_AddFinger(touch, fingerid, x, y, pressure)) {
331 return;
332 }
333
334 if (SDL_EventEnabled(type)) {
335 SDL_Event event;
336 event.type = type;
337 event.common.timestamp = timestamp;
338 event.tfinger.touchID = id;
339 event.tfinger.fingerID = fingerid;
340 event.tfinger.x = x;
341 event.tfinger.y = y;
342 event.tfinger.dx = 0;
343 event.tfinger.dy = 0;
344 event.tfinger.pressure = pressure;
345 event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0;
346 SDL_PushEvent(&event);
347 }
348 } else {
349 if (!finger) {
350 // This finger is already up
351 return;
352 }
353
354 if (SDL_EventEnabled(type)) {
355 SDL_Event event;
356 event.type = type;
357 event.common.timestamp = timestamp;
358 event.tfinger.touchID = id;
359 event.tfinger.fingerID = fingerid;
360 // I don't trust the coordinates passed on fingerUp
361 event.tfinger.x = finger->x;
362 event.tfinger.y = finger->y;
363 event.tfinger.dx = 0;
364 event.tfinger.dy = 0;
365 event.tfinger.pressure = pressure;
366 event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0;
367 SDL_PushEvent(&event);
368 }
369
370 SDL_DelFinger(touch, fingerid);
371 }
372}
373
374void SDL_SendTouchMotion(Uint64 timestamp, SDL_TouchID id, SDL_FingerID fingerid, SDL_Window *window,
375 float x, float y, float pressure)
376{
377 SDL_Touch *touch;
378 SDL_Finger *finger;
379 float xrel, yrel, prel;
380
381 touch = SDL_GetTouch(id);
382 if (!touch) {
383 return;
384 }
385
386 SDL_Mouse *mouse = SDL_GetMouse();
387
388 // SDL_HINT_TOUCH_MOUSE_EVENTS: controlling whether touch events should generate synthetic mouse events
389 {
390 if ((id != SDL_MOUSE_TOUCHID) && (id != SDL_PEN_TOUCHID)) {
391 if (mouse->touch_mouse_events) {
392 if (window) {
393 if (finger_touching == true && track_touchid == id && track_fingerid == fingerid) {
394 float pos_x = (x * (float)window->w);
395 float pos_y = (y * (float)window->h);
396 if (pos_x < 0.0f) {
397 pos_x = 0.0f;
398 }
399 if (pos_x > (float)(window->w - 1)) {
400 pos_x = (float)(window->w - 1);
401 }
402 if (pos_y < 0.0f) {
403 pos_y = 0.0f;
404 }
405 if (pos_y > (float)(window->h - 1)) {
406 pos_y = (float)(window->h - 1);
407 }
408 SDL_SendMouseMotion(timestamp, window, SDL_TOUCH_MOUSEID, false, pos_x, pos_y);
409 }
410 }
411 }
412 }
413 }
414
415 // SDL_HINT_MOUSE_TOUCH_EVENTS: if not set, discard synthetic touch events coming from platform layer
416 if (mouse->mouse_touch_events == 0) {
417 if (id == SDL_MOUSE_TOUCHID) {
418 return;
419 }
420 }
421
422 finger = SDL_GetFinger(touch, fingerid);
423 if (!finger) {
424 SDL_SendTouch(timestamp, id, fingerid, window, SDL_EVENT_FINGER_DOWN, x, y, pressure);
425 return;
426 }
427
428 xrel = x - finger->x;
429 yrel = y - finger->y;
430 prel = pressure - finger->pressure;
431
432 // Drop events that don't change state
433 if (xrel == 0.0f && yrel == 0.0f && prel == 0.0f) {
434#if 0
435 printf("Touch event didn't change state - dropped!\n");
436#endif
437 return;
438 }
439
440 // Update internal touch coordinates
441 finger->x = x;
442 finger->y = y;
443 finger->pressure = pressure;
444
445 // Post the event, if desired
446 if (SDL_EventEnabled(SDL_EVENT_FINGER_MOTION)) {
447 SDL_Event event;
448 event.type = SDL_EVENT_FINGER_MOTION;
449 event.common.timestamp = timestamp;
450 event.tfinger.touchID = id;
451 event.tfinger.fingerID = fingerid;
452 event.tfinger.x = x;
453 event.tfinger.y = y;
454 event.tfinger.dx = xrel;
455 event.tfinger.dy = yrel;
456 event.tfinger.pressure = pressure;
457 event.tfinger.windowID = window ? SDL_GetWindowID(window) : 0;
458 SDL_PushEvent(&event);
459 }
460}
461
462void SDL_DelTouch(SDL_TouchID id)
463{
464 int i, index;
465 SDL_Touch *touch;
466
467 if (SDL_num_touch == 0) {
468 // We've already cleaned up, we won't find this device
469 return;
470 }
471
472 index = SDL_GetTouchIndex(id);
473 touch = SDL_GetTouch(id);
474 if (!touch) {
475 return;
476 }
477
478 for (i = 0; i < touch->max_fingers; ++i) {
479 SDL_free(touch->fingers[i]);
480 }
481 SDL_free(touch->fingers);
482 SDL_free(touch->name);
483 SDL_free(touch);
484
485 SDL_num_touch--;
486 SDL_touchDevices[index] = SDL_touchDevices[SDL_num_touch];
487}
488
489void SDL_QuitTouch(void)
490{
491 int i;
492
493 for (i = SDL_num_touch; i--;) {
494 SDL_DelTouch(SDL_touchDevices[i]->id);
495 }
496 SDL_assert(SDL_num_touch == 0);
497
498 SDL_free(SDL_touchDevices);
499 SDL_touchDevices = NULL;
500}
501