187 lines
5.8 KiB
C++
187 lines
5.8 KiB
C++
#include <X11/Xlib.h>
|
|
#include <X11/keysym.h>
|
|
#include <functional>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include "KeyboardHook.h"
|
|
|
|
#define ClientMessageMask (1L << 24)
|
|
#define SelectMask (KeyPressMask | KeyReleaseMask | FocusChangeMask | ClientMessageMask)
|
|
|
|
using namespace std;
|
|
using KeyType = KeyboardHook::KeyType::value;
|
|
|
|
inline KeyType key_type(XID key_id) {
|
|
switch (key_id) {
|
|
case XK_Shift_L:
|
|
case XK_Shift_R:
|
|
return KeyType::KEY_SHIFT;
|
|
case XK_Alt_L:
|
|
case XK_Alt_R:
|
|
return KeyType::KEY_ALT;
|
|
case XK_Control_L:
|
|
case XK_Control_R:
|
|
return KeyType::KEY_CTRL;
|
|
case XK_Menu:
|
|
return KeyType::KEY_WIN;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/*
|
|
if(key_id >= XK_space && key_id <= XK_asciitilde) return KeyType::KEY_NORMAL; //Normal ASCI
|
|
if(key_id >= XK_KP_Space && key_id <= XK_KP_Divide) return KeyType::KEY_NORMAL; // Keypad functions, keypad numbers cleverly chosen to map to ASCII
|
|
if(key_id >= XK_Kanji && key_id <= XK_Mae_Koho) return KeyType::KEY_NORMAL; // Japanese keyboard support
|
|
*/
|
|
return KeyType::KEY_NORMAL;
|
|
}
|
|
|
|
KeyboardHook::KeyboardHook() {
|
|
this->map_special[KeyType::KEY_NORMAL] = XK_VoidSymbol;
|
|
}
|
|
KeyboardHook::~KeyboardHook() {
|
|
if(this->active)
|
|
this->detach();
|
|
}
|
|
|
|
bool KeyboardHook::attach() {
|
|
assert(!this->active);
|
|
this->active = true;
|
|
|
|
this->display = XOpenDisplay(nullptr);
|
|
if(!this->display) return false;
|
|
|
|
{
|
|
this->window_root = XDefaultRootWindow(this->display);
|
|
|
|
XGetInputFocus(display, &this->window_focused, &this->focus_revert);
|
|
if(this->window_focused == PointerRoot)
|
|
this->window_focused = this->window_root;
|
|
XSelectInput(this->display, this->window_focused, SelectMask);
|
|
//XGrabKeyboard(this->display, this->window_focused, false, GrabModeAsync, GrabModeAsync, CurrentTime);
|
|
}
|
|
|
|
XSetErrorHandler([](Display* dp, XErrorEvent* event) {
|
|
if(event->type == BadWindow)
|
|
return 0;
|
|
/*
|
|
X Error of failed request: BadWindow (invalid Window parameter)
|
|
Major opcode of failed request: 2 (X_ChangeWindowAttributes)
|
|
Resource id in failed request: 0x0
|
|
Serial number of failed request: 32
|
|
Current serial number in output stream: 32
|
|
*/
|
|
cerr << "Caught X11 error: " << event->type << endl;
|
|
return 0;
|
|
});
|
|
|
|
this->poll_thread = thread(bind(&KeyboardHook::poll_events, this));
|
|
return true;
|
|
}
|
|
|
|
void KeyboardHook::detach() {
|
|
assert(this->active);
|
|
this->active = false;
|
|
|
|
{
|
|
// push a dummy event into the queue so that the event loop has a chance to stop
|
|
XClientMessageEvent dummy_event;
|
|
memset((void*) &dummy_event, 0, sizeof(XClientMessageEvent));
|
|
dummy_event.type = ClientMessage;
|
|
dummy_event.window = this->window_focused;
|
|
dummy_event.format = 32;
|
|
dummy_event.data.l[0] = this->end_id = rand();
|
|
|
|
XSendEvent(this->display, this->window_focused, 0, ClientMessageMask, (XEvent*) &dummy_event);
|
|
XFlush(this->display);
|
|
}
|
|
|
|
if(this->poll_thread.joinable())
|
|
this->poll_thread.join();
|
|
|
|
XCloseDisplay(this->display);
|
|
this->display = nullptr;
|
|
}
|
|
|
|
void KeyboardHook::poll_events() {
|
|
XEvent event;
|
|
bool has_event = false;
|
|
|
|
while(this->active) {
|
|
if(!has_event) {
|
|
auto result = XNextEvent(this->display, &event);
|
|
if(result != 0) {
|
|
//TODO throw error
|
|
cout << "XNextEvent returned invalid code: " << result << endl;
|
|
break;
|
|
}
|
|
}
|
|
has_event = false;
|
|
|
|
if(event.type == FocusOut) {
|
|
cout << "Old windows lost focus. Attaching to new one" << endl;
|
|
int result = 0;
|
|
if(this->window_root != this->window_focused) {
|
|
result = XSelectInput(display, this->window_focused, 0); //Release events
|
|
}
|
|
result = XGetInputFocus(display, &this->window_focused, &this->focus_revert);
|
|
if(this->window_focused == PointerRoot)
|
|
this->window_focused = this->window_root;
|
|
result = XSelectInput(display, this->window_focused, SelectMask);
|
|
continue;
|
|
} else if(event.type == FocusIn) {
|
|
|
|
} else if(event.type == KeyPress) {
|
|
auto& old_flag = this->map_key[event.xkey.keycode];
|
|
bool typed = old_flag;
|
|
old_flag = true;
|
|
|
|
auto key_sym = XLookupKeysym(&event.xkey, 0);
|
|
auto type = key_type(key_sym);
|
|
|
|
if(type != KeyType::KEY_NORMAL)
|
|
this->map_special[type] = true;
|
|
else
|
|
this->map_special[type] = key_sym;
|
|
|
|
if(!typed)
|
|
this->trigger_key_event(KeyEvent::PRESS, type == KeyType::KEY_NORMAL ? XKeysymToString(key_sym) : XKeysymToString(this->map_special[KeyType::KEY_NORMAL]));
|
|
this->trigger_key_event(KeyEvent::TYPE, type == KeyType::KEY_NORMAL ? XKeysymToString(key_sym) : XKeysymToString(this->map_special[KeyType::KEY_NORMAL]));
|
|
} else if(event.type == KeyRelease) {
|
|
auto key_sym = XLookupKeysym(&event.xkey, 0);
|
|
if(XEventsQueued(this->display, 0) > 0) {
|
|
auto result = XNextEvent(this->display, &event);
|
|
if(result != 0) {
|
|
cerr << "XNextEvent(...) => " << result << endl;
|
|
break;
|
|
}
|
|
has_event = true;
|
|
if(event.type == KeyPress && XLookupKeysym(&event.xkey, 0) == key_sym) //Key retriggered
|
|
continue; //Handle repress
|
|
}
|
|
|
|
auto& old_flag = this->map_key[event.xkey.keycode];
|
|
if(!old_flag) continue; //Nothing to update
|
|
old_flag = false;
|
|
|
|
auto type = key_type(key_sym);
|
|
if(type != KeyType::KEY_NORMAL)
|
|
this->map_special[type] = false;
|
|
else if(this->map_special[KeyType::KEY_NORMAL] == key_sym)
|
|
this->map_special[KeyType::KEY_NORMAL] = XK_VoidSymbol;
|
|
|
|
this->trigger_key_event(KeyEvent::RELEASE, type == KeyType::KEY_NORMAL ? XKeysymToString(key_sym) : XKeysymToString(this->map_special[KeyType::KEY_NORMAL]));
|
|
} else if(event.type == ClientMessage) {
|
|
cout << "Got client message. End ID: " << event.xclient.data.l[0] << " <=> " << this->end_id << endl;
|
|
if(event.xclient.data.l[0] == this->end_id) break;
|
|
} else if(event.type == ButtonPress) {
|
|
cout << "Got button" << endl;
|
|
} else if(event.type == ButtonRelease) {
|
|
cout << "Got button" << endl;
|
|
} else {
|
|
cerr << "Got unknown event of type " << event.type << endl;
|
|
}
|
|
}
|
|
} |