Skip to content

Instantly share code, notes, and snippets.

@cjxgm
Last active May 1, 2016 13:53
Show Gist options
  • Save cjxgm/8a14508664118a0bc28d2dd820758e1f to your computer and use it in GitHub Desktop.
Save cjxgm/8a14508664118a0bc28d2dd820758e1f to your computer and use it in GitHub Desktop.
Detect simulteneous keydown/up events in tty in Linux, using evdev directly
#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";
}
}
#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