Linux/XInput2: Fix keys being stuck pressed on focus loss.

This commit is contained in:
Jordan Woyak 2020-10-19 10:21:28 -05:00
parent 696f08ede3
commit bbb12a7560
2 changed files with 57 additions and 33 deletions

View file

@ -97,11 +97,11 @@ 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,
@ -109,20 +109,19 @@ void KeyboardMouse::SelectEventsForDevice(Window window, XIEventMask* mask, int
// 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);
@ -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);
mask.mask_len = sizeof(mask_buf);
mask.mask = mask_buf;
memset(mask_buf, 0, sizeof(mask_buf));
XIEventMask mask;
mask.mask = mask_buf;
mask.mask_len = 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(&mask, pointer_deviceid);
}
SelectEventsForDevice(DefaultRootWindow(m_display), &mask, pointer_deviceid);
SelectEventsForDevice(DefaultRootWindow(m_display), &mask, keyboard_deviceid);
{
unsigned char mask_buf[(XI_LASTEVENT + 7) / 8] = {};
XISetMask(mask_buf, XI_KeyPress);
XISetMask(mask_buf, XI_KeyRelease);
XISetMask(mask_buf, XI_FocusOut);
XIEventMask mask;
mask.mask = mask_buf;
mask.mask_len = sizeof(mask_buf);
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
@ -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);
@ -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

View file

@ -6,6 +6,8 @@
#pragma once
#include <array>
extern "C" {
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
@ -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;
@ -90,7 +92,7 @@ private:
};
private:
void SelectEventsForDevice(Window window, XIEventMask* mask, int deviceid);
void SelectEventsForDevice(XIEventMask* mask, int deviceid);
void UpdateCursor();
public:
@ -106,8 +108,9 @@ private:
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