From d08ea692771caa8e385412c2f992089672773e30 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 3 May 2025 12:03:43 +0200 Subject: Keep keyboard led when turning on global hotkeys, move files --- src/Config.cpp | 2 +- src/CursorTracker/CursorTrackerWayland.cpp | 564 ++++++++++++++++++++++++++++ src/CursorTracker/CursorTrackerX11.cpp | 29 ++ src/CursorTrackerWayland.cpp | 564 ---------------------------- src/CursorTrackerX11.cpp | 29 -- src/GlobalHotkeys/GlobalHotkeysJoystick.cpp | 317 ++++++++++++++++ src/GlobalHotkeys/GlobalHotkeysLinux.cpp | 275 ++++++++++++++ src/GlobalHotkeys/GlobalHotkeysX11.cpp | 201 ++++++++++ src/GlobalHotkeysJoystick.cpp | 317 ---------------- src/GlobalHotkeysLinux.cpp | 275 -------------- src/GlobalHotkeysX11.cpp | 201 ---------- src/Overlay.cpp | 19 +- src/gui/GlobalSettingsPage.cpp | 1 - 13 files changed, 1397 insertions(+), 1397 deletions(-) create mode 100644 src/CursorTracker/CursorTrackerWayland.cpp create mode 100644 src/CursorTracker/CursorTrackerX11.cpp delete mode 100644 src/CursorTrackerWayland.cpp delete mode 100644 src/CursorTrackerX11.cpp create mode 100644 src/GlobalHotkeys/GlobalHotkeysJoystick.cpp create mode 100644 src/GlobalHotkeys/GlobalHotkeysLinux.cpp create mode 100644 src/GlobalHotkeys/GlobalHotkeysX11.cpp delete mode 100644 src/GlobalHotkeysJoystick.cpp delete mode 100644 src/GlobalHotkeysLinux.cpp delete mode 100644 src/GlobalHotkeysX11.cpp (limited to 'src') diff --git a/src/Config.cpp b/src/Config.cpp index e920bf0..2de6a96 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -1,7 +1,7 @@ #include "../include/Config.hpp" #include "../include/Utils.hpp" #include "../include/GsrInfo.hpp" -#include "../include/GlobalHotkeys.hpp" +#include "../include/GlobalHotkeys/GlobalHotkeys.hpp" #include #include #include diff --git a/src/CursorTracker/CursorTrackerWayland.cpp b/src/CursorTracker/CursorTrackerWayland.cpp new file mode 100644 index 0000000..b28b978 --- /dev/null +++ b/src/CursorTracker/CursorTrackerWayland.cpp @@ -0,0 +1,564 @@ +#include "../../include/CursorTracker/CursorTrackerWayland.hpp" +#include +#include +#include +#include +#include +#include +#include "xdg-output-unstable-v1-client-protocol.h" + +namespace gsr { + static const int MAX_CONNECTORS = 32; + static const int CONNECTOR_TYPE_COUNTS = 32; + static const uint32_t plane_property_all = 0xF; + + typedef struct { + int type; + int count; + } drm_connector_type_count; + + typedef enum { + PLANE_PROPERTY_CRTC_X = 1 << 0, + PLANE_PROPERTY_CRTC_Y = 1 << 1, + PLANE_PROPERTY_CRTC_ID = 1 << 2, + PLANE_PROPERTY_TYPE_CURSOR = 1 << 3, + } plane_property_mask; + + typedef struct { + uint64_t crtc_id; + mgl::vec2i size; + bool vrr_enabled; + } drm_connector; + + typedef struct { + drm_connector connectors[MAX_CONNECTORS]; + int num_connectors; + bool has_any_crtc_with_vrr_enabled; + } drm_connectors; + + /* Returns plane_property_mask */ + static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_id) { + *crtc_x = 0; + *crtc_y = 0; + *crtc_id = 0; + + uint32_t property_mask = 0; + + drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE); + if(!props) + return property_mask; + + for(uint32_t i = 0; i < props->count_props; ++i) { + drmModePropertyPtr prop = drmModeGetProperty(drm_fd, props->props[i]); + if(!prop) + continue; + + // SRC_* values are fixed 16.16 points + const uint32_t type = prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE); + if((type & DRM_MODE_PROP_SIGNED_RANGE) && strcmp(prop->name, "CRTC_X") == 0) { + *crtc_x = (int)props->prop_values[i]; + property_mask |= PLANE_PROPERTY_CRTC_X; + } else if((type & DRM_MODE_PROP_SIGNED_RANGE) && strcmp(prop->name, "CRTC_Y") == 0) { + *crtc_y = (int)props->prop_values[i]; + property_mask |= PLANE_PROPERTY_CRTC_Y; + } else if((type & DRM_MODE_PROP_OBJECT) && strcmp(prop->name, "CRTC_ID") == 0) { + *crtc_id = (int)props->prop_values[i]; + property_mask |= PLANE_PROPERTY_CRTC_ID; + } else if((type & DRM_MODE_PROP_ENUM) && strcmp(prop->name, "type") == 0) { + const uint64_t current_enum_value = props->prop_values[i]; + for(int j = 0; j < prop->count_enums; ++j) { + if(prop->enums[j].value == current_enum_value && strcmp(prop->enums[j].name, "Cursor") == 0) { + property_mask |= PLANE_PROPERTY_TYPE_CURSOR; + break; + } + } + } + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + return property_mask; + } + + static bool get_drm_property_by_name(int drm_fd, drmModeObjectPropertiesPtr props, const char *name, uint64_t *result) { + for(uint32_t i = 0; i < props->count_props; ++i) { + drmModePropertyPtr prop = drmModeGetProperty(drm_fd, props->props[i]); + if(!prop) + continue; + + if(strcmp(name, prop->name) == 0) { + *result = props->prop_values[i]; + drmModeFreeProperty(prop); + return true; + } + drmModeFreeProperty(prop); + } + return false; + } + + static bool connector_get_property_by_name(int drm_fd, drmModeConnectorPtr props, const char *name, uint64_t *result) { + drmModeObjectProperties properties; + properties.count_props = (uint32_t)props->count_props; + properties.props = props->props; + properties.prop_values = props->prop_values; + return get_drm_property_by_name(drm_fd, &properties, name, result); + } + + static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) { + for(int i = 0; i < *num_type_counts; ++i) { + if(type_counts[i].type == connector_type) + return &type_counts[i]; + } + + if(*num_type_counts == CONNECTOR_TYPE_COUNTS) + return NULL; + + const int index = *num_type_counts; + type_counts[index].type = connector_type; + type_counts[index].count = 0; + ++*num_type_counts; + return &type_counts[index]; + } + + // Note: this monitor name logic is kept in sync with gpu screen recorder + static std::string get_monitor_name_from_crtc_id(int drm_fd, uint32_t crtc_id) { + std::string result; + drmModeResPtr resources = drmModeGetResources(drm_fd); + if(!resources) + return result; + + drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS]; + int num_type_counts = 0; + + for(int i = 0; i < resources->count_connectors; ++i) { + uint64_t connector_crtc_id = 0; + drmModeConnectorPtr connector = drmModeGetConnectorCurrent(drm_fd, resources->connectors[i]); + if(!connector) + continue; + + drm_connector_type_count *connector_type = drm_connector_types_get_index(type_counts, &num_type_counts, connector->connector_type); + const char *connection_name = drmModeGetConnectorTypeName(connector->connector_type); + if(connector_type) + ++connector_type->count; + + if(connector->connection != DRM_MODE_CONNECTED) + goto next; + + if(connector_type && connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &connector_crtc_id) && connector_crtc_id == crtc_id) { + result = connection_name; + result += "-"; + result += std::to_string(connector_type->count); + drmModeFreeConnector(connector); + break; + } + + next: + drmModeFreeConnector(connector); + } + + drmModeFreeResources(resources); + return result; + } + + // Name is the crtc name. TODO: verify if this works on all wayland compositors + static const WaylandOutput* get_wayland_monitor_by_name(const std::vector &monitors, const std::string &name) { + for(const WaylandOutput &monitor : monitors) { + if(monitor.name == name) + return &monitor; + } + return nullptr; + } + + static WaylandOutput* get_wayland_monitor_by_output(CursorTrackerWayland &cursor_tracker_wayland, struct wl_output *output) { + for(WaylandOutput &monitor : cursor_tracker_wayland.monitors) { + if(monitor.output == output) + return &monitor; + } + return nullptr; + } + + static void output_handle_geometry(void *data, struct wl_output *wl_output, + int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) { + (void)wl_output; + (void)phys_width; + (void)phys_height; + (void)subpixel; + (void)make; + (void)model; + CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; + WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output); + if(!monitor) + return; + + monitor->pos.x = x; + monitor->pos.y = y; + monitor->transform = transform; + } + + static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + (void)wl_output; + (void)flags; + (void)refresh; + CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; + WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output); + if(!monitor) + return; + + monitor->size.x = width; + monitor->size.y = height; + } + + static void output_handle_done(void *data, struct wl_output *wl_output) { + (void)data; + (void)wl_output; + } + + static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) { + (void)data; + (void)wl_output; + (void)factor; + } + + static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) { + (void)wl_output; + CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; + WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output); + if(!monitor) + return; + + monitor->name = name; + } + + static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) { + (void)data; + (void)wl_output; + (void)description; + } + + static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, + output_handle_name, + output_handle_description, + }; + + static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + (void)version; + CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; + if(strcmp(interface, wl_output_interface.name) == 0) { + if(version < 4) { + fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n"); + return; + } + + struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4); + cursor_tracker_wayland->monitors.push_back( + WaylandOutput{ + name, + output, + nullptr, + mgl::vec2i{0, 0}, + mgl::vec2i{0, 0}, + 0, + "" + }); + wl_output_add_listener(output, &output_listener, cursor_tracker_wayland); + } else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { + if(version < 1) { + fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n"); + return; + } + + if(cursor_tracker_wayland->xdg_output_manager) { + zxdg_output_manager_v1_destroy(cursor_tracker_wayland->xdg_output_manager); + cursor_tracker_wayland->xdg_output_manager = NULL; + } + cursor_tracker_wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1); + } + } + + static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) { + (void)data; + (void)registry; + (void)name; + // TODO: Remove output + } + + static struct wl_registry_listener registry_listener = { + registry_add_object, + registry_remove_object, + }; + + static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) { + (void)zxdg_output_v1; + WaylandOutput *monitor = (WaylandOutput*)data; + monitor->pos.x = x; + monitor->pos.y = y; + } + + static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { + (void)data; + (void)xdg_output; + (void)width; + (void)height; + } + + static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { + (void)data; + (void)xdg_output; + } + + static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { + (void)data; + (void)xdg_output; + (void)name; + } + + static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { + (void)data; + (void)xdg_output; + (void)description; + } + + static const struct zxdg_output_v1_listener xdg_output_listener = { + xdg_output_logical_position, + xdg_output_handle_logical_size, + xdg_output_handle_done, + xdg_output_handle_name, + xdg_output_handle_description, + }; + + /* Returns nullptr if not found */ + static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) { + for(int i = 0; i < connectors->num_connectors; ++i) { + if(connectors->connectors[i].crtc_id == crtc_id) + return &connectors->connectors[i]; + } + return nullptr; + } + + static void get_drm_connectors(int drm_fd, drm_connectors *drm_connectors) { + drm_connectors->num_connectors = 0; + drm_connectors->has_any_crtc_with_vrr_enabled = false; + + drmModeResPtr resources = drmModeGetResources(drm_fd); + if(!resources) + return; + + for(int i = 0; i < resources->count_connectors && drm_connectors->num_connectors < MAX_CONNECTORS; ++i) { + drmModeConnectorPtr connector = nullptr; + drmModeCrtcPtr crtc = nullptr; + + connector = drmModeGetConnectorCurrent(drm_fd, resources->connectors[i]); + if(!connector) + continue; + + uint64_t crtc_id = 0; + connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &crtc_id); + if(crtc_id == 0) + goto next_connector; + + crtc = drmModeGetCrtc(drm_fd, crtc_id); + if(!crtc) + goto next_connector; + + drm_connectors->connectors[drm_connectors->num_connectors].crtc_id = crtc_id; + drm_connectors->connectors[drm_connectors->num_connectors].size = mgl::vec2i{(int)crtc->width, (int)crtc->height}; + drm_connectors->connectors[drm_connectors->num_connectors].vrr_enabled = false; + ++drm_connectors->num_connectors; + + next_connector: + if(crtc) + drmModeFreeCrtc(crtc); + + if(connector) + drmModeFreeConnector(connector); + } + + for(int i = 0; i < resources->count_crtcs; ++i) { + drmModeCrtcPtr crtc = nullptr; + drmModeObjectPropertiesPtr properties = nullptr; + uint64_t vrr_enabled = 0; + drm_connector *connector = nullptr; + + crtc = drmModeGetCrtc(drm_fd, resources->crtcs[i]); + if(!crtc) + continue; + + properties = drmModeObjectGetProperties(drm_fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC); + if(!properties) + goto next_crtc; + + if(!get_drm_property_by_name(drm_fd, properties, "VRR_ENABLED", &vrr_enabled)) + goto next_crtc; + + connector = get_drm_connector_by_crtc_id(drm_connectors, crtc->crtc_id); + if(!connector) + goto next_crtc; + + if(vrr_enabled) { + connector->vrr_enabled = true; + drm_connectors->has_any_crtc_with_vrr_enabled = true; + } + + next_crtc: + if(properties) + drmModeFreeObjectProperties(properties); + + if(crtc) + drmModeFreeCrtc(crtc); + } + + drmModeFreeResources(resources); + } + + CursorTrackerWayland::CursorTrackerWayland(const char *card_path) { + drm_fd = open(card_path, O_RDONLY); + if(drm_fd <= 0) { + fprintf(stderr, "Error: CursorTrackerWayland: failed to open %s\n", card_path); + return; + } + + drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + drmSetClientCap(drm_fd, DRM_CLIENT_CAP_ATOMIC, 1); + } + + CursorTrackerWayland::~CursorTrackerWayland() { + if(drm_fd > 0) + close(drm_fd); + } + + void CursorTrackerWayland::update() { + if(drm_fd <= 0) + return; + + drm_connectors connectors; + connectors.num_connectors = 0; + connectors.has_any_crtc_with_vrr_enabled = false; + get_drm_connectors(drm_fd, &connectors); + + drmModePlaneResPtr planes = drmModeGetPlaneResources(drm_fd); + if(!planes) + return; + + bool found_cursor = false; + for(uint32_t i = 0; i < planes->count_planes; ++i) { + drmModePlanePtr plane = nullptr; + const drm_connector *connector = nullptr; + int crtc_x = 0; + int crtc_y = 0; + int crtc_id = 0; + uint32_t property_mask = 0; + + plane = drmModeGetPlane(drm_fd, planes->planes[i]); + if(!plane) + goto next; + + if(!plane->fb_id) + goto next; + + property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_id); + if(property_mask != plane_property_all || crtc_id <= 0) + goto next; + + connector = get_drm_connector_by_crtc_id(&connectors, crtc_id); + if(!connector) + goto next; + + if(crtc_x >= 0 && crtc_x <= connector->size.x && crtc_y >= 0 && crtc_y <= connector->size.y) { + latest_cursor_position.x = crtc_x; + latest_cursor_position.y = crtc_y; + latest_crtc_id = crtc_id; + found_cursor = true; + drmModeFreePlane(plane); + break; + } + + next: + drmModeFreePlane(plane); + } + + // On kde plasma wayland (and possibly other wayland compositors) it uses a software cursor only for the monitors with vrr enabled. + // In that case we cant know the cursor location and we instead want to fallback to getting focused monitor by using the hack of creating a window and getting the position. + if(!found_cursor && latest_crtc_id > 0 && connectors.has_any_crtc_with_vrr_enabled) + latest_crtc_id = -1; + + drmModeFreePlaneResources(planes); + } + + void CursorTrackerWayland::set_monitor_outputs_from_xdg_output(struct wl_display *dpy) { + if(!xdg_output_manager) { + fprintf(stderr, "Warning: CursorTrackerWayland::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n"); + return; + } + + for(WaylandOutput &monitor : monitors) { + monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, monitor.output); + zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor); + } + + // Fetch xdg_output + wl_display_roundtrip(dpy); + } + + std::optional CursorTrackerWayland::get_latest_cursor_info() { + if(drm_fd <= 0 || latest_crtc_id == -1) + return std::nullopt; + + std::string monitor_name = get_monitor_name_from_crtc_id(drm_fd, latest_crtc_id); + if(monitor_name.empty()) + return std::nullopt; + + struct wl_display *dpy = wl_display_connect(nullptr); + if(!dpy) { + fprintf(stderr, "Error: CursorTrackerWayland::get_latest_cursor_info: failed to connect to the wayland server\n"); + return std::nullopt; + } + + monitors.clear(); + struct wl_registry *registry = wl_display_get_registry(dpy); + wl_registry_add_listener(registry, ®istry_listener, this); + + // Fetch globals + wl_display_roundtrip(dpy); + + // Fetch wl_output + wl_display_roundtrip(dpy); + + set_monitor_outputs_from_xdg_output(dpy); + + mgl::vec2i cursor_position = latest_cursor_position; + const WaylandOutput *wayland_monitor = get_wayland_monitor_by_name(monitors, monitor_name); + if(!wayland_monitor) + return std::nullopt; + + cursor_position = wayland_monitor->pos + latest_cursor_position; + for(WaylandOutput &monitor : monitors) { + if(monitor.output) { + wl_output_destroy(monitor.output); + monitor.output = nullptr; + } + + if(monitor.xdg_output) { + zxdg_output_v1_destroy(monitor.xdg_output); + monitor.xdg_output = nullptr; + } + } + monitors.clear(); + + if(xdg_output_manager) { + zxdg_output_manager_v1_destroy(xdg_output_manager); + xdg_output_manager = nullptr; + } + + wl_registry_destroy(registry); + wl_display_disconnect(dpy); + + return CursorInfo{ cursor_position, std::move(monitor_name) }; + } +} \ No newline at end of file diff --git a/src/CursorTracker/CursorTrackerX11.cpp b/src/CursorTracker/CursorTrackerX11.cpp new file mode 100644 index 0000000..7c98f4d --- /dev/null +++ b/src/CursorTracker/CursorTrackerX11.cpp @@ -0,0 +1,29 @@ +#include "../../include/CursorTracker/CursorTrackerX11.hpp" +#include "../../include/WindowUtils.hpp" + +namespace gsr { + CursorTrackerX11::CursorTrackerX11(Display *dpy) : dpy(dpy) { + + } + + std::optional CursorTrackerX11::get_latest_cursor_info() { + Window window = None; + const auto cursor_pos = get_cursor_position(dpy, &window); + const auto monitors = get_monitors(dpy); + std::string monitor_name; + + for(const auto &monitor : monitors) { + if(cursor_pos.x >= monitor.position.x && cursor_pos.x <= monitor.position.x + monitor.size.x + && cursor_pos.y >= monitor.position.y && cursor_pos.y <= monitor.position.y + monitor.size.y) + { + monitor_name = monitor.name; + break; + } + } + + if(monitor_name.empty()) + return std::nullopt; + + return CursorInfo{ cursor_pos, std::move(monitor_name) }; + } +} \ No newline at end of file diff --git a/src/CursorTrackerWayland.cpp b/src/CursorTrackerWayland.cpp deleted file mode 100644 index 9a0f442..0000000 --- a/src/CursorTrackerWayland.cpp +++ /dev/null @@ -1,564 +0,0 @@ -#include "../include/CursorTrackerWayland.hpp" -#include -#include -#include -#include -#include -#include -#include "xdg-output-unstable-v1-client-protocol.h" - -namespace gsr { - static const int MAX_CONNECTORS = 32; - static const int CONNECTOR_TYPE_COUNTS = 32; - static const uint32_t plane_property_all = 0xF; - - typedef struct { - int type; - int count; - } drm_connector_type_count; - - typedef enum { - PLANE_PROPERTY_CRTC_X = 1 << 0, - PLANE_PROPERTY_CRTC_Y = 1 << 1, - PLANE_PROPERTY_CRTC_ID = 1 << 2, - PLANE_PROPERTY_TYPE_CURSOR = 1 << 3, - } plane_property_mask; - - typedef struct { - uint64_t crtc_id; - mgl::vec2i size; - bool vrr_enabled; - } drm_connector; - - typedef struct { - drm_connector connectors[MAX_CONNECTORS]; - int num_connectors; - bool has_any_crtc_with_vrr_enabled; - } drm_connectors; - - /* Returns plane_property_mask */ - static uint32_t plane_get_properties(int drm_fd, uint32_t plane_id, int *crtc_x, int *crtc_y, int *crtc_id) { - *crtc_x = 0; - *crtc_y = 0; - *crtc_id = 0; - - uint32_t property_mask = 0; - - drmModeObjectPropertiesPtr props = drmModeObjectGetProperties(drm_fd, plane_id, DRM_MODE_OBJECT_PLANE); - if(!props) - return property_mask; - - for(uint32_t i = 0; i < props->count_props; ++i) { - drmModePropertyPtr prop = drmModeGetProperty(drm_fd, props->props[i]); - if(!prop) - continue; - - // SRC_* values are fixed 16.16 points - const uint32_t type = prop->flags & (DRM_MODE_PROP_LEGACY_TYPE | DRM_MODE_PROP_EXTENDED_TYPE); - if((type & DRM_MODE_PROP_SIGNED_RANGE) && strcmp(prop->name, "CRTC_X") == 0) { - *crtc_x = (int)props->prop_values[i]; - property_mask |= PLANE_PROPERTY_CRTC_X; - } else if((type & DRM_MODE_PROP_SIGNED_RANGE) && strcmp(prop->name, "CRTC_Y") == 0) { - *crtc_y = (int)props->prop_values[i]; - property_mask |= PLANE_PROPERTY_CRTC_Y; - } else if((type & DRM_MODE_PROP_OBJECT) && strcmp(prop->name, "CRTC_ID") == 0) { - *crtc_id = (int)props->prop_values[i]; - property_mask |= PLANE_PROPERTY_CRTC_ID; - } else if((type & DRM_MODE_PROP_ENUM) && strcmp(prop->name, "type") == 0) { - const uint64_t current_enum_value = props->prop_values[i]; - for(int j = 0; j < prop->count_enums; ++j) { - if(prop->enums[j].value == current_enum_value && strcmp(prop->enums[j].name, "Cursor") == 0) { - property_mask |= PLANE_PROPERTY_TYPE_CURSOR; - break; - } - } - } - - drmModeFreeProperty(prop); - } - - drmModeFreeObjectProperties(props); - return property_mask; - } - - static bool get_drm_property_by_name(int drm_fd, drmModeObjectPropertiesPtr props, const char *name, uint64_t *result) { - for(uint32_t i = 0; i < props->count_props; ++i) { - drmModePropertyPtr prop = drmModeGetProperty(drm_fd, props->props[i]); - if(!prop) - continue; - - if(strcmp(name, prop->name) == 0) { - *result = props->prop_values[i]; - drmModeFreeProperty(prop); - return true; - } - drmModeFreeProperty(prop); - } - return false; - } - - static bool connector_get_property_by_name(int drm_fd, drmModeConnectorPtr props, const char *name, uint64_t *result) { - drmModeObjectProperties properties; - properties.count_props = (uint32_t)props->count_props; - properties.props = props->props; - properties.prop_values = props->prop_values; - return get_drm_property_by_name(drm_fd, &properties, name, result); - } - - static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) { - for(int i = 0; i < *num_type_counts; ++i) { - if(type_counts[i].type == connector_type) - return &type_counts[i]; - } - - if(*num_type_counts == CONNECTOR_TYPE_COUNTS) - return NULL; - - const int index = *num_type_counts; - type_counts[index].type = connector_type; - type_counts[index].count = 0; - ++*num_type_counts; - return &type_counts[index]; - } - - // Note: this monitor name logic is kept in sync with gpu screen recorder - static std::string get_monitor_name_from_crtc_id(int drm_fd, uint32_t crtc_id) { - std::string result; - drmModeResPtr resources = drmModeGetResources(drm_fd); - if(!resources) - return result; - - drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS]; - int num_type_counts = 0; - - for(int i = 0; i < resources->count_connectors; ++i) { - uint64_t connector_crtc_id = 0; - drmModeConnectorPtr connector = drmModeGetConnectorCurrent(drm_fd, resources->connectors[i]); - if(!connector) - continue; - - drm_connector_type_count *connector_type = drm_connector_types_get_index(type_counts, &num_type_counts, connector->connector_type); - const char *connection_name = drmModeGetConnectorTypeName(connector->connector_type); - if(connector_type) - ++connector_type->count; - - if(connector->connection != DRM_MODE_CONNECTED) - goto next; - - if(connector_type && connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &connector_crtc_id) && connector_crtc_id == crtc_id) { - result = connection_name; - result += "-"; - result += std::to_string(connector_type->count); - drmModeFreeConnector(connector); - break; - } - - next: - drmModeFreeConnector(connector); - } - - drmModeFreeResources(resources); - return result; - } - - // Name is the crtc name. TODO: verify if this works on all wayland compositors - static const WaylandOutput* get_wayland_monitor_by_name(const std::vector &monitors, const std::string &name) { - for(const WaylandOutput &monitor : monitors) { - if(monitor.name == name) - return &monitor; - } - return nullptr; - } - - static WaylandOutput* get_wayland_monitor_by_output(CursorTrackerWayland &cursor_tracker_wayland, struct wl_output *output) { - for(WaylandOutput &monitor : cursor_tracker_wayland.monitors) { - if(monitor.output == output) - return &monitor; - } - return nullptr; - } - - static void output_handle_geometry(void *data, struct wl_output *wl_output, - int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, - int32_t subpixel, const char *make, const char *model, - int32_t transform) { - (void)wl_output; - (void)phys_width; - (void)phys_height; - (void)subpixel; - (void)make; - (void)model; - CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; - WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output); - if(!monitor) - return; - - monitor->pos.x = x; - monitor->pos.y = y; - monitor->transform = transform; - } - - static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - (void)wl_output; - (void)flags; - (void)refresh; - CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; - WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output); - if(!monitor) - return; - - monitor->size.x = width; - monitor->size.y = height; - } - - static void output_handle_done(void *data, struct wl_output *wl_output) { - (void)data; - (void)wl_output; - } - - static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) { - (void)data; - (void)wl_output; - (void)factor; - } - - static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) { - (void)wl_output; - CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; - WaylandOutput *monitor = get_wayland_monitor_by_output(*cursor_tracker_wayland, wl_output); - if(!monitor) - return; - - monitor->name = name; - } - - static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) { - (void)data; - (void)wl_output; - (void)description; - } - - static const struct wl_output_listener output_listener = { - output_handle_geometry, - output_handle_mode, - output_handle_done, - output_handle_scale, - output_handle_name, - output_handle_description, - }; - - static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { - (void)version; - CursorTrackerWayland *cursor_tracker_wayland = (CursorTrackerWayland*)data; - if(strcmp(interface, wl_output_interface.name) == 0) { - if(version < 4) { - fprintf(stderr, "Warning: wl output interface version is < 4, expected >= 4\n"); - return; - } - - struct wl_output *output = (struct wl_output*)wl_registry_bind(registry, name, &wl_output_interface, 4); - cursor_tracker_wayland->monitors.push_back( - WaylandOutput{ - name, - output, - nullptr, - mgl::vec2i{0, 0}, - mgl::vec2i{0, 0}, - 0, - "" - }); - wl_output_add_listener(output, &output_listener, cursor_tracker_wayland); - } else if(strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { - if(version < 1) { - fprintf(stderr, "Warning: xdg output interface version is < 1, expected >= 1\n"); - return; - } - - if(cursor_tracker_wayland->xdg_output_manager) { - zxdg_output_manager_v1_destroy(cursor_tracker_wayland->xdg_output_manager); - cursor_tracker_wayland->xdg_output_manager = NULL; - } - cursor_tracker_wayland->xdg_output_manager = (struct zxdg_output_manager_v1*)wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 1); - } - } - - static void registry_remove_object(void *data, struct wl_registry *registry, uint32_t name) { - (void)data; - (void)registry; - (void)name; - // TODO: Remove output - } - - static struct wl_registry_listener registry_listener = { - registry_add_object, - registry_remove_object, - }; - - static void xdg_output_logical_position(void *data, struct zxdg_output_v1 *zxdg_output_v1, int32_t x, int32_t y) { - (void)zxdg_output_v1; - WaylandOutput *monitor = (WaylandOutput*)data; - monitor->pos.x = x; - monitor->pos.y = y; - } - - static void xdg_output_handle_logical_size(void *data, struct zxdg_output_v1 *xdg_output, int32_t width, int32_t height) { - (void)data; - (void)xdg_output; - (void)width; - (void)height; - } - - static void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { - (void)data; - (void)xdg_output; - } - - static void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { - (void)data; - (void)xdg_output; - (void)name; - } - - static void xdg_output_handle_description(void *data, struct zxdg_output_v1 *xdg_output, const char *description) { - (void)data; - (void)xdg_output; - (void)description; - } - - static const struct zxdg_output_v1_listener xdg_output_listener = { - xdg_output_logical_position, - xdg_output_handle_logical_size, - xdg_output_handle_done, - xdg_output_handle_name, - xdg_output_handle_description, - }; - - /* Returns nullptr if not found */ - static drm_connector* get_drm_connector_by_crtc_id(drm_connectors *connectors, uint32_t crtc_id) { - for(int i = 0; i < connectors->num_connectors; ++i) { - if(connectors->connectors[i].crtc_id == crtc_id) - return &connectors->connectors[i]; - } - return nullptr; - } - - static void get_drm_connectors(int drm_fd, drm_connectors *drm_connectors) { - drm_connectors->num_connectors = 0; - drm_connectors->has_any_crtc_with_vrr_enabled = false; - - drmModeResPtr resources = drmModeGetResources(drm_fd); - if(!resources) - return; - - for(int i = 0; i < resources->count_connectors && drm_connectors->num_connectors < MAX_CONNECTORS; ++i) { - drmModeConnectorPtr connector = nullptr; - drmModeCrtcPtr crtc = nullptr; - - connector = drmModeGetConnectorCurrent(drm_fd, resources->connectors[i]); - if(!connector) - continue; - - uint64_t crtc_id = 0; - connector_get_property_by_name(drm_fd, connector, "CRTC_ID", &crtc_id); - if(crtc_id == 0) - goto next_connector; - - crtc = drmModeGetCrtc(drm_fd, crtc_id); - if(!crtc) - goto next_connector; - - drm_connectors->connectors[drm_connectors->num_connectors].crtc_id = crtc_id; - drm_connectors->connectors[drm_connectors->num_connectors].size = mgl::vec2i{(int)crtc->width, (int)crtc->height}; - drm_connectors->connectors[drm_connectors->num_connectors].vrr_enabled = false; - ++drm_connectors->num_connectors; - - next_connector: - if(crtc) - drmModeFreeCrtc(crtc); - - if(connector) - drmModeFreeConnector(connector); - } - - for(int i = 0; i < resources->count_crtcs; ++i) { - drmModeCrtcPtr crtc = nullptr; - drmModeObjectPropertiesPtr properties = nullptr; - uint64_t vrr_enabled = 0; - drm_connector *connector = nullptr; - - crtc = drmModeGetCrtc(drm_fd, resources->crtcs[i]); - if(!crtc) - continue; - - properties = drmModeObjectGetProperties(drm_fd, crtc->crtc_id, DRM_MODE_OBJECT_CRTC); - if(!properties) - goto next_crtc; - - if(!get_drm_property_by_name(drm_fd, properties, "VRR_ENABLED", &vrr_enabled)) - goto next_crtc; - - connector = get_drm_connector_by_crtc_id(drm_connectors, crtc->crtc_id); - if(!connector) - goto next_crtc; - - if(vrr_enabled) { - connector->vrr_enabled = true; - drm_connectors->has_any_crtc_with_vrr_enabled = true; - } - - next_crtc: - if(properties) - drmModeFreeObjectProperties(properties); - - if(crtc) - drmModeFreeCrtc(crtc); - } - - drmModeFreeResources(resources); - } - - CursorTrackerWayland::CursorTrackerWayland(const char *card_path) { - drm_fd = open(card_path, O_RDONLY); - if(drm_fd <= 0) { - fprintf(stderr, "Error: CursorTrackerWayland: failed to open %s\n", card_path); - return; - } - - drmSetClientCap(drm_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - drmSetClientCap(drm_fd, DRM_CLIENT_CAP_ATOMIC, 1); - } - - CursorTrackerWayland::~CursorTrackerWayland() { - if(drm_fd > 0) - close(drm_fd); - } - - void CursorTrackerWayland::update() { - if(drm_fd <= 0) - return; - - drm_connectors connectors; - connectors.num_connectors = 0; - connectors.has_any_crtc_with_vrr_enabled = false; - get_drm_connectors(drm_fd, &connectors); - - drmModePlaneResPtr planes = drmModeGetPlaneResources(drm_fd); - if(!planes) - return; - - bool found_cursor = false; - for(uint32_t i = 0; i < planes->count_planes; ++i) { - drmModePlanePtr plane = nullptr; - const drm_connector *connector = nullptr; - int crtc_x = 0; - int crtc_y = 0; - int crtc_id = 0; - uint32_t property_mask = 0; - - plane = drmModeGetPlane(drm_fd, planes->planes[i]); - if(!plane) - goto next; - - if(!plane->fb_id) - goto next; - - property_mask = plane_get_properties(drm_fd, planes->planes[i], &crtc_x, &crtc_y, &crtc_id); - if(property_mask != plane_property_all || crtc_id <= 0) - goto next; - - connector = get_drm_connector_by_crtc_id(&connectors, crtc_id); - if(!connector) - goto next; - - if(crtc_x >= 0 && crtc_x <= connector->size.x && crtc_y >= 0 && crtc_y <= connector->size.y) { - latest_cursor_position.x = crtc_x; - latest_cursor_position.y = crtc_y; - latest_crtc_id = crtc_id; - found_cursor = true; - drmModeFreePlane(plane); - break; - } - - next: - drmModeFreePlane(plane); - } - - // On kde plasma wayland (and possibly other wayland compositors) it uses a software cursor only for the monitors with vrr enabled. - // In that case we cant know the cursor location and we instead want to fallback to getting focused monitor by using the hack of creating a window and getting the position. - if(!found_cursor && latest_crtc_id > 0 && connectors.has_any_crtc_with_vrr_enabled) - latest_crtc_id = -1; - - drmModeFreePlaneResources(planes); - } - - void CursorTrackerWayland::set_monitor_outputs_from_xdg_output(struct wl_display *dpy) { - if(!xdg_output_manager) { - fprintf(stderr, "Warning: CursorTrackerWayland::set_monitor_outputs_from_xdg_output: zxdg_output_manager not found. Registered monitor positions might be incorrect\n"); - return; - } - - for(WaylandOutput &monitor : monitors) { - monitor.xdg_output = zxdg_output_manager_v1_get_xdg_output(xdg_output_manager, monitor.output); - zxdg_output_v1_add_listener(monitor.xdg_output, &xdg_output_listener, &monitor); - } - - // Fetch xdg_output - wl_display_roundtrip(dpy); - } - - std::optional CursorTrackerWayland::get_latest_cursor_info() { - if(drm_fd <= 0 || latest_crtc_id == -1) - return std::nullopt; - - std::string monitor_name = get_monitor_name_from_crtc_id(drm_fd, latest_crtc_id); - if(monitor_name.empty()) - return std::nullopt; - - struct wl_display *dpy = wl_display_connect(nullptr); - if(!dpy) { - fprintf(stderr, "Error: CursorTrackerWayland::get_latest_cursor_info: failed to connect to the wayland server\n"); - return std::nullopt; - } - - monitors.clear(); - struct wl_registry *registry = wl_display_get_registry(dpy); - wl_registry_add_listener(registry, ®istry_listener, this); - - // Fetch globals - wl_display_roundtrip(dpy); - - // Fetch wl_output - wl_display_roundtrip(dpy); - - set_monitor_outputs_from_xdg_output(dpy); - - mgl::vec2i cursor_position = latest_cursor_position; - const WaylandOutput *wayland_monitor = get_wayland_monitor_by_name(monitors, monitor_name); - if(!wayland_monitor) - return std::nullopt; - - cursor_position = wayland_monitor->pos + latest_cursor_position; - for(WaylandOutput &monitor : monitors) { - if(monitor.output) { - wl_output_destroy(monitor.output); - monitor.output = nullptr; - } - - if(monitor.xdg_output) { - zxdg_output_v1_destroy(monitor.xdg_output); - monitor.xdg_output = nullptr; - } - } - monitors.clear(); - - if(xdg_output_manager) { - zxdg_output_manager_v1_destroy(xdg_output_manager); - xdg_output_manager = nullptr; - } - - wl_registry_destroy(registry); - wl_display_disconnect(dpy); - - return CursorInfo{ cursor_position, std::move(monitor_name) }; - } -} \ No newline at end of file diff --git a/src/CursorTrackerX11.cpp b/src/CursorTrackerX11.cpp deleted file mode 100644 index 7c40cea..0000000 --- a/src/CursorTrackerX11.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "../include/CursorTrackerX11.hpp" -#include "../include/WindowUtils.hpp" - -namespace gsr { - CursorTrackerX11::CursorTrackerX11(Display *dpy) : dpy(dpy) { - - } - - std::optional CursorTrackerX11::get_latest_cursor_info() { - Window window = None; - const auto cursor_pos = get_cursor_position(dpy, &window); - const auto monitors = get_monitors(dpy); - std::string monitor_name; - - for(const auto &monitor : monitors) { - if(cursor_pos.x >= monitor.position.x && cursor_pos.x <= monitor.position.x + monitor.size.x - && cursor_pos.y >= monitor.position.y && cursor_pos.y <= monitor.position.y + monitor.size.y) - { - monitor_name = monitor.name; - break; - } - } - - if(monitor_name.empty()) - return std::nullopt; - - return CursorInfo{ cursor_pos, std::move(monitor_name) }; - } -} \ No newline at end of file diff --git a/src/GlobalHotkeys/GlobalHotkeysJoystick.cpp b/src/GlobalHotkeys/GlobalHotkeysJoystick.cpp new file mode 100644 index 0000000..b3b21c8 --- /dev/null +++ b/src/GlobalHotkeys/GlobalHotkeysJoystick.cpp @@ -0,0 +1,317 @@ +#include "../../include/GlobalHotkeys/GlobalHotkeysJoystick.hpp" +#include +#include +#include +#include +#include + +namespace gsr { + static constexpr int button_pressed = 1; + static constexpr int cross_button = 0; + static constexpr int triangle_button = 2; + static constexpr int options_button = 9; + static constexpr int playstation_button = 10; + static constexpr int axis_up_down = 7; + static constexpr int axis_left_right = 6; + + // 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"); + } + + if(save_1_min_replay) { + save_1_min_replay = false; + auto it = bound_actions_by_id.find("save_1_min_replay"); + if(it != bound_actions_by_id.end()) + it->second("save_1_min_replay"); + } + + if(save_10_min_replay) { + save_10_min_replay = false; + auto it = bound_actions_by_id.find("save_10_min_replay"); + if(it != bound_actions_by_id.end()) + it->second("save_10_min_replay"); + } + + if(take_screenshot) { + take_screenshot = false; + auto it = bound_actions_by_id.find("take_screenshot"); + if(it != bound_actions_by_id.end()) + it->second("take_screenshot"); + } + + if(toggle_record) { + toggle_record = false; + auto it = bound_actions_by_id.find("toggle_record"); + if(it != bound_actions_by_id.end()) + it->second("toggle_record"); + } + + if(toggle_replay) { + toggle_replay = false; + auto it = bound_actions_by_id.find("toggle_replay"); + if(it != bound_actions_by_id.end()) + it->second("toggle_replay"); + } + + if(toggle_show) { + toggle_show = false; + auto it = bound_actions_by_id.find("toggle_show"); + if(it != bound_actions_by_id.end()) + it->second("toggle_show"); + } + } + + 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) == JS_EVENT_BUTTON) { + switch(event.number) { + case playstation_button: { + playstation_button_pressed = event.value == button_pressed; + break; + } + case options_button: { + if(playstation_button_pressed && event.value == button_pressed) + toggle_show = true; + break; + } + case cross_button: { + if(playstation_button_pressed && event.value == button_pressed) + save_1_min_replay = true; + break; + } + case triangle_button: { + if(playstation_button_pressed && event.value == button_pressed) + save_10_min_replay = true; + break; + } + } + } else if((event.type & JS_EVENT_AXIS) == JS_EVENT_AXIS && playstation_button_pressed) { + const int trigger_threshold = 16383; + const bool prev_up_pressed = up_pressed; + const bool prev_down_pressed = down_pressed; + const bool prev_left_pressed = left_pressed; + const bool prev_right_pressed = right_pressed; + + if(event.number == axis_up_down) { + up_pressed = event.value <= -trigger_threshold; + down_pressed = event.value >= trigger_threshold; + } else if(event.number == axis_left_right) { + left_pressed = event.value <= -trigger_threshold; + right_pressed = event.value >= trigger_threshold; + } + + if(up_pressed && !prev_up_pressed) + take_screenshot = true; + else if(down_pressed && !prev_down_pressed) + save_replay = true; + else if(left_pressed && !prev_left_pressed) + toggle_record = true; + else if(right_pressed && !prev_right_pressed) + toggle_replay = true; + } + } + + 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; + } +} diff --git a/src/GlobalHotkeys/GlobalHotkeysLinux.cpp b/src/GlobalHotkeys/GlobalHotkeysLinux.cpp new file mode 100644 index 0000000..a56bbc6 --- /dev/null +++ b/src/GlobalHotkeys/GlobalHotkeysLinux.cpp @@ -0,0 +1,275 @@ +#include "../../include/GlobalHotkeys/GlobalHotkeysLinux.hpp" +#include +#include +#include +#include + +extern "C" { +#include +} +#include +#include +#include + +#define PIPE_READ 0 +#define PIPE_WRITE 1 + +namespace gsr { + static const char* grab_type_to_arg(GlobalHotkeysLinux::GrabType grab_type) { + switch(grab_type) { + case GlobalHotkeysLinux::GrabType::ALL: return "--all"; + case GlobalHotkeysLinux::GrabType::VIRTUAL: return "--virtual"; + } + return "--all"; + } + + static inline uint8_t x11_keycode_to_linux_keycode(uint8_t code) { + return code - 8; + } + + static std::vector modifiers_to_linux_keys(uint32_t modifiers) { + std::vector result; + if(modifiers & HOTKEY_MOD_LSHIFT) + result.push_back(KEY_LEFTSHIFT); + if(modifiers & HOTKEY_MOD_RSHIFT) + result.push_back(KEY_RIGHTSHIFT); + if(modifiers & HOTKEY_MOD_LCTRL) + result.push_back(KEY_LEFTCTRL); + if(modifiers & HOTKEY_MOD_RCTRL) + result.push_back(KEY_RIGHTCTRL); + if(modifiers & HOTKEY_MOD_LALT) + result.push_back(KEY_LEFTALT); + if(modifiers & HOTKEY_MOD_RALT) + result.push_back(KEY_RIGHTALT); + if(modifiers & HOTKEY_MOD_LSUPER) + result.push_back(KEY_LEFTMETA); + if(modifiers & HOTKEY_MOD_RSUPER) + result.push_back(KEY_RIGHTMETA); + return result; + } + + static std::string linux_keys_to_command_string(const uint8_t *keys, size_t size) { + std::string result; + for(size_t i = 0; i < size; ++i) { + if(!result.empty()) + result += "+"; + result += std::to_string(keys[i]); + } + return result; + } + + static bool x11_key_is_alpha_numerical(KeySym keysym) { + return (keysym >= XK_A && keysym <= XK_Z) || (keysym >= XK_a && keysym <= XK_z) || (keysym >= XK_0 && keysym <= XK_9); + } + + GlobalHotkeysLinux::GlobalHotkeysLinux(GrabType grab_type) : grab_type(grab_type) { + for(int i = 0; i < 2; ++i) { + read_pipes[i] = -1; + write_pipes[i] = -1; + } + } + + GlobalHotkeysLinux::~GlobalHotkeysLinux() { + if(write_pipes[PIPE_WRITE] > 0) { + char command[32]; + const int command_size = snprintf(command, sizeof(command), "exit\n"); + if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) { + fprintf(stderr, "Error: GlobalHotkeysLinux::~GlobalHotkeysLinux: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno)); + close_fds(); + } + } else { + close_fds(); + } + + if(process_id > 0) { + int status; + waitpid(process_id, &status, 0); + } + + close_fds(); + } + + void GlobalHotkeysLinux::close_fds() { + for(int i = 0; i < 2; ++i) { + if(read_pipes[i] > 0) { + close(read_pipes[i]); + read_pipes[i] = -1; + } + + if(write_pipes[i] > 0) { + close(write_pipes[i]); + write_pipes[i] = -1; + } + } + + if(read_file) { + fclose(read_file); + read_file = nullptr; + } + } + + bool GlobalHotkeysLinux::start() { + const char *grab_type_arg = grab_type_to_arg(grab_type); + const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; + const char *user_homepath = getenv("HOME"); + if(!user_homepath) + user_homepath = "/tmp"; + + if(process_id > 0) + return false; + + if(pipe(read_pipes) == -1) + return false; + + if(pipe(write_pipes) == -1) { + for(int i = 0; i < 2; ++i) { + close(read_pipes[i]); + read_pipes[i] = -1; + } + return false; + } + + const pid_t pid = vfork(); + if(pid == -1) { + perror("Failed to vfork"); + for(int i = 0; i < 2; ++i) { + close(read_pipes[i]); + close(write_pipes[i]); + read_pipes[i] = -1; + write_pipes[i] = -1; + } + return false; + } else if(pid == 0) { /* child */ + dup2(read_pipes[PIPE_WRITE], STDOUT_FILENO); + for(int i = 0; i < 2; ++i) { + close(read_pipes[i]); + } + + dup2(write_pipes[PIPE_READ], STDIN_FILENO); + for(int i = 0; i < 2; ++i) { + close(write_pipes[i]); + } + + if(inside_flatpak) { + const char *args[] = { "flatpak-spawn", "--host", "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/kms-server-proxy", "launch-gsr-global-hotkeys", user_homepath, grab_type_arg, nullptr }; + execvp(args[0], (char* const*)args); + } else { + const char *args[] = { "gsr-global-hotkeys", grab_type_arg, nullptr }; + execvp(args[0], (char* const*)args); + } + + perror("gsr-global-hotkeys"); + _exit(127); + } else { /* parent */ + process_id = pid; + + close(read_pipes[PIPE_WRITE]); + read_pipes[PIPE_WRITE] = -1; + + close(write_pipes[PIPE_READ]); + write_pipes[PIPE_READ] = -1; + + fcntl(read_pipes[PIPE_READ], F_SETFL, fcntl(read_pipes[PIPE_READ], F_GETFL) | O_NONBLOCK); + read_file = fdopen(read_pipes[PIPE_READ], "r"); + if(read_file) + read_pipes[PIPE_READ] = -1; + else + fprintf(stderr, "fdopen failed for read, error: %s\n", strerror(errno)); + } + + return true; + } + + bool GlobalHotkeysLinux::bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) { + if(process_id <= 0) + return false; + + if(bound_actions_by_id.find(id) != bound_actions_by_id.end()) + return false; + + if(id.find(' ') != std::string::npos || id.find('\n') != std::string::npos) { + fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: id \"%s\" contains either space or newline\n", id.c_str()); + return false; + } + + if(hotkey.key == 0) { + //fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: hotkey requires a key\n"); + return false; + } + + if(hotkey.modifiers == 0 && x11_key_is_alpha_numerical(hotkey.key)) { + //fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: hotkey requires a modifier\n"); + return false; + } + + mgl_context *context = mgl_get_context(); + Display *display = (Display*)context->connection; + const uint8_t keycode = x11_keycode_to_linux_keycode(XKeysymToKeycode(display, hotkey.key)); + const std::vector modifiers = modifiers_to_linux_keys(hotkey.modifiers); + const std::string modifiers_command = linux_keys_to_command_string(modifiers.data(), modifiers.size()); + + char command[256]; + int command_size = 0; + if(modifiers_command.empty()) + command_size = snprintf(command, sizeof(command), "bind %s %d\n", id.c_str(), (int)keycode); + else + command_size = snprintf(command, sizeof(command), "bind %s %d+%s\n", id.c_str(), (int)keycode, modifiers_command.c_str()); + + if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) { + fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno)); + return false; + } + + bound_actions_by_id[id] = std::move(callback); + return true; + } + + void GlobalHotkeysLinux::unbind_all_keys() { + if(process_id <= 0) + return; + + if(bound_actions_by_id.empty()) + return; + + char command[32]; + const int command_size = snprintf(command, sizeof(command), "unbind_all\n"); + if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) { + fprintf(stderr, "Error: GlobalHotkeysLinux::unbind_all_keys: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno)); + } + bound_actions_by_id.clear(); + } + + void GlobalHotkeysLinux::poll_events() { + if(process_id <= 0) { + //fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, process has not been started yet. Use GlobalHotkeysLinux::start to start the process first\n"); + return; + } + + if(!read_file) { + //fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, read file hasn't opened\n"); + return; + } + + std::string action; + char buffer[256]; + while(true) { + char *line = fgets(buffer, sizeof(buffer), read_file); + if(!line) + break; + + int line_len = strlen(line); + if(line_len == 0) + continue; + + if(line[line_len - 1] == '\n') { + line[line_len - 1] = '\0'; + --line_len; + } + + action = line; + auto it = bound_actions_by_id.find(action); + if(it != bound_actions_by_id.end()) + it->second(action); + } + } +} diff --git a/src/GlobalHotkeys/GlobalHotkeysX11.cpp b/src/GlobalHotkeys/GlobalHotkeysX11.cpp new file mode 100644 index 0000000..bc79ce8 --- /dev/null +++ b/src/GlobalHotkeys/GlobalHotkeysX11.cpp @@ -0,0 +1,201 @@ +#include "../../include/GlobalHotkeys/GlobalHotkeysX11.hpp" +#include +#include +#include + +namespace gsr { + static bool x_failed = false; + static int xerror_grab_error(Display*, XErrorEvent*) { + x_failed = true; + return 0; + } + + static unsigned int x11_get_numlock_mask(Display *dpy) { + unsigned int numlockmask = 0; + KeyCode numlock_keycode = XKeysymToKeycode(dpy, XK_Num_Lock); + XModifierKeymap *modmap = XGetModifierMapping(dpy); + if(modmap) { + for(int i = 0; i < 8; ++i) { + for(int j = 0; j < modmap->max_keypermod; ++j) { + if(modmap->modifiermap[i * modmap->max_keypermod + j] == numlock_keycode) + numlockmask = (1 << i); + } + } + XFreeModifiermap(modmap); + } + return numlockmask; + } + + static KeySym mgl_key_to_key_sym(mgl::Keyboard::Key key) { + switch(key) { + case mgl::Keyboard::Z: return XK_z; + case mgl::Keyboard::F7: return XK_F7; + case mgl::Keyboard::F8: return XK_F8; + case mgl::Keyboard::F9: return XK_F9; + case mgl::Keyboard::F10: return XK_F10; + default: return None; + } + } + + static uint32_t mgl_key_modifiers_to_x11_modifier_mask(const mgl::Event::KeyEvent &key_event) { + uint32_t mask = 0; + if(key_event.shift) + mask |= ShiftMask; + if(key_event.control) + mask |= ControlMask; + if(key_event.alt) + mask |= Mod1Mask; + if(key_event.system) + mask |= Mod4Mask; + return mask; + } + + static uint32_t modifiers_to_x11_modifiers(uint32_t modifiers) { + uint32_t result = 0; + if(modifiers & HOTKEY_MOD_LSHIFT) + result |= ShiftMask; + if(modifiers & HOTKEY_MOD_RSHIFT) + result |= ShiftMask; + if(modifiers & HOTKEY_MOD_LCTRL) + result |= ControlMask; + if(modifiers & HOTKEY_MOD_RCTRL) + result |= ControlMask; + if(modifiers & HOTKEY_MOD_LALT) + result |= Mod1Mask; + if(modifiers & HOTKEY_MOD_RALT) + result |= Mod5Mask; + if(modifiers & HOTKEY_MOD_LSUPER) + result |= Mod4Mask; + if(modifiers & HOTKEY_MOD_RSUPER) + result |= Mod4Mask; + return result; + } + + GlobalHotkeysX11::GlobalHotkeysX11() { + dpy = XOpenDisplay(NULL); + if(!dpy) + fprintf(stderr, "GlobalHotkeysX11 error: failed to connect to X11 server, global hotkeys wont be available\n"); + } + + GlobalHotkeysX11::~GlobalHotkeysX11() { + if(dpy) { + XCloseDisplay(dpy); + dpy = nullptr; + } + } + + bool GlobalHotkeysX11::bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) { + if(!dpy) + return false; + + auto it = bound_keys_by_id.find(id); + if(it != bound_keys_by_id.end()) + return false; + + x_failed = false; + XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); + + const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(hotkey.modifiers); + unsigned int numlock_mask = x11_get_numlock_mask(dpy); + unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; + for(int i = 0; i < 4; ++i) { + XGrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy), False, GrabModeAsync, GrabModeAsync); + } + XSync(dpy, False); + + if(x_failed) { + for(int i = 0; i < 4; ++i) { + XUngrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy)); + } + XSync(dpy, False); + XSetErrorHandler(prev_xerror); + return false; + } else { + XSetErrorHandler(prev_xerror); + bound_keys_by_id[id] = { hotkey, std::move(callback) }; + return true; + } + } + + void GlobalHotkeysX11::unbind_key_press(const std::string &id) { + if(!dpy) + return; + + auto it = bound_keys_by_id.find(id); + if(it == bound_keys_by_id.end()) + return; + + x_failed = false; + XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); + + const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(it->second.hotkey.modifiers); + unsigned int numlock_mask = x11_get_numlock_mask(dpy); + unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; + for(int i = 0; i < 4; ++i) { + XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy)); + } + XSync(dpy, False); + + XSetErrorHandler(prev_xerror); + bound_keys_by_id.erase(id); + } + + void GlobalHotkeysX11::unbind_all_keys() { + if(!dpy) + return; + + x_failed = false; + XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); + + unsigned int numlock_mask = x11_get_numlock_mask(dpy); + unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; + for(auto it = bound_keys_by_id.begin(); it != bound_keys_by_id.end();) { + const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(it->second.hotkey.modifiers); + for(int i = 0; i < 4; ++i) { + XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy)); + } + } + bound_keys_by_id.clear(); + XSync(dpy, False); + + XSetErrorHandler(prev_xerror); + } + + void GlobalHotkeysX11::poll_events() { + if(!dpy) + return; + + while(XPending(dpy)) { + XNextEvent(dpy, &xev); + if(xev.type == KeyPress) { + const KeySym key_sym = XLookupKeysym(&xev.xkey, 0); + call_hotkey_callback({ (uint32_t)key_sym, xev.xkey.state }); + } + } + } + + bool GlobalHotkeysX11::on_event(mgl::Event &event) { + if(event.type != mgl::Event::KeyPressed) + return true; + + // Note: not all keys are mapped in mgl_key_to_key_sym. If more hotkeys are added or changed then add the key mapping there + const KeySym key_sym = mgl_key_to_key_sym(event.key.code); + const uint32_t modifiers = mgl_key_modifiers_to_x11_modifier_mask(event.key); + return !call_hotkey_callback(Hotkey{(uint32_t)key_sym, modifiers}); + } + + static unsigned int key_state_without_locks(unsigned int key_state) { + return key_state & ~(Mod2Mask|LockMask); + } + + bool GlobalHotkeysX11::call_hotkey_callback(Hotkey hotkey) const { + const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(hotkey.modifiers); + for(const auto &[key, val] : bound_keys_by_id) { + if(val.hotkey.key == hotkey.key && key_state_without_locks(modifiers_to_x11_modifiers(val.hotkey.modifiers)) == key_state_without_locks(modifiers_x11)) { + val.callback(key); + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/src/GlobalHotkeysJoystick.cpp b/src/GlobalHotkeysJoystick.cpp deleted file mode 100644 index 822a73a..0000000 --- a/src/GlobalHotkeysJoystick.cpp +++ /dev/null @@ -1,317 +0,0 @@ -#include "../include/GlobalHotkeysJoystick.hpp" -#include -#include -#include -#include -#include - -namespace gsr { - static constexpr int button_pressed = 1; - static constexpr int cross_button = 0; - static constexpr int triangle_button = 2; - static constexpr int options_button = 9; - static constexpr int playstation_button = 10; - static constexpr int axis_up_down = 7; - static constexpr int axis_left_right = 6; - - // 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"); - } - - if(save_1_min_replay) { - save_1_min_replay = false; - auto it = bound_actions_by_id.find("save_1_min_replay"); - if(it != bound_actions_by_id.end()) - it->second("save_1_min_replay"); - } - - if(save_10_min_replay) { - save_10_min_replay = false; - auto it = bound_actions_by_id.find("save_10_min_replay"); - if(it != bound_actions_by_id.end()) - it->second("save_10_min_replay"); - } - - if(take_screenshot) { - take_screenshot = false; - auto it = bound_actions_by_id.find("take_screenshot"); - if(it != bound_actions_by_id.end()) - it->second("take_screenshot"); - } - - if(toggle_record) { - toggle_record = false; - auto it = bound_actions_by_id.find("toggle_record"); - if(it != bound_actions_by_id.end()) - it->second("toggle_record"); - } - - if(toggle_replay) { - toggle_replay = false; - auto it = bound_actions_by_id.find("toggle_replay"); - if(it != bound_actions_by_id.end()) - it->second("toggle_replay"); - } - - if(toggle_show) { - toggle_show = false; - auto it = bound_actions_by_id.find("toggle_show"); - if(it != bound_actions_by_id.end()) - it->second("toggle_show"); - } - } - - 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) == JS_EVENT_BUTTON) { - switch(event.number) { - case playstation_button: { - playstation_button_pressed = event.value == button_pressed; - break; - } - case options_button: { - if(playstation_button_pressed && event.value == button_pressed) - toggle_show = true; - break; - } - case cross_button: { - if(playstation_button_pressed && event.value == button_pressed) - save_1_min_replay = true; - break; - } - case triangle_button: { - if(playstation_button_pressed && event.value == button_pressed) - save_10_min_replay = true; - break; - } - } - } else if((event.type & JS_EVENT_AXIS) == JS_EVENT_AXIS && playstation_button_pressed) { - const int trigger_threshold = 16383; - const bool prev_up_pressed = up_pressed; - const bool prev_down_pressed = down_pressed; - const bool prev_left_pressed = left_pressed; - const bool prev_right_pressed = right_pressed; - - if(event.number == axis_up_down) { - up_pressed = event.value <= -trigger_threshold; - down_pressed = event.value >= trigger_threshold; - } else if(event.number == axis_left_right) { - left_pressed = event.value <= -trigger_threshold; - right_pressed = event.value >= trigger_threshold; - } - - if(up_pressed && !prev_up_pressed) - take_screenshot = true; - else if(down_pressed && !prev_down_pressed) - save_replay = true; - else if(left_pressed && !prev_left_pressed) - toggle_record = true; - else if(right_pressed && !prev_right_pressed) - toggle_replay = true; - } - } - - 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; - } -} diff --git a/src/GlobalHotkeysLinux.cpp b/src/GlobalHotkeysLinux.cpp deleted file mode 100644 index d780916..0000000 --- a/src/GlobalHotkeysLinux.cpp +++ /dev/null @@ -1,275 +0,0 @@ -#include "../include/GlobalHotkeysLinux.hpp" -#include -#include -#include -#include - -extern "C" { -#include -} -#include -#include -#include - -#define PIPE_READ 0 -#define PIPE_WRITE 1 - -namespace gsr { - static const char* grab_type_to_arg(GlobalHotkeysLinux::GrabType grab_type) { - switch(grab_type) { - case GlobalHotkeysLinux::GrabType::ALL: return "--all"; - case GlobalHotkeysLinux::GrabType::VIRTUAL: return "--virtual"; - } - return "--all"; - } - - static inline uint8_t x11_keycode_to_linux_keycode(uint8_t code) { - return code - 8; - } - - static std::vector modifiers_to_linux_keys(uint32_t modifiers) { - std::vector result; - if(modifiers & HOTKEY_MOD_LSHIFT) - result.push_back(KEY_LEFTSHIFT); - if(modifiers & HOTKEY_MOD_RSHIFT) - result.push_back(KEY_RIGHTSHIFT); - if(modifiers & HOTKEY_MOD_LCTRL) - result.push_back(KEY_LEFTCTRL); - if(modifiers & HOTKEY_MOD_RCTRL) - result.push_back(KEY_RIGHTCTRL); - if(modifiers & HOTKEY_MOD_LALT) - result.push_back(KEY_LEFTALT); - if(modifiers & HOTKEY_MOD_RALT) - result.push_back(KEY_RIGHTALT); - if(modifiers & HOTKEY_MOD_LSUPER) - result.push_back(KEY_LEFTMETA); - if(modifiers & HOTKEY_MOD_RSUPER) - result.push_back(KEY_RIGHTMETA); - return result; - } - - static std::string linux_keys_to_command_string(const uint8_t *keys, size_t size) { - std::string result; - for(size_t i = 0; i < size; ++i) { - if(!result.empty()) - result += "+"; - result += std::to_string(keys[i]); - } - return result; - } - - static bool x11_key_is_alpha_numerical(KeySym keysym) { - return (keysym >= XK_A && keysym <= XK_Z) || (keysym >= XK_a && keysym <= XK_z) || (keysym >= XK_0 && keysym <= XK_9); - } - - GlobalHotkeysLinux::GlobalHotkeysLinux(GrabType grab_type) : grab_type(grab_type) { - for(int i = 0; i < 2; ++i) { - read_pipes[i] = -1; - write_pipes[i] = -1; - } - } - - GlobalHotkeysLinux::~GlobalHotkeysLinux() { - if(write_pipes[PIPE_WRITE] > 0) { - char command[32]; - const int command_size = snprintf(command, sizeof(command), "exit\n"); - if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) { - fprintf(stderr, "Error: GlobalHotkeysLinux::~GlobalHotkeysLinux: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno)); - close_fds(); - } - } else { - close_fds(); - } - - if(process_id > 0) { - int status; - waitpid(process_id, &status, 0); - } - - close_fds(); - } - - void GlobalHotkeysLinux::close_fds() { - for(int i = 0; i < 2; ++i) { - if(read_pipes[i] > 0) { - close(read_pipes[i]); - read_pipes[i] = -1; - } - - if(write_pipes[i] > 0) { - close(write_pipes[i]); - write_pipes[i] = -1; - } - } - - if(read_file) { - fclose(read_file); - read_file = nullptr; - } - } - - bool GlobalHotkeysLinux::start() { - const char *grab_type_arg = grab_type_to_arg(grab_type); - const bool inside_flatpak = getenv("FLATPAK_ID") != NULL; - const char *user_homepath = getenv("HOME"); - if(!user_homepath) - user_homepath = "/tmp"; - - if(process_id > 0) - return false; - - if(pipe(read_pipes) == -1) - return false; - - if(pipe(write_pipes) == -1) { - for(int i = 0; i < 2; ++i) { - close(read_pipes[i]); - read_pipes[i] = -1; - } - return false; - } - - const pid_t pid = vfork(); - if(pid == -1) { - perror("Failed to vfork"); - for(int i = 0; i < 2; ++i) { - close(read_pipes[i]); - close(write_pipes[i]); - read_pipes[i] = -1; - write_pipes[i] = -1; - } - return false; - } else if(pid == 0) { /* child */ - dup2(read_pipes[PIPE_WRITE], STDOUT_FILENO); - for(int i = 0; i < 2; ++i) { - close(read_pipes[i]); - } - - dup2(write_pipes[PIPE_READ], STDIN_FILENO); - for(int i = 0; i < 2; ++i) { - close(write_pipes[i]); - } - - if(inside_flatpak) { - const char *args[] = { "flatpak-spawn", "--host", "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/kms-server-proxy", "launch-gsr-global-hotkeys", user_homepath, grab_type_arg, nullptr }; - execvp(args[0], (char* const*)args); - } else { - const char *args[] = { "gsr-global-hotkeys", grab_type_arg, nullptr }; - execvp(args[0], (char* const*)args); - } - - perror("gsr-global-hotkeys"); - _exit(127); - } else { /* parent */ - process_id = pid; - - close(read_pipes[PIPE_WRITE]); - read_pipes[PIPE_WRITE] = -1; - - close(write_pipes[PIPE_READ]); - write_pipes[PIPE_READ] = -1; - - fcntl(read_pipes[PIPE_READ], F_SETFL, fcntl(read_pipes[PIPE_READ], F_GETFL) | O_NONBLOCK); - read_file = fdopen(read_pipes[PIPE_READ], "r"); - if(read_file) - read_pipes[PIPE_READ] = -1; - else - fprintf(stderr, "fdopen failed for read, error: %s\n", strerror(errno)); - } - - return true; - } - - bool GlobalHotkeysLinux::bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) { - if(process_id <= 0) - return false; - - if(bound_actions_by_id.find(id) != bound_actions_by_id.end()) - return false; - - if(id.find(' ') != std::string::npos || id.find('\n') != std::string::npos) { - fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: id \"%s\" contains either space or newline\n", id.c_str()); - return false; - } - - if(hotkey.key == 0) { - //fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: hotkey requires a key\n"); - return false; - } - - if(hotkey.modifiers == 0 && x11_key_is_alpha_numerical(hotkey.key)) { - //fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: hotkey requires a modifier\n"); - return false; - } - - mgl_context *context = mgl_get_context(); - Display *display = (Display*)context->connection; - const uint8_t keycode = x11_keycode_to_linux_keycode(XKeysymToKeycode(display, hotkey.key)); - const std::vector modifiers = modifiers_to_linux_keys(hotkey.modifiers); - const std::string modifiers_command = linux_keys_to_command_string(modifiers.data(), modifiers.size()); - - char command[256]; - int command_size = 0; - if(modifiers_command.empty()) - command_size = snprintf(command, sizeof(command), "bind %s %d\n", id.c_str(), (int)keycode); - else - command_size = snprintf(command, sizeof(command), "bind %s %d+%s\n", id.c_str(), (int)keycode, modifiers_command.c_str()); - - if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) { - fprintf(stderr, "Error: GlobalHotkeysLinux::bind_key_press: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno)); - return false; - } - - bound_actions_by_id[id] = std::move(callback); - return true; - } - - void GlobalHotkeysLinux::unbind_all_keys() { - if(process_id <= 0) - return; - - if(bound_actions_by_id.empty()) - return; - - char command[32]; - const int command_size = snprintf(command, sizeof(command), "unbind_all\n"); - if(write(write_pipes[PIPE_WRITE], command, command_size) != command_size) { - fprintf(stderr, "Error: GlobalHotkeysLinux::unbind_all_keys: failed to write command to gsr-global-hotkeys, error: %s\n", strerror(errno)); - } - bound_actions_by_id.clear(); - } - - void GlobalHotkeysLinux::poll_events() { - if(process_id <= 0) { - //fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, process has not been started yet. Use GlobalHotkeysLinux::start to start the process first\n"); - return; - } - - if(!read_file) { - //fprintf(stderr, "error: GlobalHotkeysLinux::poll_events failed, read file hasn't opened\n"); - return; - } - - std::string action; - char buffer[256]; - while(true) { - char *line = fgets(buffer, sizeof(buffer), read_file); - if(!line) - break; - - int line_len = strlen(line); - if(line_len == 0) - continue; - - if(line[line_len - 1] == '\n') { - line[line_len - 1] = '\0'; - --line_len; - } - - action = line; - auto it = bound_actions_by_id.find(action); - if(it != bound_actions_by_id.end()) - it->second(action); - } - } -} diff --git a/src/GlobalHotkeysX11.cpp b/src/GlobalHotkeysX11.cpp deleted file mode 100644 index 9af2607..0000000 --- a/src/GlobalHotkeysX11.cpp +++ /dev/null @@ -1,201 +0,0 @@ -#include "../include/GlobalHotkeysX11.hpp" -#include -#include -#include - -namespace gsr { - static bool x_failed = false; - static int xerror_grab_error(Display*, XErrorEvent*) { - x_failed = true; - return 0; - } - - static unsigned int x11_get_numlock_mask(Display *dpy) { - unsigned int numlockmask = 0; - KeyCode numlock_keycode = XKeysymToKeycode(dpy, XK_Num_Lock); - XModifierKeymap *modmap = XGetModifierMapping(dpy); - if(modmap) { - for(int i = 0; i < 8; ++i) { - for(int j = 0; j < modmap->max_keypermod; ++j) { - if(modmap->modifiermap[i * modmap->max_keypermod + j] == numlock_keycode) - numlockmask = (1 << i); - } - } - XFreeModifiermap(modmap); - } - return numlockmask; - } - - static KeySym mgl_key_to_key_sym(mgl::Keyboard::Key key) { - switch(key) { - case mgl::Keyboard::Z: return XK_z; - case mgl::Keyboard::F7: return XK_F7; - case mgl::Keyboard::F8: return XK_F8; - case mgl::Keyboard::F9: return XK_F9; - case mgl::Keyboard::F10: return XK_F10; - default: return None; - } - } - - static uint32_t mgl_key_modifiers_to_x11_modifier_mask(const mgl::Event::KeyEvent &key_event) { - uint32_t mask = 0; - if(key_event.shift) - mask |= ShiftMask; - if(key_event.control) - mask |= ControlMask; - if(key_event.alt) - mask |= Mod1Mask; - if(key_event.system) - mask |= Mod4Mask; - return mask; - } - - static uint32_t modifiers_to_x11_modifiers(uint32_t modifiers) { - uint32_t result = 0; - if(modifiers & HOTKEY_MOD_LSHIFT) - result |= ShiftMask; - if(modifiers & HOTKEY_MOD_RSHIFT) - result |= ShiftMask; - if(modifiers & HOTKEY_MOD_LCTRL) - result |= ControlMask; - if(modifiers & HOTKEY_MOD_RCTRL) - result |= ControlMask; - if(modifiers & HOTKEY_MOD_LALT) - result |= Mod1Mask; - if(modifiers & HOTKEY_MOD_RALT) - result |= Mod5Mask; - if(modifiers & HOTKEY_MOD_LSUPER) - result |= Mod4Mask; - if(modifiers & HOTKEY_MOD_RSUPER) - result |= Mod4Mask; - return result; - } - - GlobalHotkeysX11::GlobalHotkeysX11() { - dpy = XOpenDisplay(NULL); - if(!dpy) - fprintf(stderr, "GlobalHotkeysX11 error: failed to connect to X11 server, global hotkeys wont be available\n"); - } - - GlobalHotkeysX11::~GlobalHotkeysX11() { - if(dpy) { - XCloseDisplay(dpy); - dpy = nullptr; - } - } - - bool GlobalHotkeysX11::bind_key_press(Hotkey hotkey, const std::string &id, GlobalHotkeyCallback callback) { - if(!dpy) - return false; - - auto it = bound_keys_by_id.find(id); - if(it != bound_keys_by_id.end()) - return false; - - x_failed = false; - XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); - - const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(hotkey.modifiers); - unsigned int numlock_mask = x11_get_numlock_mask(dpy); - unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; - for(int i = 0; i < 4; ++i) { - XGrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy), False, GrabModeAsync, GrabModeAsync); - } - XSync(dpy, False); - - if(x_failed) { - for(int i = 0; i < 4; ++i) { - XUngrabKey(dpy, XKeysymToKeycode(dpy, hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy)); - } - XSync(dpy, False); - XSetErrorHandler(prev_xerror); - return false; - } else { - XSetErrorHandler(prev_xerror); - bound_keys_by_id[id] = { hotkey, std::move(callback) }; - return true; - } - } - - void GlobalHotkeysX11::unbind_key_press(const std::string &id) { - if(!dpy) - return; - - auto it = bound_keys_by_id.find(id); - if(it == bound_keys_by_id.end()) - return; - - x_failed = false; - XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); - - const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(it->second.hotkey.modifiers); - unsigned int numlock_mask = x11_get_numlock_mask(dpy); - unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; - for(int i = 0; i < 4; ++i) { - XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy)); - } - XSync(dpy, False); - - XSetErrorHandler(prev_xerror); - bound_keys_by_id.erase(id); - } - - void GlobalHotkeysX11::unbind_all_keys() { - if(!dpy) - return; - - x_failed = false; - XErrorHandler prev_xerror = XSetErrorHandler(xerror_grab_error); - - unsigned int numlock_mask = x11_get_numlock_mask(dpy); - unsigned int modifiers[] = { 0, LockMask, numlock_mask, numlock_mask|LockMask }; - for(auto it = bound_keys_by_id.begin(); it != bound_keys_by_id.end();) { - const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(it->second.hotkey.modifiers); - for(int i = 0; i < 4; ++i) { - XUngrabKey(dpy, XKeysymToKeycode(dpy, it->second.hotkey.key), modifiers_x11 | modifiers[i], DefaultRootWindow(dpy)); - } - } - bound_keys_by_id.clear(); - XSync(dpy, False); - - XSetErrorHandler(prev_xerror); - } - - void GlobalHotkeysX11::poll_events() { - if(!dpy) - return; - - while(XPending(dpy)) { - XNextEvent(dpy, &xev); - if(xev.type == KeyPress) { - const KeySym key_sym = XLookupKeysym(&xev.xkey, 0); - call_hotkey_callback({ (uint32_t)key_sym, xev.xkey.state }); - } - } - } - - bool GlobalHotkeysX11::on_event(mgl::Event &event) { - if(event.type != mgl::Event::KeyPressed) - return true; - - // Note: not all keys are mapped in mgl_key_to_key_sym. If more hotkeys are added or changed then add the key mapping there - const KeySym key_sym = mgl_key_to_key_sym(event.key.code); - const uint32_t modifiers = mgl_key_modifiers_to_x11_modifier_mask(event.key); - return !call_hotkey_callback(Hotkey{(uint32_t)key_sym, modifiers}); - } - - static unsigned int key_state_without_locks(unsigned int key_state) { - return key_state & ~(Mod2Mask|LockMask); - } - - bool GlobalHotkeysX11::call_hotkey_callback(Hotkey hotkey) const { - const uint32_t modifiers_x11 = modifiers_to_x11_modifiers(hotkey.modifiers); - for(const auto &[key, val] : bound_keys_by_id) { - if(val.hotkey.key == hotkey.key && key_state_without_locks(modifiers_to_x11_modifiers(val.hotkey.modifiers)) == key_state_without_locks(modifiers_x11)) { - val.callback(key); - return true; - } - } - return false; - } -} \ No newline at end of file diff --git a/src/Overlay.cpp b/src/Overlay.cpp index 487b6bc..c423125 100644 --- a/src/Overlay.cpp +++ b/src/Overlay.cpp @@ -12,10 +12,10 @@ #include "../include/gui/Utils.hpp" #include "../include/gui/PageStack.hpp" #include "../include/WindowUtils.hpp" -#include "../include/GlobalHotkeys.hpp" -#include "../include/GlobalHotkeysLinux.hpp" -#include "../include/CursorTrackerX11.hpp" -#include "../include/CursorTrackerWayland.hpp" +#include "../include/GlobalHotkeys/GlobalHotkeys.hpp" +#include "../include/GlobalHotkeys/GlobalHotkeysLinux.hpp" +#include "../include/CursorTracker/CursorTrackerX11.hpp" +#include "../include/CursorTracker/CursorTrackerWayland.hpp" #include #include @@ -207,24 +207,21 @@ namespace gsr { return false; }*/ - // Returns the first monitor if not found. Assumes there is at least one monitor connected. static const Monitor* find_monitor_at_position(const std::vector &monitors, mgl::vec2i pos) { assert(!monitors.empty()); for(const Monitor &monitor : monitors) { if(mgl::IntRect(monitor.position, monitor.size).contains(pos)) return &monitor; } - return &monitors.front(); + return nullptr; } - // Returns the first monitor if not found. Assumes there is at least one monitor connected. static const Monitor* find_monitor_by_name(const std::vector &monitors, const std::string &name) { - assert(!monitors.empty()); for(const Monitor &monitor : monitors) { if(monitor.name == name) return &monitor; } - return &monitors.front(); + return nullptr; } static std::string get_power_supply_online_filepath() { @@ -894,10 +891,14 @@ namespace gsr { const Monitor *focused_monitor = nullptr; if(cursor_info) { focused_monitor = find_monitor_by_name(monitors, cursor_info->monitor_name); + if(!focused_monitor) + focused_monitor = &monitors.front(); cursor_position = cursor_info->position; } else { const mgl::vec2i monitor_position_query_value = (x11_cursor_window || gsr_info.system_info.display_server != DisplayServer::WAYLAND) ? cursor_position : create_window_get_center_position(display); focused_monitor = find_monitor_at_position(monitors, monitor_position_query_value); + if(!focused_monitor) + focused_monitor = &monitors.front(); } // Wayland doesn't allow XGrabPointer/XGrabKeyboard when a wayland application is focused. diff --git a/src/gui/GlobalSettingsPage.cpp b/src/gui/GlobalSettingsPage.cpp index ccebb92..5444ae5 100644 --- a/src/gui/GlobalSettingsPage.cpp +++ b/src/gui/GlobalSettingsPage.cpp @@ -1,7 +1,6 @@ #include "../../include/gui/GlobalSettingsPage.hpp" #include "../../include/Overlay.hpp" -#include "../../include/GlobalHotkeys.hpp" #include "../../include/Theme.hpp" #include "../../include/Process.hpp" #include "../../include/gui/GsrPage.hpp" -- cgit v1.2.3-70-g09d2