Last active
January 13, 2022 09:54
-
-
Save jm4R/185ae4caa47c2ec1bc75baf989c30741 to your computer and use it in GitHub Desktop.
Portable (Linux/X11 and Windows) way to monitor keyboard shortcuts + integration with asio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <boost/predef/os.h> | |
#include <boost/asio/steady_timer.hpp> | |
#include <chrono> | |
#include <initializer_list> | |
#if BOOST_OS_LINUX | |
#include <X11/Xlib.h> | |
#include <X11/keysym.h> | |
enum class key : KeySym | |
{ | |
backspace = XK_BackSpace, | |
tab = XK_Tab, | |
clear = XK_Clear, | |
return_ = XK_Return, | |
shift = XK_Shift_L, | |
lshift = XK_Shift_L, | |
rshift = XK_Shift_R, | |
ctrl = XK_Control_L, | |
lctrl = XK_Control_L, | |
rctrl = XK_Control_R, | |
lalt = XK_Alt_L, | |
ralt = XK_Alt_R, | |
pause = XK_Pause, | |
capital = XK_Caps_Lock, | |
escape = XK_Escape, | |
space = XK_space, | |
prior = XK_Prior, | |
next = XK_Next, | |
end = XK_End, | |
home = XK_Home, | |
left = XK_Left, | |
up = XK_Up, | |
right = XK_Right, | |
down = XK_Down, | |
select = XK_Select, | |
print = XK_Print, | |
execute = XK_Execute, | |
insert = XK_Insert, | |
delete_ = XK_Delete, | |
help = XK_Help, | |
f1 = XK_F1, | |
f2 = XK_F2, | |
f3 = XK_F3, | |
f4 = XK_F4, | |
f5 = XK_F5, | |
f6 = XK_F6, | |
f7 = XK_F7, | |
f8 = XK_F8, | |
f9 = XK_F9, | |
f10 = XK_F10, | |
f11 = XK_F11, | |
f12 = XK_F12, | |
f13 = XK_F13, | |
f14 = XK_F14, | |
f15 = XK_F15, | |
f16 = XK_F16, | |
f17 = XK_F17, | |
f18 = XK_F18, | |
f19 = XK_F19, | |
f20 = XK_F20, | |
f21 = XK_F21, | |
f22 = XK_F22, | |
f23 = XK_F23, | |
f24 = XK_F24, | |
}; | |
inline bool keys_pressed(std::initializer_list<key> keys) | |
{ | |
struct display_raii | |
{ | |
Display* display{XOpenDisplay(nullptr)}; | |
~display_raii() { XCloseDisplay(display); } | |
}; | |
auto byte = [](auto c) { return c / 8; }; | |
auto bit = [](auto c) { return c % 8; }; | |
static display_raii display_; | |
const auto display = display_.display; | |
if (!display) | |
return false; | |
constexpr auto keys_size = 32; | |
char required_keys[keys_size] = {}; | |
for (const auto k : keys) | |
{ | |
const auto code = XKeysymToKeycode(display, KeySym(k)); | |
required_keys[byte(code)] |= 1 << bit(code); | |
} | |
char keys_state[keys_size]; | |
XQueryKeymap(display, keys_state); | |
for (int i = 0; i < keys_size; ++i) | |
{ | |
if ((keys_state[i] & required_keys[i]) != required_keys[i]) | |
return false; | |
} | |
return true; | |
} | |
#else | |
#include <windows.h> | |
enum class key : int | |
{ | |
backspace = VK_BACK, | |
tab = VK_TAB, | |
clear = VK_CLEAR, | |
return_ = VK_RETURN, | |
shift = VK_SHIFT, | |
lshift = VK_LSHIFT, | |
rshift = VK_RSHIFT, | |
ctrl = VK_CONTROL, | |
lctrl = VK_LCONTROL, | |
rctrl = VK_RCONTROL, | |
lalt = VK_LMENU, | |
ralt = VK_RMENU, | |
pause = VK_PAUSE, | |
capital = VK_CAPITAL, | |
escape = VK_ESCAPE, | |
space = VK_SPACE, | |
prior = VK_PRIOR, | |
next = VK_NEXT, | |
end = VK_END, | |
home = VK_HOME, | |
left = VK_LEFT, | |
up = VK_UP, | |
right = VK_RIGHT, | |
down = VK_DOWN, | |
select = VK_SELECT, | |
print = VK_PRINT, | |
execute = VK_EXECUTE, | |
insert = VK_INSERT, | |
delete_ = VK_DELETE, | |
help = VK_HELP, | |
f1 = VK_F1, | |
f2 = VK_F2, | |
f3 = VK_F3, | |
f4 = VK_F4, | |
f5 = VK_F5, | |
f6 = VK_F6, | |
f7 = VK_F7, | |
f8 = VK_F8, | |
f9 = VK_F9, | |
f10 = VK_F10, | |
f11 = VK_F11, | |
f12 = VK_F12, | |
f13 = VK_F13, | |
f14 = VK_F14, | |
f15 = VK_F15, | |
f16 = VK_F16, | |
f17 = VK_F17, | |
f18 = VK_F18, | |
f19 = VK_F19, | |
f20 = VK_F20, | |
f21 = VK_F21, | |
f22 = VK_F22, | |
f23 = VK_F23, | |
f24 = VK_F24, | |
}; | |
inline bool keys_pressed(std::initializer_list<key> keys) | |
{ | |
for (const auto k : keys) | |
{ | |
if (GetAsyncKeyState(int(k)) == 0) | |
return false; | |
} | |
return true; | |
} | |
#endif | |
class keys_monitor | |
{ | |
public: | |
template <typename ExecutionContext> | |
explicit keys_monitor(ExecutionContext& exe) : timer_{exe} | |
{ | |
} | |
~keys_monitor() { cancel(); } | |
keys_monitor(const keys_monitor&) = delete; | |
keys_monitor& operator=(const keys_monitor&) = delete; | |
keys_monitor(keys_monitor&&) = default; | |
keys_monitor& operator=(keys_monitor&&) = default; | |
template <typename Handler> | |
void async_monitor(std::initializer_list<key> keys, Handler&& h) | |
{ | |
canceled_ = false; | |
async_monitor_impl(keys, std::forward<Handler>(h), false); | |
} | |
void cancel() | |
{ | |
canceled_ = true; | |
timer_.cancel(); | |
} | |
private: | |
template <typename Handler> | |
void async_monitor_impl(std::initializer_list<key> keys, Handler&& h, | |
bool last) | |
{ | |
timer_.async_wait( | |
[last, keys = keys, h = std::forward<Handler>(h), this](auto ec) { | |
if (ec == boost::asio::error::operation_aborted) | |
{ | |
if (!canceled_) | |
{ | |
// reshedule because other handler is being installed | |
async_monitor_impl(keys, std::move(h), last); | |
} | |
return; | |
} | |
const bool now = keys_pressed(keys); | |
if (now && !last) | |
h(); | |
timer_.expires_after(interval); | |
async_monitor_impl(keys, std::move(h), now); | |
}); | |
} | |
private: | |
constexpr static auto interval = std::chrono::milliseconds{20}; | |
bool canceled_{}; | |
boost::asio::steady_timer timer_; | |
}; | |
// usage: | |
// constexpr static auto shortcut = {key::lctrl, key{'c'}}; | |
// keys_monitor keys{io_ctx}; | |
// keys.async_monitor(shortcut, []{ | |
// copy(); | |
// }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment