From 3572cbe4f580a3741c13372fd5fb500b079c4151 Mon Sep 17 00:00:00 2001 From: ReimuNotMoe <34613827+ReimuNotMoe@users.noreply.github.com> Date: Tue, 1 Oct 2019 21:02:20 +0800 Subject: [PATCH] Finally the recorder tool is here --- CMakeLists.txt | 4 +- Tools/Recorder/Recorder.cpp | 244 ++++++++++++++++++++++++++++++++---- Tools/Recorder/Recorder.hpp | 14 ++- 3 files changed, 233 insertions(+), 29 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7bba1e..4f861cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,11 +57,11 @@ add_executable(ydotoold ${SOURCE_FILES_DAEMON}) target_link_libraries(ydotoold ydotool_library dl pthread boost_program_options uInputPlus evdevPlus) add_executable(ydotool_client ${SOURCE_FILES_CLIENT}) -target_link_libraries(ydotool_client ydotool_library boost_program_options uInputPlus evdevPlus) +target_link_libraries(ydotool_client ydotool_library boost_program_options pthread uInputPlus evdevPlus) set_target_properties(ydotool_client PROPERTIES OUTPUT_NAME ydotool) add_executable(ydotool_client_static ${SOURCE_FILES_CLIENT}) -target_link_libraries(ydotool_client_static ydotool_library_static boost_program_options uInputPlus evdevPlus -static) +target_link_libraries(ydotool_client_static ydotool_library_static boost_program_options pthread uInputPlus evdevPlus -static) set_target_properties(ydotool_client_static PROPERTIES OUTPUT_NAME ydotool_static) #add_library(mousemove SHARED Tools/MouseMove/MouseMove.hpp Tools/MouseMove/MouseMove.cpp) diff --git a/Tools/Recorder/Recorder.cpp b/Tools/Recorder/Recorder.cpp index f58bdda..c60ad9f 100644 --- a/Tools/Recorder/Recorder.cpp +++ b/Tools/Recorder/Recorder.cpp @@ -4,36 +4,78 @@ #include "Recorder.hpp" -using namespace ydotool::Tools; +using namespace ydotool; +using namespace Tools; const char ydotool_tool_name[] = "recorder"; static void ShowHelp(const char *argv_0){ - std::cerr << "Usage: " << argv_0 << " [devices]\n" + std::cerr << "Usage: " << argv_0 << " [--delay ] [--record [devices]] [--replay ]\n" << " --help Show this help.\n" - << " devices Devices to record from. Default is all." << std::endl; + << " --record \n" + << " devices Devices to record from. Default is all, including non-keyboard devices.\n" + << " --replay \n" + << " --display \n" + << " --delay ms Delay time before start recording/replaying. Default 5000ms.\n" + << " --duration ms Record duration. Otherwise use SIGINT to stop recording.\n" + "\n" + "The record file can't be replayed on an architecture with different endianness." << std::endl; } const char *Recorder::Name() { return ydotool_tool_name; } +static int fd_file = -1; -int Recorder::Exec(int argc, const char **argv) { - std::cout << "argc = " << argc << "\n"; +static std::vector record_buffer; +static Recorder::file_header header; - for (int i=1; i extra_args; + int delay = 5000; + int duration = 0; + int mode = 0; + try { po::options_description desc(""); desc.add_options() ("help", "Show this help") + ("record", "") + ("replay", "") + ("display", "") + ("delay", po::value()) + ("duration", po::value()) ("extra-args", po::value(&extra_args)); @@ -48,47 +90,190 @@ int Recorder::Exec(int argc, const char **argv) { run(), vm); po::notify(vm); + if (vm.count("delay")) { + delay = vm["delay"].as(); + } + + if (vm.count("duration")) { + duration = vm["duration"].as(); + } if (vm.count("help")) { ShowHelp(argv[0]); return -1; } + if (vm.count("record")) { + mode = 1; + } + + if (vm.count("replay")) { + mode = 2; + } + + if (vm.count("display")) { + mode = 3; + } + + if (!mode) + throw std::invalid_argument("mode not specified"); + if (extra_args.empty()) - throw std::invalid_argument("output file not specified"); + throw std::invalid_argument("file not specified"); } catch (std::exception &e) { std::cerr << "ydotool: " << argv[0] << ": error: " << e.what() << std::endl; + std::cerr << "Use --help for help.\n"; + return 2; } auto& filepath = extra_args.front(); - fd_file = open(filepath.c_str(), O_WRONLY|O_CREAT, 0644); + if (mode == 1) + fd_file = open(filepath.c_str(), O_WRONLY|O_CREAT, 0644); + else + fd_file = open(filepath.c_str(), O_RDONLY); if (fd_file == -1) { std::cerr << "ydotool: " << argv[0] << ": error: failed to open " << filepath << ": " - << strerror(errno) << std::endl; + << strerror(errno) << std::endl; return 2; } - extra_args.erase(extra_args.begin()); + std::cerr << "Delay was set to " + << delay << " milliseconds.\n"; - auto& device_list = extra_args; - if (device_list.empty()) { - device_list = find_all_devices(); + + if (mode == 1) { + extra_args.erase(extra_args.begin()); + + auto &device_list = extra_args; if (device_list.empty()) { - std::cerr << "ydotool: " << argv[0] << ": error: no event device found in /dev/input/" - << std::endl; - return 2; + device_list = find_all_devices(); + + if (device_list.empty()) { + std::cerr << "ydotool: " << argv[0] << ": error: no event device found in /dev/input/" + << std::endl; + return 2; + } } + + signal(SIGINT, stop_handler); + + if (duration) + std::thread([duration]() { + std::cerr << "Duration was set to " + << duration << " milliseconds.\n"; + usleep(duration * 1000); + kill(getpid(), SIGINT); + }).detach(); + + if (delay) + usleep(delay * 1000); + + do_record(device_list); + } else if (mode == 2) { + if (delay) + usleep(delay * 1000); + + do_replay(); + } else if (mode == 3) { + do_display(); + } +} + + +void Recorder::do_replay() { + struct stat statat; + + fstat(fd_file, &statat); + + if (statat.st_size < sizeof(file_header)+sizeof(data_chunk)) { + fprintf(stderr, "File too small\n"); + abort(); + } + + auto filedata = (uint8_t *)mmap(nullptr, statat.st_size, PROT_READ, MAP_SHARED, fd_file, 0); + + assert(filedata); + + auto file_hdr = (file_header *)filedata; + auto file_end = filedata + statat.st_size; + auto data_start = (filedata + sizeof(file_header)); + + auto cur_pos = data_start; + + auto size_cur = file_end - data_start; + + if (size_cur == file_hdr->size) { + fprintf(stderr, "Size match\n"); + } else { + fprintf(stderr, "Size mismatch: %zu != %zu\n", size_cur, file_hdr->size); + abort(); } - do_record(device_list); + auto crc32_cur = Utils::crc32(data_start, size_cur); + + if (crc32_cur == file_hdr->crc32) { + fprintf(stderr, "CRC32 match\n"); + } else { + fprintf(stderr, "CRC32 mismatch: %08x != %08x\n", crc32_cur, file_hdr->crc32); + abort(); + } + std::cerr << "Started replaying\n"; + + while (cur_pos < file_end) { + auto dat = (data_chunk *)cur_pos; + usleep(dat->delay[0] * 1000000 + dat->delay[1] / 1000); + + uInputContext->Emit(dat->ev_type, dat->ev_code, dat->ev_value); + + cur_pos += sizeof(data_chunk); + } +} + +void Recorder::do_display() { + struct stat statat; + + fstat(fd_file, &statat); + + if (statat.st_size < sizeof(file_header)+sizeof(data_chunk)) { + fprintf(stderr, "File too small\n"); + abort(); + } + + auto filedata = (uint8_t *)mmap(nullptr, statat.st_size, PROT_READ, MAP_SHARED, fd_file, 0); + + assert(filedata); + + auto file_hdr = (file_header *)filedata; + auto file_end = filedata + statat.st_size; + auto data_start = (filedata + sizeof(file_header)); + + auto cur_pos = data_start; + + auto size_cur = file_end - data_start; + auto crc32_cur = Utils::crc32(data_start, size_cur); + + + printf("CRC32: 0x%08x / 0x%08x\n", crc32_cur, file_hdr->crc32); + printf("Data length: %zu / %zu (%zu events)\n", file_hdr->size, size_cur, file_hdr->size / sizeof(data_chunk)); + puts("============================================"); + + + while (cur_pos < file_end) { + auto dat = (data_chunk *)cur_pos; + printf("Offset: 0x%lx\n", (uint8_t *)(dat)-filedata); + printf("Delay: %" PRIu64 ".%09" PRIu64 " second\n", dat->delay[0], dat->delay[1]); + printf("Event: %u, %u, %u\n", dat->ev_type, dat->ev_code, dat->ev_value); + puts("-"); + cur_pos += sizeof(data_chunk); + } } void Recorder::do_record(const std::vector &__devices) { @@ -123,11 +308,11 @@ void Recorder::do_record(const std::vector &__devices) { for (int i=0; iRead(); + data_chunk dat; + clock_gettime(CLOCK_MONOTONIC, &tm_now2); Utils::timespec_diff(&tm_now, &tm_now2, &tm_diff); @@ -135,13 +320,16 @@ void Recorder::do_record(const std::vector &__devices) { tm_now = tm_now2; uint64_t time_hdr[2]; - time_hdr[0] = tm_diff.tv_sec; - time_hdr[1] = tm_diff.tv_nsec; + dat.delay[0] = tm_diff.tv_sec; + dat.delay[1] = tm_diff.tv_nsec; + + dat.ev_type = buf.Type; + dat.ev_code = buf.Code; + dat.ev_value = buf.Value; - write(fd_file, time_hdr, 16); - write(fd_file, &buf.event, sizeof(input_event)); + record_buffer.insert(record_buffer.end(), (uint8_t *)&dat, (uint8_t *)&dat+sizeof(data_chunk)); - write(STDERR_FILENO, ".", 1); +// write(STDERR_FILENO, ".", 1); } } @@ -167,3 +355,7 @@ std::vector Recorder::find_all_devices() { return ret; } + + + + diff --git a/Tools/Recorder/Recorder.hpp b/Tools/Recorder/Recorder.hpp index 4536469..8a91f23 100644 --- a/Tools/Recorder/Recorder.hpp +++ b/Tools/Recorder/Recorder.hpp @@ -19,18 +19,30 @@ namespace ydotool { struct file_header { char magic[4]; uint32_t crc32; + uint64_t size; uint64_t feature_mask; } __attribute__((__packed__)); + struct data_chunk { + uint64_t delay[2]; + uint16_t ev_type; + uint16_t ev_code; + int32_t ev_value; + } __attribute__((__packed__)); + private: int fd_epoll = -1; - int fd_file = -1; +// int fd_file = -1; public: const char *Name() override; void do_record(const std::vector &__devices); + void do_replay(); + + void do_display(); + std::vector find_all_devices(); int Exec(int argc, const char **argv) override;