#include "../../SDL_internal.h"
#include "../../events/SDL_pen_c.h"
#include "../SDL_sysvideo.h"
#include "SDL_x11pen.h"
#include "SDL_x11video.h"
#include "SDL_x11xinput2.h"
#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
static bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
{
const SDL_VideoData *data = _this->internal;
for (int i = 0; i < dev->num_classes; i++) {
const XIAnyClassInfo *classinfo = dev->classes[i];
if (classinfo->type == XIValuatorClass) {
const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo;
if (val_classinfo->label == data->atoms.pen_atom_abs_pressure) {
return true;
}
}
}
return false;
}
static bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *devicename)
{
#define PEN_ERASER_NAME_TAG "eraser"
SDL_VideoData *data = _this->internal;
if (data->atoms.pen_atom_wacom_tool_type != None) {
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char *tooltype_name_info = NULL;
if (Success == X11_XIGetProperty(data->display, deviceid,
data->atoms.pen_atom_wacom_tool_type,
0, 32, False,
AnyPropertyType, &type_return, &format_return,
&num_items_return, &bytes_after_return,
&tooltype_name_info) &&
tooltype_name_info != NULL && num_items_return > 0) {
bool result = false;
char *tooltype_name = NULL;
if (type_return == XA_ATOM) {
Atom atom = *((Atom *)tooltype_name_info);
if (atom != None) {
tooltype_name = X11_XGetAtomName(data->display, atom);
}
} else if (type_return == XA_STRING && format_return == 8) {
tooltype_name = (char *)tooltype_name_info;
}
if (tooltype_name) {
if (SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG) == 0) {
result = true;
}
if (tooltype_name != (char *)tooltype_name_info) {
X11_XFree(tooltype_name_info);
}
X11_XFree(tooltype_name);
return result;
}
}
}
return (SDL_strcasestr(devicename, PEN_ERASER_NAME_TAG)) ? true : false;
}
static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words)
{
const SDL_VideoData *data = _this->internal;
Atom type_return;
int format_return;
unsigned long num_items_return;
unsigned long bytes_after_return;
unsigned char *output;
if (property == None) {
return 0;
}
if (Success != X11_XIGetProperty(data->display, deviceid,
property,
0, max_words, False,
XA_INTEGER, &type_return, &format_return,
&num_items_return, &bytes_after_return,
&output) ||
num_items_return == 0 || output == NULL) {
return 0;
}
if (type_return == XA_INTEGER) {
int k;
const int to_copy = SDL_min(max_words, num_items_return);
if (format_return == 8) {
Sint8 *numdata = (Sint8 *)output;
for (k = 0; k < to_copy; ++k) {
dest[k] = numdata[k];
}
} else if (format_return == 16) {
Sint16 *numdata = (Sint16 *)output;
for (k = 0; k < to_copy; ++k) {
dest[k] = numdata[k];
}
} else {
SDL_memcpy(dest, output, sizeof(Sint32) * to_copy);
}
X11_XFree(output);
return to_copy;
}
return 0; }
static bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial)
{
SDL_VideoData *data = _this->internal;
Sint32 serial_id_buf[3];
int result;
if ((result = X11_XInput2PenGetIntProperty(_this, deviceid, data->atoms.pen_atom_wacom_serial_ids, serial_id_buf, 3)) == 3) {
*wacom_devicetype_id = serial_id_buf[2];
*wacom_serial = serial_id_buf[1];
return true;
}
*wacom_devicetype_id = *wacom_serial = 0;
return false;
}
static bool X11_XInput2PenIsInProximity(SDL_VideoDevice *_this, int deviceid, bool *in_proximity)
{
SDL_VideoData *data = _this->internal;
Sint32 serial_id_buf[5];
if (X11_XInput2PenGetIntProperty(_this, deviceid, data->atoms.pen_atom_wacom_serial_ids, serial_id_buf, 5) == 5) {
*in_proximity = serial_id_buf[4] != 0 || serial_id_buf[3] != 0;
return true;
}
return false;
}
typedef struct FindPenByDeviceIDData
{
int x11_deviceid;
void *handle;
} FindPenByDeviceIDData;
static bool FindPenByDeviceID(void *handle, void *userdata)
{
const X11_PenHandle *x11_handle = (const X11_PenHandle *) handle;
FindPenByDeviceIDData *data = (FindPenByDeviceIDData *) userdata;
if (x11_handle->x11_deviceid != data->x11_deviceid) {
return false;
}
data->handle = handle;
return true;
}
X11_PenHandle *X11_FindPenByDeviceID(int deviceid)
{
FindPenByDeviceIDData data;
data.x11_deviceid = deviceid;
data.handle = NULL;
SDL_FindPenByCallback(FindPenByDeviceID, &data);
return (X11_PenHandle *) data.handle;
}
static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
{
SDL_VideoData *data = _this->internal;
SDL_PenCapabilityFlags capabilities = 0;
X11_PenHandle *handle = NULL;
if ((dev->use != XISlavePointer && (dev->use != XIFloatingSlave)) || dev->enabled == 0 || !X11_XInput2DeviceIsPen(_this, dev)) {
return NULL; } else if ((handle = X11_FindPenByDeviceID(dev->deviceid)) != NULL) {
return handle; } else if ((handle = SDL_calloc(1, sizeof(*handle))) == NULL) {
return NULL; }
for (int i = 0; i < SDL_arraysize(handle->valuator_for_axis); i++) {
handle->valuator_for_axis[i] = SDL_X11_PEN_AXIS_VALUATOR_MISSING; }
int total_buttons = 0;
for (int i = 0; i < dev->num_classes; i++) {
const XIAnyClassInfo *classinfo = dev->classes[i];
if (classinfo->type == XIButtonClass) {
const XIButtonClassInfo *button_classinfo = (const XIButtonClassInfo *)classinfo;
total_buttons += button_classinfo->num_buttons;
} else if (classinfo->type == XIValuatorClass) {
const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo;
const Sint8 valuator_nr = val_classinfo->number;
const Atom vname = val_classinfo->label;
const float min = (float)val_classinfo->min;
const float max = (float)val_classinfo->max;
bool use_this_axis = true;
SDL_PenAxis axis = SDL_PEN_AXIS_COUNT;
if (vname == data->atoms.pen_atom_abs_pressure) {
axis = SDL_PEN_AXIS_PRESSURE;
} else if (vname == data->atoms.pen_atom_abs_tilt_x) {
axis = SDL_PEN_AXIS_XTILT;
} else if (vname == data->atoms.pen_atom_abs_tilt_y) {
axis = SDL_PEN_AXIS_YTILT;
} else {
use_this_axis = false;
}
if (use_this_axis) {
capabilities |= SDL_GetPenCapabilityFromAxis(axis);
handle->valuator_for_axis[axis] = valuator_nr;
handle->axis_min[axis] = min;
handle->axis_max[axis] = max;
}
}
}
SDL_assert(capabilities & SDL_PEN_CAPABILITY_PRESSURE);
const bool is_eraser = X11_XInput2PenIsEraser(_this, dev->deviceid, dev->name);
Uint32 wacom_devicetype_id = 0;
Uint32 wacom_serial = 0;
X11_XInput2PenWacomDeviceID(_this, dev->deviceid, &wacom_devicetype_id, &wacom_serial);
SDL_PenInfo peninfo;
SDL_zero(peninfo);
peninfo.capabilities = capabilities;
peninfo.max_tilt = -1;
peninfo.wacom_id = wacom_devicetype_id;
peninfo.num_buttons = total_buttons;
peninfo.subtype = is_eraser ? SDL_PEN_TYPE_ERASER : SDL_PEN_TYPE_PEN;
if (is_eraser) {
peninfo.capabilities |= SDL_PEN_CAPABILITY_ERASER;
}
handle->is_eraser = is_eraser;
handle->x11_deviceid = dev->deviceid;
bool in_proximity = false;
if (!X11_XInput2PenIsInProximity(_this, dev->deviceid, &in_proximity)) {
in_proximity = true; }
handle->pen = SDL_AddPenDevice(0, dev->name, NULL, &peninfo, handle, in_proximity);
if (!handle->pen) {
SDL_free(handle);
return NULL;
}
return handle;
}
X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid)
{
if (X11_Xinput2IsInitialized()) {
SDL_VideoData *data = _this->internal;
int num_device_info = 0;
XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info);
if (device_info) {
SDL_assert(num_device_info == 1);
X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info);
X11_XIFreeDeviceInfo(device_info);
return handle;
}
}
return NULL;
}
void X11_RemovePenByDeviceID(int deviceid)
{
X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid);
if (handle) {
SDL_RemovePenDevice(0, NULL, handle->pen);
SDL_free(handle);
}
}
void X11_NotifyPenProximityChange(SDL_VideoDevice *_this, SDL_Window *window, int deviceid)
{
bool in_proximity;
X11_PenHandle *pen = X11_FindPenByDeviceID(deviceid);
if (pen && X11_XInput2PenIsInProximity(_this, deviceid, &in_proximity)) {
SDL_SendPenProximity(0, pen->pen, window, in_proximity, in_proximity);
}
}
void X11_InitPen(SDL_VideoDevice *_this)
{
if (!X11_Xinput2IsInitialized()) {
return; }
SDL_VideoData *data = _this->internal;
#define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False)
data->atoms.pen_atom_device_product_id = LOOKUP_PEN_ATOM("Device Product ID");
data->atoms.pen_atom_wacom_serial_ids = LOOKUP_PEN_ATOM("Wacom Serial IDs");
data->atoms.pen_atom_wacom_tool_type = LOOKUP_PEN_ATOM("Wacom Tool Type");
data->atoms.pen_atom_abs_pressure = LOOKUP_PEN_ATOM("Abs Pressure");
data->atoms.pen_atom_abs_tilt_x = LOOKUP_PEN_ATOM("Abs Tilt X");
data->atoms.pen_atom_abs_tilt_y = LOOKUP_PEN_ATOM("Abs Tilt Y");
#undef LOOKUP_PEN_ATOM
int num_device_info = 0;
XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info);
if (device_info) {
for (int i = 0; i < num_device_info; i++) {
X11_MaybeAddPen(_this, &device_info[i]);
}
X11_XIFreeDeviceInfo(device_info);
}
}
static void X11_FreePenHandle(SDL_PenID instance_id, void *handle, void *userdata)
{
SDL_free(handle);
}
void X11_QuitPen(SDL_VideoDevice *_this)
{
SDL_RemoveAllPenDevices(X11_FreePenHandle, NULL);
}
static void X11_XInput2NormalizePenAxes(const X11_PenHandle *pen, float *coords)
{
for (int axis = 0; axis < SDL_PEN_AXIS_COUNT; ++axis) {
const int valuator = pen->valuator_for_axis[axis];
if (valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) {
continue;
}
float value = coords[axis];
const float min = pen->axis_min[axis];
const float max = pen->axis_max[axis];
if (axis == SDL_PEN_AXIS_SLIDER) {
value += pen->slider_bias;
}
if (min < 0.0) {
if (value < 0) {
value = value / (-min);
} else {
if (max == 0.0f) {
value = 0.0f;
} else {
value = value / max;
}
}
} else {
if (max == 0.0f) {
value = 0.0f;
} else {
value = (value - min) / max;
}
}
switch (axis) {
case SDL_PEN_AXIS_XTILT:
case SDL_PEN_AXIS_YTILT:
break;
case SDL_PEN_AXIS_ROTATION:
value *= 180.0f;
value += pen->rotation_bias;
if (value >= 180.0f) {
value -= 360.0f;
} else if (value < -180.0f) {
value += 360.0f;
}
break;
default:
break;
}
coords[axis] = value;
}
}
void X11_PenAxesFromValuators(const X11_PenHandle *pen,
const double *input_values, const unsigned char *mask, int mask_len,
float axis_values[SDL_PEN_AXIS_COUNT])
{
for (int i = 0; i < SDL_PEN_AXIS_COUNT; i++) {
const int valuator = pen->valuator_for_axis[i];
if ((valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) || (valuator >= mask_len * 8) || !(XIMaskIsSet(mask, valuator))) {
axis_values[i] = 0.0f;
} else {
axis_values[i] = (float)input_values[valuator];
}
}
X11_XInput2NormalizePenAxes(pen, axis_values);
}
#else
void X11_InitPen(SDL_VideoDevice *_this)
{
(void) _this;
}
void X11_QuitPen(SDL_VideoDevice *_this)
{
(void) _this;
}
#endif