From bddabbc56d6349810f0b7011c2f08d15d618f269 Mon Sep 17 00:00:00 2001
From: Sergei Trofimov <sergei.trofimov@arm.com>
Date: Thu, 6 Apr 2017 16:38:23 +0100
Subject: [PATCH] revent workload part 1

---
 wa/commands/record.py    |   92 +++
 wa/tools/revent/Makefile |   17 +
 wa/tools/revent/revent.c | 1490 ++++++++++++++++++++++++++++++++++++++
 wa/utils/revent.py       |  251 +++++++
 4 files changed, 1850 insertions(+)
 create mode 100644 wa/commands/record.py
 create mode 100644 wa/tools/revent/Makefile
 create mode 100644 wa/tools/revent/revent.c
 create mode 100644 wa/utils/revent.py

diff --git a/wa/commands/record.py b/wa/commands/record.py
new file mode 100644
index 00000000..e45e8ff4
--- /dev/null
+++ b/wa/commands/record.py
@@ -0,0 +1,92 @@
+#    Copyright 2014-2015 ARM Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+import sys
+
+
+from wa import Command, settings
+from wa.framework.configuration import RunConfiguration
+from wa.framework.resource import Executable, NO_ONE, ResourceResolver
+from wa.utils.revent import ReventRecorder
+
+
+class RecordCommand(Command):
+
+    name = 'record'
+    description = '''Performs a revent recording
+
+    This command helps making revent recordings. It will automatically
+    deploy revent and even has the option of automatically opening apps.
+
+    Revent allows you to record raw inputs such as screen swipes or button presses.
+    This can be useful for recording inputs for workloads such as games that don't
+    have XML UI layouts that can be used with UIAutomator. As a drawback from this,
+    revent recordings are specific to the device type they were recorded on.
+
+    WA uses two parts to the names of revent recordings in the format,
+    {device_name}.{suffix}.revent.
+
+     - device_name can either be specified manually with the ``-d`` argument or
+       it can be automatically determined. On Android device it will be obtained
+       from ``build.prop``, on Linux devices it is obtained from ``/proc/device-tree/model``.
+     - suffix is used by WA to determine which part of the app execution the
+       recording is for, currently these are either ``setup`` or ``run``. This
+       should be specified with the ``-s`` argument.
+    '''
+
+    def initialize(self, context):
+        self.parser.add_argument('-d', '--device', metavar='DEVICE',
+                                 help='''
+                                 Specify the device on which to run. This will
+                                 take precedence over the device (if any)
+                                 specified in configuration.
+                                 ''')
+
+    def execute(state, args):
+        if args.device:
+            device =  args.device
+            device_config = {}
+        else:
+            device = state.run_config.device
+            device_config = state.run_config.device_config
+        target_manager = TargetManager(device, device_config)
+
+
+def get_revent_binary(abi):
+    resolver = ResourceResolver()
+    resource = Executable(NO_ONE, abi, 'revent')
+    return resolver.get(resource)
+
+
+class ReventRecorder(object):
+
+    def __init__(self, target):
+        self.target = target
+        self.executable = None
+        self.deploy()
+
+    def deploy(self):
+        host_executable = get_revent_binary(self.target.abi)
+        self.executable = self.target.install(host_executable)
+
+    def record(self, path):
+        name = os.path.basename(path)
+        target_path = self.target.get_workpath(name)
+        command = '{} record {}'
+
+    def remove(self):
+        if self.executable:
+            self.target.uninstall('revent')
diff --git a/wa/tools/revent/Makefile b/wa/tools/revent/Makefile
new file mode 100644
index 00000000..c5ff3dc1
--- /dev/null
+++ b/wa/tools/revent/Makefile
@@ -0,0 +1,17 @@
+# CROSS_COMPILE=aarch64-linux-gnu- make
+#
+CC=gcc
+
+ifdef DEBUG
+	CFLAGS=-static -lc -g
+else
+	CFLAGS=-static -lc -O2
+endif
+
+revent: revent.c
+	$(CROSS_COMPILE)$(CC) $(CFLAGS) revent.c -o revent
+
+clean:
+	rm -rf revent
+
+.PHONY: clean
diff --git a/wa/tools/revent/revent.c b/wa/tools/revent/revent.c
new file mode 100644
index 00000000..3dc50ed3
--- /dev/null
+++ b/wa/tools/revent/revent.c
@@ -0,0 +1,1490 @@
+/*    Copyright 2012-2015 ARM Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+
+#define die(args...) do { \
+	fprintf(stderr, "ERROR: "); \
+	fprintf(stderr, args);   \
+	fprintf(stderr, "\n");  \
+	exit(EXIT_FAILURE); \
+} while(0)
+
+#define dprintf(args...) if (verbose) printf(args)
+
+#define INPDEV_MAX_DEVICES  16
+#define INPDEV_MAX_PATH     30
+#define MAX_NAME_LEN 255
+#define EV_BITS_SIZE (EV_MAX / 8 + 1)
+#define KEY_BITS_SIZE (KEY_MAX / 8 + 1)
+
+
+#define HEADER_PADDING_SIZE 6
+#define EVENT_PADDING_SIZE 4
+
+const char MAGIC[] = "REVENT";
+
+// NOTE: This should be incremented if any changes are made to the file format
+uint16_t FORMAT_VERSION = 2;
+
+typedef enum {
+	FALSE=0,
+	TRUE
+} bool_t;
+
+typedef enum {
+	GENERAL_MODE=0,
+	GAMEPAD_MODE,
+	INVALID_MODE  // should be last
+} recording_mode_t;
+
+typedef enum {
+	RECORD_COMMAND=0,
+	REPLAY_COMMAND,
+	DUMP_COMMAND,
+	INFO_COMMAND,
+	INVALID_COMMAND
+} revent_command_t;
+
+typedef struct {
+	struct input_absinfo absinfo;
+	int ev_code;
+} absinfo_t;
+
+typedef struct {
+	struct input_id id;
+	char name[MAX_NAME_LEN];
+	char ev_bits[EV_BITS_SIZE];
+	char abs_bits[KEY_BITS_SIZE];
+	char rel_bits[KEY_BITS_SIZE];
+	char key_bits[KEY_BITS_SIZE];
+	uint32_t num_absinfo;
+	absinfo_t absinfo[ABS_CNT];
+} device_info_t;
+
+typedef struct {
+	revent_command_t command;
+	recording_mode_t mode;
+	int32_t record_time;
+	int32_t device_number;
+	char *file;
+} revent_args_t;
+
+typedef struct {
+	int32_t num;
+	char **paths;
+	int *fds;
+	int max_fd;
+} input_devices_t;
+
+typedef struct {
+	int16_t dev_idx;
+	struct input_event event;
+} replay_event_t;
+
+typedef struct {
+	uint16_t version;
+	recording_mode_t mode;
+} revent_record_desc_t;
+
+typedef struct {
+	revent_record_desc_t desc;
+	input_devices_t devices;
+	device_info_t *gamepad_info;
+	uint64_t num_events;
+	replay_event_t *events;
+} revent_recording_t;
+
+bool_t verbose = FALSE;
+bool_t wait_for_stdin = TRUE;
+
+bool_t is_numeric(char *string)
+{
+	int len = strlen(string);
+
+	int i = 0;
+	while(i < len)
+	{
+		if(!isdigit(string[i]))
+			return FALSE;
+		i++;
+	}
+
+	return TRUE;
+}
+
+int test_bit(const char *mask, int bit) {
+	return mask[bit / 8] & (1 << (bit % 8));
+}
+
+int count_bits(const char *mask) {
+	int count = 0, i;
+	static const uint8_t nybble_lookup[16] = {
+		0, 1, 1, 2, 1, 2, 2, 3,
+		1, 2, 2, 3, 2, 3, 3, 4
+	};
+
+	for (i = 0; i < KEY_MAX/8 + 1; i++) {
+		char byte = mask[i];
+		count +=  nybble_lookup[byte & 0x0F] + nybble_lookup[byte >> 4];
+	}
+
+	return count;
+}
+
+/*
+ * An input device is considered to be a gamepad if it supports
+ * ABS x and Y axes and the four gamepad buttons (variously known as
+ * square/triangle/circle/X, A/B/X/Y, or north/south/east/west).
+ */
+bool_t is_gamepad(device_info_t *dev)
+{
+	if (!test_bit(dev->abs_bits, ABS_X))
+		return FALSE;
+	if (!test_bit(dev->abs_bits, ABS_Y))
+		return FALSE;
+	if (!test_bit(dev->key_bits, BTN_GAMEPAD))
+		return FALSE;
+	return TRUE;
+}
+
+off_t get_file_size(const char *filename) {
+	struct stat st;
+
+	if (stat(filename, &st) == 0)
+		return st.st_size;
+
+	die("Cannot determine size of %s: %s", filename, strerror(errno));
+}
+
+int get_device_info(int fd, device_info_t *info) {
+	bzero(info, sizeof(device_info_t));
+
+	if (ioctl(fd, EVIOCGID, &info->id) < 0)
+		return errno;
+
+	if (ioctl(fd, EVIOCGNAME(MAX_NAME_LEN * sizeof(char)), &info->name) < 0)
+		return errno;
+
+	if (ioctl(fd, EVIOCGBIT(0, sizeof(info->ev_bits)), &info->ev_bits) < 0)
+		return errno;
+
+	int ev_type;
+	for (ev_type = 0 ; ev_type < EV_MAX; ev_type++) {
+		if (test_bit(info->ev_bits, ev_type)) {
+
+			if (ev_type == EV_ABS) {
+				ioctl(fd, EVIOCGBIT(ev_type, sizeof(info->abs_bits)), &info->abs_bits);
+
+				int ev_code;
+				for (ev_code = 0; ev_code < KEY_MAX; ev_code++) {
+					if (test_bit(info->abs_bits, ev_code)) {
+						absinfo_t *inf = &info->absinfo[info->num_absinfo++];
+						inf->ev_code = ev_code;
+						ioctl(fd, EVIOCGABS(ev_code), &inf->absinfo);
+					}
+				}
+			} else if (ev_type == EV_REL) {
+				ioctl(fd, EVIOCGBIT(ev_type, sizeof(info->rel_bits)), &info->rel_bits);
+			} else if (ev_type == EV_KEY) {
+				ioctl(fd, EVIOCGBIT(ev_type, sizeof(info->key_bits)), &info->key_bits);
+			}
+		}
+	}
+
+	return 0;
+}
+
+void destroy_replay_device(int fd)
+{
+	if(ioctl(fd, UI_DEV_DESTROY) < 0)
+		die("Could not destroy replay device");
+}
+
+inline void set_evbit(int fd, int bit)
+{
+	if(ioctl(fd, UI_SET_EVBIT, bit) < 0)
+		die("Could not set EVBIT %i", bit);
+}
+
+inline void set_keybit(int fd, int bit)
+{
+	if(ioctl(fd, UI_SET_KEYBIT, bit) < 0)
+		die("Could not set KEYBIT %i", bit);
+}
+
+inline void set_absbit(int fd, int bit)
+{
+	if(ioctl(fd, UI_SET_ABSBIT, bit) < 0)
+		die("Could not set ABSBIT %i", bit);
+}
+
+inline void set_relbit(int fd, int bit)
+{
+	if(ioctl(fd, UI_SET_RELBIT, bit) < 0)
+		die("Could not set RELBIT %i", bit);
+}
+
+inline void block_sigterm(sigset_t *oldset)
+{
+	sigset_t sigset;
+	sigemptyset(&sigset);
+	sigaddset(&sigset, SIGTERM);
+	sigprocmask(SIG_BLOCK, &sigset, oldset);
+}
+
+// Events are recorded with their original timestamps, but for playback, we
+// want to treat timestamps as deltas from event zero.
+void adjust_event_times(revent_recording_t *recording)
+{
+	uint64_t i;
+	struct timeval time_zero, time_delta;
+
+	if (recording->num_events == 0)
+		return;
+
+	time_zero.tv_sec = recording->events[0].event.time.tv_sec;
+	time_zero.tv_usec = recording->events[0].event.time.tv_usec;
+
+	for(i = 0; i < recording->num_events; i++) {
+		timersub(&recording->events[i].event.time, &time_zero, &time_delta);
+		recording->events[i].event.time.tv_sec = time_delta.tv_sec;
+		recording->events[i].event.time.tv_usec = time_delta.tv_usec;
+	}
+}
+
+int write_record_header(int fd, const revent_record_desc_t *desc)
+{
+	ssize_t ret;
+	char padding[HEADER_PADDING_SIZE];
+
+	ret = write(fd, MAGIC, 6);
+	if (ret < 6)
+		return errno;
+
+	ret = write(fd, &desc->version, sizeof(desc->version));
+	if (ret < sizeof(desc->version))
+		return errno;
+
+	ret = write(fd, (uint16_t *)&desc->mode, sizeof(uint16_t));
+	if (ret < sizeof(uint16_t))
+		return errno;
+
+	bzero(padding, HEADER_PADDING_SIZE);
+	ret = write(fd, padding, HEADER_PADDING_SIZE);
+	if (ret < HEADER_PADDING_SIZE)
+		return errno;
+
+	return 0;
+}
+
+int read_record_header(int fd, revent_record_desc_t *desc)
+{
+	char start[7], padding[HEADER_PADDING_SIZE];
+	ssize_t ret;
+
+	ret = read(fd, start, 6);
+	if (ret < 6)
+		return errno;
+
+	start[6] = '\0';
+	if (strcmp(start, MAGIC))
+		return EINVAL;
+
+	ret = read(fd, &desc->version, sizeof(desc->version));
+	if (ret < sizeof(desc->version))
+		return errno;
+
+	if (desc->version >= 2) {
+		ret = read(fd, &desc->mode, sizeof(uint16_t));
+		if (ret < sizeof(uint16_t))
+			return errno;
+
+		ret = read(fd, padding, HEADER_PADDING_SIZE);
+		if (ret < HEADER_PADDING_SIZE)
+			return errno;
+	} else {
+		/* Version 1 supports only general recordings (mode 0) and
+		 * does not have padding
+		 */
+		desc->mode = GENERAL_MODE;
+	}
+
+	return 0;
+}
+
+int write_general_input_devices(const input_devices_t *devices, FILE *fout)
+{
+	size_t ret;
+	uint32_t path_len;
+	int i;
+
+	ret = fwrite(&devices->num, sizeof(uint32_t), 1, fout);
+	if (ret < 1) {
+		return errno;
+	}
+
+	for (i = 0; i < devices->num; i++) {
+		path_len = (uint32_t)strlen(devices->paths[i]);
+		ret = fwrite(&path_len, sizeof(uint32_t), 1, fout);
+		if (ret < 1) {
+			return errno;
+		}
+
+		ret = fwrite(devices->paths[i], sizeof(char), path_len, fout);
+		if (ret < path_len) {
+			return errno;
+		}
+	}
+
+	return 0;
+}
+
+int read_general_input_devices(input_devices_t *devices, FILE *fin)
+{
+	size_t ret;
+	uint32_t path_len;
+	int i;
+
+	ret = fread(&devices->num, sizeof(uint32_t), 1, fin);
+	if (ret < 1) {
+		return EIO;
+	}
+
+	devices->paths = malloc(sizeof(char *) * devices->num);
+	if (devices->paths == NULL) {
+		return ENOMEM;
+	}
+
+	for (i = 0; i < devices->num; i++) {
+		ret = fread(&path_len, sizeof(uint32_t), 1, fin);
+		if (ret < 1) {
+			return EIO;
+		}
+
+		devices->paths[i] = malloc(sizeof(char) * path_len + 1);
+		if (devices->paths[i] == NULL) {
+			return ENOMEM;
+		}
+
+		ret = fread(devices->paths[i], sizeof(char), path_len, fin);
+		if (ret < path_len) {
+			return EIO;
+		}
+		devices->paths[i][path_len] = '\0';
+	}
+
+	return 0;
+}
+
+int write_input_id(FILE *fout, const struct input_id *id)
+{
+	int ret = 0;
+	ret += fwrite(&id->bustype, sizeof(uint16_t), 1, fout);
+	ret += fwrite(&id->vendor, sizeof(uint16_t), 1, fout);
+	ret += fwrite(&id->product, sizeof(uint16_t), 1, fout);
+	ret += fwrite(&id->version, sizeof(uint16_t), 1, fout);
+	if (ret < 4)
+		return errno;
+	return 0;
+}
+
+int read_input_id(FILE *fin, struct input_id *id)
+{
+	int ret = 0;
+	ret += fread(&id->bustype, sizeof(uint16_t), 1, fin);
+	ret += fread(&id->vendor, sizeof(uint16_t), 1, fin);
+	ret += fread(&id->product, sizeof(uint16_t), 1, fin);
+	ret += fread(&id->version, sizeof(uint16_t), 1, fin);
+	if (ret < 4)
+		return errno;
+	return 0;
+}
+
+int write_absinfo(FILE *fout, const absinfo_t *info)
+{
+	int ret = 0;
+	ret += fwrite(&info->ev_code, sizeof(int32_t), 1, fout);
+	ret += fwrite(&info->absinfo.value, sizeof(int32_t), 1, fout);
+	ret += fwrite(&info->absinfo.minimum, sizeof(int32_t), 1, fout);
+	ret += fwrite(&info->absinfo.maximum, sizeof(int32_t), 1, fout);
+	ret += fwrite(&info->absinfo.fuzz, sizeof(int32_t), 1, fout);
+	ret += fwrite(&info->absinfo.flat, sizeof(int32_t), 1, fout);
+	ret += fwrite(&info->absinfo.resolution, sizeof(int32_t), 1, fout);
+	if (ret < 7)
+		return errno;
+	return 0;
+}
+
+int read_absinfo(FILE *fin, absinfo_t *info)
+{
+	int ret = 0;
+	ret += fread(&info->ev_code, sizeof(int32_t), 1, fin);
+	ret += fread(&info->absinfo.value, sizeof(int32_t), 1, fin);
+	ret += fread(&info->absinfo.minimum, sizeof(int32_t), 1, fin);
+	ret += fread(&info->absinfo.maximum, sizeof(int32_t), 1, fin);
+	ret += fread(&info->absinfo.fuzz, sizeof(int32_t), 1, fin);
+	ret += fread(&info->absinfo.flat, sizeof(int32_t), 1, fin);
+	ret += fread(&info->absinfo.resolution, sizeof(int32_t), 1, fin);
+	if (ret < 7)
+		return errno;
+	return 0;
+}
+
+int write_device_info(FILE *fout, const device_info_t *info)
+{
+	int ret = write_input_id(fout, &info->id);
+	if (ret)
+		return ret;
+
+	uint32_t name_len = (uint32_t)strlen(info->name);
+	ret = fwrite(&name_len, sizeof(uint32_t), 1, fout);
+	ret += fwrite(info->name, sizeof(char), name_len, fout);
+	if (ret < (name_len + 1))
+		return EIO;
+
+	ret = fwrite(info->ev_bits, sizeof(char), EV_BITS_SIZE, fout);
+	ret += fwrite(info->abs_bits, sizeof(char), KEY_BITS_SIZE, fout);
+	ret += fwrite(info->rel_bits, sizeof(char), KEY_BITS_SIZE, fout);
+	ret += fwrite(info->key_bits, sizeof(char), KEY_BITS_SIZE, fout);
+	if (ret < (EV_BITS_SIZE + KEY_BITS_SIZE * 3))
+		return EIO;
+        printf("EV_BITS_SIZE: %d\n", EV_BITS_SIZE);
+        printf("KEY_BITS_SIZE: %d\n", KEY_BITS_SIZE);
+
+	ret = fwrite(&info->num_absinfo, sizeof(uint32_t), 1, fout);
+	if (ret < 1)
+		return errno;
+
+	int i;
+	for (i = 0; i < info->num_absinfo; i++) {
+		ret = write_absinfo(fout, &info->absinfo[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+int read_device_info(FILE *fin, device_info_t *info)
+{
+	int ret = read_input_id(fin, &info->id);
+	if (ret)
+		return ret;
+
+	uint32_t name_len = 0;
+	fread(&name_len, sizeof(uint32_t), 1, fin);
+	if (!name_len)
+		return EIO;
+
+	ret += fread(info->name, sizeof(char), name_len, fin);
+	if (ret < name_len)
+		return EIO;
+	info->name[name_len] = '\0';
+
+	ret = fread(info->ev_bits, sizeof(char), EV_BITS_SIZE, fin);
+	ret += fread(info->abs_bits, sizeof(char), KEY_BITS_SIZE, fin);
+	ret += fread(info->rel_bits, sizeof(char), KEY_BITS_SIZE, fin);
+	ret += fread(info->key_bits, sizeof(char), KEY_BITS_SIZE, fin);
+	if (ret < (EV_BITS_SIZE + KEY_BITS_SIZE * 3))
+		return EIO;
+
+	ret = fread(&info->num_absinfo, sizeof(uint32_t), 1, fin);
+	if (ret < 1)
+		return errno;
+
+	int i;
+	for (i = 0; i < info->num_absinfo; i++) {
+		ret = read_absinfo(fin, &info->absinfo[i]);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+void print_device_info(device_info_t *info)
+{
+	printf("device name: %s\n", info->name);
+	printf("bustype: 0x%x vendor: 0x%x product: 0x%x version: 0x%x\n",
+                info->id.bustype, info->id.vendor, info->id.product, info->id.version);
+	printf("abs_bits: %d\n", count_bits(info->abs_bits));
+	printf("rel_bits: %d\n", count_bits(info->rel_bits));
+	printf("key_bits: %d\n", count_bits(info->key_bits));
+	printf("num_absinfo: %ld\n", info->num_absinfo);
+
+	int i;
+	printf("KEY: ");
+	for (i = 0; i < KEY_MAX; i++) {
+		if (test_bit(info->key_bits, i)) {
+			printf("%04x ", i);
+		}
+	}
+	printf("\n");
+
+	struct input_absinfo *inf;
+	int ev_code;
+	printf("ABS:\n");
+	for (i = 0; i < info->num_absinfo; i++) {
+		ev_code = info->absinfo[i].ev_code;
+		inf = &info->absinfo[i].absinfo;
+		printf("%04x  : min %i, max %i, fuzz %0i, flat %i, res %i\n", ev_code,
+				inf->minimum, inf->maximum, inf->fuzz, inf->flat,
+				inf->resolution);
+	}
+}
+
+int write_replay_event(FILE *fout, const replay_event_t *ev)
+{
+	size_t ret;
+	uint64_t time;
+
+	ret = fwrite(&ev->dev_idx, sizeof(uint16_t), 1, fout);
+	if (ret < 1)
+		return errno;
+	
+	time = (uint64_t)ev->event.time.tv_sec;
+	ret = fwrite(&time, sizeof(uint64_t), 1, fout);
+	if (ret < 1)
+		return errno;
+
+	time = (uint64_t)ev->event.time.tv_usec;
+	ret = fwrite(&time, sizeof(uint64_t), 1, fout);
+	if (ret < 1)
+		return errno;
+
+	ret = fwrite(&ev->event.type, sizeof(uint16_t), 1, fout);
+	if (ret < 1)
+		return errno;
+
+	ret = fwrite(&ev->event.code, sizeof(uint16_t), 1, fout);
+	if (ret < 1)
+		return errno;
+
+	ret = fwrite(&ev->event.value, sizeof(uint32_t), 1, fout);
+	if (ret < 1)
+		return errno;
+
+	return 0;
+}
+
+int read_replay_event(FILE *fin, replay_event_t *ev)
+{
+	size_t ret;
+
+	ret = fread(&ev->dev_idx, sizeof(uint16_t), 1, fin);
+	if (ret < 1)
+		return errno;
+
+	ret = fread(&ev->event.time.tv_sec, sizeof(uint64_t), 1, fin);
+	if (ret < 1)
+		return errno;
+
+	ret = fread(&ev->event.time.tv_usec, sizeof(uint64_t), 1, fin);
+	if (ret < 1)
+		return errno;
+
+	ret = fread(&ev->event.type, sizeof(uint16_t), 1, fin);
+	if (ret < 1)
+		return errno;
+
+	ret = fread(&ev->event.code, sizeof(uint16_t), 1, fin);
+	if (ret < 1)
+		return errno;
+
+	ret = fread(&ev->event.value, sizeof(uint32_t), 1, fin);
+	if (ret < 1)
+		return errno;
+
+	return 0;
+}
+
+int read_legacy_replay_event(int fdin, replay_event_t* ev)
+{
+	size_t rb;
+	char padding[EVENT_PADDING_SIZE];
+
+	rb = read(fdin, &(ev->dev_idx), sizeof(int32_t));
+	if (rb < (int)sizeof(int32_t)){
+		//Allow for abrupt ending of legacy recordings.
+		if (!errno)
+			return EOF;
+		return errno;
+	}
+	rb = read(fdin, &padding, EVENT_PADDING_SIZE);
+	if (rb < (int)sizeof(int32_t))
+		return errno;
+
+	struct timeval time;
+	uint64_t temp_time;
+	rb = read(fdin, &temp_time, sizeof(uint64_t));
+	if (rb < (int)sizeof(uint64_t))
+		return errno;
+	time.tv_sec = (time_t)temp_time;
+
+	rb = read(fdin, &temp_time, sizeof(uint64_t));
+	if (rb < (int)sizeof(uint64_t))
+		return errno;
+	time.tv_usec = (suseconds_t)temp_time;
+
+	ev->event.time = time;
+
+	rb = read(fdin, &(ev->event.type), sizeof(uint16_t));
+	if (rb < (int)sizeof(uint16_t))
+		return errno;
+
+	rb = read(fdin, &(ev->event.code), sizeof(uint16_t));
+	if (rb < (int)sizeof(uint16_t))
+		return errno;
+
+	rb = read(fdin, &(ev->event.value), sizeof(int32_t));
+	if (rb < (int)sizeof(int32_t))
+		return errno;
+
+	return 0;
+}
+
+int open_revent_recording(const char *filepath, revent_record_desc_t *desc, FILE **fin)
+{
+	*fin = fopen(filepath, "r");
+	if (*fin == NULL)
+		return errno;
+
+	int ret = read_record_header(fileno(*fin), desc);
+	if (ret)
+		return ret;
+
+	if (desc->version < 0 || desc->version > FORMAT_VERSION)
+		return EPROTO;
+
+	return 0;
+}
+
+FILE *init_recording(const char *pathname, recording_mode_t mode)
+{
+	revent_record_desc_t desc = { .mode = mode, .version = FORMAT_VERSION };
+
+	FILE *fh = fopen(pathname, "w");
+	if (fh == NULL)
+		return fh;
+
+	write_record_header(fileno(fh), &desc);
+
+	return fh;
+}
+
+void init_input_devices(input_devices_t *devices)
+{
+	devices->num = 0;
+	devices->max_fd = -1;
+	devices->paths = NULL;
+	devices->fds = NULL;
+}
+
+int init_general_input_devices(input_devices_t *devices)
+{
+	uint32_t num, i, path_len;
+	char paths[INPDEV_MAX_DEVICES][INPDEV_MAX_PATH];
+	int fds[INPDEV_MAX_DEVICES];
+	int max_fd = 0;
+
+	num = 0;
+	for(i = 0; i < INPDEV_MAX_DEVICES; ++i) {
+		sprintf(paths[num], "/dev/input/event%d", i);
+		fds[num] = open(paths[num], O_RDONLY);
+		if(fds[num] > 0) {
+			if (fds[num] > max_fd)
+				max_fd = fds[num];
+			dprintf("opened %s\n", paths[num]);
+			num++;
+		}
+		else {
+			dprintf("could not open %s\n", paths[num]);
+		}
+	}
+
+	if (num == 0)
+		return EACCES;
+
+	devices->num = num;
+	devices->max_fd = max_fd;
+
+	devices->paths = malloc(sizeof(char *) * num);
+	if (devices->paths == NULL) {
+		return ENOMEM;
+	}
+	for (i = 0; i < num; i ++) {
+		path_len = strlen(paths[i]);
+		devices->paths[i] = malloc(sizeof(char) * (path_len + 1));
+		if (devices->paths[i] == NULL)
+			return ENOMEM;
+		strncpy(devices->paths[i], paths[i],  path_len + 1);
+	}
+
+	devices->fds = malloc(sizeof(int) * num);
+	if (devices->fds == NULL) {
+		return ENOMEM;
+	}
+	for (i = 0; i < num; i ++)
+		devices->fds[i] = fds[i];
+
+	return 0;
+}
+
+void fini_general_input_devices(input_devices_t *devices)
+{
+	int i;
+	for (i = 0; i < devices->num; i++) {
+		if (devices->fds != NULL)
+			close(devices->fds[i]);
+		if (devices->paths != NULL)
+			free(devices->paths[i]);
+	}
+	free(devices->fds);
+	devices->num = 0;
+}
+
+
+int init_gamepad_input_devices(input_devices_t *devices, device_info_t *gamepad_info)
+{
+	int i;
+	char *gamepad_path = NULL;
+	input_devices_t all_devices;
+	device_info_t info;
+
+	int ret = init_general_input_devices(&all_devices);
+	if (ret) {
+		return ret;
+	}
+
+	for (i = 0; i < all_devices.num; i++) {
+		ret = get_device_info(all_devices.fds[i], &info);
+		if (ret) {
+			dprintf("Could not get info for %s: %s\n", all_devices.paths[i], strerror(errno));
+			continue;
+		}
+
+		if (!is_gamepad(&info)) {
+			dprintf("not a gamepad: %s\n", all_devices.paths[i]);
+			continue;
+		}
+
+		if (gamepad_path != NULL) {
+			die("More than one device identified as a gamepad (run \"reven info\" to see which)");
+		}
+
+		gamepad_path = malloc(sizeof(char) * INPDEV_MAX_PATH);
+		if (gamepad_path == NULL)
+			die("Could not create replay device: %s", strerror(ENOMEM));
+		strncpy(gamepad_path, all_devices.paths[i], INPDEV_MAX_PATH);
+		memcpy(gamepad_info, &info, sizeof(device_info_t));
+	}
+
+	fini_general_input_devices(&all_devices);
+
+	if (gamepad_path == NULL) {
+		return ENOMEDIUM;
+	}
+
+	dprintf("Found gamepad: %s\n", gamepad_path);
+	devices->num = 1;
+
+	devices->paths = malloc(sizeof(char *));
+	devices->paths[0] = gamepad_path;
+
+	devices->fds = malloc(sizeof(int *));
+	if (devices->fds == NULL)
+		return ENOMEM;
+	devices->fds[0] = open(gamepad_path, O_RDONLY);
+	if (devices->fds[0] < 0) {
+		return errno;
+	}
+	devices->max_fd = devices->fds[0];
+
+	return 0;
+}
+
+void fini_gamepad_input_devices(input_devices_t *devices)
+{
+	fini_general_input_devices(devices);
+}
+
+void init_revent_recording(revent_recording_t *recording)
+{
+	recording->num_events = 0;
+	recording->desc.version = 0;
+	recording->desc.mode = INVALID_MODE;
+	recording->events = NULL;
+	recording->gamepad_info = NULL;
+	init_input_devices(&recording->devices);
+}
+
+void fini_revent_recording(revent_recording_t *recording)
+{
+	if (recording->desc.mode == GENERAL_MODE) {
+		fini_general_input_devices(&recording->devices);
+	} else if (recording->desc.mode == GAMEPAD_MODE) {
+		fini_gamepad_input_devices(&recording->devices);
+		free(recording->gamepad_info);
+	} else {
+		// We're finalizing the recording so at this point,
+		// we don't care.
+	}
+	free(recording->events);
+	recording->num_events = 0;
+	recording->desc.version = 0;
+	recording->desc.mode = INVALID_MODE;
+}
+
+void open_general_input_devices_for_playback_or_die(input_devices_t *devices)
+{
+	int i, ret;
+	devices->fds = malloc(sizeof(int) * devices->num);
+	if (devices->fds == NULL)
+		die("Could not allocate file descriptor array: %s", strerror(ENOMEM));
+
+	for (i = 0; i < devices->num; i++)
+	{
+		ret = open(devices->paths[i], O_WRONLY | O_NDELAY);
+		if (ret < 0) {
+			die("Could not open \"%s\" for writing: %s",
+					devices->paths[i], strerror(errno));
+		}
+		devices->fds[i] = ret;
+		if (devices->fds[i] > devices->max_fd)
+			devices->max_fd =  devices->fds[i];
+		dprintf("Opened %s\n", devices->paths[i]);
+	}
+}
+
+int create_replay_device_or_die(const device_info_t *info)
+{
+	int i;
+
+	int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK);
+	if (fd < 0) {
+		if (errno == ENOENT) {
+			die("uinput not supported by the kernel (is the module installed?)");
+		} else if (errno == EACCES) {
+			die("Cannot access \"/dev/uinput\" (try re-running as root)");
+		} else {
+			die("Could not open \"/dev/uinput\" for writing: %s", strerror(errno));
+		}
+	}
+
+	struct uinput_user_dev uidev;
+	memset(&uidev, 0, sizeof(uidev));
+	snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "revent-replay %s", info->name);
+	uidev.id.bustype = BUS_USB;
+	uidev.id.vendor  = info->id.vendor;
+	uidev.id.product = info->id.product;
+	uidev.id.version = info->id.version;
+
+	set_evbit(fd, EV_SYN);
+
+	set_evbit(fd, EV_KEY);
+	for (i = 0; i < KEY_MAX; i++) {
+		if (test_bit(info->key_bits, i))
+			set_keybit(fd, i);
+	}
+
+	set_evbit(fd, EV_REL);
+	for (i = 0; i < REL_MAX; i++) {
+		if (test_bit(info->rel_bits, i))
+			set_relbit(fd, i);
+	}
+
+	set_evbit(fd, EV_ABS);
+	for (i = 0; i < info->num_absinfo; i++) {
+		int ev_code = info->absinfo[i].ev_code;
+		set_absbit(fd, ev_code);
+		uidev.absmin[ev_code] = info->absinfo[i].absinfo.minimum;
+		uidev.absmax[ev_code] = info->absinfo[i].absinfo.maximum;
+		uidev.absfuzz[ev_code] = info->absinfo[i].absinfo.fuzz;
+		uidev.absflat[ev_code] = info->absinfo[i].absinfo.flat;
+	}
+	if (write(fd, &uidev, sizeof(uidev)) < sizeof(uidev)) {
+		die("Could not write absinfo:", strerror(errno));
+	}
+
+	if(ioctl(fd, UI_DEV_CREATE) < 0)
+		die("Could not create replay device:", strerror(errno));
+
+        // wait for the new device to be recognised by the system
+        sleep(3);
+
+	return fd;
+}
+
+inline void read_revent_recording_or_die(const char *filepath, revent_recording_t *recording)
+{
+	int ret;
+	FILE *fin;
+	uint64_t i;
+	off_t fsize;
+
+	ret = open_revent_recording(filepath, &recording->desc, &fin);
+	if (ret) {
+		if (ret == EINVAL) {
+			die("%s does not appear to be an revent recording", filepath);
+		} else if (ret == EPROTO) {
+			die("%s contains recording for unsupported version \"%u\"; max supported version is \"%u\"",
+					filepath, recording->desc.version, FORMAT_VERSION);
+		} else  {
+			die("%s revent recording appears to be corrupted", filepath);
+		}
+	}
+
+	if (recording->desc.mode == GENERAL_MODE) {
+		ret = read_general_input_devices(&recording->devices, fin);
+		if (ret) {
+			die("Could not read devices: %s", strerror(ret));
+		}
+		recording->gamepad_info = NULL;
+	} else if (recording->desc.mode == GAMEPAD_MODE) {
+		recording->gamepad_info = malloc(sizeof(device_info_t));
+		if (recording->gamepad_info == NULL)
+			die("Could not allocate gamepad info buffer: %s", strerror(ENOMEM));
+		ret = read_device_info(fin, recording->gamepad_info);
+		if (ret)
+			die("Could not read gamepad info: %s", strerror(ret));
+	} else {
+		die("Unexpected recording mode: %d", recording->desc.mode);
+	}
+
+	if (recording->desc.version > 1) {
+		ret = fread(&recording->num_events, sizeof(uint64_t), 1, fin);
+		if (ret < 1)
+			die("Could not read the number of recorded events");
+
+		recording->events = malloc(sizeof(replay_event_t) * recording->num_events);
+		if (recording->events == NULL)
+			die("Not enough memory to allocate replay buffer");
+
+		for(i=0; i < recording->num_events; i++) {
+			read_replay_event(fin, &recording->events[i]);
+		}
+	} else {   // backwards compatibility
+		/* Prior to verion 2, the total number of recorded events was not being
+		 * written as part of the recording. We will use the size of the file on
+		 * disk to estimate the recording buffer size and keep reading the events
+		 * untils EOF, keeping track of how many we read so that the total can
+		 * then be updated. The format of the events is also different -- it
+		 * featured larger device ID an unnecessary padding.
+		 */
+		 fsize  = get_file_size(filepath);
+		 recording->events = malloc((size_t)fsize);
+		 i = 0;
+
+		// Safely get file descriptor for fin, by flushing first.
+		fflush(fin);
+
+		 while (1) {
+			ret = read_legacy_replay_event(fileno(fin), &recording->events[i]);
+			if (ret == EOF) {
+				break;
+			} else if (ret) {
+				die("error reading events: %s", strerror(ret));
+			}
+			i++;
+		 }
+		 recording->num_events = i;
+	}
+
+	fclose(fin);
+}
+
+void open_gamepad_input_devices_for_playback_or_die(input_devices_t *devices, const device_info_t *info)
+{
+	int fd = create_replay_device_or_die(info);
+	devices->num = 1;
+	devices->fds = malloc(sizeof(int));
+	if (devices->fds == NULL)
+		die("Could not create replay devices: %s", strerror(ENOMEM));
+	devices->fds[0] = fd;
+	devices->max_fd = fd;
+}
+
+//Used to exit program properly on termination
+static volatile int EXIT = 0;
+void exitHandler(int z) {
+    EXIT = 1;
+}
+
+void record(const char *filepath, int delay, recording_mode_t mode)
+{
+	int ret;
+	FILE *fout = init_recording(filepath, mode);
+	if (fout == NULL)
+		die("Could not create recording \"%s\": %s", filepath, strerror(errno));
+
+	input_devices_t devices;
+	init_input_devices(&devices);
+
+	if (mode == GENERAL_MODE) {
+		ret = init_general_input_devices(&devices);
+		if (ret)
+			die("Could not initialize input devices: %s", strerror(ret));
+		ret = write_general_input_devices(&devices, fout);
+		if (ret)
+			die("Could not record input devices: %s", strerror(ret));
+	} else if (mode == GAMEPAD_MODE) {
+		device_info_t info;
+		ret = init_gamepad_input_devices(&devices, &info);
+		if (ret == ENOMEDIUM) {
+			die("There does not appear to be a gamepad connected");
+		} else if (ret) {
+			die("Problem initializing gamepad device: %s", strerror(ret));
+		}
+		ret = write_device_info(fout, &info);
+		if (ret)
+			die("Problem writing gamepad info: %s", strerror(ret));
+	} else {
+		fclose(fout);
+		die("Invalid recording mode specified");
+	}
+
+	sigset_t old_sigset;
+	sigemptyset(&old_sigset);
+	block_sigterm(&old_sigset);
+
+	// Write the zero size as a place holder and remember the position in the
+	// file stream, so that it may be updated at the end with the actual event
+	// count.
+	uint64_t event_count = 0;
+	long size_pos = ftell(fout);
+	ret = fwrite(&event_count, sizeof(uint64_t), 1, fout);
+	if (ret < 1)
+		die("Could not initialise event count: %s", strerror(errno));
+
+	char padding[EVENT_PADDING_SIZE];
+	bzero(padding, EVENT_PADDING_SIZE);
+
+	fd_set readfds;
+	struct timespec tout;
+	replay_event_t rev;
+	int32_t maxfd = 0;
+	int32_t keydev = 0;
+	int i;
+	printf("recording...\n");
+
+	errno = 0;
+	signal(SIGINT, exitHandler);
+	
+	while(1)
+	{
+		FD_ZERO(&readfds);
+		FD_SET(STDIN_FILENO, &readfds);
+		for (i=0; i < devices.num; i++)
+			FD_SET(devices.fds[i], &readfds);
+
+		/* wait for input */
+		tout.tv_sec = delay;
+		tout.tv_nsec = 0;
+
+		ret = pselect(devices.max_fd + 1, &readfds, NULL, NULL, &tout, &old_sigset);
+
+		if (EXIT){
+			break;
+		}
+		if (errno == EINTR){
+			break;
+		}
+		if (!ret){
+			break;
+		}
+
+		if (wait_for_stdin && FD_ISSET(STDIN_FILENO, &readfds)) {
+			// in this case the key down for the return key will be recorded
+			// so we need to up the key up
+			memset(&rev, 0, sizeof(rev));
+			rev.dev_idx = keydev;
+			rev.event.type = EV_KEY;
+			rev.event.code = KEY_ENTER;
+			rev.event.value = 0;
+			gettimeofday(&rev.event.time, NULL);
+			write_replay_event(fout, &rev);
+
+			// syn
+			memset(&rev, 0, sizeof(rev));
+			rev.dev_idx = keydev;
+			rev.event.type = EV_SYN;
+			rev.event.code = 0;
+			rev.event.value = 0;
+			gettimeofday(&rev.event.time, NULL);
+			write_replay_event(fout, &rev);
+
+			dprintf("added fake return exiting...\n");
+			break;
+		}
+
+		for (i = 0; i < devices.num; i++)
+		{
+			if (FD_ISSET(devices.fds[i], &readfds))
+			{
+				dprintf("got event from %s\n", devices.paths[i]);
+				memset(&rev, 0, sizeof(rev));
+				rev.dev_idx = i;
+				ret = read(devices.fds[i], (void *)&rev.event, sizeof(rev.event));
+				dprintf("%d event: type %d code %d value %d\n",
+						(unsigned int)ret, rev.event.type, rev.event.code, rev.event.value);
+				if (rev.event.type == EV_KEY && rev.event.code == KEY_ENTER && rev.event.value == 1)
+					keydev = i;
+				write_replay_event(fout, &rev);
+				event_count++;
+			}
+		}
+	}
+
+	dprintf("Writing event count...");
+	if ((ret = fseek(fout, size_pos, SEEK_SET)) == -1)
+		die("Could not write event count: %s", strerror(errno));
+	ret = fwrite(&event_count, sizeof(uint64_t), 1, fout);
+	if (ret < 1)
+		die("Could not write event count: %s", strerror(errno));
+
+	fclose(fout);
+
+	if (mode == GENERAL_MODE) {
+		fini_general_input_devices(&devices);
+	} else if (mode == GAMEPAD_MODE) {
+		fini_gamepad_input_devices(&devices);
+	} else {
+		// Should never get here, as would have failed at the beginning
+		die("Unexpected mode on finish");
+	}
+}
+
+void dump(const char *filepath)
+{
+	int i, ret = 0;
+	revent_recording_t recording;
+	init_revent_recording(&recording);
+
+	read_revent_recording_or_die(filepath, &recording);
+	printf("recording version: %u\n", recording.desc.version);
+	printf("recording type: %i\n", recording.desc.mode);
+	printf("number of recorded events: %lu\n", recording.num_events);
+
+	printf("\n");
+	if (recording.desc.mode == GENERAL_MODE) {
+		printf("devices:\n");
+		for (i = 0; i < recording.devices.num; i++) {
+			printf("%2i: %s\n", i, recording.devices.paths[i]);
+		}
+	} else if (recording.desc.mode == GAMEPAD_MODE) {
+		print_device_info(recording.gamepad_info);
+	} else {
+		die("Unexpected recording type: %d", recording.desc.mode);
+	}
+
+	printf("\nevents:\n");
+	for (i =0; i < recording.num_events; i++) {
+		printf("%ld.%06ld dev: %d type: %d code: %d value %d\n",
+				recording.events[i].event.time.tv_sec,
+				recording.events[i].event.time.tv_usec,
+				recording.events[i].dev_idx,
+				recording.events[i].event.type,
+				recording.events[i].event.code,
+				recording.events[i].event.value
+		      );
+	}
+
+	fini_revent_recording(&recording);
+}
+
+void replay(const char *filepath)
+{
+	revent_recording_t recording;
+	init_revent_recording(&recording);
+
+	read_revent_recording_or_die(filepath, &recording);
+	switch (recording.desc.mode) {
+	case GENERAL_MODE:
+		dprintf("Opening input devices for playback\n");
+		open_general_input_devices_for_playback_or_die(&recording.devices);
+		break;
+	case GAMEPAD_MODE:
+		dprintf("Creating gamepad playback device\n");
+		open_gamepad_input_devices_for_playback_or_die(&recording.devices, recording.gamepad_info);
+		break;
+	default:
+		die("Unexpected recording mod: %d", recording.desc.mode);
+	}
+	dprintf("Adjusting event timestamps\n");
+	adjust_event_times(&recording);
+
+	struct timeval start_time, now, desired_time, last_event_delta, delta;
+	bzero(&last_event_delta, sizeof(struct timeval));
+	gettimeofday(&start_time, NULL);
+
+	int ret;
+	uint64_t i = 0;
+	dprintf("Starting payback\n");
+	while (i < recording.num_events) {
+		gettimeofday(&now, NULL);
+		timeradd(&start_time, &last_event_delta, &desired_time);
+
+		if (timercmp(&desired_time, &now, >)) {
+			timersub(&desired_time, &now, &delta);
+			useconds_t d = (useconds_t)delta.tv_sec * 1000000 + delta.tv_usec;
+			dprintf("now %u.%u desiredtime %u.%u sleeping %u uS\n",
+					(unsigned int)now.tv_sec,
+					(unsigned int)now.tv_usec,
+					(unsigned int)desired_time.tv_sec,
+					(unsigned int)desired_time.tv_usec,
+					d);
+			usleep(d);
+		}
+
+		int32_t idx = (recording.events[i]).dev_idx;
+		struct input_event ev = (recording.events[i]).event;
+		while((i < recording.num_events) && !timercmp(&ev.time, &last_event_delta, !=)) {
+			ret = write(recording.devices.fds[idx], &ev, sizeof(ev));
+			if (ret != sizeof(ev))
+				die("Could not replay event");
+			dprintf("replayed event: type %d code %d value %d\n", ev.type, ev.code, ev.value);
+
+			i++;
+			idx = recording.events[i].dev_idx;
+			ev = recording.events[i].event;
+		}
+		last_event_delta = ev.time;
+	}
+
+        if (recording.desc.mode == GAMEPAD_MODE)
+		destroy_replay_device(recording.devices.fds[0]);
+	fini_revent_recording(&recording);
+}
+
+void info(void)
+{
+	input_devices_t devices;
+	init_input_devices(&devices);
+
+	int ret = init_general_input_devices(&devices);
+	if (ret) {
+		die("Could not read input devices: %s", strerror(errno));
+	}
+
+	int i;
+	device_info_t info;
+	for (i = 0; i < devices.num; i++) {
+		ret = get_device_info(devices.fds[i], &info);
+		if (ret) {
+			printf("Could not get info for %s: %s\n", devices.paths[i], strerror(errno));
+			continue;
+		}
+
+		printf("DEVICE %d\n", i);
+		printf("device path: %s\n", devices.paths[i]);
+		printf("is gamepad: %s\n", is_gamepad(&info) ? "yes" : "no");
+		print_device_info(&info);
+		printf("\n");
+	}
+
+	fini_general_input_devices(&devices);
+}
+
+void usage()
+{
+	printf("usage:\n    revent [-h] [-v] COMMAND [OPTIONS] \n"
+			"\n"
+			"    Options:\n"
+			"        -h  print this help message and quit.\n"
+			"        -v  enable verbose output.\n"
+			"\n"
+			"    Commands:\n"
+			"        record [-t SECONDS] [-d DEVICE] FILE\n"
+			"            Record input event. stops after return on STDIN (or, optionally, \n"
+			"            a fixed delay)\n"
+			"\n"
+			"                FILE       file into which events will be recorded.\n"
+			"                -t SECONDS time, in seconds, for which to record events.\n"
+			"                           if not specified, recording will continue until\n"
+			"                           return key is pressed.\n"
+			"                -d DEVICE  the number of the input device form which\n"
+			"                           events will be recorded. If not specified, \n"
+			"                           all available inputs will be used.\n"
+			"                -s         Recording will not be stopped if there is \n"
+			"                           input on STDIN.\n"
+			"                -g         Record in \"gamepad\" mode. A gamepad must be \n"
+			"                           connected to the device. The recording will only\n"
+			"                           be done for the gamepad and other input devices\n"
+			"                           will not be recorded. In addition to the input\n"
+			"                           events, the information about the gamepad will\n"
+			"                           also be stored in the recording. When this\n"
+			"                           recording is played back, revent will first\n"
+			"                           create a virtual gamepad device based on the\n"
+			"                           stored info and the event will be played back\n"
+			"                           into it. This type of recording should be more\n"
+			"                           portable across different devices.\n"
+			"\n"
+			"        replay FILE\n"
+			"            replays previously recorded events from the specified file.\n"
+			"\n"
+			"                FILE       file into which events will be recorded.\n"
+			"\n"
+			"        dump FILE\n"
+			"            dumps the contents of the specified event log to STDOUT in\n"
+			"            human-readable form.\n"
+			"\n"
+			"                FILE       event log which will be dumped.\n"
+			"\n"
+			"        info\n"
+			"             shows info about each event char device\n"
+			"\n"
+			);
+}
+
+void revent_args_init(revent_args_t **rargs, int argc, char** argv)
+{
+	*rargs = malloc(sizeof(revent_args_t));
+	revent_args_t *revent_args = *rargs;
+	revent_args->command = INVALID_COMMAND;
+	revent_args->mode = GENERAL_MODE;
+	revent_args->record_time = INT_MAX;
+	revent_args->device_number = -1;
+	revent_args->file = NULL;
+
+	int opt;
+	while ((opt = getopt(argc, argv, "hgt:d:vs")) != -1)
+	{
+		switch (opt) {
+			case 'h':
+				usage();
+				exit(0);
+				break;
+			case 'g':
+				revent_args->mode = GAMEPAD_MODE;
+				break;
+			case 't':
+				if (is_numeric(optarg)) {
+					revent_args->record_time = atoi(optarg);
+					dprintf("timeout: %d\n", revent_args->record_time);
+				} else {
+					die("-t parameter must be numeric; got %s.", optarg);
+				}
+				break;
+			case 'd':
+				if (is_numeric(optarg)) {
+					revent_args->device_number = atoi(optarg);
+					dprintf("device: %d\n", revent_args->device_number);
+				} else {
+					die("-d parameter must be numeric; got %s.", optarg);
+				}
+				break;
+			case 'v':
+				verbose = TRUE;
+				break;
+			case 's':
+				wait_for_stdin = FALSE;
+				break;
+
+			default:
+				die("Unexpected option: %c", opt);
+		}
+	}
+
+	int next_arg = optind;
+	if (next_arg == argc) {
+		usage();
+		die("Must specify a command.");
+	}
+	if (!strcmp(argv[next_arg], "record"))
+		revent_args->command = RECORD_COMMAND;
+	else if (!strcmp(argv[next_arg], "replay"))
+		revent_args->command = REPLAY_COMMAND;
+	else if (!strcmp(argv[next_arg], "dump"))
+		revent_args->command = DUMP_COMMAND;
+	else if (!strcmp(argv[next_arg], "info"))
+		revent_args->command = INFO_COMMAND;
+	else {
+		usage();
+		die("Unknown command -- %s", argv[next_arg]);
+	}
+	next_arg++;
+
+	if (next_arg != argc) {
+		revent_args->file = argv[next_arg];
+		dprintf("file: %s\n", revent_args->file);
+		next_arg++;
+		if (next_arg != argc) {
+			die("Trailling arguments (use -h for help).");
+		}
+	}
+
+	if ((revent_args->command != RECORD_COMMAND) && (revent_args->record_time != INT_MAX)) {
+		die("-t parameter is only valid for \"record\" command.");
+	}
+	if ((revent_args->command != RECORD_COMMAND) && (revent_args->device_number != -1)) {
+		die("-d parameter is only valid for \"record\" command.");
+	}
+	if ((revent_args->command == INFO_COMMAND) && (revent_args->file != NULL)) {
+		die("File path cannot be specified for \"info\" command.");
+	}
+	if (((revent_args->command == RECORD_COMMAND) || (revent_args->command == REPLAY_COMMAND))
+			&& (revent_args->file == NULL)) {
+		die("Must specify a file for recording/replaying (use -h for help).");
+	}
+}
+
+int revent_args_close(revent_args_t *rargs)
+{
+	free(rargs);
+	return 0;
+}
+
+int main(int argc, char** argv)
+{
+	int i;
+	char *logfile = NULL;
+	revent_args_t *rargs = NULL;
+
+	revent_args_init(&rargs, argc, argv);
+
+	switch(rargs->command) {
+		case RECORD_COMMAND:
+			record(rargs->file, rargs->record_time, rargs->mode);
+			break;
+		case REPLAY_COMMAND:
+			replay(rargs->file);
+			break;
+		case DUMP_COMMAND:
+			dump(rargs->file);
+			break;
+		case INFO_COMMAND:
+			info();
+			break;
+		defaut:
+			die("Unexpected revent command: %d", rargs->command);
+	};
+
+	revent_args_close(rargs);
+	return 0;
+}
diff --git a/wa/utils/revent.py b/wa/utils/revent.py
new file mode 100644
index 00000000..b7de2750
--- /dev/null
+++ b/wa/utils/revent.py
@@ -0,0 +1,251 @@
+#    Copyright 2016 ARM Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from __future__ import division
+import os
+import struct
+from datetime import datetime
+from collections import namedtuple
+
+
+GENERAL_MODE = 0
+GAMEPAD_MODE = 1
+
+
+u16_struct = struct.Struct('<H')
+u32_struct = struct.Struct('<I')
+u64_struct = struct.Struct('<Q')
+
+# See revent section in WA documentation for the detailed description of
+# the recording format.
+header_one_struct = struct.Struct('<6sH')
+header_two_struct = struct.Struct('<H6x')  # version 2 onwards
+
+devid_struct = struct.Struct('<4H')
+devinfo_struct = struct.Struct('<4s96s96s96sI')
+absinfo_struct = struct.Struct('<7i')
+
+event_struct = struct.Struct('<HqqHHi')
+old_event_struct = struct.Struct("<i4xqqHHi")  # prior to version 2
+
+
+def read_struct(fh, struct_spec):
+    data = fh.read(struct_spec.size)
+    return struct_spec.unpack(data)
+
+
+def read_string(fh):
+    length, = read_struct(fh, u32_struct)
+    str_struct = struct.Struct('<{}s'.format(length))
+    return read_struct(fh, str_struct)[0]
+
+
+def count_bits(bitarr):
+    return sum(bin(b).count('1') for b in bitarr)
+
+
+def is_set(bitarr, bit):
+    byte = bit // 8
+    bytebit = bit % 8
+    return bitarr[byte] & bytebit
+
+
+absinfo = namedtuple('absinfo', 'ev_code value min max fuzz flat resolution')
+
+
+class UinputDeviceInfo(object):
+
+    def __init__(self, fh):
+        parts = read_struct(fh, devid_struct)
+        self.bustype = parts[0]
+        self.vendor = parts[1]
+        self.product = parts[2]
+        self.version = parts[3]
+
+        self.name = read_string(fh)
+
+        parts = read_struct(fh, devinfo_struct)
+        self.ev_bits = bytearray(parts[0])
+        self.key_bits = bytearray(parts[1])
+        self.rel_bits = bytearray(parts[2])
+        self.abs_bits = bytearray(parts[3])
+        self.num_absinfo = parts[4]
+        self.absinfo = [absinfo(*read_struct(fh, absinfo_struct))
+                        for _ in xrange(self.num_absinfo)]
+
+    def __str__(self):
+        return 'UInputInfo({})'.format(self.__dict__)
+
+
+class ReventEvent(object):
+
+    def __init__(self, fh, legacy=False):
+        if not legacy:
+            dev_id, ts_sec, ts_usec, type_, code, value = read_struct(fh, event_struct)
+        else:
+            dev_id, ts_sec, ts_usec, type_, code, value = read_struct(fh, old_event_struct)
+        self.device_id = dev_id
+        self.time = datetime.fromtimestamp(ts_sec + float(ts_usec) / 1000000)
+        self.type = type_
+        self.code = code
+        self.value = value
+
+    def __str__(self):
+        return 'InputEvent({})'.format(self.__dict__)
+
+
+class ReventRecording(object):
+    """
+    Represents a parsed revent recording. This contains input events and device
+    descriptions recorded by revent. Two parsing modes are supported. By
+    default, the recording will be parsed in the "streaming" mode. In this
+    mode, initial headers and device descritions are parsed on creation and an
+    open file handle to the recording is saved. Events will be read from the
+    file as they are being iterated over. In this mode, the entire recording is
+    never loaded into memory at once. The underlying file may be "released" by
+    calling ``close`` on the recroding, after which further iteration over the
+    events will not be possible (but would still be possible to access the file
+    description and header information).
+
+    The alternative is to load the entire recording on creation (in which case
+    the file handle will be closed once the recroding is loaded). This can be
+    enabled by specifying ``streaming=False``. This will make it faster to
+    subsequently iterate over the events, and also will not "hold" the file
+    open.
+
+    .. note:: When starting a new iteration over the events in streaming mode,
+              the postion in the open file will be automatically reset to the
+              beginning of the event stream. This means it's possible to iterate
+              over the events multiple times without having to re-open the
+              recording, however it is not possible to do so in parallel. If
+              parallel iteration is required, streaming should be disabled.
+
+    """
+
+    @property
+    def duration(self):
+        if self._duration is None:
+            if self.stream:
+                events = self._iter_events()
+                try:
+                    first = last = events.next()
+                except StopIteration:
+                    self._duration = 0
+                for last in events:
+                    pass
+                self._duration = (last.time - first.time).total_seconds()
+            else:  # not streaming
+                if not self._events:
+                    self._duration = 0
+                self._duration = (self._events[-1].time -
+                                  self._events[0].time).total_seconds()
+        return self._duration
+
+    @property
+    def events(self):
+        if self.stream:
+            return self._iter_events()
+        else:
+            return self._events
+
+    def __init__(self, f, stream=True):
+        self.device_paths = []
+        self.gamepad_device = None
+        self.num_events = None
+        self.stream = stream
+        self._events = None
+        self._close_when_done = False
+        self._events_start = None
+        self._duration = None
+
+        if hasattr(f, 'name'):  # file-like object
+            self.filepath = f.name
+            self.fh = f
+        else:  # path to file
+            self.filepath = f
+            self.fh = open(self.filepath, 'rb')
+            if not self.stream:
+                self._close_when_done = True
+        try:
+            self._parse_header_and_devices(self.fh)
+            self._events_start = self.fh.tell()
+            if not self.stream:
+                self._events = [e for e in self._iter_events()]
+        finally:
+            if self._close_when_done:
+                self.close()
+
+    def close(self):
+        if self.fh is not None:
+            self.fh.close()
+            self.fh = None
+            self._events_start = None
+
+    def _parse_header_and_devices(self, fh):
+        magic, version = read_struct(fh, header_one_struct)
+        if magic != 'REVENT':
+            msg = '{} does not appear to be an revent recording'
+            raise ValueError(msg.format(self.filepath))
+        self.version = version
+
+        if self.version == 2:
+            self.mode, = read_struct(fh, header_two_struct)
+            if self.mode == GENERAL_MODE:
+                self._read_devices(fh)
+            elif self.mode == GAMEPAD_MODE:
+                self._read_gamepad_info(fh)
+            else:
+                raise ValueError('Unexpected recording mode: {}'.format(self.mode))
+            self.num_events, = read_struct(fh, u64_struct)
+        elif 2 > self.version >= 0:
+            self.mode = GENERAL_MODE
+            self._read_devices(fh)
+        else:
+            raise ValueError('Invalid recording version: {}'.format(self.version))
+
+    def _read_devices(self, fh):
+        num_devices, = read_struct(fh, u32_struct)
+        for _ in xrange(num_devices):
+            self.device_paths.append(read_string(fh))
+
+    def _read_gamepad_info(self, fh):
+        self.gamepad_device = UinputDeviceInfo(fh)
+        self.device_paths.append('[GAMEPAD]')
+
+    def _iter_events(self):
+        if self.fh is None:
+            msg = 'Attempting to iterate over events of a closed recording'
+            raise RuntimeError(msg)
+        self.fh.seek(self._events_start)
+        if self.version >= 2:
+            for _ in xrange(self.num_events):
+                yield ReventEvent(self.fh)
+        else:
+            file_size = os.path.getsize(self.filepath)
+            while self.fh.tell() < file_size:
+                yield ReventEvent(self.fh, legacy=True)
+
+    def __iter__(self):
+        for event in self.events:
+            yield event
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *args):
+        self.close()
+
+    def __del__(self):
+        self.close()