Skip to content

Instantly share code, notes, and snippets.

@jm4R
Last active January 13, 2022 09:54
Show Gist options
  • Save jm4R/185ae4caa47c2ec1bc75baf989c30741 to your computer and use it in GitHub Desktop.
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
#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