From 01045800155c74123da48f9cdd8f459d06c40ddc Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 17 May 2026 15:58:44 +0200 Subject: desktop portal capture: fallback to no modifier --- src/pipewire_video.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/pipewire_video.c b/src/pipewire_video.c index 077e733..2a2d7d5 100644 --- a/src/pipewire_video.c +++ b/src/pipewire_video.c @@ -514,19 +514,15 @@ static bool spa_video_format_get_modifiers(gsr_pipewire_video *self, const enum if(!self->egl->eglQueryDmaBufModifiersEXT(self->egl->egl_display, drm_format, max_modifiers, modifiers, NULL, num_modifiers)) { fprintf(stderr, "gsr error: spa_video_format_get_modifiers: eglQueryDmaBufModifiersEXT failed with drm format %d, %" PRIi64 "\n", (int)format, drm_format); - //modifiers[0] = DRM_FORMAT_MOD_LINEAR; - //modifiers[1] = DRM_FORMAT_MOD_INVALID; - //*num_modifiers = 2; modifiers[0] = DRM_FORMAT_MOD_INVALID; *num_modifiers = 1; return false; } - // if(*num_modifiers + 2 <= max_modifiers) { - // modifiers[*num_modifiers + 0] = DRM_FORMAT_MOD_LINEAR; - // modifiers[*num_modifiers + 1] = DRM_FORMAT_MOD_INVALID; - // *num_modifiers += 2; - // } + if(*num_modifiers < max_modifiers) { + modifiers[*num_modifiers] = DRM_FORMAT_MOD_INVALID; + *num_modifiers += 1; + } return true; } -- cgit v1.2.3 From 014125425e5f7afd39dea5a7e2b948c59933dc1f Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 17 May 2026 16:31:41 +0200 Subject: Attempt to fix iGPU & dGPU sync issue with kms capture --- src/capture/kms.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/capture/kms.c b/src/capture/kms.c index fe4eb40..89a86f7 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -274,6 +275,31 @@ static vec2i swap_vec2i(vec2i value) { return value; } +/* Wait for any pending writer fences on the dma-buf so we don't sample a + partially-written buffer. POLLIN on a dma-buf fd is the kernel's portable + way to wait on all attached writer fences (works across drivers, unlike + relying on Mesa's implicit-sync to honor a fence from another driver). + Without this, KMS capture can produce glitched frames in hybrid GPU setups + where the buffer on the captured plane is being written by a different + GPU/driver than the one gsr is encoding on (e.g. an iGPU client surface + shown on a dGPU monitor). The wait is normally a no-op because the fence + has already signaled by the time we get here; the timeout is just a guard. */ +static void gsr_capture_kms_wait_for_buffer_fence(const int *fds, int num_fds) { + struct pollfd pfds[GSR_KMS_MAX_DMA_BUFS]; + int num_pfds = 0; + for(int i = 0; i < num_fds && num_pfds < GSR_KMS_MAX_DMA_BUFS; ++i) { + if(fds[i] <= 0) + continue; + pfds[num_pfds].fd = fds[i]; + pfds[num_pfds].events = POLLIN; + pfds[num_pfds].revents = 0; + ++num_pfds; + } + if(num_pfds == 0) + return; + (void)poll(pfds, num_pfds, 100); +} + static EGLImage gsr_capture_kms_create_egl_image(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, bool use_modifiers) { intptr_t img_attr[44]; setup_dma_buf_attrs(img_attr, drm_fd->pixel_format, drm_fd->width, drm_fd->height, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs, use_modifiers); @@ -319,6 +345,8 @@ static EGLImage gsr_capture_kms_create_egl_image_with_fallback(gsr_capture_kms * modifiers[i] = drm_fd->modifier; } + gsr_capture_kms_wait_for_buffer_fence(fds, drm_fd->num_dma_bufs); + EGLImage image = NULL; if(self->no_modifiers_fallback) { image = gsr_capture_kms_create_egl_image(self, drm_fd, fds, offsets, pitches, modifiers, false); @@ -452,6 +480,8 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color modifiers[i] = cursor_drm_fd->modifier; } + gsr_capture_kms_wait_for_buffer_fence(fds, cursor_drm_fd->num_dma_bufs); + intptr_t img_attr_cursor[44]; setup_dma_buf_attrs(img_attr_cursor, cursor_drm_fd->pixel_format, cursor_drm_fd->width, cursor_drm_fd->height, fds, offsets, pitches, modifiers, cursor_drm_fd->num_dma_bufs, true); -- cgit v1.2.3 From ae331098abb7a283473269acd9d351587265f2a3 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sun, 17 May 2026 18:01:08 +0200 Subject: Revert "Attempt to fix iGPU & dGPU sync issue with kms capture" This reverts commit 014125425e5f7afd39dea5a7e2b948c59933dc1f. --- src/capture/kms.c | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/src/capture/kms.c b/src/capture/kms.c index 89a86f7..fe4eb40 100644 --- a/src/capture/kms.c +++ b/src/capture/kms.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -275,31 +274,6 @@ static vec2i swap_vec2i(vec2i value) { return value; } -/* Wait for any pending writer fences on the dma-buf so we don't sample a - partially-written buffer. POLLIN on a dma-buf fd is the kernel's portable - way to wait on all attached writer fences (works across drivers, unlike - relying on Mesa's implicit-sync to honor a fence from another driver). - Without this, KMS capture can produce glitched frames in hybrid GPU setups - where the buffer on the captured plane is being written by a different - GPU/driver than the one gsr is encoding on (e.g. an iGPU client surface - shown on a dGPU monitor). The wait is normally a no-op because the fence - has already signaled by the time we get here; the timeout is just a guard. */ -static void gsr_capture_kms_wait_for_buffer_fence(const int *fds, int num_fds) { - struct pollfd pfds[GSR_KMS_MAX_DMA_BUFS]; - int num_pfds = 0; - for(int i = 0; i < num_fds && num_pfds < GSR_KMS_MAX_DMA_BUFS; ++i) { - if(fds[i] <= 0) - continue; - pfds[num_pfds].fd = fds[i]; - pfds[num_pfds].events = POLLIN; - pfds[num_pfds].revents = 0; - ++num_pfds; - } - if(num_pfds == 0) - return; - (void)poll(pfds, num_pfds, 100); -} - static EGLImage gsr_capture_kms_create_egl_image(gsr_capture_kms *self, const gsr_kms_response_item *drm_fd, const int *fds, const uint32_t *offsets, const uint32_t *pitches, const uint64_t *modifiers, bool use_modifiers) { intptr_t img_attr[44]; setup_dma_buf_attrs(img_attr, drm_fd->pixel_format, drm_fd->width, drm_fd->height, fds, offsets, pitches, modifiers, drm_fd->num_dma_bufs, use_modifiers); @@ -345,8 +319,6 @@ static EGLImage gsr_capture_kms_create_egl_image_with_fallback(gsr_capture_kms * modifiers[i] = drm_fd->modifier; } - gsr_capture_kms_wait_for_buffer_fence(fds, drm_fd->num_dma_bufs); - EGLImage image = NULL; if(self->no_modifiers_fallback) { image = gsr_capture_kms_create_egl_image(self, drm_fd, fds, offsets, pitches, modifiers, false); @@ -480,8 +452,6 @@ static void render_drm_cursor(gsr_capture_kms *self, gsr_color_conversion *color modifiers[i] = cursor_drm_fd->modifier; } - gsr_capture_kms_wait_for_buffer_fence(fds, cursor_drm_fd->num_dma_bufs); - intptr_t img_attr_cursor[44]; setup_dma_buf_attrs(img_attr_cursor, cursor_drm_fd->pixel_format, cursor_drm_fd->width, cursor_drm_fd->height, fds, offsets, pitches, modifiers, cursor_drm_fd->num_dma_bufs, true); -- cgit v1.2.3 From 993e5e63babc9abbd958b0764d8d7a47d6eda3c6 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 18 May 2026 07:38:15 +0200 Subject: Fix gsr_pipewire_video_remove_modifier removing incorrect modifiers --- src/pipewire_video.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/pipewire_video.c b/src/pipewire_video.c index 2a2d7d5..30e00a8 100644 --- a/src/pipewire_video.c +++ b/src/pipewire_video.c @@ -553,12 +553,8 @@ static size_t gsr_pipewire_video_format_remove_modifier(gsr_pipewire_video *self } static void gsr_pipewire_video_remove_modifier(gsr_pipewire_video *self, uint64_t modifier) { - self->num_modifiers = 0; for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) { - gsr_video_format *video_format = &self->supported_video_formats[i]; - const size_t num_modifiers_video_format = gsr_pipewire_video_format_remove_modifier(self, video_format, modifier); - video_format->modifiers_index = self->num_modifiers; - self->num_modifiers += num_modifiers_video_format; + gsr_pipewire_video_format_remove_modifier(self, &self->supported_video_formats[i], modifier); } } -- cgit v1.2.3 From a5e6c6d2b30f9a072a657e995836e1f9bf493e3e Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 18 May 2026 22:48:55 +0200 Subject: FAQ nvidia stutter --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 898332b..9375344 100644 --- a/README.md +++ b/README.md @@ -275,3 +275,5 @@ This is an issue in some broken video players such as vlc. Play the video with a This may be caused by buggy GPU drivers. Try recording with HEVC video codec instead (`-k hevc`). ## The quality is low when recording my desktop Color gradients encode badly, especially with h264 video codec. You can increase the bitrate, for example by using `-bm cbr -q 30000` or by using hevc (`-k hevc`) and increasing the quality (`-q ultra`). +## My system stutters sometimes while recording on nvidia +This is an nvidia power management driver bug which can happen when the system is idle. You can change the gpu power performance level to "performance" in nvidia settings to fix this. -- cgit v1.2.3 From f9d6045b28c5ba71831896c1acc2db13f83c6825 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 19 May 2026 17:18:01 +0200 Subject: Remove vulkan dependency from meson (only headers are used) --- meson.build | 1 - project.conf | 1 - 2 files changed, 2 deletions(-) diff --git a/meson.build b/meson.build index 4f62972..7d87e62 100644 --- a/meson.build +++ b/meson.build @@ -72,7 +72,6 @@ dep = [ dependency('libdrm'), dependency('wayland-egl'), dependency('wayland-client'), - dependency('vulkan'), ] if build_machine.system() == 'linux' diff --git a/project.conf b/project.conf index b890eac..59e405c 100644 --- a/project.conf +++ b/project.conf @@ -33,4 +33,3 @@ wayland-client = ">=1" dbus-1 = ">=1" libpipewire-0.3 = ">=1" libspa-0.2 = ">=0" -vulkan = ">=1" -- cgit v1.2.3 From 280131585e07b6d5c8438af258b7de0afeff8266 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 20 May 2026 08:10:47 +0200 Subject: plugin: add is_damaged and clear_damage --- include/plugins.h | 2 ++ plugin/examples/hello_triangle/triangle.c | 10 ++++++++++ plugin/plugin.h | 6 ++++++ src/main.cpp | 3 +++ src/plugins.c | 18 ++++++++++++++++++ 5 files changed, 39 insertions(+) diff --git a/include/plugins.h b/include/plugins.h index 7588b61..db10336 100644 --- a/include/plugins.h +++ b/include/plugins.h @@ -34,5 +34,7 @@ void gsr_plugins_deinit(gsr_plugins *self); bool gsr_plugins_load_plugin(gsr_plugins *self, const char *plugin_filepath); void gsr_plugins_draw(gsr_plugins *self); +bool gsr_plugins_is_damaged(gsr_plugins *self); +void gsr_plugins_clear_damage(gsr_plugins *self); #endif /* GSR_PLUGINS_H */ diff --git a/plugin/examples/hello_triangle/triangle.c b/plugin/examples/hello_triangle/triangle.c index 194cf92..fd27a3d 100644 --- a/plugin/examples/hello_triangle/triangle.c +++ b/plugin/examples/hello_triangle/triangle.c @@ -62,6 +62,14 @@ static void draw(const gsr_plugin_draw_params *params, void *userdata) { ++triangle->counter; } +static bool is_damaged(void *userdata) { + return true; +} + +static void clear_damage(void *userdata) { + +} + bool gsr_plugin_init(const gsr_plugin_init_params *params, gsr_plugin_init_return *ret) { Triangle *triangle = calloc(1, sizeof(Triangle)); if(!triangle) @@ -98,6 +106,8 @@ bool gsr_plugin_init(const gsr_plugin_init_params *params, gsr_plugin_init_retur ret->version = 1; ret->userdata = triangle; ret->draw = draw; + ret->is_damaged = is_damaged; + ret->clear_damage = clear_damage; return true; } diff --git a/plugin/plugin.h b/plugin/plugin.h index 902d1d7..769a629 100644 --- a/plugin/plugin.h +++ b/plugin/plugin.h @@ -43,6 +43,12 @@ typedef struct { /* Optional, called when the plugin is expected to draw something to the current framebuffer */ void (*draw)(const gsr_plugin_draw_params *params, void *userdata); + /* Optional, returns false if this function is NULL. If this function is defined then this should return true + if there is an update visually that the plugin wants to display. This is useful when used with the "content" framerate mode + which only captures a frame when the frame has changed */ + bool (*is_damaged)(void *userdata); + /* Optional */ + void (*clear_damage)(void *userdata); } gsr_plugin_init_return; /* The plugin is expected to implement these functions and export them: */ diff --git a/src/main.cpp b/src/main.cpp index c2a5516..f0fbfc0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4388,6 +4388,8 @@ int main(int argc, char **argv) { damaged = true; } + damaged |= gsr_plugins_is_damaged(&plugins); + // TODO: Readd wayland sync warning when removing this if(arg_parser.framerate_mode != GSR_FRAMERATE_MODE_CONTENT) damaged = true; @@ -4417,6 +4419,7 @@ int main(int argc, char **argv) { egl.glClear(0); gsr_damage_clear(&damage); + gsr_plugins_clear_damage(&plugins); gsr_capture_kms_cleanup_kms_fds(); if(kms_client_initialized) { diff --git a/src/plugins.c b/src/plugins.c index 764dbc7..053b2a3 100644 --- a/src/plugins.c +++ b/src/plugins.c @@ -135,3 +135,21 @@ void gsr_plugins_draw(gsr_plugins *self) { self->egl->glBindFramebuffer(GL_FRAMEBUFFER, 0); } + +bool gsr_plugins_is_damaged(gsr_plugins *self) { + bool damaged = false; + for(int i = 0; i < self->num_plugins; ++i) { + const gsr_plugin *plugin = &self->plugins[i]; + if(plugin->data.is_damaged) + damaged |= plugin->data.is_damaged(plugin->data.userdata); + } + return damaged; +} + +void gsr_plugins_clear_damage(gsr_plugins *self) { + for(int i = 0; i < self->num_plugins; ++i) { + const gsr_plugin *plugin = &self->plugins[i]; + if(plugin->data.clear_damage) + plugin->data.clear_damage(plugin->data.userdata); + } +} -- cgit v1.2.3 From a0bfc27456f365192955de5effc8de0e264400ec Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 20 May 2026 08:11:26 +0200 Subject: Allow pipewire format with no modifiers --- src/pipewire_video.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/pipewire_video.c b/src/pipewire_video.c index 30e00a8..1f18819 100644 --- a/src/pipewire_video.c +++ b/src/pipewire_video.c @@ -456,8 +456,6 @@ static bool gsr_pipewire_video_build_format_params(gsr_pipewire_video *self, str return false; for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) { - if(self->supported_video_formats[i].modifiers_size == 0) - continue; params[*num_params] = build_format(pod_builder, &self->video_info, self->supported_video_formats[i].format, self->modifiers + self->supported_video_formats[i].modifiers_index, self->supported_video_formats[i].modifiers_size); ++(*num_params); } @@ -519,10 +517,10 @@ static bool spa_video_format_get_modifiers(gsr_pipewire_video *self, const enum return false; } - if(*num_modifiers < max_modifiers) { - modifiers[*num_modifiers] = DRM_FORMAT_MOD_INVALID; - *num_modifiers += 1; - } + // if(*num_modifiers < max_modifiers) { + // modifiers[*num_modifiers] = DRM_FORMAT_MOD_INVALID; + // *num_modifiers += 1; + // } return true; } -- cgit v1.2.3 From a53344b7fcf36160ce6646cf2ef5dd443ad3a7b5 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 20 May 2026 08:20:12 +0200 Subject: Revert "Allow pipewire format with no modifiers" This reverts commit a0bfc27456f365192955de5effc8de0e264400ec. --- src/pipewire_video.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pipewire_video.c b/src/pipewire_video.c index 1f18819..30e00a8 100644 --- a/src/pipewire_video.c +++ b/src/pipewire_video.c @@ -456,6 +456,8 @@ static bool gsr_pipewire_video_build_format_params(gsr_pipewire_video *self, str return false; for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) { + if(self->supported_video_formats[i].modifiers_size == 0) + continue; params[*num_params] = build_format(pod_builder, &self->video_info, self->supported_video_formats[i].format, self->modifiers + self->supported_video_formats[i].modifiers_index, self->supported_video_formats[i].modifiers_size); ++(*num_params); } @@ -517,10 +519,10 @@ static bool spa_video_format_get_modifiers(gsr_pipewire_video *self, const enum return false; } - // if(*num_modifiers < max_modifiers) { - // modifiers[*num_modifiers] = DRM_FORMAT_MOD_INVALID; - // *num_modifiers += 1; - // } + if(*num_modifiers < max_modifiers) { + modifiers[*num_modifiers] = DRM_FORMAT_MOD_INVALID; + *num_modifiers += 1; + } return true; } -- cgit v1.2.3 From b7a4b8e083257afe60e101a1f0cf19a6ca1b077f Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 20 May 2026 08:29:24 +0200 Subject: pipewire video: fallback to no modifier --- src/pipewire_video.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pipewire_video.c b/src/pipewire_video.c index 30e00a8..1f7a669 100644 --- a/src/pipewire_video.c +++ b/src/pipewire_video.c @@ -558,6 +558,14 @@ static void gsr_pipewire_video_remove_modifier(gsr_pipewire_video *self, uint64_ } } +static size_t gsr_pipewire_video_num_modifiers_in_format(gsr_pipewire_video *self, enum spa_video_format format) { + for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) { + if(self->supported_video_formats[i].format == format) + return self->supported_video_formats[i].modifiers_size; + } + return 0; +} + static bool gsr_pipewire_video_setup_stream(gsr_pipewire_video *self) { struct spa_pod *params[GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS]; uint32_t num_video_formats = 0; @@ -786,18 +794,22 @@ static EGLImage gsr_pipewire_video_create_egl_image_with_fallback(gsr_pipewire_v } else { image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, true); if(!image) { + pw_thread_loop_lock(self->thread_loop); if(self->format.info.raw.modifier == DRM_FORMAT_MOD_INVALID) { fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n"); self->no_modifiers_fallback = true; image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false); + } else if(gsr_pipewire_video_num_modifiers_in_format(self, self->format.info.raw.format) <= 1) { + fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers and ran out of modifiers to try with, trying without modifiers\n"); + self->no_modifiers_fallback = true; + image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false); } else { fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifier 0x%" PRIx64 ", renegotiating with a different modifier\n", self->format.info.raw.modifier); self->negotiated = false; - pw_thread_loop_lock(self->thread_loop); gsr_pipewire_video_remove_modifier(self, self->format.info.raw.modifier); pw_loop_signal_event(pw_thread_loop_get_loop(self->thread_loop), self->reneg); - pw_thread_loop_unlock(self->thread_loop); } + pw_thread_loop_unlock(self->thread_loop); } } return image; -- cgit v1.2.3 From 6767ab5c4da5ea580bedc97002a6dcfe98e1455d Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 20 May 2026 10:00:59 +0200 Subject: Revert tests --- src/pipewire_video.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/pipewire_video.c b/src/pipewire_video.c index 1f7a669..30e00a8 100644 --- a/src/pipewire_video.c +++ b/src/pipewire_video.c @@ -558,14 +558,6 @@ static void gsr_pipewire_video_remove_modifier(gsr_pipewire_video *self, uint64_ } } -static size_t gsr_pipewire_video_num_modifiers_in_format(gsr_pipewire_video *self, enum spa_video_format format) { - for(size_t i = 0; i < GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS; i++) { - if(self->supported_video_formats[i].format == format) - return self->supported_video_formats[i].modifiers_size; - } - return 0; -} - static bool gsr_pipewire_video_setup_stream(gsr_pipewire_video *self) { struct spa_pod *params[GSR_PIPEWIRE_VIDEO_NUM_VIDEO_FORMATS]; uint32_t num_video_formats = 0; @@ -794,22 +786,18 @@ static EGLImage gsr_pipewire_video_create_egl_image_with_fallback(gsr_pipewire_v } else { image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, true); if(!image) { - pw_thread_loop_lock(self->thread_loop); if(self->format.info.raw.modifier == DRM_FORMAT_MOD_INVALID) { fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers, trying without modifiers\n"); self->no_modifiers_fallback = true; image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false); - } else if(gsr_pipewire_video_num_modifiers_in_format(self, self->format.info.raw.format) <= 1) { - fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifiers and ran out of modifiers to try with, trying without modifiers\n"); - self->no_modifiers_fallback = true; - image = gsr_pipewire_video_create_egl_image(self, fds, offsets, pitches, modifiers, false); } else { fprintf(stderr, "gsr error: gsr_pipewire_video_create_egl_image_with_fallback: failed to create egl image with modifier 0x%" PRIx64 ", renegotiating with a different modifier\n", self->format.info.raw.modifier); self->negotiated = false; + pw_thread_loop_lock(self->thread_loop); gsr_pipewire_video_remove_modifier(self, self->format.info.raw.modifier); pw_loop_signal_event(pw_thread_loop_get_loop(self->thread_loop), self->reneg); + pw_thread_loop_unlock(self->thread_loop); } - pw_thread_loop_unlock(self->thread_loop); } } return image; -- cgit v1.2.3 From 2db954c4681aaf7dd24f187ef95df7ded9995bd7 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Thu, 21 May 2026 22:05:17 +0200 Subject: FAQ webcam --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 9375344..3805a7e 100644 --- a/README.md +++ b/README.md @@ -277,3 +277,6 @@ This may be caused by buggy GPU drivers. Try recording with HEVC video codec ins Color gradients encode badly, especially with h264 video codec. You can increase the bitrate, for example by using `-bm cbr -q 30000` or by using hevc (`-k hevc`) and increasing the quality (`-q ultra`). ## My system stutters sometimes while recording on nvidia This is an nvidia power management driver bug which can happen when the system is idle. You can change the gpu power performance level to "performance" in nvidia settings to fix this. +## Recording fails when using webcam on nvidia X11 +This is a known issue. The issue is that nvfbc which is used for screen capture only supports glx on older systems and glx is not compatible with the webcam capture method used in GPU Screen Recorder. +This will be fixed in the future. -- cgit v1.2.3 From c9edc7bf8dcaeb24e3048908dab21462d2ef86d3 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Sat, 23 May 2026 05:44:10 +0200 Subject: Update manpage: add example for constant bitrate replay --- gpu-screen-recorder.1 | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/gpu-screen-recorder.1 b/gpu-screen-recorder.1 index e511165..40a3b3e 100644 --- a/gpu-screen-recorder.1 +++ b/gpu-screen-recorder.1 @@ -484,7 +484,15 @@ Instant replay (last 60 seconds): .PP .nf .RS -gpu-screen-recorder -w screen -c mkv -r 60 -o ~/Videos +gpu-screen-recorder -w screen -c mp4 -r 60 -o ~/Videos +.RE +.fi +.PP +Instant replay with constant framerate (recommended for predicable ram usage): +.PP +.nf +.RS +gpu-screen-recorder -w screen -c mp4 -r 60 -bm cbr -q 40000 -o ~/Videos .RE .fi .PP @@ -508,7 +516,7 @@ Instant replay and launch a script when saving replay: .PP .nf .RS -gpu-screen-recorder -w screen -c mkv -r 60 -sc ./script.sh -o ~/Videos +gpu-screen-recorder -w screen -c mp4 -r 60 -sc ./script.sh -o ~/Videos .RE .fi .PP -- cgit v1.2.3 From 33aba9a2c96c0cf4f3da512b1e1af076d220ea4a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 26 May 2026 03:24:50 +0200 Subject: README: mention debian package --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3805a7e..289c5bf 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ The only official ways to install GPU Screen Recorder is either from source, arc If you install GPU Screen Recorder from somewhere else and have an issue then try installing it from one of the official sources before reporting it as an issue.\ If you still prefer to install GPU Screen Recorder with a package manager instead of from source or as a flatpak then you may be able to find a package for your distro.\ Here are some known unofficial packages: -* Debian/Ubuntu: [Pacstall](https://pacstall.dev/packages/gpu-screen-recorder) +* Ubuntu: [Pacstall](https://pacstall.dev/packages/gpu-screen-recorder) +* Debian: [gpu-screen-recorder-cli](https://tracker.debian.org/pkg/gpu-screen-recorder) * Nix: [NixOS wiki](https://wiki.nixos.org/wiki/Gpu-screen-recorder) * openSUSE: [openSUSE software repository](https://software.opensuse.org/package/gpu-screen-recorder) * Fedora, CentOS: [Copr](https://copr.fedorainfracloud.org/coprs/brycensranch/gpu-screen-recorder-git/) -- cgit v1.2.3 From 60f0459f840f568159a320f58d56063ba02e52c4 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Wed, 27 May 2026 06:01:48 +0200 Subject: Add check for libavformat version before using hybrid_fragmented, to workaround old ffmpeg bugs --- src/main.cpp | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f0fbfc0..147db4f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1039,19 +1039,23 @@ static bool add_hdr_metadata_to_video_stream(gsr_capture *cap, AVStream *video_s } static void set_format_context_options(AVFormatContext *av_format_context) { - bool hybrid_fragmented_available = false; - av_opt_set(av_format_context->priv_data, "use_editlist", "1", 0); - const AVOption *opt = av_opt_find(av_format_context->priv_data, "movflags", NULL, 0, 0); - if (opt && opt->unit) { - const AVOption *flag = av_opt_find(av_format_context->priv_data, "hybrid_fragmented", opt->unit, 0, 0); - if (flag) { - hybrid_fragmented_available = true; - av_opt_set(av_format_context->priv_data, "movflags", "+hybrid_fragmented", 0); + if(LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(62, 6, 101)) { + bool hybrid_fragmented_available = false; + av_opt_set(av_format_context->priv_data, "use_editlist", "1", 0); + const AVOption *opt = av_opt_find(av_format_context->priv_data, "movflags", NULL, 0, 0); + if (opt && opt->unit) { + const AVOption *flag = av_opt_find(av_format_context->priv_data, "hybrid_fragmented", opt->unit, 0, 0); + if (flag) { + hybrid_fragmented_available = true; + av_opt_set(av_format_context->priv_data, "movflags", "+hybrid_fragmented", 0); + } } - } - if(!hybrid_fragmented_available) - fprintf(stderr, "gsr warning: hybrid_fragmented movflags is not available in your FFmpeg version. If you experience stutter in the mp4 file then update your FFmpeg or record to a mkv file\n"); + if(!hybrid_fragmented_available) + fprintf(stderr, "gsr warning: hybrid_fragmented movflags is not available in your FFmpeg version. If you experience stutter in the mp4 file then update your FFmpeg to at least version 8 or record to a mkv file\n"); + } else { + fprintf(stderr, "gsr warning: your FFmpeg version is known to be buggy (it doesn't have working hybrid_fragmented movflags). If you experience stutter in the mp4 file then update your FFmpeg to at least version 8 or record to a mkv file\n"); + } } struct RecordingStartAudio { -- cgit v1.2.3 From 099b95a780805067aec84a62d3110760f0fcbb1a Mon Sep 17 00:00:00 2001 From: dec05eba Date: Fri, 29 May 2026 08:15:09 +0200 Subject: Fix -c whip not working (dont do avio_open for live streams) --- README.md | 7 ++++++- TODO | 5 +++++ include/args_parser.h | 1 + src/args_parser.c | 8 ++++---- src/main.cpp | 49 ++++++++++++++++++++++--------------------------- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 289c5bf..4ce7221 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The only official ways to install GPU Screen Recorder is either from source, arc If you install GPU Screen Recorder from somewhere else and have an issue then try installing it from one of the official sources before reporting it as an issue.\ If you still prefer to install GPU Screen Recorder with a package manager instead of from source or as a flatpak then you may be able to find a package for your distro.\ Here are some known unofficial packages: -* Ubuntu: [Pacstall](https://pacstall.dev/packages/gpu-screen-recorder) +* Ubuntu: [gpu-screen-recorder-cli](https://launchpad.net/ubuntu/+source/gpu-screen-recorder) * Debian: [gpu-screen-recorder-cli](https://tracker.debian.org/pkg/gpu-screen-recorder) * Nix: [NixOS wiki](https://wiki.nixos.org/wiki/Gpu-screen-recorder) * openSUSE: [openSUSE software repository](https://software.opensuse.org/package/gpu-screen-recorder) @@ -122,6 +122,11 @@ By default videos are recorded with constant quality, but with replay mode you m Streaming works the same way as recording, but the `-o` argument should be path to the live streaming service you want to use (including your live streaming key). Take a look at `scripts/twitch-stream.sh` to see an example of how to stream to twitch.\ GPU Screen Recorder uses Ffmpeg so GPU Screen Recorder supports all protocols that Ffmpeg supports.\ If you want to reduce latency one thing you can do is to use the `-keyint` option, for example `-keyint 0.5`. Lower value means lower latency at the cost of increased bitrate/decreased quality. +## Very low latency streaming (WHIP) +FFmpeg since version 7.1 supports the WHIP protocol for very low latency streaming (milliseconds latency). Here is a simple way to set it up: +1. Download the latest [mediamtx](https://github.com/bluenviron/mediamtx/releases/) binary and run it (just run `./mediamtx`) +2. Run `gpu-screen-recorder` with WHIP, for example: `gpu-screen-recorder -w screen -f 60 -k h264 -bm cbr -q 20000 -a default_output -ac opus -c whip -o "http://localhost:8889/mystream/whip"`. + If you have a high refresh rate monitor you can set `-f 60` framerate option to your monitors framerate (for example `-f 144`) to have even lower latency. ## Recording while using replay/streaming You can record a regular video while using replay/streaming by launching GPU Screen Recorder with the `-ro` option to specify a directory where to save the recording (for example: `gpu-screen-recorder -w screen -c mp4 -r 60 -o "$HOME/Videos/replays" -ro "$HOME/Videos/recordings"`).\ To start/stop (and save) recording use the SIGRTMIN signal, for example `pkill -SIGRTMIN -f "^gpu-screen-recorder"`. The path to the video will be displayed in stdout when saving the video.\ diff --git a/TODO b/TODO index 935388e..933df3a 100644 --- a/TODO +++ b/TODO @@ -422,3 +422,8 @@ Make -fm content the default, but first make it record at no lower than 2 fps if Fix camera capture on nvidia x11. It doesn't work with mjpeg either now because external shader compilation fails. Revert the revert of improving pipewire capture, for better cursor capture. It caused glitches for some, especially in kde plasma when hdr is enabled. Try marking test and moving the cursor and see. + +Add the option to use application audio with exectable name, or automatically check against both application name and executable name. + In the ui it should also list the application name beside the app audio. + +Region capture works incorrectly on gnome with hidpi, especially with 150% scaling. Same in gsr ui. diff --git a/include/args_parser.h b/include/args_parser.h index 75d8bfa..2208a9a 100644 --- a/include/args_parser.h +++ b/include/args_parser.h @@ -98,6 +98,7 @@ typedef struct { bool restore_portal_session; bool restart_replay_on_save; bool write_first_frame_ts; + bool is_replaying; bool is_livestream; bool is_output_piped; bool low_latency_recording; diff --git a/src/args_parser.c b/src/args_parser.c index 658e559..1957f32 100644 --- a/src/args_parser.c +++ b/src/args_parser.c @@ -398,18 +398,18 @@ static bool args_parser_set_values(args_parser *self) { if(self->container_format && strcmp(self->container_format, "mkv") == 0) self->container_format = "matroska"; - const bool is_replaying = self->replay_buffer_size_secs != -1; + self->is_replaying = self->replay_buffer_size_secs != -1; self->is_livestream = false; self->filename = args_get_value_by_key(self->args, NUM_ARGS, "-o"); if(self->filename) { self->is_livestream = is_livestream_path(self->filename); if(self->is_livestream) { - if(is_replaying) { + if(self->is_replaying) { fprintf(stderr, "gsr error: replay mode is not applicable to live streaming\n"); return false; } } else { - if(!is_replaying) { + if(!self->is_replaying) { char directory_buf[PATH_MAX]; snprintf(directory_buf, sizeof(directory_buf), "%s", self->filename); char *directory = dirname(directory_buf); @@ -435,7 +435,7 @@ static bool args_parser_set_values(args_parser *self) { } } } else { - if(!is_replaying) { + if(!self->is_replaying) { self->filename = "/dev/stdout"; } else { fprintf(stderr, "gsr error: Option -o is required when using option -r\n"); diff --git a/src/main.cpp b/src/main.cpp index 147db4f..d109e9e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3026,7 +3026,7 @@ static gsr_audio_codec select_audio_codec_with_fallback(gsr_audio_codec audio_co break; } case GSR_AUDIO_CODEC_OPUS: { - if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm" && file_extension != "ts") { + if(file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm" && file_extension != "ts" && file_extension != "whip") { //audio_codec_to_use = "aac"; audio_codec = GSR_AUDIO_CODEC_AAC; fprintf(stderr, "gsr warning: opus audio codec is only supported by .mp4, .mkv, .webm and .ts files, falling back to aac instead\n"); @@ -3493,30 +3493,18 @@ static bool get_image_format_from_filename(const char *filename, gsr_image_forma } } -// TODO: replace this with start_recording_create_steams -static bool av_open_file_write_header(AVFormatContext *av_format_context, const char *filename, const char *ffmpeg_opts) { - int ret = avio_open(&av_format_context->pb, filename, AVIO_FLAG_WRITE); - if(ret < 0) { - fprintf(stderr, "gsr error: Could not open '%s': %s\n", filename, av_error_to_string(ret)); - return false; - } - +static void av_write_header(AVFormatContext *av_format_context, const char *ffmpeg_opts) { AVDictionary *options = nullptr; av_dict_set(&options, "strict", "experimental", 0); if(ffmpeg_opts) av_dict_parse_string(&options, ffmpeg_opts, "=", ";", 0); - ret = avformat_write_header(av_format_context, &options); + const int ret = avformat_write_header(av_format_context, &options); if(ret < 0) fprintf(stderr, "Error occurred when writing header to output file: %s\n", av_error_to_string(ret)); - const bool success = ret >= 0; - if(!success) - avio_close(av_format_context->pb); - av_dict_free(&options); - return success; } static int audio_codec_get_frame_size(gsr_audio_codec audio_codec) { @@ -3780,8 +3768,6 @@ int main(int argc, char **argv) { validate_merged_audio_inputs_app_audio(requested_audio_inputs, app_audio_names); - const bool is_replaying = arg_parser.replay_buffer_size_secs != -1; - bool wayland = false; Display *dpy = XOpenDisplay(nullptr); if(dpy) { @@ -3896,6 +3882,9 @@ int main(int argc, char **argv) { file_extension = file_extension.substr(0, comma_index); } + if(file_extension.empty()) + file_extension = arg_parser.container_format ? arg_parser.container_format : ""; + const bool force_no_audio_offset = arg_parser.is_livestream || arg_parser.is_output_piped || (file_extension != "mp4" && file_extension != "mkv" && file_extension != "webm"); const double target_fps = 1.0 / (double)arg_parser.fps; @@ -3927,7 +3916,7 @@ int main(int argc, char **argv) { const enum AVPixelFormat video_pix_fmt = get_pixel_format(arg_parser.video_codec, egl.gpu_info.vendor, arg_parser.video_encoder == GSR_VIDEO_ENCODER_HW_CPU); AVCodecContext *video_codec_context = create_video_codec_context(video_pix_fmt, video_codec_f, egl, arg_parser, video_size.x, video_size.y); - if(!is_replaying) + if(!arg_parser.is_replaying) video_stream = create_stream(av_format_context, video_codec_context); AVFrame *video_frame = av_frame_alloc(); @@ -4014,7 +4003,7 @@ int main(int argc, char **argv) { AVCodecContext *audio_codec_context = create_audio_codec_context(arg_parser.fps, arg_parser.audio_codec, use_amix, arg_parser.audio_bitrate); AVStream *audio_stream = nullptr; - if(!is_replaying) { + if(!arg_parser.is_replaying) { audio_stream = create_stream(av_format_context, audio_codec_context); if(gsr_encoder_add_recording_destination(&encoder, audio_codec_context, av_format_context, audio_stream, 0) == (size_t)-1) fprintf(stderr, "gsr error: added too many audio sources\n"); @@ -4080,11 +4069,17 @@ int main(int argc, char **argv) { //av_dump_format(av_format_context, 0, filename, 1); - if(!is_replaying) { - if(!av_open_file_write_header(av_format_context, arg_parser.filename, arg_parser.ffmpeg_opts)) + if(!arg_parser.is_replaying && !(output_format->flags & AVFMT_NOFILE)) { + const int ret = avio_open(&av_format_context->pb, arg_parser.filename, AVIO_FLAG_WRITE); + if(ret < 0) { + fprintf(stderr, "gsr error: Could not open '%s': %s\n", arg_parser.filename, av_error_to_string(ret)); _exit(1); + } } + if(!arg_parser.is_replaying) + av_write_header(av_format_context, arg_parser.ffmpeg_opts); + double fps_start_time = clock_get_monotonic_seconds(); //double frame_timer_start = fps_start_time; int fps_counter = 0; @@ -4478,7 +4473,7 @@ int main(int argc, char **argv) { gsr_video_encoder_copy_textures_to_frame(video_encoder, video_frame, output_color_conversion); for(VideoSource &video_source : video_sources) { - if(hdr && !hdr_metadata_set && !is_replaying && add_hdr_metadata_to_video_stream(video_source.capture, video_stream)) + if(hdr && !hdr_metadata_set && !arg_parser.is_replaying && add_hdr_metadata_to_video_stream(video_source.capture, video_stream)) hdr_metadata_set = true; } @@ -4516,7 +4511,7 @@ int main(int argc, char **argv) { video_pts_counter += num_missed_frames; } - if(toggle_pause == 1 && !is_replaying) { + if(toggle_pause == 1 && !arg_parser.is_replaying) { const bool new_paused_state = !paused; if(new_paused_state) { paused_time_start = clock_get_monotonic_seconds(); @@ -4603,7 +4598,7 @@ int main(int argc, char **argv) { } } - if(save_replay_seconds != 0 && !save_replay_thread.valid() && is_replaying) { + if(save_replay_seconds != 0 && !save_replay_thread.valid() && arg_parser.is_replaying) { int current_save_replay_seconds = save_replay_seconds; if(current_save_replay_seconds > 0) current_save_replay_seconds += arg_parser.keyint; @@ -4683,11 +4678,11 @@ int main(int argc, char **argv) { amix_thread.join(); // TODO: Replace this with start_recording_create_steams - if(!is_replaying && av_write_trailer(av_format_context) != 0) { + if(!arg_parser.is_replaying && av_write_trailer(av_format_context) != 0) { //fprintf(stderr, "Failed to write trailer\n"); } - if(!is_replaying) { + if(!arg_parser.is_replaying && !(output_format->flags & AVFMT_NOFILE)) { avio_close(av_format_context->pb); avformat_free_context(av_format_context); } @@ -4708,7 +4703,7 @@ int main(int argc, char **argv) { gsr_kms_client_deinit(&kms_client); } - if(!is_replaying && arg_parser.recording_saved_script) + if(!arg_parser.is_replaying && arg_parser.recording_saved_script) run_recording_saved_script_async(arg_parser.recording_saved_script, arg_parser.filename, "regular"); if(dpy) { -- cgit v1.2.3 From 600a871d1e9a1d0634088cd705854fd8a906ba9c Mon Sep 17 00:00:00 2001 From: Connor Welsh Date: Mon, 1 Jun 2026 13:43:23 -0400 Subject: Only install nvidia profile when the driver is loaded --- src/main.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.cpp b/src/main.cpp index d109e9e..e4d1cbe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3612,6 +3612,9 @@ static void validate_args_with_capture_sources(args_parser &arg_parser, const st } static void install_cuda_no_stable_perf_limit() { + if(access("/proc/driver/nvidia/version", F_OK) != 0) + return; + const char *home = getenv("HOME"); if(!home) { fprintf(stderr, "gsr warning: install_cuda_no_stable_perf_limit: $HOME not set\n"); -- cgit v1.2.3 From f996b83a300446448f3759ae2d8274c1561a8280 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 2 Jun 2026 02:31:08 +0200 Subject: Wayland socket: bypass flatpak sandbox --- include/wayland_host_bridge.h | 7 +++ meson.build | 1 + src/wayland_host_bridge.c | 109 ++++++++++++++++++++++++++++++++++++++++++ src/window/wayland.c | 3 +- 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 include/wayland_host_bridge.h create mode 100644 src/wayland_host_bridge.c diff --git a/include/wayland_host_bridge.h b/include/wayland_host_bridge.h new file mode 100644 index 0000000..9cf6d63 --- /dev/null +++ b/include/wayland_host_bridge.h @@ -0,0 +1,7 @@ +#ifndef GSR_WAYLAND_HOST_BRIDGE_H +#define GSR_WAYLAND_HOST_BRIDGE_H + +struct wl_display; +struct wl_display *wayland_connect_to_host(); + +#endif /* GSR_WAYLAND_HOST_BRIDGE_H */ diff --git a/meson.build b/meson.build index 7d87e62..14255bc 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,7 @@ src = [ 'src/args_parser.c', 'src/defs.c', 'src/plugins.c', + 'src/wayland_host_bridge.c', 'src/sound.cpp', 'src/main.cpp', ] diff --git a/src/wayland_host_bridge.c b/src/wayland_host_bridge.c new file mode 100644 index 0000000..4e8319b --- /dev/null +++ b/src/wayland_host_bridge.c @@ -0,0 +1,109 @@ +#include "../include/wayland_host_bridge.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define GSR_UI_UNIX_SOCKET_DOMAIN_FD 3 + +static const char *bridge_host_path = + "/var/lib/flatpak/app/com.dec05eba.gpu_screen_recorder/current/active/files/bin/gsr-wayland-bridge"; + +static int recv_fd(int sock) { + char dummy = 0; + struct iovec iov = { &dummy, 1 }; + char cbuf[CMSG_SPACE(sizeof(int))]; + memset(cbuf, 0, sizeof(cbuf)); + struct msghdr msg; + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = cbuf; + msg.msg_controllen = sizeof(cbuf); + + const ssize_t n = recvmsg(sock, &msg, 0); + if(n <= 0) + return -1; + + for(struct cmsghdr *c = CMSG_FIRSTHDR(&msg); c; c = CMSG_NXTHDR(&msg, c)) { + if(c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS) { + int fd = -1; + memcpy(&fd, CMSG_DATA(c), sizeof(int)); + return fd; + } + } + return -1; +} + +static struct wl_display* connect_via_bridge() { + const char *wayland_display = getenv("WAYLAND_DISPLAY"); + + int sv[2]; + if(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sv) != 0) { + perror("WaylandHostBridge: socketpair"); + return NULL; + } + + const pid_t pid = fork(); + if(pid < 0) { + perror("WaylandHostBridge: fork"); + close(sv[0]); + close(sv[1]); + return NULL; + } + + if(pid == 0) { + close(sv[0]); + if(sv[1] != GSR_UI_UNIX_SOCKET_DOMAIN_FD) { + dup2(sv[1], GSR_UI_UNIX_SOCKET_DOMAIN_FD); + close(sv[1]); + } + const int flags = fcntl(GSR_UI_UNIX_SOCKET_DOMAIN_FD, F_GETFD); + if(flags >= 0) + fcntl(GSR_UI_UNIX_SOCKET_DOMAIN_FD, F_SETFD, flags & ~FD_CLOEXEC); + + char forward_fd_arg[32]; + snprintf(forward_fd_arg, sizeof(forward_fd_arg), "--forward-fd=%d", GSR_UI_UNIX_SOCKET_DOMAIN_FD); + + execlp("flatpak-spawn", "flatpak-spawn", "--host", forward_fd_arg, bridge_host_path, wayland_display, (char*)NULL); + _exit(127); + } + + close(sv[1]); + const int wayland_fd = recv_fd(sv[0]); + close(sv[0]); + + int status = 0; + waitpid(pid, &status, 0); + + if(wayland_fd < 0) { + fprintf(stderr, "WaylandHostBridge: gsr-wayland-bridge did not return a wayland fd\n"); + return NULL; + } + + struct wl_display *dpy = wl_display_connect_to_fd(wayland_fd); + if(!dpy) { + close(wayland_fd); + fprintf(stderr, "WaylandHostBridge: wl_display_connect_to_fd failed\n"); + return NULL; + } + return dpy; +} + +struct wl_display* wayland_connect_to_host() { + const int inside_flatpak = getenv("FLATPAK_ID") != NULL; + if(inside_flatpak) { + struct wl_display *dpy = connect_via_bridge(); + if(dpy) + return dpy; + fprintf(stderr, "WaylandHostBridge: falling back to sandboxed wl_display_connect\n"); + } + return wl_display_connect(NULL); +} diff --git a/src/window/wayland.c b/src/window/wayland.c index f126aad..f7218b3 100644 --- a/src/window/wayland.c +++ b/src/window/wayland.c @@ -1,4 +1,5 @@ #include "../../include/window/wayland.h" +#include "../../include/wayland_host_bridge.h" #include "../../include/vec2.h" #include "../../include/defs.h" @@ -295,7 +296,7 @@ static void gsr_window_wayland_deinit(gsr_window_wayland *self) { } static bool gsr_window_wayland_init(gsr_window_wayland *self) { - self->display = wl_display_connect(NULL); + self->display = wayland_connect_to_host(); if(!self->display) { fprintf(stderr, "gsr error: gsr_window_wayland_init failed: failed to connect to the Wayland server\n"); goto fail; -- cgit v1.2.3 From 227e44a649d7c19a4b25f071da7fffb1f67e3385 Mon Sep 17 00:00:00 2001 From: dec05eba Date: Tue, 2 Jun 2026 02:38:13 +0200 Subject: 5.13.9 --- meson.build | 2 +- project.conf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build index 14255bc..70b02e5 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('gpu-screen-recorder', ['c', 'cpp'], version : '5.13.8', default_options : ['warning_level=2']) +project('gpu-screen-recorder', ['c', 'cpp'], version : '5.13.9', default_options : ['warning_level=2']) add_project_arguments('-Wshadow', language : ['c', 'cpp']) if get_option('buildtype') == 'debug' diff --git a/project.conf b/project.conf index 59e405c..e6e43eb 100644 --- a/project.conf +++ b/project.conf @@ -1,7 +1,7 @@ [package] name = "gpu-screen-recorder" type = "executable" -version = "5.13.8" +version = "5.13.9" platforms = ["posix"] [config] -- cgit v1.2.3