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; |
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 (str: "expected startup id" ); |
43 | } |
44 | |
45 | static void |
46 | test_app_launch_context_init (TestAppLaunchContext *ctx) |
47 | { |
48 | } |
49 | |
50 | static void |
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; |
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, data: g_file_new_for_uri (uri: "file:///a/b" )); |
89 | files = g_list_append (list: files, data: g_file_new_for_uri (uri: "file:///c/d" )); |
90 | g_app_info_launch (G_APP_INFO (appinfo), files, NULL, NULL); |
91 | g_list_free_full (list: files, free_func: 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 (info: appinfo, action_name: "frob" , NULL); |
97 | current_state = 3; return; case 3: g_assert_cmpstr (action, ==, "frob" ); |
98 | |
99 | g_desktop_app_info_launch_action (info: appinfo, action_name: "tweak" , NULL); |
100 | current_state = 4; return; case 4: g_assert_cmpstr (action, ==, "tweak" ); |
101 | |
102 | g_desktop_app_info_launch_action (info: appinfo, action_name: "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 (object_type: test_app_launch_context_get_type (), NULL); |
111 | g_app_info_launch (G_APP_INFO (appinfo), NULL, context: ctx, NULL); |
112 | g_assert (requested_startup_id); |
113 | requested_startup_id = FALSE; |
114 | g_object_unref (object: 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 (object_type: test_app_launch_context_get_type (), NULL); |
125 | g_desktop_app_info_launch_action (info: appinfo, action_name: "frob" , launch_context: ctx); |
126 | g_assert (requested_startup_id); |
127 | requested_startup_id = FALSE; |
128 | g_object_unref (object: 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 (info: appinfo, action_name: "quit" , NULL); |
135 | current_state = 8; return; case 8: g_assert_not_reached (); |
136 | } |
137 | } |
138 | |
139 | static void |
140 | test_application_frob (GSimpleAction *action, |
141 | GVariant *parameter, |
142 | gpointer user_data) |
143 | { |
144 | g_assert (parameter == NULL); |
145 | saw_action (action: "frob" ); |
146 | } |
147 | |
148 | static void |
149 | test_application_tweak (GSimpleAction *action, |
150 | GVariant *parameter, |
151 | gpointer user_data) |
152 | { |
153 | g_assert (parameter == NULL); |
154 | saw_action (action: "tweak" ); |
155 | } |
156 | |
157 | static void |
158 | test_application_twiddle (GSimpleAction *action, |
159 | GVariant *parameter, |
160 | gpointer user_data) |
161 | { |
162 | g_assert (parameter == NULL); |
163 | saw_action (action: "twiddle" ); |
164 | } |
165 | |
166 | static void |
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 }, |
178 | { "tweak" , test_application_tweak }, |
179 | { "twiddle" , test_application_twiddle }, |
180 | { "quit" , test_application_quit } |
181 | }; |
182 | |
183 | static void |
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 (action: "activate" ); |
190 | } |
191 | |
192 | static void |
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 (uri: "file:///a/b" ); |
204 | g_assert (g_file_equal (files[0], f)); |
205 | g_object_unref (object: f); |
206 | f = g_file_new_for_uri (uri: "file:///c/d" ); |
207 | g_assert (g_file_equal (files[1], f)); |
208 | g_object_unref (object: f); |
209 | |
210 | saw_action (action: "open" ); |
211 | } |
212 | |
213 | static void |
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), entries: app_actions, G_N_ELEMENTS (app_actions), user_data: application); |
220 | } |
221 | |
222 | static void |
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 (dictionary: platform_data, key: "desktop-startup-id" , format_string: "&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 |
238 | test_application_init (TestApplication *app) |
239 | { |
240 | } |
241 | |
242 | static void |
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 |
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 (file_type: G_TEST_DIST, |
260 | first_path: "org.gtk.test.dbusappinfo.desktop" , |
261 | NULL); |
262 | appinfo = g_desktop_app_info_new_from_filename (filename: desktop_file); |
263 | g_assert (appinfo != NULL); |
264 | g_free (mem: desktop_file); |
265 | |
266 | app = g_object_new (object_type: test_application_get_type (), |
267 | first_property_name: "application-id" , "org.gtk.test.dbusappinfo" , |
268 | "flags" , G_APPLICATION_HANDLES_OPEN, |
269 | NULL); |
270 | status = g_application_run (application: app, argc: 1, argv: (gchar **) argv); |
271 | |
272 | g_assert_cmpint (status, ==, 0); |
273 | g_assert_cmpint (current_state, ==, 8); |
274 | |
275 | g_object_unref (object: appinfo); |
276 | g_object_unref (object: app); |
277 | } |
278 | |
279 | static void |
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: &error); |
288 | g_assert_no_error (error); |
289 | |
290 | g_application_release (application: app); |
291 | } |
292 | |
293 | static void |
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 (application: app); |
303 | |
304 | uri = g_filename_to_uri (filename: g_desktop_app_info_get_filename (info: flatpak_appinfo), NULL, NULL); |
305 | g_assert_nonnull (uri); |
306 | uris = g_list_prepend (NULL, data: uri); |
307 | g_app_info_launch_uris_async (G_APP_INFO (flatpak_appinfo), uris, NULL, |
308 | NULL, callback: on_flatpak_launch_uris_finish, user_data: app); |
309 | g_list_free (list: uris); |
310 | g_free (mem: uri); |
311 | } |
312 | |
313 | static void |
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 (format: "on_flatpak_open received file '%s'" , g_file_peek_path (file: files[0])); |
323 | |
324 | /* The file has been exported via the document portal */ |
325 | f = g_file_new_for_uri (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 (object: f); |
328 | } |
329 | |
330 | static 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 (summary: "Test that files launched via Flatpak apps are made available via the document portal." ); |
340 | |
341 | desktop_file = g_test_build_filename (file_type: G_TEST_DIST, |
342 | first_path: "org.gtk.test.dbusappinfo.flatpak.desktop" , |
343 | NULL); |
344 | flatpak_appinfo = g_desktop_app_info_new_from_filename (filename: desktop_file); |
345 | g_assert_nonnull (flatpak_appinfo); |
346 | g_free (mem: desktop_file); |
347 | |
348 | app = g_application_new (application_id: "org.gtk.test.dbusappinfo.flatpak" , |
349 | flags: 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 (application: app, argc: 1, argv: (gchar **) argv); |
355 | g_assert_cmpint (status, ==, 0); |
356 | |
357 | g_object_unref (object: app); |
358 | g_object_unref (object: flatpak_appinfo); |
359 | } |
360 | |
361 | int |
362 | main (int argc, char **argv) |
363 | { |
364 | g_test_init (argc: &argc, argv: &argv, NULL); |
365 | |
366 | g_test_add_func (testpath: "/appinfo/dbusappinfo" , test_func: test_dbus_appinfo); |
367 | g_test_add_func (testpath: "/appinfo/flatpak-doc-export" , test_func: test_flatpak_doc_export); |
368 | |
369 | return session_bus_run (); |
370 | } |
371 | |