Last active
May 1, 2016 13:53
-
-
Save cjxgm/8a14508664118a0bc28d2dd820758e1f to your computer and use it in GitHub Desktop.
Detect simulteneous keydown/up events in tty in Linux, using evdev directly
This file contains hidden or 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
#include <fcntl.h> | |
#include <unistd.h> | |
#include <err.h> | |
#include <iostream> | |
#include <iomanip> | |
#include <string> | |
#include <vector> | |
#include "scope-exit.hh" | |
#include <linux/input.h> | |
#include <sys/ioctl.h> | |
#include <experimental/optional> | |
using std::experimental::optional; | |
struct device | |
{ | |
std::string path; | |
std::string name; | |
int capability_bits; | |
static device from_fd(int fd, std::string path="") | |
{ | |
return { | |
.path = std::move(path), | |
.name = device_name_from_fd(fd), | |
.capability_bits = device_bits_from_fd(fd), | |
}; | |
} | |
static optional<device> from_path(std::string path) | |
{ | |
int fd = ::open(path.c_str(), O_RDONLY); | |
if (fd < 0) return {}; | |
SCOPED(::close(fd)); | |
return from_fd(fd, std::move(path)); | |
} | |
static optional<device> from_index(int index) | |
{ | |
std::string path = "/dev/input/event" + std::to_string(index); | |
return from_path(path); | |
} | |
private: | |
static std::string device_name_from_fd(int fd) | |
{ | |
char name[256]; | |
ioctl(fd, EVIOCGNAME(255), name); | |
return name; | |
} | |
static int device_bits_from_fd(int fd) | |
{ | |
int bits; | |
ioctl(fd, EVIOCGBIT(0, sizeof(bits)), &bits); | |
return bits; | |
} | |
}; | |
auto list_devices() | |
{ | |
std::vector<device> devs; | |
for (int i=0;; i++) { | |
auto opt_dev = device::from_index(i); | |
if (!opt_dev) break; | |
devs.emplace_back(*opt_dev); | |
} | |
return devs; | |
} | |
auto bits_names(int bits) | |
{ | |
std::vector<std::string> names; | |
#define CHECK_BIT(T) \ | |
if (bits & (1 << EV_ ## T)) \ | |
names.emplace_back(#T); | |
CHECK_BIT(SYN); | |
CHECK_BIT(KEY); | |
CHECK_BIT(REL); | |
CHECK_BIT(ABS); | |
CHECK_BIT(MSC); | |
CHECK_BIT(SW); | |
CHECK_BIT(LED); | |
CHECK_BIT(SND); | |
CHECK_BIT(REP); | |
CHECK_BIT(FF); | |
CHECK_BIT(PWR); | |
CHECK_BIT(FF_STATUS); | |
#undef CHECK_BIT | |
return names; | |
} | |
auto type_name(int type) | |
{ | |
#define CHECK_TYPE(T) \ | |
if (type == EV_ ## T) \ | |
return #T; | |
CHECK_TYPE(SYN); | |
CHECK_TYPE(KEY); | |
CHECK_TYPE(REL); | |
CHECK_TYPE(ABS); | |
CHECK_TYPE(MSC); | |
CHECK_TYPE(SW); | |
CHECK_TYPE(LED); | |
CHECK_TYPE(SND); | |
CHECK_TYPE(REP); | |
CHECK_TYPE(FF); | |
CHECK_TYPE(PWR); | |
CHECK_TYPE(FF_STATUS); | |
#undef CHECK_TYPE | |
return "unknown"; | |
} | |
bool is_keyboard(int bits) | |
{ | |
#define CHECK_BIT(T) (bits & (1 << EV_ ## T)) | |
return (CHECK_BIT(KEY) && CHECK_BIT(REP)); | |
#undef CHECK_BIT | |
} | |
std::ostream & operator << (std::ostream & o, device const& dev) | |
{ | |
o << "<" << dev.path << "> [" << dev.name << "]"; | |
for (auto name: bits_names(dev.capability_bits)) | |
o << " " << name; | |
return o; | |
} | |
auto find_keyboards() | |
{ | |
std::vector<device> kbds; | |
for (auto & dev: list_devices()) { | |
auto kbd = is_keyboard(dev.capability_bits); | |
if (kbd) std::cerr << "\e[1;32m"; | |
std::cerr << dev; | |
if (kbd) std::cerr << "\e[0m"; | |
std::cerr << "\n"; | |
if (kbd) kbds.emplace_back(std::move(dev)); | |
} | |
return kbds; | |
} | |
optional<device> select_keyboard() | |
{ | |
auto kbds = find_keyboards(); | |
if (kbds.size() == 0) return {}; | |
return kbds[0]; | |
} | |
int main() | |
{ | |
auto opt_kbd = select_keyboard(); | |
if (!opt_kbd) { | |
std::cerr << "no keyboard found.\n"; | |
return 1; | |
} | |
auto kbd = std::move(*opt_kbd); | |
std::cerr << "selected keyboard " << kbd << "\n"; | |
int fd = ::open(kbd.path.c_str(), O_RDONLY); | |
if (fd < 0) ::err(1, "unknown error"); | |
SCOPED(::close(fd)); | |
bool keys[KEY_CNT]{}; | |
while (true) { | |
::input_event ev; | |
if (read(fd, &ev, sizeof(ev)) < 0) | |
::err(2, "read failure"); | |
switch (ev.type) { | |
case EV_KEY: | |
if (ev.value && keys[ev.code]) { | |
std::cerr << "\e[G"; | |
continue; | |
} | |
keys[ev.code] = (ev.value == 0 ? false : true); | |
break; | |
default: | |
std::cerr << "\e[G"; | |
continue; | |
} | |
std::cerr << "\e[G\e[K "; // move cursor to first column, and then clear current line | |
for (auto i=0u; i<sizeof(keys)/sizeof(keys[0]); i++) | |
if (keys[i]) | |
std::cerr << " " << i; | |
std::cerr << "\e[G"; | |
} | |
} | |
This file contains hidden or 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 <utility> | |
template <class F> | |
struct scope_exit_caller | |
{ | |
scope_exit_caller(F && f) : f{std::forward<F>(f)} {} | |
~scope_exit_caller() { f(); } | |
private: | |
F f; | |
}; | |
template <class F> | |
auto make_scope_exit_caller(F && f) | |
{ | |
return scope_exit_caller<F>{std::forward<F>(f)}; | |
} | |
#define SCOPE_EXIT_EXPAND3(A, B, C) A ## B ## C | |
#define SCOPE_EXIT_CAT3(A, B, C) SCOPE_EXIT_EXPAND3(A, B, C) | |
#define SCOPE_EXIT_VAR(NAME, CODE...) \ | |
auto NAME = ::make_scope_exit_caller([&] { CODE ; }) | |
#define SCOPED(CODE...) \ | |
SCOPE_EXIT_VAR(SCOPE_EXIT_CAT3(scoped_, __LINE__, __), CODE) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment