SDL_x11pen.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. /*
  2. Simple DirectMedia Layer
  3. Copyright (C) 1997-2025 Sam Lantinga <[email protected]>
  4. This software is provided 'as-is', without any express or implied
  5. warranty. In no event will the authors be held liable for any damages
  6. arising from the use of this software.
  7. Permission is granted to anyone to use this software for any purpose,
  8. including commercial applications, and to alter it and redistribute it
  9. freely, subject to the following restrictions:
  10. 1. The origin of this software must not be misrepresented; you must not
  11. claim that you wrote the original software. If you use this software
  12. in a product, an acknowledgment in the product documentation would be
  13. appreciated but is not required.
  14. 2. Altered source versions must be plainly marked as such, and must not be
  15. misrepresented as being the original software.
  16. 3. This notice may not be removed or altered from any source distribution.
  17. */
  18. #include "../../SDL_internal.h"
  19. #include "../../events/SDL_pen_c.h"
  20. #include "../SDL_sysvideo.h"
  21. #include "SDL_x11pen.h"
  22. #include "SDL_x11video.h"
  23. #include "SDL_x11xinput2.h"
  24. #ifdef SDL_VIDEO_DRIVER_X11_XINPUT2
  25. // Does this device have a valuator for pressure sensitivity?
  26. static bool X11_XInput2DeviceIsPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
  27. {
  28. const SDL_VideoData *data = _this->internal;
  29. for (int i = 0; i < dev->num_classes; i++) {
  30. const XIAnyClassInfo *classinfo = dev->classes[i];
  31. if (classinfo->type == XIValuatorClass) {
  32. const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo;
  33. if (val_classinfo->label == data->atoms.pen_atom_abs_pressure) {
  34. return true;
  35. }
  36. }
  37. }
  38. return false;
  39. }
  40. // Heuristically determines if device is an eraser
  41. static bool X11_XInput2PenIsEraser(SDL_VideoDevice *_this, int deviceid, char *devicename)
  42. {
  43. #define PEN_ERASER_NAME_TAG "eraser" // String constant to identify erasers
  44. SDL_VideoData *data = _this->internal;
  45. if (data->atoms.pen_atom_wacom_tool_type != None) {
  46. Atom type_return;
  47. int format_return;
  48. unsigned long num_items_return;
  49. unsigned long bytes_after_return;
  50. unsigned char *tooltype_name_info = NULL;
  51. // Try Wacom-specific method
  52. if (Success == X11_XIGetProperty(data->display, deviceid,
  53. data->atoms.pen_atom_wacom_tool_type,
  54. 0, 32, False,
  55. AnyPropertyType, &type_return, &format_return,
  56. &num_items_return, &bytes_after_return,
  57. &tooltype_name_info) &&
  58. tooltype_name_info != NULL && num_items_return > 0) {
  59. bool result = false;
  60. char *tooltype_name = NULL;
  61. if (type_return == XA_ATOM) {
  62. // Atom instead of string? Un-intern
  63. Atom atom = *((Atom *)tooltype_name_info);
  64. if (atom != None) {
  65. tooltype_name = X11_XGetAtomName(data->display, atom);
  66. }
  67. } else if (type_return == XA_STRING && format_return == 8) {
  68. tooltype_name = (char *)tooltype_name_info;
  69. }
  70. if (tooltype_name) {
  71. if (SDL_strcasecmp(tooltype_name, PEN_ERASER_NAME_TAG) == 0) {
  72. result = true;
  73. }
  74. if (tooltype_name != (char *)tooltype_name_info) {
  75. X11_XFree(tooltype_name_info);
  76. }
  77. X11_XFree(tooltype_name);
  78. return result;
  79. }
  80. }
  81. }
  82. // Non-Wacom device?
  83. /* We assume that a device is an eraser if its name contains the string "eraser".
  84. * Unfortunately there doesn't seem to be a clean way to distinguish these cases (as of 2022-03). */
  85. return (SDL_strcasestr(devicename, PEN_ERASER_NAME_TAG)) ? true : false;
  86. }
  87. // Read out an integer property and store into a preallocated Sint32 array, extending 8 and 16 bit values suitably.
  88. // Returns number of Sint32s written (<= max_words), or 0 on error.
  89. static size_t X11_XInput2PenGetIntProperty(SDL_VideoDevice *_this, int deviceid, Atom property, Sint32 *dest, size_t max_words)
  90. {
  91. const SDL_VideoData *data = _this->internal;
  92. Atom type_return;
  93. int format_return;
  94. unsigned long num_items_return;
  95. unsigned long bytes_after_return;
  96. unsigned char *output;
  97. if (property == None) {
  98. return 0;
  99. }
  100. if (Success != X11_XIGetProperty(data->display, deviceid,
  101. property,
  102. 0, max_words, False,
  103. XA_INTEGER, &type_return, &format_return,
  104. &num_items_return, &bytes_after_return,
  105. &output) ||
  106. num_items_return == 0 || output == NULL) {
  107. return 0;
  108. }
  109. if (type_return == XA_INTEGER) {
  110. int k;
  111. const int to_copy = SDL_min(max_words, num_items_return);
  112. if (format_return == 8) {
  113. Sint8 *numdata = (Sint8 *)output;
  114. for (k = 0; k < to_copy; ++k) {
  115. dest[k] = numdata[k];
  116. }
  117. } else if (format_return == 16) {
  118. Sint16 *numdata = (Sint16 *)output;
  119. for (k = 0; k < to_copy; ++k) {
  120. dest[k] = numdata[k];
  121. }
  122. } else {
  123. SDL_memcpy(dest, output, sizeof(Sint32) * to_copy);
  124. }
  125. X11_XFree(output);
  126. return to_copy;
  127. }
  128. return 0; // type mismatch
  129. }
  130. // Identify Wacom devices (if true is returned) and extract their device type and serial IDs
  131. static bool X11_XInput2PenWacomDeviceID(SDL_VideoDevice *_this, int deviceid, Uint32 *wacom_devicetype_id, Uint32 *wacom_serial)
  132. {
  133. SDL_VideoData *data = _this->internal;
  134. Sint32 serial_id_buf[3];
  135. int result;
  136. if ((result = X11_XInput2PenGetIntProperty(_this, deviceid, data->atoms.pen_atom_wacom_serial_ids, serial_id_buf, 3)) == 3) {
  137. *wacom_devicetype_id = serial_id_buf[2];
  138. *wacom_serial = serial_id_buf[1];
  139. return true;
  140. }
  141. *wacom_devicetype_id = *wacom_serial = 0;
  142. return false;
  143. }
  144. typedef struct FindPenByDeviceIDData
  145. {
  146. int x11_deviceid;
  147. void *handle;
  148. } FindPenByDeviceIDData;
  149. static bool FindPenByDeviceID(void *handle, void *userdata)
  150. {
  151. const X11_PenHandle *x11_handle = (const X11_PenHandle *) handle;
  152. FindPenByDeviceIDData *data = (FindPenByDeviceIDData *) userdata;
  153. if (x11_handle->x11_deviceid != data->x11_deviceid) {
  154. return false;
  155. }
  156. data->handle = handle;
  157. return true;
  158. }
  159. X11_PenHandle *X11_FindPenByDeviceID(int deviceid)
  160. {
  161. FindPenByDeviceIDData data;
  162. data.x11_deviceid = deviceid;
  163. data.handle = NULL;
  164. SDL_FindPenByCallback(FindPenByDeviceID, &data);
  165. return (X11_PenHandle *) data.handle;
  166. }
  167. static X11_PenHandle *X11_MaybeAddPen(SDL_VideoDevice *_this, const XIDeviceInfo *dev)
  168. {
  169. SDL_VideoData *data = _this->internal;
  170. SDL_PenCapabilityFlags capabilities = 0;
  171. X11_PenHandle *handle = NULL;
  172. if ((dev->use != XISlavePointer && (dev->use != XIFloatingSlave)) || dev->enabled == 0 || !X11_XInput2DeviceIsPen(_this, dev)) {
  173. return NULL; // Only track physical devices that are enabled and look like pens
  174. } else if ((handle = X11_FindPenByDeviceID(dev->deviceid)) != 0) {
  175. return handle; // already have this pen, skip it.
  176. } else if ((handle = SDL_calloc(1, sizeof (*handle))) == NULL) {
  177. return NULL; // oh well.
  178. }
  179. for (int i = 0; i < SDL_arraysize(handle->valuator_for_axis); i++) {
  180. handle->valuator_for_axis[i] = SDL_X11_PEN_AXIS_VALUATOR_MISSING; // until proven otherwise
  181. }
  182. int total_buttons = 0;
  183. for (int i = 0; i < dev->num_classes; i++) {
  184. const XIAnyClassInfo *classinfo = dev->classes[i];
  185. if (classinfo->type == XIButtonClass) {
  186. const XIButtonClassInfo *button_classinfo = (const XIButtonClassInfo *)classinfo;
  187. total_buttons += button_classinfo->num_buttons;
  188. } else if (classinfo->type == XIValuatorClass) {
  189. const XIValuatorClassInfo *val_classinfo = (const XIValuatorClassInfo *)classinfo;
  190. const Sint8 valuator_nr = val_classinfo->number;
  191. const Atom vname = val_classinfo->label;
  192. const float min = (float)val_classinfo->min;
  193. const float max = (float)val_classinfo->max;
  194. bool use_this_axis = true;
  195. SDL_PenAxis axis = SDL_PEN_AXIS_COUNT;
  196. // afaict, SDL_PEN_AXIS_DISTANCE is never reported by XInput2 (Wayland can offer it, though)
  197. if (vname == data->atoms.pen_atom_abs_pressure) {
  198. axis = SDL_PEN_AXIS_PRESSURE;
  199. } else if (vname == data->atoms.pen_atom_abs_tilt_x) {
  200. axis = SDL_PEN_AXIS_XTILT;
  201. } else if (vname == data->atoms.pen_atom_abs_tilt_y) {
  202. axis = SDL_PEN_AXIS_YTILT;
  203. } else {
  204. use_this_axis = false;
  205. }
  206. // !!! 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.
  207. if (use_this_axis) {
  208. capabilities |= SDL_GetPenCapabilityFromAxis(axis);
  209. handle->valuator_for_axis[axis] = valuator_nr;
  210. handle->axis_min[axis] = min;
  211. handle->axis_max[axis] = max;
  212. }
  213. }
  214. }
  215. // We have a pen if and only if the device measures pressure.
  216. // We checked this in X11_XInput2DeviceIsPen, so just assert it here.
  217. SDL_assert(capabilities & SDL_PEN_CAPABILITY_PRESSURE);
  218. const bool is_eraser = X11_XInput2PenIsEraser(_this, dev->deviceid, dev->name);
  219. Uint32 wacom_devicetype_id = 0;
  220. Uint32 wacom_serial = 0;
  221. X11_XInput2PenWacomDeviceID(_this, dev->deviceid, &wacom_devicetype_id, &wacom_serial);
  222. SDL_PenInfo peninfo;
  223. SDL_zero(peninfo);
  224. peninfo.capabilities = capabilities;
  225. peninfo.max_tilt = -1;
  226. peninfo.wacom_id = wacom_devicetype_id;
  227. peninfo.num_buttons = total_buttons;
  228. peninfo.subtype = is_eraser ? SDL_PEN_TYPE_ERASER : SDL_PEN_TYPE_PEN;
  229. if (is_eraser) {
  230. peninfo.capabilities |= SDL_PEN_CAPABILITY_ERASER;
  231. }
  232. handle->is_eraser = is_eraser;
  233. handle->x11_deviceid = dev->deviceid;
  234. handle->pen = SDL_AddPenDevice(0, dev->name, &peninfo, handle);
  235. if (!handle->pen) {
  236. SDL_free(handle);
  237. return NULL;
  238. }
  239. return handle;
  240. }
  241. X11_PenHandle *X11_MaybeAddPenByDeviceID(SDL_VideoDevice *_this, int deviceid)
  242. {
  243. if (X11_Xinput2IsInitialized()) {
  244. SDL_VideoData *data = _this->internal;
  245. int num_device_info = 0;
  246. XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, deviceid, &num_device_info);
  247. if (device_info) {
  248. SDL_assert(num_device_info == 1);
  249. X11_PenHandle *handle = X11_MaybeAddPen(_this, device_info);
  250. X11_XIFreeDeviceInfo(device_info);
  251. return handle;
  252. }
  253. }
  254. return NULL;
  255. }
  256. void X11_RemovePenByDeviceID(int deviceid)
  257. {
  258. X11_PenHandle *handle = X11_FindPenByDeviceID(deviceid);
  259. if (handle) {
  260. SDL_RemovePenDevice(0, handle->pen);
  261. SDL_free(handle);
  262. }
  263. }
  264. void X11_InitPen(SDL_VideoDevice *_this)
  265. {
  266. if (!X11_Xinput2IsInitialized()) {
  267. return; // we need XIQueryDevice() for this.
  268. }
  269. SDL_VideoData *data = _this->internal;
  270. #define LOOKUP_PEN_ATOM(X) X11_XInternAtom(data->display, X, False)
  271. data->atoms.pen_atom_device_product_id = LOOKUP_PEN_ATOM("Device Product ID");
  272. data->atoms.pen_atom_wacom_serial_ids = LOOKUP_PEN_ATOM("Wacom Serial IDs");
  273. data->atoms.pen_atom_wacom_tool_type = LOOKUP_PEN_ATOM("Wacom Tool Type");
  274. data->atoms.pen_atom_abs_pressure = LOOKUP_PEN_ATOM("Abs Pressure");
  275. data->atoms.pen_atom_abs_tilt_x = LOOKUP_PEN_ATOM("Abs Tilt X");
  276. data->atoms.pen_atom_abs_tilt_y = LOOKUP_PEN_ATOM("Abs Tilt Y");
  277. #undef LOOKUP_PEN_ATOM
  278. // Do an initial check on devices. After this, we'll add/remove individual pens when XI_HierarchyChanged events alert us.
  279. int num_device_info = 0;
  280. XIDeviceInfo *device_info = X11_XIQueryDevice(data->display, XIAllDevices, &num_device_info);
  281. if (device_info) {
  282. for (int i = 0; i < num_device_info; i++) {
  283. X11_MaybeAddPen(_this, &device_info[i]);
  284. }
  285. X11_XIFreeDeviceInfo(device_info);
  286. }
  287. }
  288. static void X11_FreePenHandle(SDL_PenID instance_id, void *handle, void *userdata)
  289. {
  290. SDL_free(handle);
  291. }
  292. void X11_QuitPen(SDL_VideoDevice *_this)
  293. {
  294. SDL_RemoveAllPenDevices(X11_FreePenHandle, NULL);
  295. }
  296. static void X11_XInput2NormalizePenAxes(const X11_PenHandle *pen, float *coords)
  297. {
  298. // Normalise axes
  299. for (int axis = 0; axis < SDL_PEN_AXIS_COUNT; ++axis) {
  300. const int valuator = pen->valuator_for_axis[axis];
  301. if (valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) {
  302. continue;
  303. }
  304. float value = coords[axis];
  305. const float min = pen->axis_min[axis];
  306. const float max = pen->axis_max[axis];
  307. if (axis == SDL_PEN_AXIS_SLIDER) {
  308. value += pen->slider_bias;
  309. }
  310. // min ... 0 ... max
  311. if (min < 0.0) {
  312. // Normalise so that 0 remains 0.0
  313. if (value < 0) {
  314. value = value / (-min);
  315. } else {
  316. if (max == 0.0f) {
  317. value = 0.0f;
  318. } else {
  319. value = value / max;
  320. }
  321. }
  322. } else {
  323. // 0 ... min ... max
  324. // including 0.0 = min
  325. if (max == 0.0f) {
  326. value = 0.0f;
  327. } else {
  328. value = (value - min) / max;
  329. }
  330. }
  331. switch (axis) {
  332. case SDL_PEN_AXIS_XTILT:
  333. case SDL_PEN_AXIS_YTILT:
  334. //if (peninfo->info.max_tilt > 0.0f) {
  335. // value *= peninfo->info.max_tilt; // normalize to physical max
  336. //}
  337. break;
  338. case SDL_PEN_AXIS_ROTATION:
  339. // normalised to -1..1, so let's convert to degrees
  340. value *= 180.0f;
  341. value += pen->rotation_bias;
  342. // handle simple over/underflow
  343. if (value >= 180.0f) {
  344. value -= 360.0f;
  345. } else if (value < -180.0f) {
  346. value += 360.0f;
  347. }
  348. break;
  349. default:
  350. break;
  351. }
  352. coords[axis] = value;
  353. }
  354. }
  355. void X11_PenAxesFromValuators(const X11_PenHandle *pen,
  356. const double *input_values, const unsigned char *mask, int mask_len,
  357. float axis_values[SDL_PEN_AXIS_COUNT])
  358. {
  359. for (int i = 0; i < SDL_PEN_AXIS_COUNT; i++) {
  360. const int valuator = pen->valuator_for_axis[i];
  361. if ((valuator == SDL_X11_PEN_AXIS_VALUATOR_MISSING) || (valuator >= mask_len * 8) || !(XIMaskIsSet(mask, valuator))) {
  362. axis_values[i] = 0.0f;
  363. } else {
  364. axis_values[i] = (float)input_values[valuator];
  365. }
  366. }
  367. X11_XInput2NormalizePenAxes(pen, axis_values);
  368. }
  369. #else
  370. void X11_InitPen(SDL_VideoDevice *_this)
  371. {
  372. (void) _this;
  373. }
  374. void X11_QuitPen(SDL_VideoDevice *_this)
  375. {
  376. (void) _this;
  377. }
  378. #endif // SDL_VIDEO_DRIVER_X11_XINPUT2