Atlas - SDL_x11pen.c
Home / ext / SDL / src / video / x11 Lines: 1 | Size: 16630 bytes [Download] [Show on GitHub] [Search similar files] [Raw] [Raw (proxy)][FILE BEGIN]1/* 2 Simple DirectMedia Layer 3 Copyright (C) 1997-2025 Sam Lantinga <[email protected]> 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#include "../../events/SDL_pen_c.h" 24#include "../SDL_sysvideo.h" 25#include "SDL_x11pen.h" 26#include "SDL_x11video.h" 27#include "SDL_x11xinput2.h" 28 29#ifdef SDL_VIDEO_DRIVER_X11_XINPUT2 30 31// Does this device have a valuator for pressure sensitivity? 32static bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) 33{ 34 const SDL_VideoData *data = _this->internal; 35 for (int i = 0; i < dev->num_classes; i++) { 36 const XIAnyClassInfo *classinfo = dev->classes[i]; 37 if (classinfo->type == XIValuatorClass) { 38 const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo; 39 if (val_classinfo->label == data->atoms.pen_atom_abs_pressure) { 40 return true; 41 } 42 } 43 } 44 45 return false; 46} 47 48// Heuristically determines if device is an eraser 49static bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *devicename) 50{ 51 #define PEN_ERASER_NAME_TAG "eraser" // String constant to identify erasers 52 SDL_VideoData *data = _this->internal; 53 54 if (data->atoms.pen_atom_wacom_tool_type != None) { 55 Atom type_return; 56 int format_return; 57 unsigned long num_items_return; 58 unsigned long bytes_after_return; 59 unsigned char *tooltype_name_info = NULL; 60 61 // Try Wacom-specific method 62 if (Success == X11_XIGetProperty(data->display, deviceid, 63 data->atoms.pen_atom_wacom_tool_type, 64 0, 32, False, 65 AnyPropertyType, &type_return, &format_return, 66 &num_items_return, &bytes_after_return, 67 &tooltype_name_info) && 68 tooltype_name_info != NULL && num_items_return > 0) { 69 70 bool result = false; 71 char *tooltype_name = NULL; 72 73 if (type_return == XA_ATOM) { 74 // Atom instead of string? Un-intern 75 Atom atom = *((Atom *)tooltype_name_info); 76 if (atom != None) { 77 tooltype_name = X11_XGetAtomName(data->display, atom); 78 } 79 } else if (type_return == XA_STRING && format_return == 8) { 80 tooltype_name = (char *)tooltype_name_info; 81 } 82 83 if (tooltype_name) { 84 if (SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG) == 0) { 85 result = true; 86 } 87 if (tooltype_name != (char *)tooltype_name_info) { 88 X11_XFree(tooltype_name_info); 89 } 90 X11_XFree(tooltype_name); 91 92 return result; 93 } 94 } 95 } 96 97 // Non-Wacom device? 98 99 /* We assume that a device is an eraser if its name contains the string "eraser". 100 * Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */ 101 return (SDL_strcasestr(devicename, PEN_ERASER_NAME_TAG)) ? true : false; 102} 103 104// Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably. 105// Returns number of Sint32s written (<= max_words), or 0 on error. 106static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words) 107{ 108 const SDL_VideoData *data = _this->internal; 109 Atom type_return; 110 int format_return; 111 unsigned long num_items_return; 112 unsigned long bytes_after_return; 113 unsigned char *output; 114 115 if (property == None) { 116 return 0; 117 } 118 119 if (Success != X11_XIGetProperty(data->display, deviceid, 120 property, 121 0, max_words, False, 122 XA_INTEGER, &type_return, &format_return, 123 &num_items_return, &bytes_after_return, 124 &output) || 125 num_items_return == 0 || output == NULL) { 126 return 0; 127 } 128 129 if (type_return == XA_INTEGER) { 130 int k; 131 const int to_copy = SDL_min(max_words, num_items_return); 132 133 if (format_return == 8) { 134 Sint8 *numdata = (Sint8 *)output; 135 for (k = 0; k < to_copy; ++k) { 136 dest[k] = numdata[k]; 137 } 138 } else if (format_return == 16) { 139 Sint16 *numdata = (Sint16 *)output; 140 for (k = 0; k < to_copy; ++k) { 141 dest[k] = numdata[k]; 142 } 143 } else { 144 SDL_memcpy(dest, output, sizeof(Sint32) * to_copy); 145 } 146 X11_XFree(output); 147 return to_copy; 148 } 149 150 return 0; // type mismatch 151} 152 153// Identify Wacom devices (if true is returned) and extract their device type and serial IDs 154static bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial) 155{ 156 SDL_VideoData *data = _this->internal; 157 Sint32 serial_id_buf[3]; 158 int result; 159 160 if ((result = X11_XInput2PenGetIntProperty(_this, deviceid, data->atoms.pen_atom_wacom_serial_ids, serial_id_buf, 3)) == 3) { 161 *wacom_devicetype_id = serial_id_buf[2]; 162 *wacom_serial = serial_id_buf[1]; 163 return true; 164 } 165 166 *wacom_devicetype_id = *wacom_serial = 0; 167 return false; 168} 169 170// Check if a Wacom device is in proximity of the tablet 171static bool X11_XInput2PenIsInProximity(SDL_VideoDevice *_this, int deviceid, bool *in_proximity) 172{ 173 SDL_VideoData *data = _this->internal; 174 Sint32 serial_id_buf[5]; 175 if (X11_XInput2PenGetIntProperty(_this, deviceid, data->atoms.pen_atom_wacom_serial_ids, serial_id_buf, 5) == 5) { 176 *in_proximity = serial_id_buf[4] != 0 || serial_id_buf[3] != 0; 177 return true; 178 } 179 return false; 180} 181 182 183typedef struct FindPenByDeviceIDData 184{ 185 int x11_deviceid; 186 void *handle; 187} FindPenByDeviceIDData; 188 189static bool FindPenByDeviceID(void *handle, void *userdata) 190{ 191 const X11_PenHandle *x11_handle = (const X11_PenHandle *) handle; 192 FindPenByDeviceIDData *data = (FindPenByDeviceIDData *) userdata; 193 if (x11_handle->x11_deviceid != data->x11_deviceid) { 194 return false; 195 } 196 data->handle = handle; 197 return true; 198} 199 200X11_PenHandle *X11_FindPenByDeviceID(int deviceid) 201{ 202 FindPenByDeviceIDData data; 203 data.x11_deviceid = deviceid; 204 data.handle = NULL; 205 SDL_FindPenByCallback(FindPenByDeviceID, &data); 206 return (X11_PenHandle *) data.handle; 207} 208 209static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev) 210{ 211 SDL_VideoData *data = _this->internal; 212 SDL_PenCapabilityFlags capabilities = 0; 213 X11_PenHandle *handle = NULL; 214 215 if ((dev->use != XISlavePointer && (dev->use != XIFloatingSlave)) || dev->enabled == 0 || !X11_XInput2DeviceIsPen(_this, dev)) { 216 return NULL; // Only track physical devices that are enabled and look like pens 217 } else if ((handle = X11_FindPenByDeviceID(dev->deviceid)) != NULL) { 218 return handle; // already have this pen, skip it. 219 } else if ((handle = SDL_calloc(1, sizeof(*handle))) == NULL) { 220 return NULL; // oh well. 221 } 222 223 for (int i = 0; i < SDL_arraysize(handle->valuator_for_axis); i++) { 224 handle->valuator_for_axis[i] = SDL_X11_PEN_AXIS_VALUATOR_MISSING; // until proven otherwise 225 } 226 227 int total_buttons = 0; 228 for (int i = 0; i < dev->num_classes; i++) { 229 const XIAnyClassInfo *classinfo = dev->classes[i]; 230 if (classinfo->type == XIButtonClass) { 231 const XIButtonClassInfo *button_classinfo = (const XIButtonClassInfo *)classinfo; 232 total_buttons += button_classinfo->num_buttons; 233 } else if (classinfo->type == XIValuatorClass) { 234 const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo; 235 const Sint8 valuator_nr = val_classinfo->number; 236 const Atom vname = val_classinfo->label; 237 const float min = (float)val_classinfo->min; 238 const float max = (float)val_classinfo->max; 239 bool use_this_axis = true; 240 SDL_PenAxis axis = SDL_PEN_AXIS_COUNT; 241 242 // afaict, SDL_PEN_AXIS_DISTANCE is never reported by XInput2 (Wayland can offer it, though) 243 if (vname == data->atoms.pen_atom_abs_pressure) { 244 axis = SDL_PEN_AXIS_PRESSURE; 245 } else if (vname == data->atoms.pen_atom_abs_tilt_x) { 246 axis = SDL_PEN_AXIS_XTILT; 247 } else if (vname == data->atoms.pen_atom_abs_tilt_y) { 248 axis = SDL_PEN_AXIS_YTILT; 249 } else { 250 use_this_axis = false; 251 } 252 253 // !!! FIXME: there are wacom-specific hacks for getting SDL_PEN_AXIS_(ROTATION|SLIDER) on some devices, but for simplicity, we're skipping all that for now. 254 255 if (use_this_axis) { 256 capabilities |= SDL_GetPenCapabilityFromAxis(axis); 257 handle->valuator_for_axis[axis] = valuator_nr; 258 handle->axis_min[axis] = min; 259 handle->axis_max[axis] = max; 260 } 261 } 262 } 263 264 // We have a pen if and only if the device measures pressure. 265 // We checked this in X11_XInput2DeviceIsPen, so just assert it here. 266 SDL_assert(capabilities & SDL_PEN_CAPABILITY_PRESSURE); 267 268 const bool is_eraser = X11_XInput2PenIsEraser(_this, dev->deviceid, dev->name); 269 Uint32 wacom_devicetype_id = 0; 270 Uint32 wacom_serial = 0; 271 X11_XInput2PenWacomDeviceID(_this, dev->deviceid, &wacom_devicetype_id, &wacom_serial); 272 273 SDL_PenInfo peninfo; 274 SDL_zero(peninfo); 275 peninfo.capabilities = capabilities; 276 peninfo.max_tilt = -1; 277 peninfo.wacom_id = wacom_devicetype_id; 278 peninfo.num_buttons = total_buttons; 279 peninfo.subtype = is_eraser ? SDL_PEN_TYPE_ERASER : SDL_PEN_TYPE_PEN; 280 if (is_eraser) { 281 peninfo.capabilities |= SDL_PEN_CAPABILITY_ERASER; 282 } 283 284 handle->is_eraser = is_eraser; 285 handle->x11_deviceid = dev->deviceid; 286 287 bool in_proximity = false; 288 if (!X11_XInput2PenIsInProximity(_this, dev->deviceid, &in_proximity)) { 289 in_proximity = true; // just say it's in proximity if we can't detect this state. 290 } 291 292 handle->pen = SDL_AddPenDevice(0, dev->name, NULL, &peninfo, handle, in_proximity); 293 if (!handle->pen) { 294 SDL_free(handle); 295 return NULL; 296 } 297 298 return handle; 299} 300 301X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid) 302{ 303 if (X11_Xinput2IsInitialized()) { 304 SDL_VideoData *data = _this->internal; 305 int num_device_info = 0; 306 XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info); 307 if (device_info) { 308 SDL_assert(num_device_info == 1); 309 X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info); 310 X11_XIFreeDeviceInfo(device_info); 311 return handle; 312 } 313 } 314 return NULL; 315} 316 317void X11_RemovePenByDeviceID(int deviceid) 318{ 319 X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid); 320 if (handle) { 321 SDL_RemovePenDevice(0, NULL, handle->pen); 322 SDL_free(handle); 323 } 324} 325 326void X11_NotifyPenProximityChange(SDL_VideoDevice *_this, SDL_Window *window, int deviceid) 327{ 328 bool in_proximity; 329 X11_PenHandle *pen = X11_FindPenByDeviceID(deviceid); 330 if (pen && X11_XInput2PenIsInProximity(_this, deviceid, &in_proximity)) { 331 SDL_SendPenProximity(0, pen->pen, window, in_proximity); 332 } 333} 334 335void X11_InitPen(SDL_VideoDevice *_this) 336{ 337 if (!X11_Xinput2IsInitialized()) { 338 return; // we need XIQueryDevice() for this. 339 } 340 341 SDL_VideoData *data = _this->internal; 342 343 #define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False) 344 data->atoms.pen_atom_device_product_id = LOOKUP_PEN_ATOM("Device Product ID"); 345 data->atoms.pen_atom_wacom_serial_ids = LOOKUP_PEN_ATOM("Wacom Serial IDs"); 346 data->atoms.pen_atom_wacom_tool_type = LOOKUP_PEN_ATOM("Wacom Tool Type"); 347 data->atoms.pen_atom_abs_pressure = LOOKUP_PEN_ATOM("Abs Pressure"); 348 data->atoms.pen_atom_abs_tilt_x = LOOKUP_PEN_ATOM("Abs Tilt X"); 349 data->atoms.pen_atom_abs_tilt_y = LOOKUP_PEN_ATOM("Abs Tilt Y"); 350 #undef LOOKUP_PEN_ATOM 351 352 // Do an initial check on devices. After this, we'll add/remove individual pens when XI_HierarchyChanged events alert us. 353 int num_device_info = 0; 354 XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info); 355 if (device_info) { 356 for (int i = 0; i < num_device_info; i++) { 357 X11_MaybeAddPen(_this, &device_info[i]); 358 } 359 X11_XIFreeDeviceInfo(device_info); 360 } 361} 362 363static void X11_FreePenHandle(SDL_PenID instance_id, void *handle, void *userdata) 364{ 365 SDL_free(handle); 366} 367 368void X11_QuitPen(SDL_VideoDevice *_this) 369{ 370 SDL_RemoveAllPenDevices(X11_FreePenHandle, NULL); 371} 372 373static void X11_XInput2NormalizePenAxes(const X11_PenHandle *pen, float *coords) 374{ 375 // Normalise axes 376 for (int axis = 0; axis < SDL_PEN_AXIS_COUNT; ++axis) { 377 const int valuator = pen->valuator_for_axis[axis]; 378 if (valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) { 379 continue; 380 } 381 382 float value = coords[axis]; 383 const float min = pen->axis_min[axis]; 384 const float max = pen->axis_max[axis]; 385 386 if (axis == SDL_PEN_AXIS_SLIDER) { 387 value += pen->slider_bias; 388 } 389 390 // min ... 0 ... max 391 if (min < 0.0) { 392 // Normalise so that 0 remains 0.0 393 if (value < 0) { 394 value = value / (-min); 395 } else { 396 if (max == 0.0f) { 397 value = 0.0f; 398 } else { 399 value = value / max; 400 } 401 } 402 } else { 403 // 0 ... min ... max 404 // including 0.0 = min 405 if (max == 0.0f) { 406 value = 0.0f; 407 } else { 408 value = (value - min) / max; 409 } 410 } 411 412 switch (axis) { 413 case SDL_PEN_AXIS_XTILT: 414 case SDL_PEN_AXIS_YTILT: 415 //if (peninfo->info.max_tilt > 0.0f) { 416 // value *= peninfo->info.max_tilt; // normalize to physical max 417 //} 418 break; 419 420 case SDL_PEN_AXIS_ROTATION: 421 // normalised to -1..1, so let's convert to degrees 422 value *= 180.0f; 423 value += pen->rotation_bias; 424 425 // handle simple over/underflow 426 if (value >= 180.0f) { 427 value -= 360.0f; 428 } else if (value < -180.0f) { 429 value += 360.0f; 430 } 431 break; 432 433 default: 434 break; 435 } 436 437 coords[axis] = value; 438 } 439} 440 441void X11_PenAxesFromValuators(const X11_PenHandle *pen, 442 const double *input_values, const unsigned char *mask, int mask_len, 443 float axis_values[SDL_PEN_AXIS_COUNT]) 444{ 445 for (int i = 0; i < SDL_PEN_AXIS_COUNT; i++) { 446 const int valuator = pen->valuator_for_axis[i]; 447 if ((valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) || (valuator >= mask_len * 8) || !(XIMaskIsSet(mask, valuator))) { 448 axis_values[i] = 0.0f; 449 } else { 450 axis_values[i] = (float)input_values[valuator]; 451 } 452 } 453 X11_XInput2NormalizePenAxes(pen, axis_values); 454} 455 456#else 457 458void X11_InitPen(SDL_VideoDevice *_this) 459{ 460 (void) _this; 461} 462 463void X11_QuitPen(SDL_VideoDevice *_this) 464{ 465 (void) _this; 466} 467 468#endif // SDL_VIDEO_DRIVER_X11_XINPUT2 469 470[FILE END](C) 2025 0x4248 (C) 2025 4248 Media and 4248 Systems, All part of 0x4248 See LICENCE files for more information. Not all files are by 0x4248 always check Licencing.