Skip to content

Commit 58efcab

Browse files
committed
WIP: fix(linux/xdgportal): Expose pipewire streams as separate displays for multi monitor support
This will allow for multi-monitor support with client-side switching using existing shortcuts (CTRL+ALT+SHIFT+Fxx) if multiple pipewire streams are made available by the screencast portal (utilizing the multiple option). Used in conjuction with the remotedesktop portal this will provide easy access to all screens (at least on KDE) as one stream per logical screen is available.
1 parent b172a98 commit 58efcab

File tree

1 file changed

+51
-21
lines changed

1 file changed

+51
-21
lines changed

src/platform/linux/portalgrab.cpp

Lines changed: 51 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ namespace portal {
172172
int n_modifiers;
173173
};
174174

175+
struct pipewire_screenstream_t {
176+
int pipewire_node;
177+
int width;
178+
int height;
179+
};
180+
175181
class dbus_t {
176182
public:
177183
~dbus_t() noexcept {
@@ -281,7 +287,7 @@ namespace portal {
281287
return -1;
282288
}
283289

284-
if (start_portal_session(loop, session_path, pipewire_node, width, height, use_screencast_only) < 0) {
290+
if (start_portal_session(loop, session_path, pipewire_streams, use_screencast_only) < 0) {
285291
return -1;
286292
}
287293

@@ -331,10 +337,8 @@ namespace portal {
331337
return 0;
332338
}
333339

340+
std::vector<pipewire_screenstream_t> pipewire_streams;
334341
int pipewire_fd;
335-
int pipewire_node;
336-
int width;
337-
int height;
338342

339343
private:
340344
GDBusConnection *conn;
@@ -472,6 +476,7 @@ namespace portal {
472476
g_variant_builder_add(&builder, "{sv}", "handle_token", g_variant_new_string(request_token));
473477
g_variant_builder_add(&builder, "{sv}", "types", g_variant_new_uint32(SOURCE_TYPE_MONITOR));
474478
g_variant_builder_add(&builder, "{sv}", "cursor_mode", g_variant_new_uint32(CURSOR_MODE_EMBEDDED));
479+
g_variant_builder_add(&builder, "{sv}", "multiple", g_variant_new_boolean(TRUE));
475480
if (persist) {
476481
g_variant_builder_add(&builder, "{sv}", "persist_mode", g_variant_new_uint32(PERSIST_UNTIL_REVOKED));
477482
if (!restore_token_t::empty()) {
@@ -510,7 +515,7 @@ namespace portal {
510515
return 0;
511516
}
512517

513-
int start_portal_session(GMainLoop *loop, const gchar *session_path, int &out_pipewire_node, int &out_width, int &out_height, bool use_screencast) {
518+
int start_portal_session(GMainLoop *loop, const gchar *session_path, std::vector<pipewire_screenstream_t> &out_pipewire_streams, bool use_screencast) {
514519
GDBusProxy *proxy = use_screencast ? screencast_proxy : remote_desktop_proxy;
515520
const char *session_type = use_screencast ? "ScreenCast" : "RemoteDesktop";
516521

@@ -570,10 +575,14 @@ namespace portal {
570575
}
571576

572577
GVariantIter iter;
578+
int out_pipewire_node;
579+
int out_width;
580+
int out_height;
573581
g_autoptr(GVariant) value = nullptr;
574582
g_variant_iter_init(&iter, streams);
575583
while (g_variant_iter_next(&iter, "(u@a{sv})", &out_pipewire_node, &value)) {
576584
g_variant_lookup(value, "size", "(ii)", &out_width, &out_height, nullptr);
585+
out_pipewire_streams.emplace_back(pipewire_screenstream_t{out_pipewire_node, out_width, out_height});
577586
}
578587

579588
return 0;
@@ -669,15 +678,13 @@ namespace portal {
669678
*
670679
* @return 0 on success, -1 on failure
671680
*/
672-
int get_or_create_session(int &pipewire_fd, int &pipewire_node, int &width, int &height) {
681+
int get_or_create_session(int &pipewire_fd, std::vector<pipewire_screenstream_t> &pipewire_streams) {
673682
std::scoped_lock lock(mutex_);
674683

675684
if (valid_) {
676685
// Return cached session data
677686
pipewire_fd = dup(pipewire_fd_); // Duplicate FD for each caller
678-
pipewire_node = pipewire_node_;
679-
width = width_;
680-
height = height_;
687+
pipewire_streams = pipewire_streams_;
681688
BOOST_LOG(debug) << "Reusing cached portal session"sv;
682689
return 0;
683690
}
@@ -694,16 +701,12 @@ namespace portal {
694701

695702
// Cache the session data
696703
pipewire_fd_ = dbus_->pipewire_fd;
697-
pipewire_node_ = dbus_->pipewire_node;
698-
width_ = dbus_->width;
699-
height_ = dbus_->height;
704+
pipewire_streams_ = dbus_->pipewire_streams;
700705
valid_ = true;
701706

702707
// Return to caller (duplicate FD so each caller has their own)
703708
pipewire_fd = dup(pipewire_fd_);
704-
pipewire_node = pipewire_node_;
705-
width = width_;
706-
height = height_;
709+
pipewire_streams = pipewire_streams_;
707710

708711
BOOST_LOG(debug) << "Created new portal session (cached)"sv;
709712
return 0;
@@ -759,9 +762,7 @@ namespace portal {
759762
std::mutex mutex_;
760763
std::unique_ptr<dbus_t> dbus_;
761764
int pipewire_fd_ = -1;
762-
int pipewire_node_ = 0;
763-
int width_ = 0;
764-
int height_ = 0;
765+
std::vector<pipewire_screenstream_t> pipewire_streams_;
765766
bool valid_ = false;
766767
bool maxframerate_failed_ = false;
767768
};
@@ -1235,13 +1236,31 @@ namespace portal {
12351236
return -1;
12361237
}
12371238

1239+
// Convert display name back to int as it is an index
1240+
int display_index = -1;
1241+
if (!display_name.empty()) {
1242+
display_index = std::stoi(display_name);
1243+
}
1244+
12381245
// Use cached portal session to avoid creating multiple screen recordings
12391246
int pipewire_fd = -1;
1240-
int pipewire_node = 0;
1241-
if (session_cache_t::instance().get_or_create_session(pipewire_fd, pipewire_node, width, height) < 0) {
1247+
std::vector<pipewire_screenstream_t> pipewire_streams;
1248+
if (session_cache_t::instance().get_or_create_session(pipewire_fd, pipewire_streams) < 0) {
12421249
return -1;
12431250
}
12441251

1252+
// If stream_index is out of the currently valid range, use the first display to stream
1253+
if (display_index < 0 || display_index >= pipewire_streams.size()) {
1254+
BOOST_LOG(warning) << "Display index out of range (using 0): "sv << display_index;
1255+
display_index = 0;
1256+
}
1257+
1258+
// Select correct stream for requested display
1259+
int pipewire_node = pipewire_streams[display_index].pipewire_node;
1260+
width = pipewire_streams[display_index].width;
1261+
height = pipewire_streams[display_index].height;
1262+
BOOST_LOG(info) << "[portalgrab] Using display "sv << display_index << " resolution: "sv << width << "x"sv << height;
1263+
12451264
framerate = config.framerate;
12461265

12471266
if (!shared_state) {
@@ -1641,7 +1660,18 @@ namespace platf {
16411660

16421661
pw_init(nullptr, nullptr);
16431662

1644-
display_names.emplace_back("org.freedesktop.portal.Desktop");
1663+
// TODO: Unsure if it is better to use session cache here. Querying directly will always yield uptodate results but could fail if only one portal is allowed.
1664+
if (dbus->connect_to_portal() < 0) {
1665+
BOOST_LOG(warning) << "[portalgrab] Failed to connect to portal. Cannot enumerate displays, returning empty list.";
1666+
return {};
1667+
}
1668+
for (int x = 0; x < dbus->pipewire_streams.size(); ++x) {
1669+
auto display = dbus->pipewire_streams[x];
1670+
BOOST_LOG(info) << "[portalgrab] Found display "sv << x << " size: "sv << display.width << "x"sv << display.height;
1671+
// TODO: It might be better to use screen position+resolution here to detect changes in portal but it's a lot complexer to match later
1672+
display_names.emplace_back(std::to_string(x));
1673+
}
1674+
// TODO: Check if we need to close the dbus/portal connection here or not (if not using session cache)
16451675
return display_names;
16461676
}
16471677
} // namespace platf

0 commit comments

Comments
 (0)