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