From cbd62bda0252c919556b733e3db94fa431dbc29a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 18 Jul 2023 04:29:09 +0200 Subject: Fix for wayland --- src/egl.c | 350 +++++++++++++++++++++++++++++----- src/egl.h | 51 ++++- src/main.cpp | 610 ++++++++++++++++++++++++++++++++++++++++++----------------- 3 files changed, 776 insertions(+), 235 deletions(-) (limited to 'src') diff --git a/src/egl.c b/src/egl.c index ecefdf4..1daed3c 100644 --- a/src/egl.c +++ b/src/egl.c @@ -1,35 +1,211 @@ #include "egl.h" #include "library_loader.h" #include +#include +#include +#include -static bool gsr_egl_create_window(gsr_egl *self) { +#include +#include +//#include "../external/wlr-export-dmabuf-unstable-v1-client-protocol.h" +#include + +#if 0 +static struct wl_compositor *compositor = NULL; +static struct wl_output *output = NULL; +static struct zwlr_export_dmabuf_manager_v1 *export_manager = NULL; +static struct zwlr_export_dmabuf_frame_v1 *current_frame = NULL; +//static struct wl_shell *shell = NULL; + +struct window { + EGLContext egl_context; + struct wl_surface *surface; + //struct wl_shell_surface *shell_surface; + struct wl_egl_window *egl_window; + EGLSurface egl_surface; +}; + +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) { + fprintf(stderr, "output geometry, make: %s, model: %s\n", make, model); +} + +static void output_handle_mode(void *data, struct wl_output *wl_output, + uint32_t flags, int32_t width, int32_t height, int32_t refresh) { + +} + +static void output_handle_done(void* data, struct wl_output *wl_output) { + /* Nothing to do */ +} + +static void output_handle_scale(void* data, struct wl_output *wl_output, + int32_t factor) { + /* Nothing to do */ +} + +static const struct wl_output_listener output_listener = { + .geometry = output_handle_geometry, + .mode = output_handle_mode, + .done = output_handle_done, + .scale = output_handle_scale, +}; +#endif + +static void registry_add_object (void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { + (void)version; + struct wl_compositor **wayland_compositor = data; + if (strcmp(interface, "wl_compositor") == 0) { + if(*wayland_compositor) { + wl_compositor_destroy(*wayland_compositor); + *wayland_compositor = NULL; + } + *wayland_compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); + }/* else if(strcmp(interface, wl_output_interface.name) == 0) { + fprintf(stderr, "wayland output, name: %u\n", name); + output = wl_registry_bind(registry, name, &wl_output_interface, 1); + wl_output_add_listener(output, &output_listener, NULL); + } else if(strcmp(interface, zwlr_export_dmabuf_manager_v1_interface.name) == 0) { + export_manager = wl_registry_bind(registry, name, &zwlr_export_dmabuf_manager_v1_interface, 1); + }*/ + //fprintf(stderr, "interface: %s\n", interface); +} + +static void registry_remove_object (void *data, struct wl_registry *registry, uint32_t name) { + (void)data; + (void)registry; + (void)name; +} + +static struct wl_registry_listener registry_listener = {®istry_add_object, ®istry_remove_object}; + +#if 0 +static void register_cb(gsr_egl *egl); + +static void frame_start(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t width, uint32_t height, uint32_t offset_x, uint32_t offset_y, + uint32_t buffer_flags, uint32_t flags, uint32_t format, + uint32_t mod_high, uint32_t mod_low, uint32_t num_objects) { + gsr_egl *egl = data; + //fprintf(stderr, "frame start, width: %u, height: %u, offset x: %u, offset y: %u, format: %u, num objects: %u\n", width, height, offset_x, offset_y, format, num_objects); + egl->width = width; + egl->height = height; + egl->pixel_format = format; + egl->modifier = ((uint64_t)mod_high << 32) | mod_low; + current_frame = frame; +} + +static void frame_object(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t index, int32_t fd, uint32_t size, uint32_t offset, + uint32_t stride, uint32_t plane_index) { + // TODO: What if we get multiple objects? then we get multiple fd per frame + gsr_egl *egl = data; + //egl->fd = fd; + egl->pitch = stride; + egl->offset = offset; + //fprintf(stderr, "new frame, fd: %d, index: %u, size: %u, offset: %u, stride: %u, plane_index: %u\n", fd, index, size, offset, stride, plane_index); + close(fd); +} + + +static void frame_ready(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t tv_sec_hi, uint32_t tv_sec_lo, uint32_t tv_nsec) { + register_cb(data); +} + +static void frame_cancel(void *data, struct zwlr_export_dmabuf_frame_v1 *frame, + uint32_t reason) { + register_cb(data); +} + + +static const struct zwlr_export_dmabuf_frame_v1_listener frame_listener = { + .frame = frame_start, + .object = frame_object, + .ready = frame_ready, + .cancel = frame_cancel, +}; + +static struct zwlr_export_dmabuf_frame_v1 *frame_callback = NULL; +static void register_cb(gsr_egl *egl) { + bool with_cursor = false; + frame_callback = zwlr_export_dmabuf_manager_v1_capture_output(export_manager, with_cursor, output); + zwlr_export_dmabuf_frame_v1_add_listener(frame_callback, &frame_listener, egl); +} +#endif + +// TODO: Create egl context without surface (in other words, x11/wayland agnostic, doesn't require x11/wayland dependency) +static bool gsr_egl_create_window(gsr_egl *self, bool wayland) { EGLConfig ecfg; int32_t num_config = 0; + EGLDisplay egl_display = NULL; EGLSurface egl_surface = NULL; EGLContext egl_context = NULL; - Window window = None; - - int32_t attr[] = { + + Window x11_window = None; + + struct wl_registry *wayland_registry = NULL; + struct wl_compositor *wayland_compositor = NULL; + struct wl_surface *wayland_surface = NULL; + void *wayland_dpy = NULL; + void *wayland_window = NULL; + + const int32_t attr[] = { EGL_BUFFER_SIZE, 24, - EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_NONE }; - int32_t ctxattr[] = { + const int32_t ctxattr[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; - window = XCreateWindow(self->dpy, DefaultRootWindow(self->dpy), 0, 0, 1, 1, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); + if(wayland) { + wayland_dpy = wl_display_connect(NULL); + if(!wayland_dpy) { + fprintf(stderr, "gsr error: gsr_egl_create_window failed: wl_display_connect failed\n"); + goto fail; + } - if(!window) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n"); - goto fail; + wayland_registry = wl_display_get_registry(wayland_dpy); // TODO: Error checking + wl_registry_add_listener(wayland_registry, ®istry_listener, &wayland_compositor); // TODO: Error checking + + // Fetch globals + wl_display_roundtrip(wayland_dpy); + + // fetch wl_output + wl_display_roundtrip(wayland_dpy); + + if(!wayland_compositor) { + fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to find compositor\n"); + goto fail; + } + + /*if(!output) { + fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to find output\n"); + goto fail; + } + + if(!export_manager) { + fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to find export manager\n"); + goto fail; + }*/ + } else { + x11_window = XCreateWindow(self->x11_dpy, DefaultRootWindow(self->x11_dpy), 0, 0, 16, 16, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); + + if(!x11_window) { + fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n"); + goto fail; + } } - egl_display = self->eglGetDisplay(self->dpy); + self->eglBindAPI(EGL_OPENGL_ES_API); + + egl_display = self->eglGetDisplay(wayland_dpy ? (EGLNativeDisplayType)wayland_dpy : (EGLNativeDisplayType)self->x11_dpy); if(!egl_display) { fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglGetDisplay failed\n"); goto fail; @@ -45,24 +221,41 @@ static bool gsr_egl_create_window(gsr_egl *self) { goto fail; } - egl_surface = self->eglCreateWindowSurface(egl_display, ecfg, (EGLNativeWindowType)window, NULL); - if(!egl_surface) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create window surface\n"); - goto fail; - } - egl_context = self->eglCreateContext(egl_display, ecfg, NULL, ctxattr); if(!egl_context) { fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create egl context\n"); goto fail; } - self->eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context); + if(wayland) { + wayland_surface = wl_compositor_create_surface(wayland_compositor); + wayland_window = wl_egl_window_create(wayland_surface, 16, 16); + egl_surface = self->eglCreateWindowSurface(egl_display, ecfg, (EGLNativeWindowType)wayland_window, NULL); + } else { + egl_surface = self->eglCreateWindowSurface(egl_display, ecfg, (EGLNativeWindowType)x11_window, NULL); + } + + if(!egl_surface) { + fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create window surface\n"); + goto fail; + } + + if(!self->eglMakeCurrent(egl_display, egl_surface, egl_surface, egl_context)) { + fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to make context current\n"); + goto fail; + } self->egl_display = egl_display; self->egl_surface = egl_surface; self->egl_context = egl_context; - self->window = window; + + self->x11_window = x11_window; + + self->wayland_dpy = wayland_dpy; + self->wayland_window = wayland_window; + self->wayland_surface = wayland_surface; + self->wayland_compositor = wayland_compositor; + self->wayland_registry = wayland_registry; return true; fail: @@ -72,8 +265,18 @@ static bool gsr_egl_create_window(gsr_egl *self) { self->eglDestroySurface(egl_display, egl_surface); if(egl_display) self->eglTerminate(egl_display); - if(window) - XDestroyWindow(self->dpy, window); + if(x11_window) + XDestroyWindow(self->x11_dpy, x11_window); + if(wayland_window) + wl_egl_window_destroy(wayland_window); + if(wayland_surface) + wl_surface_destroy(wayland_surface); + if(wayland_compositor) + wl_compositor_destroy(wayland_compositor); + if(wayland_registry) + wl_registry_destroy(wayland_registry); + if(wayland_dpy) + wl_display_disconnect(wayland_dpy); return false; } @@ -88,6 +291,7 @@ static bool gsr_egl_load_egl(gsr_egl *self, void *library) { { (void**)&self->eglMakeCurrent, "eglMakeCurrent" }, { (void**)&self->eglDestroyContext, "eglDestroyContext" }, { (void**)&self->eglDestroySurface, "eglDestroySurface" }, + { (void**)&self->eglBindAPI, "eglBindAPI" }, { NULL, NULL } }; @@ -115,49 +319,46 @@ static bool gsr_egl_load_gl(gsr_egl *self, void *library) { return true; } -bool gsr_egl_load(gsr_egl *self, Display *dpy) { +bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland) { memset(self, 0, sizeof(gsr_egl)); - self->dpy = dpy; + self->x11_dpy = dpy; + + void *egl_lib = NULL; + void *gl_lib = NULL; dlerror(); /* clear */ - void *egl_lib = dlopen("libEGL.so.1", RTLD_LAZY); + egl_lib = dlopen("libEGL.so.1", RTLD_LAZY); if(!egl_lib) { fprintf(stderr, "gsr error: gsr_egl_load: failed to load libEGL.so.1, error: %s\n", dlerror()); - return false; + goto fail; } - void *gl_lib = dlopen("libGL.so.1", RTLD_LAZY); + gl_lib = dlopen("libGL.so.1", RTLD_LAZY); if(!egl_lib) { fprintf(stderr, "gsr error: gsr_egl_load: failed to load libGL.so.1, error: %s\n", dlerror()); - dlclose(egl_lib); - memset(self, 0, sizeof(gsr_egl)); - return false; + goto fail; } - if(!gsr_egl_load_egl(self, egl_lib)) { - dlclose(egl_lib); - dlclose(gl_lib); - memset(self, 0, sizeof(gsr_egl)); - return false; - } + if(!gsr_egl_load_egl(self, egl_lib)) + goto fail; - if(!gsr_egl_load_gl(self, gl_lib)) { - dlclose(egl_lib); - dlclose(gl_lib); - memset(self, 0, sizeof(gsr_egl)); - return false; - } + if(!gsr_egl_load_gl(self, gl_lib)) + goto fail; - if(!gsr_egl_create_window(self)) { - dlclose(egl_lib); - dlclose(gl_lib); - memset(self, 0, sizeof(gsr_egl)); - return false; - } + if(!gsr_egl_create_window(self, wayland)) + goto fail; self->egl_library = egl_lib; self->gl_library = gl_lib; return true; + + fail: + if(egl_lib) + dlclose(egl_lib); + if(gl_lib) + dlclose(gl_lib); + memset(self, 0, sizeof(gsr_egl)); + return false; } void gsr_egl_unload(gsr_egl *self) { @@ -176,9 +377,34 @@ void gsr_egl_unload(gsr_egl *self) { self->egl_display = NULL; } - if(self->window) { - XDestroyWindow(self->dpy, self->window); - self->window = None; + if(self->x11_window) { + XDestroyWindow(self->x11_dpy, self->x11_window); + self->x11_window = None; + } + + if(self->wayland_window) { + wl_egl_window_destroy(self->wayland_window); + self->wayland_window = NULL; + } + + if(self->wayland_surface) { + wl_surface_destroy(self->wayland_surface); + self->wayland_surface = NULL; + } + + if(self->wayland_compositor) { + wl_compositor_destroy(self->wayland_compositor); + self->wayland_compositor = NULL; + } + + if(self->wayland_registry) { + wl_registry_destroy(self->wayland_registry); + self->wayland_registry = NULL; + } + + if(self->wayland_dpy) { + wl_display_disconnect(self->wayland_dpy); + self->wayland_dpy = NULL; } if(self->egl_library) { @@ -193,3 +419,25 @@ void gsr_egl_unload(gsr_egl *self) { memset(self, 0, sizeof(gsr_egl)); } + +void gsr_egl_update(gsr_egl *self) { + if(!self->wayland_dpy) + return; + + wl_display_dispatch(self->wayland_dpy); +} + +void gsr_egl_cleanup_frame(gsr_egl *self) { + if(!self->wayland_dpy) + return; + + if(self->fd > 0) { + close(self->fd); + self->fd = 0; + } + + /*if(current_frame) { + zwlr_export_dmabuf_frame_v1_destroy(current_frame); + current_frame = NULL; + }*/ +} diff --git a/src/egl.h b/src/egl.h index 52422bc..6a05f5f 100644 --- a/src/egl.h +++ b/src/egl.h @@ -8,13 +8,32 @@ #include #include +#ifdef _WIN64 +typedef signed long long int khronos_intptr_t; +typedef unsigned long long int khronos_uintptr_t; +typedef signed long long int khronos_ssize_t; +typedef unsigned long long int khronos_usize_t; +#else +typedef signed long int khronos_intptr_t; +typedef unsigned long int khronos_uintptr_t; +typedef signed long int khronos_ssize_t; +typedef unsigned long int khronos_usize_t; +#endif + typedef void* EGLDisplay; -typedef void* EGLSurface; -typedef void* EGLContext; -typedef void* EGLConfig; typedef void* EGLNativeDisplayType; typedef uintptr_t EGLNativeWindowType; +typedef uintptr_t EGLNativePixmapType; +typedef void* EGLConfig; +typedef void* EGLSurface; +typedef void* EGLContext; +typedef void* EGLClientBuffer; +typedef void* EGLImage; +typedef void* EGLImageKHR; +typedef void *GLeglImageOES; +typedef void (*__eglMustCastToProperFunctionPointerType)(void); +#define EGL_OPENGL_ES_API 0x30A0 #define EGL_BUFFER_SIZE 0x3020 #define EGL_RENDERABLE_TYPE 0x3040 #define EGL_OPENGL_ES2_BIT 0x0004 @@ -27,11 +46,27 @@ typedef uintptr_t EGLNativeWindowType; typedef struct { void *egl_library; void *gl_library; - Display *dpy; + EGLDisplay egl_display; EGLSurface egl_surface; EGLContext egl_context; - Window window; + + Display *x11_dpy; + Window x11_window; + + void *wayland_dpy; + void *wayland_window; + void *wayland_registry; + void *wayland_surface; + void *wayland_compositor; + + int fd; + uint32_t width; + uint32_t height; + uint32_t pitch; + uint32_t offset; + uint32_t pixel_format; + uint64_t modifier; EGLDisplay (*eglGetDisplay)(EGLNativeDisplayType display_id); unsigned int (*eglInitialize)(EGLDisplay dpy, int32_t *major, int32_t *minor); @@ -42,11 +77,15 @@ typedef struct { unsigned int (*eglMakeCurrent)(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); unsigned int (*eglDestroyContext)(EGLDisplay dpy, EGLContext ctx); unsigned int (*eglDestroySurface)(EGLDisplay dpy, EGLSurface surface); + unsigned int (*eglBindAPI)(unsigned int api); const unsigned char* (*glGetString)(unsigned int name); } gsr_egl; -bool gsr_egl_load(gsr_egl *self, Display *dpy); +bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland); void gsr_egl_unload(gsr_egl *self); +void gsr_egl_update(gsr_egl *self); +void gsr_egl_cleanup_frame(gsr_egl *self); + #endif /* GSR_EGL_H */ diff --git a/src/main.cpp b/src/main.cpp index a1d1671..ddb2378 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,8 @@ extern "C" { #include "egl.h" } +#include +#include typedef struct { Display *display; @@ -94,6 +96,9 @@ static int num_audio_inputs_addable = 0; static std::string record_file_current_filename; static bool nvfbc_installed = false; +static bool wayland = false; +char drm_card_path[128]; + enum class HotkeyMode { NoAction, NewHotkey, @@ -257,12 +262,14 @@ static void drag_data_received (GtkWidget *widget, GdkDragContext*, } static void enable_stream_record_button_if_info_filled() { - const gchar *selected_window_area = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_area_selection_menu)); - if(strcmp(selected_window_area, "window") == 0 && select_window_userdata.selected_window == None) { - gtk_widget_set_sensitive(GTK_WIDGET(replay_button), false); - gtk_widget_set_sensitive(GTK_WIDGET(record_button), false); - gtk_widget_set_sensitive(GTK_WIDGET(stream_button), false); - return; + if(!wayland) { + const gchar *selected_window_area = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_area_selection_menu)); + if(strcmp(selected_window_area, "window") == 0 && select_window_userdata.selected_window == None) { + gtk_widget_set_sensitive(GTK_WIDGET(replay_button), false); + gtk_widget_set_sensitive(GTK_WIDGET(record_button), false); + gtk_widget_set_sensitive(GTK_WIDGET(stream_button), false); + return; + } } gtk_widget_set_sensitive(GTK_WIDGET(replay_button), true); @@ -403,8 +410,10 @@ static std::string get_date_str() { static void save_configs() { config.main_config.record_area_option = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_area_selection_menu)); - config.main_config.record_area_width = gtk_spin_button_get_value_as_int(area_width_entry); - config.main_config.record_area_height = gtk_spin_button_get_value_as_int(area_height_entry); + if(!wayland) { + config.main_config.record_area_width = gtk_spin_button_get_value_as_int(area_width_entry); + config.main_config.record_area_height = gtk_spin_button_get_value_as_int(area_height_entry); + } config.main_config.fps = gtk_spin_button_get_value_as_int(fps_entry); config.main_config.merge_audio_tracks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(merge_audio_tracks_button)); @@ -420,46 +429,86 @@ static void save_configs() { config.streaming_config.streaming_service = gtk_combo_box_get_active_id(GTK_COMBO_BOX(stream_service_input_menu)); config.streaming_config.stream_key = gtk_entry_get_text(stream_id_entry); - config.streaming_config.start_recording_hotkey.keysym = streaming_hotkey.keysym; - config.streaming_config.start_recording_hotkey.modifiers = streaming_hotkey.modkey_mask; + if(!wayland) { + config.streaming_config.start_recording_hotkey.keysym = streaming_hotkey.keysym; + config.streaming_config.start_recording_hotkey.modifiers = streaming_hotkey.modkey_mask; + } config.record_config.save_directory = gtk_button_get_label(record_file_chooser_button); config.record_config.container = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_container)); - config.record_config.start_recording_hotkey.keysym = record_hotkey.keysym; - config.record_config.start_recording_hotkey.modifiers = record_hotkey.modkey_mask; + if(!wayland) { + config.record_config.start_recording_hotkey.keysym = record_hotkey.keysym; + config.record_config.start_recording_hotkey.modifiers = record_hotkey.modkey_mask; + } config.replay_config.save_directory = gtk_button_get_label(replay_file_chooser_button); config.replay_config.container = gtk_combo_box_get_active_id(GTK_COMBO_BOX(replay_container)); config.replay_config.replay_time = gtk_spin_button_get_value_as_int(replay_time_entry); - config.replay_config.start_recording_hotkey.keysym = replay_start_stop_hotkey.keysym; - config.replay_config.start_recording_hotkey.modifiers = replay_start_stop_hotkey.modkey_mask; - config.replay_config.save_recording_hotkey.keysym = replay_save_hotkey.keysym; - config.replay_config.save_recording_hotkey.modifiers = replay_save_hotkey.modkey_mask; + if(!wayland) { + config.replay_config.start_recording_hotkey.keysym = replay_start_stop_hotkey.keysym; + config.replay_config.start_recording_hotkey.modifiers = replay_start_stop_hotkey.modkey_mask; + + config.replay_config.save_recording_hotkey.keysym = replay_save_hotkey.keysym; + config.replay_config.save_recording_hotkey.modifiers = replay_save_hotkey.modkey_mask; + } save_config(config); } +typedef struct { + int x, y; +} vec2i; + +typedef struct { + const char *name; + int name_len; + vec2i pos; + vec2i size; + XRRCrtcInfo *crt_info; /* Only on x11 */ + uint32_t connector_id; /* Only on drm */ +} gsr_monitor; + +typedef enum { + GSR_CONNECTION_X11, + GSR_CONNECTION_WAYLAND, + GSR_CONNECTION_DRM +} gsr_connection_type; + +using active_monitor_callback = std::function; + 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) return &sr->modes[i]; } - return nullptr; + return NULL; } -static void for_each_active_monitor_output(Display *display, std::function callback_func) { +static void for_each_active_monitor_output_x11(Display *display, active_monitor_callback &callback, void *userdata) { XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display)); if(!screen_res) return; + char display_name[256]; for(int i = 0; i < screen_res->noutput; ++i) { XRROutputInfo *out_info = XRRGetOutputInfo(display, screen_res, screen_res->outputs[i]); if(out_info && out_info->crtc && out_info->connection == RR_Connected) { XRRCrtcInfo *crt_info = XRRGetCrtcInfo(display, screen_res, out_info->crtc); if(crt_info && crt_info->mode) { const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode); - if(mode_info) - callback_func(out_info, crt_info, mode_info); + 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'; + + gsr_monitor monitor; + monitor.name = display_name; + monitor.name_len = out_info->nameLen; + monitor.pos = { (int)crt_info->x, (int)crt_info->y }; + monitor.size = { (int)crt_info->width, (int)crt_info->height }; + monitor.crt_info = crt_info; + monitor.connector_id = 0; // TODO: Get connector id + callback(&monitor, userdata); + } } if(crt_info) XRRFreeCrtcInfo(crt_info); @@ -471,6 +520,151 @@ static void for_each_active_monitor_output(Display *display, std::functioncount_props; ++i) { + drmModePropertyPtr prop = drmModeGetProperty(drmfd, props->props[i]); + if(prop) { + if(strcmp(name, prop->name) == 0) { + *result = props->prop_values[i]; + drmModeFreeProperty(prop); + return true; + } + drmModeFreeProperty(prop); + } + } + return false; +} + +static void for_each_active_monitor_output_drm(const char *drm_card_path, active_monitor_callback callback, void *userdata) { + int fd = open(drm_card_path, O_RDONLY); + if(fd == -1) + return; + + drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1); + + drm_connector_type_count type_counts[CONNECTOR_TYPE_COUNTS]; + int num_type_counts = 0; + + char display_name[256]; + drmModeResPtr resources = drmModeGetResources(fd); + if(resources) { + for(int i = 0; i < resources->count_connectors; ++i) { + drmModeConnectorPtr connector = drmModeGetConnectorCurrent(fd, resources->connectors[i]); + if(!connector) + continue; + + if(connector->connection != DRM_MODE_CONNECTED) { + drmModeFreeConnector(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); + const int connection_name_len = strlen(connection_name); + if(connector_type) + ++connector_type->count; + + uint64_t crtc_id = 0; + connector_get_property_by_name(fd, connector, "CRTC_ID", &crtc_id); + + drmModeCrtcPtr crtc = drmModeGetCrtc(fd, crtc_id); + 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); + gsr_monitor monitor; + monitor.name = display_name; + monitor.name_len = display_name_len; + monitor.pos = { (int)crtc->x, (int)crtc->y }; + monitor.size = { (int)crtc->width, (int)crtc->height }; + monitor.crt_info = NULL; + monitor.connector_id = connector->connector_id; + callback(&monitor, userdata); + } + + if(crtc) + drmModeFreeCrtc(crtc); + + drmModeFreeConnector(connector); + } + drmModeFreeResources(resources); + } + + close(fd); +} + +static void for_each_active_monitor_output(void *connection, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata) { + switch(connection_type) { + case GSR_CONNECTION_X11: + for_each_active_monitor_output_x11((Display*)connection, callback, userdata); + break; + case GSR_CONNECTION_WAYLAND: + // TODO: use gsr_egl here (connection) + break; + case GSR_CONNECTION_DRM: + for_each_active_monitor_output_drm((const char*)connection, callback, userdata); + break; + } +} + +/* output should be >= 128 bytes */ +static bool gsr_get_valid_card_path(char *output) { + for(int i = 0; i < 10; ++i) { + sprintf(output, "/dev/dri/card%d", i); + int fd = open(output, O_RDONLY); + if(fd == -1) + continue; + + drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); + + drmModePlaneResPtr planes = drmModeGetPlaneResources(fd); + if(!planes) { + close(fd); + continue; + } + + bool found_screen_card = false; + for(uint32_t i = 0; i < planes->count_planes; ++i) { + drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[i]); + if(!plane) + continue; + + if(plane->fb_id) + found_screen_card = true; + + drmModeFreePlane(plane); + if(found_screen_card) + break; + } + + close(fd); + if(found_screen_card) + return true; + } + return false; +} + static void show_notification(GtkApplication *app, const char *title, const char *body, GNotificationPriority priority) { fprintf(stderr, "Notification: title: %s, body: %s\n", title, body); GNotification *notification = g_notification_new(title); @@ -731,6 +925,9 @@ static int xerror_grab_error(Display*, XErrorEvent*) { } static void ungrab_keyboard(Display *display) { + if(wayland) + return; + if(current_hotkey) { gtk_grab_remove(current_hotkey->hotkey_entry); gtk_widget_set_visible(current_hotkey->hotkey_active_label, false); @@ -743,6 +940,9 @@ static void ungrab_keyboard(Display *display) { } static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab) { + if(wayland) + return true; + if(hotkey.keysym == None && hotkey.modkey_mask == 0) return true; @@ -798,6 +998,9 @@ static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab) } static void ungrab_keys(Display *display) { + if(wayland) + return; + grab_ungrab_hotkey_combo(display, streaming_hotkey, false); grab_ungrab_hotkey_combo(display, record_hotkey, false); grab_ungrab_hotkey_combo(display, replay_start_stop_hotkey, false); @@ -904,16 +1107,18 @@ static gboolean on_start_replay_click(GtkButton*, gpointer userdata) { PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata; gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->replay_page); - HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); - if(!hotkey_result.replay_start_stop_hotkey_success) { - gtk_entry_set_text(GTK_ENTRY(replay_start_stop_hotkey.hotkey_entry), ""); - replay_start_stop_hotkey.keysym = 0; - replay_start_stop_hotkey.modkey_mask = 0; - } - if(!hotkey_result.replay_save_hotkey_success) { - gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey.hotkey_entry), ""); - replay_save_hotkey.keysym = 0; - replay_save_hotkey.modkey_mask = 0; + if(!wayland) { + HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); + if(!hotkey_result.replay_start_stop_hotkey_success) { + gtk_entry_set_text(GTK_ENTRY(replay_start_stop_hotkey.hotkey_entry), ""); + replay_start_stop_hotkey.keysym = 0; + replay_start_stop_hotkey.modkey_mask = 0; + } + if(!hotkey_result.replay_save_hotkey_success) { + gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey.hotkey_entry), ""); + replay_save_hotkey.keysym = 0; + replay_save_hotkey.modkey_mask = 0; + } } return true; } @@ -924,11 +1129,13 @@ static gboolean on_start_recording_click(GtkButton*, gpointer userdata) { PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata; gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->recording_page); - HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); - if(!hotkey_result.record_hotkey_success) { - gtk_entry_set_text(GTK_ENTRY(record_hotkey.hotkey_entry), ""); - record_hotkey.keysym = 0; - record_hotkey.modkey_mask = 0; + if(!wayland) { + HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); + if(!hotkey_result.record_hotkey_success) { + gtk_entry_set_text(GTK_ENTRY(record_hotkey.hotkey_entry), ""); + record_hotkey.keysym = 0; + record_hotkey.modkey_mask = 0; + } } return true; } @@ -959,11 +1166,13 @@ static gboolean on_start_streaming_click(GtkButton*, gpointer userdata) { PageNavigationUserdata *page_navigation_userdata = (PageNavigationUserdata*)userdata; gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->streaming_page); - HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); - if(!hotkey_result.streaming_hotkey_success) { - gtk_entry_set_text(GTK_ENTRY(streaming_hotkey.hotkey_entry), ""); - streaming_hotkey.keysym = 0; - streaming_hotkey.modkey_mask = 0; + if(!wayland) { + HotkeyResult hotkey_result = replace_grabbed_keys_depending_on_active_page(); + if(!hotkey_result.streaming_hotkey_success) { + gtk_entry_set_text(GTK_ENTRY(streaming_hotkey.hotkey_entry), ""); + streaming_hotkey.keysym = 0; + streaming_hotkey.modkey_mask = 0; + } } return true; } @@ -1056,8 +1265,8 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat int fps = gtk_spin_button_get_value_as_int(fps_entry); int replay_time = gtk_spin_button_get_value_as_int(replay_time_entry); - int record_width = gtk_spin_button_get_value_as_int(area_width_entry); - int record_height = gtk_spin_button_get_value_as_int(area_height_entry); + int record_width = wayland ? 0 : gtk_spin_button_get_value_as_int(area_width_entry); + int record_height = wayland ? 0 : gtk_spin_button_get_value_as_int(area_height_entry); char dir_tmp[PATH_MAX]; strcpy(dir_tmp, dir); @@ -1177,8 +1386,8 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user save_configs(); int fps = gtk_spin_button_get_value_as_int(fps_entry); - int record_width = gtk_spin_button_get_value_as_int(area_width_entry); - int record_height = gtk_spin_button_get_value_as_int(area_height_entry); + int record_width = wayland ? 0 : gtk_spin_button_get_value_as_int(area_width_entry); + int record_height = wayland ? 0 : gtk_spin_button_get_value_as_int(area_height_entry); char dir_tmp[PATH_MAX]; strcpy(dir_tmp, dir); @@ -1292,8 +1501,8 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user const char *stream_id_str = gtk_entry_get_text(stream_id_entry); int fps = gtk_spin_button_get_value_as_int(fps_entry); - int record_width = gtk_spin_button_get_value_as_int(area_width_entry); - int record_height = gtk_spin_button_get_value_as_int(area_height_entry); + int record_width = wayland ? 0 : gtk_spin_button_get_value_as_int(area_width_entry); + int record_height = wayland ? 0 : gtk_spin_button_get_value_as_int(area_height_entry); bool follow_focused = false; std::string window_str = gtk_combo_box_get_active_id(GTK_COMBO_BOX(record_area_selection_menu)); @@ -1546,7 +1755,7 @@ static void view_combo_box_change_callback(GtkComboBox *widget, gpointer userdat const gchar *selected_view = gtk_combo_box_get_active_id(widget); gtk_widget_set_visible(GTK_WIDGET(video_codec_grid), strcmp(selected_view, "advanced") == 0); gtk_widget_set_visible(GTK_WIDGET(audio_codec_grid), strcmp(selected_view, "advanced") == 0); - gtk_widget_set_visible(GTK_WIDGET(overclock_grid), strcmp(selected_view, "advanced") == 0 && gpu_inf.vendor == GPU_VENDOR_NVIDIA); + gtk_widget_set_visible(GTK_WIDGET(overclock_grid), strcmp(selected_view, "advanced") == 0 && gpu_inf.vendor == GPU_VENDOR_NVIDIA && !wayland); } static void stream_service_item_change_callback(GtkComboBox *widget, gpointer userdata) { @@ -1596,6 +1805,9 @@ static void keypress_toggle_recording(bool recording_state, GtkButton *record_bu } static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpointer userdata) { + if(wayland) + return GDK_FILTER_CONTINUE; + if(hotkey_mode == HotkeyMode::NoAction) return GDK_FILTER_CONTINUE; @@ -1725,6 +1937,9 @@ static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpoi } static gboolean on_hotkey_entry_click(GtkWidget *button, gpointer) { + if(wayland) + return true; + hotkey_mode = HotkeyMode::NewHotkey; pressed_hotkey.hotkey_entry = nullptr; @@ -1802,70 +2017,82 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a gtk_container_add(GTK_CONTAINER(record_area_frame), GTK_WIDGET(record_area_grid)); record_area_selection_menu = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new()); - gtk_combo_box_text_append(record_area_selection_menu, "window", "Window"); - gtk_combo_box_text_append(record_area_selection_menu, "focused", "Follow focused window"); - const bool allow_screen_capture = nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA; + if(!wayland) { + gtk_combo_box_text_append(record_area_selection_menu, "window", "Window"); + gtk_combo_box_text_append(record_area_selection_menu, "focused", "Follow focused window"); + } + const bool allow_screen_capture = wayland || nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA; if(allow_screen_capture) { - if(gpu_inf.vendor == GPU_VENDOR_NVIDIA) - gtk_combo_box_text_append(record_area_selection_menu, "screen", "All monitors"); - else - gtk_combo_box_text_append(record_area_selection_menu, "screen", "All monitors (requires root access, may perform better)"); + if(!wayland) { + if(gpu_inf.vendor == GPU_VENDOR_NVIDIA) + gtk_combo_box_text_append(record_area_selection_menu, "screen", "All monitors"); + else + gtk_combo_box_text_append(record_area_selection_menu, "screen", "All monitors (requires root access, may perform better)"); + + if(gpu_inf.vendor == GPU_VENDOR_NVIDIA) + gtk_combo_box_text_append(record_area_selection_menu, "screen-direct-force", "All monitors (for VRR. No cursor, may have driver issues. Only use with VRR monitors!)"); + } - if(gpu_inf.vendor == GPU_VENDOR_NVIDIA) - gtk_combo_box_text_append(record_area_selection_menu, "screen-direct-force", "All monitors (for VRR. No cursor, may have driver issues. Only use with VRR monitors!)"); + void *connection = wayland ? (void*)drm_card_path : gdk_x11_get_default_xdisplay(); + const gsr_connection_type connection_type = wayland ? GSR_CONNECTION_DRM : GSR_CONNECTION_X11; - for_each_active_monitor_output(gdk_x11_get_default_xdisplay(), [&](const XRROutputInfo *output_info, const XRRCrtcInfo *crtc_info, const XRRModeInfo*) { + for_each_active_monitor_output(connection, connection_type, [&](const gsr_monitor *monitor, void*) { std::string label = "Monitor "; - label.append(output_info->name, output_info->nameLen); + label.append(monitor->name, monitor->name_len); label += " ("; - label += std::to_string(crtc_info->width); + label += std::to_string(monitor->size.x); label += "x"; - label += std::to_string(crtc_info->height); + label += std::to_string(monitor->size.y); if(gpu_inf.vendor != GPU_VENDOR_NVIDIA) label += ", requires root access, may perform better"; label += ")"; // Leak on purpose, what are you gonna do? stab me? - char *id = (char*)malloc(output_info->nameLen + 1); + char *id = (char*)malloc(monitor->name_len + 1); if(!id) { fprintf(stderr, "Failed to allocate memory\n"); abort(); } - memcpy(id, output_info->name, output_info->nameLen); - id[output_info->nameLen] = '\0'; + memcpy(id, monitor->name, monitor->name_len); + id[monitor->name_len] = '\0'; gtk_combo_box_text_append(record_area_selection_menu, id, label.c_str()); - }); + }, NULL); } - gtk_combo_box_set_active(GTK_COMBO_BOX(record_area_selection_menu), allow_screen_capture ? 2 : 0); + if(wayland) + gtk_combo_box_set_active(GTK_COMBO_BOX(record_area_selection_menu), 0); + else + gtk_combo_box_set_active(GTK_COMBO_BOX(record_area_selection_menu), allow_screen_capture ? 2 : 0); gtk_widget_set_hexpand(GTK_WIDGET(record_area_selection_menu), true); gtk_grid_attach(record_area_grid, GTK_WIDGET(record_area_selection_menu), 0, record_area_row++, 3, 1); - select_window_button = GTK_BUTTON(gtk_button_new_with_label("Select window...")); - gtk_widget_set_hexpand(GTK_WIDGET(select_window_button), true); - g_signal_connect(select_window_button, "clicked", G_CALLBACK(on_select_window_button_click), app); - gtk_grid_attach(record_area_grid, GTK_WIDGET(select_window_button), 0, record_area_row++, 3, 1); + if(!wayland) { + select_window_button = GTK_BUTTON(gtk_button_new_with_label("Select window...")); + gtk_widget_set_hexpand(GTK_WIDGET(select_window_button), true); + g_signal_connect(select_window_button, "clicked", G_CALLBACK(on_select_window_button_click), app); + gtk_grid_attach(record_area_grid, GTK_WIDGET(select_window_button), 0, record_area_row++, 3, 1); - g_signal_connect(record_area_selection_menu, "changed", G_CALLBACK(record_area_item_change_callback), select_window_button); + g_signal_connect(record_area_selection_menu, "changed", G_CALLBACK(record_area_item_change_callback), select_window_button); - area_size_label = GTK_LABEL(gtk_label_new("Area size: ")); - gtk_label_set_xalign(area_size_label, 0.0f); - gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_label), 0, record_area_row++, 2, 1); + area_size_label = GTK_LABEL(gtk_label_new("Area size: ")); + gtk_label_set_xalign(area_size_label, 0.0f); + gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_label), 0, record_area_row++, 2, 1); - area_size_grid = GTK_GRID(gtk_grid_new()); - gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_grid), 0, record_area_row++, 3, 1); + area_size_grid = GTK_GRID(gtk_grid_new()); + gtk_grid_attach(record_area_grid, GTK_WIDGET(area_size_grid), 0, record_area_row++, 3, 1); - area_width_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0)); - gtk_spin_button_set_value(area_width_entry, 1920.0); - gtk_widget_set_hexpand(GTK_WIDGET(area_width_entry), true); - gtk_grid_attach(area_size_grid, GTK_WIDGET(area_width_entry), 0, 0, 1, 1); + area_width_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0)); + gtk_spin_button_set_value(area_width_entry, 1920.0); + gtk_widget_set_hexpand(GTK_WIDGET(area_width_entry), true); + gtk_grid_attach(area_size_grid, GTK_WIDGET(area_width_entry), 0, 0, 1, 1); - gtk_grid_attach(area_size_grid, gtk_label_new("x"), 1, 0, 1, 1); + gtk_grid_attach(area_size_grid, gtk_label_new("x"), 1, 0, 1, 1); - area_height_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0)); - gtk_spin_button_set_value(area_height_entry, 1080.0); - gtk_widget_set_hexpand(GTK_WIDGET(area_height_entry), true); - gtk_grid_attach(area_size_grid, GTK_WIDGET(area_height_entry), 2, 0, 1, 1); + area_height_entry = GTK_SPIN_BUTTON(gtk_spin_button_new_with_range(5.0, 10000.0, 1.0)); + gtk_spin_button_set_value(area_height_entry, 1080.0); + gtk_widget_set_hexpand(GTK_WIDGET(area_height_entry), true); + gtk_grid_attach(area_size_grid, GTK_WIDGET(area_height_entry), 2, 0, 1, 1); + } GtkFrame *audio_input_frame = GTK_FRAME(gtk_frame_new("Audio input")); gtk_grid_attach(grid, GTK_WIDGET(audio_input_frame), 0, grid_row++, 2, 1); @@ -2057,34 +2284,37 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) { gtk_grid_set_column_spacing(grid, 10); gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10); - GtkWidget *hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey or Esc to cancel"); - gtk_grid_attach(grid, hotkey_active_label, 0, row++, 5, 1); + GtkWidget *hotkey_active_label = NULL; + if(!wayland) { + hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey or Esc to cancel"); + gtk_grid_attach(grid, hotkey_active_label, 0, row++, 5, 1); - GtkWidget *a = gtk_label_new("Press"); - gtk_widget_set_halign(a, GTK_ALIGN_END); + GtkWidget *a = gtk_label_new("Press"); + gtk_widget_set_halign(a, GTK_ALIGN_END); - GtkWidget *b = gtk_label_new("to start/stop the replay and "); - gtk_widget_set_halign(b, GTK_ALIGN_START); + GtkWidget *b = gtk_label_new("to start/stop the replay and "); + gtk_widget_set_halign(b, GTK_ALIGN_START); - GtkWidget *c = gtk_label_new("to save"); - gtk_widget_set_halign(c, GTK_ALIGN_START); + GtkWidget *c = gtk_label_new("to save"); + gtk_widget_set_halign(c, GTK_ALIGN_START); - replay_start_stop_hotkey_button = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(replay_start_stop_hotkey_button), "Alt + F1"); - g_signal_connect(replay_start_stop_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_start_stop_hotkey_button); + replay_start_stop_hotkey_button = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(replay_start_stop_hotkey_button), "Alt + F1"); + g_signal_connect(replay_start_stop_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_start_stop_hotkey_button); - replay_save_hotkey_button = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey_button), "Alt + F2"); - gtk_widget_set_halign(replay_save_hotkey_button, GTK_ALIGN_START); - g_signal_connect(replay_save_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_save_hotkey_button); + replay_save_hotkey_button = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(replay_save_hotkey_button), "Alt + F2"); + gtk_widget_set_halign(replay_save_hotkey_button, GTK_ALIGN_START); + g_signal_connect(replay_save_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), replay_save_hotkey_button); - gtk_grid_attach(grid, a, 0, row, 1, 1); - gtk_grid_attach(grid, replay_start_stop_hotkey_button, 1, row, 1, 1); - gtk_grid_attach(grid, b, 2, row, 1, 1); - gtk_grid_attach(grid, replay_save_hotkey_button, 3, row, 1, 1); - gtk_grid_attach(grid, c, 4, row, 1, 1); - ++row; - gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 5, 1); + gtk_grid_attach(grid, a, 0, row, 1, 1); + gtk_grid_attach(grid, replay_start_stop_hotkey_button, 1, row, 1, 1); + gtk_grid_attach(grid, b, 2, row, 1, 1); + gtk_grid_attach(grid, replay_save_hotkey_button, 3, row, 1, 1); + gtk_grid_attach(grid, c, 4, row, 1, 1); + ++row; + gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 5, 1); + } replay_start_stop_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L); replay_start_stop_hotkey.keysym = XK_F1; @@ -2166,23 +2396,26 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) { gtk_grid_set_column_spacing(grid, 10); gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10); - GtkWidget *hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey or Esc to cancel"); - gtk_grid_attach(grid, hotkey_active_label, 0, row++, 3, 1); + GtkWidget *hotkey_active_label = NULL; + if(!wayland) { + hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey or Esc to cancel"); + gtk_grid_attach(grid, hotkey_active_label, 0, row++, 3, 1); - GtkWidget *a = gtk_label_new("Press"); - gtk_widget_set_halign(a, GTK_ALIGN_END); + GtkWidget *a = gtk_label_new("Press"); + gtk_widget_set_halign(a, GTK_ALIGN_END); - GtkWidget *b = gtk_label_new("to start/stop recording"); - gtk_widget_set_halign(b, GTK_ALIGN_START); + GtkWidget *b = gtk_label_new("to start/stop recording"); + gtk_widget_set_halign(b, GTK_ALIGN_START); - record_hotkey_button = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(record_hotkey_button), "Alt + F1"); - g_signal_connect(record_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), record_hotkey_button); - gtk_grid_attach(grid, a, 0, row, 1, 1); - gtk_grid_attach(grid, record_hotkey_button, 1, row, 1, 1); - gtk_grid_attach(grid, b, 2, row, 1, 1); - ++row; - gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 3, 1); + record_hotkey_button = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(record_hotkey_button), "Alt + F1"); + g_signal_connect(record_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), record_hotkey_button); + gtk_grid_attach(grid, a, 0, row, 1, 1); + gtk_grid_attach(grid, record_hotkey_button, 1, row, 1, 1); + gtk_grid_attach(grid, b, 2, row, 1, 1); + ++row; + gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 3, 1); + } record_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L); record_hotkey.keysym = XK_F1; @@ -2240,23 +2473,26 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) { gtk_grid_set_column_spacing(grid, 10); gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10); - GtkWidget *hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey or Esc to cancel"); - gtk_grid_attach(grid, hotkey_active_label, 0, row++, 3, 1); + GtkWidget *hotkey_active_label = NULL; + if(!wayland) { + hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey or Esc to cancel"); + gtk_grid_attach(grid, hotkey_active_label, 0, row++, 3, 1); - GtkWidget *a = gtk_label_new("Press"); - gtk_widget_set_halign(a, GTK_ALIGN_END); + GtkWidget *a = gtk_label_new("Press"); + gtk_widget_set_halign(a, GTK_ALIGN_END); - GtkWidget *b = gtk_label_new("to start/stop streaming"); - gtk_widget_set_halign(b, GTK_ALIGN_START); + GtkWidget *b = gtk_label_new("to start/stop streaming"); + gtk_widget_set_halign(b, GTK_ALIGN_START); - streaming_hotkey_button = gtk_entry_new(); - gtk_entry_set_text(GTK_ENTRY(streaming_hotkey_button), "Alt + F1"); - g_signal_connect(streaming_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), streaming_hotkey_button); - gtk_grid_attach(grid, a, 0, row, 1, 1); - gtk_grid_attach(grid, streaming_hotkey_button, 1, row, 1, 1); - gtk_grid_attach(grid, b, 2, row, 1, 1); - ++row; - gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 3, 1); + streaming_hotkey_button = gtk_entry_new(); + gtk_entry_set_text(GTK_ENTRY(streaming_hotkey_button), "Alt + F1"); + g_signal_connect(streaming_hotkey_button, "button-press-event", G_CALLBACK(on_hotkey_entry_click), streaming_hotkey_button); + gtk_grid_attach(grid, a, 0, row, 1, 1); + gtk_grid_attach(grid, streaming_hotkey_button, 1, row, 1, 1); + gtk_grid_attach(grid, b, 2, row, 1, 1); + ++row; + gtk_grid_attach(grid, gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, row++, 3, 1); + } streaming_hotkey.modkey_mask = modkey_to_mask(XK_Alt_L); streaming_hotkey.keysym = XK_F1; @@ -2353,36 +2589,41 @@ static void add_audio_input_track(const char *name) { static void load_config(const gpu_info &gpu_inf) { config = read_config(); - if(strcmp(config.main_config.record_area_option.c_str(), "window") == 0) { + if(!wayland && strcmp(config.main_config.record_area_option.c_str(), "window") == 0) { // - } else if(strcmp(config.main_config.record_area_option.c_str(), "focused") == 0) { + } else if(!wayland && strcmp(config.main_config.record_area_option.c_str(), "focused") == 0) { // - } else if(strcmp(config.main_config.record_area_option.c_str(), "screen") == 0 || strcmp(config.main_config.record_area_option.c_str(), "screen-direct-force") == 0) { + } else if(!wayland && (strcmp(config.main_config.record_area_option.c_str(), "screen") == 0 || strcmp(config.main_config.record_area_option.c_str(), "screen-direct-force") == 0)) { // } else { + void *connection = wayland ? (void*)drm_card_path : gdk_x11_get_default_xdisplay(); + const gsr_connection_type connection_type = wayland ? GSR_CONNECTION_DRM : GSR_CONNECTION_X11; + bool found_monitor = false; int monitor_name_size = strlen(config.main_config.record_area_option.c_str()); - for_each_active_monitor_output(gdk_x11_get_default_xdisplay(), [&](const XRROutputInfo *output_info, const XRRCrtcInfo*, const XRRModeInfo*) { - if(monitor_name_size == output_info->nameLen && strncmp(config.main_config.record_area_option.c_str(), output_info->name, output_info->nameLen) == 0) { + for_each_active_monitor_output(connection, connection_type, [&](const gsr_monitor *monitor, void*) { + if(monitor_name_size == monitor->name_len && strncmp(config.main_config.record_area_option.c_str(), monitor->name, monitor->name_len) == 0) { found_monitor = true; } - }); + }, NULL); if(!found_monitor) config.main_config.record_area_option.clear(); } if(config.main_config.record_area_option.empty()) { - const bool allow_screen_capture = nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA; + const bool allow_screen_capture = wayland || nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA; if(allow_screen_capture) config.main_config.record_area_option = "screen"; else config.main_config.record_area_option = "window"; } - gtk_widget_set_visible(GTK_WIDGET(select_window_button), strcmp(config.main_config.record_area_option.c_str(), "window") == 0); - gtk_widget_set_visible(GTK_WIDGET(area_size_label), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0); - gtk_widget_set_visible(GTK_WIDGET(area_size_grid), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0); + if(!wayland) { + gtk_widget_set_visible(GTK_WIDGET(select_window_button), strcmp(config.main_config.record_area_option.c_str(), "window") == 0); + gtk_widget_set_visible(GTK_WIDGET(area_size_label), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0); + gtk_widget_set_visible(GTK_WIDGET(area_size_grid), strcmp(config.main_config.record_area_option.c_str(), "focused") == 0); + } if(config.main_config.record_area_width == 0) config.main_config.record_area_width = 1920; @@ -2422,8 +2663,10 @@ static void load_config(const gpu_info &gpu_inf) { config.replay_config.replay_time = 1200; gtk_combo_box_set_active_id(GTK_COMBO_BOX(record_area_selection_menu), config.main_config.record_area_option.c_str()); - gtk_spin_button_set_value(area_width_entry, config.main_config.record_area_width); - gtk_spin_button_set_value(area_height_entry, config.main_config.record_area_height); + if(!wayland) { + gtk_spin_button_set_value(area_width_entry, config.main_config.record_area_width); + gtk_spin_button_set_value(area_height_entry, config.main_config.record_area_height); + } gtk_spin_button_set_value(fps_entry, config.main_config.fps); gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(merge_audio_tracks_button), config.main_config.merge_audio_tracks); for(const std::string &audio_input : config.main_config.audio_input) { @@ -2436,7 +2679,7 @@ static void load_config(const gpu_info &gpu_inf) { gtk_combo_box_set_active_id(GTK_COMBO_BOX(stream_service_input_menu), config.streaming_config.streaming_service.c_str()); gtk_entry_set_text(stream_id_entry, config.streaming_config.stream_key.c_str()); - if(config.streaming_config.start_recording_hotkey.keysym) { + if(!wayland && config.streaming_config.start_recording_hotkey.keysym) { streaming_hotkey.keysym = config.streaming_config.start_recording_hotkey.keysym; streaming_hotkey.modkey_mask = config.streaming_config.start_recording_hotkey.modifiers; set_hotkey_text_from_hotkey_data(GTK_ENTRY(streaming_hotkey_button), streaming_hotkey); @@ -2444,7 +2687,7 @@ static void load_config(const gpu_info &gpu_inf) { gtk_button_set_label(record_file_chooser_button, config.record_config.save_directory.c_str()); gtk_combo_box_set_active_id(GTK_COMBO_BOX(record_container), config.record_config.container.c_str()); - if(config.record_config.start_recording_hotkey.keysym) { + if(!wayland && config.record_config.start_recording_hotkey.keysym) { record_hotkey.keysym = config.record_config.start_recording_hotkey.keysym; record_hotkey.modkey_mask = config.record_config.start_recording_hotkey.modifiers; set_hotkey_text_from_hotkey_data(GTK_ENTRY(record_hotkey_button), record_hotkey); @@ -2453,12 +2696,12 @@ static void load_config(const gpu_info &gpu_inf) { gtk_button_set_label(replay_file_chooser_button, config.replay_config.save_directory.c_str()); gtk_combo_box_set_active_id(GTK_COMBO_BOX(replay_container), config.replay_config.container.c_str()); gtk_spin_button_set_value(replay_time_entry, config.replay_config.replay_time); - if(config.replay_config.start_recording_hotkey.keysym) { + if(!wayland && config.replay_config.start_recording_hotkey.keysym) { replay_start_stop_hotkey.keysym = config.replay_config.start_recording_hotkey.keysym; replay_start_stop_hotkey.modkey_mask = config.replay_config.start_recording_hotkey.modifiers; set_hotkey_text_from_hotkey_data(GTK_ENTRY(replay_start_stop_hotkey_button), replay_start_stop_hotkey); } - if(config.replay_config.save_recording_hotkey.keysym) { + if(!wayland && config.replay_config.save_recording_hotkey.keysym) { replay_save_hotkey.keysym = config.replay_config.save_recording_hotkey.keysym; replay_save_hotkey.modkey_mask = config.replay_config.save_recording_hotkey.modifiers; set_hotkey_text_from_hotkey_data(GTK_ENTRY(replay_save_hotkey_button), replay_save_hotkey); @@ -2466,16 +2709,18 @@ static void load_config(const gpu_info &gpu_inf) { gtk_combo_box_set_active_id(GTK_COMBO_BOX(view_combo_box), config.main_config.advanced_view ? "advanced" : "simple"); view_combo_box_change_callback(GTK_COMBO_BOX(view_combo_box), view_combo_box); - gtk_widget_set_visible(record_hotkey.hotkey_active_label, false); - gtk_widget_set_visible(streaming_hotkey.hotkey_active_label, false); - gtk_widget_set_visible(replay_start_stop_hotkey.hotkey_active_label, false); - gtk_widget_set_visible(replay_save_hotkey.hotkey_active_label, false); + if(!wayland) { + gtk_widget_set_visible(record_hotkey.hotkey_active_label, false); + gtk_widget_set_visible(streaming_hotkey.hotkey_active_label, false); + gtk_widget_set_visible(replay_start_stop_hotkey.hotkey_active_label, false); + gtk_widget_set_visible(replay_save_hotkey.hotkey_active_label, false); + } enable_stream_record_button_if_info_filled(); } -static bool gl_get_gpu_info(Display *dpy, gpu_info *info) { +static bool gl_get_gpu_info(Display *dpy, gpu_info *info, bool wayland) { gsr_egl gl; - if(!gsr_egl_load(&gl, dpy)) { + if(!gsr_egl_load(&gl, dpy, wayland)) { fprintf(stderr, "Error: failed to load opengl\n"); return false; } @@ -2520,10 +2765,12 @@ static bool is_xwayland(Display *dpy) { return true; bool xwayland_found = false; - for_each_active_monitor_output(dpy, [&xwayland_found](const XRROutputInfo *output_info, const XRRCrtcInfo*, const XRRModeInfo*) { - if(output_info->nameLen >= 8 && strncmp(output_info->name, "XWAYLAND", 8) == 0) + for_each_active_monitor_output(dpy, GSR_CONNECTION_X11, [&xwayland_found](const gsr_monitor *monitor, void*) { + if(monitor->name_len >= 8 && strncmp(monitor->name, "XWAYLAND", 8) == 0) xwayland_found = true; - }); + else if(memmem(monitor->name, monitor->name_len, "X11", 3)) + xwayland_found = true; + }, NULL); return xwayland_found; } @@ -2537,25 +2784,29 @@ static const char* gpu_vendor_to_name(gpu_vendor vendor) { } static void activate(GtkApplication *app, gpointer) { - nvfbc_installed = is_nv_fbc_installed(); - Display *dpy = XOpenDisplay(NULL); - const bool is_wayland = !dpy; - - if(is_wayland || is_xwayland(dpy)) { + wayland = !dpy || is_xwayland(dpy); + if(dpy) XCloseDisplay(dpy); - GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "GPU Screen Recorder only works in a pure X11 session. Xwayland is not supported."); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - g_application_quit(G_APPLICATION(app)); - return; + + nvfbc_installed = !wayland && is_nv_fbc_installed(); + + if(wayland) { + if(!gsr_get_valid_card_path(drm_card_path)) { + GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "Failed to find a valid DRM card."); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_application_quit(G_APPLICATION(app)); + return; + } + } else { + drm_card_path[0] = '\0'; } - XCloseDisplay(dpy); - if(!gl_get_gpu_info(gdk_x11_get_default_xdisplay(), &gpu_inf)) { + if(!gl_get_gpu_info(gdk_x11_get_default_xdisplay(), &gpu_inf, wayland)) { GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "Failed to get OpenGL information. Make sure your are using a NVIDIA GPU and that you have NVIDIA proprietary drivers installed."); + "Failed to get OpenGL information. Make sure your GPU drivers are properly installed."); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); g_application_quit(G_APPLICATION(app)); @@ -2592,7 +2843,8 @@ static void activate(GtkApplication *app, gpointer) { select_window_userdata.app = app; - crosshair_cursor = XCreateFontCursor(gdk_x11_get_default_xdisplay(), XC_crosshair); + if(!wayland) + crosshair_cursor = XCreateFontCursor(gdk_x11_get_default_xdisplay(), XC_crosshair); GtkStack *stack = GTK_STACK(gtk_stack_new()); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(stack)); @@ -2621,11 +2873,13 @@ static void activate(GtkApplication *app, gpointer) { g_signal_connect(stream_button, "clicked", G_CALLBACK(on_start_streaming_click), &page_navigation_userdata); g_signal_connect(stream_back_button, "clicked", G_CALLBACK(on_streaming_recording_replay_page_back_click), &page_navigation_userdata); - xim = XOpenIM(gdk_x11_get_default_xdisplay(), NULL, NULL, NULL); - xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL); + if(!wayland) { + xim = XOpenIM(gdk_x11_get_default_xdisplay(), NULL, NULL, NULL); + xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL); - GdkWindow *root_window = gdk_get_default_root_window(); - gdk_window_add_filter(root_window, hotkey_filter_callback, &page_navigation_userdata); + GdkWindow *root_window = gdk_get_default_root_window(); + gdk_window_add_filter(root_window, hotkey_filter_callback, &page_navigation_userdata); + } g_timeout_add(1000, handle_child_process_death, app); -- cgit v1.2.3