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 | |
28 | static int SDL_num_touch = 0; |
29 | static SDL_Touch **SDL_touchDevices = NULL; |
30 | |
31 | // for mapping touch events to mice |
32 | static bool finger_touching = false; |
33 | static SDL_FingerID track_fingerid; |
34 | static SDL_TouchID track_touchid; |
35 | |
36 | // Public functions |
37 | bool SDL_InitTouch(void) |
38 | { |
39 | return true; |
40 | } |
41 | |
42 | bool SDL_TouchDevicesAvailable(void) |
43 | { |
44 | return SDL_num_touch > 0; |
45 | } |
46 | |
47 | SDL_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 | |
68 | static 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 | |
82 | SDL_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 | |
97 | const 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 | |
106 | SDL_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 | |
112 | static 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 | |
123 | static 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 | |
132 | SDL_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 | |
165 | int 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 | |
206 | static 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 | |
234 | static 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 | |
252 | void 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 | |
374 | void 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 | |
462 | void 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 | |
489 | void 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 | |