Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #9160 from jordan-woyak/xinput2-stuck-keys
Linux/XInput2: Fix keys being stuck pressed on focus loss.
  • Loading branch information
leoetlino committed Oct 21, 2020
2 parents 43d11ca + bbb12a7 commit 97de366
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 33 deletions.
79 changes: 50 additions & 29 deletions Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.cpp
Expand Up @@ -97,32 +97,31 @@ void PopulateDevices(void* const hwnd)
// 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)
void KeyboardMouse::SelectEventsForDevice(XIEventMask* mask, int deviceid)
{
// Set the event mask for the master device.
mask->deviceid = deviceid;
XISelectEvents(m_display, window, mask, 1);
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.

XIDeviceInfo* all_slaves;
XIDeviceInfo* current_slave;
int num_slaves;

all_slaves = XIQueryDevice(m_display, XIAllDevices, &num_slaves);
XIDeviceInfo* const 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)
XIDeviceInfo* const slave = &all_slaves[i];
if ((slave->use != XISlavePointer && slave->use != XISlaveKeyboard) ||
slave->attachment != deviceid)
{
continue;
mask->deviceid = current_slave->deviceid;
XISelectEvents(m_display, window, mask, 1);
}
mask->deviceid = slave->deviceid;
XISelectEvents(m_display, DefaultRootWindow(m_display), mask, 1);
}

XIFreeDeviceInfo(all_slaves);
Expand All @@ -138,34 +137,44 @@ KeyboardMouse::KeyboardMouse(Window window, int opcode, int pointer, int keyboar
// "context."
m_display = XOpenDisplay(nullptr);

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);
// 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);

XIEventMask mask;
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_ButtonRelease);
XISetMask(mask_buf, XI_RawMotion);

XIEventMask mask;
mask.mask = mask_buf;
mask.mask_len = sizeof(mask_buf);

mask.mask_len = sizeof(mask_buf);
mask.mask = mask_buf;
memset(mask_buf, 0, sizeof(mask_buf));
SelectEventsForDevice(&mask, pointer_deviceid);
}

{
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_ButtonPress);
XISetMask(mask_buf, XI_ButtonRelease);
XISetMask(mask_buf, XI_RawMotion);
XISetMask(mask_buf, XI_KeyPress);
XISetMask(mask_buf, XI_KeyRelease);
XIEventMask mask;
mask.mask = mask_buf;
mask.mask_len = sizeof(mask_buf);

SelectEventsForDevice(DefaultRootWindow(m_display), &mask, pointer_deviceid);
SelectEventsForDevice(DefaultRootWindow(m_display), &mask, keyboard_deviceid);
SelectEventsForDevice(&mask, keyboard_deviceid);
}

// Keyboard Keys
int min_keycode, max_keycode;
XDisplayKeycodes(m_display, &min_keycode, &max_keycode);
for (int i = min_keycode; i <= max_keycode; ++i)
{
Key* temp_key = new Key(m_display, i, m_state.keyboard);
Key* const temp_key = new Key(m_display, i, m_state.keyboard.data());
if (temp_key->m_keyname.length())
AddInput(temp_key);
else
Expand Down Expand Up @@ -284,6 +293,10 @@ void KeyboardMouse::UpdateInput()
delta_y += delta_delta;
}
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);
Expand All @@ -300,6 +313,14 @@ void KeyboardMouse::UpdateInput()
// Get the absolute position of the mouse pointer
if (mouse_moved)
UpdateCursor();

// 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.
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
Expand Down
11 changes: 7 additions & 4 deletions Source/Core/InputCommon/ControllerInterface/Xlib/XInput2.h
Expand Up @@ -6,6 +6,8 @@

#pragma once

#include <array>

extern "C" {
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
Expand All @@ -24,7 +26,7 @@ class KeyboardMouse : public Core::Device
private:
struct State
{
char keyboard[32];
std::array<char, 32> keyboard;
unsigned int buttons;
Common::Vec2 cursor;
Common::Vec2 axis;
Expand Down Expand Up @@ -90,7 +92,7 @@ class KeyboardMouse : public Core::Device
};

private:
void SelectEventsForDevice(Window window, XIEventMask* mask, int deviceid);
void SelectEventsForDevice(XIEventMask* mask, int deviceid);
void UpdateCursor();

public:
Expand All @@ -106,8 +108,9 @@ class KeyboardMouse : public Core::Device
Window m_window;
Display* m_display;
State m_state{};
int xi_opcode;
const int pointer_deviceid, keyboard_deviceid;
const int xi_opcode;
const int pointer_deviceid;
const int keyboard_deviceid;
std::string name;
};
} // namespace ciface::XInput2

0 comments on commit 97de366

Please sign in to comment.