diff options
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | TODO | 13 | ||||
-rw-r--r-- | com.dec05eba.gpu_screen_recorder.appdata.xml | 11 | ||||
-rw-r--r-- | meson.build | 5 | ||||
-rw-r--r-- | project.conf | 5 | ||||
-rw-r--r-- | src/egl.c | 380 | ||||
-rw-r--r-- | src/egl.h | 114 | ||||
-rw-r--r-- | src/library_loader.c | 34 | ||||
-rw-r--r-- | src/library_loader.h | 17 | ||||
-rw-r--r-- | src/main.cpp | 896 |
10 files changed, 386 insertions, 1093 deletions
@@ -11,7 +11,7 @@ where only the last few minutes are saved. More info at [GPU Screen Recorder](https://git.dec05eba.com/gpu-screen-recorder/about/). ## Note -This software works with x11 and wayland, but when using wayland only monitors can be recorded. Hotkeys are also not supported on wayland (wayland doesn't really support this properly yet). Use X11 if you want a proper desktop experience in general. +This software works on x11 and wayland. Hotkeys are also not supported on wayland (wayland doesn't really support this properly yet). Use X11 if you want a proper desktop experience in general. ### TEMPORARY ISSUES 1) Videos 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. You can try saving the video into a .mkv file instead as some software may have better support for .mkv files (such as kdenlive). You can use the "-fm cfr" option to to use constant framerate mode. ### AMD/Intel/Wayland root permission @@ -32,7 +32,7 @@ Bind a key to `killall -SIGINT gpu-screen-recorder` to stop recording (also save ## 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 `install.sh`: `meson gtk3 libx11 libxrandr libpulse libdrm wayland-client ayatana-appindicator3-0.1`.\ +Dependencies needed when building using `install.sh`: `meson gtk3 libx11 libpulse libdrm ayatana-appindicator3-0.1`.\ 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. ## Screenshots @@ -24,4 +24,15 @@ Use https://hosted.weblate.org/ for translation. Detect game name by using x11 window class or title. Fallback to finding pressure vessel, find the binary is runs and get the directory name directly under the proton game list directory. Fallback to pure wine. -Add option to use software encoder (-encoder cpu).
\ No newline at end of file +Add option to use software encoder (-encoder cpu) by adding "h264 (software)" video encoder option. Make video encoder option available in simple view as well. + If no hardware accelerated video encoder option is available then show a warning window (only the first time) and switch to the software video encoder. + Do not show error if only vp8/vp9 is available and automatically choose vp8 in that case. + The video encoder option should always show all possible options but make them unselectable (with text why), just like the record area option (capture method). + +Have separate options for each record option (stream, record and replay) or have option to use profiles. Remake the gui and have a proper overlay! on wlroots and kde use https://wayland.app/protocols/wlr-layer-shell-unstable-v1. + +Add refresh button for audio devices. Put it beside the "add" button. In the new ui this should update automatically without a button. + +Add support for desktop portal capture. Test first ScreenCast "version" property to see if the desktop portal is running at all with support for ScreenCast. Make it grayed out if not. + +Gray out monitor capture on intel if plane is compressed. Show the user to desktop portal capture instead. diff --git a/com.dec05eba.gpu_screen_recorder.appdata.xml b/com.dec05eba.gpu_screen_recorder.appdata.xml index 20c1e7c..1bd1690 100644 --- a/com.dec05eba.gpu_screen_recorder.appdata.xml +++ b/com.dec05eba.gpu_screen_recorder.appdata.xml @@ -18,7 +18,7 @@ <description> <p> - This is a screen recorder that has minimal impact on system performance by recording a monitor 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 is a screen recorder that has minimal impact on system performance by recording a monitor using the GPU only, similar to shadowplay on windows. This is the fastest screen recording tool for Linux. This screen recorder works on X11 and Wayland. </p> <p> 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 minutes are saved. @@ -39,10 +39,11 @@ <p> 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 a polkit agent needs to be running. + Note that this only applies to when recording a monitor on AMD/Intel or when recording on Wayland without using the desktop portal option. </p> <p>Recording a single window is only possible on X11. Hotkeys are not supported on wayland either (wayland doesn't really support this). Use X11 if you want a proper desktop experience in general.</p> <p>AV1 is currently not supported in the flatpak for Nvidia since GPU Screen Recorder uses an older ffmpeg version to support older Nvidia cards. Install GPU Screen Recorder from source or from AUR if you want to use AV1 on Nvidia.</p> - <p>On some Intel integrated GPUs the video can appear glitched or be a black screen when recording on Wayland. The only known workaround at the moment is to record on X11. This will be fixed in a future version.</p> + <p>On some Intel integrated GPUs the video can appear glitched or be a black screen when recording on Wayland. Right now the only way to fix this is to either record on X11 (maybe only with the window capture option) or to record with the desktop portal option (usually only available on Wayland).</p> <p> Videos are in variable framerate format. Very out of date video players might have an issue playing such videos. It's recommend to use MPV to play such videos, otherwise you might experience stuttering in the video. You can select constant frame rate mode in advanced view if you need it. @@ -81,10 +82,12 @@ </screenshots> <releases> - <release version="3.8.3" date="2024-07-06"> + <release version="4.0.0" date="2024-07-15"> <description> <ul> - <li>Add VP8 and VP9 if supported by the hardware</li> + <li>Added desktop portal (pipewire) capture option. This fixes issue with glitched capture on certain Intel iGPUS (on Wayland)</li> + <li>Added VP8 and VP9 video codecs if supported by the hardware</li> + <li>Added software encoding option</li> </ul> </description> </release> diff --git a/meson.build b/meson.build index d9eaa16..f7c833d 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gpu-screen-recorder-gtk', ['c', 'cpp'], version : '3.8.0', default_options : ['warning_level=2']) +project('gpu-screen-recorder-gtk', ['c', 'cpp'], version : '4.0.0', default_options : ['warning_level=2']) add_project_arguments('-Wshadow', language : ['c', 'cpp']) if get_option('buildtype') == 'debug' @@ -16,11 +16,8 @@ src = [ dep = [ dependency('gtk+-3.0'), dependency('x11'), - dependency('xrandr'), dependency('libpulse'), dependency('libdrm'), - dependency('wayland-egl'), - dependency('wayland-client'), dependency('ayatana-appindicator3-0.1'), ] diff --git a/project.conf b/project.conf index 164cf6b..93a66cd 100644 --- a/project.conf +++ b/project.conf @@ -1,7 +1,7 @@ [package] name = "gpu-screen-recorder-gtk" type = "executable" -version = "3.8.0" +version = "4.0.0" platforms = ["posix"] [config] @@ -11,9 +11,6 @@ error_on_warning = "true" [dependencies] gtk+-3.0 = "3" x11 = "1" -xrandr = "1" libpulse = ">=13" libdrm = ">=2" -wayland-egl = ">=15" -wayland-client = ">=1" ayatana-appindicator3-0.1 = ">=0" diff --git a/src/egl.c b/src/egl.c deleted file mode 100644 index 79aab1f..0000000 --- a/src/egl.c +++ /dev/null @@ -1,380 +0,0 @@ -#include "egl.h" -#include "library_loader.h" -#include <string.h> -#include <stdio.h> -#include <stdlib.h> -#include <dlfcn.h> -#include <assert.h> - -#include <wayland-client.h> -#include <wayland-egl.h> -#include <unistd.h> - -static void output_handle_geometry(void *data, struct wl_output *wl_output, - int32_t x, int32_t y, int32_t phys_width, int32_t phys_height, - int32_t subpixel, const char *make, const char *model, - int32_t transform) { - (void)wl_output; - (void)phys_width; - (void)phys_height; - (void)subpixel; - (void)make; - (void)model; - (void)transform; - gsr_wayland_output *gsr_output = data; - gsr_output->pos.x = x; - gsr_output->pos.y = y; -} - -static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, int32_t width, int32_t height, int32_t refresh) { - (void)wl_output; - (void)flags; - (void)refresh; - gsr_wayland_output *gsr_output = data; - gsr_output->size.x = width; - gsr_output->size.y = height; -} - -static void output_handle_done(void *data, struct wl_output *wl_output) { - (void)data; - (void)wl_output; -} - -static void output_handle_scale(void* data, struct wl_output *wl_output, int32_t factor) { - (void)data; - (void)wl_output; - (void)factor; -} - -static void output_handle_name(void *data, struct wl_output *wl_output, const char *name) { - (void)wl_output; - gsr_wayland_output *gsr_output = data; - if(gsr_output->name) { - free(gsr_output->name); - gsr_output->name = NULL; - } - gsr_output->name = strdup(name); -} - -static void output_handle_description(void *data, struct wl_output *wl_output, const char *description) { - (void)data; - (void)wl_output; - (void)description; -} - -static const struct wl_output_listener output_listener = { - .geometry = output_handle_geometry, - .mode = output_handle_mode, - .done = output_handle_done, - .scale = output_handle_scale, - .name = output_handle_name, - .description = output_handle_description, -}; - -static void registry_add_object(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { - (void)version; - gsr_egl *egl = data; - if (strcmp(interface, "wl_compositor") == 0) { - if(egl->wayland.compositor) { - wl_compositor_destroy(egl->wayland.compositor); - egl->wayland.compositor = NULL; - } - egl->wayland.compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); - } else if(strcmp(interface, wl_output_interface.name) == 0) { - if(version < 4) { - fprintf(stderr, "gsr warning: wl output interface version is < 4, expected >= 4 to capture a monitor. Using KMS capture instead\n"); - return; - } - - if(egl->wayland.num_outputs == GSR_MAX_OUTPUTS) { - fprintf(stderr, "gsr warning: reached maximum outputs (32), ignoring output %u\n", name); - return; - } - - gsr_wayland_output *gsr_output = &egl->wayland.outputs[egl->wayland.num_outputs]; - egl->wayland.num_outputs++; - *gsr_output = (gsr_wayland_output) { - .wl_name = name, - .output = wl_registry_bind(registry, name, &wl_output_interface, 4), - .pos = { .x = 0, .y = 0 }, - .size = { .x = 0, .y = 0 }, - .name = NULL, - }; - wl_output_add_listener(gsr_output->output, &output_listener, gsr_output); - } -} - -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 = { - .global = registry_add_object, - .global_remove = registry_remove_object, -}; - -// 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; - - const int32_t attr[] = { - EGL_BUFFER_SIZE, 24, - EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, - EGL_NONE - }; - - const int32_t ctxattr[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - if(wayland) { - self->wayland.dpy = wl_display_connect(NULL); - if(!self->wayland.dpy) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: wl_display_connect failed\n"); - goto fail; - } - - self->wayland.registry = wl_display_get_registry(self->wayland.dpy); // TODO: Error checking - wl_registry_add_listener(self->wayland.registry, ®istry_listener, self); // TODO: Error checking - - // Fetch globals - wl_display_roundtrip(self->wayland.dpy); - - // fetch wl_output - wl_display_roundtrip(self->wayland.dpy); - - if(!self->wayland.compositor) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to find compositor\n"); - goto fail; - } - } else { - self->x11.window = XCreateWindow(self->x11.dpy, DefaultRootWindow(self->x11.dpy), 0, 0, 16, 16, 0, CopyFromParent, InputOutput, CopyFromParent, 0, NULL); - - if(!self->x11.window) { - fprintf(stderr, "gsr error: gsr_gl_create_window failed: failed to create gl window\n"); - goto fail; - } - } - - self->eglBindAPI(EGL_OPENGL_API); - - self->egl_display = self->eglGetDisplay(self->wayland.dpy ? (EGLNativeDisplayType)self->wayland.dpy : (EGLNativeDisplayType)self->x11.dpy); - if(!self->egl_display) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglGetDisplay failed\n"); - goto fail; - } - - if(!self->eglInitialize(self->egl_display, NULL, NULL)) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: eglInitialize failed\n"); - goto fail; - } - - if(!self->eglChooseConfig(self->egl_display, attr, &ecfg, 1, &num_config) || num_config != 1) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to find a matching config\n"); - goto fail; - } - - self->egl_context = self->eglCreateContext(self->egl_display, ecfg, NULL, ctxattr); - if(!self->egl_context) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create egl context\n"); - goto fail; - } - - if(wayland) { - self->wayland.surface = wl_compositor_create_surface(self->wayland.compositor); - self->wayland.window = wl_egl_window_create(self->wayland.surface, 16, 16); - self->egl_surface = self->eglCreateWindowSurface(self->egl_display, ecfg, (EGLNativeWindowType)self->wayland.window, NULL); - } else { - self->egl_surface = self->eglCreateWindowSurface(self->egl_display, ecfg, (EGLNativeWindowType)self->x11.window, NULL); - } - - if(!self->egl_surface) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to create window surface\n"); - goto fail; - } - - if(!self->eglMakeCurrent(self->egl_display, self->egl_surface, self->egl_surface, self->egl_context)) { - fprintf(stderr, "gsr error: gsr_egl_create_window failed: failed to make context current\n"); - goto fail; - } - - return true; - - fail: - gsr_egl_unload(self); - return false; -} - -static bool gsr_egl_load_egl(gsr_egl *self, void *library) { - const dlsym_assign required_dlsym[] = { - { (void**)&self->eglGetDisplay, "eglGetDisplay" }, - { (void**)&self->eglInitialize, "eglInitialize" }, - { (void**)&self->eglTerminate, "eglTerminate" }, - { (void**)&self->eglChooseConfig, "eglChooseConfig" }, - { (void**)&self->eglCreateWindowSurface, "eglCreateWindowSurface" }, - { (void**)&self->eglCreateContext, "eglCreateContext" }, - { (void**)&self->eglMakeCurrent, "eglMakeCurrent" }, - { (void**)&self->eglDestroyContext, "eglDestroyContext" }, - { (void**)&self->eglDestroySurface, "eglDestroySurface" }, - { (void**)&self->eglBindAPI, "eglBindAPI" }, - { (void**)&self->eglGetProcAddress, "eglGetProcAddress" }, - - { NULL, NULL } - }; - - if(!dlsym_load_list(library, required_dlsym)) { - fprintf(stderr, "gsr error: gsr_egl_load failed: missing required symbols in libEGL.so.1\n"); - return false; - } - - return true; -} - -static bool gsr_egl_load_gl(gsr_egl *self, void *library) { - const dlsym_assign required_dlsym[] = { - { (void**)&self->glGetString, "glGetString" }, - - { NULL, NULL } - }; - - if(!dlsym_load_list(library, required_dlsym)) { - fprintf(stderr, "gsr error: gsr_egl_load failed: missing required symbols in libGL.so.1\n"); - return false; - } - - return true; -} - -static bool gsr_egl_proc_load_egl(gsr_egl *self) { - self->eglQueryDisplayAttribEXT = (FUNC_eglQueryDisplayAttribEXT)self->eglGetProcAddress("eglQueryDisplayAttribEXT"); - self->eglQueryDeviceStringEXT = (FUNC_eglQueryDeviceStringEXT)self->eglGetProcAddress("eglQueryDeviceStringEXT"); - - return true; -} - -bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland) { - memset(self, 0, sizeof(gsr_egl)); - self->x11.dpy = dpy; - - void *egl_lib = NULL; - void *gl_lib = NULL; - - dlerror(); /* clear */ - 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()); - goto fail; - } - - 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()); - goto fail; - } - - if(!gsr_egl_load_egl(self, egl_lib)) - goto fail; - - if(!gsr_egl_load_gl(self, gl_lib)) - goto fail; - - if(!gsr_egl_proc_load_egl(self)) - goto fail; - - if(!gsr_egl_create_window(self, wayland)) - goto fail; - - if(self->eglQueryDisplayAttribEXT && self->eglQueryDeviceStringEXT) { - intptr_t device = 0; - if(self->eglQueryDisplayAttribEXT(self->egl_display, EGL_DEVICE_EXT, &device) && device) - self->dri_card_path = self->eglQueryDeviceStringEXT((void*)device, EGL_DRM_DEVICE_FILE_EXT); - } - - 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) { - if(self->egl_context) { - self->eglDestroyContext(self->egl_display, self->egl_context); - self->egl_context = NULL; - } - - if(self->egl_surface) { - self->eglDestroySurface(self->egl_display, self->egl_surface); - self->egl_surface = NULL; - } - - if(self->egl_display) { - self->eglTerminate(self->egl_display); - self->egl_display = NULL; - } - - 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; - } - - for(int i = 0; i < self->wayland.num_outputs; ++i) { - if(self->wayland.outputs[i].output) { - wl_output_destroy(self->wayland.outputs[i].output); - self->wayland.outputs[i].output = NULL; - } - - if(self->wayland.outputs[i].name) { - free(self->wayland.outputs[i].name); - self->wayland.outputs[i].name = NULL; - } - } - self->wayland.num_outputs = 0; - - 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) { - dlclose(self->egl_library); - self->egl_library = NULL; - } - - if(self->gl_library) { - dlclose(self->gl_library); - self->gl_library = NULL; - } - - memset(self, 0, sizeof(gsr_egl)); -} diff --git a/src/egl.h b/src/egl.h deleted file mode 100644 index e46a6ab..0000000 --- a/src/egl.h +++ /dev/null @@ -1,114 +0,0 @@ -#ifndef GSR_EGL_H -#define GSR_EGL_H - -/* OpenGL EGL library with a hidden window context (to allow using the opengl functions) */ - -#include <X11/X.h> -#include <X11/Xutil.h> -#include <stdbool.h> -#include <stdint.h> - -typedef struct { - int x, y; -} vec2i; - -#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* 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); - -typedef int (*FUNC_eglQueryDisplayAttribEXT)(EGLDisplay dpy, int32_t attribute, intptr_t *value); -typedef const char* (*FUNC_eglQueryDeviceStringEXT)(void *device, int32_t name); - -#define EGL_BUFFER_SIZE 0x3020 -#define EGL_RENDERABLE_TYPE 0x3040 -#define EGL_OPENGL_BIT 0x0008 -#define EGL_OPENGL_API 0x30A2 -#define EGL_NONE 0x3038 -#define EGL_CONTEXT_CLIENT_VERSION 0x3098 -#define EGL_DEVICE_EXT 0x322C -#define EGL_DRM_DEVICE_FILE_EXT 0x3233 - -#define GL_VENDOR 0x1F00 -#define GL_RENDERER 0x1F01 - -#define GSR_MAX_OUTPUTS 32 - -typedef struct { - Display *dpy; - Window window; -} gsr_x11; - -typedef struct { - uint32_t wl_name; - void *output; - vec2i pos; - vec2i size; - char *name; -} gsr_wayland_output; - -typedef struct { - void *dpy; - void *window; - void *registry; - void *surface; - void *compositor; - gsr_wayland_output outputs[GSR_MAX_OUTPUTS]; - int num_outputs; -} gsr_wayland; - -typedef struct { - void *egl_library; - void *gl_library; - - EGLDisplay egl_display; - EGLSurface egl_surface; - EGLContext egl_context; - const char *dri_card_path; - - gsr_x11 x11; - gsr_wayland wayland; - char card_path[128]; - - EGLDisplay (*eglGetDisplay)(EGLNativeDisplayType display_id); - unsigned int (*eglInitialize)(EGLDisplay dpy, int32_t *major, int32_t *minor); - unsigned int (*eglTerminate)(EGLDisplay dpy); - unsigned int (*eglChooseConfig)(EGLDisplay dpy, const int32_t *attrib_list, EGLConfig *configs, int32_t config_size, int32_t *num_config); - EGLSurface (*eglCreateWindowSurface)(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const int32_t *attrib_list); - EGLContext (*eglCreateContext)(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const int32_t *attrib_list); - 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); - __eglMustCastToProperFunctionPointerType (*eglGetProcAddress)(const char *procname); - - FUNC_eglQueryDisplayAttribEXT eglQueryDisplayAttribEXT; - FUNC_eglQueryDeviceStringEXT eglQueryDeviceStringEXT; - - const unsigned char* (*glGetString)(unsigned int name); -} gsr_egl; - -bool gsr_egl_load(gsr_egl *self, Display *dpy, bool wayland); -void gsr_egl_unload(gsr_egl *self); - -#endif /* GSR_EGL_H */ diff --git a/src/library_loader.c b/src/library_loader.c deleted file mode 100644 index fed1fe5..0000000 --- a/src/library_loader.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "library_loader.h" - -#include <dlfcn.h> -#include <stdbool.h> -#include <stdio.h> - -void* dlsym_print_fail(void *handle, const char *name, bool required) { - dlerror(); - void *sym = dlsym(handle, name); - char *err_str = dlerror(); - - if(!sym) - fprintf(stderr, "%s: dlsym(handle, \"%s\") failed, error: %s\n", required ? "error" : "warning", name, err_str ? err_str : "(null)"); - - return sym; -} - -/* |dlsyms| should be null terminated */ -bool dlsym_load_list(void *handle, const dlsym_assign *dlsyms) { - bool success = true; - for(int i = 0; dlsyms[i].func; ++i) { - *dlsyms[i].func = dlsym_print_fail(handle, dlsyms[i].name, true); - if(!*dlsyms[i].func) - success = false; - } - return success; -} - -/* |dlsyms| should be null terminated */ -void dlsym_load_list_optional(void *handle, const dlsym_assign *dlsyms) { - for(int i = 0; dlsyms[i].func; ++i) { - *dlsyms[i].func = dlsym_print_fail(handle, dlsyms[i].name, false); - } -} diff --git a/src/library_loader.h b/src/library_loader.h deleted file mode 100644 index 47bc9f0..0000000 --- a/src/library_loader.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef GSR_LIBRARY_LOADER_H -#define GSR_LIBRARY_LOADER_H - -#include <stdbool.h> - -typedef struct { - void **func; - const char *name; -} dlsym_assign; - -void* dlsym_print_fail(void *handle, const char *name, bool required); -/* |dlsyms| should be null terminated */ -bool dlsym_load_list(void *handle, const dlsym_assign *dlsyms); -/* |dlsyms| should be null terminated */ -void dlsym_load_list_optional(void *handle, const dlsym_assign *dlsyms); - -#endif /* GSR_LIBRARY_LOADER_H */ diff --git a/src/main.cpp b/src/main.cpp index 2126e61..a2e8c1a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,7 +4,6 @@ #include <X11/Xlib.h> #include <X11/Xatom.h> #include <X11/cursorfont.h> -#include <X11/extensions/Xrandr.h> #include <assert.h> #include <string> #include <pulse/pulseaudio.h> @@ -16,11 +15,6 @@ #include <dlfcn.h> #include <functional> #include <vector> -extern "C" { -#include "egl.h" -} -#include <xf86drmMode.h> -#include <xf86drm.h> #include <libayatana-appindicator/app-indicator.h> typedef struct { @@ -131,9 +125,7 @@ static std::string record_file_current_filename; static bool nvfbc_installed = false; static Display *dpy = NULL; -static bool wayland = false; static bool flatpak = false; -static gsr_egl egl; static bool showing_notification = false; static double notification_timeout_seconds = 0.0; @@ -179,15 +171,68 @@ static Hotkey replay_start_stop_hotkey; static Hotkey replay_save_hotkey; struct SupportedVideoCodecs { - bool h264; - bool hevc; - bool av1; - bool vp8; - bool vp9; + bool h264 = false; + bool hevc = false; + bool av1 = false; + bool vp8 = false; + bool vp9 = false; +}; + +struct vec2i { + int x = 0; + int y = 0; +}; + +struct GsrMonitor { + std::string name; + vec2i size; +}; + +struct SupportedCaptureOptions { + bool window = false; + bool focused = false; + bool screen = false; + bool portal = false; + std::vector<GsrMonitor> monitors; +}; + +enum class DisplayServer { + UNKNOWN, + X11, + WAYLAND +}; + +struct SystemInfo { + DisplayServer display_server = DisplayServer::UNKNOWN; +}; + +enum class GpuVendor { + AMD, + INTEL, + NVIDIA +}; + +struct GpuInfo { + GpuVendor vendor; +}; + +struct GsrInfo { + SystemInfo system_info; + GpuInfo gpu_info; + SupportedVideoCodecs supported_video_codecs; + SupportedCaptureOptions supported_capture_options; +}; + +static GsrInfo gsr_info; + +enum class GsrInfoExitStatus { + OK, + FAILED_TO_RUN_COMMAND, + OPENGL_FAILED, + NO_DRM_CARD }; -static SupportedVideoCodecs supported_video_codecs; -static int supported_video_codecs_exit_status = 0; +static GsrInfoExitStatus gsr_info_exit_status; struct Container { const char *container_name; @@ -208,19 +253,6 @@ struct AudioRow { GtkComboBoxText *input_list; }; -typedef enum { - GPU_VENDOR_AMD, - GPU_VENDOR_INTEL, - GPU_VENDOR_NVIDIA -} gpu_vendor; - -typedef struct { - gpu_vendor vendor; - int gpu_version; /* 0 if unknown */ -} gpu_info; - -static gpu_info gpu_inf; - // Dumb hacks below!! why dont these fking paths work outside flatpak.. except in kde. TODO: fix this! static const char* get_tray_idle_icon_name() { if(flatpak) @@ -637,7 +669,7 @@ static void record_area_selection_menu_set_active_id(const gchar *id) { } static void enable_stream_record_button_if_info_filled() { - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) { const std::string selected_window_area = record_area_selection_menu_get_active_id(); if(strcmp(selected_window_area.c_str(), "window") == 0 && select_window_userdata.selected_window == None) { gtk_widget_set_sensitive(GTK_WIDGET(replay_button), false); @@ -767,7 +799,7 @@ static std::string get_date_str() { static void save_configs() { config.main_config.record_area_option = record_area_selection_menu_get_active_id(); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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); } @@ -794,14 +826,14 @@ static void save_configs() { config.streaming_config.twitch.stream_key = gtk_entry_get_text(twitch_stream_id_entry); config.streaming_config.custom.url = gtk_entry_get_text(custom_stream_url_entry); config.streaming_config.custom.container = gtk_combo_box_get_active_id(GTK_COMBO_BOX(custom_stream_container)); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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)); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) { config.record_config.start_recording_hotkey.keysym = record_hotkey.keysym; config.record_config.start_recording_hotkey.modifiers = record_hotkey.modkey_mask; @@ -812,7 +844,7 @@ static void save_configs() { 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); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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; @@ -823,259 +855,6 @@ static void save_configs() { save_config(config); } -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<void(const gsr_monitor *monitor, void *userdata)>; - -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 NULL; -} - -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 && 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); - } - if(out_info) - XRRFreeOutputInfo(out_info); - } - - XRRFreeScreenResources(screen_res); -} - -typedef struct { - int type; - int count; -} drm_connector_type_count; - -#define CONNECTOR_TYPE_COUNTS 32 - -static drm_connector_type_count* drm_connector_types_get_index(drm_connector_type_count *type_counts, int *num_type_counts, int connector_type) { - for(int i = 0; i < *num_type_counts; ++i) { - if(type_counts[i].type == connector_type) - return &type_counts[i]; - } - - if(*num_type_counts == CONNECTOR_TYPE_COUNTS) - return NULL; - - const int index = *num_type_counts; - type_counts[index].type = connector_type; - type_counts[index].count = 0; - ++*num_type_counts; - return &type_counts[index]; -} - -static bool connector_get_property_by_name(int drmfd, drmModeConnectorPtr props, const char *name, uint64_t *result) { - for(int i = 0; i < props->count_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_wayland(const gsr_egl *egl, active_monitor_callback callback, void *userdata) { - for(int i = 0; i < egl->wayland.num_outputs; ++i) { - if(!egl->wayland.outputs[i].name) - continue; - - gsr_monitor monitor; - monitor.name = egl->wayland.outputs[i].name; - monitor.name_len = strlen(egl->wayland.outputs[i].name); - monitor.pos = { egl->wayland.outputs[i].pos.x, egl->wayland.outputs[i].pos.y }; - monitor.size = { egl->wayland.outputs[i].size.x, egl->wayland.outputs[i].size.y }; - monitor.crt_info = NULL; - monitor.connector_id = 0; - callback(&monitor, userdata); - } -} - -static void for_each_active_monitor_output_drm(const gsr_egl *egl, active_monitor_callback callback, void *userdata) { - int fd = open(egl->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; - - 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; - - if(connector->connection != DRM_MODE_CONNECTED) { - drmModeFreeConnector(connector); - continue; - } - - 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(const gsr_egl *egl, gsr_connection_type connection_type, active_monitor_callback callback, void *userdata) { - switch(connection_type) { - case GSR_CONNECTION_X11: - for_each_active_monitor_output_x11(egl->x11.dpy, callback, userdata); - break; - case GSR_CONNECTION_WAYLAND: - for_each_active_monitor_output_wayland(egl, callback, userdata); - break; - case GSR_CONNECTION_DRM: - for_each_active_monitor_output_drm(egl, callback, userdata); - break; - } -} - -static bool try_card_has_valid_plane(const char *card_path) { - drmVersion *ver = NULL; - drmModePlaneResPtr planes = NULL; - bool found_screen_card = false; - - int fd = open(card_path, O_RDONLY); - if(fd == -1) - return false; - - ver = drmGetVersion(fd); - if(!ver || strstr(ver->name, "nouveau")) - goto next; - - drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); - - planes = drmModeGetPlaneResources(fd); - if(!planes) - goto next; - - for(uint32_t j = 0; j < planes->count_planes; ++j) { - drmModePlanePtr plane = drmModeGetPlane(fd, planes->planes[j]); - if(!plane) - continue; - - if(plane->fb_id) - found_screen_card = true; - - drmModeFreePlane(plane); - if(found_screen_card) - break; - } - - next: - if(planes) - drmModeFreePlaneResources(planes); - if(ver) - drmFreeVersion(ver); - close(fd); - if(found_screen_card) - return true; - - return false; -} - -static void string_copy(char *dst, const char *src, int len) { - int src_len = strlen(src); - int min_len = src_len; - if(len - 1 < min_len) - min_len = len - 1; - memcpy(dst, src, min_len); - dst[min_len] = '\0'; -} - -/* output should be >= 128 bytes */ -static bool gsr_get_valid_card_path(gsr_egl *egl, char *output) { - if(egl->dri_card_path) { - string_copy(output, egl->dri_card_path, 127); - return try_card_has_valid_plane(output); - } - - for(int i = 0; i < 10; ++i) { - snprintf(output, 127, DRM_DEV_NAME, DRM_DIR_NAME, i); - if(try_card_has_valid_plane(output)) - 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); @@ -1087,7 +866,7 @@ static void show_notification(GtkApplication *app, const char *title, const char if(priority < G_NOTIFICATION_PRIORITY_URGENT) { notification_timeout_seconds = 2.0; } else { - notification_timeout_seconds = 5.0; + notification_timeout_seconds = 10.0; } notification_start_seconds = clock_get_monotonic_seconds(); } @@ -1344,7 +1123,7 @@ static int xerror_grab_error(Display*, XErrorEvent*) { } static void ungrab_keyboard(Display *display) { - if(wayland) + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) return; if(current_hotkey) { @@ -1359,7 +1138,7 @@ static void ungrab_keyboard(Display *display) { } static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab) { - if(wayland) + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) return true; if(hotkey.keysym == None && hotkey.modkey_mask == 0) @@ -1417,7 +1196,7 @@ static bool grab_ungrab_hotkey_combo(Display *display, Hotkey hotkey, bool grab) } static void ungrab_keys(Display *display) { - if(wayland) + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) return; grab_ungrab_hotkey_combo(display, streaming_hotkey, false); @@ -1501,27 +1280,31 @@ static HotkeyResult replace_grabbed_keys_depending_on_active_page() { return hotkey_result; } +static bool is_monitor_capture_drm() { + return gsr_info.system_info.display_server == DisplayServer::WAYLAND || gsr_info.gpu_info.vendor != GpuVendor::NVIDIA; +} + static bool show_pkexec_flatpak_error_if_needed() { const std::string window_str = record_area_selection_menu_get_active_id(); - if((wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA) && window_str != "window" && window_str != "focused") { + if(is_monitor_capture_drm() && window_str != "window" && window_str != "focused" && window_str != "portal") { if(!is_pkexec_installed()) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "pkexec needs to be installed to record a monitor with an AMD/Intel GPU. Please install and run polkit. Alternatively, record a single window which doesn't require root access."); + "pkexec needs to be installed to record a monitor with an AMD/Intel GPU. Please install and run polkit. Alternatively, record a single window or use portal option which doesn't require root access."); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); return true; } if(flatpak && !flatpak_is_installed_as_system()) { - if(wayland) { + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "GPU Screen Recorder needs to be installed system-wide to record your monitor on Wayland. To install GPU Screen recorder system-wide, you can run this command:\n" + "GPU Screen Recorder needs to be installed system-wide to record your monitor on Wayland when not using the portal option. To install GPU Screen recorder system-wide, you can run this command:\n" "flatpak install flathub --system com.dec05eba.gpu_screen_recorder\n"); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } else { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "GPU Screen Recorder needs to be installed system-wide to record your monitor on AMD/Intel. To install GPU Screen recorder system-wide, you can run this command:\n" + "GPU Screen Recorder needs to be installed system-wide to record your monitor on AMD/Intel when not using the portal option. To install GPU Screen recorder system-wide, you can run this command:\n" "flatpak install flathub --system com.dec05eba.gpu_screen_recorder\n" "Alternatively, record a single window which doesn't have this restriction."); gtk_dialog_run(GTK_DIALOG(dialog)); @@ -1541,7 +1324,7 @@ static gboolean on_start_replay_click(GtkButton*, gpointer userdata) { gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->replay_page); app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::REPLAY))); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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), ""); @@ -1566,7 +1349,7 @@ static gboolean on_start_recording_click(GtkButton*, gpointer userdata) { gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->recording_page); app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::RECORDING))); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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), ""); @@ -1610,7 +1393,7 @@ static gboolean on_start_streaming_click(GtkButton*, gpointer userdata) { gtk_stack_set_visible_child(page_navigation_userdata->stack, page_navigation_userdata->streaming_page); app_indicator_set_menu(app_indicator, GTK_MENU(create_systray_menu(page_navigation_userdata->app, SystrayPage::STREAMING))); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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), ""); @@ -1664,9 +1447,11 @@ static gboolean on_replay_file_chooser_button_click(GtkButton *button, gpointer) return res; } -static bool kill_gpu_screen_recorder_get_result() { +static bool kill_gpu_screen_recorder_get_result(bool *already_dead) { + *already_dead = true; bool exit_success = true; if(gpu_screen_recorder_process != -1) { + *already_dead = false; int status; int wait_result = waitpid(gpu_screen_recorder_process, &status, WNOHANG); if(wait_result == -1) { @@ -1725,7 +1510,8 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat int exit_status = prev_exit_status; prev_exit_status = -1; if(replaying) { - bool exit_success = kill_gpu_screen_recorder_get_result(); + bool already_dead = true; + bool exit_success = kill_gpu_screen_recorder_get_result(&already_dead); gtk_button_set_label(button, "Start replay"); replaying = false; @@ -1742,7 +1528,7 @@ static gboolean on_start_replay_button_click(GtkButton *button, gpointer userdat if(exit_status == 10) { show_notification(app, "GPU Screen Recorder", "You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT); - } else if(!exit_success) { + } else if(!exit_success || (already_dead && exit_status != 0)) { show_notification(app, "GPU Screen Recorder", "Failed to start replay. Either your graphics card doesn't support GPU Screen Recorder with the settings you used or you don't have enough disk space to record a video", G_NOTIFICATION_PRIORITY_URGENT); } @@ -1754,8 +1540,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 = 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); + int record_width = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_width_entry); + int record_height = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_height_entry); char dir_tmp[PATH_MAX]; strcpy(dir_tmp, dir); @@ -1902,7 +1688,8 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user int exit_status = prev_exit_status; prev_exit_status = -1; if(recording) { - bool exit_success = kill_gpu_screen_recorder_get_result(); + bool already_dead = true; + bool exit_success = kill_gpu_screen_recorder_get_result(&already_dead); gtk_button_set_label(button, "Start recording"); recording = false; @@ -1924,13 +1711,13 @@ static gboolean on_start_recording_button_click(GtkButton *button, gpointer user if(exit_status == 10) { show_notification(app, "GPU Screen Recorder", "You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT); + } else if(!exit_success || (already_dead && exit_status != 0)) { + show_notification(app, "GPU Screen Recorder", "Failed to save video. Either your graphics card doesn't support GPU Screen Recorder with the settings you used or you don't have enough disk space to record a video. Run GPU Screen Recorder from the terminal to see more information when this failure happens", G_NOTIFICATION_PRIORITY_URGENT); } else if(exit_success) { if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(show_notification_button))) { const std::string notification_body = std::string("The recording was saved to ") + record_file_current_filename; show_notification(app, "GPU Screen Recorder", notification_body.c_str(), G_NOTIFICATION_PRIORITY_NORMAL); } - } else { - show_notification(app, "GPU Screen Recorder", "Failed to save video. Either your graphics card doesn't support GPU Screen Recorder with the settings you used or you don't have enough disk space to record a video", G_NOTIFICATION_PRIORITY_URGENT); } return true; } @@ -1938,8 +1725,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 = 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); + int record_width = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_width_entry); + int record_height = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_height_entry); bool follow_focused = false; std::string window_str = record_area_selection_menu_get_active_id(); @@ -2057,7 +1844,8 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user int exit_status = prev_exit_status; prev_exit_status = -1; if(streaming) { - bool exit_success = kill_gpu_screen_recorder_get_result(); + bool already_dead = true; + bool exit_success = kill_gpu_screen_recorder_get_result(&already_dead); gtk_button_set_label(button, "Start streaming"); streaming = false; @@ -2072,10 +1860,10 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user if(exit_status == 10) { show_notification(app, "GPU Screen Recorder", "You need to have pkexec installed and a polkit agent running to record your monitor", G_NOTIFICATION_PRIORITY_URGENT); + } else if(!exit_success || (already_dead && exit_status != 0)) { + show_notification(app, "GPU Screen Recorder", "Failed to stream video. There is either an error in your streaming config or your graphics card doesn't support GPU Screen Recorder with the settings you used", G_NOTIFICATION_PRIORITY_URGENT); } else if(exit_success) { show_notification(app, "GPU Screen Recorder", "Stopped streaming", G_NOTIFICATION_PRIORITY_NORMAL); - } else { - show_notification(app, "GPU Screen Recorder", "Failed to stream video. There is either an error in your streaming config or your graphics card doesn't support GPU Screen Recorder with the settings you used", G_NOTIFICATION_PRIORITY_URGENT); } return true; @@ -2084,8 +1872,8 @@ static gboolean on_start_streaming_button_click(GtkButton *button, gpointer user save_configs(); int fps = gtk_spin_button_get_value_as_int(fps_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); + int record_width = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_width_entry); + int record_height = gsr_info.system_info.display_server == DisplayServer::WAYLAND ? 0 : gtk_spin_button_get_value_as_int(area_height_entry); bool follow_focused = false; std::string window_str = record_area_selection_menu_get_active_id(); @@ -2257,7 +2045,7 @@ static void view_combo_box_change_callback(GtkComboBox *widget, gpointer userdat gtk_widget_set_visible(GTK_WIDGET(video_codec_grid), advanced_view); gtk_widget_set_visible(GTK_WIDGET(audio_codec_grid), advanced_view); gtk_widget_set_visible(GTK_WIDGET(framerate_mode_grid), advanced_view); - gtk_widget_set_visible(GTK_WIDGET(overclock_grid), advanced_view && gpu_inf.vendor == GPU_VENDOR_NVIDIA && !wayland); + gtk_widget_set_visible(GTK_WIDGET(overclock_grid), advanced_view && gsr_info.gpu_info.vendor == GpuVendor::NVIDIA && gsr_info.system_info.display_server != DisplayServer::WAYLAND); gtk_widget_set_visible(GTK_WIDGET(show_notification_button), advanced_view); } @@ -2323,7 +2111,7 @@ static void keypress_toggle_recording(bool recording_state, GtkButton *record_bu } static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpointer userdata) { - if(wayland) + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) return GDK_FILTER_CONTINUE; if(hotkey_mode == HotkeyMode::NoAction) @@ -2472,7 +2260,7 @@ static GdkFilterReturn hotkey_filter_callback(GdkXEvent *xevent, GdkEvent*, gpoi } static gboolean on_hotkey_entry_click(GtkWidget *button, gpointer) { - if(wayland) + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) return true; hotkey_mode = HotkeyMode::NewHotkey; @@ -2518,69 +2306,185 @@ static bool audio_inputs_contains(const std::vector<AudioInput> &audio_inputs, c return false; } -static gsr_connection_type get_connection_type() { - if(wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA) { - return GSR_CONNECTION_DRM; - } else { - return GSR_CONNECTION_X11; +static void parse_system_info_line(GsrInfo *gsr_info, const std::string &line) { + const size_t space_index = line.find(' '); + if(space_index == std::string::npos) + return; + + const StringView attribute_name = {line.c_str(), space_index}; + const StringView attribute_value = {line.c_str() + space_index + 1, line.size() - (space_index + 1)}; + if(attribute_name == "display_server") { + if(attribute_value == "x11") + gsr_info->system_info.display_server = DisplayServer::X11; + else if(attribute_value == "wayland") + gsr_info->system_info.display_server = DisplayServer::WAYLAND; } } -// Returns the exit status -static int get_supported_video_codecs(SupportedVideoCodecs *supported_video_codecs) { - supported_video_codecs->h264 = false; - supported_video_codecs->hevc = false; - supported_video_codecs->av1 = false; - supported_video_codecs->vp8 = false; - supported_video_codecs->vp9 = false; +static void parse_gpu_info_line(GsrInfo *gsr_info, const std::string &line) { + const size_t space_index = line.find(' '); + if(space_index == std::string::npos) + return; + + const StringView attribute_name = {line.c_str(), space_index}; + const StringView attribute_value = {line.c_str() + space_index + 1, line.size() - (space_index + 1)}; + if(attribute_name == "gpu") { + if(attribute_value == "amd") + gsr_info->gpu_info.vendor = GpuVendor::AMD; + else if(attribute_value == "intel") + gsr_info->gpu_info.vendor = GpuVendor::INTEL; + else if(attribute_value == "nvidia") + gsr_info->gpu_info.vendor = GpuVendor::NVIDIA; + } +} + +static void parse_video_codecs_line(GsrInfo *gsr_info, const std::string &line) { + if(line == "h264") + gsr_info->supported_video_codecs.h264 = true; + else if(line == "hevc") + gsr_info->supported_video_codecs.hevc = true; + else if(line == "av1") + gsr_info->supported_video_codecs.av1 = true; + else if(line == "vp8") + gsr_info->supported_video_codecs.vp8 = true; + else if(line == "vp9") + gsr_info->supported_video_codecs.vp9 = true; +} + +static GsrMonitor capture_option_line_to_monitor(const std::string &line) { + size_t space_index = line.find(' '); + if(space_index == std::string::npos) + return { line, {0, 0} }; + + vec2i size = {0, 0}; + if(sscanf(line.c_str() + space_index + 1, "%dx%d", &size.x, &size.y) != 2) + size = {0, 0}; + + return { line.substr(0, space_index), size }; +} + +static void parse_capture_options_line(GsrInfo *gsr_info, const std::string &line) { + if(line == "window") + gsr_info->supported_capture_options.window = true; + else if(line == "focused") + gsr_info->supported_capture_options.focused = true; + else if(line == "screen") + gsr_info->supported_capture_options.screen = true; + else if(line == "portal") + gsr_info->supported_capture_options.portal = true; + else + gsr_info->supported_capture_options.monitors.push_back(capture_option_line_to_monitor(line)); +} + +enum class GsrInfoSection { + UNKNOWN, + SYSTEM_INFO, + GPU_INFO, + VIDEO_CODECS, + CAPTURE_OPTIONS +}; + +static bool starts_with(const std::string &str, const char *substr) { + size_t len = strlen(substr); + return str.size() >= len && memcmp(str.data(), substr, len) == 0; +} + +static GsrInfoExitStatus get_gpu_screen_recorder_info(GsrInfo *gsr_info) { + *gsr_info = GsrInfo{}; - FILE *f = popen("gpu-screen-recorder --list-supported-video-codecs", "r"); + FILE *f = popen("gpu-screen-recorder --info", "r"); if(!f) { - fprintf(stderr, "error: 'gpu-screen-recorder --list-supported-video-codecs' failed\n"); - return -1; + fprintf(stderr, "error: 'gpu-screen-recorder --info' failed\n"); + return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; } - char output[1024]; + char output[8192]; ssize_t bytes_read = fread(output, 1, sizeof(output) - 1, f); if(bytes_read < 0 || ferror(f)) { - fprintf(stderr, "error: failed to read 'gpu-screen-recorder --list-supported-video-codecs' output\n"); + fprintf(stderr, "error: failed to read 'gpu-screen-recorder --info' output\n"); pclose(f); - return -1; + return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; } output[bytes_read] = '\0'; - if(strstr(output, "h264")) - supported_video_codecs->h264 = true; - if(strstr(output, "hevc")) - supported_video_codecs->hevc = true; - if(strstr(output, "av1")) - supported_video_codecs->av1 = true; - if(strstr(output, "vp8")) - supported_video_codecs->vp8 = true; - if(strstr(output, "vp9")) - supported_video_codecs->vp9 = true; + GsrInfoSection section = GsrInfoSection::UNKNOWN; + string_split_char(output, '\n', [&](StringView line) { + const std::string line_str(line.str, line.size); + + if(starts_with(line_str, "section=")) { + const char *section_name = line_str.c_str() + 8; + if(strcmp(section_name, "system_info") == 0) + section = GsrInfoSection::SYSTEM_INFO; + else if(strcmp(section_name, "gpu_info") == 0) + section = GsrInfoSection::GPU_INFO; + else if(strcmp(section_name, "video_codecs") == 0) + section = GsrInfoSection::VIDEO_CODECS; + else if(strcmp(section_name, "capture_options") == 0) + section = GsrInfoSection::CAPTURE_OPTIONS; + else + section = GsrInfoSection::UNKNOWN; + return true; + } + + switch(section) { + case GsrInfoSection::UNKNOWN: { + break; + } + case GsrInfoSection::SYSTEM_INFO: { + parse_system_info_line(gsr_info, line_str); + break; + } + case GsrInfoSection::GPU_INFO: { + parse_gpu_info_line(gsr_info, line_str); + break; + } + case GsrInfoSection::VIDEO_CODECS: { + parse_video_codecs_line(gsr_info, line_str); + break; + } + case GsrInfoSection::CAPTURE_OPTIONS: { + parse_capture_options_line(gsr_info, line_str); + break; + } + } + + return true; + }); int status = pclose(f); - if(WIFEXITED(status)) - return WEXITSTATUS(status); - return 0; + if(WIFEXITED(status)) { + switch(WEXITSTATUS(status)) { + case 0: return GsrInfoExitStatus::OK; + case 22: return GsrInfoExitStatus::OPENGL_FAILED; + case 23: return GsrInfoExitStatus::NO_DRM_CARD; + default: return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; + } + } + + return GsrInfoExitStatus::FAILED_TO_RUN_COMMAND; } static void record_area_set_sensitive(GtkCellLayout *cell_layout, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { (void)cell_layout; (void)data; - if(wayland) { - gchar *id; - gtk_tree_model_get(tree_model, iter, 1, &id, -1); - gboolean sensitive = g_strcmp0("window", id) != 0 && g_strcmp0("focused", id) != 0; - g_free(id); - g_object_set(cell, "sensitive", sensitive, NULL); - } else { - g_object_set(cell, "sensitive", true, NULL); - } + + gchar *id; + gtk_tree_model_get(tree_model, iter, 1, &id, -1); + + gboolean sensitive = true; + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) + sensitive = g_strcmp0("window", id) != 0 && g_strcmp0("focused", id) != 0; + + gboolean is_portal = g_strcmp0("portal", id) == 0; + if(is_portal && !gsr_info.supported_capture_options.portal) + sensitive = false; + + g_object_set(cell, "sensitive", sensitive, NULL); + + g_free(id); } -static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *app, const gpu_info &gpu_inf) { +static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *app) { GtkGrid *grid = GTK_GRID(gtk_grid_new()); gtk_stack_add_named(stack, GTK_WIDGET(grid), "common-settings"); gtk_widget_set_vexpand(GTK_WIDGET(grid), true); @@ -2619,7 +2523,7 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a GtkTreeIter iter; record_area_selection_model = GTK_TREE_MODEL(store); - if(wayland) { + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, "Window (Unavailable on Wayland)", -1); gtk_list_store_set(store, &iter, 1, "window", -1); @@ -2637,41 +2541,37 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a gtk_list_store_set(store, &iter, 1, "focused", -1); } - const bool allow_screen_capture = wayland || nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA; + const bool allow_screen_capture = is_monitor_capture_drm() || nvfbc_installed; if(allow_screen_capture) { - if(!wayland && gpu_inf.vendor == GPU_VENDOR_NVIDIA) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && gsr_info.gpu_info.vendor == GpuVendor::NVIDIA) { gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, "All monitors", -1); gtk_list_store_set(store, &iter, 1, "screen", -1); } - const gsr_connection_type connection_type = get_connection_type(); - int num_monitors = 0; - for_each_active_monitor_output(&egl, connection_type, [&](const gsr_monitor *monitor, void*) { + for(const auto &monitor : gsr_info.supported_capture_options.monitors) { std::string label = "Monitor "; - label.append(monitor->name, monitor->name_len); + label += monitor.name; label += " ("; - label += std::to_string(monitor->size.x); + label += std::to_string(monitor.size.x); label += "x"; - label += std::to_string(monitor->size.y); - if(flatpak && (wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA)) { + label += std::to_string(monitor.size.y); + if(flatpak && is_monitor_capture_drm()) { label += ", requires root access"; } label += ")"; // Leak on purpose, what are you gonna do? stab me? - char *id = (char*)malloc(monitor->name_len + 1); - memcpy(id, monitor->name, monitor->name_len); - id[monitor->name_len] = '\0'; + char *id = (char*)malloc(monitor.name.size() + 1); + memcpy(id, monitor.name.c_str(), monitor.name.size()); + id[monitor.name.size()] = '\0'; gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, label.c_str(), -1); gtk_list_store_set(store, &iter, 1, id, -1); + } - ++num_monitors; - }, NULL); - - if(num_monitors == 0 && (wayland || gpu_inf.vendor != GPU_VENDOR_NVIDIA)) { + if(gsr_info.supported_capture_options.monitors.empty() && gsr_info.system_info.display_server == DisplayServer::WAYLAND && !gsr_info.supported_capture_options.portal) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "No monitors to record found. Make sure GPU Screen Recorder is running on the same GPU device that is displaying graphics on the screen."); gtk_dialog_run(GTK_DIALOG(dialog)); @@ -2681,6 +2581,10 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a } } + gtk_list_store_append(store, &iter); + gtk_list_store_set(store, &iter, 0, "Desktop portal", -1); + gtk_list_store_set(store, &iter, 1, "portal", -1); + record_area_selection_menu = GTK_COMBO_BOX(gtk_combo_box_new_with_model(record_area_selection_model)); GtkCellRenderer *renderer = gtk_cell_renderer_text_new(); @@ -2693,7 +2597,7 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a 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); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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); @@ -2810,27 +2714,23 @@ static GtkWidget* create_common_settings_page(GtkStack *stack, GtkApplication *a gtk_grid_attach(video_codec_grid, gtk_label_new("Video codec: "), 0, 0, 1, 1); video_codec_input_menu = GTK_COMBO_BOX_TEXT(gtk_combo_box_text_new()); gtk_combo_box_text_append(video_codec_input_menu, "auto", "Auto (Recommended)"); - if(supported_video_codecs_exit_status == 0) { - if(supported_video_codecs.h264) - gtk_combo_box_text_append(video_codec_input_menu, "h264", "H264"); - if(supported_video_codecs.hevc) - gtk_combo_box_text_append(video_codec_input_menu, "hevc", "HEVC"); - if(supported_video_codecs.av1) - gtk_combo_box_text_append(video_codec_input_menu, "av1", "AV1"); - if(supported_video_codecs.vp8) - gtk_combo_box_text_append(video_codec_input_menu, "vp8", "VP8"); - if(supported_video_codecs.vp9) - gtk_combo_box_text_append(video_codec_input_menu, "vp9", "VP9"); - - if(wayland) { - if(supported_video_codecs.hevc) - gtk_combo_box_text_append(video_codec_input_menu, "hevc_hdr", "HEVC (HDR)"); - if(supported_video_codecs.av1) - gtk_combo_box_text_append(video_codec_input_menu, "av1_hdr", "AV1 (HDR)"); - } - } else { + gtk_combo_box_text_append(video_codec_input_menu, "h264_software", "H264 Software Encoder (Slow, not recommeded)"); + if(gsr_info.supported_video_codecs.h264) gtk_combo_box_text_append(video_codec_input_menu, "h264", "H264"); + if(gsr_info.supported_video_codecs.hevc) gtk_combo_box_text_append(video_codec_input_menu, "hevc", "HEVC"); + if(gsr_info.supported_video_codecs.av1) + gtk_combo_box_text_append(video_codec_input_menu, "av1", "AV1"); + if(gsr_info.supported_video_codecs.vp8) + gtk_combo_box_text_append(video_codec_input_menu, "vp8", "VP8"); + if(gsr_info.supported_video_codecs.vp9) + gtk_combo_box_text_append(video_codec_input_menu, "vp9", "VP9"); + + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { + if(gsr_info.supported_video_codecs.hevc) + gtk_combo_box_text_append(video_codec_input_menu, "hevc_hdr", "HEVC (HDR)"); + if(gsr_info.supported_video_codecs.av1) + gtk_combo_box_text_append(video_codec_input_menu, "av1_hdr", "AV1 (HDR)"); } gtk_widget_set_hexpand(GTK_WIDGET(video_codec_input_menu), true); gtk_grid_attach(video_codec_grid, GTK_WIDGET(video_codec_input_menu), 1, 0, 1, 1); @@ -2974,7 +2874,7 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) { gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10); GtkWidget *hotkey_active_label = NULL; - if(wayland) { + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { add_wayland_global_hotkeys_ui(grid, row, 5); } else { hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel"); @@ -3039,7 +2939,7 @@ static GtkWidget* create_replay_page(GtkApplication *app, GtkStack *stack) { for(auto &supported_container : supported_containers) { gtk_combo_box_text_append(replay_container, supported_container.container_name, supported_container.file_extension); } - if(supported_video_codecs.vp8 || supported_video_codecs.vp9) { + if(gsr_info.supported_video_codecs.vp8 || gsr_info.supported_video_codecs.vp9) { gtk_combo_box_text_append(replay_container, "webm", "webm"); } gtk_widget_set_hexpand(GTK_WIDGET(replay_container), true); @@ -3110,7 +3010,7 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) { gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10); GtkWidget *hotkey_active_label = NULL; - if(wayland) { + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { add_wayland_global_hotkeys_ui(grid, row, 5); } else { hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel"); @@ -3175,7 +3075,7 @@ static GtkWidget* create_recording_page(GtkApplication *app, GtkStack *stack) { for(auto &supported_container : supported_containers) { gtk_combo_box_text_append(record_container, supported_container.container_name, supported_container.file_extension); } - if(supported_video_codecs.vp8 || supported_video_codecs.vp9) { + if(gsr_info.supported_video_codecs.vp8 || gsr_info.supported_video_codecs.vp9) { gtk_combo_box_text_append(record_container, "webm", "webm"); } gtk_widget_set_hexpand(GTK_WIDGET(record_container), true); @@ -3236,7 +3136,7 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) { gtk_widget_set_margin(GTK_WIDGET(grid), 10, 10, 10, 10); GtkWidget *hotkey_active_label = NULL; - if(wayland) { + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { add_wayland_global_hotkeys_ui(grid, row, 3); } else { hotkey_active_label = gtk_label_new("Press a key combination to set a new hotkey, backspace to remove the hotkey or esc to cancel"); @@ -3300,7 +3200,7 @@ static GtkWidget* create_streaming_page(GtkApplication *app, GtkStack *stack) { for(auto &supported_container : supported_containers) { gtk_combo_box_text_append(custom_stream_container, supported_container.container_name, supported_container.file_extension); } - if(supported_video_codecs.vp8 || supported_video_codecs.vp9) { + if(gsr_info.supported_video_codecs.vp8 || gsr_info.supported_video_codecs.vp9) { gtk_combo_box_text_append(custom_stream_container, "webm", "webm"); } gtk_widget_set_hexpand(GTK_WIDGET(custom_stream_container), true); @@ -3442,38 +3342,33 @@ static void add_audio_input_track(const char *name) { gtk_list_box_insert (GTK_LIST_BOX(audio_input_used_list), row, -1); } -static void load_config(const gpu_info &gpu_inf) { +static void load_config() { bool config_empty = false; config = read_config(config_empty); std::string first_monitor; - if(!wayland && strcmp(config.main_config.record_area_option.c_str(), "window") == 0) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && strcmp(config.main_config.record_area_option.c_str(), "window") == 0) { // - } else if(!wayland && strcmp(config.main_config.record_area_option.c_str(), "focused") == 0) { + } else if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && strcmp(config.main_config.record_area_option.c_str(), "focused") == 0) { // - } else if(!wayland && gpu_inf.vendor == GPU_VENDOR_NVIDIA && strcmp(config.main_config.record_area_option.c_str(), "screen") == 0) { + } else if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && gsr_info.gpu_info.vendor == GpuVendor::NVIDIA && strcmp(config.main_config.record_area_option.c_str(), "screen") == 0) { // } else { - gsr_connection_type connection_type = get_connection_type(); - bool found_monitor = false; - int monitor_name_size = strlen(config.main_config.record_area_option.c_str()); - for_each_active_monitor_output(&egl, connection_type, [&](const gsr_monitor *monitor, void*) { - if(first_monitor.empty()) { - first_monitor.assign(monitor->name, monitor->name_len); - } + for(const auto &monitor : gsr_info.supported_capture_options.monitors) { + if(first_monitor.empty()) + first_monitor = monitor.name; - if(monitor_name_size == monitor->name_len && strncmp(config.main_config.record_area_option.c_str(), monitor->name, monitor->name_len) == 0) { + if(config.main_config.record_area_option == monitor.name) 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 = wayland || nvfbc_installed || gpu_inf.vendor != GPU_VENDOR_NVIDIA; + const bool allow_screen_capture = is_monitor_capture_drm() || nvfbc_installed; if(allow_screen_capture) { config.main_config.record_area_option = first_monitor; } else { @@ -3481,7 +3376,7 @@ static void load_config(const gpu_info &gpu_inf) { } } - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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); @@ -3514,7 +3409,7 @@ static void load_config(const gpu_info &gpu_inf) { config.main_config.codec = "auto"; } - if(!wayland && (config.main_config.codec == "hevc_hdr" || config.main_config.codec == "av1_hdr")) + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && (config.main_config.codec == "hevc_hdr" || config.main_config.codec == "av1_hdr")) config.main_config.codec = "auto"; if(config.main_config.audio_codec != "opus" && config.main_config.audio_codec != "aac") @@ -3538,7 +3433,7 @@ static void load_config(const gpu_info &gpu_inf) { config.replay_config.replay_time = 1200; record_area_selection_menu_set_active_id(config.main_config.record_area_option.c_str()); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::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); } @@ -3567,7 +3462,7 @@ static void load_config(const gpu_info &gpu_inf) { gtk_entry_set_text(twitch_stream_id_entry, config.streaming_config.twitch.stream_key.c_str()); gtk_entry_set_text(custom_stream_url_entry, config.streaming_config.custom.url.c_str()); gtk_combo_box_set_active_id(GTK_COMBO_BOX(custom_stream_container), config.streaming_config.custom.container.c_str()); - if(!wayland && streaming_hotkey_button && !config_empty) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && streaming_hotkey_button && !config_empty) { 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); @@ -3575,12 +3470,12 @@ 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(!wayland && record_hotkey_button && !config_empty) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && record_hotkey_button && !config_empty) { 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); } - if(!wayland && pause_unpause_hotkey_button && !config_empty) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && pause_unpause_hotkey_button && !config_empty) { pause_unpause_hotkey.keysym = config.record_config.pause_recording_hotkey.keysym; pause_unpause_hotkey.modkey_mask = config.record_config.pause_recording_hotkey.modifiers; set_hotkey_text_from_hotkey_data(GTK_ENTRY(pause_unpause_hotkey_button), pause_unpause_hotkey); @@ -3589,12 +3484,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(!wayland && replay_start_stop_hotkey_button && !config_empty) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && replay_start_stop_hotkey_button && !config_empty) { 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(!wayland && replay_save_hotkey_button && !config_empty) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND && replay_save_hotkey_button && !config_empty) { 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); @@ -3603,7 +3498,7 @@ 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); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) { gtk_widget_set_visible(record_hotkey.hotkey_active_label, false); gtk_widget_set_visible(pause_unpause_hotkey.hotkey_active_label, false); gtk_widget_set_visible(streaming_hotkey.hotkey_active_label, false); @@ -3613,20 +3508,8 @@ static void load_config(const gpu_info &gpu_inf) { enable_stream_record_button_if_info_filled(); stream_service_item_change_callback(GTK_COMBO_BOX(stream_service_input_menu), nullptr); - if(supported_video_codecs_exit_status != 0) { - const char *cmd = flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4" : "gpu-screen-recorder -w screen -f 60 -o video.mp4"; - GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "Failed to run 'gpu-screen-recorder' command. If you are using gpu-screen-recorder flatpak then this is a bug. Otherwise you need to make sure gpu-screen-recorder is installed on your system and working properly (install necessary depedencies depending on your GPU, such as libva-mesa-driver, libva-intel-driver, intel-media-driver and linux-firmware). Run:\n" - "%s\n" - "in a terminal to see more information about the issue.", cmd); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - g_application_quit(G_APPLICATION(select_window_userdata.app)); - return; - } - - if(!supported_video_codecs.h264 && !supported_video_codecs.hevc && gpu_inf.vendor != GPU_VENDOR_NVIDIA && config.main_config.codec != "av1") { - if(supported_video_codecs.av1) { + if(!gsr_info.supported_video_codecs.h264 && !gsr_info.supported_video_codecs.hevc && gsr_info.gpu_info.vendor != GpuVendor::NVIDIA && config.main_config.codec != "av1") { + if(gsr_info.supported_video_codecs.av1) { GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, "Switched video codec to AV1 since H264/HEVC video encoding is either missing or disabled on your system. If you know that your system supports H264/HEVC video encoding and " "you are using the flatpak version of GPU Screen Recorder then try installing mesa-extra freedesktop runtime by running this command:\n" @@ -3651,7 +3534,7 @@ static void load_config(const gpu_info &gpu_inf) { } } - if(!supported_video_codecs.h264 && !supported_video_codecs.hevc && gpu_inf.vendor == GPU_VENDOR_NVIDIA) { + if(!gsr_info.supported_video_codecs.h264 && !gsr_info.supported_video_codecs.hevc && gsr_info.gpu_info.vendor == GpuVendor::NVIDIA) { GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Failed to find H264/HEVC video codecs. Your NVIDIA GPU may be missing support for H264/HEVC video codecs for video encoding."); gtk_dialog_run(GTK_DIALOG(dialog)); @@ -3661,79 +3544,32 @@ static void load_config(const gpu_info &gpu_inf) { } } -static bool gl_get_gpu_info(gsr_egl *egl, gpu_info *info) { - const char *software_renderers[] = { "llvmpipe", "SWR", "softpipe", NULL }; - bool supported = true; - const unsigned char *gl_vendor = egl->glGetString(GL_VENDOR); - const unsigned char *gl_renderer = egl->glGetString(GL_RENDERER); - - info->gpu_version = 0; - - if(!gl_vendor) { - fprintf(stderr, "Error: failed to get gpu vendor\n"); - supported = false; - goto end; - } - - if(gl_renderer) { - for(int i = 0; software_renderers[i]; ++i) { - if(strstr((const char*)gl_renderer, software_renderers[i])) { - fprintf(stderr, "gsr error: your opengl environment is not properly setup. It's using %s (software rendering) for opengl instead of your graphics card. Please make sure your graphics driver is properly installed\n", software_renderers[i]); - supported = false; - goto end; - } - } - } - - if(strstr((const char*)gl_vendor, "AMD")) - info->vendor = GPU_VENDOR_AMD; - else if(strstr((const char*)gl_vendor, "Intel")) - info->vendor = GPU_VENDOR_INTEL; - else if(strstr((const char*)gl_vendor, "NVIDIA")) - info->vendor = GPU_VENDOR_NVIDIA; - else { - fprintf(stderr, "Error: unknown gpu vendor: %s\n", gl_vendor); - supported = false; - goto end; - } - - if(gl_renderer) { - if(info->vendor == GPU_VENDOR_NVIDIA) - sscanf((const char*)gl_renderer, "%*s %*s %*s %d", &info->gpu_version); - } - - end: - return supported; -} - -static bool is_xwayland(Display *dpy) { - int opcode, event, error; - if(XQueryExtension(dpy, "XWAYLAND", &opcode, &event, &error)) - return true; - - bool xwayland_found = false; - for_each_active_monitor_output_x11(dpy, [&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; -} - -static const char* gpu_vendor_to_name(gpu_vendor vendor) { +static const char* gpu_vendor_to_name(GpuVendor vendor) { switch(vendor) { - case GPU_VENDOR_AMD: return "AMD"; - case GPU_VENDOR_INTEL: return "Intel"; - case GPU_VENDOR_NVIDIA: return "NVIDIA"; + case GpuVendor::AMD: return "AMD"; + case GpuVendor::INTEL: return "Intel"; + case GpuVendor::NVIDIA: return "NVIDIA"; } return ""; } static void activate(GtkApplication *app, gpointer) { flatpak = is_inside_flatpak(); + nvfbc_installed = gsr_info.system_info.display_server != DisplayServer::WAYLAND && is_nv_fbc_installed(); + + if(gsr_info_exit_status == GsrInfoExitStatus::FAILED_TO_RUN_COMMAND) { + const char *cmd = flatpak ? "flatpak run --command=gpu-screen-recorder com.dec05eba.gpu_screen_recorder -w screen -f 60 -o video.mp4" : "gpu-screen-recorder -w screen -f 60 -o video.mp4"; + GtkWidget *dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "Failed to run 'gpu-screen-recorder' command. If you are using gpu-screen-recorder flatpak then this is a bug. Otherwise you need to make sure gpu-screen-recorder is installed on your system and working properly (install necessary depedencies depending on your GPU, such as libva-mesa-driver, libva-intel-driver, intel-media-driver and linux-firmware). Run:\n" + "%s\n" + "in a terminal to see more information about the issue.", cmd); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_application_quit(G_APPLICATION(select_window_userdata.app)); + return; + } - if(!wayland && !dpy) { + if(gsr_info.system_info.display_server == DisplayServer::UNKNOWN) { GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Neither X11 nor Wayland is running."); gtk_dialog_run(GTK_DIALOG(dialog)); @@ -3742,18 +3578,16 @@ static void activate(GtkApplication *app, gpointer) { return; } - nvfbc_installed = !wayland && is_nv_fbc_installed(); - - if(!gsr_egl_load(&egl, dpy, wayland)) { + if(gsr_info.system_info.display_server == DisplayServer::X11 && !dpy) { GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, - "Failed to load OpenGL. Make sure your GPU drivers are properly installed."); + "Failed to connect to X11 server"); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); g_application_quit(G_APPLICATION(app)); return; } - if(!gl_get_gpu_info(&egl, &gpu_inf)) { + if(gsr_info_exit_status == GsrInfoExitStatus::OPENGL_FAILED) { GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "Failed to get OpenGL information. Make sure your GPU drivers are properly installed. " "If you are using nvidia then make sure to run \"flatpak update\" to make sure that your flatpak nvidia driver version matches your distros nvidia driver version. If this doesn't work then you might need to manually install a flatpak nvidia driver version that matches your distros nvidia driver version."); @@ -3763,20 +3597,16 @@ static void activate(GtkApplication *app, gpointer) { return; } - if((gpu_inf.vendor != GPU_VENDOR_NVIDIA) || wayland) { - if(!gsr_get_valid_card_path(&egl, egl.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. If you are running GPU Screen Recorder with prime-run then try running without it."); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); - g_application_quit(G_APPLICATION(app)); - return; - } - } else { - egl.card_path[0] = '\0'; + if(gsr_info_exit_status == GsrInfoExitStatus::NO_DRM_CARD) { + GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, + "Failed to find a valid DRM card. If you are running GPU Screen Recorder with prime-run then try running without it."); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + g_application_quit(G_APPLICATION(app)); + return; } - if(gpu_inf.vendor == GPU_VENDOR_NVIDIA) { + if(gsr_info.gpu_info.vendor == GpuVendor::NVIDIA) { if(!is_cuda_installed()) { GtkWidget *dialog = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "CUDA is not installed on your system. GPU Screen Recorder requires CUDA to be installed to work with a NVIDIA GPU."); @@ -3796,10 +3626,8 @@ static void activate(GtkApplication *app, gpointer) { } } - supported_video_codecs_exit_status = get_supported_video_codecs(&supported_video_codecs); - std::string window_title = "GPU Screen Recorder | Running on "; - window_title += gpu_vendor_to_name(gpu_inf.vendor); + window_title += gpu_vendor_to_name(gsr_info.gpu_info.vendor); window = gtk_application_window_new(app); g_signal_connect(window, "destroy", G_CALLBACK(on_destroy_window), nullptr); @@ -3816,7 +3644,7 @@ static void activate(GtkApplication *app, gpointer) { if(!pa_default_sources.default_sink_name.empty() && audio_inputs_contains(audio_inputs, pa_default_sources.default_sink_name)) audio_inputs.insert(audio_inputs.begin(), { pa_default_sources.default_sink_name.c_str(), "Default output" }); - if(!wayland) + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) crosshair_cursor = XCreateFontCursor(gdk_x11_get_default_xdisplay(), XC_crosshair); GtkStack *stack = GTK_STACK(gtk_stack_new()); @@ -3824,7 +3652,7 @@ static void activate(GtkApplication *app, gpointer) { gtk_stack_set_transition_type(stack, GTK_STACK_TRANSITION_TYPE_NONE); gtk_stack_set_transition_duration(stack, 0); gtk_stack_set_homogeneous(stack, false); - GtkWidget *common_settings_page = create_common_settings_page(stack, app, gpu_inf); + GtkWidget *common_settings_page = create_common_settings_page(stack, app); GtkWidget *replay_page = create_replay_page(app, stack); GtkWidget *recording_page = create_recording_page(app, stack); GtkWidget *streaming_page = create_streaming_page(app, stack); @@ -3846,7 +3674,7 @@ 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); - if(!wayland) { + if(gsr_info.system_info.display_server != DisplayServer::WAYLAND) { xim = XOpenIM(gdk_x11_get_default_xdisplay(), NULL, NULL, NULL); xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, NULL); @@ -3859,7 +3687,7 @@ static void activate(GtkApplication *app, gpointer) { g_timeout_add(500, timer_timeout_handler, app); gtk_widget_show_all(window); - load_config(gpu_inf); + load_config(); } int main(int argc, char **argv) { @@ -3880,12 +3708,14 @@ int main(int argc, char **argv) { unsetenv("vblank_mode"); dpy = XOpenDisplay(NULL); - wayland = !dpy || is_xwayland(dpy); + gsr_info_exit_status = get_gpu_screen_recorder_info(&gsr_info); - if(wayland) { - setenv("GDK_BACKEND", "wayland", true); - } else { - setenv("GDK_BACKEND", "x11", true); + if(gsr_info_exit_status == GsrInfoExitStatus::OK) { + if(gsr_info.system_info.display_server == DisplayServer::WAYLAND) { + setenv("GDK_BACKEND", "wayland", true); + } else { + setenv("GDK_BACKEND", "x11", true); + } } GtkApplication *app = gtk_application_new("com.dec05eba.gpu_screen_recorder", G_APPLICATION_NON_UNIQUE); |