A keyboard handler and timer in C++ for the Linux platform

The keyboard handler used in conjunction with our timer to update our viewing position relative to the cube.

In this post we will construct a simple timer for evaluating the duration of a frame in addition to a keyboard handler. We will use these objects in conjunction to update our position relative to a cube. SDL and OpenGL will be used for rendering. Below is a frame captured from our application.

The keyboard handler used in conjunction with our timer to update our viewing position relative to the cube.
The keyboard handler used in conjunction with our timer to update our viewing position relative to the cube.

We will query an instance of our timer once for each pass through our application loop. It should yield the duration of the previous frame. The position of objects in the current frame can then be accurately updated relative to the previous frame.

The declaration of our timer object follows.

#ifndef TIMER_H
#define TIMER_H

#include <time.h>

class cTimer {
  private:
	timespec process_start, frame_start, current;
  protected:
  public:
	cTimer();
	~cTimer();
	double elapsed(bool frame);
};

#endif

In our constructor we grab the current time and assign it to both our application start time and frame start time.

cTimer::cTimer() {
	clock_gettime(CLOCK_REALTIME, &process_start);
	frame_start = process_start;
}

The elapsed method will return either the time since the previous call or the time since the constructor was called depending on the value of frame.

double cTimer::elapsed(bool frame) {
	clock_gettime(CLOCK_REALTIME, &current);
	double elapsed = frame ?
		(current.tv_sec + current.tv_nsec / 1000000000.0 -   frame_start.tv_sec -   frame_start.tv_nsec / 1000000000.0) :
		(current.tv_sec + current.tv_nsec / 1000000000.0 - process_start.tv_sec - process_start.tv_nsec / 1000000000.0);
	frame_start = current;
	return elapsed;
}

In our application loop we grab the frame duration as follows.

		double elapsed = t.elapsed(true);

Our keyboard handler will be implemented similarly to our joystick handler. The device manager for the Linux kernel, udev, will populate device nodes at "/dev/input/event*". We will query the keyboard device node for events and update our keyboard state structure appropriately. Looking in "/usr/includes/linux/input.h" we find the event structure (in addition to the keycodes).

struct input_event {
	struct timeval time;
	__u16 type;
	__u16 code;
	__s32 value;
};

For an event type of EV_KEY code holds our keycode, and value indicates whether the key was released (0), pressed (1), or held (2).

The declaration for our keyboard state structure and keyboard object are below.

#ifndef KEYBOARD_H
#define KEYBOARD_H

#include <iostream>
#include <fcntl.h>
#include <pthread.h>
#include <linux/input.h>

#define KEYBOARD_DEV "/dev/input/event0"

struct keyboard_state {
	signed short keys[KEY_CNT];
};

class cKeyboard {
  private:
	pthread_t thread;
	bool active;
	int keyboard_fd;
	input_event *keyboard_ev;
	keyboard_state *keyboard_st;
	char name[256];

  protected:
  public:
	cKeyboard();
	~cKeyboard();
	static void* loop(void* obj);
	void readEv();
	short getKeyState(short key);
};

#endif

In our constructor we attempt to access the device node for the keyboard, query the device for the name, and create a thread for updating our state structure.

cKeyboard::cKeyboard() {
	active = false;
	keyboard_fd = 0;
	keyboard_ev = new input_event();
	keyboard_st = new keyboard_state();
	keyboard_fd = open(KEYBOARD_DEV, O_RDONLY | O_NONBLOCK);
	if (keyboard_fd > 0) {
		ioctl(keyboard_fd, EVIOCGNAME(256), name);
		std::cout << "   Name: " << name << std::endl;
		active = true;
		pthread_create(&thread, 0, &cKeyboard::loop, this);
	}
}

Our keyboard loop attempts to read an event from the device, and, if an event was ready, update our keyboard state structure.

void* cKeyboard::loop(void *obj) {
	while (reinterpret_cast<cKeyboard *>(obj)->active) reinterpret_cast<cKeyboard *>(obj)->readEv();
}

void cKeyboard::readEv() {
	int bytes = read(keyboard_fd, keyboard_ev, sizeof(*keyboard_ev));
	if (bytes > 0) {
		if (keyboard_ev->type & EV_KEY) {
			keyboard_st->keys[keyboard_ev->code] = keyboard_ev->value;
		}
	}
}

We have a getKeyState method for reading the state of a key.

short cKeyboard::getKeyState(short key) {
	return keyboard_st->keys[key];
}

Finally, we have the destructor.

cKeyboard::~cKeyboard() {
	if (keyboard_fd > 0) {
		active = false;
		pthread_join(thread, 0);
		close(keyboard_fd);
	}
	delete keyboard_st;
	delete keyboard_ev;
	keyboard_fd = 0;
}

Below is an extract from our application loop. We grab the time elapsed since the previous frame and update our rotation angles based on the state of the keyboard. The value, 360.0f*elapsed, allows us to revolve once about the cube per second.

		elapsed = t.elapsed(true);

		if (kb.getKeyState(KEY_UP))    alpha += 360.0f*elapsed;
		if (kb.getKeyState(KEY_DOWN))  alpha -= 360.0f*elapsed;
		if (kb.getKeyState(KEY_LEFT))  beta  += 360.0f*elapsed;
		if (kb.getKeyState(KEY_RIGHT)) beta  -= 360.0f*elapsed;

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glLoadIdentity();
		glTranslatef(0.0f, 0.0f, -5.0f);
		glRotatef(alpha, 1.0f, 0.0f, 0.0f);
		glRotatef( beta, 0.0f, 1.0f, 0.0f);

Download this project: keyboard.tar.bz2

Leave a Reply

Your email address will not be published.