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// Pressure-sensitive pen handling code for SDL
24
25#include "../SDL_hints_c.h"
26#include "SDL_events_c.h"
27#include "SDL_pen_c.h"
28
29static SDL_PenID pen_touching = 0; // used for synthetic mouse/touch events.
30
31typedef struct SDL_Pen
32{
33 SDL_PenID instance_id;
34 char *name;
35 SDL_PenInfo info;
36 float axes[SDL_PEN_AXIS_COUNT];
37 float x;
38 float y;
39 SDL_PenInputFlags input_state;
40 void *driverdata;
41} SDL_Pen;
42
43// we assume there's usually 0-1 pens in most cases and this list doesn't
44// usually change after startup, so a simple array with a RWlock is fine for now.
45static SDL_RWLock *pen_device_rwlock = NULL;
46static SDL_Pen *pen_devices SDL_GUARDED_BY(pen_device_rwlock) = NULL;
47static int pen_device_count SDL_GUARDED_BY(pen_device_rwlock) = 0;
48
49// You must hold pen_device_rwlock before calling this, and result is only safe while lock is held!
50// If SDL isn't initialized, grabbing the NULL lock is a no-op and there will be zero devices, so
51// locking and calling this in that case will do the right thing.
52static SDL_Pen *FindPenByInstanceId(SDL_PenID instance_id) SDL_REQUIRES_SHARED(pen_device_rwlock)
53{
54 if (instance_id) {
55 for (int i = 0; i < pen_device_count; i++) {
56 if (pen_devices[i].instance_id == instance_id) {
57 return &pen_devices[i];
58 }
59 }
60 }
61 SDL_SetError("Invalid pen instance ID");
62 return NULL;
63}
64
65SDL_PenID SDL_FindPenByHandle(void *handle)
66{
67 SDL_PenID result = 0;
68 SDL_LockRWLockForReading(pen_device_rwlock);
69 for (int i = 0; i < pen_device_count; i++) {
70 if (pen_devices[i].driverdata == handle) {
71 result = pen_devices[i].instance_id;
72 break;
73 }
74 }
75 SDL_UnlockRWLock(pen_device_rwlock);
76 return result;
77}
78
79SDL_PenID SDL_FindPenByCallback(bool (*callback)(void *handle, void *userdata), void *userdata)
80{
81 SDL_PenID result = 0;
82 SDL_LockRWLockForReading(pen_device_rwlock);
83 for (int i = 0; i < pen_device_count; i++) {
84 if (callback(pen_devices[i].driverdata, userdata)) {
85 result = pen_devices[i].instance_id;
86 break;
87 }
88 }
89 SDL_UnlockRWLock(pen_device_rwlock);
90 return result;
91}
92
93
94
95// public API ...
96
97bool SDL_InitPen(void)
98{
99 SDL_assert(pen_device_rwlock == NULL);
100 SDL_assert(pen_devices == NULL);
101 SDL_assert(pen_device_count == 0);
102 pen_device_rwlock = SDL_CreateRWLock();
103 if (!pen_device_rwlock) {
104 return false;
105 }
106 return true;
107}
108
109void SDL_QuitPen(void)
110{
111 SDL_DestroyRWLock(pen_device_rwlock);
112 pen_device_rwlock = NULL;
113 if (pen_devices) {
114 for (int i = pen_device_count; i--; ) {
115 SDL_free(pen_devices[i].name);
116 }
117 SDL_free(pen_devices);
118 pen_devices = NULL;
119 }
120 pen_device_count = 0;
121 pen_touching = 0;
122}
123
124#if 0 // not a public API at the moment.
125SDL_PenID *SDL_GetPens(int *count)
126{
127 SDL_LockRWLockForReading(pen_device_rwlock);
128 const int num_devices = pen_device_count;
129 SDL_PenID *result = (SDL_PenID *) SDL_malloc((num_devices + 1) * sizeof (SDL_PenID));
130 if (result) {
131 for (int i = 0; i < num_devices; i++) {
132 result[i] = pen_devices[i].instance_id;
133 }
134 result[num_devices] = 0; // null-terminated.
135 }
136 SDL_UnlockRWLock(pen_device_rwlock);
137
138 if (count) {
139 *count = result ? num_devices : 0;
140 }
141 return result;
142}
143
144const char *SDL_GetPenName(SDL_PenID instance_id)
145{
146 SDL_LockRWLockForReading(pen_device_rwlock);
147 const SDL_Pen *pen = FindPenByInstanceId(instance_id);
148 const char *result = pen ? SDL_GetPersistentString(pen->name) : NULL;
149 SDL_UnlockRWLock(pen_device_rwlock);
150 return result;
151}
152
153bool SDL_GetPenInfo(SDL_PenID instance_id, SDL_PenInfo *info)
154{
155 SDL_LockRWLockForReading(pen_device_rwlock);
156 const SDL_Pen *pen = FindPenByInstanceId(instance_id);
157 const bool result = pen ? true : false;
158 if (info) {
159 if (result) {
160 SDL_copyp(info, &pen->info);
161 } else {
162 SDL_zerop(info);
163 }
164 }
165 SDL_UnlockRWLock(pen_device_rwlock);
166 return result;
167}
168
169bool SDL_PenConnected(SDL_PenID instance_id)
170{
171 SDL_LockRWLockForReading(pen_device_rwlock);
172 const SDL_Pen *pen = FindPenByInstanceId(instance_id);
173 const bool result = (pen != NULL);
174 SDL_UnlockRWLock(pen_device_rwlock);
175 return result;
176}
177#endif
178
179SDL_PenInputFlags SDL_GetPenStatus(SDL_PenID instance_id, float *axes, int num_axes)
180{
181 if (num_axes < 0) {
182 num_axes = 0;
183 }
184
185 SDL_LockRWLockForReading(pen_device_rwlock);
186 const SDL_Pen *pen = FindPenByInstanceId(instance_id);
187 SDL_PenInputFlags result = 0;
188 if (pen) {
189 result = pen->input_state;
190 if (axes && num_axes) {
191 SDL_memcpy(axes, pen->axes, SDL_min(num_axes, SDL_PEN_AXIS_COUNT) * sizeof (*axes));
192 // zero out axes we don't know about, in case the caller built with newer SDL headers that support more of them.
193 if (num_axes > SDL_PEN_AXIS_COUNT) {
194 SDL_memset(&axes[SDL_PEN_AXIS_COUNT], '\0', (num_axes - SDL_PEN_AXIS_COUNT) * sizeof (*axes));
195 }
196 }
197 }
198 SDL_UnlockRWLock(pen_device_rwlock);
199 return result;
200}
201
202SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis)
203{
204 // the initial capability bits happen to match up, but as
205 // more features show up later, the bits may no longer be contiguous!
206 if ((axis >= SDL_PEN_AXIS_PRESSURE) && (axis <= SDL_PEN_AXIS_SLIDER)) {
207 return ((SDL_PenCapabilityFlags) 1u) << ((SDL_PenCapabilityFlags) axis);
208 }
209 return 0; // oh well.
210}
211
212SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle)
213{
214 SDL_assert(handle != NULL); // just allocate a Uint8 so you have a unique pointer if not needed!
215 SDL_assert(SDL_FindPenByHandle(handle) == 0); // Backends shouldn't double-add pens!
216 SDL_assert(pen_device_rwlock != NULL); // subsystem should be initialized by now!
217
218 char *namecpy = SDL_strdup(name ? name : "Unnamed pen");
219 if (!namecpy) {
220 return 0;
221 }
222
223 SDL_PenID result = 0;
224
225 SDL_LockRWLockForWriting(pen_device_rwlock);
226
227 SDL_Pen *pen = NULL;
228 void *ptr = SDL_realloc(pen_devices, (pen_device_count + 1) * sizeof (*pen));
229 if (ptr) {
230 result = (SDL_PenID) SDL_GetNextObjectID();
231 pen_devices = (SDL_Pen *) ptr;
232 pen = &pen_devices[pen_device_count];
233 pen_device_count++;
234
235 SDL_zerop(pen);
236 pen->instance_id = result;
237 pen->name = namecpy;
238 if (info) {
239 SDL_copyp(&pen->info, info);
240 }
241 pen->driverdata = handle;
242 // axes and input state defaults to zero.
243 }
244 SDL_UnlockRWLock(pen_device_rwlock);
245
246 if (!pen) {
247 SDL_free(namecpy);
248 }
249
250 if (result && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_IN)) {
251 SDL_Event event;
252 SDL_zero(event);
253 event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN;
254 event.pproximity.timestamp = timestamp;
255 event.pproximity.which = result;
256 SDL_PushEvent(&event);
257 }
258
259 return result;
260}
261
262void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id)
263{
264 if (!instance_id) {
265 return;
266 }
267
268 SDL_LockRWLockForWriting(pen_device_rwlock);
269 SDL_Pen *pen = FindPenByInstanceId(instance_id);
270 if (pen) {
271 SDL_free(pen->name);
272 // we don't free `pen`, it's just part of simple array. Shuffle it out.
273 const int idx = ((int) (pen - pen_devices));
274 SDL_assert((idx >= 0) && (idx < pen_device_count));
275 if ( idx < (pen_device_count - 1) ) {
276 SDL_memmove(&pen_devices[idx], &pen_devices[idx + 1], sizeof (*pen) * ((pen_device_count - idx) - 1));
277 }
278
279 SDL_assert(pen_device_count > 0);
280 pen_device_count--;
281
282 if (pen_device_count) {
283 void *ptr = SDL_realloc(pen_devices, sizeof (*pen) * pen_device_count); // shrink it down.
284 if (ptr) {
285 pen_devices = (SDL_Pen *) ptr;
286 }
287 } else {
288 SDL_free(pen_devices);
289 pen_devices = NULL;
290 }
291 }
292 SDL_UnlockRWLock(pen_device_rwlock);
293
294 if (pen && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_OUT)) {
295 SDL_Event event;
296 SDL_zero(event);
297 event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT;
298 event.pproximity.timestamp = timestamp;
299 event.pproximity.which = instance_id;
300 SDL_PushEvent(&event);
301 }
302}
303
304// This presumably is happening during video quit, so we don't send PROXIMITY_OUT events here.
305void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata)
306{
307 SDL_LockRWLockForWriting(pen_device_rwlock);
308 if (pen_device_count > 0) {
309 SDL_assert(pen_devices != NULL);
310 for (int i = 0; i < pen_device_count; i++) {
311 callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata);
312 SDL_free(pen_devices[i].name);
313 }
314 }
315 SDL_free(pen_devices);
316 pen_devices = NULL;
317 SDL_UnlockRWLock(pen_device_rwlock);
318}
319
320void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool eraser, bool down)
321{
322 bool send_event = false;
323 SDL_PenInputFlags input_state = 0;
324 float x = 0.0f;
325 float y = 0.0f;
326
327 // note that this locks for _reading_ because the lock protects the
328 // pen_devices array from being reallocated from under us, not the data in it;
329 // we assume only one thread (in the backend) is modifying an individual pen at
330 // a time, so it can update input state cleanly here.
331 SDL_LockRWLockForReading(pen_device_rwlock);
332 SDL_Pen *pen = FindPenByInstanceId(instance_id);
333 if (pen) {
334 input_state = pen->input_state;
335 x = pen->x;
336 y = pen->y;
337
338 if (down && ((input_state & SDL_PEN_INPUT_DOWN) == 0)) {
339 input_state |= SDL_PEN_INPUT_DOWN;
340 send_event = true;
341 } else if (!down && (input_state & SDL_PEN_INPUT_DOWN)) {
342 input_state &= ~SDL_PEN_INPUT_DOWN;
343 send_event = true;
344 }
345
346 if (eraser && ((input_state & SDL_PEN_INPUT_ERASER_TIP) == 0)) {
347 input_state |= SDL_PEN_INPUT_ERASER_TIP;
348 send_event = true;
349 } else if (!eraser && (input_state & SDL_PEN_INPUT_ERASER_TIP)) {
350 input_state &= ~SDL_PEN_INPUT_ERASER_TIP;
351 send_event = true;
352 }
353
354 pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble...
355 }
356 SDL_UnlockRWLock(pen_device_rwlock);
357
358 if (send_event) {
359 const SDL_EventType evtype = down ? SDL_EVENT_PEN_DOWN : SDL_EVENT_PEN_UP;
360 if (SDL_EventEnabled(evtype)) {
361 SDL_Event event;
362 SDL_zero(event);
363 event.ptouch.type = evtype;
364 event.ptouch.timestamp = timestamp;
365 event.ptouch.windowID = window ? window->id : 0;
366 event.ptouch.which = instance_id;
367 event.ptouch.pen_state = input_state;
368 event.ptouch.x = x;
369 event.ptouch.y = y;
370 event.ptouch.eraser = eraser;
371 event.ptouch.down = down;
372 SDL_PushEvent(&event);
373 }
374
375 SDL_Mouse *mouse = SDL_GetMouse();
376 if (mouse && window) {
377 if (mouse->pen_mouse_events) {
378 if (down) {
379 if (!pen_touching) {
380 SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y);
381 SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, SDL_BUTTON_LEFT, true);
382 }
383 } else {
384 if (pen_touching == instance_id) {
385 SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, SDL_BUTTON_LEFT, false);
386 }
387 }
388 }
389
390 if (mouse->pen_touch_events) {
391 const SDL_EventType touchtype = down ? SDL_EVENT_FINGER_DOWN : SDL_EVENT_FINGER_UP;
392 const float normalized_x = x / (float)window->w;
393 const float normalized_y = y / (float)window->h;
394 if (!pen_touching || (pen_touching == instance_id)) {
395 SDL_SendTouch(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, touchtype, normalized_x, normalized_y, pen->axes[SDL_PEN_AXIS_PRESSURE]);
396 }
397 }
398 }
399
400 if (down) {
401 if (!pen_touching) {
402 pen_touching = instance_id;
403 }
404 } else {
405 if (pen_touching == instance_id) {
406 pen_touching = 0;
407 }
408 }
409 }
410}
411
412void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, SDL_PenAxis axis, float value)
413{
414 SDL_assert((axis >= 0) && (axis < SDL_PEN_AXIS_COUNT)); // fix the backend if this triggers.
415
416 bool send_event = false;
417 SDL_PenInputFlags input_state = 0;
418 float x = 0.0f;
419 float y = 0.0f;
420
421 // note that this locks for _reading_ because the lock protects the
422 // pen_devices array from being reallocated from under us, not the data in it;
423 // we assume only one thread (in the backend) is modifying an individual pen at
424 // a time, so it can update input state cleanly here.
425 SDL_LockRWLockForReading(pen_device_rwlock);
426 SDL_Pen *pen = FindPenByInstanceId(instance_id);
427 if (pen) {
428 if (pen->axes[axis] != value) {
429 pen->axes[axis] = value; // we could do an SDL_SetAtomicInt here if we run into trouble...
430 input_state = pen->input_state;
431 x = pen->x;
432 y = pen->y;
433 send_event = true;
434 }
435 }
436 SDL_UnlockRWLock(pen_device_rwlock);
437
438 if (send_event && SDL_EventEnabled(SDL_EVENT_PEN_AXIS)) {
439 SDL_Event event;
440 SDL_zero(event);
441 event.paxis.type = SDL_EVENT_PEN_AXIS;
442 event.paxis.timestamp = timestamp;
443 event.paxis.windowID = window ? window->id : 0;
444 event.paxis.which = instance_id;
445 event.paxis.pen_state = input_state;
446 event.paxis.x = x;
447 event.paxis.y = y;
448 event.paxis.axis = axis;
449 event.paxis.value = value;
450 SDL_PushEvent(&event);
451
452 if (window && (axis == SDL_PEN_AXIS_PRESSURE) && (pen_touching == instance_id)) {
453 SDL_Mouse *mouse = SDL_GetMouse();
454 if (mouse && mouse->pen_touch_events) {
455 const float normalized_x = x / (float)window->w;
456 const float normalized_y = y / (float)window->h;
457 SDL_SendTouchMotion(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, normalized_x, normalized_y, value);
458 }
459 }
460 }
461}
462
463void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, float x, float y)
464{
465 bool send_event = false;
466 SDL_PenInputFlags input_state = 0;
467
468 // note that this locks for _reading_ because the lock protects the
469 // pen_devices array from being reallocated from under us, not the data in it;
470 // we assume only one thread (in the backend) is modifying an individual pen at
471 // a time, so it can update input state cleanly here.
472 SDL_LockRWLockForReading(pen_device_rwlock);
473 SDL_Pen *pen = FindPenByInstanceId(instance_id);
474 if (pen) {
475 if ((pen->x != x) || (pen->y != y)) {
476 pen->x = x; // we could do an SDL_SetAtomicInt here if we run into trouble...
477 pen->y = y; // we could do an SDL_SetAtomicInt here if we run into trouble...
478 input_state = pen->input_state;
479 send_event = true;
480 }
481 }
482 SDL_UnlockRWLock(pen_device_rwlock);
483
484 if (send_event && SDL_EventEnabled(SDL_EVENT_PEN_MOTION)) {
485 SDL_Event event;
486 SDL_zero(event);
487 event.pmotion.type = SDL_EVENT_PEN_MOTION;
488 event.pmotion.timestamp = timestamp;
489 event.pmotion.windowID = window ? window->id : 0;
490 event.pmotion.which = instance_id;
491 event.pmotion.pen_state = input_state;
492 event.pmotion.x = x;
493 event.pmotion.y = y;
494 SDL_PushEvent(&event);
495
496 if (window) {
497 SDL_Mouse *mouse = SDL_GetMouse();
498 if (mouse) {
499 if (pen_touching == instance_id) {
500 if (mouse->pen_mouse_events) {
501 SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y);
502 }
503
504 if (mouse->pen_touch_events) {
505 const float normalized_x = x / (float)window->w;
506 const float normalized_y = y / (float)window->h;
507 SDL_SendTouchMotion(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, normalized_x, normalized_y, pen->axes[SDL_PEN_AXIS_PRESSURE]);
508 }
509 } else if (pen_touching == 0) { // send mouse motion (without a pressed button) for pens that aren't touching.
510 // this might cause a little chaos if you have multiple pens hovering at the same time, but this seems unlikely in the real world, and also something you did to yourself. :)
511 SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y);
512 }
513 }
514 }
515 }
516}
517
518void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, Uint8 button, bool down)
519{
520 bool send_event = false;
521 SDL_PenInputFlags input_state = 0;
522 float x = 0.0f;
523 float y = 0.0f;
524
525 if ((button < 1) || (button > 5)) {
526 return; // clamp for now.
527 }
528
529 // note that this locks for _reading_ because the lock protects the
530 // pen_devices array from being reallocated from under us, not the data in it;
531 // we assume only one thread (in the backend) is modifying an individual pen at
532 // a time, so it can update input state cleanly here.
533 SDL_LockRWLockForReading(pen_device_rwlock);
534 SDL_Pen *pen = FindPenByInstanceId(instance_id);
535 if (pen) {
536 input_state = pen->input_state;
537 const Uint32 flag = (Uint32) (1u << button);
538 const bool current = ((input_state & flag) != 0);
539 x = pen->x;
540 y = pen->y;
541 if (down && !current) {
542 input_state |= flag;
543 send_event = true;
544 } else if (!down && current) {
545 input_state &= ~flag;
546 send_event = true;
547 }
548 pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble...
549 }
550 SDL_UnlockRWLock(pen_device_rwlock);
551
552 if (send_event) {
553 const SDL_EventType evtype = down ? SDL_EVENT_PEN_BUTTON_DOWN : SDL_EVENT_PEN_BUTTON_UP;
554 if (SDL_EventEnabled(evtype)) {
555 SDL_Event event;
556 SDL_zero(event);
557 event.pbutton.type = evtype;
558 event.pbutton.timestamp = timestamp;
559 event.pbutton.windowID = window ? window->id : 0;
560 event.pbutton.which = instance_id;
561 event.pbutton.pen_state = input_state;
562 event.pbutton.x = x;
563 event.pbutton.y = y;
564 event.pbutton.button = button;
565 event.pbutton.down = down;
566 SDL_PushEvent(&event);
567
568 if (window && (pen_touching == instance_id)) {
569 SDL_Mouse *mouse = SDL_GetMouse();
570 if (mouse && mouse->pen_mouse_events) {
571 SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, button + 1, down);
572 }
573 }
574 }
575 }
576}
577
578