From da4925b23e7ebd6df35cdb0ba39ac8cc1701a102 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 16 Aug 2024 19:37:00 +0200 Subject: Allow capture of external monitors on a laptop with dedicated gpu (prime) on x11, fix cursor not visible on some wayland compositors (hyprland) with multiple monitors --- src/capture/kms.c | 67 +++++++++++++++++++++++++++++++++++++++++------- src/capture/xcomposite.c | 10 ++++---- src/egl.c | 47 ++++++++++++++++++++++++++++++--- src/main.cpp | 35 +++++++++++++++---------- src/utils.c | 54 +++++++++++++++++++++++--------------- 5 files changed, 161 insertions(+), 52 deletions(-) (limited to 'src') diff --git a/src/capture/kms.c b/src/capture/kms.c index 1616bd7..c677bbb 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -1,8 +1,10 @@ #include "../../include/capture/kms.h" #include "../../include/utils.h" #include "../../include/color_conversion.h" +#include "../../include/cursor.h" #include "../../kms/client/kms_client.h" +#include #include #include #include @@ -45,6 +47,9 @@ typedef struct { struct hdr_output_metadata hdr_metadata; bool hdr_metadata_set; + + gsr_cursor x11_cursor; + XEvent xev; } gsr_capture_kms; static void gsr_capture_kms_cleanup_kms_fds(gsr_capture_kms *self) { @@ -79,6 +84,7 @@ static void gsr_capture_kms_stop(gsr_capture_kms *self) { gsr_capture_kms_cleanup_kms_fds(self); gsr_kms_client_deinit(&self->kms_client); + gsr_cursor_deinit(&self->x11_cursor); } static int max_int(int a, int b) { @@ -151,14 +157,19 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c if(kms_init_res != 0) return kms_init_res; + const bool is_x11 = gsr_egl_get_display_server(self->params.egl) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; + if(is_x11) + gsr_cursor_init(&self->x11_cursor, self->params.egl, self->params.egl->x11.dpy); + MonitorCallbackUserdata monitor_callback_userdata = { &self->monitor_id, self->params.display_to_capture, strlen(self->params.display_to_capture), 0, }; - for_each_active_monitor_output(self->params.egl, GSR_CONNECTION_DRM, monitor_callback, &monitor_callback_userdata); + for_each_active_monitor_output(self->params.egl, connection_type, monitor_callback, &monitor_callback_userdata); - if(!get_monitor_by_name(self->params.egl, GSR_CONNECTION_DRM, self->params.display_to_capture, &monitor)) { + if(!get_monitor_by_name(self->params.egl, connection_type, self->params.display_to_capture, &monitor)) { fprintf(stderr, "gsr error: gsr_capture_kms_start: failed to find monitor by name \"%s\"\n", self->params.display_to_capture); gsr_capture_kms_stop(self); return -1; @@ -168,7 +179,8 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c self->monitor_rotation = drm_monitor_get_display_server_rotation(self->params.egl, &monitor); self->capture_pos = monitor.pos; - if(self->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270) { + /* Monitor size is already rotated on x11 when the monitor is rotated, no need to apply it ourselves */ + if(!is_x11 && (self->monitor_rotation == GSR_MONITOR_ROT_90 || self->monitor_rotation == GSR_MONITOR_ROT_270)) { self->capture_size.x = monitor.size.y; self->capture_size.y = monitor.size.x; } else { @@ -186,6 +198,16 @@ static int gsr_capture_kms_start(gsr_capture *cap, AVCodecContext *video_codec_c return 0; } +static void gsr_capture_kms_tick(gsr_capture *cap, AVCodecContext *video_codec_context) { + (void)video_codec_context; + gsr_capture_kms *self = cap->priv; + + while(XPending(self->params.egl->x11.dpy)) { + XNextEvent(self->params.egl->x11.dpy, &self->xev); + gsr_cursor_update(&self->x11_cursor, &self->xev); + } +} + static float monitor_rotation_to_radians(gsr_monitor_rotation rot) { switch(rot) { case GSR_MONITOR_ROT_0: return 0.0f; @@ -238,12 +260,16 @@ static gsr_kms_response_item* find_largest_drm(gsr_kms_response *kms_response) { return largest_drm; } -static gsr_kms_response_item* find_cursor_drm(gsr_kms_response *kms_response) { +static gsr_kms_response_item* find_cursor_drm(gsr_kms_response *kms_response, uint32_t connector_id) { + gsr_kms_response_item *cursor_drm = NULL; for(int i = 0; i < kms_response->num_items; ++i) { - if(kms_response->items[i].is_cursor) - return &kms_response->items[i]; + if(kms_response->items[i].is_cursor) { + cursor_drm = &kms_response->items[i]; + if(kms_response->items[i].connector_id == connector_id) + break; + } } - return NULL; + return cursor_drm; } static bool hdr_metadata_is_supported_format(const struct hdr_output_metadata *hdr_metadata) { @@ -329,7 +355,7 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c capture_is_combined_plane = true; } - cursor_drm_fd = find_cursor_drm(&self->kms_response); + cursor_drm_fd = find_cursor_drm(&self->kms_response, drm_fd->connector_id); if(!drm_fd) return -1; @@ -337,6 +363,10 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c if(!capture_is_combined_plane && cursor_drm_fd && cursor_drm_fd->connector_id != drm_fd->connector_id) cursor_drm_fd = NULL; + const bool is_x11 = gsr_egl_get_display_server(self->params.egl) == GSR_DISPLAY_SERVER_X11; + if(is_x11) + cursor_drm_fd = NULL; + if(drm_fd->has_hdr_metadata && self->params.hdr && hdr_metadata_is_supported_format(&drm_fd->hdr_metadata)) gsr_kms_set_hdr_metadata(self, drm_fd); @@ -471,6 +501,25 @@ static int gsr_capture_kms_capture(gsr_capture *cap, AVFrame *frame, gsr_color_c self->params.egl->glDisable(GL_SCISSOR_TEST); } + if(self->params.record_cursor && !cursor_drm_fd && is_x11) { + gsr_cursor_tick(&self->x11_cursor, DefaultRootWindow(self->params.egl->x11.dpy)); + + const vec2i cursor_pos = { + target_x + self->x11_cursor.position.x - self->x11_cursor.hotspot.x - capture_pos.x, + target_y + self->x11_cursor.position.y - self->x11_cursor.hotspot.y - capture_pos.y + }; + + self->params.egl->glEnable(GL_SCISSOR_TEST); + self->params.egl->glScissor(target_x, target_y, self->capture_size.x, self->capture_size.y); + + gsr_color_conversion_draw(color_conversion, self->x11_cursor.texture_id, + cursor_pos, self->x11_cursor.size, + (vec2i){0, 0}, self->x11_cursor.size, + 0.0f, false); + + self->params.egl->glDisable(GL_SCISSOR_TEST); + } + //self->params.egl->glFlush(); //self->params.egl->glFinish(); @@ -566,7 +615,7 @@ gsr_capture* gsr_capture_kms_create(const gsr_capture_kms_params *params) { *cap = (gsr_capture) { .start = gsr_capture_kms_start, - .tick = NULL, + .tick = gsr_capture_kms_tick, .should_stop = gsr_capture_kms_should_stop, .capture = gsr_capture_kms_capture, .capture_end = gsr_capture_kms_capture_end, diff --git a/src/capture/xcomposite.c b/src/capture/xcomposite.c index a81c19f..899ffe0 100644 --- a/src/capture/xcomposite.c +++ b/src/capture/xcomposite.c @@ -325,11 +325,6 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_ const int target_x = max_int(0, frame->width / 2 - self->texture_size.x / 2); const int target_y = max_int(0, frame->height / 2 - self->texture_size.y / 2); - const vec2i cursor_pos = { - target_x + self->cursor.position.x - self->cursor.hotspot.x, - target_y + self->cursor.position.y - self->cursor.hotspot.y - }; - gsr_color_conversion_draw(color_conversion, window_texture_get_opengl_texture_id(&self->window_texture), (vec2i){target_x, target_y}, self->texture_size, (vec2i){0, 0}, self->texture_size, @@ -338,6 +333,11 @@ static int gsr_capture_xcomposite_capture(gsr_capture *cap, AVFrame *frame, gsr_ if(self->params.record_cursor && self->cursor.visible) { gsr_cursor_tick(&self->cursor, self->window); + const vec2i cursor_pos = { + target_x + self->cursor.position.x - self->cursor.hotspot.x, + target_y + self->cursor.position.y - self->cursor.hotspot.y + }; + self->params.egl->glEnable(GL_SCISSOR_TEST); self->params.egl->glScissor(target_x, target_y, self->texture_size.x, self->texture_size.y); diff --git a/src/egl.c b/src/egl.c index 3dda33d..2f5230f 100644 --- a/src/egl.c +++ b/src/egl.c @@ -1,16 +1,17 @@ #include "../include/egl.h" #include "../include/library_loader.h" #include "../include/utils.h" + #include #include #include #include #include +#include +#include #include #include -#include -#include // TODO: rename gsr_egl to something else since this includes both egl and glx and in the future maybe vulkan too @@ -93,7 +94,7 @@ static void registry_add_object(void *data, struct wl_registry *registry, uint32 } if(egl->wayland.num_outputs == GSR_MAX_OUTPUTS) { - fprintf(stderr, "gsr warning: reached maximum outputs (32), ignoring output %u\n", name); + fprintf(stderr, "gsr warning: reached maximum outputs (%d), ignoring output %u\n", GSR_MAX_OUTPUTS, name); return; } @@ -134,6 +135,26 @@ static void reset_cap_nice(void) { cap_free(caps); } +static void store_x11_monitor(const gsr_monitor *monitor, void *userdata) { + gsr_egl *egl = userdata; + if(egl->x11.num_outputs == GSR_MAX_OUTPUTS) { + fprintf(stderr, "gsr warning: reached maximum outputs (%d), ignoring output %s\n", GSR_MAX_OUTPUTS, monitor->name); + return; + } + + char *monitor_name = strdup(monitor->name); + if(!monitor_name) + return; + + const int index = egl->x11.num_outputs; + egl->x11.outputs[index].name = monitor_name; + egl->x11.outputs[index].pos = monitor->pos; + egl->x11.outputs[index].size = monitor->size; + egl->x11.outputs[index].connector_id = monitor->connector_id; + egl->x11.outputs[index].rotation = monitor->rotation; + ++egl->x11.num_outputs; +} + #define GLX_DRAWABLE_TYPE 0x8010 #define GLX_RENDER_TYPE 0x8011 #define GLX_RGBA_BIT 0x00000001 @@ -271,6 +292,11 @@ static bool gsr_egl_create_window(gsr_egl *self, bool wayland) { goto fail; } + if(!wayland) { + self->x11.num_outputs = 0; + for_each_active_monitor_output_x11_not_cached(self->x11.dpy, store_x11_monitor, self); + } + reset_cap_nice(); return true; @@ -587,6 +613,14 @@ void gsr_egl_unload(gsr_egl *self) { self->x11.window = None; } + for(int i = 0; i < self->x11.num_outputs; ++i) { + if(self->x11.outputs[i].name) { + free(self->x11.outputs[i].name); + self->x11.outputs[i].name = NULL; + } + } + self->x11.num_outputs = 0; + if(self->wayland.window) { wl_egl_window_destroy(self->wayland.window); self->wayland.window = NULL; @@ -658,3 +692,10 @@ void gsr_egl_swap_buffers(gsr_egl *self) { self->glXSwapBuffers(self->x11.dpy, self->x11.window); } } + +gsr_display_server gsr_egl_get_display_server(const gsr_egl *egl) { + if(egl->wayland.dpy) + return GSR_DISPLAY_SERVER_WAYLAND; + else + return GSR_DISPLAY_SERVER_X11; +} diff --git a/src/main.cpp b/src/main.cpp index 760dcd6..cd1ec5d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1650,7 +1650,7 @@ static bool is_xwayland(Display *display) { return true; bool xwayland_found = false; - for_each_active_monitor_output_x11(display, xwayland_check_callback, &xwayland_found); + for_each_active_monitor_output_x11_not_cached(display, xwayland_check_callback, &xwayland_found); return xwayland_found; } @@ -1716,7 +1716,7 @@ typedef struct { static void output_monitor_info(const gsr_monitor *monitor, void *userdata) { const capture_options_callback *options = (capture_options_callback*)userdata; - if(monitor_capture_use_drm(options->egl, options->wayland)) { + if(options->wayland && monitor_capture_use_drm(options->egl, options->wayland)) { vec2i monitor_size = monitor->size; const gsr_monitor_rotation rot = drm_monitor_get_display_server_rotation(options->egl, monitor); if(rot == GSR_MONITOR_ROT_90 || rot == GSR_MONITOR_ROT_270) @@ -1737,7 +1737,9 @@ static void list_supported_capture_options(gsr_egl *egl, bool wayland) { options.wayland = wayland; options.egl = egl; if(monitor_capture_use_drm(egl, wayland)) { - for_each_active_monitor_output(egl, GSR_CONNECTION_DRM, output_monitor_info, &options); + const bool is_x11 = gsr_egl_get_display_server(egl) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; + for_each_active_monitor_output(egl, connection_type, output_monitor_info, &options); } else { puts("screen"); // All monitors in one, only available on Nvidia X11 for_each_active_monitor_output(egl, GSR_CONNECTION_X11, output_monitor_info, &options); @@ -1775,10 +1777,11 @@ static void info_command() { if(!wayland) wayland = is_xwayland(dpy); - if(!wayland) { + if(!wayland && is_using_prime_run()) { // Disable prime-run and similar options as it doesn't work, the monitor to capture has to be run on the same device. // This is fine on wayland since nvidia uses drm interface there and the monitor query checks the monitors connected // to the drm device. + fprintf(stderr, "Warning: use of prime-run on X11 is not supported. Disabling prime-run\n"); disable_prime_run(); } @@ -1890,10 +1893,13 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre #endif } else if(contains_non_hex_number(window_str)) { if(monitor_capture_use_drm(egl, wayland)) { + const bool is_x11 = gsr_egl_get_display_server(egl) == GSR_DISPLAY_SERVER_X11; + const gsr_connection_type connection_type = is_x11 ? GSR_CONNECTION_X11 : GSR_CONNECTION_DRM; + if(strcmp(window_str, "screen") == 0) { FirstOutputCallback first_output; first_output.output_name = NULL; - for_each_active_monitor_output(egl, GSR_CONNECTION_DRM, get_first_output, &first_output); + for_each_active_monitor_output(egl, connection_type, get_first_output, &first_output); if(first_output.output_name) { window_str = first_output.output_name; @@ -1901,14 +1907,14 @@ static gsr_capture* create_capture_impl(const char *window_str, const char *scre fprintf(stderr, "Error: no usable output found\n"); _exit(1); } - } - - gsr_monitor gmon; - if(!get_monitor_by_name(egl, GSR_CONNECTION_DRM, window_str, &gmon)) { - fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); - fprintf(stderr, " \"screen\"\n"); - for_each_active_monitor_output(egl, GSR_CONNECTION_DRM, monitor_output_callback_print, NULL); - _exit(1); + } else { + gsr_monitor gmon; + if(!get_monitor_by_name(egl, connection_type, window_str, &gmon)) { + fprintf(stderr, "gsr error: display \"%s\" not found, expected one of:\n", window_str); + fprintf(stderr, " \"screen\"\n"); + for_each_active_monitor_output(egl, connection_type, monitor_output_callback_print, NULL); + _exit(1); + } } } else { if(strcmp(window_str, "screen") != 0 && strcmp(window_str, "screen-direct") != 0 && strcmp(window_str, "screen-direct-force") != 0) { @@ -2464,10 +2470,11 @@ int main(int argc, char **argv) { if(!wayland) wayland = is_xwayland(dpy); - if(!wayland) { + if(!wayland && is_using_prime_run()) { // Disable prime-run and similar options as it doesn't work, the monitor to capture has to be run on the same device. // This is fine on wayland since nvidia uses drm interface there and the monitor query checks the monitors connected // to the drm device. + fprintf(stderr, "Warning: use of prime-run on X11 is not supported. Disabling prime-run\n"); disable_prime_run(); } diff --git a/src/utils.c b/src/utils.c index 28c66e0..7cd57cb 100644 --- a/src/utils.c +++ b/src/utils.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -24,6 +25,16 @@ double clock_get_monotonic_seconds(void) { return (double)ts.tv_sec + (double)ts.tv_nsec * 0.000000001; } +static gsr_monitor_rotation wayland_transform_to_gsr_rotation(int32_t rot) { + switch(rot) { + case 0: return GSR_MONITOR_ROT_0; + case 1: return GSR_MONITOR_ROT_90; + case 2: return GSR_MONITOR_ROT_180; + case 3: return GSR_MONITOR_ROT_270; + } + return GSR_MONITOR_ROT_0; +} + static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id) { for(int i = 0; i < sr->nmode; ++i) { if(sr->modes[i].id == id) @@ -42,16 +53,6 @@ static gsr_monitor_rotation x11_rotation_to_gsr_rotation(int rot) { return GSR_MONITOR_ROT_0; } -static gsr_monitor_rotation wayland_transform_to_gsr_rotation(int32_t rot) { - switch(rot) { - case 0: return GSR_MONITOR_ROT_0; - case 1: return GSR_MONITOR_ROT_90; - case 2: return GSR_MONITOR_ROT_180; - case 3: return GSR_MONITOR_ROT_270; - } - return GSR_MONITOR_ROT_0; -} - static uint32_t x11_output_get_connector_id(Display *dpy, RROutput output, Atom randr_connector_id_atom) { Atom type = 0; int format = 0; @@ -68,7 +69,7 @@ static uint32_t x11_output_get_connector_id(Display *dpy, RROutput output, Atom return result; } -void for_each_active_monitor_output_x11(Display *display, active_monitor_callback callback, void *userdata) { +void for_each_active_monitor_output_x11_not_cached(Display *display, active_monitor_callback callback, void *userdata) { XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); if(!screen_res) return; @@ -83,15 +84,12 @@ void for_each_active_monitor_output_x11(Display *display, active_monitor_callbac if(crt_info && crt_info->mode) { const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode); if(mode_info && out_info->nameLen < (int)sizeof(display_name)) { - memcpy(display_name, out_info->name, out_info->nameLen); - display_name[out_info->nameLen] = '\0'; - + snprintf(display_name, sizeof(display_name), "%.*s", (int)out_info->nameLen, out_info->name); const gsr_monitor monitor = { .name = display_name, .name_len = out_info->nameLen, .pos = { .x = crt_info->x, .y = crt_info->y }, .size = { .x = (int)crt_info->width, .y = (int)crt_info->height }, - .crt_info = crt_info, .connector_id = x11_output_get_connector_id(display, screen_res->outputs[i], randr_connector_id_atom), .rotation = x11_rotation_to_gsr_rotation(crt_info->rotation), .monitor_identifier = 0 @@ -109,6 +107,22 @@ void for_each_active_monitor_output_x11(Display *display, active_monitor_callbac XRRFreeScreenResources(screen_res); } +void for_each_active_monitor_output_x11(const gsr_egl *egl, active_monitor_callback callback, void *userdata) { + for(int i = 0; i < egl->x11.num_outputs; ++i) { + const gsr_x11_output *output = &egl->x11.outputs[i]; + const gsr_monitor monitor = { + .name = output->name, + .name_len = strlen(output->name), + .pos = output->pos, + .size = output->size, + .connector_id = output->connector_id, + .rotation = output->rotation, + .monitor_identifier = 0 + }; + callback(&monitor, userdata); + } +} + typedef struct { int type; int count; @@ -192,7 +206,6 @@ static void for_each_active_monitor_output_wayland(const gsr_egl *egl, active_mo .name_len = strlen(output->name), .pos = { .x = output->pos.x, .y = output->pos.y }, .size = { .x = output->size.x, .y = output->size.y }, - .crt_info = NULL, .connector_id = 0, .rotation = wayland_transform_to_gsr_rotation(output->transform), .monitor_identifier = connector_type ? monitor_identifier_from_type_and_count(connector_type_index, connector_type->count_active) : 0 @@ -240,12 +253,11 @@ static void for_each_active_monitor_output_drm(const gsr_egl *egl, active_monito if(connector_type && crtc_id > 0 && crtc && connection_name_len + 5 < (int)sizeof(display_name)) { const int display_name_len = snprintf(display_name, sizeof(display_name), "%s-%d", connection_name, connector_type->count); const int connector_type_index_name = get_connector_type_by_name(display_name); - const gsr_monitor monitor = { + gsr_monitor monitor = { .name = display_name, .name_len = display_name_len, .pos = { .x = crtc->x, .y = crtc->y }, .size = { .x = (int)crtc->width, .y = (int)crtc->height }, - .crt_info = NULL, .connector_id = connector->connector_id, .rotation = GSR_MONITOR_ROT_0, .monitor_identifier = connector_type_index_name != -1 ? monitor_identifier_from_type_and_count(connector_type_index_name, connector_type->count_active) : 0 @@ -267,7 +279,7 @@ static void for_each_active_monitor_output_drm(const gsr_egl *egl, active_monito void for_each_active_monitor_output(const gsr_egl *egl, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata) { switch(connection_type) { case GSR_CONNECTION_X11: - for_each_active_monitor_output_x11(egl->x11.dpy, callback, userdata); + for_each_active_monitor_output_x11(egl, callback, userdata); break; case GSR_CONNECTION_WAYLAND: for_each_active_monitor_output_wayland(egl, callback, userdata); @@ -329,7 +341,7 @@ static void get_monitor_by_connector_id_callback(const gsr_monitor *monitor, voi } gsr_monitor_rotation drm_monitor_get_display_server_rotation(const gsr_egl *egl, const gsr_monitor *monitor) { - if(egl->wayland.dpy) { + if(gsr_egl_get_display_server(egl) == GSR_DISPLAY_SERVER_WAYLAND) { { get_monitor_by_connector_id_userdata userdata; userdata.monitor = monitor; @@ -352,7 +364,7 @@ gsr_monitor_rotation drm_monitor_get_display_server_rotation(const gsr_egl *egl, userdata.monitor = monitor; userdata.rotation = GSR_MONITOR_ROT_0; userdata.match_found = false; - for_each_active_monitor_output_x11(egl->x11.dpy, get_monitor_by_connector_id_callback, &userdata); + for_each_active_monitor_output_x11(egl, get_monitor_by_connector_id_callback, &userdata); return userdata.rotation; } -- cgit v1.2.3