aboutsummaryrefslogtreecommitdiff
path: root/src/GlobalHotkeysJoystick.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/GlobalHotkeysJoystick.cpp')
-rw-r--r--src/GlobalHotkeysJoystick.cpp243
1 files changed, 243 insertions, 0 deletions
diff --git a/src/GlobalHotkeysJoystick.cpp b/src/GlobalHotkeysJoystick.cpp
new file mode 100644
index 0000000..dfe1e6f
--- /dev/null
+++ b/src/GlobalHotkeysJoystick.cpp
@@ -0,0 +1,243 @@
+#include "../include/GlobalHotkeysJoystick.hpp"
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/eventfd.h>
+
+namespace gsr {
+ static constexpr double double_click_timeout_seconds = 0.33;
+
+ // Returns -1 on error
+ static int get_js_dev_input_id_from_filepath(const char *dev_input_filepath) {
+ if(strncmp(dev_input_filepath, "/dev/input/js", 13) != 0)
+ return -1;
+
+ int dev_input_id = -1;
+ if(sscanf(dev_input_filepath + 13, "%d", &dev_input_id) == 1)
+ return dev_input_id;
+ return -1;
+ }
+
+ GlobalHotkeysJoystick::~GlobalHotkeysJoystick() {
+ if(event_fd > 0) {
+ const uint64_t exit = 1;
+ write(event_fd, &exit, sizeof(exit));
+ }
+
+ if(read_thread.joinable())
+ read_thread.join();
+
+ if(event_fd > 0)
+ close(event_fd);
+
+ for(int i = 0; i < num_poll_fd; ++i) {
+ close(poll_fd[i].fd);
+ }
+ }
+
+ bool GlobalHotkeysJoystick::start() {
+ if(num_poll_fd > 0)
+ return false;
+
+ event_fd = eventfd(0, 0);
+ if(event_fd <= 0)
+ return false;
+
+ event_index = num_poll_fd;
+ poll_fd[num_poll_fd] = {
+ event_fd,
+ POLLIN,
+ 0
+ };
+ extra_data[num_poll_fd] = {
+ -1
+ };
+ ++num_poll_fd;
+
+ if(!hotplug.start()) {
+ fprintf(stderr, "Warning: failed to setup hotplugging\n");
+ } else {
+ hotplug_poll_index = num_poll_fd;
+ poll_fd[num_poll_fd] = {
+ hotplug.steal_fd(),
+ POLLIN,
+ 0
+ };
+ extra_data[num_poll_fd] = {
+ -1
+ };
+ ++num_poll_fd;
+ }
+
+ char dev_input_path[128];
+ for(int i = 0; i < 8; ++i) {
+ snprintf(dev_input_path, sizeof(dev_input_path), "/dev/input/js%d", i);
+ add_device(dev_input_path, false);
+ }
+
+ if(num_poll_fd == 0)
+ fprintf(stderr, "Info: no joysticks found, assuming they might be connected later\n");
+
+ read_thread = std::thread(&GlobalHotkeysJoystick::read_events, this);
+ return true;
+ }
+
+ bool GlobalHotkeysJoystick::bind_action(const std::string &id, GlobalHotkeyCallback callback) {
+ if(num_poll_fd == 0)
+ return false;
+ return bound_actions_by_id.insert(std::make_pair(id, std::move(callback))).second;
+ }
+
+ void GlobalHotkeysJoystick::poll_events() {
+ if(num_poll_fd == 0)
+ return;
+
+ if(save_replay) {
+ save_replay = false;
+ auto it = bound_actions_by_id.find("save_replay");
+ if(it != bound_actions_by_id.end())
+ it->second("save_replay");
+ }
+ }
+
+ void GlobalHotkeysJoystick::read_events() {
+ js_event event;
+ while(poll(poll_fd, num_poll_fd, -1) > 0) {
+ for(int i = 0; i < num_poll_fd; ++i) {
+ if(poll_fd[i].revents & (POLLHUP|POLLERR|POLLNVAL)) {
+ if(i == event_index)
+ goto done;
+
+ if(remove_poll_fd(i))
+ --i; // This item was removed so we want to repeat the same index to continue to the next item
+
+ continue;
+ }
+
+ if(!(poll_fd[i].revents & POLLIN))
+ continue;
+
+ if(i == event_index) {
+ goto done;
+ } else if(i == hotplug_poll_index) {
+ hotplug.process_event_data(poll_fd[i].fd, [&](HotplugAction hotplug_action, const char *devname) {
+ char dev_input_filepath[1024];
+ snprintf(dev_input_filepath, sizeof(dev_input_filepath), "/dev/%s", devname);
+ switch(hotplug_action) {
+ case HotplugAction::ADD: {
+ // Cant open the /dev/input device immediately or it fails.
+ // TODO: Remove this hack when a better solution is found.
+ usleep(50 * 1000);
+ add_device(dev_input_filepath);
+ break;
+ }
+ case HotplugAction::REMOVE: {
+ if(remove_device(dev_input_filepath))
+ --i; // This item was removed so we want to repeat the same index to continue to the next item
+ break;
+ }
+ }
+ });
+ } else {
+ process_js_event(poll_fd[i].fd, event);
+ }
+ }
+ }
+
+ done:
+ ;
+ }
+
+ void GlobalHotkeysJoystick::process_js_event(int fd, js_event &event) {
+ if(read(fd, &event, sizeof(event)) != sizeof(event))
+ return;
+
+ if((event.type & JS_EVENT_BUTTON) == 0)
+ return;
+
+ if(event.number == 8 && event.value == 1) {
+ const double now = double_click_clock.get_elapsed_time_seconds();
+ if(!prev_time_clicked.has_value()) {
+ prev_time_clicked = now;
+ return;
+ }
+
+ if(prev_time_clicked.has_value()) {
+ const bool double_clicked = (now - prev_time_clicked.value()) < double_click_timeout_seconds;
+ if(double_clicked) {
+ save_replay = true;
+ prev_time_clicked.reset();
+ } else {
+ prev_time_clicked = now;
+ }
+ }
+ }
+ }
+
+ bool GlobalHotkeysJoystick::add_device(const char *dev_input_filepath, bool print_error) {
+ if(num_poll_fd >= max_js_poll_fd) {
+ fprintf(stderr, "Warning: failed to add joystick device %s, too many joysticks have been added\n", dev_input_filepath);
+ return false;
+ }
+
+ const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath);
+ if(dev_input_id == -1)
+ return false;
+
+ const int fd = open(dev_input_filepath, O_RDONLY);
+ if(fd <= 0) {
+ if(print_error)
+ fprintf(stderr, "Error: failed to add joystick %s, error: %s\n", dev_input_filepath, strerror(errno));
+ return false;
+ }
+
+ poll_fd[num_poll_fd] = {
+ fd,
+ POLLIN,
+ 0
+ };
+
+ extra_data[num_poll_fd] = {
+ dev_input_id
+ };
+
+ ++num_poll_fd;
+ fprintf(stderr, "Info: added joystick: %s\n", dev_input_filepath);
+ return true;
+ }
+
+ bool GlobalHotkeysJoystick::remove_device(const char *dev_input_filepath) {
+ const int dev_input_id = get_js_dev_input_id_from_filepath(dev_input_filepath);
+ if(dev_input_id == -1)
+ return false;
+
+ const int poll_fd_index = get_poll_fd_index_by_dev_input_id(dev_input_id);
+ if(poll_fd_index == -1)
+ return false;
+
+ fprintf(stderr, "Info: removed joystick: %s\n", dev_input_filepath);
+ return remove_poll_fd(poll_fd_index);
+ }
+
+ bool GlobalHotkeysJoystick::remove_poll_fd(int index) {
+ if(index < 0 || index >= num_poll_fd)
+ return false;
+
+ close(poll_fd[index].fd);
+ for(int i = index + 1; i < num_poll_fd; ++i) {
+ poll_fd[i - 1] = poll_fd[i];
+ extra_data[i - 1] = extra_data[i];
+ }
+ --num_poll_fd;
+ return true;
+ }
+
+ int GlobalHotkeysJoystick::get_poll_fd_index_by_dev_input_id(int dev_input_id) const {
+ for(int i = 0; i < num_poll_fd; ++i) {
+ if(dev_input_id == extra_data[i].dev_input_id)
+ return i;
+ }
+ return -1;
+ }
+}