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