123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- // Copyright 2013 Max Eliaser
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include "InputCommon/ControllerInterface/Xlib/XInput2.h"
- #include <X11/XKBlib.h>
- #include <X11/extensions/XInput2.h>
- #include <cmath>
- #include <cstdlib>
- #include <cstring>
- #include <fmt/format.h>
- #include "Common/Logging/Log.h"
- #include "Common/StringUtil.h"
- #include "Core/Host.h"
- // 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 32 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
- // The scroll axis value should decay a lot faster than the mouse axes since
- // it should ideally register each click of the scroll wheel. Decreasing this
- // value makes it more likely that a scroll wheel input is registered, but less
- // likely to differentiate between different inputs, while increasing it will
- // 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
- {
- constexpr std::string_view SOURCE_NAME = "XInput2";
- class InputBackend final : public ciface::InputBackend
- {
- public:
- using ciface::InputBackend::InputBackend;
- void PopulateDevices() override;
- void HandleWindowChange() override;
- };
- std::unique_ptr<ciface::InputBackend> CreateInputBackend(ControllerInterface* controller_interface)
- {
- return std::make_unique<InputBackend>(controller_interface);
- }
- void InputBackend::HandleWindowChange()
- {
- GetControllerInterface().RemoveDevice(
- [](const auto* dev) { return dev->GetSource() == SOURCE_NAME; }, true);
- PopulateDevices();
- }
- // This function will add zero or more KeyboardMouse objects to devices.
- void InputBackend::PopulateDevices()
- {
- const WindowSystemInfo wsi = GetControllerInterface().GetWindowSystemInfo();
- if (wsi.type != WindowSystemType::X11)
- return;
- const auto hwnd = wsi.render_window;
- Display* dpy = XOpenDisplay(nullptr);
- // 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))
- {
- WARN_LOG_FMT(CONTROLLERINTERFACE, "XInput extension not available (XQueryExtension)");
- return;
- }
- 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
- XIDeviceInfo* all_masters;
- XIDeviceInfo* current_master;
- double scroll_increment = 1.0f;
- 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)
- {
- // We need to query the master for the scroll wheel's increment, since the increment used
- // varies depending on what input driver is being used. For example, xf86-libinput uses 120.0.
- for (int j = 0; j < current_master->num_classes; j++)
- {
- if (current_master->classes[j]->type == XIScrollClass)
- {
- XIScrollClassInfo* scroll_event =
- reinterpret_cast<XIScrollClassInfo*>(current_master->classes[j]);
- scroll_increment = scroll_event->increment;
- break;
- }
- }
- // Since current_master is a master pointer, its attachment must
- // be a master keyboard.
- GetControllerInterface().AddDevice(
- std::make_shared<KeyboardMouse>((Window)hwnd, xi_opcode, current_master->deviceid,
- current_master->attachment, scroll_increment));
- }
- }
- XCloseDisplay(dpy);
- XIFreeDeviceInfo(all_masters);
- }
- 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),
- scroll_increment(scroll_increment_)
- {
- // 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(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);
- name = std::string(pointer_device->name);
- XIFreeDeviceInfo(pointer_device);
- // Tell core X functions which keyboard is "the" keyboard for this
- // X connection.
- XISetClientPointer(m_display, None, pointer_deviceid);
- {
- unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {};
- 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);
- mask.deviceid = pointer_deviceid;
- XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1);
- }
- {
- unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {};
- XISetMask(mask_buf, XI_RawKeyPress);
- XISetMask(mask_buf, XI_RawKeyRelease);
- XIEventMask mask;
- mask.mask = mask_buf;
- mask.mask_len = sizeof(mask_buf);
- mask.deviceid = keyboard_deviceid;
- XISelectEvents(m_display, DefaultRootWindow(m_display), &mask, 1);
- }
- // Keyboard Keys
- int min_keycode, max_keycode;
- XDisplayKeycodes(m_display, &min_keycode, &max_keycode);
- for (int i = min_keycode; i <= max_keycode; ++i)
- {
- Key* const temp_key = new Key(m_display, i, m_state.keyboard.data());
- if (temp_key->m_keyname.length())
- AddInput(temp_key);
- else
- delete temp_key;
- }
- // Add combined left/right modifiers with consistent naming across platforms.
- AddCombinedInput("Alt", {"Alt_L", "Alt_R"});
- AddCombinedInput("Shift", {"Shift_L", "Shift_R"});
- AddCombinedInput("Ctrl", {"Control_L", "Control_R"});
- // Mouse Buttons
- for (int i = 0; i < 32; 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), (i & 2) ? &m_state.cursor.y : &m_state.cursor.x));
- // Mouse Axis, X-/+, Y-/+ and Z-/+
- AddInput(new Axis(0, false, &m_state.axis.x));
- AddInput(new Axis(0, true, &m_state.axis.x));
- AddInput(new Axis(1, false, &m_state.axis.y));
- AddInput(new Axis(1, true, &m_state.axis.y));
- AddInput(new Axis(2, false, &m_state.axis.z));
- AddInput(new Axis(2, true, &m_state.axis.z));
- // Relative Mouse, X-/+, Y-/+ and Z-/+
- AddInput(new RelativeMouse(0, false, &m_state.relative_mouse.x));
- AddInput(new RelativeMouse(0, true, &m_state.relative_mouse.x));
- AddInput(new RelativeMouse(1, false, &m_state.relative_mouse.y));
- AddInput(new RelativeMouse(1, true, &m_state.relative_mouse.y));
- AddInput(new RelativeMouse(2, false, &m_state.relative_mouse.z));
- AddInput(new RelativeMouse(2, true, &m_state.relative_mouse.z));
- }
- KeyboardMouse::~KeyboardMouse()
- {
- XCloseDisplay(m_display);
- }
- // Update the mouse cursor controls
- void KeyboardMouse::UpdateCursor(bool should_center_mouse)
- {
- double root_x, root_y, win_x, win_y;
- Window root, child;
- XWindowAttributes win_attribs;
- XGetWindowAttributes(m_display, m_window, &win_attribs);
- 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<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)
- {
- win_x = win_width / 2;
- win_y = win_height / 2;
- XIWarpPointer(m_display, pointer_deviceid, None, m_window, 0.0, 0.0, 0, 0, win_x, win_y);
- g_controller_interface.SetMouseCenteringRequested(false);
- }
- const auto window_scale = g_controller_interface.GetWindowInputScale();
- // the mouse position as a range from -1 to 1
- m_state.cursor.x = (win_x / win_width * 2 - 1) * window_scale.x;
- m_state.cursor.y = (win_y / win_height * 2 - 1) * window_scale.y;
- }
- Core::DeviceRemoval KeyboardMouse::UpdateInput()
- {
- XFlush(m_display);
- // for the axis controls
- float delta_x = 0.0f, delta_y = 0.0f, delta_z = 0.0f;
- double delta_delta;
- bool update_mouse = false, update_keyboard = false;
- // 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))
- {
- XNextEvent(m_display, &event);
- if (event.xcookie.type != GenericEvent)
- continue;
- if (event.xcookie.extension != xi_opcode)
- continue;
- if (!XGetEventData(m_display, &event.xcookie))
- continue;
- switch (event.xcookie.evtype)
- {
- case XI_RawButtonPress:
- case XI_RawButtonRelease:
- update_mouse = true;
- break;
- case XI_RawKeyPress:
- case XI_RawKeyRelease:
- update_keyboard = true;
- break;
- case XI_RawMotion:
- {
- update_mouse = true;
- XIRawEvent* raw_event = (XIRawEvent*)event.xcookie.data;
- float values[4] = {};
- size_t value_idx = 0;
- // We only care about the first 4 axes, which should always be available at minimum
- for (int i = 0; i < 4; ++i)
- {
- if (XIMaskIsSet(raw_event->valuators.mask, i))
- {
- values[i] = raw_event->raw_values[value_idx++];
- }
- }
- delta_delta = values[0];
- // test for inf and nan
- if (delta_delta == delta_delta && 1 + delta_delta != delta_delta)
- delta_x += delta_delta;
- delta_delta = values[1];
- // test for inf and nan
- if (delta_delta == delta_delta && 1 + delta_delta != delta_delta)
- delta_y += delta_delta;
- // Scroll wheel input gets scaled to be similar to the mouse axes
- delta_delta = values[3] * 8.0 / scroll_increment;
- // test for inf and nan
- if (delta_delta == delta_delta && 1 + delta_delta != delta_delta)
- delta_z += delta_delta;
- break;
- }
- }
- XFreeEventData(m_display, &event.xcookie);
- }
- m_state.relative_mouse.x = delta_x;
- m_state.relative_mouse.y = delta_y;
- m_state.relative_mouse.z = delta_z;
- // 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;
- m_state.axis.z += delta_z;
- m_state.axis.z /= SCROLL_AXIS_DECAY;
- const bool should_center_mouse = g_controller_interface.IsMouseCenteringRequested() &&
- (Host_RendererHasFocus() || Host_TASInputHasFocus());
- // When a TAS Input window has focus and "Enable Controller Input" is checked most types of
- // input should be read normally as if the render window had focus instead. The cursor is an
- // exception, as otherwise using the mouse to set any control in the TAS Input window will also
- // update the Wii IR value (or any other input controlled by the cursor).
- const bool should_update_mouse = update_mouse && !Host_TASInputHasFocus();
- if (should_update_mouse || should_center_mouse)
- UpdateCursor(should_center_mouse);
- if (update_keyboard)
- XQueryKeymap(m_display, m_state.keyboard.data());
- return Core::DeviceRemoval::Keep;
- }
- 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 std::string(SOURCE_NAME);
- }
- int KeyboardMouse::GetSortPriority() const
- {
- return DEFAULT_DEVICE_SORT_PRIORITY;
- }
- 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) == nullptr)
- 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;
- }
- KeyboardMouse::Button::Button(unsigned int index, u32* buttons) : m_buttons(buttons), m_index(index)
- {
- name = fmt::format("Click {}", m_index + 1);
- }
- ControlState KeyboardMouse::Button::GetState() const
- {
- return ((*m_buttons & (1 << m_index)) != 0);
- }
- KeyboardMouse::Cursor::Cursor(u8 index, bool positive, const float* cursor)
- : m_cursor(cursor), m_index(index), m_positive(positive)
- {
- name = fmt::format("Cursor {}{}", static_cast<char>('X' + m_index), (m_positive ? '+' : '-'));
- }
- ControlState KeyboardMouse::Cursor::GetState() const
- {
- return std::max(0.0f, *m_cursor / (m_positive ? 1.0f : -1.0f));
- }
- KeyboardMouse::Axis::Axis(u8 index, bool positive, const float* axis)
- : m_axis(axis), m_index(index), m_positive(positive)
- {
- name = fmt::format("Axis {}{}", static_cast<char>('X' + m_index), (m_positive ? '+' : '-'));
- }
- KeyboardMouse::RelativeMouse::RelativeMouse(u8 index, bool positive, const float* axis)
- : m_axis(axis), m_index(index), m_positive(positive)
- {
- name =
- fmt::format("RelativeMouse {}{}", static_cast<char>('X' + m_index), (m_positive ? '+' : '-'));
- }
- ControlState KeyboardMouse::Axis::GetState() const
- {
- return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY));
- }
- ControlState KeyboardMouse::RelativeMouse::GetState() const
- {
- return std::max(0.0f, *m_axis / (m_positive ? MOUSE_AXIS_SENSITIVITY : -MOUSE_AXIS_SENSITIVITY));
- }
- } // namespace ciface::XInput2
|