Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ list(FILTER COMMON_SOURCES EXCLUDE REGEX "platform/*")
# Platform-specific source files
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
file(GLOB PLATFORM_SOURCES "platform/linux/*_linux.cpp")
# Find GTK package for Linux
# Find packages for Linux
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
pkg_check_modules(X11 REQUIRED IMPORTED_TARGET x11)
pkg_check_modules(XI REQUIRED IMPORTED_TARGET xi)
elseif(APPLE)
file(GLOB PLATFORM_SOURCES "platform/macos/*_macos.mm")
elseif(WIN32)
Expand Down Expand Up @@ -58,5 +60,5 @@ if(APPLE)
target_link_libraries(libnativeapi PUBLIC "-framework Cocoa")
target_compile_options(libnativeapi PRIVATE "-x" "objective-c++")
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(libnativeapi PUBLIC PkgConfig::GTK)
target_link_libraries(libnativeapi PUBLIC PkgConfig::GTK PkgConfig::X11 PkgConfig::XI pthread)
endif ()
216 changes: 216 additions & 0 deletions src/platform/linux/keyboard_monitor_linux.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <atomic>
#include <chrono>

#include "../../keyboard_monitor.h"

// Import X11 headers after including the header to avoid conflicts
// We'll need to undefine None to avoid conflicts with our enum
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XInput2.h>
#include <X11/keysym.h>

// Handle the None conflict from X11
#ifdef None
#undef None
#endif

namespace nativeapi {

class KeyboardMonitor::Impl {
public:
Impl(KeyboardMonitor* monitor) : monitor_(monitor), display_(nullptr), monitoring_(false) {}

Display* display_;
std::atomic<bool> monitoring_;
std::thread monitoring_thread_;
KeyboardMonitor* monitor_;
int xi_opcode_;

void MonitoringLoop();
void InitializeXInput();
void CleanupXInput();
uint32_t GetModifierState();
};

KeyboardMonitor::KeyboardMonitor() : impl_(std::make_unique<Impl>(this)), event_handler_(nullptr) {}

KeyboardMonitor::~KeyboardMonitor() {
Stop();
}

void KeyboardMonitor::Impl::InitializeXInput() {
display_ = XOpenDisplay(nullptr);
if (!display_) {
std::cerr << "Failed to open X display" << std::endl;
return;
}

// Check for XInput extension
int event, error;
if (!XQueryExtension(display_, "XInputExtension", &xi_opcode_, &event, &error)) {
std::cerr << "XInput extension not available" << std::endl;
XCloseDisplay(display_);
display_ = nullptr;
return;
}

// Check XInput version
int major = 2, minor = 0;
if (XIQueryVersion(display_, &major, &minor) != Success) {
std::cerr << "XInput 2.0 not available" << std::endl;
XCloseDisplay(display_);
display_ = nullptr;
return;
}

// Select for keyboard events on root window
XIEventMask eventmask;
unsigned char mask[XIMaskLen(XI_LASTEVENT)] = {0};

eventmask.deviceid = XIAllMasterDevices;
eventmask.mask_len = sizeof(mask);
eventmask.mask = mask;

XISetMask(mask, XI_KeyPress);
XISetMask(mask, XI_KeyRelease);

Window root = DefaultRootWindow(display_);
if (XISelectEvents(display_, root, &eventmask, 1) != Success) {
std::cerr << "Failed to select XI events" << std::endl;
XCloseDisplay(display_);
display_ = nullptr;
return;
}
}

void KeyboardMonitor::Impl::CleanupXInput() {
if (display_) {
XCloseDisplay(display_);
display_ = nullptr;
}
}

uint32_t KeyboardMonitor::Impl::GetModifierState() {
uint32_t modifier_keys = static_cast<uint32_t>(ModifierKey::None);

if (!display_) return modifier_keys;

// Query current keyboard state
char keys[32];
XQueryKeymap(display_, keys);

// Check for common modifier keycodes
// These keycodes may vary by system, but are common defaults
int shift_keycode = XKeysymToKeycode(display_, XK_Shift_L);
int ctrl_keycode = XKeysymToKeycode(display_, XK_Control_L);
int alt_keycode = XKeysymToKeycode(display_, XK_Alt_L);
int meta_keycode = XKeysymToKeycode(display_, XK_Super_L);
int caps_keycode = XKeysymToKeycode(display_, XK_Caps_Lock);
int num_keycode = XKeysymToKeycode(display_, XK_Num_Lock);
int scroll_keycode = XKeysymToKeycode(display_, XK_Scroll_Lock);

// Check if keys are pressed
if (shift_keycode && (keys[shift_keycode / 8] & (1 << (shift_keycode % 8)))) {
modifier_keys |= static_cast<uint32_t>(ModifierKey::Shift);
}
if (ctrl_keycode && (keys[ctrl_keycode / 8] & (1 << (ctrl_keycode % 8)))) {
modifier_keys |= static_cast<uint32_t>(ModifierKey::Ctrl);
}
if (alt_keycode && (keys[alt_keycode / 8] & (1 << (alt_keycode % 8)))) {
modifier_keys |= static_cast<uint32_t>(ModifierKey::Alt);
}
if (meta_keycode && (keys[meta_keycode / 8] & (1 << (meta_keycode % 8)))) {
modifier_keys |= static_cast<uint32_t>(ModifierKey::Meta);
}
if (caps_keycode && (keys[caps_keycode / 8] & (1 << (caps_keycode % 8)))) {
modifier_keys |= static_cast<uint32_t>(ModifierKey::CapsLock);
}
if (num_keycode && (keys[num_keycode / 8] & (1 << (num_keycode % 8)))) {
modifier_keys |= static_cast<uint32_t>(ModifierKey::NumLock);
}
if (scroll_keycode && (keys[scroll_keycode / 8] & (1 << (scroll_keycode % 8)))) {
modifier_keys |= static_cast<uint32_t>(ModifierKey::ScrollLock);
}

return modifier_keys;
}

void KeyboardMonitor::Impl::MonitoringLoop() {
if (!display_) return;

while (monitoring_) {
// Check for pending events
while (XPending(display_) && monitoring_) {
XEvent event;
XNextEvent(display_, &event);

// Handle XI2 events
if (event.xcookie.type == GenericEvent && event.xcookie.extension == xi_opcode_) {
if (XGetEventData(display_, &event.xcookie)) {
XIDeviceEvent* xi_event = (XIDeviceEvent*)event.xcookie.data;

auto* eventHandler = monitor_->GetEventHandler();
if (eventHandler) {
if (xi_event->evtype == XI_KeyPress) {
eventHandler->OnKeyPressed(xi_event->detail);
eventHandler->OnModifierKeysChanged(GetModifierState());
} else if (xi_event->evtype == XI_KeyRelease) {
eventHandler->OnKeyReleased(xi_event->detail);
eventHandler->OnModifierKeysChanged(GetModifierState());
}
}

XFreeEventData(display_, &event.xcookie);
}
}
}

// Small sleep to prevent excessive CPU usage
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}

void KeyboardMonitor::Start() {
if (impl_->monitoring_) {
return; // Already started
}

impl_->InitializeXInput();
if (!impl_->display_) {
std::cerr << "Failed to initialize X11 display for keyboard monitoring" << std::endl;
return;
}

impl_->monitoring_ = true;
impl_->monitoring_thread_ = std::thread(&KeyboardMonitor::Impl::MonitoringLoop, impl_.get());

std::cout << "Keyboard monitor started successfully" << std::endl;
}

void KeyboardMonitor::Stop() {
if (!impl_->monitoring_) {
return; // Already stopped
}

impl_->monitoring_ = false;

// Wait for monitoring thread to finish
if (impl_->monitoring_thread_.joinable()) {
impl_->monitoring_thread_.join();
}

impl_->CleanupXInput();

std::cout << "Keyboard monitor stopped successfully" << std::endl;
}

bool KeyboardMonitor::IsMonitoring() const {
return impl_->monitoring_;
}

} // namespace nativeapi