Browse Source

Feature: Linux mouse/keyboard input without /dev/*

This works without directly accessing /dev/input devices (many Linux
distros restrict access to /dev/input), but still offers raw mouse
movement data that is useful e.g. for rotating a camera. The XInput2
extension for xorg is used to receive these raw events.

Keyboard events are simply forwarded from the Platform class to the
Input handler.

Since X events are handled on the core thread, the mouse and keyboard
events are stored in the LinuxPlatform class until the Mouse/Keyboard
capture methods, which are called by the main/sim thread, read them.
Florian Will 8 năm trước cách đây
mục cha
commit
8f1cb3f75a

+ 2 - 43
Source/BansheeCore/Linux/BsLinuxInput.cpp

@@ -238,40 +238,6 @@ namespace bs
 		return isGamepad;
 	}
 
-	/** Checks is the event handler at the specified input file handle is a mouse. */
-	bool isMouse(int fileHandle)
-	{
-		EventInfo eventInfo;
-		if(!getEventInfo(fileHandle, eventInfo))
-			return false;
-
-		// Check for mouse buttons
-		for(auto& entry : eventInfo.buttons)
-		{
-			if((entry >= BTN_MOUSE && entry < BTN_JOYSTICK))
-				return true;
-		}
-
-		return false;
-	}
-
-	/** Checks is the event handler at the specified input file handle is a keyboard. */
-	bool isKeyboard(int fileHandle)
-	{
-		EventInfo eventInfo;
-		if(!getEventInfo(fileHandle, eventInfo))
-			return false;
-
-		// Check for keyboard keys
-		for(auto& entry : eventInfo.buttons)
-		{
-			if(entry >= KEY_ESC && entry < KEY_KPDOT)
-				return true;
-		}
-
-		return false;
-	}
-
 	void Input::initRawInput()
 	{
 		mPlatformData = bs_new<InputPrivateData>();
@@ -295,19 +261,12 @@ namespace bs
 				info.id = (UINT32)mPlatformData->gamepadInfos.size();
 				mPlatformData->gamepadInfos.push_back(info);
 			}
-			else if(isKeyboard(file))
-				mPlatformData->keyboards.push_back(i);
-			else if(isMouse(file))
-				mPlatformData->mice.push_back(i);
 
 			close(file);
 		}
 
-		if (getDeviceCount(InputDevice::Keyboard) > 0)
-			mKeyboard = bs_new<Keyboard>("Keyboard", this);
-
-		if (getDeviceCount(InputDevice::Mouse) > 0)
-			mMouse = bs_new<Mouse>("Mouse", this);
+		mKeyboard = bs_new<Keyboard>("Keyboard", this);
+		mMouse = bs_new<Mouse>("Mouse", this);
 
 		UINT32 numGamepads = getDeviceCount(InputDevice::Gamepad);
 		for (UINT32 i = 0; i < numGamepads; i++)

+ 17 - 4
Source/BansheeCore/Linux/BsLinuxInput.h

@@ -3,6 +3,7 @@
 #pragma once
 
 #include "BsCorePrerequisites.h"
+#include "Input/BsInputFwd.h"
 
 namespace bs
 {
@@ -32,12 +33,24 @@ namespace bs
 	struct InputPrivateData
 	{
 		Vector<GamepadInfo> gamepadInfos;
-		Vector<INT32> mice;
-		Vector<INT32> keyboards;
+	};
+
+	/** Data about relative pointer / scroll wheel movement. */
+	struct LinuxMouseMotionEvent
+	{
+		double deltaX; /**< Relative pointer movement in X direction. */
+		double deltaY; /**< Relative pointer movement in Y direction. */
+		double deltaZ; /**< Relative vertical scroll amount. */
+	};
+
+	/** Data about a single button press or release. */
+	struct LinuxButtonEvent
+	{
+		UINT64 timestamp;
+		ButtonCode button;
+		bool pressed;
 	};
 
 #define BUFFER_SIZE_GAMEPAD 64
-#define BUFFER_SIZE_KEYBOARD 128
-#define BUFFER_SIZE_MOUSE 16
 }
 

+ 17 - 64
Source/BansheeCore/Linux/BsLinuxKeyboard.cpp

@@ -2,98 +2,51 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "Input/BsKeyboard.h"
 #include "Input/BsInput.h"
-#include "Linux/BsLinuxInput.h"
-#include <fcntl.h>
-#include <linux/input.h>
+#include "Linux/BsLinuxPlatform.h"
 
 namespace bs
 {
-	constexpr UINT32 MAX_DEVICES = 8;
 
 	/** Contains private data for the Linux Keyboard implementation. */
 	struct Keyboard::Pimpl
 	{
-		INT32 fileHandles[MAX_DEVICES];
 		bool hasInputFocus;
 	};
 
 	Keyboard::Keyboard(const String& name, Input* owner)
 		: mName(name), mOwner(owner)
 	{
-		InputPrivateData* pvtData = owner->_getPrivateData();
-
 		m = bs_new<Pimpl>();
 		m->hasInputFocus = true;
-
-		for(UINT32 i = 0; i < MAX_DEVICES; i++)
-			m->fileHandles[i] = -1;
-
-		UINT32 idx = 0;
-		for(auto& entry : pvtData->keyboards)
-		{
-			String eventPath = "/dev/input/event" + toString(entry);
-			m->fileHandles[idx] = open(eventPath.c_str(), O_RDWR | O_NONBLOCK);
-
-			if(m->fileHandles[idx] == -1)
-				LOGERR("Failed to open input event file handle for device: " + mName);
-
-			idx++;
-
-			if(idx >= MAX_DEVICES)
-				break;
-		}
 	}
 
 	Keyboard::~Keyboard()
 	{
-		for(UINT32 i = 0; i < MAX_DEVICES; i++)
-		{
-			if(m->fileHandles[i] != -1)
-				close(m->fileHandles[i]);
-		}
-
 		bs_delete(m);
 	}
 
 	void Keyboard::capture()
 	{
-		input_event events[BUFFER_SIZE_KEYBOARD];
-		for(UINT32 i = 0; i < MAX_DEVICES; i++)
-		{
-			if(m->fileHandles[i] == -1)
-				continue;
-
-			if(!m->hasInputFocus)
-				continue;
+		Lock lock(LinuxPlatform::eventLock);
 
-			while(true)
+		if(m->hasInputFocus)
+		{
+			while (!LinuxPlatform::buttonEvents.empty())
 			{
-				ssize_t numReadBytes = read(m->fileHandles[i], &events, sizeof(events));
-				if(numReadBytes < 0)
-					break;
-
-				UINT32 numEvents = numReadBytes / sizeof(input_event);
-				for(UINT32 j = 0; j < numEvents; ++j)
-				{
-					switch(events[j].type)
-					{
-					case EV_KEY:
-					{
-						ButtonCode bc = (ButtonCode)events[j].code;
-						if(bc < BC_MOUSE_LEFT)
-						{
-							if(events[j].value)
-								mOwner->_notifyButtonPressed(0, bc, (UINT64)events[j].time.tv_usec);
-							else
-								mOwner->_notifyButtonReleased(0, bc, (UINT64)events[j].time.tv_usec);
-						}
-					}
-						break;
-					default: break;
-					}
-				}
+				LinuxButtonEvent& event = LinuxPlatform::buttonEvents.front();
+				if(event.pressed)
+					mOwner->_notifyButtonPressed(0, event.button, event.timestamp);
+				else
+					mOwner->_notifyButtonReleased(0, event.button, event.timestamp);
+				LinuxPlatform::buttonEvents.pop();
 			}
 		}
+		else
+		{
+			// Discard queued data
+			while (!LinuxPlatform::buttonEvents.empty())
+				LinuxPlatform::buttonEvents.pop();
+		}
 	}
 
 	void Keyboard::changeCaptureContext(UINT64 windowHandle)

+ 18 - 127
Source/BansheeCore/Linux/BsLinuxMouse.cpp

@@ -2,161 +2,52 @@
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 #include "Input/BsMouse.h"
 #include "Input/BsInput.h"
-#include "Linux/BsLinuxInput.h"
-#include <fcntl.h>
-#include <linux/input.h>
+#include "Linux/BsLinuxPlatform.h"
 
 namespace bs
 {
-	constexpr UINT32 MAX_DEVICES = 8;
-
 	/** Contains private data for the Linux Mouse implementation. */
 	struct Mouse::Pimpl
 	{
-		INT32 fileHandles[MAX_DEVICES];
 		bool hasInputFocus;
 	};
 
 	Mouse::Mouse(const String& name, Input* owner)
 		: mName(name), mOwner(owner)
 	{
-		InputPrivateData* pvtData = owner->_getPrivateData();
-
 		m = bs_new<Pimpl>();
 		m->hasInputFocus = true;
-
-		for(UINT32 i = 0; i < MAX_DEVICES; i++)
-			m->fileHandles[i] = -1;
-
-		UINT32 idx = 0;
-		for(auto& entry : pvtData->mice)
-		{
-			String eventPath = "/dev/input/event" + toString(entry);
-			m->fileHandles[idx] = open(eventPath.c_str(), O_RDWR | O_NONBLOCK);
-
-			if(m->fileHandles[idx] == -1)
-				LOGERR("Failed to open input event file handle for device: " + mName);
-
-			idx++;
-
-			if(idx >= MAX_DEVICES)
-				break;
-		}
 	}
 
 	Mouse::~Mouse()
 	{
-		for(UINT32 i = 0; i < MAX_DEVICES; i++)
-		{
-			if(m->fileHandles[i] != -1)
-				close(m->fileHandles[i]);
-		}
-
 		bs_delete(m);
 	}
 
 	void Mouse::capture()
 	{
-		INT32 relX = 0;
-		INT32 relY = 0;
-		INT32 relZ = 0;
+		Lock lock(LinuxPlatform::eventLock);
 
-		input_event events[BUFFER_SIZE_MOUSE];
-		for(UINT32 i = 0; i < MAX_DEVICES; i++)
+		if(m->hasInputFocus)
 		{
-			if(m->fileHandles[i] == -1)
-				continue;
-
-			while(true)
-			{
-				ssize_t numReadBytes = read(m->fileHandles[i], &events, sizeof(events));
-				if(numReadBytes < 0)
-					break;
-
-				if(!m->hasInputFocus)
-					continue;
+			double deltaX = round(LinuxPlatform::mouseMotionEvent.deltaX);
+			double deltaY = round(LinuxPlatform::mouseMotionEvent.deltaY);
+			double deltaZ = round(LinuxPlatform::mouseMotionEvent.deltaZ);
 
-				UINT32 numEvents = numReadBytes / sizeof(input_event);
-				for(UINT32 j = 0; j < numEvents; ++j)
-				{
-					switch(events[j].type)
-					{
-					case EV_KEY:
-					{
-						ButtonCode bc = BC_UNASSIGNED;
+			if (deltaX != 0 || deltaY != 0 || deltaZ != 0)
+				mOwner->_notifyMouseMoved(deltaX, deltaY, deltaZ);
 
-						// Unnamed mouse buttons
-						if(events[j].code >= BTN_MISC && events[j].code < BTN_MOUSE)
-							bc = (ButtonCode)((INT32)BC_MOUSE_BTN4 + (events[j].code - BTN_MISC));
-						// Named mouse buttons
-						else if(events[j].code >= BTN_MOUSE && events[j].code < BTN_JOYSTICK)
-						{
-							switch (events[j].code)
-							{
-							case BTN_LEFT:
-								bc = BC_MOUSE_LEFT;
-								break;
-							case BTN_MIDDLE:
-								bc = BC_MOUSE_MIDDLE;
-								break;
-							case BTN_RIGHT:
-								bc = BC_MOUSE_RIGHT;
-								break;
-							case BTN_SIDE:
-								bc = BC_MOUSE_BTN15;
-								break;
-							case BTN_EXTRA:
-								bc = BC_MOUSE_BTN16;
-								break;
-							case BTN_FORWARD:
-								bc = BC_MOUSE_BTN17;
-								break;
-							case BTN_BACK:
-								bc = BC_MOUSE_BTN18;
-								break;
-							case BTN_TASK:
-								bc = BC_MOUSE_BTN19;
-								break;
-							default:
-								break;
-							}
-						}
-
-						if(bc != BC_UNASSIGNED)
-						{
-							if (events[j].value)
-								mOwner->_notifyButtonPressed(0, bc, (UINT64) events[j].time.tv_usec);
-							else
-								mOwner->_notifyButtonReleased(0, bc, (UINT64) events[j].time.tv_usec);
-						}
-					}
-						break;
-					case EV_REL:
-					{
-						switch(events[j].code)
-						{
-						case REL_X:
-							relX += events[j].value;
-							break;
-						case REL_Y:
-							relY += events[j].value;
-							break;
-						case REL_WHEEL:
-							relZ += events[j].value;
-							break;
-						default:
-							break;
-						}
-						break;
-					}
-					default: break;
-					}
-				}
-			}
+			LinuxPlatform::mouseMotionEvent.deltaX -= deltaX;
+			LinuxPlatform::mouseMotionEvent.deltaY -= deltaY;
+			LinuxPlatform::mouseMotionEvent.deltaZ -= deltaZ;
+		}
+		else
+		{
+			// Discard accumulated data
+			LinuxPlatform::mouseMotionEvent.deltaX = 0;
+			LinuxPlatform::mouseMotionEvent.deltaY = 0;
+			LinuxPlatform::mouseMotionEvent.deltaZ = 0;
 		}
-
-		if(relX != 0 || relY != 0 || relZ != 0)
-			mOwner->_notifyMouseMoved(relX, relY, relZ);
 	}
 
 	void Mouse::changeCaptureContext(UINT64 windowHandle)

+ 218 - 30
Source/BansheeCore/Linux/BsLinuxPlatform.cpp

@@ -3,6 +3,7 @@
 #include "Image/BsPixelData.h"
 #include "Image/BsPixelUtil.h"
 #include "String/BsUnicode.h"
+#include "Linux/BsLinuxInput.h"
 #include "Linux/BsLinuxPlatform.h"
 #include "Linux/BsLinuxWindow.h"
 #include "RenderAPI/BsRenderWindow.h"
@@ -13,6 +14,7 @@
 #include <X11/Xcursor/Xcursor.h>
 #include <X11/Xlib.h>
 #include <X11/XKBlib.h>
+#include <X11/extensions/XInput2.h>
 #include <pwd.h>
 
 namespace bs
@@ -27,6 +29,10 @@ namespace bs
 
 	Event<void()> Platform::onMouseCaptureChanged;
 
+	Mutex LinuxPlatform::eventLock;
+	Queue<LinuxButtonEvent> LinuxPlatform::buttonEvents;
+	LinuxMouseMotionEvent LinuxPlatform::mouseMotionEvent;
+
 	enum class X11CursorType
 	{
 		Arrow,
@@ -45,7 +51,7 @@ namespace bs
 		Deny,
 
 		Count
-	};;
+	};
 
 	struct Platform::Pimpl
 	{
@@ -65,6 +71,11 @@ namespace bs
 		Atom atomWmStateMaxVert;
 		Atom atomWmStateMaxHorz;
 
+		// X11 Event handling
+		int xInput2Opcode;
+		UnorderedMap<String, KeyCode> keyNameMap; /**< Maps X11 key name (e.g. "TAB") to system-specific X11 KeyCode. */
+		Vector<ButtonCode> keyCodeMap; /**< Maps system-specific X11 KeyCode to Banshee ButtonCode. */
+
 		// Clipboard
 		WString clipboardData;
 
@@ -494,12 +505,29 @@ namespace bs
 		return L"";
 	}
 
+	/** Maps X11 mouse button codes to Banshee button codes. */
+	ButtonCode xButtonToButtonCode(int button)
+	{
+		switch (button)
+		{
+		case Button1:
+			return BC_MOUSE_LEFT;
+		case Button2:
+			return BC_MOUSE_MIDDLE;
+		case Button3:
+			return BC_MOUSE_RIGHT;
+		default:
+			return (ButtonCode)(BC_MOUSE_LEFT + button - 1);
+		}
+	}
+
 	/** Maps Banshee button codes to X11 names for physical key locations. */
-	const char* keyCodeToKeyName(ButtonCode code)
+	const char* buttonCodeToKeyName(ButtonCode code)
 	{
 		switch(code)
 		{
 			// Row #1
+		case BC_ESCAPE:		return "ESC";
 		case BC_F1:			return "FK01";
 		case BC_F2:			return "FK02";
 		case BC_F3:			return "FK03";
@@ -512,6 +540,9 @@ namespace bs
 		case BC_F10:		return "FK10";
 		case BC_F11:		return "FK11";
 		case BC_F12:		return "FK12";
+		case BC_F13:		return "FK13";
+		case BC_F14:		return "FK14";
+		case BC_F15:		return "FK15";
 
 			// Row #2
 		case BC_GRAVE:		return "TLDE";
@@ -543,8 +574,10 @@ namespace bs
 		case BC_P:			return "AD10";
 		case BC_LBRACKET:	return "AD11";
 		case BC_RBRACKET:	return "AD12";
+		case BC_RETURN:		return "RTRN";
 
 			// Row #4
+		case BC_CAPITAL:	return "CAPS";
 		case BC_A:			return "AC01";
 		case BC_S:			return "AC02";
 		case BC_D:			return "AC03";
@@ -559,6 +592,7 @@ namespace bs
 		case BC_BACKSLASH:	return "BKSL";
 
 			// Row #5
+		case BC_LSHIFT:		return "LFSH";
 		case BC_Z:			return "AB01";
 		case BC_X:			return "AB02";
 		case BC_C:			return "AB03";
@@ -569,6 +603,16 @@ namespace bs
 		case BC_COMMA:		return "AB08";
 		case BC_PERIOD:		return "AB09";
 		case BC_SLASH:		return "AB10";
+		case BC_RSHIFT:		return "RTSH";
+
+			// Row #6
+		case BC_LCONTROL:	return "LCTL";
+		case BC_LWIN:		return "LWIN";
+		case BC_LMENU:		return "LALT";
+		case BC_SPACE:		return "SPCE";
+		case BC_RMENU:		return "RALT";
+		case BC_RWIN:		return "RWIN";
+		case BC_RCONTROL:	return "RCTL";
 
 			// Keypad
 		case BC_NUMPAD0:	return "KP0";
@@ -582,7 +626,47 @@ namespace bs
 		case BC_NUMPAD8:	return "KP8";
 		case BC_NUMPAD9:	return "KP9";
 
+		case BC_NUMLOCK:		return "NMLK";
+		case BC_DIVIDE:			return "KPDV";
+		case BC_MULTIPLY:		return "KPMU";
+		case BC_SUBTRACT:		return "KPSU";
+		case BC_ADD:			return "KPAD";
+		case BC_DECIMAL:		return "KPDL";
+		case BC_NUMPADENTER:	return "KPEN";
+		case BC_NUMPADEQUALS:	return "KPEQ";
+
+			// Special keys
+		case BC_SCROLL:		return "SCLK";
+		case BC_PAUSE:		return "PAUS";
+
+		case BC_INSERT:		return "INS";
+		case BC_HOME:		return "HOME";
+		case BC_PGUP:		return "PGUP";
+		case BC_DELETE:		return "DELE";
+		case BC_END:		return "END";
+		case BC_PGDOWN:		return "PGDN";
+
+		case BC_UP:			return "UP";
+		case BC_LEFT:		return "LEFT";
+		case BC_DOWN:		return "DOWN";
+		case BC_RIGHT:		return "RGHT";
+
+		case BC_MUTE:		return "MUTE";
+		case BC_VOLUMEDOWN:	return "VOL-";
+		case BC_VOLUMEUP:	return "VOL+";
+		case BC_POWER:		return "POWR";
+
+			// International keys
+		case BC_OEM_102:	return "LSGT"; // German keyboard: < > |
+		case BC_KANA:		return "AB11"; // Taking a guess here, many layouts map <AB11> to "kana_RO"
+		case BC_YEN:		return "AE13"; // Taking a guess, often mapped to yen
+
 		default:
+			// Missing Japanese (?): KATA, HIRA, HENK, MUHE, JPCM
+			// Missing Korean (?): HNGL, HJCV
+			// Missing because it's not clear which BC_ is correct: PRSC (print screen), LVL3 (AltGr), MENU
+			// Misc: LNFD (line feed), I120, I126, I128, I129, COMP, STOP, AGAI (redo), PROP, UNDO, FRNT, COPY, OPEN, PAST
+			// FIND, CUT, HELP, I147-I190, FK16-FK24, MDSW (mode switch), ALT, META, SUPR, HYPR, I208-I253
 			break;
 		}
 
@@ -593,38 +677,15 @@ namespace bs
 	{
 		Lock lock(mData->lock);
 
-		static bool mapInitialized = false;
-		static UnorderedMap<String, KeyCode> keyMap;
-		if(!mapInitialized)
-		{
-			char name[XkbKeyNameLength + 1];
-
-			XkbDescPtr desc = XkbGetMap(mData->xDisplay, 0, XkbUseCoreKbd);
-			XkbGetNames(mData->xDisplay, XkbKeyNamesMask, desc);
-
-			for(UINT32 keyCode = desc->min_key_code; keyCode <= desc->max_key_code; keyCode++)
-			{
-				memcpy(name, desc->names->keys[keyCode].name, XkbKeyNameLength);
-				name[XkbKeyNameLength] = '\0';
-
-				keyMap[String(name)] = keyCode;
-			}
-
-			XkbFreeNames(desc, XkbKeyNamesMask, True);
-			XkbFreeKeyboard(desc, 0, True);
-
-			mapInitialized = true;
-		}
-
-		const char* keyName = keyCodeToKeyName((ButtonCode)buttonCode);
+		const char* keyName = buttonCodeToKeyName((ButtonCode)buttonCode);
 		if(keyName == nullptr)
 		{
 			// Not a printable key
 			return L"";
 		}
 
-		auto iterFind = keyMap.find(String(keyName));
-		if(iterFind == keyMap.end())
+		auto iterFind = mData->keyNameMap.find(String(keyName));
+		if(iterFind == mData->keyNameMap.end())
 		{
 			// Cannot find mapping, although this shouldn't really happen
 			return L"";
@@ -730,6 +791,27 @@ namespace bs
 		return nullptr;
 	}
 
+	/**
+	 * Enqueue a button press/release event to be handled by the main thread
+	 *
+	 * @param bc        ButtonCode for the button that was pressed or released
+	 * @param pressed   true if the button was pressed, false if it was released
+	 * @param timestamp Time when the event happened
+	 */
+	void enqueueButtonEvent(ButtonCode bc, bool pressed, UINT64 timestamp)
+	{
+		if (bc == BC_UNASSIGNED)
+			return;
+
+		Lock eventLock(LinuxPlatform::eventLock);
+
+		LinuxButtonEvent event;
+		event.button = bc;
+		event.pressed = pressed;
+		event.timestamp = timestamp;
+		LinuxPlatform::buttonEvents.push(event);
+	}
+
 	void Platform::_messagePump()
 	{
 		while(true)
@@ -742,6 +824,40 @@ namespace bs
 			XEvent event;
 			XNextEvent(mData->xDisplay, &event);
 
+			XGenericEventCookie* cookie = &event.xcookie;
+			if (cookie->type == GenericEvent && cookie->extension == mData->xInput2Opcode)
+			{
+				XGetEventData(mData->xDisplay, cookie);
+				XIRawEvent* xInput2Event = (XIRawEvent*) cookie->data;
+				switch (xInput2Event->evtype)
+				{
+				case XI_RawMotion:
+					if (xInput2Event->valuators.mask_len > 0)
+					{
+						// Assume X/Y delta is stored in valuators 0/1 and vertical scroll in valuator 3.
+						// While there is an API that reliably tells us the valuator index for vertical scroll, there's
+						// nothing "more reliable" for X/Y axes, as the only way to possibly identify them from device
+						// info is by axis name, so we can use the axis index directly just as well. GDK seems to assume
+						// 0 for x and 1 for y too, so that's hopefully safe, and 3 appears to be common for the scroll
+						// wheel.
+						float deltas[4] = {0};
+						int currentValuesIndex = 0;
+						for (unsigned int valuator = 0; valuator < 4; valuator++)
+							if (XIMaskIsSet(xInput2Event->valuators.mask, valuator))
+								deltas[valuator] = xInput2Event->raw_values[currentValuesIndex++];
+
+						Lock eventLock(LinuxPlatform::eventLock);
+						LinuxPlatform::mouseMotionEvent.deltaX += deltas[0];
+						LinuxPlatform::mouseMotionEvent.deltaY += deltas[1];
+						LinuxPlatform::mouseMotionEvent.deltaZ += deltas[3]; // Not a typo - 2 is for horizontal scroll.
+					}
+					break;
+				}
+
+				XFreeEventData(mData->xDisplay, cookie);
+			}
+
+
 			switch (event.type)
 			{
 			case ClientMessage:
@@ -767,6 +883,9 @@ namespace bs
 				break;
 			case KeyPress:
 			{
+				XKeyPressedEvent* keyEvent = (XKeyPressedEvent*) &event;
+				enqueueButtonEvent(mData->keyCodeMap[keyEvent->keycode], true, (UINT64) keyEvent->time);
+
 				// Process text input
 				KeySym keySym = XkbKeycodeToKeysym(mData->xDisplay, (KeyCode)event.xkey.keycode, 0, 0);
 
@@ -806,11 +925,16 @@ namespace bs
 			}
 				break;
 			case KeyRelease:
-				// Do nothing
+			{
+				XKeyReleasedEvent* keyEvent = (XKeyReleasedEvent*) &event;
+				enqueueButtonEvent(mData->keyCodeMap[keyEvent->keycode], false, (UINT64) keyEvent->time);
+			}
 				break;
 			case ButtonPress:
 			{
+				XButtonPressedEvent* buttonEvent = (XButtonPressedEvent*) &event;
 				UINT32 button = event.xbutton.button;
+				enqueueButtonEvent(xButtonToButtonCode(button), true, (UINT64) buttonEvent->time);
 
 				OSPointerButtonStates btnStates;
 				btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
@@ -878,7 +1002,9 @@ namespace bs
 			}
 			case ButtonRelease:
 			{
+				XButtonReleasedEvent* buttonEvent = (XButtonReleasedEvent*) &event;
 				UINT32 button = event.xbutton.button;
+				enqueueButtonEvent(xButtonToButtonCode(button), false, (UINT64) buttonEvent->time);
 
 				Vector2I pos;
 				pos.x = event.xbutton.x_root;
@@ -1129,6 +1255,32 @@ namespace bs
 		mData->xDisplay = XOpenDisplay(nullptr);
 		XSetErrorHandler(x11ErrorHandler);
 
+		// For raw, relative mouse motion events, XInput2 extension is required
+		int firstEvent;
+		int firstError;
+		if (!XQueryExtension(mData->xDisplay, "XInputExtension", &mData->xInput2Opcode, &firstEvent, &firstError)) {
+			BS_EXCEPT(InternalErrorException, "X Server doesn't support the XInput extension");
+		}
+
+		int majorVersion = 2;
+		int minorVersion = 0;
+		if (XIQueryVersion(mData->xDisplay, &majorVersion, &minorVersion) != Success) {
+			BS_EXCEPT(InternalErrorException, "X Server doesn't support at least the XInput 2.0 extension");
+		}
+
+		// Let XInput know we are interested in raw mouse movement events
+		XIEventMask mask;
+		mask.deviceid = XIAllDevices;
+		mask.mask_len = XIMaskLen(XI_LASTEVENT);
+		unsigned char maskBuffer[mask.mask_len] = {0};
+		mask.mask = maskBuffer;
+		XISetMask(mask.mask, XI_RawMotion);
+
+		// "RawEvents are sent exclusively to all root windows", so this should receive all events, even though we only
+		// select on one display's root window (untested for lack of second screen).
+		XISelectEvents(mData->xDisplay, XRootWindow(mData->xDisplay, DefaultScreen(mData->xDisplay)), &mask, 1);
+		XFlush(mData->xDisplay);
+
 		if(XSupportsLocale())
 		{
 			XSetLocaleModifiers("@im=none");
@@ -1159,6 +1311,42 @@ namespace bs
 		mData->emptyCursor = XCreatePixmapCursor(mData->xDisplay, pixmap, pixmap, &color, &color, 0, 0);
 
 		XFreePixmap(mData->xDisplay, pixmap);
+
+		// Initialize "unique X11 keyname" -> "X11 keycode" map
+		char name[XkbKeyNameLength + 1];
+
+		XkbDescPtr desc = XkbGetMap(mData->xDisplay, 0, XkbUseCoreKbd);
+		XkbGetNames(mData->xDisplay, XkbKeyNamesMask, desc);
+
+		for (UINT32 keyCode = desc->min_key_code; keyCode <= desc->max_key_code; keyCode++)
+		{
+			memcpy(name, desc->names->keys[keyCode].name, XkbKeyNameLength);
+			name[XkbKeyNameLength] = '\0';
+
+			mData->keyNameMap[String(name)] = keyCode;
+		}
+
+		XkbFreeNames(desc, XkbKeyNamesMask, True);
+		XkbFreeKeyboard(desc, 0, True);
+
+		// Initialize "X11 keycode" -> "Banshee ButtonCode" map, based on the keyNameMap and keyCodeToKeyName()
+		mData->keyCodeMap.resize(desc->max_key_code + 1, BC_UNASSIGNED);
+		for (UINT32 buttonCodeNum = BC_UNASSIGNED; buttonCodeNum <= BC_NumKeys; buttonCodeNum++)
+		{
+			ButtonCode buttonCode = (ButtonCode) buttonCodeNum;
+			const char* keyNameCStr = buttonCodeToKeyName(buttonCode);
+
+			if (keyNameCStr != nullptr)
+			{
+				String keyName = String(keyNameCStr);
+				auto iterFind = mData->keyNameMap.find(keyName);
+				if (iterFind != mData->keyNameMap.end())
+				{
+					KeyCode keyCode = iterFind->second;
+					mData->keyCodeMap[keyCode] = buttonCode;
+				}
+			}
+		}
 	}
 
 	void Platform::_update()
@@ -1296,4 +1484,4 @@ namespace bs
 
 		return pixmap;
 	}
-}
+}

+ 16 - 0
Source/BansheeCore/Linux/BsLinuxPlatform.h

@@ -3,6 +3,7 @@
 #pragma once
 
 #include "Platform/BsPlatform.h"
+#include "Linux/BsLinuxInput.h"
 #include <X11/X.h>
 #include <X11/Xlib.h>
 
@@ -43,6 +44,21 @@ namespace bs
 
 		/** Generates a X11 Pixmap from the provided pixel data. */
 		static Pixmap createPixmap(const PixelData& data, UINT32 depth);
+
+		/** Mutex for accessing buttonEvents / mouseEvent. */
+		static Mutex eventLock;
+
+		/**
+		 * Stores events captured on the core thread, waiting to be processed by the main thread.
+		 * Always lock on eventLock when accessing this.
+		 */
+		static Queue<LinuxButtonEvent> buttonEvents;
+
+		/**
+		 * Stores accumulated mouse motion events, waiting to be processed by the main thread.
+		 * Always lock on eventLock when accessing this.
+		 */
+		static LinuxMouseMotionEvent mouseMotionEvent;
 	};
 
 	/** @} */

+ 5 - 1
Source/BansheeUtility/CMakeLists.txt

@@ -15,6 +15,10 @@ if(LINUX)
 	if(NOT X11_Xrandr_FOUND)
 		message(FATAL_ERROR "Could not find XRandR library.")
 	endif()
+
+	if(NOT X11_Xi_FOUND)
+		message(FATAL_ERROR "Could not find Xi (XInput) library.")
+	endif()
 endif()
 
 # Third party (non-package) libraries
@@ -52,7 +56,7 @@ else()
 	target_link_libraries(BansheeUtility PRIVATE dl pthread)
 
 	## External lib: X11, LibUUID
-	target_link_libraries(BansheeUtility PUBLIC ${X11_LIBRARIES} ${X11_Xcursor_LIB} ${X11_Xrandr_LIB})
+	target_link_libraries(BansheeUtility PUBLIC ${X11_LIBRARIES} ${X11_Xcursor_LIB} ${X11_Xrandr_LIB} ${X11_Xi_LIB})
 	target_link_libraries(BansheeUtility PRIVATE ${LibUUID_LIBRARIES})
 endif()