/* Copyright 2012-2017 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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. // Should that be the case, also make sure to update the format description // in doc/source/revent.rst and the Python parser in wa/utils/revent.py. uint16_t FORMAT_VERSION = 3; 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; struct timeval start_time; struct timeval end_time; 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_timestamps(revent_recording_t *recording) { uint64_t i; struct timeval time_zero, time_delta; time_zero.tv_sec = recording->start_time.tv_sec; time_zero.tv_usec = recording->start_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; } timersub(&recording->end_time, &time_zero, &time_delta); recording->end_time.tv_sec = time_delta.tv_sec; recording->end_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 read_record_timestamps(FILE *fin, revent_recording_t *recording) { int ret; ret = fread(&recording->start_time.tv_sec, sizeof(uint64_t), 1, fin); if (ret < 1) return errno; ret = fread(&recording->start_time.tv_usec, sizeof(uint64_t), 1, fin); if (ret < 1) return errno; ret = fread(&recording->end_time.tv_sec, sizeof(uint64_t), 1, fin); if (ret < 1) return errno; ret = fread(&recording->end_time.tv_usec, sizeof(uint64_t), 1, fin); if (ret < 1) return errno; return 0; } 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; int ret; int clk_id = CLOCK_MONOTONIC; 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]; if (ret = ioctl(fds[num], EVIOCSCLOCKID, &clk_id)) { dprintf("Failed to set monotonic clock for %s.\n", paths[num]); return -ret; } 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; } int clk_id = CLOCK_MONOTONIC; if (ret = ioctl(devices->fds[0], EVIOCSCLOCKID, &clk_id)) { dprintf("Could not set monotonic clock for the gamepad.\n"); return -ret; } 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. } if (recording->num_events) { 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"); if (recording->desc.version > 2) { ret = read_record_timestamps(fin, recording); if (ret) die("Could not read recroding timestamps."); } recording->events = malloc(sizeof(replay_event_t) * recording->num_events); if (recording->events == NULL) die("Not enough memory to allocate replay buffer"); // start/end times tracking for recording as a whole was added in version 3 // of recording format; for earlier recordings, use timestamps of the first and // last events. read_replay_event(fin, &recording->events[0]); if (recording->desc.version <= 2) { recording->start_time.tv_sec = recording->events[0].event.time.tv_sec; recording->start_time.tv_usec = recording->events[0].event.time.tv_usec; } for(i=1; i < recording->num_events; i++) { read_replay_event(fin, &recording->events[i]); } if (recording->desc.version <= 2) { recording->end_time.tv_sec = recording->events[i].event.time.tv_sec; recording->end_time.tv_usec = recording->events[i].event.time.tv_usec; } } 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; struct timespec start_time, end_time; 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. Reserving space for five uint64_t's -- the number of events and // end time stamps. uint64_t event_count = 0; long size_pos = ftell(fout); ret = fwrite(&event_count, sizeof(uint64_t), 5, 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); clock_gettime(CLOCK_MONOTONIC, &start_time); 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++; } } } clock_gettime(CLOCK_MONOTONIC, &end_time); dprintf("Writing event count...\n"); 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)); dprintf("Writing recording timestamps...\n"); uint64_t secs, usecs; secs = start_time.tv_sec; fwrite(&secs, sizeof(uint64_t), 1, fout); usecs = start_time.tv_nsec / 1000; fwrite(&usecs, sizeof(uint64_t), 1, fout); secs = end_time.tv_sec; fwrite(&secs, sizeof(uint64_t), 1, fout); usecs = end_time.tv_nsec / 1000; ret = fwrite(&usecs, sizeof(uint64_t), 1, fout); if (ret < 1) die("Could not write recording timestamps: %s\n", strerror(errno)); fclose(fout); dprintf("Recording complete.\n"); 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("start time: %ld.%06ld \n", recording.start_time.tv_sec, recording.start_time.tv_usec); printf("end time: %ld.%06ld \n", recording.end_time.tv_sec, recording.end_time.tv_usec); 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 timestamps\n"); adjust_timestamps(&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(!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++; if (i >= recording.num_events) { break; } idx = recording.events[i].dev_idx; ev = recording.events[i].event; } last_event_delta = ev.time; } timeradd(&start_time, &recording.end_time, &desired_time); gettimeofday(&now, NULL); 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 recording end time %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); } else { dprintf("now %u.%u recording end time %u.%u; no need to sleep\n", (unsigned int)now.tv_sec, (unsigned int)now.tv_usec, (unsigned int)desired_time.tv_sec, (unsigned int)desired_time.tv_usec); } dprintf("Playback complete\n"); 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; }