From 9381de992df4eb90d2a041cab428f5b56eef6e91 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Thu, 2 Apr 2026 08:13:22 +0200 Subject: [PATCH 1/5] feat(linux/xdgportal): Prefix all log messages with '[portalgrab]' So the log can easily be filtered for messages related to xdg portalgrab --- src/platform/linux/portalgrab.cpp | 124 +++++++++++++++--------------- 1 file changed, 62 insertions(+), 62 deletions(-) diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index badb4be2936..70262af1965 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -93,7 +93,7 @@ namespace portal { if (file.is_open()) { std::getline(file, *token_); if (!token_->empty()) { - BOOST_LOG(info) << "Loaded portal restore token from disk"sv; + BOOST_LOG(info) << "[portalgrab] Loaded portal restore token from disk"sv; } } } @@ -105,9 +105,9 @@ namespace portal { std::ofstream file(get_file_path()); if (file.is_open()) { file << *token_; - BOOST_LOG(info) << "Saved portal restore token to disk"sv; + BOOST_LOG(info) << "[portalgrab] Saved portal restore token to disk"sv; } else { - BOOST_LOG(warning) << "Failed to save portal restore token"sv; + BOOST_LOG(warning) << "[portalgrab] Failed to save portal restore token"sv; } } @@ -194,15 +194,15 @@ namespace portal { ); if (err) { - BOOST_LOG(warning) << "Failed to explicitly close portal session: "sv << err->message; + BOOST_LOG(warning) << "[portalgrab] Failed to explicitly close portal session: "sv << err->message; } else { - BOOST_LOG(debug) << "Explicitly closed portal session: "sv << session_handle; + BOOST_LOG(debug) << "[portalgrab] Explicitly closed portal session: "sv << session_handle; } } } catch (const std::exception &e) { - BOOST_LOG(error) << "Standard exception caught in ~dbus_t: "sv << e.what(); + BOOST_LOG(error) << "[portalgrab] Standard exception caught in ~dbus_t: "sv << e.what(); } catch (...) { - BOOST_LOG(error) << "Unknown exception caught in ~dbus_t"sv; + BOOST_LOG(error) << "[portalgrab] Unknown exception caught in ~dbus_t"sv; } if (screencast_proxy) { @@ -237,11 +237,11 @@ namespace portal { void finalize_portal_security() { #if !defined(__FreeBSD__) - BOOST_LOG(debug) << "Finalizing Portal security: dropping capabilities and resetting dumpable"sv; + BOOST_LOG(debug) << "[portalgrab] Finalizing Portal security: dropping capabilities and resetting dumpable"sv; cap_t caps = cap_get_proc(); if (!caps) { - BOOST_LOG(error) << "Failed to get process capabilities"sv; + BOOST_LOG(error) << "[portalgrab] Failed to get process capabilities"sv; return; } @@ -252,14 +252,14 @@ namespace portal { cap_set_flag(caps, CAP_PERMITTED, permitted_list.size(), permitted_list.data(), CAP_CLEAR); if (cap_set_proc(caps) != 0) { - BOOST_LOG(error) << "Failed to prune capabilities: "sv << std::strerror(errno); + BOOST_LOG(error) << "[portalgrab] Failed to prune capabilities: "sv << std::strerror(errno); } cap_free(caps); // Reset dumpable AFTER the caps have been pruned to ensure the Portal can // access /proc/pid/root. if (prctl(PR_SET_DUMPABLE, 1) != 0) { - BOOST_LOG(error) << "Failed to set PR_SET_DUMPABLE: "sv << std::strerror(errno); + BOOST_LOG(error) << "[portalgrab] Failed to set PR_SET_DUMPABLE: "sv << std::strerror(errno); } #endif } @@ -300,14 +300,14 @@ namespace portal { } if (select_remote_desktop_devices(loop, *session_path) < 0) { - BOOST_LOG(warning) << "RemoteDesktop.SelectDevices failed, falling back to ScreenCast-only mode"sv; + BOOST_LOG(warning) << "[portalgrab] RemoteDesktop.SelectDevices failed, falling back to ScreenCast-only mode"sv; g_free(*session_path); *session_path = nullptr; return false; } if (select_screencast_sources(loop, *session_path, false) < 0) { - BOOST_LOG(warning) << "ScreenCast.SelectSources failed with RemoteDesktop session, trying ScreenCast-only mode"sv; + BOOST_LOG(warning) << "[portalgrab] ScreenCast.SelectSources failed with RemoteDesktop session, trying ScreenCast-only mode"sv; g_free(*session_path); *session_path = nullptr; return false; @@ -363,7 +363,7 @@ namespace portal { g_autoptr(GVariant) reply = g_dbus_proxy_call_sync(proxy, "CreateSession", g_variant_builder_end(&builder), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err); if (err) { - BOOST_LOG(error) << "Could not create "sv << session_type << " session: "sv << err->message; + BOOST_LOG(error) << "[portalgrab] Could not create "sv << session_type << " session: "sv << err->message; return -1; } @@ -374,7 +374,7 @@ namespace portal { g_autoptr(GVariant) create_response = dbus_response_wait(&response); if (!create_response) { - BOOST_LOG(error) << session_type << " CreateSession: no response received"sv; + BOOST_LOG(error) << "[portalgrab] " << session_type << " CreateSession: no response received"sv; return -1; } @@ -382,16 +382,16 @@ namespace portal { g_autoptr(GVariant) results = nullptr; g_variant_get(create_response, "(u@a{sv})", &response_code, &results); - BOOST_LOG(debug) << session_type << " CreateSession response_code: "sv << response_code; + BOOST_LOG(debug) << "[portalgrab] " << session_type << " CreateSession response_code: "sv << response_code; if (response_code != 0) { - BOOST_LOG(error) << session_type << " CreateSession failed with response code: "sv << response_code; + BOOST_LOG(error) << "[portalgrab] " << session_type << " CreateSession failed with response code: "sv << response_code; return -1; } g_autoptr(GVariant) session_handle_v = g_variant_lookup_value(results, "session_handle", nullptr); if (!session_handle_v) { - BOOST_LOG(error) << session_type << " CreateSession: session_handle not found in response"sv; + BOOST_LOG(error) << "[portalgrab] " << session_type << " CreateSession: session_handle not found in response"sv; return -1; } @@ -402,7 +402,7 @@ namespace portal { *session_path_out = g_strdup(g_variant_get_string(session_handle_v, nullptr)); } - BOOST_LOG(debug) << session_type << " CreateSession: got session handle: "sv << *session_path_out; + BOOST_LOG(debug) << "[portalgrab] " << session_type << " CreateSession: got session handle: "sv << *session_path_out; // Save it for the destructor to use during cleanup this->session_handle = *session_path_out; return 0; @@ -431,7 +431,7 @@ namespace portal { g_autoptr(GVariant) reply = g_dbus_proxy_call_sync(remote_desktop_proxy, "SelectDevices", g_variant_builder_end(&builder), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err); if (err) { - BOOST_LOG(error) << "Could not select devices: "sv << err->message; + BOOST_LOG(error) << "[portalgrab] Could not select devices: "sv << err->message; return -1; } @@ -442,16 +442,16 @@ namespace portal { g_autoptr(GVariant) devices_response = dbus_response_wait(&response); if (!devices_response) { - BOOST_LOG(error) << "SelectDevices: no response received"sv; + BOOST_LOG(error) << "[portalgrab] SelectDevices: no response received"sv; return -1; } guint32 response_code; g_variant_get(devices_response, "(u@a{sv})", &response_code, nullptr); - BOOST_LOG(debug) << "SelectDevices response_code: "sv << response_code; + BOOST_LOG(debug) << "[portalgrab] SelectDevices response_code: "sv << response_code; if (response_code != 0) { - BOOST_LOG(error) << "SelectDevices failed with response code: "sv << response_code; + BOOST_LOG(error) << "[portalgrab] SelectDevices failed with response code: "sv << response_code; return -1; } @@ -483,7 +483,7 @@ namespace portal { g_autoptr(GError) err = nullptr; g_autoptr(GVariant) reply = g_dbus_proxy_call_sync(screencast_proxy, "SelectSources", g_variant_builder_end(&builder), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err); if (err) { - BOOST_LOG(error) << "Could not select sources: "sv << err->message; + BOOST_LOG(error) << "[portalgrab] Could not select sources: "sv << err->message; return -1; } @@ -494,16 +494,16 @@ namespace portal { g_autoptr(GVariant) sources_response = dbus_response_wait(&response); if (!sources_response) { - BOOST_LOG(error) << "SelectSources: no response received"sv; + BOOST_LOG(error) << "[portalgrab] SelectSources: no response received"sv; return -1; } guint32 response_code; g_variant_get(sources_response, "(u@a{sv})", &response_code, nullptr); - BOOST_LOG(debug) << "SelectSources response_code: "sv << response_code; + BOOST_LOG(debug) << "[portalgrab] SelectSources response_code: "sv << response_code; if (response_code != 0) { - BOOST_LOG(error) << "SelectSources failed with response code: "sv << response_code; + BOOST_LOG(error) << "[portalgrab] SelectSources failed with response code: "sv << response_code; return -1; } @@ -531,7 +531,7 @@ namespace portal { g_autoptr(GError) err = nullptr; g_autoptr(GVariant) reply = g_dbus_proxy_call_sync(proxy, "Start", g_variant_builder_end(&builder), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err); if (err) { - BOOST_LOG(error) << "Could not start "sv << session_type << " session: "sv << err->message; + BOOST_LOG(error) << "[portalgrab] Could not start "sv << session_type << " session: "sv << err->message; return -1; } @@ -542,7 +542,7 @@ namespace portal { g_autoptr(GVariant) start_response = dbus_response_wait(&response); if (!start_response) { - BOOST_LOG(error) << session_type << " Start: no response received"sv; + BOOST_LOG(error) << "[portalgrab] " << session_type << " Start: no response received"sv; return -1; } @@ -551,16 +551,16 @@ namespace portal { g_autoptr(GVariant) streams = nullptr; g_variant_get(start_response, "(u@a{sv})", &response_code, &dict); - BOOST_LOG(debug) << session_type << " Start response_code: "sv << response_code; + BOOST_LOG(debug) << "[portalgrab] " << session_type << " Start response_code: "sv << response_code; if (response_code != 0) { - BOOST_LOG(error) << session_type << " Start failed with response code: "sv << response_code; + BOOST_LOG(error) << "[portalgrab] " << session_type << " Start failed with response code: "sv << response_code; return -1; } streams = g_variant_lookup_value(dict, "streams", G_VARIANT_TYPE("a(ua{sv})")); if (!streams) { - BOOST_LOG(error) << session_type << " Start: no streams in response"sv; + BOOST_LOG(error) << "[portalgrab] " << session_type << " Start: no streams in response"sv; return -1; } @@ -586,7 +586,7 @@ namespace portal { g_autoptr(GError) err = nullptr; g_autoptr(GVariant) reply = g_dbus_proxy_call_with_unix_fd_list_sync(screencast_proxy, "OpenPipeWireRemote", msg, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &fd_list, nullptr, &err); if (err) { - BOOST_LOG(error) << "Could not open pipewire remote: "sv << err->message; + BOOST_LOG(error) << "[portalgrab] Could not open pipewire remote: "sv << err->message; return -1; } @@ -678,7 +678,7 @@ namespace portal { pipewire_node = pipewire_node_; width = width_; height = height_; - BOOST_LOG(debug) << "Reusing cached portal session"sv; + BOOST_LOG(debug) << "[portalgrab] Reusing cached portal session"sv; return 0; } @@ -705,7 +705,7 @@ namespace portal { width = width_; height = height_; - BOOST_LOG(debug) << "Created new portal session (cached)"sv; + BOOST_LOG(debug) << "[portalgrab] Created new portal session (cached)"sv; return 0; } @@ -718,7 +718,7 @@ namespace portal { try { std::scoped_lock lock(mutex_); if (valid_) { - BOOST_LOG(debug) << "Invalidating cached portal session"sv; + BOOST_LOG(debug) << "[portalgrab] Invalidating cached portal session"sv; if (pipewire_fd_ >= 0) { close(pipewire_fd_); pipewire_fd_ = -1; @@ -729,9 +729,9 @@ namespace portal { valid_ = false; } } catch (const std::exception &e) { - BOOST_LOG(error) << "Exception during session invalidation: "sv << e.what(); + BOOST_LOG(error) << "[portalgrab] Exception during session invalidation: "sv << e.what(); } catch (...) { - BOOST_LOG(error) << "Unknown error during session invalidation"sv; + BOOST_LOG(error) << "[portalgrab] Unknown error during session invalidation"sv; } } @@ -1027,11 +1027,11 @@ namespace portal { } static void on_core_info_cb([[maybe_unused]] void *user_data, const struct pw_core_info *pw_info) { - BOOST_LOG(info) << "Connected to pipewire version "sv << pw_info->version; + BOOST_LOG(info) << "[portalgrab] Connected to pipewire version "sv << pw_info->version; } static void on_core_error_cb([[maybe_unused]] void *user_data, const uint32_t id, const int seq, [[maybe_unused]] int res, const char *message) { - BOOST_LOG(info) << "Pipewire Error, id:"sv << id << " seq:"sv << seq << " message: "sv << message; + BOOST_LOG(info) << "[portalgrab] Pipewire Error, id:"sv << id << " seq:"sv << seq << " message: "sv << message; } constexpr static const struct pw_core_events core_events = { @@ -1041,7 +1041,7 @@ namespace portal { }; static void on_stream_state_changed(void *user_data, enum pw_stream_state old, enum pw_stream_state state, const char *err_msg) { - BOOST_LOG(debug) << "PipeWire stream state: " << pw_stream_state_as_string(old) + BOOST_LOG(debug) << "[portalgrab] PipeWire stream state: " << pw_stream_state_as_string(old) << " -> " << pw_stream_state_as_string(state); auto *d = static_cast(user_data); @@ -1060,7 +1060,7 @@ namespace portal { break; case PW_STREAM_STATE_ERROR: if (old != PW_STREAM_STATE_STREAMING && !session_cache_t::instance().is_maxframerate_failed()) { - BOOST_LOG(warning) << "Negotiation failed, will retry without maxFramerate"sv; + BOOST_LOG(warning) << "[portalgrab] Negotiation failed, will retry without maxFramerate"sv; session_cache_t::instance().set_maxframerate_failed(); } [[fallthrough]]; @@ -1145,13 +1145,13 @@ namespace portal { return; } - BOOST_LOG(info) << "Video format: "sv << d->format.info.raw.format; - BOOST_LOG(info) << "Size: "sv << d->format.info.raw.size.width << "x"sv << d->format.info.raw.size.height; + BOOST_LOG(info) << "[portalgrab] Video format: "sv << d->format.info.raw.format; + BOOST_LOG(info) << "[portalgrab] Size: "sv << d->format.info.raw.size.width << "x"sv << d->format.info.raw.size.height; if (d->format.info.raw.max_framerate.num == 0 && d->format.info.raw.max_framerate.denom == 1) { - BOOST_LOG(info) << "Framerate (from compositor): 0/1 (variable rate capture)"; + BOOST_LOG(info) << "[portalgrab] Framerate (from compositor): 0/1 (variable rate capture)"; } else { - BOOST_LOG(info) << "Framerate (from compositor): "sv << d->format.info.raw.framerate.num << "/"sv << d->format.info.raw.framerate.denom; - BOOST_LOG(info) << "Framerate (from compositor, max): "sv << d->format.info.raw.max_framerate.num << "/"sv << d->format.info.raw.max_framerate.denom; + BOOST_LOG(info) << "[portalgrab] Framerate (from compositor): "sv << d->format.info.raw.framerate.num << "/"sv << d->format.info.raw.framerate.denom; + BOOST_LOG(info) << "[portalgrab] Framerate (from compositor, max): "sv << d->format.info.raw.max_framerate.num << "/"sv << d->format.info.raw.max_framerate.denom; } int physical_w = d->format.info.raw.size.width; @@ -1180,10 +1180,10 @@ namespace portal { uint32_t buffer_types = 0; if (spa_pod_find_prop(param, nullptr, SPA_FORMAT_VIDEO_modifier) != nullptr && d->drm_format) { - BOOST_LOG(info) << "using DMA-BUF buffers"sv; + BOOST_LOG(info) << "[portalgrab] using DMA-BUF buffers"sv; buffer_types |= 1 << SPA_DATA_DmaBuf; } else { - BOOST_LOG(info) << "using memory buffers"sv; + BOOST_LOG(info) << "[portalgrab] using memory buffers"sv; buffer_types |= 1 << SPA_DATA_MemPtr; } @@ -1224,10 +1224,10 @@ namespace portal { delay = std::chrono::nanoseconds( (static_cast(fps_strict.den) * 1'000'000'000LL) / fps_strict.num ); - BOOST_LOG(info) << "Requested frame rate [" << fps_strict.num << "/" << fps_strict.den << ", approx. " << av_q2d(fps_strict) << " fps]"; + BOOST_LOG(info) << "[portalgrab] Requested frame rate [" << fps_strict.num << "/" << fps_strict.den << ", approx. " << av_q2d(fps_strict) << " fps]"; } else { delay = std::chrono::nanoseconds {1s} / framerate; - BOOST_LOG(info) << "Requested frame rate [" << framerate << "fps]"; + BOOST_LOG(info) << "[portalgrab] Requested frame rate [" << framerate << "fps]"; } mem_type = hwdevice_type; @@ -1285,7 +1285,7 @@ namespace portal { } if (negotiated_w > 0 && negotiated_h > 0 && (negotiated_w != width || negotiated_h != height)) { - BOOST_LOG(info) << "Using negotiated resolution "sv + BOOST_LOG(info) << "[portalgrab] Using negotiated resolution "sv << negotiated_w << "x" << negotiated_h; width = negotiated_w; @@ -1361,7 +1361,7 @@ namespace portal { if (stream_stopped.load() || shared_state->stream_dead.exchange(false)) { // If stream is marked as stopped, clear state and send interrupted status if (stream_stopped.load()) { - BOOST_LOG(warning) << "PipeWire stream stopped by user."sv; + BOOST_LOG(warning) << "[portalgrab] PipeWire stream stopped by user."sv; capture_running.store(false); stream_stopped.store(false); previous_height.store(0); @@ -1369,7 +1369,7 @@ namespace portal { pipewire.frame_cv().notify_all(); return platf::capture_e::error; } else { - BOOST_LOG(warning) << "PipeWire stream disconnected. Forcing session reset."sv; + BOOST_LOG(warning) << "[portalgrab] PipeWire stream disconnected. Forcing session reset."sv; return platf::capture_e::reinit; } } @@ -1400,7 +1400,7 @@ namespace portal { case platf::capture_e::timeout: if (!pull_free_image_cb(img_out)) { // Detect if shutdown is pending - BOOST_LOG(debug) << "PipeWire: timeout -> interrupt nudge"; + BOOST_LOG(debug) << "[portalgrab] PipeWire: timeout -> interrupt nudge"; capture_running.store(false); stream_stopped.store(false); previous_height.store(0); @@ -1414,7 +1414,7 @@ namespace portal { push_captured_image_cb(std::move(img_out), true); break; default: - BOOST_LOG(error) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']'; + BOOST_LOG(error) << "[portalgrab] Unrecognized capture status ["sv << std::to_underlying(status) << ']'; return status; } } @@ -1487,7 +1487,7 @@ namespace portal { img->sequence = ++sequence; if (retries > 0) { - BOOST_LOG(debug) << "Processed frame after " << retries << " redundant events."sv; + BOOST_LOG(debug) << "[portalgrab] Processed frame after " << retries << " redundant events."sv; } } @@ -1523,7 +1523,7 @@ namespace portal { eglQueryDmaBufFormatsEXT(egl_display, MAX_DMABUF_FORMATS, dmabuf_formats.data(), &num_dmabuf_formats); if (num_dmabuf_formats > MAX_DMABUF_FORMATS) { - BOOST_LOG(warning) << "Some DMA-BUF formats are being ignored"sv; + BOOST_LOG(warning) << "[portalgrab] Some DMA-BUF formats are being ignored"sv; } for (EGLint i = 0; i < MIN(num_dmabuf_formats, MAX_DMABUF_FORMATS); i++) { @@ -1537,7 +1537,7 @@ namespace portal { eglQueryDmaBufModifiersEXT(egl_display, dmabuf_formats[i], MAX_DMABUF_MODIFIERS, mods.data(), nullptr, &num_modifiers); if (num_modifiers > MAX_DMABUF_MODIFIERS) { - BOOST_LOG(warning) << "Some DMA-BUF modifiers are being ignored"sv; + BOOST_LOG(warning) << "[portalgrab] Some DMA-BUF modifiers are being ignored"sv; } dmabuf_infos[n_dmabuf_infos].format = pw_format; @@ -1577,13 +1577,13 @@ namespace portal { bool intel_present = check_intel("/sys/class/drm/card0/device/vendor") || check_intel("/sys/class/drm/card1/device/vendor"); if (intel_present) { - BOOST_LOG(info) << "Hybrid GPU system detected (Intel + discrete) - CUDA will use memory buffers"sv; + BOOST_LOG(info) << "[portalgrab] Hybrid GPU system detected (Intel + discrete) - CUDA will use memory buffers"sv; display_is_nvidia = false; } else { // No Intel GPU found, check if NVIDIA is present const char *vendor = eglQueryString(egl_display.get(), EGL_VENDOR); if (vendor && std::string_view(vendor).contains("NVIDIA")) { - BOOST_LOG(info) << "Pure NVIDIA system - DMA-BUF will be enabled for CUDA"sv; + BOOST_LOG(info) << "[portalgrab] Pure NVIDIA system - DMA-BUF will be enabled for CUDA"sv; display_is_nvidia = true; } } @@ -1619,7 +1619,7 @@ namespace platf { std::shared_ptr portal_display(mem_type_e hwdevice_type, const std::string &display_name, const video::config_t &config) { using enum platf::mem_type_e; if (hwdevice_type != system && hwdevice_type != vaapi && hwdevice_type != cuda && hwdevice_type != vulkan) { - BOOST_LOG(error) << "Could not initialize display with the given hw device type."sv; + BOOST_LOG(error) << "[portalgrab] Could not initialize display with the given hw device type."sv; return nullptr; } From e69a508d7d4fa7deaf38d5552ad438ad2523be42 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Wed, 8 Apr 2026 19:22:08 +0200 Subject: [PATCH 2/5] feat(linux/xdgportal): Detect if portal session was stopped directly using DBus. This improves stopping the stream drastically as it does no longer re-init before erroring out. This commit also removes all of the old stream_stopped logic in favor of just erroring out on stream_dead if the portal session is closed. --- src/platform/linux/portalgrab.cpp | 88 ++++++++++++++++--------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index 70262af1965..aa945b8baad 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -331,6 +331,33 @@ namespace portal { return 0; } + bool is_session_closed() const { + if (conn && !session_handle.empty()) { + // Try to retrieve property org.freedesktop.portal.Session::version + g_autoptr(GError) err = nullptr; + g_dbus_connection_call_sync( + conn, + "org.freedesktop.portal.Desktop", + session_handle.c_str(), + "org.freedesktop.DBus.Properties", + "Get", + g_variant_new("(ss)", "org.freedesktop.portal.Session", "version"), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &err + ); + // If we cannot get the property then the session portal was closed. + if (err) { + BOOST_LOG(debug) << "[portalgrab] Session closed as check failed: "sv << err->message; + return true; + } + } + // The session is not closed (or might not have been opened yet). + return false; + } + int pipewire_fd; int pipewire_node; int width; @@ -709,6 +736,10 @@ namespace portal { return 0; } + bool is_session_closed() const { + return dbus_ && dbus_->is_session_closed(); + } + /** * @brief Invalidate the cached session. * @@ -1270,20 +1301,6 @@ namespace portal { timeout_ms -= 10; } - // Check previous logical dimensions - if (previous_width.load() == width && previous_height.load() == height) { - if (capture_running.load()) { - { - std::scoped_lock lock(pipewire.frame_mutex()); - stream_stopped.store(true); - } - pipewire.frame_cv().notify_all(); - } - } else { - previous_width.store(width); - previous_height.store(height); - } - if (negotiated_w > 0 && negotiated_h > 0 && (negotiated_w != width || negotiated_h != height)) { BOOST_LOG(info) << "[portalgrab] Using negotiated resolution "sv << negotiated_w << "x" << negotiated_h; @@ -1309,7 +1326,7 @@ namespace portal { while (std::chrono::steady_clock::now() < deadline) { if (!wait_for_frame(deadline)) { - return stream_stopped.load() ? platf::capture_e::interrupted : platf::capture_e::timeout; + return platf::capture_e::timeout; } if (!pull_free_image_cb(img_out)) { @@ -1354,24 +1371,19 @@ namespace portal { pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia); sleep_overshoot_logger.reset(); - capture_running.store(true); while (true) { - // Check if PipeWire signaled a state change or error - if (stream_stopped.load() || shared_state->stream_dead.exchange(false)) { - // If stream is marked as stopped, clear state and send interrupted status - if (stream_stopped.load()) { - BOOST_LOG(warning) << "[portalgrab] PipeWire stream stopped by user."sv; - capture_running.store(false); - stream_stopped.store(false); - previous_height.store(0); - previous_width.store(0); + // Check if PipeWire signaled a dead stream + if (shared_state->stream_dead.exchange(false)) { + // If the pipewire stream stopped due to closed portal session stop the capture with an error + if (session_cache_t::instance().is_session_closed()) { + BOOST_LOG(warning) << "[portalgrab] PipeWire stream stopped by closed portal session."sv; pipewire.frame_cv().notify_all(); return platf::capture_e::error; - } else { - BOOST_LOG(warning) << "[portalgrab] PipeWire stream disconnected. Forcing session reset."sv; - return platf::capture_e::reinit; } + // Re-init the capture if the stream is dead for any other reason + BOOST_LOG(warning) << "[portalgrab] PipeWire stream disconnected. Forcing session reset."sv; + return platf::capture_e::reinit; } // Advance to (or catch up with) next delay interval @@ -1391,20 +1403,12 @@ namespace portal { case platf::capture_e::reinit: case platf::capture_e::error: case platf::capture_e::interrupted: - capture_running.store(false); - stream_stopped.store(false); - previous_height.store(0); - previous_width.store(0); pipewire.frame_cv().notify_all(); return status; case platf::capture_e::timeout: if (!pull_free_image_cb(img_out)) { // Detect if shutdown is pending BOOST_LOG(debug) << "[portalgrab] PipeWire: timeout -> interrupt nudge"; - capture_running.store(false); - stream_stopped.store(false); - previous_height.store(0); - previous_width.store(0); pipewire.frame_cv().notify_all(); return platf::capture_e::interrupted; } @@ -1495,10 +1499,10 @@ namespace portal { std::unique_lock lock(pipewire.frame_mutex()); bool success = pipewire.frame_cv().wait_until(lock, deadline, [&] { - return pipewire.is_frame_ready() || stream_stopped.load() || shared_state->stream_dead.load(); + return pipewire.is_frame_ready() || shared_state->stream_dead.load(); }); - if (success && !stream_stopped.load()) { + if (success) { pipewire.set_frame_ready(false); return true; } @@ -1607,10 +1611,6 @@ namespace portal { std::optional last_seq {}; std::uint64_t sequence {}; uint32_t framerate; - static inline std::atomic previous_height {0}; - static inline std::atomic previous_width {0}; - static inline std::atomic stream_stopped {false}; - static inline std::atomic capture_running {false}; std::shared_ptr shared_state; }; } // namespace portal @@ -1623,6 +1623,10 @@ namespace platf { return nullptr; } + if (portal::session_cache_t::instance().is_session_closed()) { + portal::session_cache_t::instance().invalidate(); + } + auto portal = std::make_shared(); if (portal->init(hwdevice_type, display_name, config)) { return nullptr; From ff7b17437f59b10a40cd4adb1e4f81daaca21a1b Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Wed, 8 Apr 2026 19:29:36 +0200 Subject: [PATCH 3/5] fix(linux/xdgportal): Remove portal session caching to avoid persistent XDG notification When the session_cache is working properly the XDG notification for a running session is always active while Sunshine is running. To avoid this and also reduce complexity of the portalgrab logic remove the session_cache handling for the portal session itself but keep it intact for other uses (like tracking maxFramerateFailed). Removing caching fully is possible after implementing the Portal.Session::Closed signal first. Note: Sunshine already ensures that there is just one running Display (therefore Portalsession) at a time. --- src/platform/linux/portalgrab.cpp | 116 +++--------------------------- 1 file changed, 11 insertions(+), 105 deletions(-) diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index aa945b8baad..f65097124b2 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -679,93 +679,13 @@ namespace portal { }; /** - * @brief Singleton cache for portal session data. + * @brief Singleton cache for persistent portalgrab session data. * - * This prevents creating multiple portal sessions during encoder probing, - * which would show multiple screen recording indicators in the system tray. */ class session_cache_t { public: static session_cache_t &instance(); - /** - * @brief Get or create a portal session. - * - * If a cached session exists and is valid, returns the cached data. - * Otherwise, creates a new session and caches it. - * - * @return 0 on success, -1 on failure - */ - int get_or_create_session(int &pipewire_fd, int &pipewire_node, int &width, int &height) { - std::scoped_lock lock(mutex_); - - if (valid_) { - // Return cached session data - pipewire_fd = dup(pipewire_fd_); // Duplicate FD for each caller - pipewire_node = pipewire_node_; - width = width_; - height = height_; - BOOST_LOG(debug) << "[portalgrab] Reusing cached portal session"sv; - return 0; - } - - // Create new session - dbus_ = std::make_unique(); - if (dbus_->init() < 0) { - return -1; - } - if (dbus_->connect_to_portal() < 0) { - dbus_.reset(); - return -1; - } - - // Cache the session data - pipewire_fd_ = dbus_->pipewire_fd; - pipewire_node_ = dbus_->pipewire_node; - width_ = dbus_->width; - height_ = dbus_->height; - valid_ = true; - - // Return to caller (duplicate FD so each caller has their own) - pipewire_fd = dup(pipewire_fd_); - pipewire_node = pipewire_node_; - width = width_; - height = height_; - - BOOST_LOG(debug) << "[portalgrab] Created new portal session (cached)"sv; - return 0; - } - - bool is_session_closed() const { - return dbus_ && dbus_->is_session_closed(); - } - - /** - * @brief Invalidate the cached session. - * - * Call this when the session becomes invalid (e.g., on error). - */ - void invalidate() noexcept { - try { - std::scoped_lock lock(mutex_); - if (valid_) { - BOOST_LOG(debug) << "[portalgrab] Invalidating cached portal session"sv; - if (pipewire_fd_ >= 0) { - close(pipewire_fd_); - pipewire_fd_ = -1; - } - - dbus_.reset(); - - valid_ = false; - } - } catch (const std::exception &e) { - BOOST_LOG(error) << "[portalgrab] Exception during session invalidation: "sv << e.what(); - } catch (...) { - BOOST_LOG(error) << "[portalgrab] Unknown error during session invalidation"sv; - } - } - bool is_maxframerate_failed() const { return maxframerate_failed_; } @@ -777,23 +697,10 @@ namespace portal { private: session_cache_t() = default; - ~session_cache_t() { - if (pipewire_fd_ >= 0) { - close(pipewire_fd_); - } - } - // Prevent copying session_cache_t(const session_cache_t &) = delete; session_cache_t &operator=(const session_cache_t &) = delete; - std::mutex mutex_; - std::unique_ptr dbus_; - int pipewire_fd_ = -1; - int pipewire_node_ = 0; - int width_ = 0; - int height_ = 0; - bool valid_ = false; bool maxframerate_failed_ = false; }; @@ -887,7 +794,6 @@ namespace portal { pw_thread_loop_unlock(loop); } - session_cache_t::instance().invalidate(); } void ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) { @@ -1266,10 +1172,13 @@ namespace portal { return -1; } - // Use cached portal session to avoid creating multiple screen recordings - int pipewire_fd = -1; - int pipewire_node = 0; - if (session_cache_t::instance().get_or_create_session(pipewire_fd, pipewire_node, width, height) < 0) { + // Connect DBus portal session + if (dbus.init() < 0) { + BOOST_LOG(error) << "[portalgrab] Failed to connect to dbus. portal_t::init() failed."; + return -1; + } + if (dbus.connect_to_portal() < 0) { + BOOST_LOG(error) << "[portalgrab] Failed to connect to portal. portal_t::init() failed."; return -1; } @@ -1282,7 +1191,7 @@ namespace portal { shared_state->negotiated_width.store(0); shared_state->negotiated_height.store(0); } - pipewire.init(pipewire_fd, pipewire_node, shared_state); + pipewire.init(dbus.pipewire_fd, dbus.pipewire_node, shared_state); // Start PipeWire now so format negotiation can proceed before capture start pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia); @@ -1376,7 +1285,7 @@ namespace portal { // Check if PipeWire signaled a dead stream if (shared_state->stream_dead.exchange(false)) { // If the pipewire stream stopped due to closed portal session stop the capture with an error - if (session_cache_t::instance().is_session_closed()) { + if (dbus.is_session_closed()) { BOOST_LOG(warning) << "[portalgrab] PipeWire stream stopped by closed portal session."sv; pipewire.frame_cv().notify_all(); return platf::capture_e::error; @@ -1602,6 +1511,7 @@ namespace portal { platf::mem_type_e mem_type; wl::display_t wl_display; + dbus_t dbus; pipewire_t pipewire; std::array dmabuf_infos; int n_dmabuf_infos; @@ -1623,10 +1533,6 @@ namespace platf { return nullptr; } - if (portal::session_cache_t::instance().is_session_closed()) { - portal::session_cache_t::instance().invalidate(); - } - auto portal = std::make_shared(); if (portal->init(hwdevice_type, display_name, config)) { return nullptr; From 98d5a6a82919075eaf2752407f97dda7b1c42f09 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Wed, 8 Apr 2026 19:37:54 +0200 Subject: [PATCH 4/5] fix(linux/xdgportal): Improve pipewire handling * Add additional debug logging * Ensure pipewire_fd's are not needlessly duplicated and closed at the correct point. * Add minimal error handling for pipewire context/core to avoid obvious segfaults * (From Sonarqube:) Add exception handler for cleanup_stream in destructor --- src/platform/linux/portalgrab.cpp | 67 ++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index f65097124b2..b31469e92a8 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -205,6 +205,9 @@ namespace portal { BOOST_LOG(error) << "[portalgrab] Unknown exception caught in ~dbus_t"sv; } + if (pipewire_fd >= 0) { + close(pipewire_fd); + } if (screencast_proxy) { g_clear_object(&screencast_proxy); } @@ -714,22 +717,33 @@ namespace portal { public: pipewire_t(): loop(pw_thread_loop_new("Pipewire thread", nullptr)) { + BOOST_LOG(debug) << "[portalgrab] Start PW thread loop"sv; pw_thread_loop_start(loop); } ~pipewire_t() { + BOOST_LOG(debug) << "[portalgrab] Destroying pipewire_t"sv; if (loop) { + BOOST_LOG(debug) << "[portalgrab] Stop PW thread loop"sv; pw_thread_loop_stop(loop); } - cleanup_stream(); + try { + cleanup_stream(); + } catch (const std::exception &e) { + BOOST_LOG(error) << "[portalgrab] Standard exception caught in ~pipewire_t: "sv << e.what(); + } catch (...) { + BOOST_LOG(error) << "[portalgrab] Unknown exception caught in ~pipewire_t"sv; + } pw_thread_loop_lock(loop); if (core) { + BOOST_LOG(debug) << "[portalgrab] Disconnect PW core"sv; pw_core_disconnect(core); core = nullptr; } if (context) { + BOOST_LOG(debug) << "[portalgrab] Destroy PW context"sv; pw_context_destroy(context); context = nullptr; } @@ -737,8 +751,12 @@ namespace portal { pw_thread_loop_unlock(loop); if (fd >= 0) { + BOOST_LOG(debug) << "[portalgrab] Close pipewire_fd"sv; close(fd); } + BOOST_LOG(debug) << "[portalgrab] Stop PW thread loop"sv; + pw_thread_loop_stop(loop); + BOOST_LOG(debug) << "[portalgrab] Destroy PW thread loop"sv; pw_thread_loop_destroy(loop); } @@ -758,29 +776,39 @@ namespace portal { stream_data.frame_ready = ready; } - void init(int stream_fd, int stream_node, std::shared_ptr shared_state) { + int init(int stream_fd, int stream_node, std::shared_ptr shared_state) { fd = stream_fd; node = stream_node; stream_data.shared = std::move(shared_state); pw_thread_loop_lock(loop); - + BOOST_LOG(debug) << "[portalgrab] Setup PW context"sv; context = pw_context_new(pw_thread_loop_get_loop(loop), nullptr, 0); if (context) { - core = pw_context_connect_fd(context, dup(fd), nullptr, 0); + BOOST_LOG(debug) << "[portalgrab] Connect PW context to fd"sv; + core = pw_context_connect_fd(context, fd, nullptr, 0); if (core) { pw_core_add_listener(core, &core_listener, &core_events, nullptr); + } else { + BOOST_LOG(debug) << "[portalgrab] Failed to connect to PW core. Error: "sv << errno << "(" << strerror(errno) << ")"sv; + return -1; } + } else { + BOOST_LOG(debug) << "[portalgrab] Failed to setup PW context. Error: "sv << errno << "(" << strerror(errno) << ")"sv; + return -1; } pw_thread_loop_unlock(loop); + return 0; } void cleanup_stream() { + BOOST_LOG(debug) << "[portalgrab] 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) << "[portalgrab] Stop fill_img"sv; { std::scoped_lock lock(stream_data.frame_mutex); stream_data.frame_ready = false; @@ -788,6 +816,9 @@ namespace portal { } if (stream_data.stream) { + BOOST_LOG(debug) << "[portalgrab] Disconnect stream"sv; + pw_stream_disconnect(stream_data.stream); + BOOST_LOG(debug) << "[portalgrab] Destroy stream"sv; pw_stream_destroy(stream_data.stream); stream_data.stream = nullptr; } @@ -796,11 +827,18 @@ namespace portal { } } - void ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) { + int ensure_stream(const platf::mem_type_e mem_type, const uint32_t width, const uint32_t height, const uint32_t refresh_rate, const struct dmabuf_format_info_t *dmabuf_infos, const int n_dmabuf_infos, const bool display_is_nvidia) { pw_thread_loop_lock(loop); if (!stream_data.stream) { + if (!core) { + BOOST_LOG(debug) << "[portalgrab] PW core not available. Cannot ensure stream."sv; + pw_thread_loop_unlock(loop); + return -1; + } + struct pw_properties *props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Screen", nullptr); + BOOST_LOG(debug) << "[portalgrab] Create PW stream"sv; stream_data.stream = pw_stream_new(core, "Sunshine Video Capture", props); pw_stream_add_listener(stream_data.stream, &stream_data.stream_listener, &stream_events, &stream_data); @@ -834,10 +872,11 @@ namespace portal { params[n_params] = format_param; n_params++; } - + BOOST_LOG(debug) << "[portalgrab] Connect PW stream - fd "sv << fd << " node "sv << node; pw_stream_connect(stream_data.stream, PW_DIRECTION_INPUT, node, (enum pw_stream_flags)(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS), params.data(), n_params); } pw_thread_loop_unlock(loop); + return 0; } static void close_img_fds(egl::img_descriptor_t *img_descriptor) { @@ -1191,10 +1230,16 @@ namespace portal { shared_state->negotiated_width.store(0); shared_state->negotiated_height.store(0); } - pipewire.init(dbus.pipewire_fd, dbus.pipewire_node, shared_state); + if (pipewire.init(dbus.pipewire_fd, dbus.pipewire_node, shared_state) < 0) { + BOOST_LOG(error) << "[portalgrab] Failed to init pipewire. portal_t::init() failed."; + return -1; + } // Start PipeWire now so format negotiation can proceed before capture start - pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia); + if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) { + BOOST_LOG(error) << "[portalgrab] Failed to ensure pipewire stream. portal_t::init() failed."; + return -1; + } int timeout_ms = 1500; int negotiated_w = 0; @@ -1210,6 +1255,7 @@ namespace portal { timeout_ms -= 10; } + // Set width and height to the values negotiated by pipewire if (negotiated_w > 0 && negotiated_h > 0 && (negotiated_w != width || negotiated_h != height)) { BOOST_LOG(info) << "[portalgrab] Using negotiated resolution "sv << negotiated_w << "x" << negotiated_h; @@ -1278,7 +1324,10 @@ namespace portal { platf::capture_e capture(const push_captured_image_cb_t &push_captured_image_cb, const pull_free_image_cb_t &pull_free_image_cb, bool *cursor) override { auto next_frame = std::chrono::steady_clock::now(); - pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia); + if (pipewire.ensure_stream(mem_type, width, height, framerate, dmabuf_infos.data(), n_dmabuf_infos, display_is_nvidia) < 0) { + BOOST_LOG(error) << "[portalgrab] Failed to ensure pipewire stream. capture() failed with error."; + return platf::capture_e::error; + } sleep_overshoot_logger.reset(); while (true) { From 5149ada119dcfbdc0b696fc37a0ef7247882f2b8 Mon Sep 17 00:00:00 2001 From: Kishi85 Date: Wed, 8 Apr 2026 20:35:26 +0200 Subject: [PATCH 5/5] fix(linux/xdgportal): Properly support multiple screens by exposing pipewire streams as separate displays Enables display switching on the client-side with Sunshine's existing shortcuts (CTRL+ALT+Fxx) when selecting multiple screens on the screencast source selection dialog (or automatically all availabe screens when using a combined remotedesktop+screencast session, tested with KDE). Each pipewire stream given by the portal will be available as a separate display (consistently ordered by position). --- src/platform/linux/portalgrab.cpp | 125 +++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/src/platform/linux/portalgrab.cpp b/src/platform/linux/portalgrab.cpp index b31469e92a8..9bc5bfda1bf 100644 --- a/src/platform/linux/portalgrab.cpp +++ b/src/platform/linux/portalgrab.cpp @@ -172,6 +172,55 @@ namespace portal { int n_modifiers; }; + struct pipewire_streaminfo_t { + int pipewire_node; + int width; + int height; + int pos_x; + int pos_y; + std::string monitor_name; + + std::string to_display_name() { + if (monitor_name.length() > 0) { + return std::format("n{}", monitor_name); + } + return std::format("p{},{},{},{}", pos_x, pos_y, width, height); + } + + bool match_display_name(const std::string &display_name) const { + // Empty display_name never matches + if (display_name.empty()) { + return false; + } + // Method 1: Display_name starts with 'n' match by monitor_name starting from pos 1 + if (display_name[0] == 'n') { + return display_name.substr(1) == monitor_name; + } + // Method 2: Display_name starts with 'p' match by position+resolution starting from pos 1 + if (display_name[0] == 'p') { + auto stringstream = std::stringstream(display_name.substr(1)); + std::string stringvalue; + std::vector values; + constexpr char display_name_delimiter = ','; + while (std::getline(stringstream, stringvalue, display_name_delimiter)) { + if (std::ranges::all_of(stringvalue, ::isdigit)) { + values.emplace_back(std::stoi(stringvalue)); + } else { + BOOST_LOG(debug) << "[portalgrab] Failed to parse int value: '"sv << stringvalue << "'"; + } + } + // Check if the vector has 4 values (pos_x, pos_y, width, height) from display_names formatting + if (values.size() != 4) { + BOOST_LOG(debug) << "[portalgrab] Display name does not match expected format 'x,y,w,h': '"sv << display_name << "'"; + return false; + } + return pos_x == values[0] && pos_y == values[1] && width == values[2] && height == values[3]; + } + // All matching methods have failed. No match! + return false; + } + }; + class dbus_t { public: ~dbus_t() noexcept { @@ -284,7 +333,7 @@ namespace portal { return -1; } - if (start_portal_session(loop, session_path, pipewire_node, width, height, use_screencast_only) < 0) { + if (start_portal_session(loop, session_path, pipewire_streams, use_screencast_only) < 0) { return -1; } @@ -361,10 +410,8 @@ namespace portal { return false; } + std::vector pipewire_streams; int pipewire_fd; - int pipewire_node; - int width; - int height; private: GDBusConnection *conn; @@ -502,6 +549,7 @@ namespace portal { g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(request_token)); g_variant_builder_add(&builder, "{sv}", "types", g_variant_new_uint32(SOURCE_TYPE_MONITOR)); g_variant_builder_add(&builder, "{sv}", "cursor_mode", g_variant_new_uint32(CURSOR_MODE_EMBEDDED)); + g_variant_builder_add(&builder, "{sv}", "multiple", g_variant_new_boolean(TRUE)); if (persist) { g_variant_builder_add(&builder, "{sv}", "persist_mode", g_variant_new_uint32(PERSIST_UNTIL_REVOKED)); if (!restore_token_t::empty()) { @@ -540,7 +588,7 @@ namespace portal { return 0; } - int start_portal_session(GMainLoop *loop, const gchar *session_path, int &out_pipewire_node, int &out_width, int &out_height, bool use_screencast) { + int start_portal_session(GMainLoop *loop, const gchar *session_path, std::vector &out_pipewire_streams, bool use_screencast) { GDBusProxy *proxy = use_screencast ? screencast_proxy : remote_desktop_proxy; const char *session_type = use_screencast ? "ScreenCast" : "RemoteDesktop"; @@ -600,10 +648,30 @@ namespace portal { } GVariantIter iter; + const auto wl_monitors = wl::monitors(); + int out_pipewire_node; g_autoptr(GVariant) value = nullptr; g_variant_iter_init(&iter, streams); while (g_variant_iter_next(&iter, "(u@a{sv})", &out_pipewire_node, &value)) { + int out_width; + int out_height; g_variant_lookup(value, "size", "(ii)", &out_width, &out_height, nullptr); + + int out_pos_x; + int out_pos_y; + g_variant_lookup(value, "position", "(ii)", &out_pos_x, &out_pos_y, nullptr); + + auto stream = pipewire_streaminfo_t {out_pipewire_node, out_width, out_height, out_pos_x, out_pos_y}; + + // Try to match the stream to a monitor_name by position/resolution and update stream info + for (const auto &monitor : wl_monitors) { + if (monitor->viewport.offset_x == out_pos_x && monitor->viewport.offset_y == out_pos_y && monitor->viewport.logical_width == out_width && monitor->viewport.logical_height == out_height) { + stream.monitor_name = monitor->name; + break; + } + } + + out_pipewire_streams.emplace_back(stream); } return 0; @@ -1221,6 +1289,26 @@ namespace portal { return -1; } + // Match display_name to a stream from the pipewire_streams vector + bool use_fallback = true; + pipewire_streaminfo_t stream; + for (const auto &stream_ : dbus.pipewire_streams) { + if (stream_.match_display_name(display_name)) { + stream = stream_; + use_fallback = false; + break; + } + } + // Fall back to first stream if we cannot match the given display_name to a stream in currently available streams. + if (use_fallback) { + BOOST_LOG(info) << "[portalgrab] Using first available stream as no matching stream was found for: '"sv << display_name << "'"; + stream = dbus.pipewire_streams[0]; + } + // Set values inherited from display_t + width = stream.width; + height = stream.height; + BOOST_LOG(info) << "[portalgrab] Streaming display '"sv << stream.monitor_name << "' from position: "sv << stream.pos_x << "x"sv << stream.pos_y << " resolution: "sv << width << "x"sv << height; + framerate = config.framerate; if (!shared_state) { @@ -1230,7 +1318,7 @@ namespace portal { shared_state->negotiated_width.store(0); shared_state->negotiated_height.store(0); } - if (pipewire.init(dbus.pipewire_fd, dbus.pipewire_node, shared_state) < 0) { + if (pipewire.init(dbus.pipewire_fd, stream.pipewire_node, shared_state) < 0) { BOOST_LOG(error) << "[portalgrab] Failed to init pipewire. portal_t::init() failed."; return -1; } @@ -1370,10 +1458,16 @@ namespace portal { pipewire.frame_cv().notify_all(); return platf::capture_e::interrupted; } - push_captured_image_cb(std::move(img_out), false); + if (!push_captured_image_cb(std::move(img_out), false)) { + BOOST_LOG(debug) << "[portalgrab] PipeWire: !push_captured_image_cb -> ok"; + return platf::capture_e::ok; + } break; case platf::capture_e::ok: - push_captured_image_cb(std::move(img_out), true); + if (!push_captured_image_cb(std::move(img_out), true)) { + BOOST_LOG(debug) << "[portalgrab] PipeWire: !push_captured_image_cb -> ok"; + return platf::capture_e::ok; + } break; default: BOOST_LOG(error) << "[portalgrab] Unrecognized capture status ["sv << std::to_underlying(status) << ']'; @@ -1595,12 +1689,25 @@ namespace platf { auto dbus = std::make_shared(); if (dbus->init() < 0) { + BOOST_LOG(warning) << "[portalgrab] Failed to connect to dbus. Cannot enumerate displays, returning empty list."; return {}; } pw_init(nullptr, nullptr); - display_names.emplace_back("org.freedesktop.portal.Desktop"); + if (dbus->connect_to_portal() < 0) { + BOOST_LOG(warning) << "[portalgrab] Failed to connect to portal. Cannot enumerate displays, returning empty list."; + return {}; + } + + for (auto stream_ : dbus->pipewire_streams) { + BOOST_LOG(info) << "[portalgrab] Found stream for display: '"sv << stream_.monitor_name << "' position: "sv << stream_.pos_x << "x"sv << stream_.pos_y << " resolution: "sv << stream_.width << "x"sv << stream_.height; + display_names.emplace_back(stream_.to_display_name()); + } + // Release the portal session as soon as possible to properly release related resources early. + dbus.reset(); + + // Return currently active display names return display_names; } } // namespace platf