Merge pull request #11762 from jbosboom/xinput2-raw-event-query
Xinput2: use raw events and queries
This commit is contained in:
commit
38b033a476
|
@ -5,12 +5,14 @@
|
||||||
|
|
||||||
#include <X11/XKBlib.h>
|
#include <X11/XKBlib.h>
|
||||||
|
|
||||||
|
#include <X11/extensions/XInput2.h>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
#include "Common/StringUtil.h"
|
#include "Common/StringUtil.h"
|
||||||
|
|
||||||
#include "Core/Host.h"
|
#include "Core/Host.h"
|
||||||
|
@ -54,6 +56,14 @@
|
||||||
// more cleanly separate each scroll wheel click, but risks dropping some inputs
|
// more cleanly separate each scroll wheel click, but risks dropping some inputs
|
||||||
#define SCROLL_AXIS_DECAY 1.1f
|
#define SCROLL_AXIS_DECAY 1.1f
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// We need XInput 2.1 to get raw events on the root window even while another
|
||||||
|
// client has a grab. If we request 2.2 or later, the server will not generate
|
||||||
|
// emulated button presses from touch events, so we want exactly 2.1.
|
||||||
|
constexpr int XINPUT_MAJOR = 2, XINPUT_MINOR = 1;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
namespace ciface::XInput2
|
namespace ciface::XInput2
|
||||||
{
|
{
|
||||||
// This function will add zero or more KeyboardMouse objects to devices.
|
// This function will add zero or more KeyboardMouse objects to devices.
|
||||||
|
@ -67,13 +77,18 @@ void PopulateDevices(void* const hwnd)
|
||||||
|
|
||||||
// verify that the XInput extension is available
|
// verify that the XInput extension is available
|
||||||
if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error))
|
if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error))
|
||||||
|
{
|
||||||
|
WARN_LOG_FMT(CONTROLLERINTERFACE, "XInput extension not available (XQueryExtension)");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// verify that the XInput extension is at at least version 2.0
|
int major = XINPUT_MAJOR, minor = XINPUT_MINOR;
|
||||||
int major = 2, minor = 0;
|
if (XIQueryVersion(dpy, &major, &minor) != Success || major < XINPUT_MAJOR ||
|
||||||
|
(major == XINPUT_MAJOR && minor < XINPUT_MINOR))
|
||||||
if (XIQueryVersion(dpy, &major, &minor) != Success)
|
{
|
||||||
|
WARN_LOG_FMT(CONTROLLERINTERFACE, "XInput extension not available (XIQueryVersion)");
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// register all master devices with Dolphin
|
// register all master devices with Dolphin
|
||||||
|
|
||||||
|
@ -115,39 +130,6 @@ void PopulateDevices(void* const hwnd)
|
||||||
XIFreeDeviceInfo(all_masters);
|
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(XIEventMask* mask, int deviceid)
|
|
||||||
{
|
|
||||||
// Set the event mask for the master device.
|
|
||||||
mask->deviceid = deviceid;
|
|
||||||
XISelectEvents(m_display, DefaultRootWindow(m_display), 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.
|
|
||||||
|
|
||||||
int num_slaves;
|
|
||||||
XIDeviceInfo* const all_slaves = XIQueryDevice(m_display, XIAllDevices, &num_slaves);
|
|
||||||
|
|
||||||
for (int i = 0; i < num_slaves; i++)
|
|
||||||
{
|
|
||||||
XIDeviceInfo* const slave = &all_slaves[i];
|
|
||||||
if ((slave->use != XISlavePointer && slave->use != XISlaveKeyboard) ||
|
|
||||||
slave->attachment != deviceid)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
mask->deviceid = slave->deviceid;
|
|
||||||
XISelectEvents(m_display, DefaultRootWindow(m_display), mask, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
XIFreeDeviceInfo(all_slaves);
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboard,
|
KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboard,
|
||||||
double scroll_increment_)
|
double scroll_increment_)
|
||||||
: m_window(window), xi_opcode(opcode), pointer_deviceid(pointer), keyboard_deviceid(keyboard),
|
: m_window(window), xi_opcode(opcode), pointer_deviceid(pointer), keyboard_deviceid(keyboard),
|
||||||
|
@ -160,6 +142,9 @@ KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboar
|
||||||
// "context."
|
// "context."
|
||||||
m_display = XOpenDisplay(nullptr);
|
m_display = XOpenDisplay(nullptr);
|
||||||
|
|
||||||
|
int major = XINPUT_MAJOR, minor = XINPUT_MINOR;
|
||||||
|
XIQueryVersion(m_display, &major, &minor);
|
||||||
|
|
||||||
// should always be 1
|
// should always be 1
|
||||||
int unused;
|
int unused;
|
||||||
XIDeviceInfo* const pointer_device = XIQueryDevice(m_display, pointer_deviceid, &unused);
|
XIDeviceInfo* const pointer_device = XIQueryDevice(m_display, pointer_deviceid, &unused);
|
||||||
|
@ -172,28 +157,28 @@ KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboar
|
||||||
|
|
||||||
{
|
{
|
||||||
unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {};
|
unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {};
|
||||||
XISetMask(mask_buf, XI_ButtonPress);
|
XISetMask(mask_buf, XI_RawButtonPress);
|
||||||
XISetMask(mask_buf, XI_ButtonRelease);
|
XISetMask(mask_buf, XI_RawButtonRelease);
|
||||||
XISetMask(mask_buf, XI_RawMotion);
|
XISetMask(mask_buf, XI_RawMotion);
|
||||||
|
|
||||||
XIEventMask mask;
|
XIEventMask mask;
|
||||||
mask.mask = mask_buf;
|
mask.mask = mask_buf;
|
||||||
mask.mask_len = sizeof(mask_buf);
|
mask.mask_len = sizeof(mask_buf);
|
||||||
|
|
||||||
SelectEventsForDevice(&mask, pointer_deviceid);
|
mask.deviceid = pointer_deviceid;
|
||||||
|
XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {};
|
unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {};
|
||||||
XISetMask(mask_buf, XI_KeyPress);
|
XISetMask(mask_buf, XI_RawKeyPress);
|
||||||
XISetMask(mask_buf, XI_KeyRelease);
|
XISetMask(mask_buf, XI_RawKeyRelease);
|
||||||
XISetMask(mask_buf, XI_FocusOut);
|
|
||||||
|
|
||||||
XIEventMask mask;
|
XIEventMask mask;
|
||||||
mask.mask = mask_buf;
|
mask.mask = mask_buf;
|
||||||
mask.mask_len = sizeof(mask_buf);
|
mask.mask_len = sizeof(mask_buf);
|
||||||
|
mask.deviceid = keyboard_deviceid;
|
||||||
SelectEventsForDevice(&mask, keyboard_deviceid);
|
XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard Keys
|
// Keyboard Keys
|
||||||
|
@ -254,6 +239,25 @@ void KeyboardMouse::UpdateCursor(bool should_center_mouse)
|
||||||
const auto win_width = std::max(win_attribs.width, 1);
|
const auto win_width = std::max(win_attribs.width, 1);
|
||||||
const auto win_height = std::max(win_attribs.height, 1);
|
const auto win_height = std::max(win_attribs.height, 1);
|
||||||
|
|
||||||
|
{
|
||||||
|
XIButtonState button_state;
|
||||||
|
XIModifierState mods;
|
||||||
|
XIGroupState group;
|
||||||
|
|
||||||
|
// Get the absolute position of the mouse pointer and the button state.
|
||||||
|
XIQueryPointer(m_display, pointer_deviceid, m_window, &root, &child, &root_x, &root_y, &win_x,
|
||||||
|
&win_y, &button_state, &mods, &group);
|
||||||
|
|
||||||
|
// X buttons are 1-indexed, so to get 32 button bits we need a larger type
|
||||||
|
// for the shift.
|
||||||
|
u64 buttons_zero_indexed = 0;
|
||||||
|
std::memcpy(&buttons_zero_indexed, button_state.mask,
|
||||||
|
std::min<size_t>(button_state.mask_len, sizeof(m_state.buttons)));
|
||||||
|
m_state.buttons = buttons_zero_indexed >> 1;
|
||||||
|
|
||||||
|
free(button_state.mask);
|
||||||
|
}
|
||||||
|
|
||||||
if (should_center_mouse)
|
if (should_center_mouse)
|
||||||
{
|
{
|
||||||
win_x = win_width / 2;
|
win_x = win_width / 2;
|
||||||
|
@ -263,19 +267,6 @@ void KeyboardMouse::UpdateCursor(bool should_center_mouse)
|
||||||
|
|
||||||
g_controller_interface.SetMouseCenteringRequested(false);
|
g_controller_interface.SetMouseCenteringRequested(false);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto window_scale = g_controller_interface.GetWindowInputScale();
|
const auto window_scale = g_controller_interface.GetWindowInputScale();
|
||||||
|
|
||||||
|
@ -291,10 +282,10 @@ void KeyboardMouse::UpdateInput()
|
||||||
// for the axis controls
|
// for the axis controls
|
||||||
float delta_x = 0.0f, delta_y = 0.0f, delta_z = 0.0f;
|
float delta_x = 0.0f, delta_y = 0.0f, delta_z = 0.0f;
|
||||||
double delta_delta;
|
double delta_delta;
|
||||||
bool mouse_moved = false;
|
bool update_mouse = false, update_keyboard = false;
|
||||||
|
|
||||||
// Iterate through the event queue - update the axis controls, mouse
|
// Iterate through the event queue, processing raw pointer motion events and
|
||||||
// button controls, and keyboard controls.
|
// noting whether the button or key state has changed.
|
||||||
XEvent event;
|
XEvent event;
|
||||||
while (XPending(m_display))
|
while (XPending(m_display))
|
||||||
{
|
{
|
||||||
|
@ -307,28 +298,21 @@ void KeyboardMouse::UpdateInput()
|
||||||
if (!XGetEventData(m_display, &event.xcookie))
|
if (!XGetEventData(m_display, &event.xcookie))
|
||||||
continue;
|
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)
|
switch (event.xcookie.evtype)
|
||||||
{
|
{
|
||||||
case XI_ButtonPress:
|
case XI_RawButtonPress:
|
||||||
m_state.buttons |= 1 << (dev_event->detail - 1);
|
case XI_RawButtonRelease:
|
||||||
|
update_mouse = true;
|
||||||
break;
|
break;
|
||||||
case XI_ButtonRelease:
|
case XI_RawKeyPress:
|
||||||
m_state.buttons &= ~(1 << (dev_event->detail - 1));
|
case XI_RawKeyRelease:
|
||||||
break;
|
update_keyboard = true;
|
||||||
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;
|
break;
|
||||||
case XI_RawMotion:
|
case XI_RawMotion:
|
||||||
{
|
{
|
||||||
mouse_moved = true;
|
update_mouse = true;
|
||||||
|
|
||||||
|
XIRawEvent* raw_event = (XIRawEvent*)event.xcookie.data;
|
||||||
float values[4] = {};
|
float values[4] = {};
|
||||||
size_t value_idx = 0;
|
size_t value_idx = 0;
|
||||||
|
|
||||||
|
@ -359,10 +343,6 @@ void KeyboardMouse::UpdateInput()
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case XI_FocusOut:
|
|
||||||
// Clear keyboard state on FocusOut as we will not be receiving KeyRelease events.
|
|
||||||
m_state.keyboard.fill(0);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
XFreeEventData(m_display, &event.xcookie);
|
XFreeEventData(m_display, &event.xcookie);
|
||||||
|
@ -382,23 +362,13 @@ void KeyboardMouse::UpdateInput()
|
||||||
m_state.axis.z += delta_z;
|
m_state.axis.z += delta_z;
|
||||||
m_state.axis.z /= SCROLL_AXIS_DECAY;
|
m_state.axis.z /= SCROLL_AXIS_DECAY;
|
||||||
|
|
||||||
// Get the absolute position of the mouse pointer
|
|
||||||
const bool should_center_mouse =
|
const bool should_center_mouse =
|
||||||
g_controller_interface.IsMouseCenteringRequested() && Host_RendererHasFocus();
|
g_controller_interface.IsMouseCenteringRequested() && Host_RendererHasFocus();
|
||||||
if (mouse_moved || should_center_mouse)
|
if (update_mouse || should_center_mouse)
|
||||||
UpdateCursor(should_center_mouse);
|
UpdateCursor(should_center_mouse);
|
||||||
|
|
||||||
// KeyRelease and FocusOut events are sometimes not received.
|
if (update_keyboard)
|
||||||
// Cycling Alt-Tab and landing on the same window results in a stuck "Alt" key.
|
XQueryKeymap(m_display, m_state.keyboard.data());
|
||||||
// Unpressed keys are released here.
|
|
||||||
// Because we called XISetClientPointer in the constructor, XQueryKeymap
|
|
||||||
// will return the state of the associated keyboard, even if it isn't the
|
|
||||||
// first master keyboard. (XInput2 doesn't provide a function to query
|
|
||||||
// keyboard state.)
|
|
||||||
std::array<char, 32> keyboard;
|
|
||||||
XQueryKeymap(m_display, keyboard.data());
|
|
||||||
for (size_t i = 0; i != keyboard.size(); ++i)
|
|
||||||
m_state.keyboard[i] &= keyboard[i];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string KeyboardMouse::GetName() const
|
std::string KeyboardMouse::GetName() const
|
||||||
|
@ -441,8 +411,7 @@ ControlState KeyboardMouse::Key::GetState() const
|
||||||
return (m_keyboard[m_keycode / 8] & (1 << (m_keycode % 8))) != 0;
|
return (m_keyboard[m_keycode / 8] & (1 << (m_keycode % 8))) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyboardMouse::Button::Button(unsigned int index, unsigned int* buttons)
|
KeyboardMouse::Button::Button(unsigned int index, u32* buttons) : m_buttons(buttons), m_index(index)
|
||||||
: m_buttons(buttons), m_index(index)
|
|
||||||
{
|
{
|
||||||
name = fmt::format("Click {}", m_index + 1);
|
name = fmt::format("Click {}", m_index + 1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ extern "C" {
|
||||||
#include <X11/keysym.h>
|
#include <X11/keysym.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/Matrix.h"
|
#include "Common/Matrix.h"
|
||||||
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
#include "InputCommon/ControllerInterface/ControllerInterface.h"
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ private:
|
||||||
struct State
|
struct State
|
||||||
{
|
{
|
||||||
std::array<char, 32> keyboard;
|
std::array<char, 32> keyboard;
|
||||||
unsigned int buttons;
|
u32 buttons;
|
||||||
Common::Vec2 cursor;
|
Common::Vec2 cursor;
|
||||||
Common::Vec3 axis;
|
Common::Vec3 axis;
|
||||||
Common::Vec3 relative_mouse;
|
Common::Vec3 relative_mouse;
|
||||||
|
@ -52,11 +53,11 @@ private:
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
std::string GetName() const override { return name; }
|
std::string GetName() const override { return name; }
|
||||||
Button(unsigned int index, unsigned int* buttons);
|
Button(unsigned int index, u32* buttons);
|
||||||
ControlState GetState() const override;
|
ControlState GetState() const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const unsigned int* m_buttons;
|
const u32* m_buttons;
|
||||||
const unsigned int m_index;
|
const unsigned int m_index;
|
||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
|
@ -107,7 +108,6 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void SelectEventsForDevice(XIEventMask* mask, int deviceid);
|
|
||||||
void UpdateCursor(bool should_center_mouse);
|
void UpdateCursor(bool should_center_mouse);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
Loading…
Reference in New Issue