| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- /*
- Simple DirectMedia Layer
- Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
- This software is provided 'as-is', without any express or implied
- warranty. In no event will the authors be held liable for any damages
- arising from the use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software
- in a product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not be
- misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source distribution.
- */
- #include "SDL_internal.h"
- // Pressure-sensitive pen handling code for SDL
- #include "../SDL_hints_c.h"
- #include "SDL_events_c.h"
- #include "SDL_pen_c.h"
- static SDL_PenID pen_touching = 0; // used for synthetic mouse/touch events.
- typedef struct SDL_Pen
- {
- SDL_PenID instance_id;
- char *name;
- SDL_PenInfo info;
- float axes[SDL_PEN_AXIS_COUNT];
- float x;
- float y;
- SDL_PenInputFlags input_state;
- void *driverdata;
- } SDL_Pen;
- // we assume there's usually 0-1 pens in most cases and this list doesn't
- // usually change after startup, so a simple array with a RWlock is fine for now.
- static SDL_RWLock *pen_device_rwlock = NULL;
- static SDL_Pen *pen_devices SDL_GUARDED_BY(pen_device_rwlock) = NULL;
- static int pen_device_count SDL_GUARDED_BY(pen_device_rwlock) = 0;
- // You must hold pen_device_rwlock before calling this, and result is only safe while lock is held!
- // If SDL isn't initialized, grabbing the NULL lock is a no-op and there will be zero devices, so
- // locking and calling this in that case will do the right thing.
- static SDL_Pen *FindPenByInstanceId(SDL_PenID instance_id) SDL_REQUIRES_SHARED(pen_device_rwlock)
- {
- if (instance_id) {
- for (int i = 0; i < pen_device_count; i++) {
- if (pen_devices[i].instance_id == instance_id) {
- return &pen_devices[i];
- }
- }
- }
- SDL_SetError("Invalid pen instance ID");
- return NULL;
- }
- SDL_PenID SDL_FindPenByHandle(void *handle)
- {
- SDL_PenID result = 0;
- SDL_LockRWLockForReading(pen_device_rwlock);
- for (int i = 0; i < pen_device_count; i++) {
- if (pen_devices[i].driverdata == handle) {
- result = pen_devices[i].instance_id;
- break;
- }
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- return result;
- }
- SDL_PenID SDL_FindPenByCallback(bool (*callback)(void *handle, void *userdata), void *userdata)
- {
- SDL_PenID result = 0;
- SDL_LockRWLockForReading(pen_device_rwlock);
- for (int i = 0; i < pen_device_count; i++) {
- if (callback(pen_devices[i].driverdata, userdata)) {
- result = pen_devices[i].instance_id;
- break;
- }
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- return result;
- }
- // public API ...
- bool SDL_InitPen(void)
- {
- SDL_assert(pen_device_rwlock == NULL);
- SDL_assert(pen_devices == NULL);
- SDL_assert(pen_device_count == 0);
- pen_device_rwlock = SDL_CreateRWLock();
- if (!pen_device_rwlock) {
- return false;
- }
- return true;
- }
- void SDL_QuitPen(void)
- {
- SDL_DestroyRWLock(pen_device_rwlock);
- pen_device_rwlock = NULL;
- if (pen_devices) {
- for (int i = pen_device_count; i--; ) {
- SDL_free(pen_devices[i].name);
- }
- SDL_free(pen_devices);
- pen_devices = NULL;
- }
- pen_device_count = 0;
- pen_touching = 0;
- }
- #if 0 // not a public API at the moment.
- SDL_PenID *SDL_GetPens(int *count)
- {
- SDL_LockRWLockForReading(pen_device_rwlock);
- const int num_devices = pen_device_count;
- SDL_PenID *result = (SDL_PenID *) SDL_malloc((num_devices + 1) * sizeof (SDL_PenID));
- if (result) {
- for (int i = 0; i < num_devices; i++) {
- result[i] = pen_devices[i].instance_id;
- }
- result[num_devices] = 0; // null-terminated.
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- if (count) {
- *count = result ? num_devices : 0;
- }
- return result;
- }
- const char *SDL_GetPenName(SDL_PenID instance_id)
- {
- SDL_LockRWLockForReading(pen_device_rwlock);
- const SDL_Pen *pen = FindPenByInstanceId(instance_id);
- const char *result = pen ? SDL_GetPersistentString(pen->name) : NULL;
- SDL_UnlockRWLock(pen_device_rwlock);
- return result;
- }
- bool SDL_GetPenInfo(SDL_PenID instance_id, SDL_PenInfo *info)
- {
- SDL_LockRWLockForReading(pen_device_rwlock);
- const SDL_Pen *pen = FindPenByInstanceId(instance_id);
- const bool result = pen ? true : false;
- if (info) {
- if (result) {
- SDL_copyp(info, &pen->info);
- } else {
- SDL_zerop(info);
- }
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- return result;
- }
- bool SDL_PenConnected(SDL_PenID instance_id)
- {
- SDL_LockRWLockForReading(pen_device_rwlock);
- const SDL_Pen *pen = FindPenByInstanceId(instance_id);
- const bool result = (pen != NULL);
- SDL_UnlockRWLock(pen_device_rwlock);
- return result;
- }
- #endif
- SDL_PenInputFlags SDL_GetPenStatus(SDL_PenID instance_id, float *axes, int num_axes)
- {
- if (num_axes < 0) {
- num_axes = 0;
- }
- SDL_LockRWLockForReading(pen_device_rwlock);
- const SDL_Pen *pen = FindPenByInstanceId(instance_id);
- SDL_PenInputFlags result = 0;
- if (pen) {
- result = pen->input_state;
- if (axes && num_axes) {
- SDL_memcpy(axes, pen->axes, SDL_min(num_axes, SDL_PEN_AXIS_COUNT) * sizeof (*axes));
- // zero out axes we don't know about, in case the caller built with newer SDL headers that support more of them.
- if (num_axes > SDL_PEN_AXIS_COUNT) {
- SDL_memset(&axes[SDL_PEN_AXIS_COUNT], '\0', (num_axes - SDL_PEN_AXIS_COUNT) * sizeof (*axes));
- }
- }
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- return result;
- }
- SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis)
- {
- // the initial capability bits happen to match up, but as
- // more features show up later, the bits may no longer be contiguous!
- if ((axis >= SDL_PEN_AXIS_PRESSURE) && (axis <= SDL_PEN_AXIS_SLIDER)) {
- return ((SDL_PenCapabilityFlags) 1u) << ((SDL_PenCapabilityFlags) axis);
- }
- return 0; // oh well.
- }
- SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, const SDL_PenInfo *info, void *handle)
- {
- SDL_assert(handle != NULL); // just allocate a Uint8 so you have a unique pointer if not needed!
- SDL_assert(SDL_FindPenByHandle(handle) == 0); // Backends shouldn't double-add pens!
- SDL_assert(pen_device_rwlock != NULL); // subsystem should be initialized by now!
- char *namecpy = SDL_strdup(name ? name : "Unnamed pen");
- if (!namecpy) {
- return 0;
- }
- SDL_PenID result = 0;
- SDL_LockRWLockForWriting(pen_device_rwlock);
- SDL_Pen *pen = NULL;
- void *ptr = SDL_realloc(pen_devices, (pen_device_count + 1) * sizeof (*pen));
- if (ptr) {
- result = (SDL_PenID) SDL_GetNextObjectID();
- pen_devices = (SDL_Pen *) ptr;
- pen = &pen_devices[pen_device_count];
- pen_device_count++;
- SDL_zerop(pen);
- pen->instance_id = result;
- pen->name = namecpy;
- if (info) {
- SDL_copyp(&pen->info, info);
- }
- pen->driverdata = handle;
- // axes and input state defaults to zero.
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- if (!pen) {
- SDL_free(namecpy);
- }
- if (result && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_IN)) {
- SDL_Event event;
- SDL_zero(event);
- event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_IN;
- event.pproximity.timestamp = timestamp;
- event.pproximity.which = result;
- SDL_PushEvent(&event);
- }
- return result;
- }
- void SDL_RemovePenDevice(Uint64 timestamp, SDL_PenID instance_id)
- {
- if (!instance_id) {
- return;
- }
- SDL_LockRWLockForWriting(pen_device_rwlock);
- SDL_Pen *pen = FindPenByInstanceId(instance_id);
- if (pen) {
- SDL_free(pen->name);
- // we don't free `pen`, it's just part of simple array. Shuffle it out.
- const int idx = ((int) (pen - pen_devices));
- SDL_assert((idx >= 0) && (idx < pen_device_count));
- if ( idx < (pen_device_count - 1) ) {
- SDL_memmove(&pen_devices[idx], &pen_devices[idx + 1], sizeof (*pen) * ((pen_device_count - idx) - 1));
- }
- SDL_assert(pen_device_count > 0);
- pen_device_count--;
- if (pen_device_count) {
- void *ptr = SDL_realloc(pen_devices, sizeof (*pen) * pen_device_count); // shrink it down.
- if (ptr) {
- pen_devices = (SDL_Pen *) ptr;
- }
- } else {
- SDL_free(pen_devices);
- pen_devices = NULL;
- }
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- if (pen && SDL_EventEnabled(SDL_EVENT_PEN_PROXIMITY_OUT)) {
- SDL_Event event;
- SDL_zero(event);
- event.pproximity.type = SDL_EVENT_PEN_PROXIMITY_OUT;
- event.pproximity.timestamp = timestamp;
- event.pproximity.which = instance_id;
- SDL_PushEvent(&event);
- }
- }
- // This presumably is happening during video quit, so we don't send PROXIMITY_OUT events here.
- void SDL_RemoveAllPenDevices(void (*callback)(SDL_PenID instance_id, void *handle, void *userdata), void *userdata)
- {
- SDL_LockRWLockForWriting(pen_device_rwlock);
- if (pen_device_count > 0) {
- SDL_assert(pen_devices != NULL);
- for (int i = 0; i < pen_device_count; i++) {
- callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata);
- SDL_free(pen_devices[i].name);
- }
- }
- SDL_free(pen_devices);
- pen_devices = NULL;
- SDL_UnlockRWLock(pen_device_rwlock);
- }
- void SDL_SendPenTouch(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool eraser, bool down)
- {
- bool send_event = false;
- SDL_PenInputFlags input_state = 0;
- float x = 0.0f;
- float y = 0.0f;
- // note that this locks for _reading_ because the lock protects the
- // pen_devices array from being reallocated from under us, not the data in it;
- // we assume only one thread (in the backend) is modifying an individual pen at
- // a time, so it can update input state cleanly here.
- SDL_LockRWLockForReading(pen_device_rwlock);
- SDL_Pen *pen = FindPenByInstanceId(instance_id);
- if (pen) {
- input_state = pen->input_state;
- x = pen->x;
- y = pen->y;
- if (down && ((input_state & SDL_PEN_INPUT_DOWN) == 0)) {
- input_state |= SDL_PEN_INPUT_DOWN;
- send_event = true;
- } else if (!down && (input_state & SDL_PEN_INPUT_DOWN)) {
- input_state &= ~SDL_PEN_INPUT_DOWN;
- send_event = true;
- }
- if (eraser && ((input_state & SDL_PEN_INPUT_ERASER_TIP) == 0)) {
- input_state |= SDL_PEN_INPUT_ERASER_TIP;
- send_event = true;
- } else if (!down && (input_state & SDL_PEN_INPUT_ERASER_TIP)) {
- input_state &= ~SDL_PEN_INPUT_ERASER_TIP;
- send_event = true;
- }
- pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble...
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- if (send_event) {
- const SDL_EventType evtype = down ? SDL_EVENT_PEN_DOWN : SDL_EVENT_PEN_UP;
- if (SDL_EventEnabled(evtype)) {
- SDL_Event event;
- SDL_zero(event);
- event.ptouch.type = evtype;
- event.ptouch.timestamp = timestamp;
- event.ptouch.windowID = window ? window->id : 0;
- event.ptouch.which = instance_id;
- event.ptouch.pen_state = input_state;
- event.ptouch.x = x;
- event.ptouch.y = y;
- event.ptouch.eraser = eraser;
- event.ptouch.down = down;
- SDL_PushEvent(&event);
- }
- SDL_Mouse *mouse = SDL_GetMouse();
- if (mouse && window) {
- if (mouse->pen_mouse_events) {
- if (down) {
- if (!pen_touching) {
- SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y);
- SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, SDL_BUTTON_LEFT, true);
- }
- } else {
- if (pen_touching == instance_id) {
- SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, SDL_BUTTON_LEFT, false);
- }
- }
- }
- if (mouse->pen_touch_events) {
- const SDL_EventType touchtype = down ? SDL_EVENT_FINGER_DOWN : SDL_EVENT_FINGER_UP;
- const float normalized_x = x / (float)window->w;
- const float normalized_y = y / (float)window->h;
- if (!pen_touching || (pen_touching == instance_id)) {
- SDL_SendTouch(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, touchtype, normalized_x, normalized_y, pen->axes[SDL_PEN_AXIS_PRESSURE]);
- }
- }
- }
- if (down) {
- if (!pen_touching) {
- pen_touching = instance_id;
- }
- } else {
- if (pen_touching == instance_id) {
- pen_touching = 0;
- }
- }
- }
- }
- void SDL_SendPenAxis(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, SDL_PenAxis axis, float value)
- {
- SDL_assert((axis >= 0) && (axis < SDL_PEN_AXIS_COUNT)); // fix the backend if this triggers.
- bool send_event = false;
- SDL_PenInputFlags input_state = 0;
- float x = 0.0f;
- float y = 0.0f;
- // note that this locks for _reading_ because the lock protects the
- // pen_devices array from being reallocated from under us, not the data in it;
- // we assume only one thread (in the backend) is modifying an individual pen at
- // a time, so it can update input state cleanly here.
- SDL_LockRWLockForReading(pen_device_rwlock);
- SDL_Pen *pen = FindPenByInstanceId(instance_id);
- if (pen) {
- if (pen->axes[axis] != value) {
- pen->axes[axis] = value; // we could do an SDL_SetAtomicInt here if we run into trouble...
- input_state = pen->input_state;
- x = pen->x;
- y = pen->y;
- send_event = true;
- }
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- if (send_event && SDL_EventEnabled(SDL_EVENT_PEN_AXIS)) {
- SDL_Event event;
- SDL_zero(event);
- event.paxis.type = SDL_EVENT_PEN_AXIS;
- event.paxis.timestamp = timestamp;
- event.paxis.windowID = window ? window->id : 0;
- event.paxis.which = instance_id;
- event.paxis.pen_state = input_state;
- event.paxis.x = x;
- event.paxis.y = y;
- event.paxis.axis = axis;
- event.paxis.value = value;
- SDL_PushEvent(&event);
- if (window && (axis == SDL_PEN_AXIS_PRESSURE) && (pen_touching == instance_id)) {
- SDL_Mouse *mouse = SDL_GetMouse();
- if (mouse && mouse->pen_touch_events) {
- const float normalized_x = x / (float)window->w;
- const float normalized_y = y / (float)window->h;
- SDL_SendTouchMotion(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, normalized_x, normalized_y, value);
- }
- }
- }
- }
- void SDL_SendPenMotion(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, float x, float y)
- {
- bool send_event = false;
- SDL_PenInputFlags input_state = 0;
- // note that this locks for _reading_ because the lock protects the
- // pen_devices array from being reallocated from under us, not the data in it;
- // we assume only one thread (in the backend) is modifying an individual pen at
- // a time, so it can update input state cleanly here.
- SDL_LockRWLockForReading(pen_device_rwlock);
- SDL_Pen *pen = FindPenByInstanceId(instance_id);
- if (pen) {
- if ((pen->x != x) || (pen->y != y)) {
- pen->x = x; // we could do an SDL_SetAtomicInt here if we run into trouble...
- pen->y = y; // we could do an SDL_SetAtomicInt here if we run into trouble...
- input_state = pen->input_state;
- send_event = true;
- }
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- if (send_event && SDL_EventEnabled(SDL_EVENT_PEN_MOTION)) {
- SDL_Event event;
- SDL_zero(event);
- event.pmotion.type = SDL_EVENT_PEN_MOTION;
- event.pmotion.timestamp = timestamp;
- event.pmotion.windowID = window ? window->id : 0;
- event.pmotion.which = instance_id;
- event.pmotion.pen_state = input_state;
- event.pmotion.x = x;
- event.pmotion.y = y;
- SDL_PushEvent(&event);
- if (window) {
- SDL_Mouse *mouse = SDL_GetMouse();
- if (mouse) {
- if (pen_touching == instance_id) {
- if (mouse->pen_mouse_events) {
- SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y);
- }
- if (mouse->pen_touch_events) {
- const float normalized_x = x / (float)window->w;
- const float normalized_y = y / (float)window->h;
- SDL_SendTouchMotion(timestamp, SDL_PEN_TOUCHID, SDL_BUTTON_LEFT, window, normalized_x, normalized_y, pen->axes[SDL_PEN_AXIS_PRESSURE]);
- }
- } else if (pen_touching == 0) { // send mouse motion (without a pressed button) for pens that aren't touching.
- // 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. :)
- SDL_SendMouseMotion(timestamp, window, SDL_PEN_MOUSEID, false, x, y);
- }
- }
- }
- }
- }
- void SDL_SendPenButton(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, Uint8 button, bool down)
- {
- bool send_event = false;
- SDL_PenInputFlags input_state = 0;
- float x = 0.0f;
- float y = 0.0f;
- if ((button < 1) || (button > 5)) {
- return; // clamp for now.
- }
- // note that this locks for _reading_ because the lock protects the
- // pen_devices array from being reallocated from under us, not the data in it;
- // we assume only one thread (in the backend) is modifying an individual pen at
- // a time, so it can update input state cleanly here.
- SDL_LockRWLockForReading(pen_device_rwlock);
- SDL_Pen *pen = FindPenByInstanceId(instance_id);
- if (pen) {
- input_state = pen->input_state;
- const Uint32 flag = (Uint32) (1u << button);
- const bool current = ((input_state & flag) != 0);
- x = pen->x;
- y = pen->y;
- if (down && !current) {
- input_state |= flag;
- send_event = true;
- } else if (!down && current) {
- input_state &= ~flag;
- send_event = true;
- }
- pen->input_state = input_state; // we could do an SDL_SetAtomicInt here if we run into trouble...
- }
- SDL_UnlockRWLock(pen_device_rwlock);
- if (send_event) {
- const SDL_EventType evtype = down ? SDL_EVENT_PEN_BUTTON_DOWN : SDL_EVENT_PEN_BUTTON_UP;
- if (SDL_EventEnabled(evtype)) {
- SDL_Event event;
- SDL_zero(event);
- event.pbutton.type = evtype;
- event.pbutton.timestamp = timestamp;
- event.pbutton.windowID = window ? window->id : 0;
- event.pbutton.which = instance_id;
- event.pbutton.pen_state = input_state;
- event.pbutton.x = x;
- event.pbutton.y = y;
- event.pbutton.button = button;
- event.pbutton.down = down;
- SDL_PushEvent(&event);
- if (window && (pen_touching == instance_id)) {
- SDL_Mouse *mouse = SDL_GetMouse();
- if (mouse && mouse->pen_mouse_events) {
- SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, button + 1, down);
- }
- }
- }
- }
- }
|