1 /*
2 * Copyright © 2013 Canonical Limited
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 *
19 * Authors: Ryan Lortie <desrt@desrt.ca>
20 */
21
22 #include <gio/gio.h>
23 #include <gio/gdesktopappinfo.h>
24
25 #include "gdbus-sessionbus.h"
26
27 static GDesktopAppInfo *appinfo;
28 static int current_state;
29 static gboolean saw_startup_id;
30 static gboolean requested_startup_id;
31
32
33 static GType test_app_launch_context_get_type (void);
34 typedef GAppLaunchContext TestAppLaunchContext;
35 typedef GAppLaunchContextClass TestAppLaunchContextClass;
36 G_DEFINE_TYPE (TestAppLaunchContext, test_app_launch_context, G_TYPE_APP_LAUNCH_CONTEXT)
37
38 static gchar *
39 test_app_launch_context_get_startup_notify_id (GAppLaunchContext *context,
40 GAppInfo *info,
41 GList *uris)
42 {
43 requested_startup_id = TRUE;
44 return g_strdup ("expected startup id");
45 }
46
47 static void
48 test_app_launch_context_init (TestAppLaunchContext *ctx)
49 {
50 }
51
52 static void
53 test_app_launch_context_class_init (GAppLaunchContextClass *class)
54 {
55 class->get_startup_notify_id = test_app_launch_context_get_startup_notify_id;
56 }
57
58 static GType test_application_get_type (void);
59 typedef GApplication TestApplication;
60 typedef GApplicationClass TestApplicationClass;
61 G_DEFINE_TYPE (TestApplication, test_application, G_TYPE_APPLICATION)
62
63 static void
64 saw_action (const gchar *action)
65 {
66 /* This is the main driver of the test. It's a bit of a state
67 * machine.
68 *
69 * Each time some event arrives on the app, it calls here to report
70 * which event it was. The initial activation of the app is what
71 * starts everything in motion (starting from state 0). At each
72 * state, we assert that we receive the expected event, send the next
73 * event, then update the current_state variable so we do the correct
74 * thing next time.
75 */
76
77 switch (current_state)
78 {
79 case 0: g_assert_cmpstr (action, ==, "activate");
80
81 /* Let's try another activation... */
82 g_app_info_launch (G_APP_INFO (appinfo), NULL, NULL, NULL);
83 current_state = 1; return; case 1: g_assert_cmpstr (action, ==, "activate");
84
85
86 /* Now let's try opening some files... */
87 {
88 GList *files;
89
90 files = g_list_prepend (NULL, g_file_new_for_uri ("file:///a/b"));
91 files = g_list_append (files, g_file_new_for_uri ("file:///c/d"));
92 g_app_info_launch (G_APP_INFO (appinfo), files, NULL, NULL);
93 g_list_free_full (files, g_object_unref);
94 }
95 current_state = 2; return; case 2: g_assert_cmpstr (action, ==, "open");
96
97 /* Now action activations... */
98 g_desktop_app_info_launch_action (appinfo, "frob", NULL);
99 current_state = 3; return; case 3: g_assert_cmpstr (action, ==, "frob");
100
101 g_desktop_app_info_launch_action (appinfo, "tweak", NULL);
102 current_state = 4; return; case 4: g_assert_cmpstr (action, ==, "tweak");
103
104 g_desktop_app_info_launch_action (appinfo, "twiddle", NULL);
105 current_state = 5; return; case 5: g_assert_cmpstr (action, ==, "twiddle");
106
107 /* Now launch the app with startup notification */
108 {
109 GAppLaunchContext *ctx;
110
111 g_assert (saw_startup_id == FALSE);
112 ctx = g_object_new (test_app_launch_context_get_type (), NULL);
113 g_app_info_launch (G_APP_INFO (appinfo), NULL, ctx, NULL);
114 g_assert (requested_startup_id);
115 requested_startup_id = FALSE;
116 g_object_unref (ctx);
117 }
118 current_state = 6; return; case 6: g_assert_cmpstr (action, ==, "activate"); g_assert (saw_startup_id);
119 saw_startup_id = FALSE;
120
121 /* Now do the same for an action */
122 {
123 GAppLaunchContext *ctx;
124
125 g_assert (saw_startup_id == FALSE);
126 ctx = g_object_new (test_app_launch_context_get_type (), NULL);
127 g_desktop_app_info_launch_action (appinfo, "frob", ctx);
128 g_assert (requested_startup_id);
129 requested_startup_id = FALSE;
130 g_object_unref (ctx);
131 }
132 current_state = 7; return; case 7: g_assert_cmpstr (action, ==, "frob"); g_assert (saw_startup_id);
133 saw_startup_id = FALSE;
134
135 /* Now quit... */
136 g_desktop_app_info_launch_action (appinfo, "quit", NULL);
137 current_state = 8; return; case 8: g_assert_not_reached ();
138 }
139 }
140
141 static void
142 test_application_frob (GSimpleAction *action,
143 GVariant *parameter,
144 gpointer user_data)
145 {
146 g_assert (parameter == NULL);
147 saw_action ("frob");
148 }
149
150 static void
151 test_application_tweak (GSimpleAction *action,
152 GVariant *parameter,
153 gpointer user_data)
154 {
155 g_assert (parameter == NULL);
156 saw_action ("tweak");
157 }
158
159 static void
160 test_application_twiddle (GSimpleAction *action,
161 GVariant *parameter,
162 gpointer user_data)
163 {
164 g_assert (parameter == NULL);
165 saw_action ("twiddle");
166 }
167
168 static void
169 test_application_quit (GSimpleAction *action,
170 GVariant *parameter,
171 gpointer user_data)
172 {
173 GApplication *application = user_data;
174
175 g_application_quit (application);
176 }
177
178 static const GActionEntry app_actions[] = {
179 { "frob", test_application_frob, NULL, NULL, NULL, { 0 } },
180 { "tweak", test_application_tweak, NULL, NULL, NULL, { 0 } },
181 { "twiddle", test_application_twiddle, NULL, NULL, NULL, { 0 } },
182 { "quit", test_application_quit, NULL, NULL, NULL, { 0 } }
183 };
184
185 static void
186 test_application_activate (GApplication *application)
187 {
188 /* Unbalanced, but that's OK because we will quit() */
189 g_application_hold (application);
190
191 saw_action ("activate");
192 }
193
194 static void
195 test_application_open (GApplication *application,
196 GFile **files,
197 gint n_files,
198 const gchar *hint)
199 {
200 GFile *f;
201
202 g_assert_cmpstr (hint, ==, "");
203
204 g_assert_cmpint (n_files, ==, 2);
205 f = g_file_new_for_uri ("file:///a/b");
206 g_assert (g_file_equal (files[0], f));
207 g_object_unref (f);
208 f = g_file_new_for_uri ("file:///c/d");
209 g_assert (g_file_equal (files[1], f));
210 g_object_unref (f);
211
212 saw_action ("open");
213 }
214
215 static void
216 test_application_startup (GApplication *application)
217 {
218 G_APPLICATION_CLASS (test_application_parent_class)
219 ->startup (application);
220
221 g_action_map_add_action_entries (G_ACTION_MAP (application), app_actions, G_N_ELEMENTS (app_actions), application);
222 }
223
224 static void
225 test_application_before_emit (GApplication *application,
226 GVariant *platform_data)
227 {
228 const gchar *startup_id;
229 gsize i;
230
231 g_assert (!saw_startup_id);
232
233 const gchar *startup_id_keys[] = {
234 "desktop-startup-id",
235 "activation-token",
236 NULL,
237 };
238
239 for (i = 0; startup_id_keys[i] != NULL; i++)
240 {
241 if (!g_variant_lookup (platform_data, startup_id_keys[i], "&s", &startup_id))
242 return;
243
244 g_assert_cmpstr (startup_id, ==, "expected startup id");
245 }
246
247 saw_startup_id = TRUE;
248 }
249
250 static void
251 test_application_init (TestApplication *app)
252 {
253 }
254
255 static void
256 test_application_class_init (GApplicationClass *class)
257 {
258 class->before_emit = test_application_before_emit;
259 class->startup = test_application_startup;
260 class->activate = test_application_activate;
261 class->open = test_application_open;
262 }
263
264 static void
265 test_dbus_appinfo (void)
266 {
267 const gchar *argv[] = { "myapp", NULL };
268 TestApplication *app;
269 int status;
270 gchar *desktop_file = NULL;
271
272 desktop_file = g_test_build_filename (G_TEST_DIST,
273 "org.gtk.test.dbusappinfo.desktop",
274 NULL);
275 appinfo = g_desktop_app_info_new_from_filename (desktop_file);
276 g_assert (appinfo != NULL);
277 g_free (desktop_file);
278
279 app = g_object_new (test_application_get_type (),
280 "application-id", "org.gtk.test.dbusappinfo",
281 "flags", G_APPLICATION_HANDLES_OPEN,
282 NULL);
283 status = g_application_run (app, 1, (gchar **) argv);
284
285 g_assert_cmpint (status, ==, 0);
286 g_assert_cmpint (current_state, ==, 8);
287
288 g_object_unref (appinfo);
289 g_object_unref (app);
290 }
291
292 static void
293 on_flatpak_launch_uris_finish (GObject *object,
294 GAsyncResult *result,
295 gpointer user_data)
296 {
297 GApplication *app = user_data;
298 GError *error = NULL;
299
300 g_app_info_launch_uris_finish (G_APP_INFO (object), result, &error);
301 g_assert_no_error (error);
302
303 g_application_release (app);
304 }
305
306 static void
307 on_flatpak_activate (GApplication *app,
308 gpointer user_data)
309 {
310 GDesktopAppInfo *flatpak_appinfo = user_data;
311 char *uri;
312 GList *uris;
313
314 /* The app will be released in on_flatpak_launch_uris_finish */
315 g_application_hold (app);
316
317 uri = g_filename_to_uri (g_desktop_app_info_get_filename (flatpak_appinfo), NULL, NULL);
318 g_assert_nonnull (uri);
319 uris = g_list_prepend (NULL, uri);
320 g_app_info_launch_uris_async (G_APP_INFO (flatpak_appinfo), uris, NULL,
321 NULL, on_flatpak_launch_uris_finish, app);
322 g_list_free (uris);
323 g_free (uri);
324 }
325
326 static void
327 on_flatpak_open (GApplication *app,
328 GFile **files,
329 gint n_files,
330 const char *hint)
331 {
332 GFile *f;
333
334 g_assert_cmpint (n_files, ==, 1);
335 g_test_message ("on_flatpak_open received file '%s'", g_file_peek_path (files[0]));
336
337 /* The file has been exported via the document portal */
338 f = g_file_new_for_uri ("file:///document-portal/document-id/org.gtk.test.dbusappinfo.flatpak.desktop");
339 g_assert_true (g_file_equal (files[0], f));
340 g_object_unref (f);
341 }
342
343 static void
344 test_flatpak_doc_export (void)
345 {
346 const gchar *argv[] = { "myapp", NULL };
347 gchar *desktop_file = NULL;
348 GDesktopAppInfo *flatpak_appinfo;
349 GApplication *app;
350 int status;
351
352 g_test_summary ("Test that files launched via Flatpak apps are made available via the document portal.");
353
354 desktop_file = g_test_build_filename (G_TEST_DIST,
355 "org.gtk.test.dbusappinfo.flatpak.desktop",
356 NULL);
357 flatpak_appinfo = g_desktop_app_info_new_from_filename (desktop_file);
358 g_assert_nonnull (flatpak_appinfo);
359 g_free (desktop_file);
360
361 app = g_application_new ("org.gtk.test.dbusappinfo.flatpak",
362 G_APPLICATION_HANDLES_OPEN);
363 g_signal_connect (app, "activate", G_CALLBACK (on_flatpak_activate),
364 flatpak_appinfo);
365 g_signal_connect (app, "open", G_CALLBACK (on_flatpak_open), NULL);
366
367 status = g_application_run (app, 1, (gchar **) argv);
368 g_assert_cmpint (status, ==, 0);
369
370 g_object_unref (app);
371 g_object_unref (flatpak_appinfo);
372 }
373
374 static void
375 on_flatpak_launch_invalid_uri_finish (GObject *object,
376 GAsyncResult *result,
377 gpointer user_data)
378 {
379 GApplication *app = user_data;
380 GError *error = NULL;
381
382 g_app_info_launch_uris_finish (G_APP_INFO (object), result, &error);
383 g_assert_no_error (error);
384
385 g_application_release (app);
386 }
387
388 static void
389 on_flatpak_activate_invalid_uri (GApplication *app,
390 gpointer user_data)
391 {
392 GDesktopAppInfo *flatpak_appinfo = user_data;
393 GList *uris;
394
395 /* The app will be released in on_flatpak_launch_uris_finish */
396 g_application_hold (app);
397
398 uris = g_list_prepend (NULL, "file:///hopefully/an/invalid/path.desktop");
399 g_app_info_launch_uris_async (G_APP_INFO (flatpak_appinfo), uris, NULL,
400 NULL, on_flatpak_launch_invalid_uri_finish, app);
401 g_list_free (uris);
402 }
403
404 static void
405 on_flatpak_open_invalid_uri (GApplication *app,
406 GFile **files,
407 gint n_files,
408 const char *hint)
409 {
410 GFile *f;
411
412 g_assert_cmpint (n_files, ==, 1);
413 g_test_message ("on_flatpak_open received file '%s'", g_file_peek_path (files[0]));
414
415 /* The file has been exported via the document portal */
416 f = g_file_new_for_uri ("file:///hopefully/an/invalid/path.desktop");
417 g_assert_true (g_file_equal (files[0], f));
418 g_object_unref (f);
419 }
420
421 static void
422 test_flatpak_missing_doc_export (void)
423 {
424 const gchar *argv[] = { "myapp", NULL };
425 gchar *desktop_file = NULL;
426 GDesktopAppInfo *flatpak_appinfo;
427 GApplication *app;
428 int status;
429
430 g_test_summary ("Test that files launched via Flatpak apps are made available via the document portal.");
431
432 desktop_file = g_test_build_filename (G_TEST_DIST,
433 "org.gtk.test.dbusappinfo.flatpak.desktop",
434 NULL);
435 flatpak_appinfo = g_desktop_app_info_new_from_filename (desktop_file);
436 g_assert_nonnull (flatpak_appinfo);
437
438 app = g_application_new ("org.gtk.test.dbusappinfo.flatpak",
439 G_APPLICATION_HANDLES_OPEN);
440 g_signal_connect (app, "activate", G_CALLBACK (on_flatpak_activate_invalid_uri),
441 flatpak_appinfo);
442 g_signal_connect (app, "open", G_CALLBACK (on_flatpak_open_invalid_uri), NULL);
443
444 status = g_application_run (app, 1, (gchar **) argv);
445 g_assert_cmpint (status, ==, 0);
446
447 g_object_unref (app);
448 g_object_unref (flatpak_appinfo);
449 g_free (desktop_file);
450 }
451
452 int
453 main (int argc, char **argv)
454 {
455 g_test_init (&argc, &argv, NULL);
456
457 g_test_add_func ("/appinfo/dbusappinfo", test_dbus_appinfo);
458 g_test_add_func ("/appinfo/flatpak-doc-export", test_flatpak_doc_export);
459 g_test_add_func ("/appinfo/flatpak-missing-doc-export", test_flatpak_missing_doc_export);
460
461 return session_bus_run ();
462 }