Skip to content

Instantly share code, notes, and snippets.

@lierdakil
Last active December 13, 2018 12:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lierdakil/af9af445c76dc4624199b09fef4566e4 to your computer and use it in GitHub Desktop.
Save lierdakil/af9af445c76dc4624199b09fef4566e4 to your computer and use it in GitHub Desktop.
Linux key polling and counting KPM
// Licensed under WTFPL, Version 2.
// See http://www.wtfpl.net/about/ for more information
// system imports
#include <linux/input.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
// c++stdlib imports
#include <iostream>
#include <filesystem>
#include <atomic>
#include <thread>
#include <numeric>
// for CHAR_BIT
#include <climits>
bool supportsKeyEvents(const int &fd) {
unsigned long evbit;
ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), &evbit);
if (evbit & (1 << EV_KEY)) {
unsigned char keybits[KEY_MAX/CHAR_BIT+1];
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits);
// check if device reports any of the standard 255 keypresses
for(std::size_t n = 0; n < 0x100/CHAR_BIT; ++n) {
if (keybits[n] > 0) return true;
}
}
return false;
}
bool shouldExit = false;
void sigintHandler(int) {
shouldExit = true;
}
void eventListenThreadWorker(std::atomic<unsigned int>* counter) {
// find all /dev/input/event* which report key events for keycodes 0..255
const char eventPathStart[] = "event";
std::vector<pollfd> poll_fds;
for (const auto &i : std::filesystem::directory_iterator("/dev/input")) {
if(i.is_character_file()) {
const auto &path = i.path();
const auto &filename = path.filename();
if (filename.string().compare(0, sizeof(eventPathStart)-1, eventPathStart) == 0) {
const int evfile = open(path.c_str(), O_RDONLY | O_NONBLOCK);
if(supportsKeyEvents(evfile)) {
std::cerr << "Listening for events on " << filename << std::endl;
poll_fds.push_back({evfile, POLLIN, 0});
} else {
close(evfile);
}
}
}
}
// poll for events
input_event ev;
while(!shouldExit) {
poll(poll_fds.data(), poll_fds.size(), -1);
for (const auto &pfd : poll_fds) {
if (pfd.revents & POLLIN) {
if(read(pfd.fd, &ev, sizeof(ev)) == sizeof(ev)) {
if(ev.type == EV_KEY && ev.value == 1 && ev.code < 0x100) {
(*counter)++;
}
}
}
}
}
// cleanup
for (auto &pfd : poll_fds) {
close(pfd.fd);
}
}
int main() {
signal(SIGINT, sigintHandler);
signal(SIGTERM, sigintHandler);
std::ios_base::sync_with_stdio(false);
std::atomic<unsigned int> counter = 0;
std::thread eventThread(eventListenThreadWorker, &counter);
using namespace std::chrono_literals;
// time between querying number of keypresses since last time
constexpr auto sleepTime = 100ms;
// number of periods that are smoothed over
constexpr std::size_t smoothingNum = 10;
// ring buffer to store a few recent values
std::array<unsigned int, smoothingNum> keysPerSleepTimeBuf = {0};
// constant to normalize keys/time to keys/minute
constexpr double ratio = 1min/(sleepTime*smoothingNum);
std::cerr << ratio << std::endl;
std::size_t ringIdx = 0;
auto now = std::chrono::system_clock::now();
while(!shouldExit) {
std::this_thread::sleep_until(now+sleepTime);
now = std::chrono::system_clock::now();
keysPerSleepTimeBuf[ringIdx] = counter.exchange(0);
ringIdx = (ringIdx + 1) % smoothingNum;
double kpm = ratio*std::accumulate(keysPerSleepTimeBuf.cbegin(), keysPerSleepTimeBuf.cend(), 0);
std::cout << "Current speed is " << kpm << " kpm " << "\r";
std::cout.flush();
}
eventThread.join();
std::cout << "\nfinished!\n";
}
all:
clang++ -std=c++17 keypoll.cpp -o keypoll -lstdc++fs -lpthread
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment