From cbd62bda0252c919556b733e3db94fa431dbc29a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 18 Jul 2023 04:29:09 +0200 Subject: Fix for wayland --- README.md | 4 +- TODO | 2 + build.sh | 10 +- com.dec05eba.gpu_screen_recorder.appdata.xml | 11 +- project.conf | 7 +- src/egl.c | 350 ++++++++++++--- src/egl.h | 51 ++- src/main.cpp | 610 +++++++++++++++++++-------- 8 files changed, 801 insertions(+), 244 deletions(-) diff --git a/README.md b/README.md index a035d35..f66585d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ where only the last few seconds are saved. More info at [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/about/). ## Note -This software works only on X11 (Wayland with Xwayland is NOT supported). +This software works with x11 and wayland, but when using wayland only monitors can be recorded and root access is needed. ### TEMPORARY ISSUES 1) Recording the monitor on steam deck might fail sometimes. This happens even when using ffmpeg directly. This might be a steam deck driver bug. Recording a single window doesn't have this issue. 2) Videos created on AMD/Intel are in variable framerate format. Use MPV to play such videos, otherwise you might experience stuttering in the video if you are using a buggy video player. Try saving the video into a .mkv file instead when using AMD/Intel, as some software may have better support for .mkv files (such as kdenlive). @@ -27,7 +27,7 @@ Performance is the same when recording a single window or the monitor, however i ## Installation This program depends on [gpu-screen-recorder](https://git.dec05eba.com/gpu-screen-recorder/) which needs to be installed first.\ Run `sudo ./install.sh` or if you are running Arch Linux, then you can find gpu screen recorder gtk on aur under the name gpu-screen-recorder-gtk-git (`yay -S gpu-screen-recorder-gtk-git`).\ -Dependencies needed when building using `build.sh` or `install.sh`: `gtk3 libx11 libxrandr libpulse`.\ +Dependencies needed when building using `build.sh` or `install.sh`: `gtk3 libx11 libxrandr libpulse libdrm wayland-client wayland-egl`.\ You can also install gpu screen recorder (the gtk gui version) from [flathub](https://flathub.org/apps/details/com.dec05eba.gpu_screen_recorder). This flatpak includes gpu-screen-recorder so no need to install that first.\ Note that if you use the flatpak version then you wont be able to use overclocking unless you set "Coolbits" NVIDIA X setting. See https://git.dec05eba.com/gpu-screen-recorder/about/ for more information and how to overcome this. diff --git a/TODO b/TODO index c225d44..41123e0 100644 --- a/TODO +++ b/TODO @@ -3,3 +3,5 @@ Make sure the resolution is allowed for streaming. Add list of windows to select from. This makes it easier to select another window that is not in the view to be clickable. Disable overclocking and show some kind of sign that overclocking is not possible (if coolbits not set). Button (in the field) to remove hotkey. +Implement global hotkeys on wayland. +If wlroots, use wayland instead of drm. \ No newline at end of file diff --git a/build.sh b/build.sh index 572d354..aa1b992 100755 --- a/build.sh +++ b/build.sh @@ -1,11 +1,17 @@ #!/bin/sh -e +script_dir=$(dirname "$0") +cd "$script_dir" + +CC=${CC:-gcc} +CXX=${CXX:-g++} + opts="-O2 -g0 -DNDEBUG -Wall -Wextra -Werror -s" [ -n "$DEBUG" ] && opts="-O0 -g3 -Wall -Wextra -Werror"; -dependencies="gtk+-3.0 x11 xrandr libpulse" +dependencies="gtk+-3.0 x11 xrandr libpulse libdrm wayland-egl wayland-client" includes="$(pkg-config --cflags $dependencies)" libs="$(pkg-config --libs $dependencies) -ldl" gcc -c src/egl.c $opts $includes g++ -c src/main.cpp $opts $includes -g++ -o gpu-screen-recorder-gtk -O2 egl.o main.o $libs $opts +g++ -o gpu-screen-recorder-gtk egl.o main.o $libs $opts diff --git a/com.dec05eba.gpu_screen_recorder.appdata.xml b/com.dec05eba.gpu_screen_recorder.appdata.xml index d9ac2c8..4421aea 100644 --- a/com.dec05eba.gpu_screen_recorder.appdata.xml +++ b/com.dec05eba.gpu_screen_recorder.appdata.xml @@ -2,7 +2,7 @@ com.dec05eba.gpu_screen_recorder GPU Screen Recorder - A shadowplay-like screen recorder for Linux. The fastest screen recorder for Linux. + A shadowplay-like screen recorder for Linux. The fastest screen recorder for Linux dec05eba CC0-1.0 GPL-3.0 @@ -18,7 +18,7 @@

- This is a screen recorder that has minimal impact on system performance by recording a window using the GPU only, similar to shadowplay on windows. This is the fastest screen recording tool for Linux. This screen recorder only works on X11 (Wayland with Xwayland is NOT supported). + This is a screen recorder that has minimal impact on system performance by recording a window using the GPU only, similar to shadowplay on windows. This is the fastest screen recording tool for Linux. This screen recorder works with both X11 and Wayland.

This screen recorder can be used for recording your desktop offline, for live streaming and for nvidia-like instant replay, where only the last few seconds are saved. @@ -26,7 +26,7 @@

There are some restrictions when recording on AMD/Intel. Videos created on AMD/Intel are in variable framerate format. Very out of date video players might have an issue playing such videos. I recommend using MPV or a browser to play such videos, otherwise you might experience stuttering in the video. Recording a monitor requires (restricted) root access which means that you have to install GPU Screen Recorder system-wide: flatpak install flathub --system com.dec05eba.gpu_screen_recorder - and pkexec needs to be installed on the system. + and pkexec needs to be installed on the system. This is also the case when using wayland on Nvidia. Some distros such as manjaro disable hardware accelerated H264/HEVC which means GPU Screen Recorder wont work on AMD/Intel and you have to either switch to another distro or install mesa from source (or install mesa-git for example).

@@ -44,6 +44,11 @@ + + +

Experimental wayland support on AMD/Intel/NVIDIA. Hotkeys not supported.

+
+

Attempt to fix screen recording when multiple graphics cards are connected

diff --git a/project.conf b/project.conf index bc9b3b6..9c58c71 100644 --- a/project.conf +++ b/project.conf @@ -1,7 +1,7 @@ [package] name = "gpu-screen-recorder-gtk" type = "executable" -version = "1.3.2" +version = "3.0.0" platforms = ["posix"] [config] @@ -11,4 +11,7 @@ error_on_warning = "true" gtk+-3.0 = "3" x11 = "1" xrandr = "1" -libpulse = ">=13" \ No newline at end of file +libpulse = ">=13" +libdrm = ">=2" +wayland-egl = ">=15" +wayland-client = ">=1" \ No newline at end of file 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