2015-03-10 13:09:31 +00:00
|
|
|
/* 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 <stdio.h>
|
2015-03-23 18:55:53 +00:00
|
|
|
#include <stdint.h>
|
2015-03-10 13:09:31 +00:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <linux/input.h>
|
|
|
|
#include <sys/stat.h>
|
2016-01-21 17:11:53 +00:00
|
|
|
#include <signal.h>
|
|
|
|
#include <ctype.h>
|
2015-03-10 13:09:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
#define die(args...) do { \
|
|
|
|
fprintf(stderr, "ERROR: "); \
|
|
|
|
fprintf(stderr, args); \
|
|
|
|
exit(EXIT_FAILURE); \
|
|
|
|
} while(0)
|
|
|
|
|
|
|
|
#define dprintf(args...) if (verbose) printf(args)
|
|
|
|
|
|
|
|
|
|
|
|
#define INPDEV_MAX_DEVICES 16
|
|
|
|
#define INPDEV_MAX_PATH 30
|
2016-07-19 11:27:52 +01:00
|
|
|
const char magic[] = "REVENT";
|
2015-03-10 13:09:31 +00:00
|
|
|
|
2016-07-19 11:27:52 +01:00
|
|
|
//This should be incremented if any changes are made to the file format
|
|
|
|
uint16_t file_version = 1;
|
2015-03-10 13:09:31 +00:00
|
|
|
|
|
|
|
int strlcpy(char *dest, char *source, size_t size)
|
|
|
|
{
|
|
|
|
strncpy(dest, source, size-1);
|
|
|
|
dest[size-1] = '\0';
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
FALSE=0,
|
|
|
|
TRUE
|
|
|
|
} bool_t;
|
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
RECORD=0,
|
|
|
|
REPLAY,
|
|
|
|
DUMP,
|
|
|
|
INFO,
|
|
|
|
INVALID
|
|
|
|
} revent_mode_t;
|
|
|
|
|
|
|
|
typedef struct {
|
|
|
|
revent_mode_t mode;
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t record_time;
|
|
|
|
int32_t device_number;
|
2015-03-10 13:09:31 +00:00
|
|
|
char *file;
|
|
|
|
} revent_args_t;
|
|
|
|
|
|
|
|
typedef struct {
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t id_pathc; /* Count of total paths so far. */
|
2015-03-10 13:09:31 +00:00
|
|
|
char id_pathv[INPDEV_MAX_DEVICES][INPDEV_MAX_PATH]; /* List of paths matching pattern. */
|
|
|
|
} inpdev_t;
|
|
|
|
|
|
|
|
typedef struct {
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t dev_idx;
|
|
|
|
int32_t _padding;
|
2015-03-10 13:09:31 +00:00
|
|
|
struct input_event event;
|
|
|
|
} replay_event_t;
|
|
|
|
|
|
|
|
typedef struct {
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t num_fds;
|
|
|
|
int32_t num_events;
|
2015-03-10 13:09:31 +00:00
|
|
|
int *fds;
|
|
|
|
replay_event_t *events;
|
|
|
|
} replay_buffer_t;
|
|
|
|
|
|
|
|
|
|
|
|
bool_t verbose = FALSE;
|
2016-01-21 17:11:53 +00:00
|
|
|
bool_t wait_for_stdin = TRUE;
|
2015-03-10 13:09:31 +00:00
|
|
|
|
|
|
|
bool_t is_numeric(char *string)
|
|
|
|
{
|
|
|
|
int len = strlen(string);
|
|
|
|
|
|
|
|
int i = 0;
|
2016-01-21 17:11:53 +00:00
|
|
|
while(i < len)
|
2015-03-10 13:09:31 +00:00
|
|
|
{
|
|
|
|
if(!isdigit(string[i]))
|
|
|
|
return FALSE;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2016-07-18 17:30:15 +01:00
|
|
|
// Has to be done this explicitly to maintain compatibility between
|
|
|
|
// 32-bit and 64-bit devices
|
|
|
|
int read_replay_event(int fdin, replay_event_t* ev)
|
|
|
|
{
|
|
|
|
size_t rb;
|
|
|
|
|
|
|
|
rb = read(fdin, &(ev->dev_idx), sizeof(int32_t));
|
|
|
|
if (rb < (int)sizeof(int32_t)) return -1;
|
|
|
|
|
|
|
|
rb = read(fdin, &(ev->_padding), sizeof(int32_t));
|
|
|
|
if (rb < (int)sizeof(int32_t)) return -1;
|
|
|
|
|
|
|
|
struct timeval time;
|
|
|
|
uint64_t temp_time;
|
|
|
|
rb = read(fdin, &temp_time, sizeof(uint64_t));
|
|
|
|
if (rb < (int)sizeof(uint64_t)) return -1;
|
|
|
|
time.tv_sec = (time_t)temp_time;
|
|
|
|
|
|
|
|
rb = read(fdin, &temp_time, sizeof(uint64_t));
|
|
|
|
if (rb < (int)sizeof(uint64_t)) return -1;
|
|
|
|
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 -1;
|
|
|
|
|
|
|
|
rb = read(fdin, &(ev->event.code), sizeof(uint16_t));
|
|
|
|
if (rb < (int)sizeof(uint16_t)) return -1;
|
|
|
|
|
|
|
|
rb = read(fdin, &(ev->event.value), sizeof(int32_t));
|
|
|
|
if (rb < (int)sizeof(int32_t)) return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void write_input_event(FILE * fdout, struct input_event* ev)
|
|
|
|
{
|
|
|
|
uint64_t time;
|
|
|
|
time = (uint64_t)ev->time.tv_sec;
|
|
|
|
fwrite(&time, sizeof(uint64_t), 1, fdout);
|
|
|
|
time = (uint64_t)ev->time.tv_usec;
|
|
|
|
fwrite(&time, sizeof(uint64_t), 1, fdout);
|
|
|
|
fwrite(&(ev->type), sizeof(uint16_t), 1, fdout);
|
|
|
|
fwrite(&(ev->code), sizeof(uint16_t), 1, fdout);
|
|
|
|
fwrite(&(ev->value), sizeof(int32_t), 1, fdout);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-03-10 13:09:31 +00:00
|
|
|
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\n", filename, strerror(errno));
|
2016-01-21 17:11:53 +00:00
|
|
|
}
|
2015-03-10 13:09:31 +00:00
|
|
|
|
|
|
|
int inpdev_init(inpdev_t **inpdev, int devid)
|
|
|
|
{
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t i;
|
2015-03-10 13:09:31 +00:00
|
|
|
int fd;
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t num_devices;
|
2015-03-10 13:09:31 +00:00
|
|
|
|
|
|
|
*inpdev = malloc(sizeof(inpdev_t));
|
|
|
|
(*inpdev)->id_pathc = 0;
|
|
|
|
|
|
|
|
if (devid == -1) {
|
|
|
|
// device id was not specified so we want to record from all available input devices.
|
|
|
|
for(i = 0; i < INPDEV_MAX_DEVICES; ++i)
|
|
|
|
{
|
|
|
|
sprintf((*inpdev)->id_pathv[(*inpdev)->id_pathc], "/dev/input/event%d", i);
|
|
|
|
fd = open((*inpdev)->id_pathv[(*inpdev)->id_pathc], O_RDONLY);
|
|
|
|
if(fd > 0)
|
|
|
|
{
|
|
|
|
close(fd);
|
|
|
|
dprintf("opened %s\n", (*inpdev)->id_pathv[(*inpdev)->id_pathc]);
|
|
|
|
(*inpdev)->id_pathc++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dprintf("could not open %s\n", (*inpdev)->id_pathv[(*inpdev)->id_pathc]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// device id was specified so record just that device.
|
|
|
|
sprintf((*inpdev)->id_pathv[0], "/dev/input/event%d", devid);
|
|
|
|
fd = open((*inpdev)->id_pathv[0], O_RDONLY);
|
|
|
|
if(fd > 0)
|
|
|
|
{
|
|
|
|
close(fd);
|
|
|
|
dprintf("opened %s\n", (*inpdev)->id_pathv[0]);
|
|
|
|
(*inpdev)->id_pathc++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
die("could not open %s\n", (*inpdev)->id_pathv[0]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int inpdev_close(inpdev_t *inpdev)
|
|
|
|
{
|
|
|
|
free(inpdev);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void printDevProperties(const char* aDev)
|
|
|
|
{
|
|
|
|
int fd = -1;
|
|
|
|
char name[256]= "Unknown";
|
|
|
|
if ((fd = open(aDev, O_RDONLY)) < 0)
|
|
|
|
die("could not open %s\n", aDev);
|
|
|
|
|
|
|
|
if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0)
|
|
|
|
die("evdev ioctl failed on %s\n", aDev);
|
|
|
|
|
|
|
|
printf("The device on %s says its name is %s\n",
|
|
|
|
aDev, name);
|
|
|
|
close(fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
void dump(const char *logfile)
|
|
|
|
{
|
|
|
|
int fdin = open(logfile, O_RDONLY);
|
|
|
|
if (fdin < 0) die("Could not open eventlog %s\n", logfile);
|
|
|
|
|
2016-07-19 11:27:52 +01:00
|
|
|
int32_t len;
|
|
|
|
int32_t i;
|
|
|
|
char buf[INPDEV_MAX_PATH];
|
|
|
|
|
|
|
|
//Read magic
|
|
|
|
len = strlen(magic);
|
|
|
|
size_t rb = read(fdin, &buf[0], len);
|
2016-07-21 15:14:11 +01:00
|
|
|
buf[len] = '\0';
|
2016-07-19 11:27:52 +01:00
|
|
|
if (rb != len) die("problems reading eventlog\n");
|
|
|
|
if(strcmp(magic, buf) != 0)
|
|
|
|
die("File is not an revent recording, are you using an old recording?");
|
|
|
|
|
|
|
|
//Read file format version
|
|
|
|
uint16_t version;
|
|
|
|
rb = read(fdin, &version, sizeof(version));
|
|
|
|
if (rb != sizeof(version)) die("problems reading eventlog\n");
|
|
|
|
printf("File format version: %i\n", version);
|
|
|
|
|
2016-07-18 17:30:15 +01:00
|
|
|
int32_t nfds;
|
2016-07-19 11:27:52 +01:00
|
|
|
rb = read(fdin, &nfds, sizeof(nfds));
|
2015-03-10 13:09:31 +00:00
|
|
|
if (rb != sizeof(nfds)) die("problems reading eventlog\n");
|
|
|
|
int *fds = malloc(sizeof(int)*nfds);
|
|
|
|
if (!fds) die("out of memory\n");
|
|
|
|
|
|
|
|
inpdev_t *inpdev = malloc(sizeof(inpdev_t));
|
2016-07-18 17:30:15 +01:00
|
|
|
inpdev->id_pathc = nfds;
|
2015-03-10 13:09:31 +00:00
|
|
|
for (i=0; i<nfds; i++) {
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
rb = read(fdin, &len, sizeof(len));
|
|
|
|
if (rb != sizeof(len)) die("problems reading eventlog\n");
|
2016-07-18 17:30:15 +01:00
|
|
|
if (len >= INPDEV_MAX_PATH) die("path length too long, file corrupt");
|
2015-03-10 13:09:31 +00:00
|
|
|
rb = read(fdin, &buf[0], len);
|
|
|
|
if (rb != len) die("problems reading eventlog\n");
|
|
|
|
strlcpy(inpdev->id_pathv[inpdev->id_pathc], buf, INPDEV_MAX_PATH);
|
|
|
|
}
|
|
|
|
|
2016-07-18 17:30:15 +01:00
|
|
|
replay_event_t rep_ev;
|
2015-03-10 13:09:31 +00:00
|
|
|
struct input_event ev;
|
|
|
|
int count = 0;
|
|
|
|
while(1) {
|
2016-07-18 17:30:15 +01:00
|
|
|
if (read_replay_event(fdin, &rep_ev) == -1)
|
|
|
|
break;
|
|
|
|
ev = rep_ev.event;
|
2015-03-10 13:09:31 +00:00
|
|
|
|
|
|
|
printf("%10u.%-6u %30s type %2d code %3d value %4d\n",
|
|
|
|
(unsigned int)ev.time.tv_sec, (unsigned int)ev.time.tv_usec,
|
2016-07-18 17:30:15 +01:00
|
|
|
inpdev->id_pathv[rep_ev.dev_idx], ev.type, ev.code, ev.value);
|
2015-03-10 13:09:31 +00:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("\nTotal: %d events\n", count);
|
|
|
|
close(fdin);
|
|
|
|
free(inpdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
int replay_buffer_init(replay_buffer_t **buffer, const char *logfile)
|
|
|
|
{
|
|
|
|
*buffer = malloc(sizeof(replay_buffer_t));
|
|
|
|
replay_buffer_t *buff = *buffer;
|
|
|
|
off_t fsize = get_file_size(logfile);
|
|
|
|
buff->events = (replay_event_t *)malloc((size_t)fsize);
|
|
|
|
if (!buff->events)
|
|
|
|
die("out of memory\n");
|
|
|
|
|
|
|
|
int fdin = open(logfile, O_RDONLY);
|
2016-01-21 17:11:53 +00:00
|
|
|
if (fdin < 0)
|
2015-03-10 13:09:31 +00:00
|
|
|
die("Could not open eventlog %s\n", logfile);
|
|
|
|
|
2016-07-19 11:27:52 +01:00
|
|
|
int32_t len, i;
|
|
|
|
|
|
|
|
//Read magic
|
|
|
|
char buf[7];
|
|
|
|
len = strlen(magic);
|
|
|
|
size_t rb = read(fdin, &buf[0], len);
|
2016-07-21 15:14:11 +01:00
|
|
|
buf[len] = '\0';
|
2016-07-19 11:27:52 +01:00
|
|
|
if (rb != len) die("problems reading eventlog\n");
|
|
|
|
if(strcmp(magic, buf) != 0)
|
|
|
|
die("File is not an revent recording, are you using an old recording?");
|
|
|
|
|
|
|
|
//Read file format version
|
|
|
|
uint16_t version;
|
|
|
|
rb = read(fdin, &version, sizeof(version));
|
|
|
|
if (rb != sizeof(version)) die("problems reading eventlog\n");
|
|
|
|
|
|
|
|
rb = read(fdin, &(buff->num_fds), sizeof(buff->num_fds));
|
2016-01-21 17:11:53 +00:00
|
|
|
if (rb!=sizeof(buff->num_fds))
|
2015-03-10 13:09:31 +00:00
|
|
|
die("problems reading eventlog\n");
|
|
|
|
|
|
|
|
buff->fds = malloc(sizeof(int) * buff->num_fds);
|
2016-01-21 17:11:53 +00:00
|
|
|
if (!buff->fds)
|
2015-03-10 13:09:31 +00:00
|
|
|
die("out of memory\n");
|
|
|
|
|
2016-07-19 11:27:52 +01:00
|
|
|
|
2016-07-18 17:30:15 +01:00
|
|
|
char path_buff[INPDEV_MAX_PATH];
|
2015-03-10 13:09:31 +00:00
|
|
|
for (i = 0; i < buff->num_fds; i++) {
|
|
|
|
memset(path_buff, 0, sizeof(path_buff));
|
|
|
|
rb = read(fdin, &len, sizeof(len));
|
2016-01-21 17:11:53 +00:00
|
|
|
if (rb!=sizeof(len))
|
2015-03-10 13:09:31 +00:00
|
|
|
die("problems reading eventlog\n");
|
2016-07-18 17:30:15 +01:00
|
|
|
if (len >= INPDEV_MAX_PATH)
|
|
|
|
die("path length too long, file corrupt");
|
2015-03-10 13:09:31 +00:00
|
|
|
rb = read(fdin, &path_buff[0], len);
|
2016-01-21 17:11:53 +00:00
|
|
|
if (rb != len)
|
2015-03-10 13:09:31 +00:00
|
|
|
die("problems reading eventlog\n");
|
|
|
|
|
|
|
|
buff->fds[i] = open(path_buff, O_WRONLY | O_NDELAY);
|
|
|
|
if (buff->fds[i] < 0)
|
|
|
|
die("could not open device file %s\n", path_buff);
|
|
|
|
}
|
|
|
|
|
|
|
|
struct timeval start_time;
|
|
|
|
replay_event_t rep_ev;
|
2015-03-23 18:55:53 +00:00
|
|
|
i = 0;
|
2015-03-10 13:09:31 +00:00
|
|
|
while(1) {
|
2016-07-18 17:30:15 +01:00
|
|
|
if (read_replay_event(fdin, &rep_ev) == -1)
|
2015-03-10 13:09:31 +00:00
|
|
|
break;
|
|
|
|
|
2015-03-23 18:55:53 +00:00
|
|
|
if (i == 0) {
|
2015-03-10 13:09:31 +00:00
|
|
|
start_time = rep_ev.event.time;
|
|
|
|
}
|
|
|
|
timersub(&(rep_ev.event.time), &start_time, &(rep_ev.event.time));
|
2015-03-23 18:55:53 +00:00
|
|
|
memcpy(&(buff->events[i]), &rep_ev, sizeof(rep_ev));
|
|
|
|
i++;
|
2015-03-10 13:09:31 +00:00
|
|
|
}
|
2015-03-23 18:55:53 +00:00
|
|
|
buff->num_events = i - 1;
|
2015-03-10 13:09:31 +00:00
|
|
|
close(fdin);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int replay_buffer_close(replay_buffer_t *buff)
|
|
|
|
{
|
|
|
|
free(buff->fds);
|
|
|
|
free(buff->events);
|
|
|
|
free(buff);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int replay_buffer_play(replay_buffer_t *buff)
|
|
|
|
{
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t i = 0, rb;
|
2015-03-10 13:09:31 +00:00
|
|
|
struct timeval start_time, now, desired_time, last_event_delta, delta;
|
|
|
|
memset(&last_event_delta, 0, sizeof(struct timeval));
|
|
|
|
gettimeofday(&start_time, NULL);
|
|
|
|
|
|
|
|
while (i < buff->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);
|
|
|
|
}
|
|
|
|
|
2015-03-23 18:55:53 +00:00
|
|
|
int32_t idx = (buff->events[i]).dev_idx;
|
2015-03-10 13:09:31 +00:00
|
|
|
struct input_event ev = (buff->events[i]).event;
|
|
|
|
while((i < buff->num_events) && !timercmp(&ev.time, &last_event_delta, !=)) {
|
|
|
|
rb = write(buff->fds[idx], &ev, sizeof(ev));
|
2016-01-21 17:11:53 +00:00
|
|
|
if (rb!=sizeof(ev))
|
2015-03-10 13:09:31 +00:00
|
|
|
die("problems writing\n");
|
|
|
|
dprintf("replayed event: type %d code %d value %d\n", ev.type, ev.code, ev.value);
|
|
|
|
|
|
|
|
i++;
|
|
|
|
idx = (buff->events[i]).dev_idx;
|
|
|
|
ev = (buff->events[i]).event;
|
|
|
|
}
|
|
|
|
last_event_delta = ev.time;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void replay(const char *logfile)
|
|
|
|
{
|
|
|
|
replay_buffer_t *replay_buffer;
|
|
|
|
replay_buffer_init(&replay_buffer, logfile);
|
|
|
|
replay_buffer_play(replay_buffer);
|
|
|
|
replay_buffer_close(replay_buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
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 specifed, recording will continue until\n"
|
|
|
|
" return key is pressed.\n"
|
|
|
|
" -d DEVICE the number of the input device form which\n"
|
|
|
|
" events will be recoreded. If not specified, \n"
|
|
|
|
" all available inputs will be used.\n"
|
2016-07-18 17:30:15 +01:00
|
|
|
" -s Recording will not be stopped if there is \n"
|
|
|
|
" input on STDIN.\n"
|
2015-03-10 13:09:31 +00:00
|
|
|
"\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->mode = INVALID;
|
|
|
|
revent_args->record_time = INT_MAX;
|
|
|
|
revent_args->device_number = -1;
|
|
|
|
revent_args->file = NULL;
|
|
|
|
|
|
|
|
int opt;
|
2016-01-21 17:11:53 +00:00
|
|
|
while ((opt = getopt(argc, argv, "ht:d:vs")) != -1)
|
2015-03-10 13:09:31 +00:00
|
|
|
{
|
|
|
|
switch (opt) {
|
|
|
|
case 'h':
|
|
|
|
usage();
|
|
|
|
exit(0);
|
|
|
|
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.\n", 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.\n", optarg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
verbose = TRUE;
|
|
|
|
break;
|
2016-01-21 17:11:53 +00:00
|
|
|
case 's':
|
|
|
|
wait_for_stdin = FALSE;
|
|
|
|
break;
|
|
|
|
|
2015-03-10 13:09:31 +00:00
|
|
|
default:
|
|
|
|
die("Unexpected option: %c", opt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int next_arg = optind;
|
|
|
|
if (next_arg == argc) {
|
|
|
|
usage();
|
|
|
|
die("Must specify a command.\n");
|
|
|
|
}
|
2016-01-21 17:11:53 +00:00
|
|
|
if (!strcmp(argv[next_arg], "record"))
|
2015-03-10 13:09:31 +00:00
|
|
|
revent_args->mode = RECORD;
|
2016-01-21 17:11:53 +00:00
|
|
|
else if (!strcmp(argv[next_arg], "replay"))
|
2015-03-10 13:09:31 +00:00
|
|
|
revent_args->mode = REPLAY;
|
2016-01-21 17:11:53 +00:00
|
|
|
else if (!strcmp(argv[next_arg], "dump"))
|
2015-03-10 13:09:31 +00:00
|
|
|
revent_args->mode = DUMP;
|
2016-01-21 17:11:53 +00:00
|
|
|
else if (!strcmp(argv[next_arg], "info"))
|
2015-03-10 13:09:31 +00:00
|
|
|
revent_args->mode = INFO;
|
|
|
|
else {
|
|
|
|
usage();
|
|
|
|
die("Unknown command -- %s\n", 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).\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((revent_args->mode != RECORD) && (revent_args->record_time != INT_MAX)) {
|
|
|
|
die("-t parameter is only valid for \"record\" command.\n");
|
|
|
|
}
|
|
|
|
if ((revent_args->mode != RECORD) && (revent_args->device_number != -1)) {
|
|
|
|
die("-d parameter is only valid for \"record\" command.\n");
|
|
|
|
}
|
|
|
|
if ((revent_args->mode == INFO) && (revent_args->file != NULL)) {
|
|
|
|
die("File path cannot be specified for \"info\" command.\n");
|
|
|
|
}
|
|
|
|
if (((revent_args->mode == RECORD) || (revent_args->mode == REPLAY)) && (revent_args->file == NULL)) {
|
|
|
|
die("Must specify a file for recording/replaying (use -h for help).\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int revent_args_close(revent_args_t *rargs)
|
|
|
|
{
|
|
|
|
free(rargs);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-01-21 17:11:53 +00:00
|
|
|
int* fds = NULL;
|
|
|
|
FILE* fdout = NULL;
|
|
|
|
revent_args_t *rargs = NULL;
|
|
|
|
inpdev_t *inpdev = NULL;
|
|
|
|
int count;
|
|
|
|
|
|
|
|
void term_handler(int signum)
|
|
|
|
{
|
2016-07-18 17:30:15 +01:00
|
|
|
(void)signum;
|
2016-01-21 17:11:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void record(inpdev_t *inpdev, int delay, const char *logfile)
|
|
|
|
{
|
|
|
|
fd_set readfds;
|
|
|
|
struct input_event ev;
|
|
|
|
int32_t i;
|
|
|
|
int32_t _padding = 0xdeadbeef;
|
|
|
|
int32_t maxfd = 0;
|
|
|
|
int32_t keydev=0;
|
|
|
|
|
|
|
|
//signal handler
|
|
|
|
struct sigaction action;
|
|
|
|
memset(&action, 0, sizeof(struct sigaction));
|
|
|
|
action.sa_handler = term_handler;
|
|
|
|
sigaction(SIGTERM, &action, NULL);
|
|
|
|
|
|
|
|
fds = malloc(sizeof(int)*inpdev->id_pathc);
|
|
|
|
if (!fds) die("out of memory\n");
|
|
|
|
|
|
|
|
fdout = fopen(logfile, "wb");
|
|
|
|
if (!fdout) die("Could not open eventlog %s\n", logfile);
|
|
|
|
|
2016-07-19 11:27:52 +01:00
|
|
|
//Write magic & file format version
|
|
|
|
fwrite(&magic, strlen(magic), 1, fdout);
|
|
|
|
fwrite(&file_version, sizeof(file_version), 1, fdout);
|
|
|
|
|
|
|
|
//Write device paths
|
2016-01-21 17:11:53 +00:00
|
|
|
fwrite(&inpdev->id_pathc, sizeof(inpdev->id_pathc), 1, fdout);
|
|
|
|
for (i=0; i<inpdev->id_pathc; i++) {
|
|
|
|
int32_t len = strlen(inpdev->id_pathv[i]);
|
|
|
|
fwrite(&len, sizeof(len), 1, fdout);
|
|
|
|
fwrite(inpdev->id_pathv[i], len, 1, fdout);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i=0; i < inpdev->id_pathc; i++)
|
|
|
|
{
|
|
|
|
fds[i] = open(inpdev->id_pathv[i], O_RDONLY);
|
|
|
|
if (fds[i]>maxfd) maxfd = fds[i];
|
|
|
|
dprintf("opened %s with %d\n", inpdev->id_pathv[i], fds[i]);
|
2016-07-19 11:27:52 +01:00
|
|
|
if (fds[i]<0) die("could not open %s\n", inpdev->id_pathv[i]);
|
2016-01-21 17:11:53 +00:00
|
|
|
}
|
|
|
|
|
2016-07-18 17:30:15 +01:00
|
|
|
//Block SIGTERM
|
|
|
|
sigset_t sigset, oldset;
|
|
|
|
sigemptyset(&sigset);
|
|
|
|
sigaddset(&sigset, SIGTERM);
|
|
|
|
sigprocmask(SIG_BLOCK, &sigset, &oldset);
|
|
|
|
|
2016-01-21 17:11:53 +00:00
|
|
|
count = 0;
|
2016-07-18 17:30:15 +01:00
|
|
|
struct timespec tout;
|
2016-01-21 17:11:53 +00:00
|
|
|
while(1)
|
|
|
|
{
|
|
|
|
FD_ZERO(&readfds);
|
|
|
|
if (wait_for_stdin)
|
|
|
|
{
|
|
|
|
FD_SET(STDIN_FILENO, &readfds);
|
|
|
|
}
|
|
|
|
for (i=0; i < inpdev->id_pathc; i++)
|
|
|
|
FD_SET(fds[i], &readfds);
|
|
|
|
/* wait for input */
|
|
|
|
tout.tv_sec = delay;
|
2016-07-18 17:30:15 +01:00
|
|
|
tout.tv_nsec = 0;
|
|
|
|
int32_t r = pselect(maxfd+1, &readfds, NULL, NULL, &tout, &oldset);
|
|
|
|
if (errno == EINTR)
|
|
|
|
break;
|
2016-01-21 17:11:53 +00:00
|
|
|
/* dprintf("got %d (err %d)\n", r, errno); */
|
2016-07-18 17:30:15 +01:00
|
|
|
if (!r)
|
|
|
|
break;
|
2016-01-21 17:11:53 +00:00
|
|
|
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(&ev, 0, sizeof(ev));
|
|
|
|
ev.type = EV_KEY;
|
|
|
|
ev.code = KEY_ENTER;
|
|
|
|
ev.value = 0;
|
|
|
|
gettimeofday(&ev.time, NULL);
|
|
|
|
fwrite(&keydev, sizeof(keydev), 1, fdout);
|
|
|
|
fwrite(&_padding, sizeof(_padding), 1, fdout);
|
2016-07-18 17:30:15 +01:00
|
|
|
write_input_event(fdout, &ev);
|
2016-01-21 17:11:53 +00:00
|
|
|
memset(&ev, 0, sizeof(ev)); // SYN
|
|
|
|
gettimeofday(&ev.time, NULL);
|
|
|
|
fwrite(&keydev, sizeof(keydev), 1, fdout);
|
|
|
|
fwrite(&_padding, sizeof(_padding), 1, fdout);
|
2016-07-18 17:30:15 +01:00
|
|
|
write_input_event(fdout, &ev);
|
2016-01-21 17:11:53 +00:00
|
|
|
dprintf("added fake return exiting...\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i=0; i < inpdev->id_pathc; i++)
|
|
|
|
{
|
|
|
|
if (FD_ISSET(fds[i], &readfds))
|
|
|
|
{
|
|
|
|
dprintf("Got event from %s\n", inpdev->id_pathv[i]);
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
|
|
size_t rb = read(fds[i], (void*) &ev, sizeof(ev));
|
|
|
|
dprintf("%d event: type %d code %d value %d\n",
|
|
|
|
(unsigned int)rb, ev.type, ev.code, ev.value);
|
|
|
|
if (ev.type == EV_KEY && ev.code == KEY_ENTER && ev.value == 1)
|
|
|
|
keydev = i;
|
|
|
|
fwrite(&i, sizeof(i), 1, fdout);
|
|
|
|
fwrite(&_padding, sizeof(_padding), 1, fdout);
|
2016-07-18 17:30:15 +01:00
|
|
|
write_input_event(fdout, &ev);
|
2016-01-21 17:11:53 +00:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i=0; i < inpdev->id_pathc; i++)
|
|
|
|
{
|
|
|
|
close(fds[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose(fdout);
|
|
|
|
free(fds);
|
|
|
|
dprintf("Recorded %d events\n", count);
|
|
|
|
}
|
|
|
|
|
2015-03-10 13:09:31 +00:00
|
|
|
int main(int argc, char** argv)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
char *logfile = NULL;
|
|
|
|
|
|
|
|
revent_args_init(&rargs, argc, argv);
|
|
|
|
|
|
|
|
inpdev_init(&inpdev, rargs->device_number);
|
|
|
|
|
|
|
|
switch(rargs->mode) {
|
|
|
|
case RECORD:
|
|
|
|
record(inpdev, rargs->record_time, rargs->file);
|
|
|
|
break;
|
|
|
|
case REPLAY:
|
|
|
|
replay(rargs->file);
|
|
|
|
break;
|
|
|
|
case DUMP:
|
|
|
|
dump(rargs->file);
|
|
|
|
break;
|
|
|
|
case INFO:
|
|
|
|
for (i = 0; i < inpdev->id_pathc; i++) {
|
|
|
|
printDevProperties(inpdev->id_pathv[i]);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
inpdev_close(inpdev);
|
|
|
|
revent_args_close(rargs);
|
|
|
|
return 0;
|
|
|
|
}
|