mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-08-07 10:20:46 +00:00
fix(linux/xdgportal): Properly support multiple screens by exposing pipewire streams as separate displays (#4931)
This commit is contained in:
+275
-209
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<int> 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 {
|
||||
@@ -194,17 +243,20 @@ 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 (pipewire_fd >= 0) {
|
||||
close(pipewire_fd);
|
||||
}
|
||||
if (screencast_proxy) {
|
||||
g_clear_object(&screencast_proxy);
|
||||
}
|
||||
@@ -237,11 +289,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 +304,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
|
||||
}
|
||||
@@ -281,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;
|
||||
}
|
||||
|
||||
@@ -300,14 +352,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;
|
||||
@@ -331,10 +383,35 @@ 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;
|
||||
}
|
||||
|
||||
std::vector<pipewire_streaminfo_t> pipewire_streams;
|
||||
int pipewire_fd;
|
||||
int pipewire_node;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
private:
|
||||
GDBusConnection *conn;
|
||||
@@ -363,7 +440,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 +451,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 +459,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 +479,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 +508,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 +519,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;
|
||||
}
|
||||
|
||||
@@ -472,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()) {
|
||||
@@ -483,7 +561,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,23 +572,23 @@ 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;
|
||||
}
|
||||
|
||||
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<pipewire_streaminfo_t> &out_pipewire_streams, bool use_screencast) {
|
||||
GDBusProxy *proxy = use_screencast ? screencast_proxy : remote_desktop_proxy;
|
||||
const char *session_type = use_screencast ? "ScreenCast" : "RemoteDesktop";
|
||||
|
||||
@@ -531,7 +609,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 +620,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 +629,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;
|
||||
}
|
||||
|
||||
@@ -570,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;
|
||||
@@ -586,7 +684,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;
|
||||
}
|
||||
|
||||
@@ -652,89 +750,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) << "Reusing cached portal session"sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create new session
|
||||
dbus_ = std::make_unique<dbus_t>();
|
||||
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) << "Created new portal session (cached)"sv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) << "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) << "Exception during session invalidation: "sv << e.what();
|
||||
} catch (...) {
|
||||
BOOST_LOG(error) << "Unknown error during session invalidation"sv;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_maxframerate_failed() const {
|
||||
return maxframerate_failed_;
|
||||
}
|
||||
@@ -746,23 +768,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_t> dbus_;
|
||||
int pipewire_fd_ = -1;
|
||||
int pipewire_node_ = 0;
|
||||
int width_ = 0;
|
||||
int height_ = 0;
|
||||
bool valid_ = false;
|
||||
bool maxframerate_failed_ = false;
|
||||
};
|
||||
|
||||
@@ -776,22 +785,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;
|
||||
}
|
||||
@@ -799,8 +819,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);
|
||||
}
|
||||
|
||||
@@ -820,29 +844,39 @@ namespace portal {
|
||||
stream_data.frame_ready = ready;
|
||||
}
|
||||
|
||||
void init(int stream_fd, int stream_node, std::shared_ptr<shared_state_t> shared_state) {
|
||||
int init(int stream_fd, int stream_node, std::shared_ptr<shared_state_t> 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;
|
||||
@@ -850,20 +884,29 @@ 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
@@ -897,10 +940,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) {
|
||||
@@ -1027,11 +1071,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 +1085,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<stream_data_t *>(user_data);
|
||||
@@ -1060,7 +1104,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 +1189,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 +1224,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 +1268,10 @@ namespace portal {
|
||||
delay = std::chrono::nanoseconds(
|
||||
(static_cast<int64_t>(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;
|
||||
|
||||
@@ -1235,12 +1279,35 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -1251,10 +1318,16 @@ namespace portal {
|
||||
shared_state->negotiated_width.store(0);
|
||||
shared_state->negotiated_height.store(0);
|
||||
}
|
||||
pipewire.init(pipewire_fd, pipewire_node, shared_state);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -1270,22 +1343,9 @@ 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);
|
||||
}
|
||||
|
||||
// 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) << "Using negotiated resolution "sv
|
||||
BOOST_LOG(info) << "[portalgrab] Using negotiated resolution "sv
|
||||
<< negotiated_w << "x" << negotiated_h;
|
||||
|
||||
width = negotiated_w;
|
||||
@@ -1309,7 +1369,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)) {
|
||||
@@ -1352,26 +1412,24 @@ 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();
|
||||
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) << "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 (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;
|
||||
} else {
|
||||
BOOST_LOG(warning) << "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,30 +1449,28 @@ 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) << "PipeWire: timeout -> interrupt nudge";
|
||||
capture_running.store(false);
|
||||
stream_stopped.store(false);
|
||||
previous_height.store(0);
|
||||
previous_width.store(0);
|
||||
BOOST_LOG(debug) << "[portalgrab] PipeWire: timeout -> interrupt nudge";
|
||||
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) << "Unrecognized capture status ["sv << std::to_underlying(status) << ']';
|
||||
BOOST_LOG(error) << "[portalgrab] Unrecognized capture status ["sv << std::to_underlying(status) << ']';
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -1487,7 +1543,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1495,10 +1551,10 @@ namespace portal {
|
||||
std::unique_lock<std::mutex> 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;
|
||||
}
|
||||
@@ -1523,7 +1579,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 +1593,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 +1633,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;
|
||||
}
|
||||
}
|
||||
@@ -1598,6 +1654,7 @@ namespace portal {
|
||||
|
||||
platf::mem_type_e mem_type;
|
||||
wl::display_t wl_display;
|
||||
dbus_t dbus;
|
||||
pipewire_t pipewire;
|
||||
std::array<struct dmabuf_format_info_t, MAX_DMABUF_FORMATS> dmabuf_infos;
|
||||
int n_dmabuf_infos;
|
||||
@@ -1607,10 +1664,6 @@ namespace portal {
|
||||
std::optional<std::uint64_t> last_seq {};
|
||||
std::uint64_t sequence {};
|
||||
uint32_t framerate;
|
||||
static inline std::atomic<uint32_t> previous_height {0};
|
||||
static inline std::atomic<uint32_t> previous_width {0};
|
||||
static inline std::atomic<bool> stream_stopped {false};
|
||||
static inline std::atomic<bool> capture_running {false};
|
||||
std::shared_ptr<shared_state_t> shared_state;
|
||||
};
|
||||
} // namespace portal
|
||||
@@ -1619,7 +1672,7 @@ namespace platf {
|
||||
std::shared_ptr<display_t> 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;
|
||||
}
|
||||
|
||||
@@ -1636,12 +1689,25 @@ namespace platf {
|
||||
auto dbus = std::make_shared<portal::dbus_t>();
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user