Skip to content

Commit 913e1ed

Browse files
Copilotlijy91
andauthored
[Linux] Implement KeyboardMonitor class using X11/XInput2 (#10)
* Initial plan * Implement Linux KeyboardMonitor using X11/XInput2 Co-authored-by: lijy91 <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: lijy91 <[email protected]>
1 parent aabca37 commit 913e1ed

File tree

2 files changed

+220
-2
lines changed

2 files changed

+220
-2
lines changed

src/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ list(FILTER COMMON_SOURCES EXCLUDE REGEX "platform/*")
2121
# Platform-specific source files
2222
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
2323
file(GLOB PLATFORM_SOURCES "platform/linux/*_linux.cpp")
24-
# Find GTK package for Linux
24+
# Find packages for Linux
2525
find_package(PkgConfig REQUIRED)
2626
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
27+
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11)
28+
pkg_check_modules(XI REQUIRED IMPORTED_TARGET xi)
2729
elseif(APPLE)
2830
file(GLOB PLATFORM_SOURCES "platform/macos/*_macos.mm")
2931
elseif(WIN32)
@@ -58,5 +60,5 @@ if(APPLE)
5860
target_link_libraries(libnativeapi PUBLIC "-framework Cocoa")
5961
target_compile_options(libnativeapi PRIVATE "-x" "objective-c++")
6062
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
61-
target_link_libraries(libnativeapi PUBLIC PkgConfig::GTK)
63+
target_link_libraries(libnativeapi PUBLIC PkgConfig::GTK PkgConfig::X11 PkgConfig::XI pthread)
6264
endif ()
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
#include <iostream>
2+
#include <string>
3+
#include <memory>
4+
#include <thread>
5+
#include <atomic>
6+
#include <chrono>
7+
8+
#include "../../keyboard_monitor.h"
9+
10+
// Import X11 headers after including the header to avoid conflicts
11+
// We'll need to undefine None to avoid conflicts with our enum
12+
#include <X11/Xlib.h>
13+
#include <X11/Xutil.h>
14+
#include <X11/extensions/XInput2.h>
15+
#include <X11/keysym.h>
16+
17+
// Handle the None conflict from X11
18+
#ifdef None
19+
#undef None
20+
#endif
21+
22+
namespace nativeapi {
23+
24+
class KeyboardMonitor::Impl {
25+
public:
26+
Impl(KeyboardMonitor* monitor) : monitor_(monitor), display_(nullptr), monitoring_(false) {}
27+
28+
Display* display_;
29+
std::atomic<bool> monitoring_;
30+
std::thread monitoring_thread_;
31+
KeyboardMonitor* monitor_;
32+
int xi_opcode_;
33+
34+
void MonitoringLoop();
35+
void InitializeXInput();
36+
void CleanupXInput();
37+
uint32_t GetModifierState();
38+
};
39+
40+
KeyboardMonitor::KeyboardMonitor() : impl_(std::make_unique<Impl>(this)), event_handler_(nullptr) {}
41+
42+
KeyboardMonitor::~KeyboardMonitor() {
43+
Stop();
44+
}
45+
46+
void KeyboardMonitor::Impl::InitializeXInput() {
47+
display_ = XOpenDisplay(nullptr);
48+
if (!display_) {
49+
std::cerr << "Failed to open X display" << std::endl;
50+
return;
51+
}
52+
53+
// Check for XInput extension
54+
int event, error;
55+
if (!XQueryExtension(display_, "XInputExtension", &xi_opcode_, &event, &error)) {
56+
std::cerr << "XInput extension not available" << std::endl;
57+
XCloseDisplay(display_);
58+
display_ = nullptr;
59+
return;
60+
}
61+
62+
// Check XInput version
63+
int major = 2, minor = 0;
64+
if (XIQueryVersion(display_, &major, &minor) != Success) {
65+
std::cerr << "XInput 2.0 not available" << std::endl;
66+
XCloseDisplay(display_);
67+
display_ = nullptr;
68+
return;
69+
}
70+
71+
// Select for keyboard events on root window
72+
XIEventMask eventmask;
73+
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0};
74+
75+
eventmask.deviceid = XIAllMasterDevices;
76+
eventmask.mask_len = sizeof(mask);
77+
eventmask.mask = mask;
78+
79+
XISetMask(mask, XI_KeyPress);
80+
XISetMask(mask, XI_KeyRelease);
81+
82+
Window root = DefaultRootWindow(display_);
83+
if (XISelectEvents(display_, root, &eventmask, 1) != Success) {
84+
std::cerr << "Failed to select XI events" << std::endl;
85+
XCloseDisplay(display_);
86+
display_ = nullptr;
87+
return;
88+
}
89+
}
90+
91+
void KeyboardMonitor::Impl::CleanupXInput() {
92+
if (display_) {
93+
XCloseDisplay(display_);
94+
display_ = nullptr;
95+
}
96+
}
97+
98+
uint32_t KeyboardMonitor::Impl::GetModifierState() {
99+
uint32_t modifier_keys = static_cast<uint32_t>(ModifierKey::None);
100+
101+
if (!display_) return modifier_keys;
102+
103+
// Query current keyboard state
104+
char keys[32];
105+
XQueryKeymap(display_, keys);
106+
107+
// Check for common modifier keycodes
108+
// These keycodes may vary by system, but are common defaults
109+
int shift_keycode = XKeysymToKeycode(display_, XK_Shift_L);
110+
int ctrl_keycode = XKeysymToKeycode(display_, XK_Control_L);
111+
int alt_keycode = XKeysymToKeycode(display_, XK_Alt_L);
112+
int meta_keycode = XKeysymToKeycode(display_, XK_Super_L);
113+
int caps_keycode = XKeysymToKeycode(display_, XK_Caps_Lock);
114+
int num_keycode = XKeysymToKeycode(display_, XK_Num_Lock);
115+
int scroll_keycode = XKeysymToKeycode(display_, XK_Scroll_Lock);
116+
117+
// Check if keys are pressed
118+
if (shift_keycode && (keys[shift_keycode / 8] & (1 << (shift_keycode % 8)))) {
119+
modifier_keys |= static_cast<uint32_t>(ModifierKey::Shift);
120+
}
121+
if (ctrl_keycode && (keys[ctrl_keycode / 8] & (1 << (ctrl_keycode % 8)))) {
122+
modifier_keys |= static_cast<uint32_t>(ModifierKey::Ctrl);
123+
}
124+
if (alt_keycode && (keys[alt_keycode / 8] & (1 << (alt_keycode % 8)))) {
125+
modifier_keys |= static_cast<uint32_t>(ModifierKey::Alt);
126+
}
127+
if (meta_keycode && (keys[meta_keycode / 8] & (1 << (meta_keycode % 8)))) {
128+
modifier_keys |= static_cast<uint32_t>(ModifierKey::Meta);
129+
}
130+
if (caps_keycode && (keys[caps_keycode / 8] & (1 << (caps_keycode % 8)))) {
131+
modifier_keys |= static_cast<uint32_t>(ModifierKey::CapsLock);
132+
}
133+
if (num_keycode && (keys[num_keycode / 8] & (1 << (num_keycode % 8)))) {
134+
modifier_keys |= static_cast<uint32_t>(ModifierKey::NumLock);
135+
}
136+
if (scroll_keycode && (keys[scroll_keycode / 8] & (1 << (scroll_keycode % 8)))) {
137+
modifier_keys |= static_cast<uint32_t>(ModifierKey::ScrollLock);
138+
}
139+
140+
return modifier_keys;
141+
}
142+
143+
void KeyboardMonitor::Impl::MonitoringLoop() {
144+
if (!display_) return;
145+
146+
while (monitoring_) {
147+
// Check for pending events
148+
while (XPending(display_) && monitoring_) {
149+
XEvent event;
150+
XNextEvent(display_, &event);
151+
152+
// Handle XI2 events
153+
if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi_opcode_) {
154+
if (XGetEventData(display_, &event.xcookie)) {
155+
XIDeviceEvent* xi_event = (XIDeviceEvent*)event.xcookie.data;
156+
157+
auto* eventHandler = monitor_->GetEventHandler();
158+
if (eventHandler) {
159+
if (xi_event->evtype == XI_KeyPress) {
160+
eventHandler->OnKeyPressed(xi_event->detail);
161+
eventHandler->OnModifierKeysChanged(GetModifierState());
162+
} else if (xi_event->evtype == XI_KeyRelease) {
163+
eventHandler->OnKeyReleased(xi_event->detail);
164+
eventHandler->OnModifierKeysChanged(GetModifierState());
165+
}
166+
}
167+
168+
XFreeEventData(display_, &event.xcookie);
169+
}
170+
}
171+
}
172+
173+
// Small sleep to prevent excessive CPU usage
174+
std::this_thread::sleep_for(std::chrono::milliseconds(1));
175+
}
176+
}
177+
178+
void KeyboardMonitor::Start() {
179+
if (impl_->monitoring_) {
180+
return; // Already started
181+
}
182+
183+
impl_->InitializeXInput();
184+
if (!impl_->display_) {
185+
std::cerr << "Failed to initialize X11 display for keyboard monitoring" << std::endl;
186+
return;
187+
}
188+
189+
impl_->monitoring_ = true;
190+
impl_->monitoring_thread_ = std::thread(&KeyboardMonitor::Impl::MonitoringLoop, impl_.get());
191+
192+
std::cout << "Keyboard monitor started successfully" << std::endl;
193+
}
194+
195+
void KeyboardMonitor::Stop() {
196+
if (!impl_->monitoring_) {
197+
return; // Already stopped
198+
}
199+
200+
impl_->monitoring_ = false;
201+
202+
// Wait for monitoring thread to finish
203+
if (impl_->monitoring_thread_.joinable()) {
204+
impl_->monitoring_thread_.join();
205+
}
206+
207+
impl_->CleanupXInput();
208+
209+
std::cout << "Keyboard monitor stopped successfully" << std::endl;
210+
}
211+
212+
bool KeyboardMonitor::IsMonitoring() const {
213+
return impl_->monitoring_;
214+
}
215+
216+
} // namespace nativeapi

0 commit comments

Comments
 (0)