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

Proposed by Ted Gould on 2015-12-02
Status: Rejected
Rejected by: dobey on 2016-08-08
Proposed branch: lp:~mterry/ubuntu-app-launch/warn-on-xapp
Merge into: lp:ubuntu-app-launch/16.04
Prerequisite: lp:~mterry/ubuntu-app-launch/fix-ftbfs
Diff against target: 1337 lines (+760/-123)
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 (+133/-22)
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
dobey (community) 2015-12-02 Resubmit on 2016-08-08
PS Jenkins bot (community) continuous-integration 2015-12-02 Approve on 2015-12-02
Review via email: mp+279323@code.launchpad.net

This proposal supersedes a proposal from 2015-11-24.

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

dobey (dobey) wrote :

If this is still relevant, it needs to be rebuilt/resubmitted against current trunk. Also, a bug describing the problem it's meant to solve would be nice.

review: Resubmit

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

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

Subscribers

People subscribed via source and target branches