1 /*
2 * Copyright (C) 2018, Matthias Clasen
3 *
4 * This file is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as
6 * published by the Free Software Foundation, version 3.0 of the
7 * License.
8 *
9 * This file is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * SPDX-License-Identifier: LGPL-3.0-only
18 */
19
20 #include "config.h"
21
22 #include <gio/gunixfdlist.h>
23
24 #include "remote.h"
25 #include "portal-private.h"
26 #include "session-private.h"
27
28 typedef struct {
29 XdpPortal *portal;
30 char *id;
31 XdpSessionType type;
32 XdpDeviceType devices;
33 XdpOutputType outputs;
34 XdpCursorMode cursor_mode;
35 XdpPersistMode persist_mode;
36 char *restore_token;
37 gboolean multiple;
38 guint signal_id;
39 GTask *task;
40 char *request_path;
41 guint cancelled_id;
42 } CreateCall;
43
44 static void
create_call_free(CreateCall * call)45 create_call_free (CreateCall *call)
46 {
47 if (call->signal_id)
48 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
49
50 if (call->cancelled_id)
51 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
52
53 g_free (call->request_path);
54 g_free (call->restore_token);
55
56 g_object_unref (call->portal);
57 g_object_unref (call->task);
58
59 g_free (call->id);
60
61 g_free (call);
62 }
63
64 static void
sources_selected(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)65 sources_selected (GDBusConnection *bus,
66 const char *sender_name,
67 const char *object_path,
68 const char *interface_name,
69 const char *signal_name,
70 GVariant *parameters,
71 gpointer data)
72 {
73 CreateCall *call = data;
74 guint32 response;
75 g_autoptr(GVariant) ret = NULL;
76
77 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
78
79 if (response == 0)
80 {
81 XdpSession *session;
82
83 session = _xdp_session_new (call->portal, call->id, call->type);
84 g_task_return_pointer (call->task, session, g_object_unref);
85 }
86 else if (response == 1)
87 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Screencast SelectSources() canceled");
88 else if (response == 2)
89 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Screencast SelectSources() failed");
90
91 create_call_free (call);
92 }
93
94 static void
call_returned(GObject * object,GAsyncResult * result,gpointer data)95 call_returned (GObject *object,
96 GAsyncResult *result,
97 gpointer data)
98 {
99 CreateCall *call = data;
100 GError *error = NULL;
101 g_autoptr(GVariant) ret = NULL;
102
103 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
104 if (error)
105 {
106 g_task_return_error (call->task, error);
107 create_call_free (call);
108 }
109 }
110
111 static void
select_sources(CreateCall * call)112 select_sources (CreateCall *call)
113 {
114 GVariantBuilder options;
115 g_autofree char *token = NULL;
116 g_autofree char *handle = NULL;
117
118 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
119 handle = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
120 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
121 PORTAL_BUS_NAME,
122 REQUEST_INTERFACE,
123 "Response",
124 handle,
125 NULL,
126 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
127 sources_selected,
128 call,
129 NULL);
130
131 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
132 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
133 g_variant_builder_add (&options, "{sv}", "types", g_variant_new_uint32 (call->outputs));
134 g_variant_builder_add (&options, "{sv}", "multiple", g_variant_new_boolean (call->multiple));
135 g_variant_builder_add (&options, "{sv}", "cursor_mode", g_variant_new_uint32 (call->cursor_mode));
136 if (call->portal->screencast_interface_version >= 4)
137 {
138 g_variant_builder_add (&options, "{sv}", "persist_mode", g_variant_new_uint32 (call->persist_mode));
139 if (call->restore_token)
140 g_variant_builder_add (&options, "{sv}", "restore_token", g_variant_new_string (call->restore_token));
141 }
142 g_dbus_connection_call (call->portal->bus,
143 PORTAL_BUS_NAME,
144 PORTAL_OBJECT_PATH,
145 "org.freedesktop.portal.ScreenCast",
146 "SelectSources",
147 g_variant_new ("(oa{sv})", call->id, &options),
148 NULL,
149 G_DBUS_CALL_FLAGS_NONE,
150 -1,
151 g_task_get_cancellable (call->task),
152 call_returned,
153 call);
154 }
155
156 static void
devices_selected(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)157 devices_selected (GDBusConnection *bus,
158 const char *sender_name,
159 const char *object_path,
160 const char *interface_name,
161 const char *signal_name,
162 GVariant *parameters,
163 gpointer data)
164 {
165 CreateCall *call = data;
166 guint32 response;
167 g_autoptr(GVariant) ret = NULL;
168
169 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
170
171 if (response == 0)
172 {
173 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
174 call->signal_id = 0;
175
176 if (call->outputs != 0)
177 select_sources (call);
178 else
179 {
180 g_task_return_pointer (call->task, _xdp_session_new (call->portal, call->id, call->type), g_object_unref);
181 create_call_free (call);
182 }
183 }
184 else if (response == 1)
185 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Remote desktop SelectDevices() canceled");
186 else if (response == 2)
187 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "Remote desktop SelectDevices() failed");
188
189 if (response != 0)
190 create_call_free (call);
191 }
192
193 static void
select_devices(CreateCall * call)194 select_devices (CreateCall *call)
195 {
196 GVariantBuilder options;
197 g_autofree char *token = NULL;
198 g_autofree char *handle = NULL;
199
200 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
201 handle = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
202 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
203 PORTAL_BUS_NAME,
204 REQUEST_INTERFACE,
205 "Response",
206 handle,
207 NULL,
208 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
209 devices_selected,
210 call,
211 NULL);
212
213 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
214 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
215 g_variant_builder_add (&options, "{sv}", "types", g_variant_new_uint32 (call->devices));
216 g_dbus_connection_call (call->portal->bus,
217 PORTAL_BUS_NAME,
218 PORTAL_OBJECT_PATH,
219 "org.freedesktop.portal.RemoteDesktop",
220 "SelectDevices",
221 g_variant_new ("(oa{sv})", call->id, &options),
222 NULL,
223 G_DBUS_CALL_FLAGS_NONE,
224 -1,
225 g_task_get_cancellable (call->task),
226 call_returned,
227 call);
228 }
229
230 static void
session_created(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)231 session_created (GDBusConnection *bus,
232 const char *sender_name,
233 const char *object_path,
234 const char *interface_name,
235 const char *signal_name,
236 GVariant *parameters,
237 gpointer data)
238 {
239 CreateCall *call = data;
240 guint32 response;
241 g_autoptr(GVariant) ret = NULL;
242
243 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
244
245 if (response == 0)
246 {
247 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
248 call->signal_id = 0;
249
250 if (call->type == XDP_SESSION_REMOTE_DESKTOP)
251 select_devices (call);
252 else
253 select_sources (call);
254 }
255 else if (response == 1)
256 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "CreateSession canceled");
257 else if (response == 2)
258 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED, "CreateSession failed");
259
260 if (response != 0)
261 create_call_free (call);
262 }
263
264 static void
create_cancelled_cb(GCancellable * cancellable,gpointer data)265 create_cancelled_cb (GCancellable *cancellable,
266 gpointer data)
267 {
268 CreateCall *call = data;
269
270 g_dbus_connection_call (call->portal->bus,
271 PORTAL_BUS_NAME,
272 call->request_path,
273 REQUEST_INTERFACE,
274 "Close",
275 NULL,
276 NULL,
277 G_DBUS_CALL_FLAGS_NONE,
278 -1,
279 NULL, NULL, NULL);
280 }
281
282 static void
create_session(CreateCall * call)283 create_session (CreateCall *call)
284 {
285 GVariantBuilder options;
286 g_autofree char *token = NULL;
287 g_autofree char *session_token = NULL;
288 GCancellable *cancellable;
289
290 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
291 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
292 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
293 PORTAL_BUS_NAME,
294 REQUEST_INTERFACE,
295 "Response",
296 call->request_path,
297 NULL,
298 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
299 session_created,
300 call,
301 NULL);
302
303 session_token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
304 call->id = g_strconcat (SESSION_PATH_PREFIX, call->portal->sender, "/", session_token, NULL);
305
306 cancellable = g_task_get_cancellable (call->task);
307 if (cancellable)
308 call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (create_cancelled_cb), call);
309
310 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
311 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
312 g_variant_builder_add (&options, "{sv}", "session_handle_token", g_variant_new_string (session_token));
313 g_dbus_connection_call (call->portal->bus,
314 PORTAL_BUS_NAME,
315 PORTAL_OBJECT_PATH,
316 call->type == XDP_SESSION_REMOTE_DESKTOP ?
317 "org.freedesktop.portal.RemoteDesktop" :
318 "org.freedesktop.portal.ScreenCast",
319 "CreateSession",
320 g_variant_new ("(a{sv})", &options),
321 NULL,
322 G_DBUS_CALL_FLAGS_NONE,
323 -1,
324 cancellable,
325 call_returned,
326 call);
327 }
328
329 static void
get_version_returned(GObject * object,GAsyncResult * result,gpointer data)330 get_version_returned (GObject *object,
331 GAsyncResult *result,
332 gpointer data)
333 {
334 CreateCall *call = data;
335 GError *error = NULL;
336 g_autoptr(GVariant) version_variant = NULL;
337 g_autoptr(GVariant) ret = NULL;
338
339 ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (object), result, &error);
340 if (error)
341 {
342 g_task_return_error (call->task, error);
343 create_call_free (call);
344 return;
345 }
346
347 g_variant_get_child (ret, 0, "v", &version_variant);
348 call->portal->screencast_interface_version = g_variant_get_uint32 (version_variant);
349
350 create_session (call);
351 }
352
353 static void
get_screencast_interface_version(CreateCall * call)354 get_screencast_interface_version (CreateCall *call)
355 {
356 g_dbus_connection_call (call->portal->bus,
357 PORTAL_BUS_NAME,
358 PORTAL_OBJECT_PATH,
359 "org.freedesktop.DBus.Properties",
360 "Get",
361 g_variant_new ("(ss)", "org.freedesktop.portal.ScreenCast", "version"),
362 NULL,
363 G_DBUS_CALL_FLAGS_NONE,
364 -1,
365 g_task_get_cancellable (call->task),
366 get_version_returned,
367 call);
368 }
369
370 /**
371 * xdp_portal_create_screencast_session:
372 * @portal: a [class@Portal]
373 * @outputs: which kinds of source to offer in the dialog
374 * @flags: options for this call
375 * @cursor_mode: the cursor mode of the session
376 * @persist_mode: the persist mode of the session
377 * @restore_token: (nullable): the token of a previous screencast session to restore
378 * @cancellable: (nullable): optional [class@Gio.Cancellable]
379 * @callback: (scope async): a callback to call when the request is done
380 * @data: (closure): data to pass to @callback
381 *
382 * Creates a session for a screencast.
383 *
384 * When the request is done, @callback will be called. You can then
385 * call [method@Portal.create_screencast_session_finish] to get the results.
386 */
387 void
xdp_portal_create_screencast_session(XdpPortal * portal,XdpOutputType outputs,XdpScreencastFlags flags,XdpCursorMode cursor_mode,XdpPersistMode persist_mode,const char * restore_token,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)388 xdp_portal_create_screencast_session (XdpPortal *portal,
389 XdpOutputType outputs,
390 XdpScreencastFlags flags,
391 XdpCursorMode cursor_mode,
392 XdpPersistMode persist_mode,
393 const char *restore_token,
394 GCancellable *cancellable,
395 GAsyncReadyCallback callback,
396 gpointer data)
397 {
398 CreateCall *call;
399
400 g_return_if_fail (XDP_IS_PORTAL (portal));
401 g_return_if_fail ((flags & ~(XDP_SCREENCAST_FLAG_MULTIPLE)) == 0);
402
403 call = g_new0 (CreateCall, 1);
404 call->portal = g_object_ref (portal);
405 call->type = XDP_SESSION_SCREENCAST;
406 call->devices = XDP_DEVICE_NONE;
407 call->outputs = outputs;
408 call->cursor_mode = cursor_mode;
409 call->persist_mode = persist_mode;
410 call->restore_token = g_strdup (restore_token);
411 call->multiple = (flags & XDP_SCREENCAST_FLAG_MULTIPLE) != 0;
412 call->task = g_task_new (portal, cancellable, callback, data);
413
414 if (portal->screencast_interface_version == 0)
415 get_screencast_interface_version (call);
416 else
417 create_session (call);
418 }
419
420 /**
421 * xdp_portal_create_screencast_session_finish:
422 * @portal: a [class@Portal]
423 * @result: a [iface@Gio.AsyncResult]
424 * @error: return location for an error
425 *
426 * Finishes the create-screencast request, and returns an [class@Session].
427 *
428 * Returns: (transfer full): a [class@Session]
429 */
430 XdpSession *
xdp_portal_create_screencast_session_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)431 xdp_portal_create_screencast_session_finish (XdpPortal *portal,
432 GAsyncResult *result,
433 GError **error)
434 {
435 XdpSession *session;
436
437 g_return_val_if_fail (XDP_IS_PORTAL (portal), NULL);
438 g_return_val_if_fail (g_task_is_valid (result, portal), NULL);
439
440 session = g_task_propagate_pointer (G_TASK (result), error);
441 if (session)
442 return g_object_ref (session);
443 else
444 return NULL;
445 }
446
447 /**
448 * xdp_portal_create_remote_desktop_session:
449 * @portal: a [class@Portal]
450 * @devices: which kinds of input devices to ofer in the new dialog
451 * @outputs: which kinds of source to offer in the dialog
452 * @flags: options for this call
453 * @cursor_mode: the cursor mode of the session
454 * @cancellable: (nullable): optional [class@Gio.Cancellable]
455 * @callback: (scope async): a callback to call when the request is done
456 * @data: (closure): data to pass to @callback
457 *
458 * Creates a session for remote desktop.
459 *
460 * When the request is done, @callback will be called. You can then
461 * call [method@Portal.create_remote_desktop_session_finish] to get the results.
462 */
463 void
xdp_portal_create_remote_desktop_session(XdpPortal * portal,XdpDeviceType devices,XdpOutputType outputs,XdpRemoteDesktopFlags flags,XdpCursorMode cursor_mode,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)464 xdp_portal_create_remote_desktop_session (XdpPortal *portal,
465 XdpDeviceType devices,
466 XdpOutputType outputs,
467 XdpRemoteDesktopFlags flags,
468 XdpCursorMode cursor_mode,
469 GCancellable *cancellable,
470 GAsyncReadyCallback callback,
471 gpointer data)
472 {
473 CreateCall *call;
474
475 g_return_if_fail (XDP_IS_PORTAL (portal));
476 g_return_if_fail ((flags & ~(XDP_REMOTE_DESKTOP_FLAG_MULTIPLE)) == 0);
477
478 call = g_new0 (CreateCall, 1);
479 call->portal = g_object_ref (portal);
480 call->type = XDP_SESSION_REMOTE_DESKTOP;
481 call->devices = devices;
482 call->outputs = outputs;
483 call->cursor_mode = cursor_mode;
484 call->persist_mode = XDP_PERSIST_MODE_NONE;
485 call->restore_token = NULL;
486 call->multiple = (flags & XDP_REMOTE_DESKTOP_FLAG_MULTIPLE) != 0;
487 call->task = g_task_new (portal, cancellable, callback, data);
488
489 create_session (call);
490 }
491
492 /**
493 * xdp_portal_create_remote_desktop_session_finish:
494 * @portal: a [class@Portal]
495 * @result: a [iface@Gio.AsyncResult]
496 * @error: return location for an error
497 *
498 * Finishes the create-remote-desktop request, and returns an [class@Session].
499 *
500 * Returns: (transfer full): a [class@Session]
501 */
502 XdpSession *
xdp_portal_create_remote_desktop_session_finish(XdpPortal * portal,GAsyncResult * result,GError ** error)503 xdp_portal_create_remote_desktop_session_finish (XdpPortal *portal,
504 GAsyncResult *result,
505 GError **error)
506 {
507 XdpSession *session;
508
509 g_return_val_if_fail (XDP_IS_PORTAL (portal), NULL);
510 g_return_val_if_fail (g_task_is_valid (result, portal), NULL);
511
512 session = g_task_propagate_pointer (G_TASK (result), error);
513
514 if (session)
515 return g_object_ref (session);
516 else
517 return NULL;
518 }
519
520
521 typedef struct {
522 XdpPortal *portal;
523 XdpSession *session;
524 XdpParent *parent;
525 char *parent_handle;
526 guint signal_id;
527 GTask *task;
528 char *request_path;
529 guint cancelled_id;
530 } StartCall;
531
532 static void
start_call_free(StartCall * call)533 start_call_free (StartCall *call)
534 {
535 if (call->parent)
536 {
537 call->parent->parent_unexport (call->parent);
538 xdp_parent_free (call->parent);
539 }
540 g_free (call->parent_handle);
541
542 if (call->signal_id)
543 g_dbus_connection_signal_unsubscribe (call->portal->bus, call->signal_id);
544
545 if (call->cancelled_id)
546 g_signal_handler_disconnect (g_task_get_cancellable (call->task), call->cancelled_id);
547
548 g_free (call->request_path);
549
550 g_object_unref (call->session);
551 g_object_unref (call->portal);
552 g_object_unref (call->task);
553
554 g_free (call);
555 }
556
557 static void
session_started(GDBusConnection * bus,const char * sender_name,const char * object_path,const char * interface_name,const char * signal_name,GVariant * parameters,gpointer data)558 session_started (GDBusConnection *bus,
559 const char *sender_name,
560 const char *object_path,
561 const char *interface_name,
562 const char *signal_name,
563 GVariant *parameters,
564 gpointer data)
565 {
566 StartCall *call = data;
567 guint32 response;
568 g_autoptr(GVariant) ret = NULL;
569
570 g_variant_get (parameters, "(u@a{sv})", &response, &ret);
571
572 _xdp_session_set_session_state (call->session, response == 0 ? XDP_SESSION_ACTIVE
573 : XDP_SESSION_CLOSED);
574
575 if (response == 0)
576 {
577 guint32 devices;
578 GVariant *streams;
579
580 if (!g_variant_lookup (ret, "persist_mode", "u", &call->session->persist_mode))
581 call->session->persist_mode = XDP_PERSIST_MODE_NONE;
582 if (!g_variant_lookup (ret, "restore_token", "s", &call->session->restore_token))
583 call->session->restore_token = NULL;
584 if (g_variant_lookup (ret, "devices", "u", &devices))
585 _xdp_session_set_devices (call->session, devices);
586 if (g_variant_lookup (ret, "streams", "@a(ua{sv})", &streams))
587 _xdp_session_set_streams (call->session, streams);
588
589 g_task_return_boolean (call->task, TRUE);
590 }
591 else if (response == 1)
592 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_CANCELLED,
593 call->session->type == XDP_SESSION_REMOTE_DESKTOP ?
594 "Remote desktop canceled" : "Screencast canceled");
595 else if (response == 2)
596 g_task_return_new_error (call->task, G_IO_ERROR, G_IO_ERROR_FAILED,
597 call->session->type == XDP_SESSION_REMOTE_DESKTOP ?
598 "Remote desktop failed" : "Screencast failed");
599
600 start_call_free (call);
601 }
602
603 static void start_session (StartCall *call);
604
605 static void
parent_exported(XdpParent * parent,const char * handle,gpointer data)606 parent_exported (XdpParent *parent,
607 const char *handle,
608 gpointer data)
609 {
610 StartCall *call = data;
611 call->parent_handle = g_strdup (handle);
612 start_session (call);
613 }
614
615 static void
start_cancelled_cb(GCancellable * cancellable,gpointer data)616 start_cancelled_cb (GCancellable *cancellable,
617 gpointer data)
618 {
619 StartCall *call = data;
620
621 g_dbus_connection_call (call->portal->bus,
622 PORTAL_BUS_NAME,
623 call->request_path,
624 REQUEST_INTERFACE,
625 "Close",
626 NULL,
627 NULL,
628 G_DBUS_CALL_FLAGS_NONE,
629 -1,
630 NULL, NULL, NULL);
631 }
632
633 static void
start_session(StartCall * call)634 start_session (StartCall *call)
635 {
636 GVariantBuilder options;
637 g_autofree char *token = NULL;
638 GCancellable *cancellable;
639
640 if (call->parent_handle == NULL)
641 {
642 call->parent->parent_export (call->parent, parent_exported, call);
643 return;
644 }
645
646 token = g_strdup_printf ("portal%d", g_random_int_range (0, G_MAXINT));
647 call->request_path = g_strconcat (REQUEST_PATH_PREFIX, call->portal->sender, "/", token, NULL);
648 call->signal_id = g_dbus_connection_signal_subscribe (call->portal->bus,
649 PORTAL_BUS_NAME,
650 REQUEST_INTERFACE,
651 "Response",
652 call->request_path,
653 NULL,
654 G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
655 session_started,
656 call,
657 NULL);
658
659 cancellable = g_task_get_cancellable (call->task);
660 if (cancellable)
661 call->cancelled_id = g_signal_connect (cancellable, "cancelled", G_CALLBACK (start_cancelled_cb), call);
662
663 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
664 g_variant_builder_add (&options, "{sv}", "handle_token", g_variant_new_string (token));
665 g_dbus_connection_call (call->portal->bus,
666 PORTAL_BUS_NAME,
667 PORTAL_OBJECT_PATH,
668 call->session->type == XDP_SESSION_REMOTE_DESKTOP
669 ? "org.freedesktop.portal.RemoteDesktop"
670 : "org.freedesktop.portal.ScreenCast",
671 "Start",
672 g_variant_new ("(osa{sv})",
673 call->session->id,
674 call->parent_handle,
675 &options),
676 NULL,
677 G_DBUS_CALL_FLAGS_NONE,
678 -1,
679 cancellable,
680 NULL,
681 NULL);
682 }
683
684 /**
685 * xdp_session_start:
686 * @session: a [class@Session] in initial state
687 * @parent: (nullable): parent window information
688 * @cancellable: (nullable): optional [class@Gio.Cancellable]
689 * @callback: (scope async): a callback to call when the request is done
690 * @data: (closure): data to pass to @callback
691 *
692 * Starts the session.
693 *
694 * When the request is done, @callback will be called. You can then
695 * call [method@Session.start_finish] to get the results.
696 */
697 void
xdp_session_start(XdpSession * session,XdpParent * parent,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)698 xdp_session_start (XdpSession *session,
699 XdpParent *parent,
700 GCancellable *cancellable,
701 GAsyncReadyCallback callback,
702 gpointer data)
703 {
704 StartCall *call = NULL;
705
706 g_return_if_fail (XDP_IS_SESSION (session));
707
708 call = g_new0 (StartCall, 1);
709 call->portal = g_object_ref (session->portal);
710 call->session = g_object_ref (session);
711 if (parent)
712 call->parent = xdp_parent_copy (parent);
713 else
714 call->parent_handle = g_strdup ("");
715 call->task = g_task_new (session, cancellable, callback, data);
716
717 start_session (call);
718 }
719
720 /**
721 * xdp_session_start_finish:
722 * @session: a [class@Session]
723 * @result: a [iface@Gio.AsyncResult]
724 * @error: return location for an error
725 *
726 * Finishes the session-start request.
727 *
728 * Returns: `TRUE` if the session was started successfully.
729 */
730 gboolean
xdp_session_start_finish(XdpSession * session,GAsyncResult * result,GError ** error)731 xdp_session_start_finish (XdpSession *session,
732 GAsyncResult *result,
733 GError **error)
734 {
735 g_return_val_if_fail (XDP_IS_SESSION (session), FALSE);
736 g_return_val_if_fail (g_task_is_valid (result, session), FALSE);
737
738 return g_task_propagate_boolean (G_TASK (result), error);
739 }
740
741 /**
742 * xdp_session_close:
743 * @session: an active [class@Session]
744 *
745 * Closes the session.
746 */
747 void
xdp_session_close(XdpSession * session)748 xdp_session_close (XdpSession *session)
749 {
750 g_return_if_fail (XDP_IS_SESSION (session));
751
752 g_dbus_connection_call (session->portal->bus,
753 PORTAL_BUS_NAME,
754 session->id,
755 SESSION_INTERFACE,
756 "Close",
757 NULL,
758 NULL, 0, -1, NULL, NULL, NULL);
759
760 _xdp_session_set_session_state (session, XDP_SESSION_CLOSED);
761 g_signal_emit_by_name (session, "closed");
762 }
763
764 /**
765 * xdp_session_open_pipewire_remote:
766 * @session: a [class@Session]
767 *
768 * Opens a file descriptor to the pipewire remote where the screencast
769 * streams are available. The file descriptor should be used to create
770 * a pw_remote object, by using pw_remote_connect_fd(). Only the
771 * screencast stream nodes will be available from this pipewire node.
772 *
773 * Returns: the file descriptor
774 */
775 int
xdp_session_open_pipewire_remote(XdpSession * session)776 xdp_session_open_pipewire_remote (XdpSession *session)
777 {
778 GVariantBuilder options;
779 g_autoptr(GError) error = NULL;
780 g_autoptr(GVariant) ret = NULL;
781 g_autoptr(GUnixFDList) fd_list = NULL;
782 int fd_out;
783
784 g_return_val_if_fail (XDP_IS_SESSION (session), -1);
785
786 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
787 ret = g_dbus_connection_call_with_unix_fd_list_sync (session->portal->bus,
788 PORTAL_BUS_NAME,
789 PORTAL_OBJECT_PATH,
790 "org.freedesktop.portal.ScreenCast",
791 "OpenPipeWireRemote",
792 g_variant_new ("(oa{sv})", session->id, &options),
793 G_VARIANT_TYPE ("(h)"),
794 G_DBUS_CALL_FLAGS_NONE,
795 -1,
796 NULL,
797 &fd_list,
798 NULL,
799 &error);
800 if (ret == NULL)
801 {
802 g_warning ("Failed to get pipewire fd: %s", error->message);
803 return -1;
804 }
805
806 g_variant_get (ret, "(h)", &fd_out);
807
808 return g_unix_fd_list_get (fd_list, fd_out, NULL);
809 }
810
811 /**
812 * xdp_session_pointer_motion:
813 * @session: a [class@Session]
814 * @dx: relative horizontal movement
815 * @dy: relative vertical movement
816 *
817 * Moves the pointer from its current position.
818 *
819 * May only be called on a remote desktop session
820 * with `XDP_DEVICE_POINTER` access.
821 */
822 void
xdp_session_pointer_motion(XdpSession * session,double dx,double dy)823 xdp_session_pointer_motion (XdpSession *session,
824 double dx,
825 double dy)
826 {
827 GVariantBuilder options;
828
829 g_return_if_fail (XDP_IS_SESSION (session) &&
830 session->type == XDP_SESSION_REMOTE_DESKTOP &&
831 session->state == XDP_SESSION_ACTIVE &&
832 ((session->devices & XDP_DEVICE_POINTER) != 0));
833
834 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
835 g_dbus_connection_call (session->portal->bus,
836 PORTAL_BUS_NAME,
837 PORTAL_OBJECT_PATH,
838 "org.freedesktop.portal.RemoteDesktop",
839 "NotifyPointerMotion",
840 g_variant_new ("(oa{sv}dd)", session->id, &options, dx, dy),
841 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
842 }
843
844 /**
845 * xdp_session_pointer_position:
846 * @session: a [class@Session]
847 * @stream: the node ID of the pipewire stream the position is relative to
848 * @x: new X position
849 * @y: new Y position
850 *
851 * Moves the pointer to a new position in the given streams logical
852 * coordinate space.
853 *
854 * May only be called on a remote desktop session
855 * with `XDP_DEVICE_POINTER` access.
856 */
857 void
xdp_session_pointer_position(XdpSession * session,guint stream,double x,double y)858 xdp_session_pointer_position (XdpSession *session,
859 guint stream,
860 double x,
861 double y)
862 {
863 GVariantBuilder options;
864
865 g_return_if_fail (XDP_IS_SESSION (session) &&
866 session->type == XDP_SESSION_REMOTE_DESKTOP &&
867 session->state == XDP_SESSION_ACTIVE &&
868 ((session->devices & XDP_DEVICE_POINTER) != 0));
869
870 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
871 g_dbus_connection_call (session->portal->bus,
872 PORTAL_BUS_NAME,
873 PORTAL_OBJECT_PATH,
874 "org.freedesktop.portal.RemoteDesktop",
875 "NotifyPointerMotionAbsolute",
876 g_variant_new ("(oa{sv}udd)", session->id, &options, stream, x, y),
877 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
878 }
879
880 /**
881 * xdp_session_pointer_button:
882 * @session: a [class@Session]
883 * @button: the button
884 * @state: the new state
885 *
886 * Changes the state of the button to @state.
887 *
888 * May only be called on a remote desktop session
889 * with `XDP_DEVICE_POINTER` access.
890 */
891 void
xdp_session_pointer_button(XdpSession * session,int button,XdpButtonState state)892 xdp_session_pointer_button (XdpSession *session,
893 int button,
894 XdpButtonState state)
895 {
896 GVariantBuilder options;
897
898 g_return_if_fail (XDP_IS_SESSION (session) &&
899 session->type == XDP_SESSION_REMOTE_DESKTOP &&
900 session->state == XDP_SESSION_ACTIVE &&
901 ((session->devices & XDP_DEVICE_POINTER) != 0));
902
903 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
904 g_dbus_connection_call (session->portal->bus,
905 PORTAL_BUS_NAME,
906 PORTAL_OBJECT_PATH,
907 "org.freedesktop.portal.RemoteDesktop",
908 "NotifyPointerButton",
909 g_variant_new ("(oa{sv}iu)", session->id, &options, button, state),
910 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
911 }
912
913 /**
914 * xdp_session_pointer_axis:
915 * @session: a [class@Session]
916 * @finish: whether this is the last in a series of related events
917 * @dx: relative axis movement on the X axis
918 * @dy: relative axis movement on the Y axis
919 *
920 * The axis movement from a smooth scroll device, such as a touchpad.
921 * When applicable, the size of the motion delta should be equivalent to
922 * the motion vector of a pointer motion done using the same advice.
923 *
924 * May only be called on a remote desktop session
925 * with `XDP_DEVICE_POINTER` access.
926 */
927 void
xdp_session_pointer_axis(XdpSession * session,gboolean finish,double dx,double dy)928 xdp_session_pointer_axis (XdpSession *session,
929 gboolean finish,
930 double dx,
931 double dy)
932 {
933 GVariantBuilder options;
934
935 g_return_if_fail (XDP_IS_SESSION (session) &&
936 session->type == XDP_SESSION_REMOTE_DESKTOP &&
937 session->state == XDP_SESSION_ACTIVE &&
938 ((session->devices & XDP_DEVICE_POINTER) != 0));
939
940 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
941 g_variant_builder_add (&options, "{sv}", "finish", g_variant_new_boolean (finish));
942 g_dbus_connection_call (session->portal->bus,
943 PORTAL_BUS_NAME,
944 PORTAL_OBJECT_PATH,
945 "org.freedesktop.portal.RemoteDesktop",
946 "NotifyPointerAxis",
947 g_variant_new ("(oa{sv}dd)", session->id, &options, dx, dy),
948 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
949 }
950
951 /**
952 * xdp_session_pointer_axis_discrete:
953 * @session: a [class@Session]
954 * @axis: the axis to change
955 * @steps: number of steps scrolled
956 *
957 * The axis movement from a discrete scroll device.
958 *
959 * May only be called on a remote desktop session
960 * with `XDP_DEVICE_POINTER` access.
961 */
962 void
xdp_session_pointer_axis_discrete(XdpSession * session,XdpDiscreteAxis axis,int steps)963 xdp_session_pointer_axis_discrete (XdpSession *session,
964 XdpDiscreteAxis axis,
965 int steps)
966 {
967 GVariantBuilder options;
968
969 g_return_if_fail (XDP_IS_SESSION (session) &&
970 session->type == XDP_SESSION_REMOTE_DESKTOP &&
971 session->state == XDP_SESSION_ACTIVE &&
972 ((session->devices & XDP_DEVICE_POINTER) != 0));
973
974 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
975 g_dbus_connection_call (session->portal->bus,
976 PORTAL_BUS_NAME,
977 PORTAL_OBJECT_PATH,
978 "org.freedesktop.portal.RemoteDesktop",
979 "NotifyPointerAxisDiscrete",
980 g_variant_new ("(oa{sv}ui)", session->id, &options, axis, steps),
981 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
982 }
983
984 /**
985 * xdp_session_keyboard_key:
986 * @session: a remote desktop [class@Session]
987 * @keysym: whether to interpret @key as a keysym instead of a keycode
988 * @key: the keysym or keycode to change
989 * @state: the new state
990 *
991 * Changes the state of the key to @state.
992 *
993 * May only be called on a remote desktop session
994 * with `XDP_DEVICE_KEYBOARD` access.
995 */
996 void
xdp_session_keyboard_key(XdpSession * session,gboolean keysym,int key,XdpKeyState state)997 xdp_session_keyboard_key (XdpSession *session,
998 gboolean keysym,
999 int key,
1000 XdpKeyState state)
1001 {
1002 GVariantBuilder options;
1003
1004 g_return_if_fail (XDP_IS_SESSION (session) &&
1005 session->type == XDP_SESSION_REMOTE_DESKTOP &&
1006 session->state == XDP_SESSION_ACTIVE &&
1007 ((session->devices & XDP_DEVICE_KEYBOARD) != 0));
1008
1009 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
1010 g_dbus_connection_call (session->portal->bus,
1011 PORTAL_BUS_NAME,
1012 PORTAL_OBJECT_PATH,
1013 "org.freedesktop.portal.RemoteDesktop",
1014 keysym ? "NotifyKeyboardKeysym" : "NotifyKeyboardKeycode",
1015 g_variant_new ("(oa{sv}iu)", session->id, &options, key, state),
1016 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
1017 }
1018
1019 /**
1020 * xdp_session_touch_down:
1021 * @session: a [class@Session]
1022 * @stream: the node ID of the pipewire stream the position is relative to
1023 * @slot: touch slot where the touch point appeared
1024 * @x: new X position
1025 * @y: new Y position
1026 *
1027 * Notify about a new touch down event. The `(x, y)` position
1028 * represents the new touch point position in the streams logical
1029 * coordinate space.
1030 *
1031 * May only be called on a remote desktop session
1032 * with `XDP_DEVICE_TOUCHSCREEN` access.
1033 */
1034 void
xdp_session_touch_down(XdpSession * session,guint stream,guint slot,double x,double y)1035 xdp_session_touch_down (XdpSession *session,
1036 guint stream,
1037 guint slot,
1038 double x,
1039 double y)
1040 {
1041 GVariantBuilder options;
1042
1043 g_return_if_fail (XDP_IS_SESSION (session) &&
1044 session->type == XDP_SESSION_REMOTE_DESKTOP &&
1045 session->state == XDP_SESSION_ACTIVE &&
1046 ((session->devices & XDP_DEVICE_TOUCHSCREEN) != 0));
1047
1048 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
1049 g_dbus_connection_call (session->portal->bus,
1050 PORTAL_BUS_NAME,
1051 PORTAL_OBJECT_PATH,
1052 "org.freedesktop.portal.RemoteDesktop",
1053 "NotifyTouchDown",
1054 g_variant_new ("(oa{sv}uudd)", session->id, &options, stream, slot, x, y),
1055 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
1056 }
1057
1058 /**
1059 * xdp_session_touch_position:
1060 * @session: a [class@Session]
1061 * @stream: the node ID of the pipewire stream the position is relative to
1062 * @slot: touch slot that is changing position
1063 * @x: new X position
1064 * @y: new Y position
1065 *
1066 * Notify about a new touch motion event. The `(x, y)` position
1067 * represents where the touch point position in the streams logical
1068 * coordinate space moved.
1069 *
1070 * May only be called on a remote desktop session
1071 * with `XDP_DEVICE_TOUCHSCREEN` access.
1072 */
1073 void
xdp_session_touch_position(XdpSession * session,guint stream,guint slot,double x,double y)1074 xdp_session_touch_position (XdpSession *session,
1075 guint stream,
1076 guint slot,
1077 double x,
1078 double y)
1079 {
1080 GVariantBuilder options;
1081
1082 g_return_if_fail (XDP_IS_SESSION (session) &&
1083 session->type == XDP_SESSION_REMOTE_DESKTOP &&
1084 session->state == XDP_SESSION_ACTIVE &&
1085 ((session->devices & XDP_DEVICE_TOUCHSCREEN) != 0));
1086
1087 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
1088 g_dbus_connection_call (session->portal->bus,
1089 PORTAL_BUS_NAME,
1090 PORTAL_OBJECT_PATH,
1091 "org.freedesktop.portal.RemoteDesktop",
1092 "NotifyTouchMotion",
1093 g_variant_new ("(oa{sv}uudd)", session->id, &options, stream, slot, x, y),
1094 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
1095 }
1096
1097 /**
1098 * xdp_session_touch_up:
1099 * @session: a [class@Session]
1100 * @slot: touch slot that changed
1101 *
1102 * Notify about a new touch up event.
1103 *
1104 * May only be called on a remote desktop session
1105 * with `XDP_DEVICE_TOUCHSCREEN` access.
1106 */
1107 void
xdp_session_touch_up(XdpSession * session,guint slot)1108 xdp_session_touch_up (XdpSession *session,
1109 guint slot)
1110 {
1111 GVariantBuilder options;
1112
1113 g_return_if_fail (XDP_IS_SESSION (session) &&
1114 session->type == XDP_SESSION_REMOTE_DESKTOP &&
1115 session->state == XDP_SESSION_ACTIVE &&
1116 ((session->devices & XDP_DEVICE_TOUCHSCREEN) != 0));
1117
1118 g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT);
1119 g_dbus_connection_call (session->portal->bus,
1120 PORTAL_BUS_NAME,
1121 PORTAL_OBJECT_PATH,
1122 "org.freedesktop.portal.RemoteDesktop",
1123 "NotifyTouchMotion",
1124 g_variant_new ("(oa{sv}u)", session->id, &options, slot),
1125 NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
1126 }
1127
1128 /**
1129 * xdp_session_get_persist_mode:
1130 * @session: a [class@Session]
1131 *
1132 * Retrieves the effective persist mode of @session.
1133 *
1134 * May only be called after @session is successfully started, i.e. after
1135 * [method@Session.start_finish].
1136 *
1137 * Returns: the effective persist mode of @session
1138 */
1139 XdpPersistMode
xdp_session_get_persist_mode(XdpSession * session)1140 xdp_session_get_persist_mode (XdpSession *session)
1141 {
1142 g_return_val_if_fail (XDP_IS_SESSION (session), XDP_PERSIST_MODE_NONE);
1143 g_return_val_if_fail (session->state == XDP_SESSION_ACTIVE, XDP_PERSIST_MODE_NONE);
1144
1145 return session->persist_mode;
1146 }
1147
1148 /**
1149 * xdp_session_get_restore_token:
1150 * @session: a [class@Session]
1151 *
1152 * Retrieves the restore token of @session.
1153 *
1154 * A restore token will only be available if `XDP_PERSIST_MODE_TRANSIENT`
1155 * or `XDP_PERSIST_MODE_PERSISTENT` was passed when creating the screencast
1156 * session.
1157 *
1158 * Remote desktop sessions cannot be restored.
1159 *
1160 * May only be called after @session is successfully started, i.e. after
1161 * [method@Session.start_finish].
1162 *
1163 * Returns: (nullable): the restore token of @session
1164 */
1165 char *
xdp_session_get_restore_token(XdpSession * session)1166 xdp_session_get_restore_token (XdpSession *session)
1167 {
1168 g_return_val_if_fail (XDP_IS_SESSION (session), NULL);
1169 g_return_val_if_fail (session->state == XDP_SESSION_ACTIVE, NULL);
1170
1171 return g_strdup (session->restore_token);
1172 }
1173