UltraGrid
UltraGrid Low-Latency High-Resolution Transmission System
Loading...
Searching...
No Matches
dbus_portal.cpp
Go to the documentation of this file.
1
6/*
7 * Copyright (c) 2023 CESNET z.s.p.o.
8 * All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, is permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 *
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 *
21 * 3. Neither the name of CESNET nor the names of its contributors may be
22 * used to endorse or promote products derived from this software without
23 * specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING,
27 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
28 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
29 * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
35 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38
39
40#include <algorithm>
41#include <string>
42#include <memory>
43#include <cassert>
44#include <future>
45#include <fstream>
46#include <gio/gio.h>
47#include <gio/gunixfdlist.h>
48#include <glib-object.h> // for GObject, g_object_unref
49#include <glib.h> // for g_variant_builder_add, GVariant, g_vari...
50#include <glib/gtypes.h> // for gint32
51#include <initializer_list> // for initializer_list
52
53#include "debug.h"
54#include "misc.h"
55#include "dbus_portal.hpp"
56
57
58#define MOD_NAME "[dbus] "
59
60namespace {
61struct request_path_t {
62 std::string token;
63 std::string path;
64
65 static request_path_t create(const std::string &name) {
66 ++token_counter;
67
68 auto token = std::string("uv") + std::to_string(token_counter);
69 request_path_t result = {
70 .token = token,
71 .path = std::string("/org/freedesktop/portal/desktop/request/") + name + "/" + token
72 };
73
74 LOG(LOG_LEVEL_DEBUG) << "new request: '" << result.path << "'\n";
75 return result;
76 }
77
78private:
79 static unsigned int token_counter;
80};
81
82unsigned int request_path_t::token_counter = 0;
83
84struct session_path_t {
85 std::string token;
86 std::string path;
87
88 static session_path_t create(const std::string &name) {
89 ++token_counter;
90
91 auto token = std::string("uv") + std::to_string(token_counter);
92 return {
93 .token = token,
94 .path = std::string("/org/freedesktop/portal/desktop/session/") + name + "/" + token
95 };
96 }
97
98private:
99 static unsigned int token_counter;
100};
101
102unsigned int session_path_t::token_counter = 0;
103
104struct GVariant_deleter { void operator()(GVariant *a) { g_variant_unref(a); } };
105using GVariant_uniq = std::unique_ptr<GVariant, GVariant_deleter>;
106
107
108} //anon namespace
109
111private:
112 template<auto callback>
113 void call_with_request(const char* method_name,
114 std::initializer_list<GVariant*> arguments,
115 GVariantBuilder &params_builder);
116
117 static void pipewire_opened(GObject *source,
118 GAsyncResult *res,
119 void *user_data);
120
121 static void started(uint32_t response, GVariant *results, void *user_data);
122 static void sources_selected(uint32_t response, GVariant *results, ScreenCastPortal_impl *s);
123 static void session_created(uint32_t response, GVariant *results, ScreenCastPortal_impl *s);
124
125 GMainLoop *dbus_loop;
126 GDBusConnection *connection;
127 GDBusProxy *screencast_proxy;
128 std::string unique_name;
129 session_path_t session;
130
131 std::string restore_file;
132 bool show_cursor = false;
133
134 int pw_fd = -1;
135 uint32_t pw_node = UINT32_MAX;
136
137 std::promise<std::string> promise;
138
139public:
140 // see https://flatpak.github.io/xdg-desktop-portal/#gdbus-signal-org-freedesktop-portal-Request.Response
141 static constexpr uint32_t REQUEST_RESPONSE_OK = 0;
142 static constexpr uint32_t REQUEST_RESPONSE_CANCELLED_BY_USER = 1;
143 static constexpr uint32_t REQUEST_RESPONSE_OTHER_ERROR = 2;
144
147
148 void run(std::string restore_file, bool show_cursor);
149
150 std::future<std::string> get_future() {
151 return promise.get_future();
152 }
153
154 int get_pw_fd(){ return pw_fd; }
155 uint32_t get_pw_node(){ return pw_node; }
156
157
158 void run_loop() {
159 g_main_loop_run(dbus_loop);
160 LOG(LOG_LEVEL_VERBOSE) << MOD_NAME "finished dbus loop \n";
161 }
162
163 void quit_loop() {
164 g_main_loop_quit(dbus_loop);
165 }
166
167 GDBusProxy *proxy() {
168 return screencast_proxy;
169 }
170
171 GDBusConnection *dbus_connection() const {
172 return connection;
173 }
174
175 const std::string& sender_name() const
176 {
177 return unique_name;
178 }
179
180 const std::string& session_path() const
181 {
182 return session.path;
183 }
184
185 const std::string& session_token() const
186 {
187 return session.token;
188 }
189};
190
192 GError *error = nullptr;
193
194 dbus_loop = g_main_loop_new(nullptr, false);
195 connection = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, &error);
196 g_assert_no_error(error);
197 assert(connection != nullptr);
198
199 unique_name = g_dbus_connection_get_unique_name(connection) + 1;
200 std::replace(unique_name.begin(), unique_name.end(), '.', '_');
201 screencast_proxy = g_dbus_proxy_new_sync(
202 connection, G_DBUS_PROXY_FLAGS_NONE, nullptr,
203 "org.freedesktop.portal.Desktop",
204 "/org/freedesktop/portal/desktop",
205 "org.freedesktop.portal.ScreenCast", nullptr, &error);
206 g_assert_no_error(error);
207 assert(screencast_proxy != nullptr);
208
209 session = session_path_t::create(unique_name);
210 LOG(LOG_LEVEL_VERBOSE) << MOD_NAME "session path: '" << session.path << "'" << " token: '" << session.token << "'\n";
211}
212
214 g_dbus_connection_call(dbus_connection(),
215 "org.freedesktop.portal.Desktop",
216 session.path.c_str(),
217 "org.freedesktop.portal.Session",
218 "Close", nullptr, nullptr,
219 G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr,
220 nullptr);
221 g_main_loop_quit(dbus_loop);
222 g_object_unref(screencast_proxy);
223 g_object_unref(connection);
224 g_main_loop_unref(dbus_loop);
225}
226
227template<auto func>
228void CallbackWrapper(GDBusConnection *connection,
229 const gchar *sender_name,
230 const gchar *object_path,
231 const gchar *interface_name,
232 const gchar *signal_name,
233 GVariant *parameters,
234 gpointer user_data)
235{
236 (void) sender_name;
237 (void) interface_name;
238 (void) signal_name;
239
240 uint32_t response;
241 GVariant_uniq results;
242 g_variant_get(parameters, "(u@a{sv})", &response, out_ptr(results).operator GVariant**());
243
244 func(response, results.get(), static_cast<ScreenCastPortal_impl *>(user_data));
245
246 g_dbus_connection_call(connection, "org.freedesktop.portal.Desktop",
247 object_path, "org.freedesktop.portal.Request", "Close",
248 nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, nullptr, nullptr);
249
250}
251
252template<auto callback>
253void ScreenCastPortal_impl::call_with_request(const char* method_name,
254 std::initializer_list<GVariant*> arguments,
255 GVariantBuilder &params_builder)
256{
257 assert(method_name != nullptr);
258 request_path_t request_path = request_path_t::create(sender_name());
259 LOG(LOG_LEVEL_VERBOSE) << MOD_NAME "call_with_request: '" << method_name << "' request: '" << request_path.path << "'\n";
260
261 g_dbus_connection_signal_subscribe(connection, "org.freedesktop.portal.Desktop",
262 "org.freedesktop.portal.Request",
263 "Response",
264 request_path.path.c_str(),
265 nullptr,
266 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
268 this,
269 nullptr);
270
271 auto call_finished = [](GObject *source_object, GAsyncResult *result, gpointer user_data) {
272 auto s = static_cast<ScreenCastPortal_impl *>(user_data);
273 GError *error = nullptr;
274 GVariant_uniq result_finished(g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), result, &error));
275
276 if(error != nullptr){
277 s->promise.set_value(error->message);
278 return;
279 }
280
281 const char *path = nullptr;
282 g_variant_get(result_finished.get(), "(o)", &path);
283 LOG(LOG_LEVEL_VERBOSE) << MOD_NAME "call_with_request finished: '" << path << "'\n";
284 };
285
286
287 g_variant_builder_add(&params_builder, "{sv}", "handle_token", g_variant_new_string(request_path.token.c_str()));
288
289 GVariantBuilder args_builder;
290 g_variant_builder_init(&args_builder, G_VARIANT_TYPE_TUPLE);
291 for(GVariant* arg : arguments){
292 g_variant_builder_add_value(&args_builder, arg);
293 }
294 g_variant_builder_add_value(&args_builder, g_variant_builder_end(&params_builder));
295
296 g_dbus_proxy_call(screencast_proxy, method_name, g_variant_builder_end(&args_builder), G_DBUS_CALL_FLAGS_NONE, -1, nullptr, call_finished, this);
297}
298
299static void on_portal_session_closed([[maybe_unused]] GDBusConnection *connection,
300 [[maybe_unused]] const gchar *sender_name,
301 [[maybe_unused]] const gchar *object_path,
302 [[maybe_unused]] const gchar *interface_name,
303 [[maybe_unused]] const gchar *signal_name,
304 [[maybe_unused]] GVariant *parameters,
305 [[maybe_unused]] gpointer user_data)
306{
307 //Doesn't seem to be called by GNOME
308 LOG(LOG_LEVEL_INFO) << MOD_NAME "session closed by compositor\n";
309}
310
312 GAsyncResult *res,
313 void *user_data)
314{
315 auto s = static_cast<ScreenCastPortal_impl *>(user_data);
316 GError *error = nullptr;
317 GUnixFDList *fd_list = nullptr;
318
319 GVariant_uniq result(g_dbus_proxy_call_with_unix_fd_list_finish(G_DBUS_PROXY(source), &fd_list, res, &error));
320 g_assert_no_error(error);
321
322 gint32 handle;
323 g_variant_get(result.get(), "(h)", &handle);
324 assert(handle == 0); //it should always be the first index
325
326 s->pw_fd = g_unix_fd_list_get(fd_list, handle, &error);
327 g_assert_no_error(error);
328
329 s->promise.set_value("");
330}
331
332void ScreenCastPortal_impl::started(uint32_t response, GVariant *results, void *user_data) {
333 auto s = static_cast<ScreenCastPortal_impl *>(user_data);
334 LOG(LOG_LEVEL_DEBUG) << MOD_NAME "started: " << g_variant_print(results, true) << "\n";
335
337 s->promise.set_value("failed to start (dialog cancelled by user)");
338 return;
340 s->promise.set_value("failed to start (unknown reason)");
341 return;
342 }
343
344 const char *restore_token = nullptr;
345 if (g_variant_lookup(results, "restore_token", "s", &restore_token)){
346 std::ofstream file(s->restore_file);
347 file<<restore_token;
348 }
349
350 GVariant *streams = g_variant_lookup_value(results, "streams", G_VARIANT_TYPE_ARRAY);
351 GVariant *stream_properties;
352 GVariantIter iter;
353 g_variant_iter_init(&iter, streams);
354 assert(g_variant_iter_n_children(&iter) == 1);
355 bool got_item = g_variant_iter_loop(&iter, "(u@a{sv})", &s->pw_node, &stream_properties);
356 assert(got_item);
357
358 GVariantBuilder builder;
359 g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
360 g_dbus_proxy_call_with_unix_fd_list(s->proxy(), "OpenPipeWireRemote",
361 g_variant_new("(oa{sv})", s->session_path().c_str(), &builder),
362 G_DBUS_CALL_FLAGS_NONE, -1,
363 nullptr, nullptr, ScreenCastPortal_impl::pipewire_opened, s);
364 g_variant_builder_clear(&builder);
365}
366
368 gchar *pretty = g_variant_print(results, true);
369 LOG(LOG_LEVEL_INFO) << MOD_NAME "selected sources: " << pretty << "\n";
370 g_free((gpointer) pretty);
371
373 s->promise.set_value("Failed to select sources");
374 return;
375 }
376
377 {
378 GVariantBuilder options;
379 g_variant_builder_init(&options, G_VARIANT_TYPE_VARDICT);
380 s->call_with_request<started>("Start", {g_variant_new_object_path(s->session_path().c_str()), /*parent window: */ g_variant_new_string("")}, options);
381 }
382};
383
386 s->promise.set_value("Failed to create session");
387 return;
388 }
389
390 const char *session_handle = nullptr;
391 g_variant_lookup(results, "session_handle", "s", &session_handle);
392
393 LOG(LOG_LEVEL_DEBUG) << MOD_NAME "session created with handle: " << session_handle << "\n";
394 assert(s->session_path() == session_handle);
395
396 {
397 GVariantBuilder params;
398 g_variant_builder_init(&params, G_VARIANT_TYPE_VARDICT);
399 g_variant_builder_add(&params, "{sv}", "types", g_variant_new_uint32(3)); // 1 full screen, 2 - a window, 3 - both
400 g_variant_builder_add(&params, "{sv}", "multiple", g_variant_new_boolean(false));
401 if(s->show_cursor)
402 g_variant_builder_add(&params, "{sv}", "cursor_mode", g_variant_new_uint32(2));
403
404 if(!s->restore_file.empty()){
405 std::string token;
406 std::ifstream file(s->restore_file);
407
408 if(file.is_open()) {
409 std::ostringstream ss;
410 ss << file.rdbuf();
411 token = ss.str();
412 }
413
414 // 0: Do not persist (default), 1: Permissions persist as long as the application is running, 2: Permissions persist until explicitly revoked
415 g_variant_builder_add(&params, "{sv}", "persist_mode", g_variant_new_uint32(2));
416 if(!token.empty())
417 g_variant_builder_add(&params, "{sv}", "restore_token", g_variant_new_string(token.c_str()));
418 }
419
420 s->call_with_request<sources_selected>("SelectSources", {g_variant_new_object_path(s->session_path().c_str())}, params);
421 }
422};
423
424void ScreenCastPortal_impl::run(std::string restore_file, bool show_cursor){
425 this->restore_file = restore_file;
426 this->show_cursor = show_cursor;
427
428 g_dbus_connection_signal_subscribe(dbus_connection(),
429 nullptr, // sender
430 "org.freedesktop.portal.Session", // interface_name
431 "closed", //signal name
432 session_path().c_str(), // object path
433 nullptr, // arg0
434 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
436 nullptr,
437 nullptr);
438
439 GVariantBuilder params;
440 g_variant_builder_init(&params, G_VARIANT_TYPE_VARDICT);
441 g_variant_builder_add(&params, "{sv}", "session_handle_token", g_variant_new_string(session_token().c_str()));
442
443 call_with_request<session_created>("CreateSession", {}, params);
444
445 run_loop();
446}
447
448ScreenCastPortal::ScreenCastPortal():
449 impl(std::make_unique<ScreenCastPortal_impl>())
450{
451
452}
453
454ScreenCastPortal::~ScreenCastPortal(){
455 if(thread.joinable()){
456 impl->quit_loop();
457 thread.join();
458 }
459}
460
461ScreenCastPortalResult ScreenCastPortal::run(std::string restore_file, bool show_cursor){
462 auto future = impl->get_future();
463
464 thread = std::thread(&ScreenCastPortal_impl::run, impl.get(),
465 restore_file, show_cursor);
466
467 auto error = future.get();
468 if(!error.empty()){
469 log_msg(LOG_LEVEL_ERROR, MOD_NAME "%s\n", error.c_str());
470 return {-1, UINT32_MAX};
471 }
472
473 return {impl->get_pw_fd(), impl->get_pw_node()};
474}
GDBusConnection * dbus_connection() const
static void pipewire_opened(GObject *source, GAsyncResult *res, void *user_data)
void call_with_request(const char *method_name, std::initializer_list< GVariant * > arguments, GVariantBuilder &params_builder)
static void session_created(uint32_t response, GVariant *results, ScreenCastPortal_impl *s)
const std::string & session_token() const
GDBusConnection * connection
static constexpr uint32_t REQUEST_RESPONSE_CANCELLED_BY_USER
static void sources_selected(uint32_t response, GVariant *results, ScreenCastPortal_impl *s)
void run(std::string restore_file, bool show_cursor)
session_path_t session
const std::string & session_path() const
static constexpr uint32_t REQUEST_RESPONSE_OTHER_ERROR
const std::string & sender_name() const
static constexpr uint32_t REQUEST_RESPONSE_OK
static void started(uint32_t response, GVariant *results, void *user_data)
GDBusProxy * screencast_proxy
std::future< std::string > get_future()
std::promise< std::string > promise
Definition misc.h:94
static void on_portal_session_closed(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data)
#define MOD_NAME
void CallbackWrapper(GDBusConnection *connection, const gchar *sender_name, const gchar *object_path, const gchar *interface_name, const gchar *signal_name, GVariant *parameters, gpointer user_data)
void log_msg(int level, const char *format,...)
Definition debug.cpp:130
#define LOG_LEVEL_INFO
normal reporting
Definition debug.h:62
#define LOG_LEVEL_ERROR
general errors
Definition debug.h:59
#define LOG_LEVEL_VERBOSE
Definition debug.h:63
#define LOG(level)
Definition debug.h:313
static struct @19 params[100]
#define LOG_LEVEL_DEBUG
Definition kernels.cu:48
HRESULT __stdcall(* func)()
Definition screen_win.c:325
const char * name
Definition settings.cpp:193
Definition rtp.c:206
Definition rtp.c:253