1 // license:BSD-3-Clause
2 // copyright-holders:Olivier Galibert, R. Belmont, Brad Hughes
3 //============================================================
4 //
5 // input_x11.cpp - X11 XLib/XInput routines
6 //
7 // SDLMAME by Olivier Galibert and R. Belmont
8 //
9 //============================================================
10
11 #include "input_module.h"
12 #include "modules/osdmodule.h"
13
14 #if defined(SDLMAME_SDL2) && !defined(SDLMAME_WIN32) && defined(USE_XINPUT) && USE_XINPUT
15
16 // for X11 xinput
17 #include <X11/Xlib.h>
18 #include <X11/extensions/XInput.h>
19 #include <X11/Xutil.h>
20
21 // standard sdl header
22 #include <SDL2/SDL.h>
23 #include <cctype>
24 #include <cstddef>
25 #include <mutex>
26 #include <memory>
27 #include <queue>
28 #include <string>
29 #include <algorithm>
30
31 // MAME headers
32 #include "emu.h"
33 #include "osdepend.h"
34
35 // MAMEOS headers
36 #include "../lib/osdobj_common.h"
37 #include "input_common.h"
38 #include "../../sdl/osdsdl.h"
39 #include "input_sdlcommon.h"
40
41 #define MAX_DEVMAP_ENTRIES 16
42
43 #define INVALID_EVENT_TYPE -1
44 static int motion_type = INVALID_EVENT_TYPE;
45 static int button_press_type = INVALID_EVENT_TYPE;
46 static int button_release_type = INVALID_EVENT_TYPE;
47 static int key_press_type = INVALID_EVENT_TYPE;
48 static int key_release_type = INVALID_EVENT_TYPE;
49 static int proximity_in_type = INVALID_EVENT_TYPE;
50 static int proximity_out_type = INVALID_EVENT_TYPE;
51
52 // state information for a lightgun
53 struct lightgun_state
54 {
55 int32_t lX, lY;
56 int32_t buttons[MAX_BUTTONS];
57 };
58
59 struct x11_api_state
60 {
61 XID deviceid; // X11 device id
62 int32_t maxx, maxy;
63 int32_t minx, miny;
64 };
65
66 //============================================================
67 // DEBUG MACROS
68 //============================================================
69
70 #if defined(XINPUT_DEBUG) && XINPUT_DEBUG
71 #define XI_DBG(format, ...) osd_printf_verbose(format, __VA_ARGS__)
72
73 #define print_motion_event(motion) print_motion_event_impl(motion)
print_motion_event_impl(XDeviceMotionEvent * motion)74 static inline void print_motion_event_impl(XDeviceMotionEvent *motion)
75 {
76 /*
77 * print a lot of debug informations of the motion event(s).
78 */
79 osd_printf_verbose(
80 "XDeviceMotionEvent:\n"
81 " type: %d\n"
82 " serial: %lu\n"
83 " send_event: %d\n"
84 " display: %p\n"
85 " window: --\n"
86 " deviceid: %lu\n"
87 " root: --\n"
88 " subwindow: --\n"
89 " time: --\n"
90 " x: %d, y: %d\n"
91 " x_root: %d, y_root: %d\n"
92 " state: %u\n"
93 " is_hint: %2.2X\n"
94 " same_screen: %d\n"
95 " device_state: %u\n"
96 " axes_count: %2.2X\n"
97 " first_axis: %2.2X\n"
98 " axis_data[6]: {%d,%d,%d,%d,%d,%d}\n",
99 motion->type,
100 motion->serial,
101 motion->send_event,
102 motion->display,
103 /* motion->window, */
104 motion->deviceid,
105 /* motion->root */
106 /* motion->subwindow */
107 /* motion->time, */
108 motion->x, motion->y,
109 motion->x_root, motion->y_root,
110 motion->state,
111 motion->is_hint,
112 motion->same_screen,
113 motion->device_state,
114 motion->axes_count,
115 motion->first_axis,
116 motion->axis_data[0], motion->axis_data[1], motion->axis_data[2], motion->axis_data[3], motion->axis_data[4], motion->axis_data[5]
117 );
118 }
119 #else
120 #define XI_DBG(format, ...) while(0) {}
121 #define print_motion_event(motion) while(0) {}
122 #endif
123
124 //============================================================
125 // lightgun helpers: copy-past from xinfo
126 //============================================================
127
128 XDeviceInfo*
find_device_info(Display * display,const char * name,bool only_extended)129 find_device_info(Display *display,
130 const char *name,
131 bool only_extended)
132 {
133 XDeviceInfo *devices;
134 XDeviceInfo *found = nullptr;
135 int loop;
136 int num_devices;
137 int len = strlen(name);
138 bool is_id = true;
139 XID id = static_cast<XID>(-1);
140
141 for(loop = 0; loop < len; loop++)
142 {
143 if (!isdigit(name[loop]))
144 {
145 is_id = false;
146 break;
147 }
148 }
149
150 if (is_id)
151 {
152 id = atoi(name);
153 }
154
155 devices = XListInputDevices(display, &num_devices);
156
157 for(loop = 0; loop < num_devices; loop++)
158 {
159 osd_printf_verbose("Evaluating device with name: %s\n", devices[loop].name);
160
161 // if only extended devices and our device isn't extended, skip
162 if (only_extended && devices[loop].use < IsXExtensionDevice)
163 continue;
164
165 // Adjust name to remove spaces for accurate comparison
166 std::string name_no_space = remove_spaces(devices[loop].name);
167 if ((!is_id && strcmp(name_no_space.c_str(), name) == 0)
168 || (is_id && devices[loop].id == id))
169 {
170 if (found)
171 {
172 osd_printf_verbose(
173 "Warning: There are multiple devices named \"%s\".\n"
174 "To ensure the correct one is selected, please use "
175 "the device ID instead.\n\n", name);
176 }
177 else
178 {
179 found = &devices[loop];
180 }
181 }
182 }
183
184 return found;
185 }
186
187 //Copypasted from xinfo
188 static int
register_events(Display * dpy,XDeviceInfo * info,const char * dev_name,bool handle_proximity)189 register_events(
190 Display *dpy,
191 XDeviceInfo *info,
192 const char *dev_name,
193 bool handle_proximity)
194 {
195 int number = 0; /* number of events registered */
196 XEventClass event_list[7];
197 int i;
198 XDevice * device;
199 Window root_win;
200 unsigned long screen;
201 XInputClassInfo * ip;
202
203 screen = DefaultScreen(dpy);
204 root_win = RootWindow(dpy, screen);
205
206 device = XOpenDevice(dpy, info->id);
207 if (device == nullptr)
208 {
209 osd_printf_verbose("unable to open device %s\n", dev_name);
210 return 0;
211 }
212
213 if (device->num_classes > 0)
214 {
215 for (ip = device->classes, i = 0; i < info->num_classes; ip++, i++)
216 {
217 switch (ip->input_class)
218 {
219 case KeyClass:
220 DeviceKeyPress(device, key_press_type, event_list[number]); number++;
221 DeviceKeyRelease(device, key_release_type, event_list[number]); number++;
222 break;
223
224 case ButtonClass:
225 DeviceButtonPress(device, button_press_type, event_list[number]); number++;
226 DeviceButtonRelease(device, button_release_type, event_list[number]); number++;
227 break;
228
229 case ValuatorClass:
230 DeviceMotionNotify(device, motion_type, event_list[number]); number++;
231 osd_printf_verbose("Motion = %i\n",motion_type);
232 if (handle_proximity)
233 {
234 ProximityIn(device, proximity_in_type, event_list[number]); number++;
235 ProximityOut(device, proximity_out_type, event_list[number]); number++;
236 }
237 break;
238
239 default:
240 osd_printf_verbose("unknown class\n");
241 break;
242 }
243 }
244
245 if (XSelectExtensionEvent(dpy, root_win, event_list, number))
246 {
247 osd_printf_verbose("error selecting extended events\n");
248 return 0;
249 }
250 }
251
252 return number;
253 }
254
255 //============================================================
256 // x11_event_manager
257 //============================================================
258
259 class x11_event_handler
260 {
261 public:
~x11_event_handler()262 virtual ~x11_event_handler() {}
263
264 virtual void handle_event(XEvent &xevent) = 0;
265 };
266
267 class x11_event_manager : public event_manager_t<x11_event_handler>
268 {
269 private:
270 Display * m_display;
271
x11_event_manager()272 x11_event_manager()
273 : event_manager_t(),
274 m_display(nullptr)
275 {
276 }
277 public:
display() const278 Display * display() const { return m_display; }
279
instance()280 static x11_event_manager& instance()
281 {
282 static x11_event_manager s_instance;
283 return s_instance;
284 }
285
initialize()286 int initialize()
287 {
288 std::lock_guard<std::mutex> scope_lock(m_lock);
289
290 if (m_display != nullptr)
291 return 0;
292
293 m_display = XOpenDisplay(nullptr);
294 if (m_display == nullptr)
295 {
296 osd_printf_verbose("Unable to connect to X server\n");
297 return -1;
298 }
299
300 XExtensionVersion *version = XGetExtensionVersion(m_display, INAME);
301 if (!version || (version == reinterpret_cast<XExtensionVersion*>(NoSuchExtension)))
302 {
303 osd_printf_verbose("xinput extension not available!\n");
304 return -1;
305 }
306
307 return 0;
308 }
309
process_events(running_machine & machine)310 void process_events(running_machine &machine) override
311 {
312 std::lock_guard<std::mutex> scope_lock(m_lock);
313 XEvent xevent;
314
315 // If X11 has become invalid for some reason, XPending will crash. Assert instead.
316 assert(m_display != nullptr);
317
318 //Get XInput events
319 while (XPending(m_display) != 0)
320 {
321 XNextEvent(m_display, &xevent);
322
323 // Find all subscribers for the event type
324 auto subscribers = m_subscription_index.equal_range(xevent.type);
325
326 // Dispatch the events
327 std::for_each(subscribers.first, subscribers.second, [&xevent](auto &pair)
328 {
329 pair.second->handle_event(xevent);
330 });
331 }
332 }
333 };
334
335 //============================================================
336 // x11_input_device
337 //============================================================
338
339 class x11_input_device : public event_based_device<XEvent>
340 {
341 public:
342 x11_api_state x11_state;
343
x11_input_device(running_machine & machine,const char * name,const char * id,input_device_class devclass,input_module & module)344 x11_input_device(running_machine &machine, const char *name, const char *id, input_device_class devclass, input_module &module)
345 : event_based_device(machine, name, id, devclass, module),
346 x11_state({0})
347 {
348 }
349 };
350
351 //============================================================
352 // x11_lightgun_device
353 //============================================================
354
355 class x11_lightgun_device : public x11_input_device
356 {
357 public:
358 lightgun_state lightgun;
359
x11_lightgun_device(running_machine & machine,const char * name,const char * id,input_module & module)360 x11_lightgun_device(running_machine &machine, const char *name, const char *id, input_module &module)
361 : x11_input_device(machine, name, id, DEVICE_CLASS_LIGHTGUN, module),
362 lightgun({0})
363 {
364 }
365
process_event(XEvent & xevent)366 void process_event(XEvent &xevent) override
367 {
368 if (xevent.type == motion_type)
369 {
370 XDeviceMotionEvent *motion = reinterpret_cast<XDeviceMotionEvent *>(&xevent);
371 print_motion_event(motion);
372
373 /*
374 * We have to check with axis will start on array index 0.
375 * We have also to check the number of axes that are stored in the array.
376 */
377 switch (motion->first_axis)
378 {
379 /*
380 * Starting with x, check number of axis, if there is also the y axis stored.
381 */
382 case 0:
383 if (motion->axes_count >= 1)
384 {
385 lightgun.lX = normalize_absolute_axis(motion->axis_data[0], x11_state.minx, x11_state.maxx);
386 if (motion->axes_count >= 2)
387 {
388 lightgun.lY = normalize_absolute_axis(motion->axis_data[1], x11_state.miny, x11_state.maxy);
389 }
390 }
391 break;
392
393 /*
394 * Starting with y, ...
395 */
396 case 1:
397 if (motion->axes_count >= 1)
398 {
399 lightgun.lY = normalize_absolute_axis(motion->axis_data[0], x11_state.miny, x11_state.maxy);
400 }
401 break;
402 }
403 }
404 else if (xevent.type == button_press_type || xevent.type == button_release_type)
405 {
406 XDeviceButtonEvent *button = reinterpret_cast<XDeviceButtonEvent *>(&xevent);
407
408 /*
409 * SDL/X11 Number the buttons 1,2,3, while windows and other parts of MAME
410 * like offscreen_reload expect 0,2,1. Transpose buttons 2 and 3, and then
411 * -1 the button number to align the numbering schemes.
412 */
413 int button_number = button->button;
414 switch (button_number)
415 {
416 case 2:
417 button_number = 3;
418 break;
419 case 3:
420 button_number = 2;
421 break;
422 }
423 lightgun.buttons[button_number - 1] = (xevent.type == button_press_type) ? 0x80 : 0;
424 }
425 }
426
reset()427 void reset() override
428 {
429 memset(&lightgun, 0, sizeof(lightgun));
430 }
431 };
432
433 //============================================================
434 // x11_lightgun_module
435 //============================================================
436
437 class x11_lightgun_module : public input_module_base, public x11_event_handler
438 {
439 private:
440 device_map_t m_lightgun_map;
441 Display * m_display;
442 public:
x11_lightgun_module()443 x11_lightgun_module()
444 : input_module_base(OSD_LIGHTGUNINPUT_PROVIDER, "x11"),
445 m_display(nullptr)
446 {
447 }
448
probe()449 virtual bool probe() override
450 {
451 // If there is no X server, X11 lightguns cannot be supported
452 if (XOpenDisplay(nullptr) == nullptr)
453 {
454 return false;
455 }
456
457 return true;
458 }
459
input_init(running_machine & machine)460 void input_init(running_machine &machine) override
461 {
462 int index;
463
464 osd_printf_verbose("Lightgun: Begin initialization\n");
465
466 devmap_init(machine, &m_lightgun_map, SDLOPTION_LIGHTGUNINDEX, 8, "Lightgun mapping");
467
468 x11_event_manager::instance().initialize();
469 m_display = x11_event_manager::instance().display();
470
471 // If the X server has become invalid, a crash can occur
472 assert(m_display != nullptr);
473
474 // Loop through all 8 possible devices
475 for (index = 0; index < 8; index++)
476 {
477 XDeviceInfo *info;
478
479 // Skip if the name is empty
480 if (m_lightgun_map.map[index].name.length() == 0)
481 continue;
482
483 x11_lightgun_device *devinfo;
484 std::string const &name = m_lightgun_map.map[index].name;
485 char defname[512];
486
487 // Register and add the device
488 devinfo = create_lightgun_device(machine, index);
489 osd_printf_verbose("%i: %s\n", index, name);
490
491 // Find the device info associated with the name
492 info = find_device_info(m_display, name.c_str(), 0);
493
494 // If we couldn't find the device, skip
495 if (info == nullptr)
496 {
497 osd_printf_verbose("Can't find device %s!\n", name);
498 continue;
499 }
500
501 //Grab device info and translate to stuff mame can use
502 if (info->num_classes > 0)
503 {
504 // Add the lightgun buttons based on what we read
505 add_lightgun_buttons(static_cast<XAnyClassPtr>(info->inputclassinfo), info->num_classes, devinfo);
506
507 // Also, set the axix min/max ranges if we got them
508 set_lightgun_axis_props(static_cast<XAnyClassPtr>(info->inputclassinfo), info->num_classes, devinfo);
509 }
510
511 // Add X and Y axis
512 sprintf(defname, "X %s", devinfo->name());
513 devinfo->device()->add_item(defname, ITEM_ID_XAXIS, generic_axis_get_state<std::int32_t>, &devinfo->lightgun.lX);
514
515 sprintf(defname, "Y %s", devinfo->name());
516 devinfo->device()->add_item(defname, ITEM_ID_YAXIS, generic_axis_get_state<std::int32_t>, &devinfo->lightgun.lY);
517
518 // Save the device id
519 devinfo->x11_state.deviceid = info->id;
520
521 // Register this device to receive event notifications
522 int events_registered = register_events(m_display, info, m_lightgun_map.map[index].name.c_str(), 0);
523 osd_printf_verbose("Device %i: Registered %i events.\n", static_cast<int>(info->id), events_registered);
524
525 // register ourself to handle events from event manager
526 int event_types[] = { motion_type, button_press_type, button_release_type };
527 osd_printf_verbose("Events types to register: motion:%d, press:%d, release:%d\n", motion_type, button_press_type, button_release_type);
528 x11_event_manager::instance().subscribe(event_types, ARRAY_LENGTH(event_types), this);
529 }
530
531 osd_printf_verbose("Lightgun: End initialization\n");
532 }
533
should_poll_devices(running_machine & machine)534 bool should_poll_devices(running_machine &machine) override
535 {
536 return sdl_event_manager::instance().has_focus();
537 }
538
before_poll(running_machine & machine)539 void before_poll(running_machine &machine) override
540 {
541 if (!should_poll_devices(machine))
542 return;
543
544 // Tell the event manager to process events and push them to the devices
545 x11_event_manager::instance().process_events(machine);
546
547 // Also trigger the SDL event manager so it can process window events
548 sdl_event_manager::instance().process_events(machine);
549 }
550
handle_event(XEvent & xevent)551 void handle_event(XEvent &xevent) override
552 {
553 XID deviceid;
554 if (xevent.type == motion_type)
555 {
556 XDeviceMotionEvent *motion = reinterpret_cast<XDeviceMotionEvent *>(&xevent);
557 deviceid = motion->deviceid;
558 }
559 else if (xevent.type == button_press_type || xevent.type == button_release_type)
560 {
561 XDeviceButtonEvent *button = reinterpret_cast<XDeviceButtonEvent *>(&xevent);
562 deviceid = button->deviceid;
563 }
564 else
565 {
566 return;
567 }
568
569 // Figure out which lightgun this event id destined for
570 auto target_device = std::find_if(devicelist()->begin(), devicelist()->end(), [deviceid](auto &device)
571 {
572 std::unique_ptr<device_info> &ptr = device;
573 return downcast<x11_input_device*>(ptr.get())->x11_state.deviceid == deviceid;
574 });
575
576 // If we find a matching lightgun, dispatch the event to the lightgun
577 if (target_device != devicelist()->end())
578 {
579 downcast<x11_input_device*>((*target_device).get())->queue_events(&xevent, 1);
580 }
581 }
582
583 private:
create_lightgun_device(running_machine & machine,int index)584 x11_lightgun_device* create_lightgun_device(running_machine &machine, int index)
585 {
586 char tempname[20];
587
588 if (m_lightgun_map.map[index].name.length() == 0)
589 {
590 if (m_lightgun_map.initialized)
591 {
592 snprintf(tempname, ARRAY_LENGTH(tempname), "NC%d", index);
593 return devicelist()->create_device<x11_lightgun_device>(machine, tempname, tempname, *this);
594 } else
595 return nullptr;
596 }
597
598 return devicelist()->create_device<x11_lightgun_device>(machine, m_lightgun_map.map[index].name.c_str(), m_lightgun_map.map[index].name.c_str(), *this);
599 }
600
add_lightgun_buttons(XAnyClassPtr first_info_class,int num_classes,x11_lightgun_device * devinfo) const601 void add_lightgun_buttons(XAnyClassPtr first_info_class, int num_classes, x11_lightgun_device *devinfo) const
602 {
603 XAnyClassPtr any = first_info_class;
604
605 for (int i = 0; i < num_classes; i++)
606 {
607 switch (any->c_class)
608 {
609 case ButtonClass:
610 XButtonInfoPtr b = reinterpret_cast<XButtonInfoPtr>(any);
611 for (int button = 0; button < b->num_buttons; button++)
612 {
613 input_item_id itemid = static_cast<input_item_id>(ITEM_ID_BUTTON1 + button);
614 devinfo->device()->add_item(default_button_name(button), itemid, generic_button_get_state<std::int32_t>, &devinfo->lightgun.buttons[button]);
615 }
616 break;
617 }
618
619 any = reinterpret_cast<XAnyClassPtr>(reinterpret_cast<char *>(any) + any->length);
620 }
621 }
622
set_lightgun_axis_props(XAnyClassPtr first_info_class,int num_classes,x11_lightgun_device * devinfo) const623 void set_lightgun_axis_props(XAnyClassPtr first_info_class, int num_classes, x11_lightgun_device *devinfo) const
624 {
625 XAnyClassPtr any = first_info_class;
626
627 for (int i = 0; i < num_classes; i++)
628 {
629 switch (any->c_class)
630 {
631 case ValuatorClass:
632 XValuatorInfoPtr valuator_info = reinterpret_cast<XValuatorInfoPtr>(any);
633 XAxisInfoPtr axis_info = reinterpret_cast<XAxisInfoPtr>(reinterpret_cast<char *>(valuator_info) + sizeof(XValuatorInfo));
634 for (int j = 0; j < valuator_info->num_axes; j++, axis_info++)
635 {
636 if (j == 0)
637 {
638 XI_DBG("Set minx=%d, maxx=%d\n", axis_info->min_value, axis_info->max_value);
639 devinfo->x11_state.maxx = axis_info->max_value;
640 devinfo->x11_state.minx = axis_info->min_value;
641 }
642
643 if (j == 1)
644 {
645 XI_DBG("Set miny=%d, maxy=%d\n", axis_info->min_value, axis_info->max_value);
646 devinfo->x11_state.maxy = axis_info->max_value;
647 devinfo->x11_state.miny = axis_info->min_value;
648 }
649 }
650 break;
651 }
652
653 any = reinterpret_cast<XAnyClassPtr>(reinterpret_cast<char *>(any) + any->length);
654 }
655 }
656 };
657
658 #else
659 MODULE_NOT_SUPPORTED(x11_lightgun_module, OSD_LIGHTGUNINPUT_PROVIDER, "x11")
660 #endif
661
662 MODULE_DEFINITION(LIGHTGUN_X11, x11_lightgun_module)
663