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