#include "SDL_internal.h"
#include "../SDL_hints_c.h"
#include "SDL_events_c.h"
#include "SDL_pen_c.h"
static SDL_PenID pen_touching = 0;
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;
bool pending_proximity_out;
SDL_WindowID pending_proximity_window_id;
void *driverdata;
} SDL_Pen;
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;
static SDL_AtomicInt pending_proximity_out;
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;
}
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_RemoveAllPenDevices(NULL, NULL);
SDL_DestroyRWLock(pen_device_rwlock);
pen_device_rwlock = NULL;
}
#if 0#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));
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_PenDeviceType SDL_GetPenDeviceType(SDL_PenID instance_id)
{
SDL_LockRWLockForReading(pen_device_rwlock);
const SDL_Pen *pen = FindPenByInstanceId(instance_id);
const SDL_PenDeviceType result = pen ? pen->info.device_type : SDL_PEN_DEVICE_TYPE_INVALID;
SDL_UnlockRWLock(pen_device_rwlock);
return result;
}
SDL_PenCapabilityFlags SDL_GetPenCapabilityFromAxis(SDL_PenAxis axis)
{
if ((axis >= SDL_PEN_AXIS_PRESSURE) && (axis <= SDL_PEN_AXIS_SLIDER)) {
return ((SDL_PenCapabilityFlags) 1u) << ((SDL_PenCapabilityFlags) axis);
}
return 0; }
SDL_PenID SDL_AddPenDevice(Uint64 timestamp, const char *name, SDL_Window *window, const SDL_PenInfo *info, void *handle, bool in_proximity)
{
SDL_assert(handle != NULL); SDL_assert(SDL_FindPenByHandle(handle) == 0); SDL_assert(pen_device_rwlock != NULL);
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;
}
SDL_UnlockRWLock(pen_device_rwlock);
if (!pen) {
SDL_free(namecpy);
}
if (result && in_proximity) {
SDL_SendPenProximity(timestamp, result, window, true, true);
}
return result;
}
void SDL_RemovePenDevice(Uint64 timestamp, SDL_Window *window, SDL_PenID instance_id)
{
if (!instance_id) {
return;
}
SDL_SendPenProximity(timestamp, instance_id, window, false, true);
SDL_LockRWLockForWriting(pen_device_rwlock);
SDL_Pen *pen = FindPenByInstanceId(instance_id);
if (pen) {
SDL_free(pen->name);
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); if (ptr) {
pen_devices = (SDL_Pen *) ptr;
}
} else {
SDL_free(pen_devices);
pen_devices = NULL;
}
}
SDL_UnlockRWLock(pen_device_rwlock);
}
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++) {
if (callback) {
callback(pen_devices[i].instance_id, pen_devices[i].driverdata, userdata);
}
SDL_free(pen_devices[i].name);
}
}
SDL_free(pen_devices);
pen_devices = NULL;
pen_device_count = 0;
pen_touching = 0;
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;
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 (!eraser && (input_state & SDL_PEN_INPUT_ERASER_TIP)) {
input_state &= ~SDL_PEN_INPUT_ERASER_TIP;
send_event = true;
}
pen->input_state = input_state; }
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));
bool send_event = false;
SDL_PenInputFlags input_state = 0;
float x = 0.0f;
float y = 0.0f;
SDL_LockRWLockForReading(pen_device_rwlock);
SDL_Pen *pen = FindPenByInstanceId(instance_id);
if (pen) {
if (pen->axes[axis] != value) {
pen->axes[axis] = value; 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);
}
}
}
}
static void EnsurePenProximity(Uint64 timestamp, SDL_Pen *pen, SDL_Window *window)
{
if (pen->pending_proximity_out) {
pen->pending_proximity_out = false;
} else if (!(pen->input_state & SDL_PEN_INPUT_IN_PROXIMITY)) {
SDL_SendPenProximity(timestamp, pen->instance_id, window, true, true);
}
}
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;
SDL_LockRWLockForReading(pen_device_rwlock);
SDL_Pen *pen = FindPenByInstanceId(instance_id);
if (pen) {
EnsurePenProximity(timestamp, pen, window);
if ((pen->x != x) || (pen->y != y)) {
pen->x = x; pen->y = y; 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) { if (mouse->pen_mouse_events) {
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; }
SDL_LockRWLockForReading(pen_device_rwlock);
SDL_Pen *pen = FindPenByInstanceId(instance_id);
if (pen) {
EnsurePenProximity(timestamp, pen, window);
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; }
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 || (pen_touching == instance_id))) {
SDL_Mouse *mouse = SDL_GetMouse();
if (mouse && mouse->pen_mouse_events) {
static const Uint8 mouse_buttons[] = {
SDL_BUTTON_LEFT,
SDL_BUTTON_RIGHT,
SDL_BUTTON_MIDDLE,
SDL_BUTTON_X1,
SDL_BUTTON_X2
};
if (button < SDL_arraysize(mouse_buttons)) {
SDL_SendMouseButton(timestamp, window, SDL_PEN_MOUSEID, mouse_buttons[button], down);
}
}
}
}
}
}
void SDL_SendPenProximity(Uint64 timestamp, SDL_PenID instance_id, SDL_Window *window, bool in, bool immediate)
{
bool send_event = false;
SDL_PenInputFlags input_state = 0;
SDL_LockRWLockForReading(pen_device_rwlock);
SDL_Pen *pen = FindPenByInstanceId(instance_id);
if (pen) {
if (in || immediate) {
input_state = pen->input_state;
const bool in_proximity = ((input_state & SDL_PEN_INPUT_IN_PROXIMITY) != 0);
if (in_proximity != in) {
if (in) {
input_state |= SDL_PEN_INPUT_IN_PROXIMITY;
} else {
input_state &= ~SDL_PEN_INPUT_IN_PROXIMITY;
}
send_event = true;
pen->input_state = input_state; }
pen->pending_proximity_out = false;
} else {
pen->pending_proximity_out = true;
pen->pending_proximity_window_id = (window ? window->id : 0);
SDL_SetAtomicInt(&pending_proximity_out, true);
}
}
SDL_UnlockRWLock(pen_device_rwlock);
const Uint32 event_type = in ? SDL_EVENT_PEN_PROXIMITY_IN : SDL_EVENT_PEN_PROXIMITY_OUT;
if (send_event && SDL_EventEnabled(event_type)) {
SDL_Event event;
SDL_zero(event);
event.pproximity.type = event_type;
event.pproximity.timestamp = timestamp;
event.pproximity.windowID = window ? window->id : 0;
event.pproximity.which = instance_id;
SDL_PushEvent(&event);
}
}
void SDL_SendPendingPenProximity(void)
{
if (SDL_CompareAndSwapAtomicInt(&pending_proximity_out, true, false)) {
SDL_LockRWLockForReading(pen_device_rwlock);
for (int i = 0; i < pen_device_count; i++) {
SDL_Pen *pen = &pen_devices[i];
if (pen->pending_proximity_out) {
pen->pending_proximity_out = false;
SDL_Window *window = NULL;
if (pen->pending_proximity_window_id) {
window = SDL_GetWindowFromID(pen->pending_proximity_window_id);
if (!window) {
continue;
}
}
SDL_SendPenProximity(0, pen->instance_id, window, false, true);
}
}
SDL_UnlockRWLock(pen_device_rwlock);
}
}