/** * @file src/platform/linux/portalgrab.cpp * @brief Definitions for XDG portal grab. */ // local includes #include "pipewire.cpp" namespace { // Portal configuration constants constexpr uint32_t SOURCE_TYPE_MONITOR = 1; constexpr uint32_t CURSOR_MODE_EMBEDDED = 2; constexpr uint32_t PERSIST_FORGET = 0; constexpr uint32_t PERSIST_WHILE_RUNNING = 1; constexpr uint32_t PERSIST_UNTIL_REVOKED = 2; constexpr uint32_t TYPE_KEYBOARD = 1; constexpr uint32_t TYPE_POINTER = 2; constexpr uint32_t TYPE_TOUCHSCREEN = 4; // Portal D-Bus interface names and paths constexpr const char *PORTAL_NAME = "org.freedesktop.portal.Desktop"; constexpr const char *PORTAL_PATH = "/org/freedesktop/portal/desktop"; constexpr const char *REMOTE_DESKTOP_IFACE = "org.freedesktop.portal.RemoteDesktop"; constexpr const char *SCREENCAST_IFACE = "org.freedesktop.portal.ScreenCast"; constexpr const char *REQUEST_IFACE = "org.freedesktop.portal.Request"; constexpr const char REQUEST_PREFIX[] = "/org/freedesktop/portal/desktop/request/"; constexpr const char SESSION_PREFIX[] = "/org/freedesktop/portal/desktop/session/"; } // namespace using namespace std::literals; namespace portal { // Forward declarations class runtime_t; class restore_token_t { public: static std::string get() { return *token_; } static void set(std::string_view value) { *token_ = value; } static bool empty() { return token_->empty(); } static void load() { std::ifstream file(get_file_path()); if (file.is_open()) { std::getline(file, *token_); if (!token_->empty()) { BOOST_LOG(info) << "[portalgrab] Loaded portal restore token from disk"sv; } } } static void save() { if (token_->empty()) { return; } std::ofstream file(get_file_path()); if (file.is_open()) { file << *token_; BOOST_LOG(info) << "[portalgrab] Saved portal restore token to disk"sv; } else { BOOST_LOG(warning) << "[portalgrab] Failed to save portal restore token"sv; } } private: static inline const std::unique_ptr token_ = std::make_unique(); static std::string get_file_path() { return platf::appdata().string() + "/portal_token"; } }; struct dbus_response_t { GMainLoop *loop; GVariant *response; guint subscription_id; }; struct pipewire_streaminfo_t { int pipewire_node = -1; int width = 0; int height = 0; int pos_x = 0; int pos_y = 0; std::string monitor_name; std::string to_display_name() { if (!monitor_name.empty()) { return monitor_name; } return std::format("position-{}x{}-resolution-{}x{}", pos_x, pos_y, width, height); } bool match_display_name(const std::string_view &display_name) { // Check the given non-empty display name matches the display name for this struct return !display_name.empty() && display_name == to_display_name(); } }; class dbus_t { public: dbus_t &operator=(dbus_t &&) = delete; // Do not allow to copying ~dbus_t() noexcept { try { if (conn && !session_handle.empty()) { g_autoptr(GError) err = nullptr; // This is a blocking C call; it won't throw, but we wrap for safety g_dbus_connection_call_sync( conn, "org.freedesktop.portal.Desktop", session_handle.c_str(), "org.freedesktop.portal.Session", "Close", nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &err ); if (err) { BOOST_LOG(warning) << "[portalgrab] Failed to explicitly close portal session: "sv << err->message; } else { BOOST_LOG(debug) << "[portalgrab] Explicitly closed portal session: "sv << session_handle; } } } catch (const std::exception &e) { BOOST_LOG(error) << "[portalgrab] Standard exception caught in ~dbus_t: "sv << e.what(); } catch (...) { 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); } if (remote_desktop_proxy) { g_clear_object(&remote_desktop_proxy); } if (conn) { g_clear_object(&conn); } } int init() { restore_token_t::load(); conn = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); if (!conn) { return -1; } remote_desktop_proxy = g_dbus_proxy_new_sync(conn, G_DBUS_PROXY_FLAGS_NONE, nullptr, PORTAL_NAME, PORTAL_PATH, REMOTE_DESKTOP_IFACE, nullptr, nullptr); if (!remote_desktop_proxy) { return -1; } screencast_proxy = g_dbus_proxy_new_sync(conn, G_DBUS_PROXY_FLAGS_NONE, nullptr, PORTAL_NAME, PORTAL_PATH, SCREENCAST_IFACE, nullptr, nullptr); if (!screencast_proxy) { return -1; } return 0; } int connect_to_portal() { g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, FALSE); g_autofree gchar *session_path = nullptr; g_autofree gchar *session_token = nullptr; create_session_path(conn, nullptr, &session_token); // Try combined RemoteDesktop + ScreenCast session first bool use_screencast_only = !try_remote_desktop_session(loop, &session_path, session_token); // Fall back to ScreenCast-only if RemoteDesktop failed if (use_screencast_only && try_screencast_only_session(loop, &session_path) < 0) { return -1; } if (start_portal_session(loop, session_path, pipewire_streams, use_screencast_only) < 0) { return -1; } if (open_pipewire_remote(session_path, pipewire_fd) < 0) { return -1; } return 0; } // Try to create a combined RemoteDesktop + ScreenCast session // Returns true on success, false if should fall back to ScreenCast-only bool try_remote_desktop_session(GMainLoop *loop, gchar **session_path, const gchar *session_token) { if (create_portal_session(loop, session_path, session_token, false) < 0) { return false; } if (select_remote_desktop_devices(loop, *session_path) < 0) { 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) << "[portalgrab] ScreenCast.SelectSources failed with RemoteDesktop session, trying ScreenCast-only mode"sv; g_free(*session_path); *session_path = nullptr; return false; } return true; } // Create a ScreenCast-only session int try_screencast_only_session(GMainLoop *loop, gchar **session_path) { g_autofree gchar *new_session_token = nullptr; create_session_path(conn, nullptr, &new_session_token); if (create_portal_session(loop, session_path, new_session_token, true) < 0) { return -1; } if (select_screencast_sources(loop, *session_path, true) < 0) { g_free(*session_path); *session_path = nullptr; return -1; } 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; } std::vector pipewire_streams; int pipewire_fd; private: GDBusConnection *conn; GDBusProxy *screencast_proxy; GDBusProxy *remote_desktop_proxy; std::string session_handle; int create_portal_session(GMainLoop *loop, gchar **session_path_out, const gchar *session_token, bool use_screencast) { GDBusProxy *proxy = use_screencast ? screencast_proxy : remote_desktop_proxy; const char *session_type = use_screencast ? "ScreenCast" : "RemoteDesktop"; dbus_response_t response = { nullptr, }; g_autofree gchar *request_token = nullptr; create_request_path(conn, nullptr, &request_token); GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("(a{sv})")); g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(request_token)); g_variant_builder_add(&builder, "{sv}", "session_handle_token", g_variant_new_string(session_token)); g_variant_builder_close(&builder); g_autoptr(GError) err = nullptr; 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) << "[portalgrab] Could not create "sv << session_type << " session: "sv << err->message; return -1; } const gchar *request_path = nullptr; g_variant_get(reply, "(o)", &request_path); dbus_response_init(&response, loop, conn, request_path); g_autoptr(GVariant) create_response = dbus_response_wait(&response); if (!create_response) { BOOST_LOG(error) << "[portalgrab] " << session_type << " CreateSession: no response received"sv; return -1; } guint32 response_code; g_autoptr(GVariant) results = nullptr; g_variant_get(create_response, "(u@a{sv})", &response_code, &results); BOOST_LOG(debug) << "[portalgrab] " << session_type << " CreateSession response_code: "sv << response_code; if (response_code != 0) { 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) << "[portalgrab] " << session_type << " CreateSession: session_handle not found in response"sv; return -1; } if (g_variant_is_of_type(session_handle_v, G_VARIANT_TYPE_VARIANT)) { g_autoptr(GVariant) inner = g_variant_get_variant(session_handle_v); *session_path_out = g_strdup(g_variant_get_string(inner, nullptr)); } else { *session_path_out = g_strdup(g_variant_get_string(session_handle_v, nullptr)); } 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; } int select_remote_desktop_devices(GMainLoop *loop, const gchar *session_path) { dbus_response_t response = { nullptr, }; g_autofree gchar *request_token = nullptr; create_request_path(conn, nullptr, &request_token); GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("(oa{sv})")); g_variant_builder_add(&builder, "o", session_path); g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); 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(TYPE_KEYBOARD | TYPE_POINTER | TYPE_TOUCHSCREEN)); g_variant_builder_add(&builder, "{sv}", "persist_mode", g_variant_new_uint32(PERSIST_UNTIL_REVOKED)); if (!restore_token_t::empty()) { g_variant_builder_add(&builder, "{sv}", "restore_token", g_variant_new_string(restore_token_t::get().c_str())); } g_variant_builder_close(&builder); g_autoptr(GError) err = nullptr; 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) << "[portalgrab] Could not select devices: "sv << err->message; return -1; } const gchar *request_path = nullptr; g_variant_get(reply, "(o)", &request_path); dbus_response_init(&response, loop, conn, request_path); g_autoptr(GVariant) devices_response = dbus_response_wait(&response); if (!devices_response) { 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) << "[portalgrab] SelectDevices response_code: "sv << response_code; if (response_code != 0) { BOOST_LOG(error) << "[portalgrab] SelectDevices failed with response code: "sv << response_code; return -1; } return 0; } int select_screencast_sources(GMainLoop *loop, const gchar *session_path, bool persist) { dbus_response_t response = { nullptr, }; g_autofree gchar *request_token = nullptr; create_request_path(conn, nullptr, &request_token); GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("(oa{sv})")); g_variant_builder_add(&builder, "o", session_path); g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); 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()) { g_variant_builder_add(&builder, "{sv}", "restore_token", g_variant_new_string(restore_token_t::get().c_str())); } } g_variant_builder_close(&builder); 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) << "[portalgrab] Could not select sources: "sv << err->message; return -1; } const gchar *request_path = nullptr; g_variant_get(reply, "(o)", &request_path); dbus_response_init(&response, loop, conn, request_path); g_autoptr(GVariant) sources_response = dbus_response_wait(&response); if (!sources_response) { 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) << "[portalgrab] SelectSources response_code: "sv << response_code; if (response_code != 0) { BOOST_LOG(error) << "[portalgrab] SelectSources failed with response code: "sv << response_code; return -1; } return 0; } 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"; dbus_response_t response = { nullptr, }; g_autofree gchar *request_token = nullptr; create_request_path(conn, nullptr, &request_token); GVariantBuilder builder; g_variant_builder_init(&builder, G_VARIANT_TYPE("(osa{sv})")); g_variant_builder_add(&builder, "o", session_path); g_variant_builder_add(&builder, "s", ""); // parent_window g_variant_builder_open(&builder, G_VARIANT_TYPE("a{sv}")); g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(request_token)); g_variant_builder_close(&builder); 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) << "[portalgrab] Could not start "sv << session_type << " session: "sv << err->message; return -1; } const gchar *request_path = nullptr; g_variant_get(reply, "(o)", &request_path); dbus_response_init(&response, loop, conn, request_path); g_autoptr(GVariant) start_response = dbus_response_wait(&response); if (!start_response) { BOOST_LOG(error) << "[portalgrab] " << session_type << " Start: no response received"sv; return -1; } guint32 response_code; g_autoptr(GVariant) dict = nullptr; g_autoptr(GVariant) streams = nullptr; g_variant_get(start_response, "(u@a{sv})", &response_code, &dict); BOOST_LOG(debug) << "[portalgrab] " << session_type << " Start response_code: "sv << response_code; if (response_code != 0) { 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) << "[portalgrab] " << session_type << " Start: no streams in response"sv; return -1; } if (const gchar *new_token = nullptr; g_variant_lookup(dict, "restore_token", "s", &new_token) && new_token && new_token[0] != '\0' && restore_token_t::get() != new_token) { restore_token_t::set(new_token); restore_token_t::save(); } 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; bool result = g_variant_lookup(value, "size", "(ii)", &out_width, &out_height, nullptr); if (!result) { BOOST_LOG(warning) << "[portalgrab] Ignoring stream without proper resolution on pipewire node "sv << out_pipewire_node; continue; } int out_pos_x; int out_pos_y; result = g_variant_lookup(value, "position", "(ii)", &out_pos_x, &out_pos_y, nullptr); if (!result) { BOOST_LOG(warning) << "[portalgrab] Falling back to position 0x0 for stream with resolution "sv << out_width << "x"sv << out_height << "on pipewire node "sv << out_pipewire_node; out_pos_x = 0; out_pos_y = 0; } 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); } // The portal call returns the streams sorted by out_pipewire_node which can shuffle displays around, so // we have to sort pipewire streams by position here to be consistent std::ranges::sort(out_pipewire_streams, [](const auto &a, const auto &b) { return a.pos_x < b.pos_x || a.pos_y < b.pos_y; }); return 0; } int open_pipewire_remote(const gchar *session_path, int &fd) { g_autoptr(GUnixFDList) fd_list = nullptr; g_autoptr(GVariant) msg = g_variant_ref_sink(g_variant_new("(oa{sv})", session_path, nullptr)); 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) << "[portalgrab] Could not open pipewire remote: "sv << err->message; return -1; } int fd_handle; g_variant_get(reply, "(h)", &fd_handle); fd = g_unix_fd_list_get(fd_list, fd_handle, nullptr); return 0; } static void on_response_received_cb([[maybe_unused]] GDBusConnection *connection, [[maybe_unused]] const gchar *sender_name, [[maybe_unused]] const gchar *object_path, [[maybe_unused]] const gchar *interface_name, [[maybe_unused]] const gchar *signal_name, GVariant *parameters, gpointer user_data) { auto *response = static_cast(user_data); response->response = g_variant_ref_sink(parameters); g_main_loop_quit(response->loop); } static gchar *get_sender_string(GDBusConnection *conn) { gchar *sender = g_strdup(g_dbus_connection_get_unique_name(conn) + 1); gchar *dot; while ((dot = strstr(sender, ".")) != nullptr) { *dot = '_'; } return sender; } static void create_request_path(GDBusConnection *conn, gchar **out_path, gchar **out_token) { static uint32_t request_count = 0; request_count++; if (out_token) { *out_token = g_strdup_printf("Sunshine%u", request_count); } if (out_path) { g_autofree gchar *sender = get_sender_string(conn); *out_path = g_strdup(std::format("{}{}{}{}", REQUEST_PREFIX, sender, "/Sunshine", request_count).c_str()); } } static void create_session_path(GDBusConnection *conn, gchar **out_path, gchar **out_token) { static uint32_t session_count = 0; session_count++; if (out_token) { *out_token = g_strdup_printf("Sunshine%u", session_count); } if (out_path) { g_autofree gchar *sender = get_sender_string(conn); *out_path = g_strdup(std::format("{}{}{}{}", SESSION_PREFIX, sender, "/Sunshine", session_count).c_str()); } } static void dbus_response_init(struct dbus_response_t *response, GMainLoop *loop, GDBusConnection *conn, const char *request_path) { response->loop = loop; response->subscription_id = g_dbus_connection_signal_subscribe(conn, PORTAL_NAME, REQUEST_IFACE, "Response", request_path, nullptr, G_DBUS_SIGNAL_FLAGS_NONE, on_response_received_cb, response, nullptr); } static GVariant *dbus_response_wait(struct dbus_response_t *response) { g_main_loop_run(response->loop); return response->response; } }; class portal_t: public pipewire::pipewire_display_t { public: int configure_stream(const std::string &display_name, int &out_pipewire_fd, uint32_t &out_pipewire_node, uint64_t &out_pipewire_object_serial [[maybe_unused]]) override { // Connect DBus portal session if (dbus.init() < 0) { BOOST_LOG(error) << "[portalgrab] Failed to connect to dbus. portal_t setup failed."; return -1; } if (dbus.connect_to_portal() < 0) { BOOST_LOG(error) << "[portalgrab] Failed to connect to portal. portal_t setup failed."; return -1; } // Match display_name to a stream from the pipewire_streams vector bool use_fallback = true; pipewire_streaminfo_t stream; auto streams = dbus.pipewire_streams; if (streams.empty()) { BOOST_LOG(error) << "[portalgrab] No streams found on portal. portal_t setup failed."; return -1; } for (auto &stream_ : 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.at(0); } // Restore global maxframerate negotiation state pipewire.set_negotiate_maxframerate(negotiate_maxframerate.load()); // Return values for pipewire init out_pipewire_fd = dbus.pipewire_fd; out_pipewire_node = stream.pipewire_node; // Set/update basic stream parameters on display_t this->offset_x = stream.pos_x; this->offset_y = stream.pos_y; this->width = stream.width; this->height = stream.height; this->logical_width = 0; // Explicitly mark for pipewire_display_t to try to figure this out. this->logical_height = 0; // Explicitly Mark for pipewire_display_t to try to figure this out. // Flag successful setup return 0; } bool check_stream_dead(platf::capture_e &out_status) override { // If the pipewire stream stopped due to closed portal session stop the capture with an error if (dbus.is_session_closed()) { BOOST_LOG(warning) << "[portalgrab] PipeWire stream stopped by closed portal session."sv; pipewire.frame_cv().notify_all(); out_status = platf::capture_e::error; return true; // Stop capture with error (due to out_status) } // Disable maxframerate negotiation if the stream died without having ever started (e.g. GNOME mutter does not support it) if (shared_state->previous_state != PW_STREAM_STATE_STREAMING && negotiate_maxframerate.load()) { BOOST_LOG(warning) << "[portalgrab] Negotiation failed, will retry without maxFramerate"sv; negotiate_maxframerate.store(false); pipewire.set_negotiate_maxframerate(false); out_status = platf::capture_e::reinit; return true; // Stop capture with reinit (due to out_status) } return false; // Return to default stream dead handling } // DBus portal connection dbus_t dbus; // Class variable to store runtime state of maxFramerate negotiation static inline std::atomic negotiate_maxframerate {true}; }; } // namespace portal 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 (!pipewire::pipewire_display_t::init_pipewire_and_check_hwdevice_type(hwdevice_type)) { BOOST_LOG(error) << "[portalgrab] Could not initialize pipewire-based display with the given hw device type."sv; return nullptr; } // Drop CAP_SYS_ADMIN and set DUMPABLE flag to allow XDG /root access if (has_elevated_privileges()) { drop_elevated_privileges(); } auto portal = std::make_shared(); if (portal->init(hwdevice_type, display_name, config)) { return nullptr; } return portal; } std::vector portal_display_names() { std::vector display_names; 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 {}; } if (has_elevated_privileges()) { // We're still in the probing phase of Sunshine startup. Dropping portal security early will break KMS. // Just return a dummy screen for now. Display re-enumeration after encoder probing will yield full result. display_names.emplace_back("init"); return display_names; } 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 id/name: '"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