From b0d41a42c2cd7dbe4080e8624994efb50e454047 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Tue, 30 Jun 2026 14:51:58 +0200 Subject: [PATCH 1/4] fix(linux/pipewire): roll cleanup_stream into destructor as it is never used outside of it --- src/platform/linux/pipewire.cpp | 60 ++++++++++++--------------------- 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/src/platform/linux/pipewire.cpp b/src/platform/linux/pipewire.cpp index 978d5aaf0b3..fe71ff3b2d6 100644 --- a/src/platform/linux/pipewire.cpp +++ b/src/platform/linux/pipewire.cpp @@ -136,34 +136,44 @@ namespace pipewire { ~pipewire_t() { BOOST_LOG(debug) << "[pipewire] Destroying pipewire_t"sv; - try { - cleanup_stream(); - } catch (const std::exception &e) { - BOOST_LOG(error) << "[pipewire] Standard exception caught in ~pipewire_t cleanup_stream: "sv << e.what(); - } catch (...) { - BOOST_LOG(error) << "[pipewire] Unknown exception caught in ~pipewire_t cleanup_stream"sv; - } - pw_thread_loop_lock(loop); + // Lock the frame mutex to stop fill_img + BOOST_LOG(debug) << "[pipewire] Stop fill_img"sv; + { + std::scoped_lock lock(stream_data.frame_mutex); + stream_data.frame_ready = false; + stream_data.current_buffer = nullptr; + } + + // Release pipewire stream + if (stream_data.stream) { + BOOST_LOG(debug) << "[pipewire] Disconnect stream"sv; + pw_stream_disconnect(stream_data.stream); + BOOST_LOG(debug) << "[pipewire] Destroy stream"sv; + pw_stream_destroy(stream_data.stream); + stream_data.stream = nullptr; + } + // Release pipewire core if (core) { BOOST_LOG(debug) << "[pipewire] Disconnect PW core"sv; pw_core_disconnect(core); core = nullptr; } + // Release pipewire context if (context) { BOOST_LOG(debug) << "[pipewire] Destroy PW context"sv; pw_context_destroy(context); context = nullptr; } - - pw_thread_loop_unlock(loop); - + // Release pipewire file descriptor if (fd >= 0) { BOOST_LOG(debug) << "[pipewire] Close pipewire_fd"sv; close(fd); } + // Release pipewire thread loop BOOST_LOG(debug) << "[pipewire] Stop PW thread loop"sv; + pw_thread_loop_unlock(loop); pw_thread_loop_stop(loop); BOOST_LOG(debug) << "[pipewire] Destroy PW thread loop"sv; pw_thread_loop_destroy(loop); @@ -245,34 +255,6 @@ namespace pipewire { return 0; } - /** - * @brief Release the active PipeWire stream and listener. - */ - void cleanup_stream() { - BOOST_LOG(debug) << "[pipewire] Cleaning up stream"sv; - if (loop && stream_data.stream) { - pw_thread_loop_lock(loop); - - // 1. Lock the frame mutex to stop fill_img - BOOST_LOG(debug) << "[pipewire] Stop fill_img"sv; - { - std::scoped_lock lock(stream_data.frame_mutex); - stream_data.frame_ready = false; - stream_data.current_buffer = nullptr; - } - - if (stream_data.stream) { - BOOST_LOG(debug) << "[pipewire] Disconnect stream"sv; - pw_stream_disconnect(stream_data.stream); - BOOST_LOG(debug) << "[pipewire] Destroy stream"sv; - pw_stream_destroy(stream_data.stream); - stream_data.stream = nullptr; - } - - pw_thread_loop_unlock(loop); - } - } - /** * @brief Create the PipeWire stream if it is not already active. * From 6d890a3d79a8b30ea4fcf1a3f00598fe32d49fa2 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Thu, 2 Jul 2026 11:56:01 +0200 Subject: [PATCH 2/4] fix(linux/pipewire): let dummy_img() just return 0 to avoid memory leak This is the same implementation as for kmsgrab/wlgrab Co-Authored-By: Psyke83 --- src/platform/linux/pipewire.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/platform/linux/pipewire.cpp b/src/platform/linux/pipewire.cpp index fe71ff3b2d6..8c8442149fa 100644 --- a/src/platform/linux/pipewire.cpp +++ b/src/platform/linux/pipewire.cpp @@ -1049,12 +1049,7 @@ namespace pipewire { * @return Capture status reported to the streaming pipeline. */ int dummy_img(platf::img_t *img) override { - if (!img) { - return -1; - } - - img->data = new std::uint8_t[img->height * img->row_pitch]; - std::fill_n(img->data, img->height * img->row_pitch, 0); + // Empty images are recognized as dummies by the zero sequence number return 0; } From a71acaf49a7b09ac1817e41bb6b00a6cc4086bd4 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Thu, 2 Jul 2026 12:01:09 +0200 Subject: [PATCH 3/4] fix(linux/pipewire): subclass img_descriptor_t with cleanup destructor Stop memory from leaking on pipewire's images like wlgrab and kmsgrab do. Co-Authored-By: Psyke83 --- src/platform/linux/pipewire.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/platform/linux/pipewire.cpp b/src/platform/linux/pipewire.cpp index 8c8442149fa..1048b8399ca 100644 --- a/src/platform/linux/pipewire.cpp +++ b/src/platform/linux/pipewire.cpp @@ -123,6 +123,18 @@ namespace pipewire { int n_modifiers; ///< Number of entries in `modifiers`. }; + /** + * @brief Pipewire image assembled for encoding. + */ + struct img_descriptor_t: public egl::img_descriptor_t { + ~img_descriptor_t() override { + if (data) { + delete[] data; + data = nullptr; + } + } + }; + /** * @brief PipeWire core, context, and stream setup used for screencast capture. */ @@ -916,7 +928,7 @@ namespace pipewire { */ std::shared_ptr alloc_img() override { // Note: this img_t type is also used for memory buffers - auto img = std::make_shared(); + auto img = std::make_shared(); img->width = width; img->height = height; From 665aef92588a326ebfde720ffdb66c9ebbeb6260 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Mon, 29 Jun 2026 15:19:43 +0200 Subject: [PATCH 4/4] fix(linux/kwin): explicitly cleanup screencast_t allocated memory resources to ensure nothing is leaking --- src/platform/linux/kwingrab.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/platform/linux/kwingrab.cpp b/src/platform/linux/kwingrab.cpp index 6e0ba02f77d..934318713c1 100644 --- a/src/platform/linux/kwingrab.cpp +++ b/src/platform/linux/kwingrab.cpp @@ -248,7 +248,7 @@ namespace kwin { * @brief KWin screencast output name and geometry. */ struct output_parameter_t { - std::string name = ""; ///< KWin output name. + std::string name; ///< KWin output name. int width = 0; ///< Output width in pixels. int height = 0; ///< Output height in pixels. int pos_x = 0; ///< Output X position in the compositor layout. @@ -272,6 +272,7 @@ namespace kwin { screencast_t &operator=(screencast_t &&) = delete; // Do not allow to copying ~screencast_t() { + // Release KDE screencast wayland extensions and reset pointers if (kde_screencast_stream_v1_) { zkde_screencast_stream_unstable_v1_close(kde_screencast_stream_v1_); kde_screencast_stream_v1_ = nullptr; @@ -280,23 +281,30 @@ namespace kwin { zkde_screencast_unstable_v1_destroy(kde_screencast_v1_); kde_screencast_v1_ = nullptr; } - if (kde_output_order) { kde_output_order_v1_destroy(kde_output_order); kde_output_order = nullptr; } + // Clear output order list + output_order.clear(); + // Clear current output parameters + out_params.reset(); + out_params = nullptr; + // wl_output is owned by the registry, released on disconnect - for (const auto &out : outputs | std::views::keys) { - wl_output_destroy(out); + // also cleanup associated output parameters and clear output list when done + for (auto &[output, params] : outputs) { + wl_output_destroy(output); + params.reset(); } outputs.clear(); + // Release wayland registry, display and reset pointers if (wl_registry) { wl_registry_destroy(wl_registry); wl_registry = nullptr; } - if (wl_display) { wl_display_disconnect(wl_display); wl_display = nullptr;