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