Merge lp:~mterry/ubuntu-app-launch/warn-on-xapp into lp:ubuntu-app-launch/15.10

Proposed by Michael Terry on 2015-11-24
Status: Superseded
Proposed branch: lp:~mterry/ubuntu-app-launch/warn-on-xapp
Merge into: lp:ubuntu-app-launch/15.10
Prerequisite: lp:~mterry/ubuntu-app-launch/fix-ftbfs
Diff against target: 1334 lines (+757/-124)
16 files modified
CMakeLists.txt (+4/-0)
data/com.canonical.UbuntuAppLaunch.xml (+4/-0)
debian/changelog (+8/-0)
debian/libubuntu-app-launch2.symbols (+3/-0)
helpers.c (+130/-23)
helpers.h (+7/-2)
libubuntu-app-launch/click-exec.c (+0/-13)
libubuntu-app-launch/desktop-exec.c (+0/-13)
libubuntu-app-launch/ubuntu-app-launch-trace.tp (+18/-24)
libubuntu-app-launch/ubuntu-app-launch.c (+124/-26)
libubuntu-app-launch/ubuntu-app-launch.h (+41/-2)
tests/CMakeLists.txt (+1/-1)
tests/exec-util-test.cc (+2/-1)
tests/helper-handshake-test.cc (+263/-13)
tests/libual-test.cc (+151/-6)
tools/ubuntu-app-watch.c (+1/-0)
To merge this branch: bzr merge lp:~mterry/ubuntu-app-launch/warn-on-xapp
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing on 2015-12-02
Indicator Applet Developers 2015-11-24 Pending
Review via email: mp+278497@code.launchpad.net

This proposal has been superseded by a proposal from 2015-12-02.

Commit message

Allow Unity to prevent an app from starting and give it as long as it wants to figure that out.

Description of the change

Allow Unity to prevent an app from starting and give it as long as it wants to figure that out.

This branch gives Unity the option to prevent an app from launching by blocking on its answer before actually starting the app and then acting on that answer.

Related branches:
 https://code.launchpad.net/~mterry/unity-api/warn-on-xapp/+merge/277922
 https://code.launchpad.net/~mterry/qtmir/warn-on-xapp/+merge/279172
 https://code.launchpad.net/~mterry/unity8/warn-on-xapp/+merge/277915

== The existing design ==

The current code goes through pains to avoid any knowledge of Unity (despite using the name Unity in several signals). It doesn't connect to it on the bus. And it doesn't restrict who can answer its "I am starting an app" signal.

I'm not sure why it does this, but it does. So the way it communicates is via broadcast signals on the bus. Which gets answered by "someone" (presumably Unity). If there are multiple signal observers on the bus, we continue launching the app as soon as we hear the first response.

It also waits 1s for an answer; if no answer is received, it just launches the app anyway. The signal is more of a courtesy to Unity than a requirement. So if Unity isn't running, exits before it can respond, or is taking too long, UAL just continues.

== New design ==

The new paradigm is that Unity is an approver of which apps can launch. The proximate reason for this MP is that we want to be able to stop legacy apps from launching when not in Desktop mode. But I could imagine other scenarios (parental controls on some apps, or admin privileges needed for some apps, etc). [1]

I also wanted to allow Unity to take as long as it wanted to decide. Because maybe it is taking user input (authenticating as parent maybe) or in the case of a legacy app on the phone, waiting for user to either cancel the app launch or dock the phone (in which case we'd like to continue the app launch).

If Unity is not running, I've assumed we don't want the app to launch (which is a behavior change).

== Changes ==

- So we want to change the API for observers of the "starting" signal to allow for providing an "approved or not" answer. And we want to make it easy to answer in an async manner. Unfortunately, the current API has no state (no objects on which the API acts) to match up callbacks to their answers. So rather than redo the API, I've just added a new API call that app-starting-observers can use to answer any app-starting message: ubuntu_app_launch_observer_finish_app_starting(). It just broadcasts the answer on the bus. If that new method is not called, the app will never finish launching. The only current user of this API should be unity8, for which I have a related branch to use this new API.

- We want to know if Unity is even running (if not, we should reject the app). So I've kept the immediate signal response UnityStartingSignal, so that UAL knows someone is listening and handling the request. If it doesn't hear that signal within 5s, we reject the app. (I think it would be nicer to call com.canonical.Unity.RequestAppStart or similar, which would mean that only Unity could handle the request, would immediately tell us if Unity is running, and would give us a nice error instead of silence on the bus if something is wrong. But I didn't want to change the design of UAL that much, so I've kept the signal dance.)

- As noted above, I've bumped the timeout from 1s to 5s. This is because now we reject by default if the timeout happens, so it's more important that we give unity8 time. And secondly, we're adding an async API, so increasing the potential blocking time shouldn't be as much of a burden.

- We only support listening to the first responder we hear on the bus. This would be easy to extend to support listening to all responders we hear and waiting for all of them to weigh in. But I didn't need that feature yet, so I didn't write it.

- Since we now wait as long as Unity wants, we have to be sensitive to Unity quitting on us. So once we hear UnityStartingSignal, we also listen for NameOwnerChanges on the bus.

- Eventually, Unity gets back to us with a UnityStartingApproved signal that tells us to continue or not.

- Consolidating the handshake logic out of click-exec.c and desktop-exec.c has the side effect of fixing a bug where task_setup failed but we already emitted a UnityStartingBroadcast. And we had no way to cancel the handshake. So Unity would think an app was starting even though it wasn't. Now all the handshaking happens right in a row, without the ability to fail due to desktop parsing or similar.

- I've added an async version of ubuntu_app_launch_start_application so that callers don't have to block on this whole process (even without this branch, there was blocking occurring, waiting up to 5s on signals -- this method should definitively have existed before anyway).

== Footnotes ==

[1] ubuntu_app_launch_start_application is only for unconfined apps, right? Specifically, Touch apps or scopes can't start something via upstart directly themselves, bypassing the above scheme? I *believe* they always have to go the URLDispatcher route. Otherwise, this paradigm wouldn't really work for parental controls.

To post a comment you must log in.
216. By Michael Terry on 2015-12-02

Fix tests to still use old smaller timeout for convenience

Unmerged revisions

216. By Michael Terry on 2015-12-02

Fix tests to still use old smaller timeout for convenience

215. By Michael Terry on 2015-12-02

Bump timeout to 5s

214. By Michael Terry on 2015-11-25

Add async versions of start_application calls

213. By Michael Terry on 2015-11-24

fix tabs and revert some unnecessary wording changes

212. By Michael Terry on 2015-11-24

Wait for unity to get back to us

211. By Michael Terry on 2015-11-23

Allow server-side of handshake to be async by adding ubuntu_app_launch_observer_finish_app_starting

210. By Michael Terry on 2015-11-23

Add 'approved' bool parameter to UnityStartingSignal return signal, so that unity could prevent an app from opening

209. By Michael Terry on 2015-11-23

Fix ftbfs

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2015-08-11 02:45:49 +0000
3+++ CMakeLists.txt 2015-12-02 17:21:30 +0000
4@@ -90,6 +90,10 @@
5 add_library(helpers STATIC helpers.c helpers-shared.c libubuntu-app-launch/recoverable-problem.c)
6 target_link_libraries(helpers ${GIO2_LIBRARIES} ${JSONGLIB_LIBRARIES} ${CLICK_LIBRARIES})
7
8+add_library(helpers-test STATIC helpers.c helpers-shared.c libubuntu-app-launch/recoverable-problem.c)
9+set_target_properties(helpers-test PROPERTIES COMPILE_DEFINITIONS "UAL_TEST_MODE")
10+target_link_libraries(helpers-test ${GIO2_LIBRARIES} ${JSONGLIB_LIBRARIES} ${CLICK_LIBRARIES})
11+
12 ####################
13 # desktop-hook
14 ####################
15
16=== modified file 'data/com.canonical.UbuntuAppLaunch.xml'
17--- data/com.canonical.UbuntuAppLaunch.xml 2015-03-04 14:26:35 +0000
18+++ data/com.canonical.UbuntuAppLaunch.xml 2015-12-02 17:21:30 +0000
19@@ -16,6 +16,10 @@
20 <signal name="UnityStartingSignal">
21 <arg type="s" name="appid" />
22 </signal>
23+ <signal name="UnityStartingApproved">
24+ <arg type="s" name="appid" />
25+ <arg type="b" name="approved" />
26+ </signal>
27 <signal name="ApplicationFailed">
28 <arg type="s" name="appid" />
29 <arg type="s" name="stage" />
30
31=== modified file 'debian/changelog'
32--- debian/changelog 2015-08-17 21:38:34 +0000
33+++ debian/changelog 2015-12-02 17:21:30 +0000
34@@ -1,3 +1,11 @@
35+ubuntu-app-launch (0.6) xenial; urgency=medium
36+
37+ * Bump version because the expectations for callers of
38+ ubuntu_app_launch_observer_add_app_starting has changed (to expect
39+ a further call to ubuntu_app_launch_observer_finish_app_starting)
40+
41+ -- Michael Terry <mterry@ubuntu.com> Mon, 23 Nov 2015 16:25:15 -0500
42+
43 ubuntu-app-launch (0.5+15.10.20150817-0ubuntu1) wily; urgency=medium
44
45 [ CI Train Bot ]
46
47=== modified file 'debian/libubuntu-app-launch2.symbols'
48--- debian/libubuntu-app-launch2.symbols 2015-08-17 21:38:34 +0000
49+++ debian/libubuntu-app-launch2.symbols 2015-12-02 17:21:30 +0000
50@@ -27,10 +27,13 @@
51 ubuntu_app_launch_observer_delete_app_stop@Base 0.4
52 ubuntu_app_launch_observer_delete_helper_started@Base 0.4
53 ubuntu_app_launch_observer_delete_helper_stop@Base 0.4
54+ ubuntu_app_launch_observer_finish_app_starting@Base 0.6
55 ubuntu_app_launch_pause_application@Base 0.4+14.10.20140915.3
56 ubuntu_app_launch_pid_in_app_id@Base 0.4
57 ubuntu_app_launch_resume_application@Base 0.4+14.10.20140915.3
58 ubuntu_app_launch_start_application@Base 0.4
59+ ubuntu_app_launch_start_application_async@Base 0.6
60+ ubuntu_app_launch_start_application_async_test@Base 0.6
61 ubuntu_app_launch_start_application_test@Base 0.4
62 ubuntu_app_launch_start_helper@Base 0.4
63 ubuntu_app_launch_start_multiple_helper@Base 0.4
64
65=== modified file 'helpers.c'
66--- helpers.c 2014-08-08 19:50:53 +0000
67+++ helpers.c 2015-12-02 17:21:30 +0000
68@@ -473,42 +473,146 @@
69 return;
70 }
71
72-static void
73-unity_signal_cb (GDBusConnection * con, const gchar * sender, const gchar * path, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
74-{
75- GMainLoop * mainloop = (GMainLoop *)user_data;
76- g_main_loop_quit(mainloop);
77-}
78-
79 struct _handshake_t {
80 GDBusConnection * con;
81 GMainLoop * mainloop;
82 guint signal_subscribe;
83+ guint approved_subscribe;
84+ guint name_subscribe;
85 guint timeout;
86+ gboolean received;
87+ gchar *receiver;
88+ HandshakeCallback callback;
89+ gpointer user_data;
90+ GDestroyNotify user_data_free;
91 };
92
93+static void
94+handshake_free(handshake_t * handshake)
95+{
96+ if (handshake->timeout != 0) {
97+ g_source_remove(handshake->timeout);
98+ }
99+ if (handshake->mainloop != NULL) {
100+ g_main_loop_unref(handshake->mainloop);
101+ }
102+ if (handshake->signal_subscribe != 0) {
103+ g_dbus_connection_signal_unsubscribe(handshake->con, handshake->signal_subscribe);
104+ }
105+ if (handshake->approved_subscribe != 0) {
106+ g_dbus_connection_signal_unsubscribe(handshake->con, handshake->approved_subscribe);
107+ }
108+ if (handshake->name_subscribe != 0) {
109+ g_dbus_connection_signal_unsubscribe(handshake->con, handshake->name_subscribe);
110+ }
111+ g_free(handshake->receiver);
112+ g_object_unref(handshake->con);
113+
114+ if (handshake->user_data_free != NULL && handshake->user_data != NULL) {
115+ handshake->user_data_free(handshake->user_data);
116+ }
117+
118+ g_free(handshake);
119+}
120+
121+static void
122+handshake_stop(handshake_t * handshake, gboolean approved)
123+{
124+ if (approved) {
125+ handshake->callback(handshake->user_data);
126+ }
127+
128+ if (handshake->mainloop != NULL) {
129+ g_main_loop_quit(handshake->mainloop);
130+ }
131+
132+ handshake_free(handshake);
133+}
134+
135+static void
136+unity_name_cb (GDBusConnection * con, const gchar * sender, const gchar * path, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
137+{
138+ handshake_t * handshake = (handshake_t *)user_data;
139+
140+ if (g_variant_check_format_string(params, "(sss)", FALSE)) {
141+ const gchar *new_name = NULL;
142+ g_variant_get(params, "(ss&s)", NULL, NULL, &new_name);
143+
144+ if (new_name == NULL || new_name[0] == 0) {
145+ // The process we were talking to exited! Let's stop waiting.
146+ handshake_stop(handshake, FALSE);
147+ }
148+ }
149+}
150+
151+static void
152+unity_signal_cb (GDBusConnection * con, const gchar * sender, const gchar * path, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
153+{
154+ handshake_t * handshake = (handshake_t *)user_data;
155+
156+ if (!handshake->received) { // only allow the first responder
157+ handshake->receiver = g_strdup(sender);
158+ handshake->received = TRUE;
159+
160+ handshake->name_subscribe = g_dbus_connection_signal_subscribe(handshake->con,
161+#ifdef UAL_TEST_MODE
162+ NULL, /* accept from any sender, making it easier for tests to fake this signal */
163+#else
164+ "org.freedesktop.DBus", /* sender */
165+#endif
166+ "org.freedesktop.DBus", /* interface */
167+ "NameOwnerChanged", /* signal */
168+ "/org/freedesktop/DBus", /* path */
169+ sender, /* arg0 */
170+ G_DBUS_SIGNAL_FLAGS_NONE,
171+ unity_name_cb, handshake,
172+ NULL); /* user data destroy */
173+ }
174+}
175+
176+static void
177+unity_approved_cb (GDBusConnection * con, const gchar * sender, const gchar * path, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
178+{
179+ handshake_t * handshake = (handshake_t *)user_data;
180+
181+ if (!handshake->received || g_strcmp0(sender, handshake->receiver) != 0) {
182+ return;
183+ }
184+
185+ gboolean approved = FALSE;
186+ if (g_variant_check_format_string(params, "(sb)", FALSE)) {
187+ g_variant_get(params, "(sb)", NULL, &approved);
188+ }
189+
190+ handshake_stop(handshake, approved);
191+}
192+
193 static gboolean
194 unity_too_slow_cb (gpointer user_data)
195 {
196 handshake_t * handshake = (handshake_t *)user_data;
197- g_main_loop_quit(handshake->mainloop);
198 handshake->timeout = 0;
199+ if (!handshake->received) {
200+ handshake_stop(handshake, FALSE);
201+ }
202 return G_SOURCE_REMOVE;
203 }
204
205 handshake_t *
206-starting_handshake_start (const gchar * app_id)
207+starting_handshake_start (const gchar * app_id, HandshakeCallback callback, gpointer user_data, GDestroyNotify user_data_free)
208 {
209 GError * error = NULL;
210 handshake_t * handshake = g_new0(handshake_t, 1);
211
212- handshake->mainloop = g_main_loop_new(NULL, FALSE);
213+ handshake->callback = callback;
214+ handshake->user_data = user_data;
215+ handshake->user_data_free = user_data_free;
216+
217 handshake->con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
218-
219 if (error != NULL) {
220 g_critical("Unable to connect to session bus: %s", error->message);
221 g_error_free(error);
222- g_free(handshake);
223+ handshake_free(handshake);
224 return NULL;
225 }
226
227@@ -520,7 +624,17 @@
228 "/", /* path */
229 app_id, /* arg0 */
230 G_DBUS_SIGNAL_FLAGS_NONE,
231- unity_signal_cb, handshake->mainloop,
232+ unity_signal_cb, handshake,
233+ NULL); /* user data destroy */
234+
235+ handshake->approved_subscribe = g_dbus_connection_signal_subscribe(handshake->con,
236+ NULL, /* sender */
237+ "com.canonical.UbuntuAppLaunch", /* interface */
238+ "UnityStartingApproved", /* signal */
239+ "/", /* path */
240+ app_id, /* arg0 */
241+ G_DBUS_SIGNAL_FLAGS_NONE,
242+ unity_approved_cb, handshake,
243 NULL); /* user data destroy */
244
245 /* Send unfreeze to to Unity */
246@@ -532,8 +646,8 @@
247 g_variant_new("(s)", app_id),
248 &error);
249
250- /* Really, Unity? */
251- handshake->timeout = g_timeout_add_seconds(1, unity_too_slow_cb, handshake);
252+ /* In case Unity is either not running or taking way too long, we early exit after 5s */
253+ handshake->timeout = g_timeout_add_seconds(5, unity_too_slow_cb, handshake);
254
255 return handshake;
256 }
257@@ -544,15 +658,8 @@
258 if (handshake == NULL)
259 return;
260
261+ handshake->mainloop = g_main_loop_new(NULL, FALSE);
262 g_main_loop_run(handshake->mainloop);
263-
264- if (handshake->timeout != 0)
265- g_source_remove(handshake->timeout);
266- g_main_loop_unref(handshake->mainloop);
267- g_dbus_connection_signal_unsubscribe(handshake->con, handshake->signal_subscribe);
268- g_object_unref(handshake->con);
269-
270- g_free(handshake);
271 }
272
273 EnvHandle *
274
275=== modified file 'helpers.h'
276--- helpers.h 2015-07-15 02:32:54 +0000
277+++ helpers.h 2015-12-02 17:21:30 +0000
278@@ -21,6 +21,8 @@
279
280 typedef struct _EnvHandle EnvHandle;
281
282+typedef void (*HandshakeCallback) (gpointer user_data);
283+
284 gboolean app_id_to_triplet (const gchar * app_id,
285 gchar ** package,
286 gchar ** application,
287@@ -45,8 +47,11 @@
288 void env_handle_finish (EnvHandle * handle);
289
290 typedef struct _handshake_t handshake_t;
291-handshake_t * starting_handshake_start (const gchar * app_id);
292-void starting_handshake_wait (handshake_t * handshake);
293+handshake_t * starting_handshake_start (const gchar * app_id,
294+ HandshakeCallback callback,
295+ gpointer user_data,
296+ GDestroyNotify user_data_free);
297+void starting_handshake_wait (handshake_t * handshake);
298
299 GDBusConnection * cgroup_manager_connection (void);
300 void cgroup_manager_unref (GDBusConnection * cgroup_manager);
301
302=== modified file 'libubuntu-app-launch/click-exec.c'
303--- libubuntu-app-launch/click-exec.c 2014-09-17 14:11:59 +0000
304+++ libubuntu-app-launch/click-exec.c 2015-12-02 17:21:30 +0000
305@@ -51,13 +51,6 @@
306
307 GError * error = NULL;
308
309- handshake_t * handshake = starting_handshake_start(app_id);
310- if (handshake == NULL) {
311- g_warning("Unable to setup starting handshake");
312- }
313-
314- ual_tracepoint(click_starting_sent, app_id);
315-
316 gchar * package = NULL;
317 /* 'Parse' the App ID */
318 if (!app_id_to_triplet(app_id, &package, NULL, NULL)) {
319@@ -160,11 +153,5 @@
320 g_key_file_unref(keyfile);
321 g_free(desktopfile);
322
323- ual_tracepoint(handshake_wait, app_id);
324-
325- starting_handshake_wait(handshake);
326-
327- ual_tracepoint(handshake_complete, app_id);
328-
329 return TRUE;
330 }
331
332=== modified file 'libubuntu-app-launch/desktop-exec.c'
333--- libubuntu-app-launch/desktop-exec.c 2015-08-12 02:52:05 +0000
334+++ libubuntu-app-launch/desktop-exec.c 2015-12-02 17:21:30 +0000
335@@ -126,13 +126,6 @@
336
337 ual_tracepoint(desktop_start, app_id);
338
339- handshake_t * handshake = starting_handshake_start(app_id);
340- if (handshake == NULL) {
341- g_warning("Unable to setup starting handshake");
342- }
343-
344- ual_tracepoint(desktop_starting_sent, app_id);
345-
346 gchar * desktopfilename = NULL;
347 GKeyFile * keyfile = NULL;
348 gchar * libertinecontainer = NULL;
349@@ -208,11 +201,5 @@
350
351 g_key_file_free(keyfile);
352
353- ual_tracepoint(handshake_wait, app_id);
354-
355- starting_handshake_wait(handshake);
356-
357- ual_tracepoint(handshake_complete, app_id);
358-
359 return TRUE;
360 }
361
362=== modified file 'libubuntu-app-launch/ubuntu-app-launch-trace.tp'
363--- libubuntu-app-launch/ubuntu-app-launch-trace.tp 2015-07-15 01:42:22 +0000
364+++ libubuntu-app-launch/ubuntu-app-launch-trace.tp 2015-12-02 17:21:30 +0000
365@@ -44,6 +44,24 @@
366 ctf_string(appid, appid)
367 )
368 )
369+TRACEPOINT_EVENT(ubuntu_app_launch, libual_handshake_start,
370+ TP_ARGS(const char *, appid),
371+ TP_FIELDS(
372+ ctf_string(appid, appid)
373+ )
374+)
375+TRACEPOINT_EVENT(ubuntu_app_launch, libual_handshake_wait,
376+ TP_ARGS(const char *, appid),
377+ TP_FIELDS(
378+ ctf_string(appid, appid)
379+ )
380+)
381+TRACEPOINT_EVENT(ubuntu_app_launch, libual_handshake_complete,
382+ TP_ARGS(const char *, appid),
383+ TP_FIELDS(
384+ ctf_string(appid, appid)
385+ )
386+)
387
388 /*******************************
389 LibUAL observers
390@@ -211,12 +229,6 @@
391 ctf_string(appid, appid)
392 )
393 )
394-TRACEPOINT_EVENT(ubuntu_app_launch, click_starting_sent,
395- TP_ARGS(const char *, appid),
396- TP_FIELDS(
397- ctf_string(appid, appid)
398- )
399-)
400 TRACEPOINT_EVENT(ubuntu_app_launch, click_found_pkgdir,
401 TP_ARGS(const char *, appid),
402 TP_FIELDS(
403@@ -247,18 +259,6 @@
404 ctf_string(appid, appid)
405 )
406 )
407-TRACEPOINT_EVENT(ubuntu_app_launch, handshake_wait,
408- TP_ARGS(const char *, appid),
409- TP_FIELDS(
410- ctf_string(appid, appid)
411- )
412-)
413-TRACEPOINT_EVENT(ubuntu_app_launch, handshake_complete,
414- TP_ARGS(const char *, appid),
415- TP_FIELDS(
416- ctf_string(appid, appid)
417- )
418-)
419
420 /*******************************
421 Desktop Exec
422@@ -270,12 +270,6 @@
423 ctf_string(appid, appid)
424 )
425 )
426-TRACEPOINT_EVENT(ubuntu_app_launch, desktop_starting_sent,
427- TP_ARGS(const char *, appid),
428- TP_FIELDS(
429- ctf_string(appid, appid)
430- )
431-)
432 TRACEPOINT_EVENT(ubuntu_app_launch, desktop_found,
433 TP_ARGS(const char *, appid),
434 TP_FIELDS(
435
436=== modified file 'libubuntu-app-launch/ubuntu-app-launch.c'
437--- libubuntu-app-launch/ubuntu-app-launch.c 2015-12-02 17:21:30 +0000
438+++ libubuntu-app-launch/ubuntu-app-launch.c 2015-12-02 17:21:30 +0000
439@@ -71,6 +71,14 @@
440 } app_start_t;
441
442 static void
443+app_start_data_free (app_start_t *data)
444+{
445+ g_free(data->appid);
446+ g_free(data->uris);
447+ g_free(data);
448+}
449+
450+static void
451 application_start_cb (GObject * obj, GAsyncResult * res, gpointer user_data)
452 {
453 app_start_t * data = (app_start_t *)user_data;
454@@ -101,9 +109,7 @@
455 g_error_free(error);
456 }
457
458- g_free(data->appid);
459- g_free(data->uris);
460- g_free(data);
461+ app_start_data_free(data);
462 }
463
464 /* Get the path of the job from Upstart, if we've got it already, we'll just
465@@ -220,8 +226,55 @@
466 }
467 }
468
469+typedef struct {
470+ gchar * jobpath;
471+ GVariant * params;
472+ app_start_t * app_start_data;
473+} handshake_data_t;
474+
475+static void
476+handshake_data_free (handshake_data_t *data)
477+{
478+ g_free(data->jobpath);
479+ g_variant_unref(data->params);
480+ app_start_data_free(data->app_start_data);
481+ g_free(data);
482+}
483+
484+static void
485+handshake_cb (gpointer user_data)
486+{
487+ handshake_data_t *data = (handshake_data_t *) user_data;
488+
489+ GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
490+ g_return_if_fail(con != NULL);
491+
492+ /* Copy app start data, so it can be freed by application_start_cb */
493+ app_start_t * app_data_copy = g_new0(app_start_t, 1);
494+ app_data_copy->appid = g_strdup(data->app_start_data->appid);
495+ app_data_copy->uris = g_strdup(data->app_start_data->uris);
496+
497+ /* Call the job start function */
498+ g_dbus_connection_call(con,
499+ DBUS_SERVICE_UPSTART,
500+ data->jobpath,
501+ DBUS_INTERFACE_UPSTART_JOB,
502+ "Start",
503+ data->params,
504+ NULL,
505+ G_DBUS_CALL_FLAGS_NONE,
506+ -1,
507+ NULL, /* cancelable */
508+ application_start_cb,
509+ app_data_copy);
510+
511+ ual_tracepoint(libual_start_message_sent, data->app_start_data->appid);
512+
513+ g_object_unref(con);
514+}
515+
516 static gboolean
517-start_application_core (const gchar * appid, const gchar * const * uris, gboolean test)
518+start_application_core (const gchar * appid, const gchar * const * uris, gboolean async, gboolean test)
519 {
520 ual_tracepoint(libual_start, appid);
521
522@@ -300,24 +353,29 @@
523 if (setup_complete) {
524 g_variant_builder_close(&builder);
525 g_variant_builder_add_value(&builder, g_variant_new_boolean(TRUE));
526-
527- /* Call the job start function */
528- g_dbus_connection_call(con,
529- DBUS_SERVICE_UPSTART,
530- jobpath,
531- DBUS_INTERFACE_UPSTART_JOB,
532- "Start",
533- g_variant_builder_end(&builder),
534- NULL,
535- G_DBUS_CALL_FLAGS_NONE,
536- -1,
537- NULL, /* cancelable */
538- application_start_cb,
539- app_start_data);
540-
541- ual_tracepoint(libual_start_message_sent, appid);
542+
543+ ual_tracepoint(libual_handshake_start, appid);
544+
545+ handshake_data_t *handshake_data = g_new0(handshake_data_t, 1);
546+ handshake_data->jobpath = g_strdup(jobpath);
547+ handshake_data->params = g_variant_ref_sink(g_variant_builder_end(&builder));
548+ handshake_data->app_start_data = app_start_data;
549+
550+ handshake_t * handshake = starting_handshake_start(appid, handshake_cb, handshake_data, (GDestroyNotify)handshake_data_free);
551+
552+ if (handshake == NULL) {
553+ g_warning("Unable to setup starting handshake");
554+ setup_complete = FALSE;
555+ } else if (!async) {
556+ ual_tracepoint(libual_handshake_wait, appid);
557+
558+ starting_handshake_wait(handshake);
559+ }
560+
561+ ual_tracepoint(libual_handshake_complete, appid);
562 } else {
563 g_variant_builder_clear(&builder);
564+ app_start_data_free(app_start_data);
565 }
566
567 g_object_unref(con);
568@@ -328,13 +386,25 @@
569 gboolean
570 ubuntu_app_launch_start_application (const gchar * appid, const gchar * const * uris)
571 {
572- return start_application_core(appid, uris, FALSE);
573+ return start_application_core(appid, uris, FALSE, FALSE);
574 }
575
576 gboolean
577 ubuntu_app_launch_start_application_test (const gchar * appid, const gchar * const * uris)
578 {
579- return start_application_core(appid, uris, TRUE);
580+ return start_application_core(appid, uris, FALSE, TRUE);
581+}
582+
583+void
584+ubuntu_app_launch_start_application_async (const gchar * appid, const gchar * const * uris)
585+{
586+ start_application_core(appid, uris, TRUE, FALSE);
587+}
588+
589+void
590+ubuntu_app_launch_start_application_async_test (const gchar * appid, const gchar * const * uris)
591+{
592+ start_application_core(appid, uris, TRUE, TRUE);
593 }
594
595 static void
596@@ -953,7 +1023,7 @@
597 observer_t * observer = (observer_t *)user_data;
598 const gchar * appid = NULL;
599
600- if (observer->func != NULL) {
601+ if (observer->func != NULL && g_variant_check_format_string(params, "(s)", FALSE)) {
602 g_variant_get(params, "(&s)", &appid);
603 observer->func(appid, observer->user_data);
604 }
605@@ -1007,14 +1077,13 @@
606 return add_session_generic(observer, user_data, "UnityResumeRequest", &resume_array, resume_signal_cb);
607 }
608
609-/* Handle the starting signal when it occurs, call the observer, then send a signal back when we're done */
610+/* Handle the starting signal when it occurs and call the observer,
611+ which will send a signal back when it's done via finish_app_starting */
612 static void
613 starting_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
614 {
615 ual_tracepoint(observer_start, "starting");
616
617- generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
618-
619 GError * error = NULL;
620 g_dbus_connection_emit_signal(conn,
621 sender, /* destination */
622@@ -1027,6 +1096,8 @@
623 if (error != NULL) {
624 g_warning("Unable to emit response signal: %s", error->message);
625 g_error_free(error);
626+ } else {
627+ generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
628 }
629
630 ual_tracepoint(observer_finish, "starting");
631@@ -1038,6 +1109,33 @@
632 return add_session_generic(observer, user_data, "UnityStartingBroadcast", &starting_array, starting_signal_cb);
633 }
634
635+gboolean
636+ubuntu_app_launch_observer_finish_app_starting (const gchar *appid, gboolean approved)
637+{
638+ GDBusConnection * conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
639+ if (conn == NULL) {
640+ return FALSE;
641+ }
642+
643+ GError * error = NULL;
644+ g_dbus_connection_emit_signal(conn,
645+ NULL, /* destination */
646+ "/", /* path */
647+ "com.canonical.UbuntuAppLaunch", /* interface */
648+ "UnityStartingApproved", /* signal */
649+ g_variant_new("(sb)", appid, approved), /* params */
650+ &error);
651+ g_object_unref(conn);
652+
653+ if (error != NULL) {
654+ g_warning("Unable to emit response signal: %s", error->message);
655+ g_error_free(error);
656+ return FALSE;
657+ }
658+
659+ return TRUE;
660+}
661+
662 /* Handle the failed signal when it occurs, call the observer */
663 static void
664 failed_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
665
666=== modified file 'libubuntu-app-launch/ubuntu-app-launch.h'
667--- libubuntu-app-launch/ubuntu-app-launch.h 2015-08-10 14:19:05 +0000
668+++ libubuntu-app-launch/ubuntu-app-launch.h 2015-12-02 17:21:30 +0000
669@@ -97,6 +97,31 @@
670 const gchar * const * uris);
671
672 /**
673+ * ubuntu_app_launch_start_application_async:
674+ * @appid: ID of the application to launch
675+ * @uris: (allow-none) (array zero-terminated=1) (element-type utf8) (transfer none): A NULL terminated list of URIs to send to the application
676+ *
677+ * Asks upstart to launch an application, but does not block. To discover
678+ * whether it successfully started or not, register an observer. For example,
679+ * call ubuntu_app_launch_observer_add_app_started.
680+ */
681+void ubuntu_app_launch_start_application_async (const gchar * appid,
682+ const gchar * const * uris);
683+
684+/**
685+ * ubuntu_app_launch_start_application_async_test:
686+ * @appid: ID of the application to launch
687+ * @uris: (allow-none) (array zero-terminated=1) (element-type utf8) (transfer none): A NULL terminated list of URIs to send to the application
688+ *
689+ * Asks upstart to launch an application with environment variables set
690+ * to enable testing, but does not block. To discover whether it successfully
691+ * started or not, register an observer. For example, call
692+ * ubuntu_app_launch_observer_add_app_started. Should only be used in testing.
693+ */
694+void ubuntu_app_launch_start_application_async_test (const gchar * appid,
695+ const gchar * const * uris);
696+
697+/**
698 * ubuntu_app_launch_stop_application:
699 * @appid: ID of the application to launch
700 *
701@@ -163,14 +188,28 @@
702 * @user_data: (closure) (allow-none): Data to pass to the observer
703 *
704 * Sets up a callback to get called each time an application
705- * is about to start. The application will not start until the
706- * function returns.
707+ * is about to start. The application will not start until
708+ * ubuntu_app_launch_observer_finish_app_starting is called after
709+ * the callback is called.
710 *
711 * Return value: Whether adding the observer was successful.
712 */
713 gboolean ubuntu_app_launch_observer_add_app_starting (UbuntuAppLaunchAppObserver observer,
714 gpointer user_data);
715 /**
716+ * ubuntu_app_launch_observer_finish_app_starting:
717+ * @appid: ID of the application being started
718+ * @approved: Whether the app can continue starting
719+ *
720+ * Finishes handling an app that is starting up via the callback
721+ * registered with ubuntu_app_launch_observer_add_app_starting.
722+ * If the app is allowed to continue starting, pass TRUE.
723+ *
724+ * Return value: Whether finish app startup was successful.
725+ */
726+gboolean ubuntu_app_launch_observer_finish_app_starting (const gchar *appid,
727+ gboolean approved);
728+/**
729 * ubuntu_app_launch_observer_add_app_started:
730 * @observer: (scope notified): Callback when an application started
731 * @user_data: (closure) (allow-none): Data to pass to the observer
732
733=== modified file 'tests/CMakeLists.txt'
734--- tests/CMakeLists.txt 2015-06-26 17:26:50 +0000
735+++ tests/CMakeLists.txt 2015-12-02 17:21:30 +0000
736@@ -24,7 +24,7 @@
737 # Helper test
738
739 add_executable (helper-handshake-test helper-handshake-test.cc)
740-target_link_libraries (helper-handshake-test helpers gtest ${GTEST_LIBS})
741+target_link_libraries (helper-handshake-test helpers-test gtest ${GTEST_LIBS})
742
743 add_test (helper-handshake-test helper-handshake-test)
744
745
746=== modified file 'tests/exec-util-test.cc'
747--- tests/exec-util-test.cc 2015-08-11 19:11:15 +0000
748+++ tests/exec-util-test.cc 2015-12-02 17:21:30 +0000
749@@ -34,7 +34,8 @@
750
751 protected:
752 static void starting_cb (const gchar * appid, gpointer user_data) {
753- g_debug("I'm too sexy to callback");
754+ g_debug("Starting app %s", appid);
755+ ASSERT_TRUE(ubuntu_app_launch_observer_finish_app_starting(appid, TRUE));
756 }
757
758 virtual void SetUp() {
759
760=== modified file 'tests/helper-handshake-test.cc'
761--- tests/helper-handshake-test.cc 2014-04-30 16:18:29 +0000
762+++ tests/helper-handshake-test.cc 2015-12-02 17:21:30 +0000
763@@ -72,12 +72,20 @@
764 return static_cast<HelperHandshakeTest *>(user_data)->FilterFunc(conn, message, incomming);
765 }
766
767+static void
768+handshake_cb (gpointer user_data)
769+{
770+ bool * reached = static_cast<bool *>(user_data);
771+ *reached = true;
772+}
773+
774 TEST_F(HelperHandshakeTest, BaseHandshake)
775 {
776+ bool handshake_succeeded = false;
777 GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
778 guint filter = g_dbus_connection_add_filter(con, filter_func, this, NULL);
779
780- handshake_t * handshake = starting_handshake_start("fooapp");
781+ handshake_t * handshake = starting_handshake_start("fooapp", handshake_cb, &handshake_succeeded, NULL);
782
783 g_main_loop_run(mainloop);
784
785@@ -91,8 +99,89 @@
786 g_variant_new("(s)", "fooapp"), /* params, the same */
787 NULL);
788
789- starting_handshake_wait(handshake);
790-
791+ g_dbus_connection_emit_signal(con,
792+ g_dbus_connection_get_unique_name(con), /* destination */
793+ "/", /* path */
794+ "com.canonical.UbuntuAppLaunch", /* interface */
795+ "UnityStartingApproved", /* signal */
796+ g_variant_new("(sb)", "fooapp", TRUE), /* params */
797+ NULL);
798+
799+ starting_handshake_wait(handshake);
800+
801+ ASSERT_TRUE(handshake_succeeded);
802+ g_object_unref(con);
803+
804+ return;
805+}
806+
807+TEST_F(HelperHandshakeTest, UnapprovedHandshake)
808+{
809+ bool handshake_succeeded = false;
810+ GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
811+ guint filter = g_dbus_connection_add_filter(con, filter_func, this, NULL);
812+
813+ handshake_t * handshake = starting_handshake_start("fooapp", handshake_cb, &handshake_succeeded, NULL);
814+
815+ g_main_loop_run(mainloop);
816+
817+ g_dbus_connection_remove_filter(con, filter);
818+
819+ g_dbus_connection_emit_signal(con,
820+ g_dbus_connection_get_unique_name(con), /* destination */
821+ "/", /* path */
822+ "com.canonical.UbuntuAppLaunch", /* interface */
823+ "UnityStartingSignal", /* signal */
824+ g_variant_new("(s)", "fooapp"), /* params */
825+ NULL);
826+
827+ g_dbus_connection_emit_signal(con,
828+ g_dbus_connection_get_unique_name(con), /* destination */
829+ "/", /* path */
830+ "com.canonical.UbuntuAppLaunch", /* interface */
831+ "UnityStartingApproved", /* signal */
832+ g_variant_new("(sb)", "fooapp", FALSE), /* params */
833+ NULL);
834+
835+ starting_handshake_wait(handshake);
836+
837+ ASSERT_FALSE(handshake_succeeded);
838+ g_object_unref(con);
839+
840+ return;
841+}
842+
843+TEST_F(HelperHandshakeTest, InvalidHandshake)
844+{
845+ bool handshake_succeeded = false;
846+ GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
847+ guint filter = g_dbus_connection_add_filter(con, filter_func, this, NULL);
848+
849+ handshake_t * handshake = starting_handshake_start("fooapp", handshake_cb, &handshake_succeeded, NULL);
850+
851+ g_main_loop_run(mainloop);
852+
853+ g_dbus_connection_remove_filter(con, filter);
854+
855+ g_dbus_connection_emit_signal(con,
856+ g_dbus_connection_get_unique_name(con), /* destination */
857+ "/", /* path */
858+ "com.canonical.UbuntuAppLaunch", /* interface */
859+ "UnityStartingSignal", /* signal */
860+ g_variant_new("(s)", "fooapp"), /* params */
861+ NULL);
862+
863+ g_dbus_connection_emit_signal(con,
864+ g_dbus_connection_get_unique_name(con), /* destination */
865+ "/", /* path */
866+ "com.canonical.UbuntuAppLaunch", /* interface */
867+ "UnityStartingApproved", /* signal */
868+ g_variant_new("(ss)", "fooapp", "true"), /* bad params */
869+ NULL);
870+
871+ starting_handshake_wait(handshake);
872+
873+ ASSERT_FALSE(handshake_succeeded);
874 g_object_unref(con);
875
876 return;
877@@ -103,18 +192,179 @@
878 {
879 bool * reached = static_cast<bool *>(user_data);
880 *reached = true;
881+ return G_SOURCE_REMOVE;
882 }
883
884 TEST_F(HelperHandshakeTest, HandshakeTimeout)
885 {
886 bool timeout_reached = false;
887- handshake_t * handshake = starting_handshake_start("fooapp");
888-
889- guint outertimeout = g_timeout_add_seconds(2, two_second_reached, &timeout_reached);
890-
891- starting_handshake_wait(handshake);
892-
893- ASSERT_FALSE(timeout_reached);
894-
895- return;
896-}
897+ bool handshake_succeeded = false;
898+ handshake_t * handshake = starting_handshake_start("fooapp", handshake_cb, &handshake_succeeded, NULL);
899+
900+ guint outertimeout = g_timeout_add_seconds(2, two_second_reached, &timeout_reached);
901+
902+ starting_handshake_wait(handshake);
903+
904+ ASSERT_FALSE(timeout_reached);
905+ ASSERT_FALSE(handshake_succeeded);
906+ g_source_remove(outertimeout);
907+
908+ return;
909+}
910+
911+TEST_F(HelperHandshakeTest, HandshakeTimeoutWithOnlyApprovedSignal)
912+{
913+ bool timeout_reached = false;
914+ bool handshake_succeeded = false;
915+ GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
916+
917+ guint outertimeout = g_timeout_add_seconds(2, two_second_reached, &timeout_reached);
918+
919+ guint filter = g_dbus_connection_add_filter(con, filter_func, this, NULL);
920+ handshake_t * handshake = starting_handshake_start("fooapp", handshake_cb, &handshake_succeeded, NULL);
921+ g_main_loop_run(mainloop);
922+ g_dbus_connection_remove_filter(con, filter);
923+
924+ g_dbus_connection_emit_signal(con,
925+ g_dbus_connection_get_unique_name(con), /* destination */
926+ "/", /* path */
927+ "com.canonical.UbuntuAppLaunch", /* interface */
928+ "UnityStartingApproved", /* signal */
929+ g_variant_new("(sb)", "fooapp", true), /* bad params */
930+ NULL);
931+
932+ starting_handshake_wait(handshake);
933+
934+ ASSERT_FALSE(timeout_reached);
935+ ASSERT_FALSE(handshake_succeeded);
936+ g_source_remove(outertimeout);
937+ g_object_unref(con);
938+
939+ return;
940+}
941+
942+static gboolean
943+emit_approval (gpointer user_data)
944+{
945+ GDBusConnection * con = (GDBusConnection *) user_data;
946+
947+ g_dbus_connection_emit_signal(con,
948+ g_dbus_connection_get_unique_name(con), /* destination */
949+ "/", /* path */
950+ "com.canonical.UbuntuAppLaunch", /* interface */
951+ "UnityStartingApproved", /* signal */
952+ g_variant_new("(sb)", "fooapp", true), /* params */
953+ NULL);
954+
955+ return G_SOURCE_REMOVE;
956+}
957+
958+TEST_F(HelperHandshakeTest, HandshakeNoTimeoutWithReceivedSignal)
959+{
960+ bool timeout_reached = false;
961+ bool handshake_succeeded = false;
962+ GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
963+
964+ g_timeout_add_seconds(2, two_second_reached, &timeout_reached);
965+ g_timeout_add_seconds(2, emit_approval, con);
966+
967+ guint filter = g_dbus_connection_add_filter(con, filter_func, this, NULL);
968+ handshake_t * handshake = starting_handshake_start("fooapp", handshake_cb, &handshake_succeeded, NULL);
969+ g_main_loop_run(mainloop);
970+ g_dbus_connection_remove_filter(con, filter);
971+
972+ g_dbus_connection_emit_signal(con,
973+ g_dbus_connection_get_unique_name(con), /* destination */
974+ "/", /* path */
975+ "com.canonical.UbuntuAppLaunch", /* interface */
976+ "UnityStartingSignal", /* signal */
977+ g_variant_new("(s)", "fooapp"), /* params */
978+ NULL);
979+
980+ starting_handshake_wait(handshake);
981+
982+ ASSERT_TRUE(timeout_reached);
983+ ASSERT_TRUE(handshake_succeeded);
984+ g_object_unref(con);
985+
986+ return;
987+}
988+
989+TEST_F(HelperHandshakeTest, AsyncHandshake)
990+{
991+ GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
992+
993+ g_timeout_add_seconds(2, emit_approval, con);
994+
995+ guint filter = g_dbus_connection_add_filter(con, filter_func, this, NULL);
996+ handshake_t * handshake = starting_handshake_start("fooapp", (HandshakeCallback)g_main_loop_quit, mainloop, NULL);
997+ g_main_loop_run(mainloop);
998+ g_dbus_connection_remove_filter(con, filter);
999+
1000+ g_dbus_connection_emit_signal(con,
1001+ g_dbus_connection_get_unique_name(con), /* destination */
1002+ "/", /* path */
1003+ "com.canonical.UbuntuAppLaunch", /* interface */
1004+ "UnityStartingSignal", /* signal */
1005+ g_variant_new("(s)", "fooapp"), /* params, the same */
1006+ NULL);
1007+
1008+ g_main_loop_run(mainloop);
1009+
1010+ g_object_unref(con);
1011+
1012+ return;
1013+}
1014+
1015+static gboolean
1016+emit_name_change (gpointer user_data)
1017+{
1018+ GDBusConnection * con = (GDBusConnection *) user_data;
1019+ const gchar *unique_name = g_dbus_connection_get_unique_name(con);
1020+
1021+ // We are allowed to emit this (instead of DBus) because we link against
1022+ // helpers-test, a special version of the helper code that listens
1023+ // for this signal from anyone.
1024+ g_dbus_connection_emit_signal(con,
1025+ unique_name, /* destination */
1026+ "/org/freedesktop/DBus", /* path */
1027+ "org.freedesktop.DBus", /* interface */
1028+ "NameOwnerChanged", /* signal */
1029+ g_variant_new("(sss)", unique_name, unique_name, ""), /* params */
1030+ NULL);
1031+
1032+ return G_SOURCE_REMOVE;
1033+}
1034+
1035+TEST_F(HelperHandshakeTest, StopsWaitingIfUnityExits)
1036+{
1037+ bool timeout_reached = false;
1038+ bool handshake_succeeded = false;
1039+ GDBusConnection * con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1040+
1041+ g_timeout_add_seconds(1, emit_name_change, con);
1042+ guint outertimeout = g_timeout_add_seconds(2, two_second_reached, &timeout_reached);
1043+
1044+ guint filter = g_dbus_connection_add_filter(con, filter_func, this, NULL);
1045+ handshake_t * handshake = starting_handshake_start("fooapp", handshake_cb, &handshake_succeeded, NULL);
1046+ g_main_loop_run(mainloop);
1047+ g_dbus_connection_remove_filter(con, filter);
1048+
1049+ g_dbus_connection_emit_signal(con,
1050+ g_dbus_connection_get_unique_name(con), /* destination */
1051+ "/", /* path */
1052+ "com.canonical.UbuntuAppLaunch", /* interface */
1053+ "UnityStartingSignal", /* signal */
1054+ g_variant_new("(s)", "fooapp"), /* params */
1055+ NULL);
1056+
1057+ starting_handshake_wait(handshake);
1058+
1059+ ASSERT_FALSE(timeout_reached);
1060+ ASSERT_FALSE(handshake_succeeded);
1061+ g_source_remove(outertimeout);
1062+ g_object_unref(con);
1063+
1064+ return;
1065+}
1066+
1067
1068=== modified file 'tests/libual-test.cc'
1069--- tests/libual-test.cc 2015-08-12 02:45:40 +0000
1070+++ tests/libual-test.cc 2015-12-02 17:21:30 +0000
1071@@ -38,11 +38,19 @@
1072 DbusTestDbusMock * mock = NULL;
1073 DbusTestDbusMock * cgmock = NULL;
1074 GDBusConnection * bus = NULL;
1075+ std::string last_starting_appid;
1076 std::string last_focus_appid;
1077 std::string last_resume_appid;
1078 guint resume_timeout = 0;
1079
1080 private:
1081+ static void starting_cb (const gchar * appid, gpointer user_data) {
1082+ g_debug("Starting Callback: %s", appid);
1083+ LibUAL * _this = static_cast<LibUAL *>(user_data);
1084+ _this->last_starting_appid = appid;
1085+ ASSERT_TRUE(ubuntu_app_launch_observer_finish_app_starting(appid, TRUE));
1086+ }
1087+
1088 static void focus_cb (const gchar * appid, gpointer user_data) {
1089 g_debug("Focus Callback: %s", appid);
1090 LibUAL * _this = static_cast<LibUAL *>(user_data);
1091@@ -258,11 +266,13 @@
1092 /* Make sure we pretend the CG manager is just on our bus */
1093 g_setenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS", "YES", TRUE);
1094
1095+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_starting(starting_cb, this));
1096 ASSERT_TRUE(ubuntu_app_launch_observer_add_app_focus(focus_cb, this));
1097 ASSERT_TRUE(ubuntu_app_launch_observer_add_app_resume(resume_cb, this));
1098 }
1099
1100 virtual void TearDown() {
1101+ ubuntu_app_launch_observer_delete_app_starting(starting_cb, this);
1102 ubuntu_app_launch_observer_delete_app_focus(focus_cb, this);
1103 ubuntu_app_launch_observer_delete_app_resume(resume_cb, this);
1104
1105@@ -426,6 +436,140 @@
1106 g_variant_unref(env);
1107 }
1108
1109+typedef struct {
1110+ int count;
1111+ GMainLoop *loop;
1112+} async_data_t;
1113+
1114+static void
1115+async_test_app_focused_cb (const gchar * appid, gpointer user_data)
1116+{
1117+ async_data_t * data = (async_data_t *)user_data;
1118+ data->count++;
1119+ g_main_loop_quit(data->loop);
1120+}
1121+
1122+static gboolean
1123+two_second_reached (gpointer user_data)
1124+{
1125+ GMainLoop * loop = (GMainLoop *)user_data;
1126+ g_main_loop_quit(loop);
1127+ return G_SOURCE_REMOVE;
1128+}
1129+
1130+TEST_F(LibUAL, StartApplicationAsync)
1131+{
1132+ DbusTestDbusMockObject * obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_click", "com.ubuntu.Upstart0_6.Job", NULL);
1133+
1134+ async_data_t data = {
1135+ .count = 0,
1136+ .loop = g_main_loop_new(NULL, FALSE)
1137+ };
1138+
1139+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_focus(async_test_app_focused_cb, &data));
1140+
1141+ /* Basic make sure we can send the event */
1142+ guint outertimeout = g_timeout_add_seconds(2, two_second_reached, data.loop);
1143+ ubuntu_app_launch_start_application_async("com.test.good_application_1.2.3", NULL);
1144+ g_main_loop_run(data.loop);
1145+ g_source_remove(outertimeout);
1146+ ASSERT_EQ(1, data.count);
1147+
1148+ EXPECT_EQ(1, dbus_test_dbus_mock_object_check_method_call(mock, obj, "Start", NULL, NULL));
1149+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
1150+
1151+ /* Now look at the details of the call */
1152+ outertimeout = g_timeout_add_seconds(2, two_second_reached, data.loop);
1153+ ubuntu_app_launch_start_application_async("com.test.good_application_1.2.3", NULL);
1154+ g_main_loop_run(data.loop);
1155+ g_source_remove(outertimeout);
1156+ ASSERT_EQ(2, data.count);
1157+
1158+ guint len = 0;
1159+ const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
1160+ EXPECT_NE(nullptr, calls);
1161+ EXPECT_EQ(1, len);
1162+
1163+ EXPECT_STREQ("Start", calls->name);
1164+ EXPECT_EQ(2, g_variant_n_children(calls->params));
1165+
1166+ GVariant * block = g_variant_get_child_value(calls->params, 1);
1167+ EXPECT_TRUE(g_variant_get_boolean(block));
1168+ g_variant_unref(block);
1169+
1170+ GVariant * env = g_variant_get_child_value(calls->params, 0);
1171+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.good_application_1.2.3"));
1172+ g_variant_unref(env);
1173+
1174+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
1175+
1176+ /* Let's pass some URLs */
1177+ const gchar * urls[] = {
1178+ "http://ubuntu.com/",
1179+ "https://ubuntu.com/",
1180+ "file:///home/phablet/test.txt",
1181+ NULL
1182+ };
1183+ outertimeout = g_timeout_add_seconds(2, two_second_reached, data.loop);
1184+ ubuntu_app_launch_start_application("com.test.good_application_1.2.3", urls);
1185+ g_main_loop_run(data.loop);
1186+ g_source_remove(outertimeout);
1187+ ASSERT_EQ(3, data.count);
1188+
1189+ len = 0;
1190+ calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
1191+ EXPECT_NE(nullptr, calls);
1192+ EXPECT_EQ(1, len);
1193+
1194+ env = g_variant_get_child_value(calls->params, 0);
1195+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.good_application_1.2.3"));
1196+ EXPECT_TRUE(check_env(env, "APP_URIS", "'http://ubuntu.com/' 'https://ubuntu.com/' 'file:///home/phablet/test.txt'"));
1197+ g_variant_unref(env);
1198+
1199+ ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_focus(async_test_app_focused_cb, &data));
1200+ g_main_loop_unref(data.loop);
1201+
1202+ return;
1203+}
1204+
1205+TEST_F(LibUAL, StartApplicationAsyncTest)
1206+{
1207+ DbusTestDbusMockObject * obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_click", "com.ubuntu.Upstart0_6.Job", NULL);
1208+
1209+ async_data_t data = {
1210+ .count = 0,
1211+ .loop = g_main_loop_new(NULL, FALSE)
1212+ };
1213+
1214+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_focus(async_test_app_focused_cb, &data));
1215+
1216+ guint outertimeout = g_timeout_add_seconds(2, two_second_reached, data.loop);
1217+ ubuntu_app_launch_start_application_async_test("com.test.good_application_1.2.3", NULL);
1218+ g_main_loop_run(data.loop);
1219+ g_source_remove(outertimeout);
1220+ ASSERT_EQ(1, data.count);
1221+
1222+ guint len = 0;
1223+ const DbusTestDbusMockCall * calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
1224+ EXPECT_NE(nullptr, calls);
1225+ EXPECT_EQ(1, len);
1226+
1227+ EXPECT_STREQ("Start", calls->name);
1228+ EXPECT_EQ(2, g_variant_n_children(calls->params));
1229+
1230+ GVariant * block = g_variant_get_child_value(calls->params, 1);
1231+ EXPECT_TRUE(g_variant_get_boolean(block));
1232+ g_variant_unref(block);
1233+
1234+ GVariant * env = g_variant_get_child_value(calls->params, 0);
1235+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.good_application_1.2.3"));
1236+ EXPECT_TRUE(check_env(env, "QT_LOAD_TESTABILITY", "1"));
1237+ g_variant_unref(env);
1238+
1239+ ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_focus(async_test_app_focused_cb, &data));
1240+ g_main_loop_unref(data.loop);
1241+}
1242+
1243 TEST_F(LibUAL, StopApplication)
1244 {
1245 DbusTestDbusMockObject * obj = dbus_test_dbus_mock_get_object(mock, "/com/test/application_click", "com.ubuntu.Upstart0_6.Job", NULL);
1246@@ -748,7 +892,6 @@
1247
1248 TEST_F(LibUAL, StartingResponses)
1249 {
1250- std::string last_observer;
1251 unsigned int starting_count = 0;
1252 GDBusConnection * session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1253 guint filter = g_dbus_connection_add_filter(session,
1254@@ -756,8 +899,6 @@
1255 &starting_count,
1256 NULL);
1257
1258- EXPECT_TRUE(ubuntu_app_launch_observer_add_app_starting(starting_observer, &last_observer));
1259-
1260 g_dbus_connection_emit_signal(session,
1261 NULL, /* destination */
1262 "/", /* path */
1263@@ -768,11 +909,9 @@
1264
1265 pause(100);
1266
1267- EXPECT_EQ("com.test.good_application_1.2.3", last_observer);
1268+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_starting_appid);
1269 EXPECT_EQ(1, starting_count);
1270
1271- EXPECT_TRUE(ubuntu_app_launch_observer_delete_app_starting(starting_observer, &last_observer));
1272-
1273 g_dbus_connection_remove_filter(session, filter);
1274 g_object_unref(session);
1275 }
1276@@ -781,6 +920,7 @@
1277 {
1278 ASSERT_TRUE(ubuntu_app_launch_start_application("com.test.good_application_1.2.3", NULL));
1279 pause(50); /* Ensure all the events come through */
1280+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_starting_appid);
1281 EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
1282 EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
1283 }
1284@@ -817,6 +957,7 @@
1285 ASSERT_TRUE(ubuntu_app_launch_start_application("com.test.good_application_1.2.3", uris));
1286 pause(100); /* Ensure all the events come through */
1287
1288+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_starting_appid);
1289 EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
1290 EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
1291
1292@@ -851,6 +992,7 @@
1293 ASSERT_TRUE(ubuntu_app_launch_start_application("com.test.good_application_1.2.3", uris));
1294 pause(100); /* Ensure all the events come through */
1295
1296+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_starting_appid);
1297 EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
1298 EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
1299 }
1300@@ -861,6 +1003,7 @@
1301
1302 ASSERT_TRUE(ubuntu_app_launch_start_application("com.test.good_application_1.2.3", NULL));
1303 pause(1000); /* Ensure all the events come through */
1304+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_starting_appid);
1305 EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
1306 EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
1307 }
1308@@ -876,6 +1019,7 @@
1309
1310 ASSERT_TRUE(ubuntu_app_launch_start_application("com.test.good_application_1.2.3", uris));
1311 pause(1000); /* Ensure all the events come through */
1312+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_starting_appid);
1313 EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
1314 EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
1315 }
1316@@ -915,6 +1059,7 @@
1317
1318 pause(1000); /* Ensure all the events come through */
1319
1320+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_starting_appid);
1321 EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
1322 EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
1323
1324
1325=== modified file 'tools/ubuntu-app-watch.c'
1326--- tools/ubuntu-app-watch.c 2015-02-25 22:50:24 +0000
1327+++ tools/ubuntu-app-watch.c 2015-12-02 17:21:30 +0000
1328@@ -23,6 +23,7 @@
1329 starting (const gchar * appid, gpointer user_data)
1330 {
1331 g_print("Starting %s\n", appid);
1332+ ubuntu_app_launch_observer_finish_app_starting(appid, TRUE);
1333 return;
1334 }
1335

Subscribers

People subscribed via source and target branches