diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp index 07d8e4bfa5..d90139fe03 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp @@ -5,12 +5,14 @@ #include +#include #include #include #include #include +#include "Common/Logging/Log.h" #include "Common/StringUtil.h" #include "Core/Host.h" @@ -54,6 +56,14 @@ // more cleanly separate each scroll wheel click, but risks dropping some inputs #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 { // 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 if (!XQueryExtension(dpy, "XInputExtension", &xi_opcode, &event, &error)) + { + WARN_LOG_FMT(CONTROLLERINTERFACE, "XInput extension not available (XQueryExtension)"); return; + } - // verify that the XInput extension is at at least version 2.0 - int major = 2, minor = 0; - - if (XIQueryVersion(dpy, &major, &minor) != Success) + int major = XINPUT_MAJOR, minor = XINPUT_MINOR; + if (XIQueryVersion(dpy, &major, &minor) != Success || major < XINPUT_MAJOR || + (major == XINPUT_MAJOR && minor < XINPUT_MINOR)) + { + WARN_LOG_FMT(CONTROLLERINTERFACE, "XInput extension not available (XIQueryVersion)"); return; + } // register all master devices with Dolphin @@ -115,39 +130,6 @@ void PopulateDevices(void* const hwnd) 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, double scroll_increment_) : 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." m_display = XOpenDisplay(nullptr); + int major = XINPUT_MAJOR, minor = XINPUT_MINOR; + XIQueryVersion(m_display, &major, &minor); + // should always be 1 int 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] = {}; - XISetMask(mask_buf, XI_ButtonPress); - XISetMask(mask_buf, XI_ButtonRelease); + XISetMask(mask_buf, XI_RawButtonPress); + XISetMask(mask_buf, XI_RawButtonRelease); XISetMask(mask_buf, XI_RawMotion); XIEventMask mask; mask.mask = 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] = {}; - XISetMask(mask_buf, XI_KeyPress); - XISetMask(mask_buf, XI_KeyRelease); - XISetMask(mask_buf, XI_FocusOut); + XISetMask(mask_buf, XI_RawKeyPress); + XISetMask(mask_buf, XI_RawKeyRelease); XIEventMask mask; mask.mask = mask_buf; mask.mask_len = sizeof(mask_buf); - - SelectEventsForDevice(&mask, keyboard_deviceid); + mask.deviceid = keyboard_deviceid; + XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1); } // 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_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(button_state.mask_len, sizeof(m_state.buttons))); + m_state.buttons = buttons_zero_indexed >> 1; + + free(button_state.mask); + } + if (should_center_mouse) { win_x = win_width / 2; @@ -263,19 +267,6 @@ void KeyboardMouse::UpdateCursor(bool should_center_mouse) 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(); @@ -291,10 +282,10 @@ void KeyboardMouse::UpdateInput() // for the axis controls float delta_x = 0.0f, delta_y = 0.0f, delta_z = 0.0f; double delta_delta; - bool mouse_moved = false; + bool update_mouse = false, update_keyboard = false; - // Iterate through the event queue - update the axis controls, mouse - // button controls, and keyboard controls. + // Iterate through the event queue, processing raw pointer motion events and + // noting whether the button or key state has changed. XEvent event; while (XPending(m_display)) { @@ -307,28 +298,21 @@ void KeyboardMouse::UpdateInput() 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); + case XI_RawButtonPress: + case XI_RawButtonRelease: + update_mouse = true; 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)); + case XI_RawKeyPress: + case XI_RawKeyRelease: + update_keyboard = true; break; case XI_RawMotion: { - mouse_moved = true; + update_mouse = true; + XIRawEvent* raw_event = (XIRawEvent*)event.xcookie.data; float values[4] = {}; size_t value_idx = 0; @@ -359,10 +343,6 @@ void KeyboardMouse::UpdateInput() 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); @@ -382,23 +362,13 @@ void KeyboardMouse::UpdateInput() m_state.axis.z += delta_z; m_state.axis.z /= SCROLL_AXIS_DECAY; - // Get the absolute position of the mouse pointer const bool should_center_mouse = g_controller_interface.IsMouseCenteringRequested() && Host_RendererHasFocus(); - if (mouse_moved || should_center_mouse) + if (update_mouse || should_center_mouse) UpdateCursor(should_center_mouse); - // KeyRelease and FocusOut events are sometimes not received. - // Cycling Alt-Tab and landing on the same window results in a stuck "Alt" key. - // 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 keyboard; - XQueryKeymap(m_display, keyboard.data()); - for (size_t i = 0; i != keyboard.size(); ++i) - m_state.keyboard[i] &= keyboard[i]; + if (update_keyboard) + XQueryKeymap(m_display, m_state.keyboard.data()); } 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; } -KeyboardMouse::Button::Button(unsigned int index, unsigned int* buttons) - : m_buttons(buttons), m_index(index) +KeyboardMouse::Button::Button(unsigned int index, u32* buttons) : m_buttons(buttons), m_index(index) { name = fmt::format("Click {}", m_index + 1); } diff --git a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h index ff2c787b1d..6a4f8436f1 100644 --- a/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h +++ b/Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h @@ -13,6 +13,7 @@ extern "C" { #include } +#include "Common/CommonTypes.h" #include "Common/Matrix.h" #include "InputCommon/ControllerInterface/ControllerInterface.h" @@ -26,7 +27,7 @@ private: struct State { std::array keyboard; - unsigned int buttons; + u32 buttons; Common::Vec2 cursor; Common::Vec3 axis; Common::Vec3 relative_mouse; @@ -52,11 +53,11 @@ private: { public: std::string GetName() const override { return name; } - Button(unsigned int index, unsigned int* buttons); + Button(unsigned int index, u32* buttons); ControlState GetState() const override; private: - const unsigned int* m_buttons; + const u32* m_buttons; const unsigned int m_index; std::string name; }; @@ -107,7 +108,6 @@ private: }; private: - void SelectEventsForDevice(XIEventMask* mask, int deviceid); void UpdateCursor(bool should_center_mouse); public: