diff --git a/CMakeLists.txt b/CMakeLists.txt index ac4e97e944..324a2406b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -430,6 +430,12 @@ if(NOT ANDROID) else() add_definitions(-DHAVE_XRANDR=0) endif(XRANDR_FOUND) + include(FindX11XInput2) + if(X11_Xinput2_FOUND) + add_definitions(-DHAVE_X11_XINPUT2=1) + else() + add_definitions(-DHAVE_X11_XINPUT2=0) + endif(X11_Xinput2_FOUND) endif() if(ENCODE_FRAMEDUMPS) check_libav() diff --git a/CMakeTests/FindX11XInput2.cmake b/CMakeTests/FindX11XInput2.cmake new file mode 100644 index 0000000000..1b1f26f2cd --- /dev/null +++ b/CMakeTests/FindX11XInput2.cmake @@ -0,0 +1,30 @@ +# For finding the X11 XInput extension, version 2. Because the standard CMake +# FindX11.cmake may only know to look for version 1. Newer versions of CMake +# than 2.8.7 might not have this problem, I wouldn't know. Better to be safe. + + +if(USE_X11) + + IF(NOT X11_FOUND) + INCLUDE(FindX11) + ENDIF(NOT X11_FOUND) + + IF(X11_FOUND) + FIND_PATH(X11_Xinput2_INCLUDE_PATH X11/extensions/XInput2.h ${X11_INC_SEARCH_PATH}) + + FIND_LIBRARY(X11_Xinput2_LIB Xi ${X11_LIB_SEARCH_PATH}) + + IF (X11_Xinput2_INCLUDE_PATH AND X11_Xinput2_LIB) + SET(X11_Xinput2_FOUND TRUE) + SET(X11_INCLUDE_DIR ${X11_INCLUDE_DIR} ${X11_Xinput2_INCLUDE_PATH}) + message("X11 Xinput2 found") + ENDIF (X11_Xinput2_INCLUDE_PATH AND X11_Xinput2_LIB) + + MARK_AS_ADVANCED( + X11_Xinput2_INCLUDE_PATH + X11_Xinput2_LIB + ) + + ENDIF(X11_FOUND) + +endif() diff --git a/Source/Core/InputCommon/CMakeLists.txt b/Source/Core/InputCommon/CMakeLists.txt index 518bdbb3ba..05ea6eb35e 100644 --- a/Source/Core/InputCommon/CMakeLists.txt +++ b/Source/Core/InputCommon/CMakeLists.txt @@ -23,6 +23,10 @@ elseif(X11_FOUND) set(SRCS ${SRCS} Src/ControllerInterface/SDL/SDL.cpp Src/ControllerInterface/Xlib/Xlib.cpp) + if(X11_Xinput2_FOUND) + set(SRCS ${SRCS} + Src/ControllerInterface/Xlib/XInput2.cpp) + endif() elseif(ANDROID) set(SRCS ${SRCS} Src/ControllerInterface/Android/Android.cpp) diff --git a/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.cpp b/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.cpp index 58e0653bbe..f0870a18a4 100644 --- a/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.cpp +++ b/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.cpp @@ -8,6 +8,9 @@ #endif #ifdef CIFACE_USE_XLIB #include "Xlib/Xlib.h" + #ifdef CIFACE_USE_X11_XINPUT2 + #include "Xlib/XInput2.h" + #endif #endif #ifdef CIFACE_USE_OSX #include "OSX/OSX.h" @@ -48,6 +51,9 @@ void ControllerInterface::Initialize() #endif #ifdef CIFACE_USE_XLIB ciface::Xlib::Init(m_devices, m_hwnd); + #ifdef CIFACE_USE_X11_XINPUT2 + ciface::XInput2::Init(m_devices, m_hwnd); + #endif #endif #ifdef CIFACE_USE_OSX ciface::OSX::Init(m_devices, m_hwnd); diff --git a/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.h b/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.h index 57903a08a7..711f393894 100644 --- a/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.h +++ b/Source/Core/InputCommon/Src/ControllerInterface/ControllerInterface.h @@ -21,6 +21,9 @@ #if defined(HAVE_X11) && HAVE_X11 #define CIFACE_USE_XLIB #define CIFACE_USE_SDL + #if defined(HAVE_X11_XINPUT2) && HAVE_X11_XINPUT2 + #define CIFACE_USE_X11_XINPUT2 + #endif #endif #if defined(__APPLE__) #define CIFACE_USE_OSX diff --git a/Source/Core/InputCommon/Src/ControllerInterface/Xlib/XInput2.cpp b/Source/Core/InputCommon/Src/ControllerInterface/Xlib/XInput2.cpp new file mode 100644 index 0000000000..e4abe03f2f --- /dev/null +++ b/Source/Core/InputCommon/Src/ControllerInterface/Xlib/XInput2.cpp @@ -0,0 +1,387 @@ +// Copyright 2013 Max Eliaser +// Licensed under the GNU General Public License, version 2 or higher. +// Refer to the license.txt file included. + + +#include "XInput2.h" +#include +#include + +// This is an input plugin using the XInput 2.0 extension to the X11 protocol, +// loosely based on the old XLib plugin. (Has nothing to do with the XInput +// API on Windows.) + +// This plugin creates one KeyboardMouse object for each master pointer/ +// keyboard pair. Each KeyboardMouse object exports four types of controls: +// * Mouse button controls: hardcoded at five of them, but could be made to +// support infinitely many mouse buttons in theory; XInput2 has no limit. +// * Mouse cursor controls: one for each cardinal direction. Calculated by +// comparing the absolute position of the mouse pointer on screen to the +// center of the emulator window. +// * Mouse axis controls: one for each cardinal direction. Calculated using +// a running average of relative mouse motion on each axis. +// * Key controls: these correspond to a limited subset of the keyboard +// keys. + + +// Mouse axis control tuning. Unlike absolute mouse position, relative mouse +// motion data needs to be tweaked and smoothed out a bit to be usable. + +// Mouse axis control output is simply divided by this number. In practice, +// that just means you can use a smaller "dead zone" if you bind axis controls +// to a joystick. No real need to make this customizable. +#define MOUSE_AXIS_SENSITIVITY 8.0f + +// The mouse axis controls use a weighted running average. Each frame, the new +// value is the average of the old value and the amount of relative mouse +// motion during that frame. The old value is weighted by a ratio of +// MOUSE_AXIS_SMOOTHING:1 compared to the new value. Increasing +// MOUSE_AXIS_SMOOTHING makes the controls smoother, decreasing it makes them +// more responsive. This might be useful as a user-customizable option. +#define MOUSE_AXIS_SMOOTHING 1.5f + +namespace ciface +{ +namespace XInput2 +{ + +// This function will add zero or more KeyboardMouse objects to devices. +void Init(std::vector& devices, void* const hwnd) +{ + Display* dpy; + + dpy = XOpenDisplay(NULL); + + // xi_opcode is important; it will be used to identify XInput events by + // the polling loop in UpdateInput. + int xi_opcode, event, error; + + // verify that the XInput extension is available + if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) + return; + + // verify that the XInput extension is at at least version 2.0 + int major = 2, minor = 0; + + if (XIQueryVersion(dpy, &major, &minor) != Success) + return; + + // register all master devices with Dolphin + + XIDeviceInfo* all_masters; + XIDeviceInfo* current_master; + int num_masters; + + all_masters = XIQueryDevice(dpy, XIAllMasterDevices, &num_masters); + + for (int i = 0; i < num_masters; i++) + { + current_master = &all_masters[i]; + if (current_master->use == XIMasterPointer) + // Since current_master is a master pointer, its attachment must + // be a master keyboard. + devices.push_back(new KeyboardMouse((Window)hwnd, xi_opcode, current_master->deviceid, current_master->attachment)); + } + + XCloseDisplay(dpy); + + XIFreeDeviceInfo(all_masters); +} + +// Apply the event mask to the device and all its slaves. Only used in the +// constructor. Remember, each KeyboardMouse has its own copy of the event +// stream, which is how multiple event masks can "coexist." +void KeyboardMouse::SelectEventsForDevice(Window window, XIEventMask *mask, int deviceid) +{ + // Set the event mask for the master device. + + mask->deviceid = deviceid; + XISelectEvents(m_display, window, mask, 1); + + // Query all the master device's slaves and set the same event mask for + // those too. There are two reasons we want to do this. For mouse devices, + // we want the raw motion events, and only slaves (i.e. physical hardware + // devices) emit those. For keyboard devices, selecting slaves avoids + // dealing with key focus. + + XIDeviceInfo* all_slaves; + XIDeviceInfo* current_slave; + int num_slaves; + + all_slaves = XIQueryDevice(m_display, XIAllDevices, &num_slaves); + + for (int i = 0; i < num_slaves; i++) + { + current_slave = &all_slaves[i]; + if ((current_slave->use != XISlavePointer && current_slave->use != XISlaveKeyboard) || current_slave->attachment != deviceid) + continue; + mask->deviceid = current_slave->deviceid; + XISelectEvents(m_display, window, mask, 1); + } + + XIFreeDeviceInfo(all_slaves); +} + +KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboard) + : m_window(window), xi_opcode(opcode), pointer_deviceid(pointer), keyboard_deviceid(keyboard) +{ + memset(&m_state, 0, sizeof(m_state)); + + // The cool thing about each KeyboardMouse object having its own Display + // is that each one gets its own separate copy of the X11 event stream, + // which it can individually filter to get just the events it's interested + // in. So be aware that each KeyboardMouse object actually has its own X11 + // "context." + m_display = XOpenDisplay(NULL); + + int min_keycode, max_keycode; + XDisplayKeycodes(m_display, &min_keycode, &max_keycode); + + int unused; // should always be 1 + XIDeviceInfo* pointer_device = XIQueryDevice(m_display, pointer_deviceid, &unused); + name = std::string(pointer_device->name); + XIFreeDeviceInfo(pointer_device); + + XIEventMask mask; + unsigned char mask_buf[(XI_LASTEVENT + 7)/8]; + + mask.mask_len = sizeof(mask_buf); + mask.mask = mask_buf; + memset(mask_buf, 0, sizeof(mask_buf)); + + XISetMask(mask_buf, XI_ButtonPress); + XISetMask(mask_buf, XI_ButtonRelease); + XISetMask(mask_buf, XI_RawMotion); + XISetMask(mask_buf, XI_KeyPress); + XISetMask(mask_buf, XI_KeyRelease); + + SelectEventsForDevice(DefaultRootWindow(m_display), &mask, pointer_deviceid); + SelectEventsForDevice(DefaultRootWindow(m_display), &mask, keyboard_deviceid); + + // Keyboard Keys + for (int i = min_keycode; i <= max_keycode; ++i) + { + Key* temp_key = new Key(m_display, i, m_state.keyboard); + if (temp_key->m_keyname.length()) + AddInput(temp_key); + else + delete temp_key; + } + + // Mouse Buttons + for (int i = 0; i < 5; i++) + AddInput(new Button(i, m_state.buttons)); + + // Mouse Cursor, X-/+ and Y-/+ + for (int i = 0; i != 4; ++i) + AddInput(new Cursor(!!(i & 2), !!(i & 1), (&m_state.cursor.x)[!!(i & 2)])); + + // Mouse Axis, X-/+ and Y-/+ + for (int i = 0; i != 4; ++i) + AddInput(new Axis(!!(i & 2), !!(i & 1), (&m_state.axis.x)[!!(i & 2)])); +} + +KeyboardMouse::~KeyboardMouse() +{ + XCloseDisplay(m_display); +} + +// Update the mouse cursor controls +void KeyboardMouse::UpdateCursor() +{ + double root_x, root_y, win_x, win_y; + Window root, child; + + // unused-- we're not interested in button presses here, as those are + // updated using events + XIButtonState button_state; + XIModifierState mods; + XIGroupState group; + + XIQueryPointer(m_display, pointer_deviceid, m_window, &root, &child, &root_x, &root_y, &win_x, &win_y, &button_state, &mods, &group); + + free (button_state.mask); + + XWindowAttributes win_attribs; + XGetWindowAttributes(m_display, m_window, &win_attribs); + + // the mouse position as a range from -1 to 1 + m_state.cursor.x = win_x / (float)win_attribs.width * 2 - 1; + m_state.cursor.y = win_y / (float)win_attribs.height * 2 - 1; +} + +bool KeyboardMouse::UpdateInput() +{ + XFlush(m_display); + + // Get the absolute position of the mouse pointer + UpdateCursor(); + + // for the axis controls + float delta_x = 0.0f, delta_y = 0.0f; + double delta_delta; + + // Iterate through the event queue - update the axis controls, mouse + // button controls, and keyboard controls. + XEvent event; + while (XPending(m_display)) + { + XNextEvent(m_display, &event); + + if (event.xcookie.type != GenericEvent) + continue; + if (event.xcookie.extension != xi_opcode) + continue; + if (!XGetEventData(m_display, &event.xcookie)) + continue; + + // only one of these will get used + XIDeviceEvent* dev_event = (XIDeviceEvent*)event.xcookie.data; + XIRawEvent* raw_event = (XIRawEvent*)event.xcookie.data; + + switch (event.xcookie.evtype) + { + case XI_ButtonPress: + m_state.buttons |= 1<<(dev_event->detail-1); + break; + case XI_ButtonRelease: + m_state.buttons &= ~(1<<(dev_event->detail-1)); + break; + case XI_KeyPress: + m_state.keyboard[dev_event->detail / 8] |= 1<<(dev_event->detail % 8); + break; + case XI_KeyRelease: + m_state.keyboard[dev_event->detail / 8] &= ~(1<<(dev_event->detail % 8)); + break; + case XI_RawMotion: + // always safe because there is always at least one byte in + // raw_event->valuators.mask, and if a bit is set in the mask, + // then the value in raw_values is also available. + if (XIMaskIsSet(raw_event->valuators.mask, 0)) + { + delta_delta = raw_event->raw_values[0]; + // test for inf and nan + if (delta_delta == delta_delta && 1+delta_delta != delta_delta) + delta_x += delta_delta; + } + if (XIMaskIsSet(raw_event->valuators.mask, 1)) + { + delta_delta = raw_event->raw_values[1]; + // test for inf and nan + if (delta_delta == delta_delta && 1+delta_delta != delta_delta) + delta_y += delta_delta; + } + break; + } + + XFreeEventData(m_display, &event.xcookie); + } + + // apply axis smoothing + m_state.axis.x *= MOUSE_AXIS_SMOOTHING; + m_state.axis.x += delta_x; + m_state.axis.x /= MOUSE_AXIS_SMOOTHING+1.0f; + m_state.axis.y *= MOUSE_AXIS_SMOOTHING; + m_state.axis.y += delta_y; + m_state.axis.y /= MOUSE_AXIS_SMOOTHING+1.0f; + + return true; +} + +bool KeyboardMouse::UpdateOutput() +{ + return true; +} + +std::string KeyboardMouse::GetName() const +{ + // This is the name string we got from the X server for this master + // pointer/keyboard pair. + return name; +} + +std::string KeyboardMouse::GetSource() const +{ + return "XInput2"; +} + +int KeyboardMouse::GetId() const +{ + return -1; +} + +KeyboardMouse::Key::Key(Display* const display, KeyCode keycode, const char* keyboard) + : m_display(display), m_keyboard(keyboard), m_keycode(keycode) +{ + int i = 0; + KeySym keysym = 0; + do + { + keysym = XkbKeycodeToKeysym(m_display, keycode, i, 0); + i++; + } + while (keysym == NoSymbol && i < 8); + + // Convert to upper case for the keyname + if (keysym >= 97 && keysym <= 122) + keysym -= 32; + + // 0x0110ffff is the top of the unicode character range according + // to keysymdef.h although it is probably more than we need. + if (keysym == NoSymbol || keysym > 0x0110ffff || + XKeysymToString(keysym) == NULL) + m_keyname = std::string(); + else + m_keyname = std::string(XKeysymToString(keysym)); +} + +ControlState KeyboardMouse::Key::GetState() const +{ + return (m_keyboard[m_keycode / 8] & (1 << (m_keycode % 8))) != 0; +} + +ControlState KeyboardMouse::Button::GetState() const +{ + return ((m_buttons & (1 << m_index)) != 0); +} + +ControlState KeyboardMouse::Cursor::GetState() const +{ + return std::max(0.0f, m_cursor / (m_positive ? 1.0f : -1.0f)); +} + +ControlState KeyboardMouse::Axis::GetState() const +{ + return std::max(0.0f, m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY)); +} + +std::string KeyboardMouse::Key::GetName() const +{ + return m_keyname; +} + +std::string KeyboardMouse::Cursor::GetName() const +{ + static char tmpstr[] = "Cursor .."; + tmpstr[7] = (char)('X' + m_index); + tmpstr[8] = (m_positive ? '+' : '-'); + return tmpstr; +} + +std::string KeyboardMouse::Axis::GetName() const +{ + static char tmpstr[] = "Axis .."; + tmpstr[5] = (char)('X' + m_index); + tmpstr[6] = (m_positive ? '+' : '-'); + return tmpstr; +} + +std::string KeyboardMouse::Button::GetName() const +{ + static char tmpstr[] = "Click ."; + tmpstr[6] = m_index + '1'; + return tmpstr; +} + +} +} diff --git a/Source/Core/InputCommon/Src/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/Src/ControllerInterface/Xlib/XInput2.h new file mode 100644 index 0000000000..3f138f1416 --- /dev/null +++ b/Source/Core/InputCommon/Src/ControllerInterface/Xlib/XInput2.h @@ -0,0 +1,124 @@ +// Copyright 2013 Max Eliaser +// Licensed under the GNU General Public License, version 2 or higher. +// Refer to the license.txt file included. + +// See XInput2.cpp for extensive documentation. + +#ifndef _CIFACE_X11_XINPUT2_H_ +#define _CIFACE_X11_XINPUT2_H_ + +#include "../Device.h" + +extern "C" { +#include +#include +#include +} + +namespace ciface +{ +namespace XInput2 +{ + +void Init(std::vector& devices, void* const hwnd); + +class KeyboardMouse : public Core::Device +{ + +private: + struct State + { + char keyboard[32]; + unsigned int buttons; + struct + { + float x, y; + } cursor, axis; + }; + + class Key : public Input + { + friend class KeyboardMouse; + public: + std::string GetName() const; + Key(Display* display, KeyCode keycode, const char* keyboard); + ControlState GetState() const; + + private: + std::string m_keyname; + Display* const m_display; + const char* const m_keyboard; + const KeyCode m_keycode; + }; + + class Button : public Input + { + public: + std::string GetName() const; + Button(unsigned int index, unsigned int& buttons) + : m_buttons(buttons), m_index(index) {} + ControlState GetState() const; + + private: + const unsigned int& m_buttons; + const unsigned int m_index; + }; + + class Cursor : public Input + { + public: + std::string GetName() const; + bool IsDetectable() { return false; } + Cursor(u8 index, bool positive, const float& cursor) + : m_cursor(cursor), m_index(index), m_positive(positive) {} + ControlState GetState() const; + + private: + const float& m_cursor; + const u8 m_index; + const bool m_positive; + }; + + class Axis : public Input + { + public: + std::string GetName() const; + bool IsDetectable() { return false; } + Axis(u8 index, bool positive, const float& axis) + : m_axis(axis), m_index(index), m_positive(positive) {} + ControlState GetState() const; + + private: + const float& m_axis; + const u8 m_index; + const bool m_positive; + }; + +private: + void SelectEventsForDevice(Window window, XIEventMask *mask, int deviceid); + void UpdateCursor(); + +public: + bool UpdateInput(); + bool UpdateOutput(); + + KeyboardMouse(Window window, int opcode, int pointer_deviceid, int keyboard_deviceid); + ~KeyboardMouse(); + + std::string GetName() const; + std::string GetSource() const; + int GetId() const; + +private: + Window m_window; + Display* m_display; + State m_state; + int xi_opcode; + const int pointer_deviceid, keyboard_deviceid; + std::string name; +}; + +} +} + +#endif