Merge lp:~bregma/ubuntu-app-launch/app-object into lp:ubuntu-app-launch/16.04

Proposed by Stephen M. Webb on 2016-01-30
Status: Rejected
Rejected by: Ted Gould on 2016-05-02
Proposed branch: lp:~bregma/ubuntu-app-launch/app-object
Merge into: lp:ubuntu-app-launch/16.04
Diff against target: 5605 lines (+4756/-236)
57 files modified
.bzrignore (+6/-0)
CMakeLists.txt (+5/-1)
_clang-format (+48/-0)
debian/changelog (+13/-0)
debian/control (+2/-0)
debian/libubuntu-app-launch2.shlibs (+1/-0)
debian/libubuntu-app-launch2.symbols (+0/-41)
debian/rules (+0/-3)
helpers-shared.c (+11/-2)
libubuntu-app-launch/CMakeLists.txt (+34/-0)
libubuntu-app-launch/appid.h (+83/-0)
libubuntu-app-launch/application-impl-base.cpp (+162/-0)
libubuntu-app-launch/application-impl-base.h (+52/-0)
libubuntu-app-launch/application-impl-click.cpp (+157/-0)
libubuntu-app-launch/application-impl-click.h (+56/-0)
libubuntu-app-launch/application-impl-legacy.cpp (+128/-0)
libubuntu-app-launch/application-impl-legacy.h (+57/-0)
libubuntu-app-launch/application-impl-libertine.cpp (+127/-0)
libubuntu-app-launch/application-impl-libertine.h (+56/-0)
libubuntu-app-launch/application-info-desktop.cpp (+202/-0)
libubuntu-app-launch/application-info-desktop.h (+87/-0)
libubuntu-app-launch/application.cpp (+225/-0)
libubuntu-app-launch/application.h (+150/-0)
libubuntu-app-launch/desktop-exec.c (+1/-1)
libubuntu-app-launch/glib-thread.cpp (+156/-0)
libubuntu-app-launch/glib-thread.h (+90/-0)
libubuntu-app-launch/helper-impl-click.cpp (+175/-0)
libubuntu-app-launch/helper-impl-click.h (+62/-0)
libubuntu-app-launch/helper.cpp (+36/-0)
libubuntu-app-launch/helper.h (+71/-0)
libubuntu-app-launch/libubuntu-app-launch.map (+17/-0)
libubuntu-app-launch/registry-impl.cpp (+185/-0)
libubuntu-app-launch/registry-impl.h (+69/-0)
libubuntu-app-launch/registry.cpp (+99/-0)
libubuntu-app-launch/registry.h (+91/-0)
libubuntu-app-launch/type-tagger.h (+37/-0)
libubuntu-app-launch/ubuntu-app-launch.c (+7/-7)
libubuntu-app-launch/ubuntu-app-launch.h (+12/-0)
tests/CMakeLists.txt (+20/-0)
tests/application-info-desktop.cpp (+148/-0)
tests/click-app-dir/application.desktop (+3/-1)
tests/exec-util-test.cc (+1/-1)
tests/helper-handshake-test.cc (+3/-0)
tests/helper-test.cc (+0/-2)
tests/libertine-data/libertine-container/container-name/rootfs/usr/share/applications/test.desktop (+1/-0)
tests/libual-cpp-test.cc (+1599/-0)
tests/libual-test.cc (+1/-2)
tests/mir-mock.cpp (+1/-1)
tools/CMakeLists.txt (+16/-7)
tools/ubuntu-app-list-pids.cpp (+44/-0)
tools/ubuntu-app-list.cpp (+11/-17)
tools/ubuntu-app-pid.cpp (+25/-19)
tools/ubuntu-app-stop.cpp (+21/-15)
tools/ubuntu-app-triplet.cpp (+26/-32)
tools/ubuntu-helper-list.cpp (+20/-31)
tools/ubuntu-helper-start.cpp (+21/-27)
tools/ubuntu-helper-stop.cpp (+25/-26)
To merge this branch: bzr merge lp:~bregma/ubuntu-app-launch/app-object
Reviewer Review Type Date Requested Status
Indicator Applet Developers 2016-01-30 Pending
Review via email: mp+284521@code.launchpad.net

Commit message

fix build deps

Description of the change

test merge for Libertine demo

To post a comment you must log in.
229. By Ted Gould on 2016-02-08

Removing signals from the API for this MR

230. By Ted Gould on 2016-02-08

Revert back to 'C' versions of these tools

231. By Ted Gould on 2016-02-08

Fix symbols

232. By Ted Gould on 2016-02-08

Switch to a different arbitrary style

233. By Ted Gould on 2016-02-08

Switch from symbols to shlibs

234. By Ted Gould on 2016-02-09

Namespace bike shedding

235. By Ted Gould on 2016-02-09

Remove as member functions

236. By Ted Gould on 2016-02-09

Adding the class keyword to enums

237. By Ted Gould on 2016-02-09

Optimize constructors

238. By Ted Gould on 2016-02-09

Explicit constructor

239. By Ted Gould on 2016-02-09

Check to ensure cpath isn't null

240. By Ted Gould on 2016-02-09

Make URL vectors const references

241. By Ted Gould on 2016-02-09

Make the constructor fail if there is no keyfile instead of waiting when we get info()

242. By Ted Gould on 2016-02-09

Passing the manifests by const reference

243. By Ted Gould on 2016-02-09

Adding a format target

244. By Ted Gould on 2016-02-09

Exception on manifest failures and ensure that we have it early

245. By Ted Gould on 2016-02-10

Stop incrementing ref counts to make the code faster

246. By Ted Gould on 2016-02-10

Check for keyfile in constructor

247. By Ted Gould on 2016-02-10

Check for keyfile in constructor

248. By Ted Gould on 2016-02-10

Move prototype

249. By Ted Gould on 2016-02-10

Making voids implicit instead of explicit

250. By Ted Gould on 2016-02-10

Move from one implementation only location to another implementation only location

251. By Ted Gould on 2016-02-10

Constexpr

252. By Ted Gould on 2016-02-10

Adding extra commas

253. By Ted Gould on 2016-02-10

Less splash, more namespacing

254. By Ted Gould on 2016-02-10

Change to supportsUbuntuLifecycle

255. By Stephen M. Webb on 2016-02-17

added missing build-dep on libproperties-cpp-dev

256. By Stephen M. Webb on 2016-02-17

use -std=gnu99 instead of c99 to fix FTBFS on some architectures

257. By Stephen M. Webb on 2016-02-17

Fixed a coupla FTBFS and bumped version for Silo 58 testing.

258. By Stephen M. Webb on 2016-02-18

Disabled a bunch of chronically-failing tests.

259. By Stephen M. Webb on 2016-03-04

merge latest ted upstream branch

Unmerged revisions

259. By Stephen M. Webb on 2016-03-04

merge latest ted upstream branch

258. By Stephen M. Webb on 2016-02-18

Disabled a bunch of chronically-failing tests.

257. By Stephen M. Webb on 2016-02-17

Fixed a coupla FTBFS and bumped version for Silo 58 testing.

256. By Stephen M. Webb on 2016-02-17

use -std=gnu99 instead of c99 to fix FTBFS on some architectures

255. By Stephen M. Webb on 2016-02-17

added missing build-dep on libproperties-cpp-dev

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2014-06-14 16:32:00 +0000
3+++ .bzrignore 2016-03-04 11:34:19 +0000
4@@ -5,3 +5,9 @@
5 cmake_install.cmake
6 *-trace.[ch]
7 cgroup-reap-all
8+libubuntu-app-launch/UbuntuAppLaunch-2.gir
9+libubuntu-app-launch/UbuntuAppLaunch-2.typelib
10+libubuntu-app-launch/libubuntu-app-launch.so.2*
11+libubuntu-app-launch/proxy-socket-demangler.c
12+libubuntu-app-launch/proxy-socket-demangler.h
13+libubuntu-app-launch/ubuntu-app-launch-2.pc
14
15=== modified file 'CMakeLists.txt'
16--- CMakeLists.txt 2015-08-11 02:45:49 +0000
17+++ CMakeLists.txt 2016-03-04 11:34:19 +0000
18@@ -37,7 +37,8 @@
19 set(ubuntu_app_launch_arch "${UBUNTU_APP_LAUNCH_ARCH}")
20
21 # Deprecated needed for g_atexit() in libual
22-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wno-error=unused-function -Wno-error=deprecated-declarations -std=c99")
23+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Werror -Wno-error=unused-function -Wno-error=deprecated-declarations -std=gnu99")
24+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -std=c++11 -pthread")
25
26 enable_testing()
27
28@@ -77,6 +78,9 @@
29 pkg_check_modules(MIR mirclient)
30 include_directories(${MIR_INCLUDE_DIRS})
31
32+pkg_check_modules(LIBERTINE libertine)
33+include_directories(${LIBERTINE_INCLUDE_DIRS})
34+
35 include_directories(${CMAKE_CURRENT_SOURCE_DIR})
36
37 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC")
38
39=== added file '_clang-format'
40--- _clang-format 1970-01-01 00:00:00 +0000
41+++ _clang-format 2016-03-04 11:34:19 +0000
42@@ -0,0 +1,48 @@
43+---
44+AccessModifierOffset: -4
45+AlignEscapedNewlinesLeft: true
46+AlignTrailingComments: true
47+AllowAllParametersOfDeclarationOnNextLine: true
48+AllowShortFunctionsOnASingleLine: false
49+AllowShortIfStatementsOnASingleLine: false
50+AllowShortLoopsOnASingleLine: false
51+AlwaysBreakBeforeMultilineStrings: true
52+AlwaysBreakTemplateDeclarations: true
53+BinPackParameters: false
54+BreakBeforeBinaryOperators: false
55+BreakBeforeBraces: Allman
56+BreakBeforeTernaryOperators: false
57+BreakConstructorInitializersBeforeComma: true
58+ColumnLimit: 120
59+ConstructorInitializerAllOnOneLineOrOnePerLine: false
60+ConstructorInitializerIndentWidth: 4
61+ContinuationIndentWidth: 4
62+Cpp11BracedListStyle: true
63+DerivePointerBinding: true
64+ExperimentalAutoDetectBinPacking: false
65+IndentCaseLabels: true
66+IndentFunctionDeclarationAfterType: true
67+IndentWidth: 4
68+Language: Cpp
69+MaxEmptyLinesToKeep: 1
70+NamespaceIndentation: None
71+ObjCSpaceBeforeProtocolList: false
72+PenaltyBreakBeforeFirstCallParameter: 1
73+PenaltyBreakComment: 60
74+PenaltyBreakFirstLessLess: 120
75+PenaltyBreakString: 1000
76+PenaltyExcessCharacter: 1000000
77+PenaltyReturnTypeOnItsOwnLine: 200
78+PointerBindsToType: true
79+SpaceBeforeAssignmentOperators: true
80+SpaceBeforeParens: ControlStatements
81+SpaceInEmptyParentheses: false
82+SpacesBeforeTrailingComments: 2
83+SpacesInAngles: false
84+SpacesInCStyleCastParentheses: false
85+SpacesInParentheses: false
86+Standard: Cpp11
87+TabWidth: 8
88+UseTab: Never
89+...
90+
91
92=== modified file 'debian/changelog'
93--- debian/changelog 2015-08-17 21:38:34 +0000
94+++ debian/changelog 2016-03-04 11:34:19 +0000
95@@ -1,3 +1,16 @@
96+ubuntu-app-launch (0.9~smw2) UNRELEASED; urgency=medium
97+
98+ [ Ted Gould ]
99+ * Add new C++ API that has a retained object for maintaining
100+ connections through multiple calls. Also provides for getting
101+ consistent application metadata.
102+
103+ [ Stephen M. Webb ]
104+ * Fixed a coupla FTBFS and bumped version for Silo 58 testing.
105+ * Disabled a bunch of chronically-failing tests.
106+
107+ -- Ted Gould <ted@ubuntu.com> Tue, 26 Jan 2016 15:22:06 -0600
108+
109 ubuntu-app-launch (0.5+15.10.20150817-0ubuntu1) wily; urgency=medium
110
111 [ CI Train Bot ]
112
113=== modified file 'debian/control'
114--- debian/control 2015-07-31 00:15:57 +0000
115+++ debian/control 2016-03-04 11:34:19 +0000
116@@ -15,10 +15,12 @@
117 libglib2.0-dev,
118 libgtest-dev,
119 libjson-glib-dev,
120+ liblibertine-dev,
121 liblttng-ust-dev,
122 libmirclient-dev (>= 0.5),
123 libnih-dbus-dev,
124 libnih-dev,
125+ libproperties-cpp-dev,
126 libupstart-dev,
127 libzeitgeist-2.0-dev,
128 gobject-introspection,
129
130=== added file 'debian/libubuntu-app-launch2.shlibs'
131--- debian/libubuntu-app-launch2.shlibs 1970-01-01 00:00:00 +0000
132+++ debian/libubuntu-app-launch2.shlibs 2016-03-04 11:34:19 +0000
133@@ -0,0 +1,1 @@
134+libubuntu-app-launch 2 libubuntu-app-launch2 (>= 0.9)
135
136=== removed file 'debian/libubuntu-app-launch2.symbols'
137--- debian/libubuntu-app-launch2.symbols 2015-08-17 21:38:34 +0000
138+++ debian/libubuntu-app-launch2.symbols 1970-01-01 00:00:00 +0000
139@@ -1,41 +0,0 @@
140-libubuntu-app-launch.so.2 libubuntu-app-launch2 #MINVER#
141- ubuntu_app_launch_app_id_parse@Base 0.4
142- ubuntu_app_launch_application_info@Base 0.5+15.10.20150817
143- ubuntu_app_launch_application_log_path@Base 0.4
144- ubuntu_app_launch_get_primary_pid@Base 0.4
145- ubuntu_app_launch_helper_set_exec@Base 0.5+15.10.20150604
146- ubuntu_app_launch_list_helper_instances@Base 0.4
147- ubuntu_app_launch_list_helpers@Base 0.4
148- ubuntu_app_launch_list_running_apps@Base 0.4
149- ubuntu_app_launch_observer_add_app_failed@Base 0.4
150- ubuntu_app_launch_observer_add_app_focus@Base 0.4
151- ubuntu_app_launch_observer_add_app_paused@Base 0.4+15.04.20150305.1
152- ubuntu_app_launch_observer_add_app_resume@Base 0.4
153- ubuntu_app_launch_observer_add_app_resumed@Base 0.4+15.04.20150305.1
154- ubuntu_app_launch_observer_add_app_started@Base 0.4
155- ubuntu_app_launch_observer_add_app_starting@Base 0.4
156- ubuntu_app_launch_observer_add_app_stop@Base 0.4
157- ubuntu_app_launch_observer_add_helper_started@Base 0.4
158- ubuntu_app_launch_observer_add_helper_stop@Base 0.4
159- ubuntu_app_launch_observer_delete_app_failed@Base 0.4
160- ubuntu_app_launch_observer_delete_app_focus@Base 0.4
161- ubuntu_app_launch_observer_delete_app_paused@Base 0.4+15.04.20150305.1
162- ubuntu_app_launch_observer_delete_app_resume@Base 0.4
163- ubuntu_app_launch_observer_delete_app_resumed@Base 0.4+15.04.20150305.1
164- ubuntu_app_launch_observer_delete_app_started@Base 0.4
165- ubuntu_app_launch_observer_delete_app_starting@Base 0.4
166- ubuntu_app_launch_observer_delete_app_stop@Base 0.4
167- ubuntu_app_launch_observer_delete_helper_started@Base 0.4
168- ubuntu_app_launch_observer_delete_helper_stop@Base 0.4
169- ubuntu_app_launch_pause_application@Base 0.4+14.10.20140915.3
170- ubuntu_app_launch_pid_in_app_id@Base 0.4
171- ubuntu_app_launch_resume_application@Base 0.4+14.10.20140915.3
172- ubuntu_app_launch_start_application@Base 0.4
173- ubuntu_app_launch_start_application_test@Base 0.4
174- ubuntu_app_launch_start_helper@Base 0.4
175- ubuntu_app_launch_start_multiple_helper@Base 0.4
176- ubuntu_app_launch_start_session_helper@Base 0.5+15.10.20150604
177- ubuntu_app_launch_stop_application@Base 0.4
178- ubuntu_app_launch_stop_helper@Base 0.4
179- ubuntu_app_launch_stop_multiple_helper@Base 0.4
180- ubuntu_app_launch_triplet_to_app_id@Base 0.4
181
182=== modified file 'debian/rules'
183--- debian/rules 2014-11-21 16:43:12 +0000
184+++ debian/rules 2016-03-04 11:34:19 +0000
185@@ -1,9 +1,6 @@
186 #!/usr/bin/make -f
187 # -*- makefile -*-
188
189-# Error on symbol errors
190-export DPKG_GENSYMBOLS_CHECK_LEVEL=4
191-
192 # Get full logs in tests
193 export G_MESSAGES_DEBUG=all
194
195
196=== modified file 'helpers-shared.c'
197--- helpers-shared.c 2015-07-15 02:32:54 +0000
198+++ helpers-shared.c 2016-03-04 11:34:19 +0000
199@@ -145,6 +145,8 @@
200 cgroup_manager_connection_core_cb(g_dbus_connection_new_for_address_finish, res, (cgm_connection_t *)data);
201 }
202
203+G_DEFINE_QUARK(CGMANAGER_CONTEXT, cgmanager_context);
204+
205 /* Get the connection to the cgroup manager */
206 GDBusConnection *
207 cgroup_manager_connection (void)
208@@ -191,7 +193,9 @@
209 g_main_context_pop_thread_default(context);
210
211 if (!use_session_bus && connection.con != NULL) {
212- g_object_set_data(G_OBJECT(connection.con), "cgmanager-context", context);
213+ g_object_set_qdata(G_OBJECT(connection.con),
214+ cgmanager_context_quark(),
215+ context);
216 } else {
217 g_main_context_unref(context);
218 }
219@@ -213,7 +217,7 @@
220 if (cgmanager == NULL)
221 return;
222
223- GMainContext * creationcontext = g_object_get_data(G_OBJECT(cgmanager), "cgmanager-context");
224+ GMainContext * creationcontext = g_object_get_qdata(G_OBJECT(cgmanager), cgmanager_context_quark());
225 if (creationcontext == NULL) {
226 g_object_unref(cgmanager);
227 return;
228@@ -228,6 +232,11 @@
229 }
230
231 g_object_unref(cgmanager);
232+
233+ while (g_main_context_pending(creationcontext)) {
234+ g_main_context_iteration(creationcontext, TRUE /* may block */);
235+ }
236+
237 g_main_context_unref(creationcontext);
238 }
239
240
241=== modified file 'libubuntu-app-launch/CMakeLists.txt'
242--- libubuntu-app-launch/CMakeLists.txt 2015-08-11 19:11:15 +0000
243+++ libubuntu-app-launch/CMakeLists.txt 2016-03-04 11:34:19 +0000
244@@ -17,6 +17,7 @@
245 add_lttng_gen_tp(NAME ubuntu-app-launch-trace)
246
247 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
248+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden")
249 add_definitions ( -DOOM_HELPER="${pkglibexecdir}/oom-adjust-setuid-helper" -DDEMANGLER_PATH="${pkglibexecdir}/socket-demangler" )
250 add_definitions ( -DLIBERTINE_LAUNCH="${CMAKE_INSTALL_FULL_BINDIR}/libertine-launch" )
251
252@@ -24,6 +25,28 @@
253 ubuntu-app-launch.h
254 )
255
256+set(LAUNCHER_CPP_HEADERS
257+appid.h
258+application.h
259+helper.h
260+registry.h
261+type-tagger.h
262+)
263+
264+set(LAUNCHER_CPP_SOURCES
265+application.cpp
266+helper.cpp
267+registry.cpp
268+registry-impl.cpp
269+application-impl-base.cpp
270+application-impl-click.cpp
271+application-impl-legacy.cpp
272+application-impl-libertine.cpp
273+application-info-desktop.cpp
274+helper-impl-click.cpp
275+glib-thread.cpp
276+)
277+
278 set(LAUNCHER_SOURCES
279 ubuntu-app-launch.c
280 second-exec-core.c
281@@ -31,6 +54,11 @@
282 desktop-exec.c
283 ubuntu-app-launch-trace.c
284 app-info.c
285+${LAUNCHER_CPP_SOURCES}
286+)
287+
288+add_custom_target(format
289+ COMMAND clang-format -i -style=file ${LAUNCHER_CPP_HEADERS} ${LAUNCHER_CPP_SOURCES}
290 )
291
292 set(LAUNCHER_GEN_SOURCES
293@@ -59,6 +87,7 @@
294 ${CLICK_LIBRARIES}
295 ${ZEITGEIST_LIBRARIES}
296 ${MIR_LIBRARIES}
297+ ${LIBERTINE_LIBRARIES}
298 helpers
299 -Wl,--no-undefined
300 )
301@@ -69,6 +98,11 @@
302 )
303
304 install(
305+ FILES ${LAUNCHER_CPP_HEADERS}
306+ DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/libubuntu-app-launch-${API_VERSION}/ubuntu-app-launch"
307+)
308+
309+install(
310 TARGETS ubuntu-launcher
311 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
312 )
313
314=== added file 'libubuntu-app-launch/appid.h'
315--- libubuntu-app-launch/appid.h 1970-01-01 00:00:00 +0000
316+++ libubuntu-app-launch/appid.h 2016-03-04 11:34:19 +0000
317@@ -0,0 +1,83 @@
318+/*
319+ * Copyright © 2016 Canonical Ltd.
320+ *
321+ * This program is free software: you can redistribute it and/or modify it
322+ * under the terms of the GNU General Public License version 3, as published
323+ * by the Free Software Foundation.
324+ *
325+ * This program is distributed in the hope that it will be useful, but
326+ * WITHOUT ANY WARRANTY; without even the implied warranties of
327+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
328+ * PURPOSE. See the GNU General Public License for more details.
329+ *
330+ * You should have received a copy of the GNU General Public License along
331+ * with this program. If not, see <http://www.gnu.org/licenses/>.
332+ *
333+ * Authors:
334+ * Ted Gould <ted.gould@canonical.com>
335+ */
336+
337+#include <string>
338+
339+#include "type-tagger.h"
340+
341+#pragma once
342+#pragma GCC visibility push(default)
343+
344+namespace ubuntu
345+{
346+namespace app_launch
347+{
348+
349+struct AppID
350+{
351+ struct PackageTag;
352+ struct AppNameTag;
353+ struct VersionTag;
354+
355+ typedef TypeTagger<PackageTag, std::string> Package;
356+ typedef TypeTagger<AppNameTag, std::string> AppName;
357+ typedef TypeTagger<VersionTag, std::string> Version;
358+
359+ Package package;
360+ AppName appname;
361+ Version version;
362+
363+ operator std::string() const;
364+
365+ AppID();
366+ AppID(Package pkg, AppName app, Version ver);
367+ bool empty() const;
368+
369+ static AppID parse(const std::string& appid);
370+
371+ enum class ApplicationWildcard
372+ {
373+ FIRST_LISTED,
374+ LAST_LISTED,
375+ ONLY_LISTED,
376+ };
377+ enum class VersionWildcard
378+ {
379+ CURRENT_USER_VERSION,
380+ };
381+
382+ static AppID discover(const std::string& package,
383+ ApplicationWildcard appwildcard = ApplicationWildcard::FIRST_LISTED,
384+ VersionWildcard versionwildcard = VersionWildcard::CURRENT_USER_VERSION);
385+ static AppID discover(const std::string& package,
386+ const std::string& appname,
387+ VersionWildcard versionwildcard = VersionWildcard::CURRENT_USER_VERSION);
388+ static AppID discover(const std::string& package, const std::string& appname, const std::string& version);
389+
390+ static AppID find(const std::string& sappid);
391+ static bool valid(const std::string& sappid);
392+};
393+
394+bool operator==(const AppID& a, const AppID& b);
395+bool operator!=(const AppID& a, const AppID& b);
396+
397+}; // namespace app_launch
398+}; // namespace ubuntu
399+
400+#pragma GCC visibility pop
401
402=== added file 'libubuntu-app-launch/application-impl-base.cpp'
403--- libubuntu-app-launch/application-impl-base.cpp 1970-01-01 00:00:00 +0000
404+++ libubuntu-app-launch/application-impl-base.cpp 2016-03-04 11:34:19 +0000
405@@ -0,0 +1,162 @@
406+/*
407+ * Copyright © 2016 Canonical Ltd.
408+ *
409+ * This program is free software: you can redistribute it and/or modify it
410+ * under the terms of the GNU General Public License version 3, as published
411+ * by the Free Software Foundation.
412+ *
413+ * This program is distributed in the hope that it will be useful, but
414+ * WITHOUT ANY WARRANTY; without even the implied warranties of
415+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
416+ * PURPOSE. See the GNU General Public License for more details.
417+ *
418+ * You should have received a copy of the GNU General Public License along
419+ * with this program. If not, see <http://www.gnu.org/licenses/>.
420+ *
421+ * Authors:
422+ * Ted Gould <ted.gould@canonical.com>
423+ */
424+
425+#include "application-impl-base.h"
426+
427+namespace ubuntu
428+{
429+namespace app_launch
430+{
431+namespace app_impls
432+{
433+
434+Base::Base(const std::shared_ptr<Registry>& registry)
435+ : _registry(registry)
436+{
437+}
438+
439+bool Base::hasInstances()
440+{
441+ std::string sappid = appId();
442+ return ubuntu_app_launch_get_primary_pid(sappid.c_str()) != 0;
443+}
444+
445+class BaseInstance : public Application::Instance
446+{
447+public:
448+ explicit BaseInstance(const std::string& appId);
449+
450+ /* Query lifecycle */
451+ bool isRunning() override
452+ {
453+ return ubuntu_app_launch_get_primary_pid(_appId.c_str()) != 0;
454+ }
455+ pid_t primaryPid() override
456+ {
457+ return ubuntu_app_launch_get_primary_pid(_appId.c_str());
458+ }
459+ bool hasPid(pid_t pid) override
460+ {
461+ return ubuntu_app_launch_pid_in_app_id(pid, _appId.c_str()) == TRUE;
462+ }
463+ std::string logPath() override
464+ {
465+ auto cpath = ubuntu_app_launch_application_log_path(_appId.c_str());
466+ if (cpath != nullptr)
467+ {
468+ std::string retval(cpath);
469+ g_free(cpath);
470+ return retval;
471+ }
472+ else
473+ {
474+ return {};
475+ }
476+ }
477+ std::vector<pid_t> pids() override
478+ {
479+ std::vector<pid_t> vector;
480+ GList* list = ubuntu_app_launch_get_pids(_appId.c_str());
481+
482+ for (GList* pntr = list; pntr != nullptr; pntr = g_list_next(pntr))
483+ {
484+ vector.push_back(static_cast<pid_t>(GPOINTER_TO_INT(list->data)));
485+ }
486+
487+ g_list_free(list);
488+
489+ return vector;
490+ }
491+
492+ /* Manage lifecycle */
493+ void pause() override
494+ {
495+ ubuntu_app_launch_pause_application(_appId.c_str());
496+ }
497+ void resume() override
498+ {
499+ ubuntu_app_launch_resume_application(_appId.c_str());
500+ }
501+ void stop() override
502+ {
503+ ubuntu_app_launch_stop_application(_appId.c_str());
504+ }
505+
506+private:
507+ std::string _appId;
508+};
509+
510+BaseInstance::BaseInstance(const std::string& appId)
511+ : _appId(appId)
512+{
513+}
514+
515+std::vector<std::shared_ptr<Application::Instance>> Base::instances()
516+{
517+ std::vector<std::shared_ptr<Instance>> vect;
518+ vect.emplace_back(std::make_shared<BaseInstance>(appId()));
519+ return vect;
520+}
521+
522+std::shared_ptr<gchar*> urlsToStrv(std::vector<Application::URL> urls)
523+{
524+ auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
525+
526+ for (auto url : urls)
527+ {
528+ auto str = g_strdup(url.value().c_str());
529+ g_array_append_val(array, str);
530+ }
531+
532+ return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
533+}
534+
535+std::shared_ptr<Application::Instance> Base::launch(const std::vector<Application::URL>& urls)
536+{
537+ std::string appIdStr = appId();
538+ std::shared_ptr<gchar*> urlstrv;
539+
540+ if (urls.size() > 0)
541+ {
542+ urlstrv = urlsToStrv(urls);
543+ }
544+
545+ ubuntu_app_launch_start_application(appIdStr.c_str(), urlstrv.get());
546+
547+ return std::make_shared<BaseInstance>(appIdStr);
548+}
549+
550+std::shared_ptr<Application::Instance> Base::launchTest(const std::vector<Application::URL>& urls)
551+{
552+ std::string appIdStr = appId();
553+ std::shared_ptr<gchar*> urlstrv;
554+
555+ if (urls.size() > 0)
556+ {
557+ urlstrv = urlsToStrv(urls);
558+ }
559+
560+ ubuntu_app_launch_start_application_test(appIdStr.c_str(), urlstrv.get());
561+
562+ return std::make_shared<BaseInstance>(appIdStr);
563+}
564+
565+}; // namespace app_impls
566+}; // namespace app_launch
567+}; // namespace ubuntu
568
569=== added file 'libubuntu-app-launch/application-impl-base.h'
570--- libubuntu-app-launch/application-impl-base.h 1970-01-01 00:00:00 +0000
571+++ libubuntu-app-launch/application-impl-base.h 2016-03-04 11:34:19 +0000
572@@ -0,0 +1,52 @@
573+/*
574+ * Copyright © 2016 Canonical Ltd.
575+ *
576+ * This program is free software: you can redistribute it and/or modify it
577+ * under the terms of the GNU General Public License version 3, as published
578+ * by the Free Software Foundation.
579+ *
580+ * This program is distributed in the hope that it will be useful, but
581+ * WITHOUT ANY WARRANTY; without even the implied warranties of
582+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
583+ * PURPOSE. See the GNU General Public License for more details.
584+ *
585+ * You should have received a copy of the GNU General Public License along
586+ * with this program. If not, see <http://www.gnu.org/licenses/>.
587+ *
588+ * Authors:
589+ * Ted Gould <ted.gould@canonical.com>
590+ */
591+
592+#include "application.h"
593+
594+extern "C" {
595+#include "ubuntu-app-launch.h"
596+}
597+
598+#pragma once
599+
600+namespace ubuntu
601+{
602+namespace app_launch
603+{
604+namespace app_impls
605+{
606+
607+class Base : public ubuntu::app_launch::Application
608+{
609+public:
610+ Base(const std::shared_ptr<Registry> &registry);
611+
612+ bool hasInstances() override;
613+ std::vector<std::shared_ptr<Instance>> instances() override;
614+
615+ std::shared_ptr<Instance> launch(const std::vector<Application::URL> &urls = {}) override;
616+ std::shared_ptr<Instance> launchTest(const std::vector<Application::URL> &urls = {}) override;
617+
618+protected:
619+ std::shared_ptr<Registry> _registry;
620+};
621+
622+}; // namespace app_impls
623+}; // namespace app_launch
624+}; // namespace ubuntu
625
626=== added file 'libubuntu-app-launch/application-impl-click.cpp'
627--- libubuntu-app-launch/application-impl-click.cpp 1970-01-01 00:00:00 +0000
628+++ libubuntu-app-launch/application-impl-click.cpp 2016-03-04 11:34:19 +0000
629@@ -0,0 +1,157 @@
630+/*
631+ * Copyright © 2016 Canonical Ltd.
632+ *
633+ * This program is free software: you can redistribute it and/or modify it
634+ * under the terms of the GNU General Public License version 3, as published
635+ * by the Free Software Foundation.
636+ *
637+ * This program is distributed in the hope that it will be useful, but
638+ * WITHOUT ANY WARRANTY; without even the implied warranties of
639+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
640+ * PURPOSE. See the GNU General Public License for more details.
641+ *
642+ * You should have received a copy of the GNU General Public License along
643+ * with this program. If not, see <http://www.gnu.org/licenses/>.
644+ *
645+ * Authors:
646+ * Ted Gould <ted.gould@canonical.com>
647+ */
648+
649+#include "application-impl-click.h"
650+#include "application-info-desktop.h"
651+#include "registry-impl.h"
652+
653+namespace ubuntu
654+{
655+namespace app_launch
656+{
657+namespace app_impls
658+{
659+
660+AppID::Version manifestVersion(const std::shared_ptr<JsonObject>& manifest);
661+std::list<AppID::AppName> manifestApps(const std::shared_ptr<JsonObject>& manifest);
662+std::shared_ptr<GKeyFile> manifestAppDesktop(const std::shared_ptr<JsonObject>& manifest,
663+ const std::string& app,
664+ const std::string& clickDir);
665+
666+Click::Click(const AppID& appid, const std::shared_ptr<Registry>& registry)
667+ : Click(appid, registry->impl->getClickManifest(appid.package), registry)
668+{
669+}
670+
671+Click::Click(const AppID& appid, const std::shared_ptr<JsonObject>& manifest, const std::shared_ptr<Registry>& registry)
672+ : Base(registry)
673+ , _appid(appid)
674+ , _manifest(manifest)
675+ , _clickDir(registry->impl->getClickDir(appid.package))
676+ , _keyfile(manifestAppDesktop(manifest, appid.appname, _clickDir))
677+{
678+ if (!_keyfile)
679+ throw std::runtime_error{"No keyfile found for click application: " + (std::string)appid};
680+}
681+
682+AppID Click::appId()
683+{
684+ return _appid;
685+}
686+
687+std::shared_ptr<Application::Info> Click::info()
688+{
689+ return std::make_shared<app_info::Desktop>(_keyfile, _clickDir);
690+}
691+
692+AppID::Version manifestVersion(const std::shared_ptr<JsonObject>& manifest)
693+{
694+ auto cstr = json_object_get_string_member(manifest.get(), "version");
695+
696+ if (cstr == nullptr)
697+ throw std::runtime_error("Unable to find version number in manifest");
698+
699+ auto cppstr = AppID::Version::from_raw((const gchar*)cstr);
700+ return cppstr;
701+}
702+
703+std::list<AppID::AppName> manifestApps(const std::shared_ptr<JsonObject>& manifest)
704+{
705+ auto hooks = json_object_get_object_member(manifest.get(), "hooks");
706+ if (hooks == nullptr)
707+ throw std::runtime_error("Manifest for application does not have a 'hooks' field");
708+
709+ auto gapps = json_object_get_members(hooks);
710+ if (gapps == nullptr)
711+ throw std::runtime_error("GLib JSON confusion, please talk to your library vendor");
712+
713+ std::list<AppID::AppName> apps;
714+
715+ for (GList* item = gapps; item != nullptr; item = g_list_next(item))
716+ {
717+ auto appname = (const gchar*)item->data;
718+
719+ auto hooklist = json_object_get_object_member(hooks, appname);
720+
721+ if (json_object_has_member(hooklist, "desktop") == TRUE)
722+ {
723+ apps.emplace_back(AppID::AppName::from_raw(appname));
724+ }
725+ }
726+
727+ g_list_free_full(gapps, g_free);
728+ return apps;
729+}
730+
731+std::shared_ptr<GKeyFile> manifestAppDesktop(const std::shared_ptr<JsonObject>& manifest,
732+ const std::string& app,
733+ const std::string& clickDir)
734+{
735+ auto hooks = json_object_get_object_member(manifest.get(), "hooks");
736+ if (hooks == nullptr)
737+ throw std::runtime_error("Manifest for application '" + app + "' does not have a 'hooks' field");
738+
739+ auto gapps = json_object_get_members(hooks);
740+ if (gapps == nullptr)
741+ throw std::runtime_error("GLib JSON confusion, please talk to your library vendor");
742+
743+ auto hooklist = json_object_get_object_member(hooks, app.c_str());
744+ if (hooklist == nullptr)
745+ throw std::runtime_error("Manifest for does not have an application '" + app + "'");
746+
747+ auto desktoppath = json_object_get_string_member(hooklist, "desktop");
748+ if (desktoppath == nullptr)
749+ throw std::runtime_error("Manifest for application '" + app + "' does not have a 'desktop' hook");
750+
751+ auto path = std::shared_ptr<gchar>(g_build_filename(clickDir.c_str(), desktoppath, nullptr), g_free);
752+
753+ std::shared_ptr<GKeyFile> keyfile(g_key_file_new(), g_key_file_free);
754+ GError* error = nullptr;
755+ g_key_file_load_from_file(keyfile.get(), path.get(), G_KEY_FILE_NONE, &error);
756+ if (error != nullptr)
757+ {
758+ auto perror = std::shared_ptr<GError>(error, g_error_free);
759+ throw std::runtime_error(perror.get()->message);
760+ }
761+
762+ return keyfile;
763+}
764+
765+std::list<std::shared_ptr<Application>> Click::list(const std::shared_ptr<Registry>& registry)
766+{
767+ std::list<std::shared_ptr<Application>> applist;
768+
769+ for (auto pkg : registry->impl->getClickPackages())
770+ {
771+ auto manifest = registry->impl->getClickManifest(pkg);
772+
773+ for (auto appname : manifestApps(manifest))
774+ {
775+ AppID appid{package : pkg, appname : appname, version : manifestVersion(manifest)};
776+ auto app = std::make_shared<Click>(appid, manifest, registry);
777+ applist.push_back(app);
778+ }
779+ }
780+
781+ return applist;
782+}
783+
784+}; // namespace app_impls
785+}; // namespace app_launch
786+}; // namespace ubuntu
787
788=== added file 'libubuntu-app-launch/application-impl-click.h'
789--- libubuntu-app-launch/application-impl-click.h 1970-01-01 00:00:00 +0000
790+++ libubuntu-app-launch/application-impl-click.h 2016-03-04 11:34:19 +0000
791@@ -0,0 +1,56 @@
792+/*
793+ * Copyright © 2016 Canonical Ltd.
794+ *
795+ * This program is free software: you can redistribute it and/or modify it
796+ * under the terms of the GNU General Public License version 3, as published
797+ * by the Free Software Foundation.
798+ *
799+ * This program is distributed in the hope that it will be useful, but
800+ * WITHOUT ANY WARRANTY; without even the implied warranties of
801+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
802+ * PURPOSE. See the GNU General Public License for more details.
803+ *
804+ * You should have received a copy of the GNU General Public License along
805+ * with this program. If not, see <http://www.gnu.org/licenses/>.
806+ *
807+ * Authors:
808+ * Ted Gould <ted.gould@canonical.com>
809+ */
810+
811+#include <gio/gdesktopappinfo.h>
812+#include <json-glib/json-glib.h>
813+#include "application-impl-base.h"
814+
815+#pragma once
816+
817+namespace ubuntu
818+{
819+namespace app_launch
820+{
821+namespace app_impls
822+{
823+
824+class Click : public Base
825+{
826+public:
827+ Click(const AppID& appid, const std::shared_ptr<Registry>& registry);
828+ Click(const AppID& appid, const std::shared_ptr<JsonObject>& manifest, const std::shared_ptr<Registry>& registry);
829+
830+ static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry>& registry);
831+
832+ AppID appId() override;
833+
834+ std::shared_ptr<Info> info() override;
835+
836+private:
837+ AppID _appid;
838+
839+ std::shared_ptr<JsonObject> _manifest;
840+
841+ std::string _clickDir;
842+ std::shared_ptr<GKeyFile> _keyfile;
843+};
844+
845+}; // namespace app_impls
846+}; // namespace app_launch
847+}; // namespace ubuntu
848
849=== added file 'libubuntu-app-launch/application-impl-legacy.cpp'
850--- libubuntu-app-launch/application-impl-legacy.cpp 1970-01-01 00:00:00 +0000
851+++ libubuntu-app-launch/application-impl-legacy.cpp 2016-03-04 11:34:19 +0000
852@@ -0,0 +1,128 @@
853+/*
854+ * Copyright © 2016 Canonical Ltd.
855+ *
856+ * This program is free software: you can redistribute it and/or modify it
857+ * under the terms of the GNU General Public License version 3, as published
858+ * by the Free Software Foundation.
859+ *
860+ * This program is distributed in the hope that it will be useful, but
861+ * WITHOUT ANY WARRANTY; without even the implied warranties of
862+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
863+ * PURPOSE. See the GNU General Public License for more details.
864+ *
865+ * You should have received a copy of the GNU General Public License along
866+ * with this program. If not, see <http://www.gnu.org/licenses/>.
867+ *
868+ * Authors:
869+ * Ted Gould <ted.gould@canonical.com>
870+ */
871+
872+#include "application-impl-legacy.h"
873+#include "application-info-desktop.h"
874+
875+namespace ubuntu
876+{
877+namespace app_launch
878+{
879+namespace app_impls
880+{
881+
882+std::shared_ptr<GKeyFile> keyfileForApp(const AppID::AppName& name);
883+
884+void clear_keyfile(GKeyFile* keyfile)
885+{
886+ if (keyfile != nullptr)
887+ {
888+ g_key_file_free(keyfile);
889+ }
890+}
891+
892+Legacy::Legacy(const AppID::AppName& appname,
893+ const std::shared_ptr<GKeyFile>& keyfile,
894+ const std::shared_ptr<Registry>& registry)
895+ : Base(registry)
896+ , _appname(appname)
897+ , _keyfile(keyfile)
898+{
899+ if (!_keyfile)
900+ throw std::runtime_error{"Unable to find keyfile for legacy application: " + appname.value()};
901+}
902+
903+Legacy::Legacy(const AppID::AppName& appname, const std::shared_ptr<Registry>& registry)
904+ : Legacy(appname, keyfileForApp(appname), registry)
905+{
906+}
907+
908+std::shared_ptr<GKeyFile> keyfileForApp(const AppID::AppName& name)
909+{
910+ std::string desktopName = name.value() + ".desktop";
911+ auto keyfilecheck = [desktopName](const gchar* dir) -> std::shared_ptr<GKeyFile> {
912+ auto fullname = g_build_filename(dir, "applications", desktopName.c_str(), nullptr);
913+ if (!g_file_test(fullname, G_FILE_TEST_EXISTS))
914+ {
915+ g_free(fullname);
916+ return {};
917+ }
918+
919+ auto keyfile = std::shared_ptr<GKeyFile>(g_key_file_new(), clear_keyfile);
920+
921+ GError* error = nullptr;
922+ g_key_file_load_from_file(keyfile.get(), fullname, G_KEY_FILE_NONE, &error);
923+ g_free(fullname);
924+
925+ if (error != nullptr)
926+ {
927+ g_debug("Unable to load keyfile '%s' becuase: %s", desktopName.c_str(), error->message);
928+ g_error_free(error);
929+ return {};
930+ }
931+
932+ return keyfile;
933+ };
934+
935+ auto retval = keyfilecheck(g_get_user_data_dir());
936+
937+ auto systemDirs = g_get_system_data_dirs();
938+ for (auto i = 0; !retval && systemDirs[i] != nullptr; i++)
939+ {
940+ retval = keyfilecheck(systemDirs[i]);
941+ }
942+
943+ return retval;
944+}
945+
946+std::shared_ptr<Application::Info> Legacy::info()
947+{
948+ return std::make_shared<app_info::Desktop>(_keyfile, "/usr/share/icons/");
949+}
950+
951+std::list<std::shared_ptr<Application>> Legacy::list(const std::shared_ptr<Registry>& registry)
952+{
953+ std::list<std::shared_ptr<Application>> list;
954+ GList* head = g_app_info_get_all();
955+ for (GList* item = head; item != nullptr; item = g_list_next(item))
956+ {
957+ GDesktopAppInfo* appinfo = G_DESKTOP_APP_INFO(item->data);
958+
959+ if (appinfo == nullptr)
960+ {
961+ continue;
962+ }
963+
964+ if (g_app_info_should_show(G_APP_INFO(appinfo)) == FALSE)
965+ {
966+ continue;
967+ }
968+
969+ auto app = std::make_shared<Legacy>(AppID::AppName::from_raw(g_app_info_get_id(G_APP_INFO(appinfo))), registry);
970+ list.push_back(app);
971+ }
972+
973+ g_list_free_full(head, g_object_unref);
974+
975+ return list;
976+}
977+
978+}; // namespace app_impls
979+}; // namespace app_launch
980+}; // namespace ubuntu
981
982=== added file 'libubuntu-app-launch/application-impl-legacy.h'
983--- libubuntu-app-launch/application-impl-legacy.h 1970-01-01 00:00:00 +0000
984+++ libubuntu-app-launch/application-impl-legacy.h 2016-03-04 11:34:19 +0000
985@@ -0,0 +1,57 @@
986+/*
987+ * Copyright © 2016 Canonical Ltd.
988+ *
989+ * This program is free software: you can redistribute it and/or modify it
990+ * under the terms of the GNU General Public License version 3, as published
991+ * by the Free Software Foundation.
992+ *
993+ * This program is distributed in the hope that it will be useful, but
994+ * WITHOUT ANY WARRANTY; without even the implied warranties of
995+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
996+ * PURPOSE. See the GNU General Public License for more details.
997+ *
998+ * You should have received a copy of the GNU General Public License along
999+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1000+ *
1001+ * Authors:
1002+ * Ted Gould <ted.gould@canonical.com>
1003+ */
1004+
1005+#include <gio/gdesktopappinfo.h>
1006+
1007+#include "application-impl-base.h"
1008+
1009+#pragma once
1010+
1011+namespace ubuntu
1012+{
1013+namespace app_launch
1014+{
1015+namespace app_impls
1016+{
1017+
1018+class Legacy : public Base
1019+{
1020+public:
1021+ Legacy(const AppID::AppName& appname, const std::shared_ptr<Registry>& registry);
1022+ Legacy(const AppID::AppName& appname,
1023+ const std::shared_ptr<GKeyFile>& keyfile,
1024+ const std::shared_ptr<Registry>& registry);
1025+
1026+ AppID appId() override
1027+ {
1028+ return {package : AppID::Package::from_raw({}), appname : _appname, version : AppID::Version::from_raw({})};
1029+ }
1030+
1031+ std::shared_ptr<Info> info() override;
1032+
1033+ static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry> &registry);
1034+
1035+private:
1036+ AppID::AppName _appname;
1037+ std::shared_ptr<GKeyFile> _keyfile;
1038+};
1039+
1040+}; // namespace app_impls
1041+}; // namespace app_launch
1042+}; // namespace ubuntu
1043
1044=== added file 'libubuntu-app-launch/application-impl-libertine.cpp'
1045--- libubuntu-app-launch/application-impl-libertine.cpp 1970-01-01 00:00:00 +0000
1046+++ libubuntu-app-launch/application-impl-libertine.cpp 2016-03-04 11:34:19 +0000
1047@@ -0,0 +1,127 @@
1048+/*
1049+ * Copyright © 2016 Canonical Ltd.
1050+ *
1051+ * This program is free software: you can redistribute it and/or modify it
1052+ * under the terms of the GNU General Public License version 3, as published
1053+ * by the Free Software Foundation.
1054+ *
1055+ * This program is distributed in the hope that it will be useful, but
1056+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1057+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1058+ * PURPOSE. See the GNU General Public License for more details.
1059+ *
1060+ * You should have received a copy of the GNU General Public License along
1061+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1062+ *
1063+ * Authors:
1064+ * Ted Gould <ted.gould@canonical.com>
1065+ */
1066+
1067+#include "application-impl-libertine.h"
1068+#include "application-info-desktop.h"
1069+#include "libertine.h"
1070+
1071+namespace ubuntu
1072+{
1073+namespace app_launch
1074+{
1075+namespace app_impls
1076+{
1077+
1078+std::shared_ptr<GKeyFile> keyfileFromPath(const gchar* pathname);
1079+
1080+Libertine::Libertine(const AppID::Package& container,
1081+ const AppID::AppName& appname,
1082+ const std::shared_ptr<Registry>& registry)
1083+ : Base(registry)
1084+ , _container(container)
1085+ , _appname(appname)
1086+{
1087+ if (!_keyfile)
1088+ {
1089+ auto container_path = libertine_container_path(container.value().c_str());
1090+ auto container_app_path = g_build_filename(container_path, "usr", "share", "applications",
1091+ (appname.value() + ".desktop").c_str(), NULL);
1092+
1093+ _keyfile = keyfileFromPath(container_app_path);
1094+
1095+ g_free(container_app_path);
1096+ g_free(container_path);
1097+ }
1098+
1099+ if (!_keyfile)
1100+ {
1101+ auto home_path = libertine_container_home_path(container.value().c_str());
1102+ auto home_app_path = g_build_filename(home_path, ".local", "share", "applications",
1103+ (appname.value() + ".desktop").c_str(), NULL);
1104+
1105+ _keyfile = keyfileFromPath(home_app_path);
1106+
1107+ g_free(home_app_path);
1108+ g_free(home_path);
1109+ }
1110+
1111+ if (!_keyfile)
1112+ throw std::runtime_error{"Unable to find a keyfile for application '" + appname.value() + "' in container '" +
1113+ container.value() + "'"};
1114+}
1115+
1116+std::shared_ptr<GKeyFile> keyfileFromPath(const gchar* pathname)
1117+{
1118+ if (!g_file_test(pathname, G_FILE_TEST_EXISTS))
1119+ {
1120+ return {};
1121+ }
1122+
1123+ std::shared_ptr<GKeyFile> keyfile(g_key_file_new(), [](GKeyFile* keyfile) {
1124+ if (keyfile != nullptr)
1125+ {
1126+ g_key_file_free(keyfile);
1127+ }
1128+ });
1129+ GError* error = nullptr;
1130+
1131+ g_key_file_load_from_file(keyfile.get(), pathname, G_KEY_FILE_NONE, &error);
1132+
1133+ if (error != nullptr)
1134+ {
1135+ g_error_free(error);
1136+ return {};
1137+ }
1138+
1139+ return keyfile;
1140+}
1141+
1142+std::list<std::shared_ptr<Application>> Libertine::list(const std::shared_ptr<Registry>& registry)
1143+{
1144+ std::list<std::shared_ptr<Application>> applist;
1145+
1146+ auto containers = libertine_list_containers();
1147+
1148+ for (int i = 0; containers[i] != nullptr; i++)
1149+ {
1150+ auto container = containers[i];
1151+ auto apps = libertine_list_apps_for_container(container);
1152+
1153+ for (int i = 0; apps[i] != nullptr; i++)
1154+ {
1155+ auto sapp = std::make_shared<Libertine>(AppID::Package::from_raw(container),
1156+ AppID::AppName::from_raw(apps[i]), registry);
1157+ applist.push_back(sapp);
1158+ }
1159+
1160+ g_strfreev(apps);
1161+ }
1162+ g_strfreev(containers);
1163+
1164+ return applist;
1165+}
1166+
1167+std::shared_ptr<Application::Info> Libertine::info()
1168+{
1169+ return std::make_shared<app_info::Desktop>(_keyfile, libertine_container_path(_container.value().c_str()));
1170+}
1171+
1172+}; // namespace app_impls
1173+}; // namespace app_launch
1174+}; // namespace ubuntu
1175
1176=== added file 'libubuntu-app-launch/application-impl-libertine.h'
1177--- libubuntu-app-launch/application-impl-libertine.h 1970-01-01 00:00:00 +0000
1178+++ libubuntu-app-launch/application-impl-libertine.h 2016-03-04 11:34:19 +0000
1179@@ -0,0 +1,56 @@
1180+/*
1181+ * Copyright © 2016 Canonical Ltd.
1182+ *
1183+ * This program is free software: you can redistribute it and/or modify it
1184+ * under the terms of the GNU General Public License version 3, as published
1185+ * by the Free Software Foundation.
1186+ *
1187+ * This program is distributed in the hope that it will be useful, but
1188+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1189+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1190+ * PURPOSE. See the GNU General Public License for more details.
1191+ *
1192+ * You should have received a copy of the GNU General Public License along
1193+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1194+ *
1195+ * Authors:
1196+ * Ted Gould <ted.gould@canonical.com>
1197+ */
1198+
1199+#include "application-impl-base.h"
1200+#include <gio/gdesktopappinfo.h>
1201+
1202+#pragma once
1203+
1204+namespace ubuntu
1205+{
1206+namespace app_launch
1207+{
1208+namespace app_impls
1209+{
1210+
1211+class Libertine : public Base
1212+{
1213+public:
1214+ Libertine(const AppID::Package& container,
1215+ const AppID::AppName& appname,
1216+ const std::shared_ptr<Registry>& registry);
1217+
1218+ static std::list<std::shared_ptr<Application>> list(const std::shared_ptr<Registry> &registry);
1219+
1220+ AppID appId() override
1221+ {
1222+ return {package : _container, appname : _appname, version : AppID::Version::from_raw("0.0")};
1223+ }
1224+
1225+ std::shared_ptr<Info> info() override;
1226+
1227+private:
1228+ AppID::Package _container;
1229+ AppID::AppName _appname;
1230+ std::shared_ptr<GKeyFile> _keyfile;
1231+};
1232+
1233+}; // namespace app_impls
1234+}; // namespace app_launch
1235+}; // namespace ubuntu
1236
1237=== added file 'libubuntu-app-launch/application-info-desktop.cpp'
1238--- libubuntu-app-launch/application-info-desktop.cpp 1970-01-01 00:00:00 +0000
1239+++ libubuntu-app-launch/application-info-desktop.cpp 2016-03-04 11:34:19 +0000
1240@@ -0,0 +1,202 @@
1241+/*
1242+ * Copyright © 2016 Canonical Ltd.
1243+ *
1244+ * This program is free software: you can redistribute it and/or modify it
1245+ * under the terms of the GNU General Public License version 3, as published
1246+ * by the Free Software Foundation.
1247+ *
1248+ * This program is distributed in the hope that it will be useful, but
1249+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1250+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1251+ * PURPOSE. See the GNU General Public License for more details.
1252+ *
1253+ * You should have received a copy of the GNU General Public License along
1254+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1255+ *
1256+ * Authors:
1257+ * Ted Gould <ted.gould@canonical.com>
1258+ */
1259+
1260+#include "application-info-desktop.h"
1261+
1262+namespace ubuntu
1263+{
1264+namespace app_launch
1265+{
1266+namespace app_info
1267+{
1268+
1269+constexpr const char* DESKTOP_GROUP = "Desktop Entry";
1270+
1271+template <typename T>
1272+auto stringFromKeyfile(std::shared_ptr<GKeyFile> keyfile, const std::string& key, const std::string& exceptionText = {})
1273+ -> T
1274+{
1275+ GError* error = nullptr;
1276+ auto keyval = g_key_file_get_locale_string(keyfile.get(), DESKTOP_GROUP, key.c_str(), nullptr, &error);
1277+
1278+ if (error != nullptr)
1279+ {
1280+ auto perror = std::shared_ptr<GError>(error, g_error_free);
1281+ if (!exceptionText.empty())
1282+ {
1283+ throw std::runtime_error(exceptionText + perror.get()->message);
1284+ }
1285+
1286+ return T::from_raw({});
1287+ }
1288+
1289+ T retval = T::from_raw(keyval);
1290+ g_free(keyval);
1291+ return retval;
1292+}
1293+
1294+template <typename T>
1295+auto fileFromKeyfile(std::shared_ptr<GKeyFile> keyfile,
1296+ const std::string basePath,
1297+ const std::string& key,
1298+ const std::string& exceptionText = {}) -> T
1299+{
1300+ GError* error = nullptr;
1301+ auto keyval = g_key_file_get_locale_string(keyfile.get(), DESKTOP_GROUP, key.c_str(), nullptr, &error);
1302+
1303+ if (error != nullptr)
1304+ {
1305+ auto perror = std::shared_ptr<GError>(error, g_error_free);
1306+ if (!exceptionText.empty())
1307+ {
1308+ throw std::runtime_error(exceptionText + perror.get()->message);
1309+ }
1310+
1311+ return T::from_raw({});
1312+ }
1313+
1314+ /* If we're already an absolute path, don't prepend the base path */
1315+ if (keyval[0] == '/')
1316+ {
1317+ T retval = T::from_raw(keyval);
1318+ g_free(keyval);
1319+ return retval;
1320+ }
1321+
1322+ auto cpath = g_build_filename(basePath.c_str(), keyval, nullptr);
1323+
1324+ T retval = T::from_raw(cpath);
1325+
1326+ g_free(keyval);
1327+ g_free(cpath);
1328+
1329+ return retval;
1330+}
1331+
1332+template <typename T>
1333+auto boolFromKeyfile(std::shared_ptr<GKeyFile> keyfile,
1334+ const std::string& key,
1335+ bool defaultReturn,
1336+ const std::string& exceptionText = {}) -> T
1337+{
1338+ GError* error = nullptr;
1339+ auto keyval = g_key_file_get_boolean(keyfile.get(), DESKTOP_GROUP, key.c_str(), &error);
1340+
1341+ if (error != nullptr)
1342+ {
1343+ auto perror = std::shared_ptr<GError>(error, g_error_free);
1344+ if (!exceptionText.empty())
1345+ {
1346+ throw std::runtime_error(exceptionText + perror.get()->message);
1347+ }
1348+
1349+ return T::from_raw(defaultReturn);
1350+ }
1351+
1352+ T retval = T::from_raw(keyval == TRUE);
1353+ return retval;
1354+}
1355+
1356+Desktop::Desktop(std::shared_ptr<GKeyFile> keyfile, const std::string& basePath)
1357+ : _keyfile([keyfile]() {
1358+ if (!keyfile)
1359+ {
1360+ throw std::runtime_error("Can not build a desktop application info object with a null keyfile");
1361+ }
1362+ return keyfile;
1363+ }())
1364+ , _basePath(basePath)
1365+ , _name(stringFromKeyfile<Application::Info::Name>(keyfile, "Name", "Unable to get name from keyfile"))
1366+ , _description(stringFromKeyfile<Application::Info::Description>(keyfile, "Comment"))
1367+ , _iconPath(
1368+ fileFromKeyfile<Application::Info::IconPath>(keyfile, basePath, "Icon", "Missing icon for desktop file"))
1369+ , _splashInfo({
1370+ title : stringFromKeyfile<Application::Info::Splash::Title>(keyfile, "X-Ubuntu-Splash-Title"),
1371+ image : fileFromKeyfile<Application::Info::Splash::Image>(keyfile, basePath, "X-Ubuntu-Splash-Image"),
1372+ backgroundColor : stringFromKeyfile<Application::Info::Splash::Color>(keyfile, "X-Ubuntu-Splash-Color"),
1373+ headerColor : stringFromKeyfile<Application::Info::Splash::Color>(keyfile, "X-Ubuntu-Splash-Color-Header"),
1374+ footerColor : stringFromKeyfile<Application::Info::Splash::Color>(keyfile, "X-Ubuntu-Splash-Color-Footer"),
1375+ showHeader :
1376+ boolFromKeyfile<Application::Info::Splash::ShowHeader>(keyfile, "X-Ubuntu-Splash-Show-Header", false)
1377+ })
1378+ , _supportedOrientations([keyfile]() {
1379+ Orientations all = {portrait : true, landscape : true, invertedPortrait : true, invertedLandscape : true};
1380+
1381+ GError* error = nullptr;
1382+ auto orientationStrv = g_key_file_get_string_list(keyfile.get(), DESKTOP_GROUP,
1383+ "X-Ubuntu-Supported-Orientations", nullptr, &error);
1384+
1385+ if (error != nullptr)
1386+ {
1387+ g_error_free(error);
1388+ return all;
1389+ }
1390+
1391+ Orientations retval =
1392+ {portrait : false, landscape : false, invertedPortrait : false, invertedLandscape : false};
1393+
1394+ try
1395+ {
1396+ for (auto i = 0; orientationStrv[i] != nullptr; i++)
1397+ {
1398+ g_strstrip(orientationStrv[i]); /* remove whitespace */
1399+
1400+ if (g_ascii_strcasecmp("portrait", orientationStrv[i]) == 0)
1401+ {
1402+ retval.portrait = true;
1403+ }
1404+ else if (g_ascii_strcasecmp("landscape", orientationStrv[i]) == 0)
1405+ {
1406+ retval.landscape = true;
1407+ }
1408+ else if (g_ascii_strcasecmp("invertedPortrait", orientationStrv[i]) == 0)
1409+ {
1410+ retval.invertedPortrait = true;
1411+ }
1412+ else if (g_ascii_strcasecmp("invertedLandscape", orientationStrv[i]) == 0)
1413+ {
1414+ retval.invertedLandscape = true;
1415+ }
1416+ else if (g_ascii_strcasecmp("primary", orientationStrv[i]) == 0 && i == 0)
1417+ {
1418+ /* Pass, we'll let primary be the first entry, it should be the only. */
1419+ }
1420+ else
1421+ {
1422+ throw std::runtime_error("Invalid orientation string '" + std::string(orientationStrv[i]) + "'");
1423+ }
1424+ }
1425+ }
1426+ catch (...)
1427+ {
1428+ retval = all;
1429+ }
1430+
1431+ g_strfreev(orientationStrv);
1432+ return retval;
1433+ }())
1434+ , _rotatesWindow(
1435+ boolFromKeyfile<Application::Info::RotatesWindow>(keyfile, "X-Ubuntu-Rotates-Window-Content", false))
1436+ , _ubuntuLifecycle(boolFromKeyfile<Application::Info::UbuntuLifecycle>(keyfile, "X-Ubuntu-Touch", false))
1437+{
1438+}
1439+
1440+}; // namespace app_info
1441+}; // namespace app_launch
1442+}; // namespace ubuntu
1443
1444=== added file 'libubuntu-app-launch/application-info-desktop.h'
1445--- libubuntu-app-launch/application-info-desktop.h 1970-01-01 00:00:00 +0000
1446+++ libubuntu-app-launch/application-info-desktop.h 2016-03-04 11:34:19 +0000
1447@@ -0,0 +1,87 @@
1448+/*
1449+ * Copyright © 2016 Canonical Ltd.
1450+ *
1451+ * This program is free software: you can redistribute it and/or modify it
1452+ * under the terms of the GNU General Public License version 3, as published
1453+ * by the Free Software Foundation.
1454+ *
1455+ * This program is distributed in the hope that it will be useful, but
1456+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1457+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1458+ * PURPOSE. See the GNU General Public License for more details.
1459+ *
1460+ * You should have received a copy of the GNU General Public License along
1461+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1462+ *
1463+ * Authors:
1464+ * Ted Gould <ted.gould@canonical.com>
1465+ */
1466+
1467+#include "application.h"
1468+#include <glib.h>
1469+#include <mutex>
1470+
1471+#pragma once
1472+
1473+namespace ubuntu
1474+{
1475+namespace app_launch
1476+{
1477+namespace app_info
1478+{
1479+
1480+class Desktop : public Application::Info
1481+{
1482+public:
1483+ Desktop(std::shared_ptr<GKeyFile> keyfile, const std::string& basePath);
1484+
1485+ const Application::Info::Name& name() override
1486+ {
1487+ return _name;
1488+ }
1489+ const Application::Info::Description& description() override
1490+ {
1491+ return _description;
1492+ }
1493+ const Application::Info::IconPath& iconPath() override
1494+ {
1495+ return _iconPath;
1496+ }
1497+
1498+ Application::Info::Splash splash() override
1499+ {
1500+ return _splashInfo;
1501+ }
1502+
1503+ Application::Info::Orientations supportedOrientations() override
1504+ {
1505+ return _supportedOrientations;
1506+ }
1507+
1508+ Application::Info::RotatesWindow rotatesWindowContents() override
1509+ {
1510+ return _rotatesWindow;
1511+ }
1512+
1513+ Application::Info::UbuntuLifecycle supportsUbuntuLifecycle() override
1514+ {
1515+ return _ubuntuLifecycle;
1516+ }
1517+
1518+private:
1519+ std::shared_ptr<GKeyFile> _keyfile;
1520+ std::string _basePath;
1521+
1522+ Application::Info::Name _name;
1523+ Application::Info::Description _description;
1524+ Application::Info::IconPath _iconPath;
1525+
1526+ Application::Info::Splash _splashInfo;
1527+ Application::Info::Orientations _supportedOrientations;
1528+ Application::Info::RotatesWindow _rotatesWindow;
1529+ Application::Info::UbuntuLifecycle _ubuntuLifecycle;
1530+};
1531+
1532+}; // namespace AppInfo
1533+}; // namespace AppLaunch
1534+}; // namespace Ubuntu
1535
1536=== added file 'libubuntu-app-launch/application.cpp'
1537--- libubuntu-app-launch/application.cpp 1970-01-01 00:00:00 +0000
1538+++ libubuntu-app-launch/application.cpp 2016-03-04 11:34:19 +0000
1539@@ -0,0 +1,225 @@
1540+/*
1541+ * Copyright © 2016 Canonical Ltd.
1542+ *
1543+ * This program is free software: you can redistribute it and/or modify it
1544+ * under the terms of the GNU General Public License version 3, as published
1545+ * by the Free Software Foundation.
1546+ *
1547+ * This program is distributed in the hope that it will be useful, but
1548+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1549+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1550+ * PURPOSE. See the GNU General Public License for more details.
1551+ *
1552+ * You should have received a copy of the GNU General Public License along
1553+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1554+ *
1555+ * Authors:
1556+ * Ted Gould <ted.gould@canonical.com>
1557+ */
1558+
1559+extern "C" {
1560+#include "app-info.h"
1561+#include "ubuntu-app-launch.h"
1562+}
1563+
1564+#include "application-impl-click.h"
1565+#include "application-impl-legacy.h"
1566+#include "application-impl-libertine.h"
1567+#include "application.h"
1568+
1569+#include <iostream>
1570+#include <regex>
1571+
1572+namespace ubuntu
1573+{
1574+namespace app_launch
1575+{
1576+
1577+std::shared_ptr<Application> Application::create(const AppID& appid, const std::shared_ptr<Registry>& registry)
1578+{
1579+ if (appid.empty())
1580+ {
1581+ throw std::runtime_error("AppID is empty");
1582+ }
1583+
1584+ std::string sappid = appid;
1585+ if (app_info_click(sappid.c_str(), NULL, NULL))
1586+ {
1587+ return std::make_shared<app_impls::Click>(appid, registry);
1588+ }
1589+ else if (app_info_libertine(sappid.c_str(), NULL, NULL))
1590+ {
1591+ return std::make_shared<app_impls::Libertine>(appid.package, appid.appname, registry);
1592+ }
1593+ else if (app_info_legacy(sappid.c_str(), NULL, NULL))
1594+ {
1595+ return std::make_shared<app_impls::Legacy>(appid.appname, registry);
1596+ }
1597+ else
1598+ {
1599+ throw std::runtime_error("Invalid app ID: " + sappid);
1600+ }
1601+}
1602+
1603+AppID::AppID()
1604+ : package(Package::from_raw({}))
1605+ , appname(AppName::from_raw({}))
1606+ , version(Version::from_raw({}))
1607+{
1608+}
1609+
1610+AppID::AppID(Package pkg, AppName app, Version ver)
1611+ : package(pkg)
1612+ , appname(app)
1613+ , version(ver)
1614+{
1615+}
1616+
1617+/* These are the Regex's taken from the Click Reviewer Tools
1618+ on Jan 16, 2016 revision 566 */
1619+#define REGEX_PKGNAME "([a-z0-9][a-z0-9+.-]+)"
1620+#define REGEX_APPNAME "([A-Za-z0-9+-.:~-]+)"
1621+#define REGEX_VERSION "([\\d+:]?[A-Za-z0-9.+:~-]+?[-[A-Za-z0-9+.~]+]?)"
1622+
1623+const std::regex full_appid_regex("^" REGEX_PKGNAME "_" REGEX_APPNAME "_" REGEX_VERSION "$");
1624+const std::regex short_appid_regex("^" REGEX_PKGNAME "_" REGEX_APPNAME "$");
1625+const std::regex legacy_appid_regex("^" REGEX_APPNAME "$");
1626+
1627+AppID AppID::parse(const std::string& sappid)
1628+{
1629+ std::smatch match;
1630+
1631+ if (std::regex_match(sappid, match, full_appid_regex))
1632+ {
1633+ return {AppID::Package::from_raw(match[1].str()), AppID::AppName::from_raw(match[2].str()),
1634+ AppID::Version::from_raw(match[3].str())};
1635+ }
1636+ else
1637+ {
1638+ /* Allow returning an empty AppID with empty internal */
1639+ return {AppID::Package::from_raw({}), AppID::AppName::from_raw({}), AppID::Version::from_raw({})};
1640+ }
1641+}
1642+
1643+bool AppID::valid(const std::string& sappid)
1644+{
1645+ return std::regex_match(sappid, full_appid_regex);
1646+}
1647+
1648+AppID AppID::find(const std::string& sappid)
1649+{
1650+ std::smatch match;
1651+
1652+ if (std::regex_match(sappid, match, full_appid_regex))
1653+ {
1654+ return {AppID::Package::from_raw(match[1].str()), AppID::AppName::from_raw(match[2].str()),
1655+ AppID::Version::from_raw(match[3].str())};
1656+ }
1657+ else if (std::regex_match(sappid, match, short_appid_regex))
1658+ {
1659+ return discover(match[1].str(), match[2].str());
1660+ }
1661+ else if (std::regex_match(sappid, match, legacy_appid_regex))
1662+ {
1663+ return {AppID::Package::from_raw({}), AppID::AppName::from_raw(sappid), AppID::Version::from_raw({})};
1664+ }
1665+ else
1666+ {
1667+ return {AppID::Package::from_raw({}), AppID::AppName::from_raw({}), AppID::Version::from_raw({})};
1668+ }
1669+}
1670+
1671+AppID::operator std::string() const
1672+{
1673+ if (package.value().empty() && version.value().empty())
1674+ {
1675+ if (appname.value().empty())
1676+ {
1677+ return {};
1678+ }
1679+ else
1680+ {
1681+ return appname.value();
1682+ }
1683+ }
1684+
1685+ return package.value() + "_" + appname.value() + "_" + version.value();
1686+}
1687+
1688+bool operator==(const AppID& a, const AppID& b)
1689+{
1690+ return a.package.value() == b.package.value() && a.appname.value() == b.appname.value() &&
1691+ a.version.value() == b.version.value();
1692+}
1693+
1694+bool operator!=(const AppID& a, const AppID& b)
1695+{
1696+ return a.package.value() != b.package.value() || a.appname.value() != b.appname.value() ||
1697+ a.version.value() != b.version.value();
1698+}
1699+
1700+bool AppID::empty() const
1701+{
1702+ return package.value().empty() && appname.value().empty() && version.value().empty();
1703+}
1704+
1705+std::string app_wildcard(AppID::ApplicationWildcard card)
1706+{
1707+ switch (card)
1708+ {
1709+ case AppID::ApplicationWildcard::FIRST_LISTED:
1710+ return "first-listed-app";
1711+ case AppID::ApplicationWildcard::LAST_LISTED:
1712+ return "last-listed-app";
1713+ case AppID::ApplicationWildcard::ONLY_LISTED:
1714+ return "only-listed-app";
1715+ }
1716+
1717+ return "";
1718+}
1719+
1720+std::string ver_wildcard(AppID::VersionWildcard card)
1721+{
1722+ switch (card)
1723+ {
1724+ case AppID::VersionWildcard::CURRENT_USER_VERSION:
1725+ return "current-user-version";
1726+ }
1727+
1728+ return "";
1729+}
1730+
1731+AppID AppID::discover(const std::string& package, const std::string& appname, const std::string& version)
1732+{
1733+ auto cappid = ubuntu_app_launch_triplet_to_app_id(package.c_str(), appname.c_str(), version.c_str());
1734+
1735+ auto appid = cappid != nullptr ? AppID::parse(cappid) : AppID::parse("");
1736+
1737+ g_free(cappid);
1738+
1739+ return appid;
1740+}
1741+
1742+AppID AppID::discover(const std::string& package, ApplicationWildcard appwildcard, VersionWildcard versionwildcard)
1743+{
1744+ return discover(package, app_wildcard(appwildcard), ver_wildcard(versionwildcard));
1745+}
1746+
1747+AppID AppID::discover(const std::string& package, const std::string& appname, VersionWildcard versionwildcard)
1748+{
1749+ auto appid = discover(package, appname, ver_wildcard(versionwildcard));
1750+
1751+ if (appid.empty())
1752+ {
1753+ /* If we weren't able to go that route, we can see if it's libertine */
1754+ if (app_info_libertine((package + "_" + appname + "_0.0").c_str(), nullptr, nullptr))
1755+ {
1756+ appid = AppID(Package::from_raw(package), AppName::from_raw(appname), Version::from_raw("0.0"));
1757+ }
1758+ }
1759+
1760+ return appid;
1761+}
1762+
1763+}; // namespace app_launch
1764+}; // namespace ubuntu
1765
1766=== added file 'libubuntu-app-launch/application.h'
1767--- libubuntu-app-launch/application.h 1970-01-01 00:00:00 +0000
1768+++ libubuntu-app-launch/application.h 2016-03-04 11:34:19 +0000
1769@@ -0,0 +1,150 @@
1770+/*
1771+ * Copyright © 2016 Canonical Ltd.
1772+ *
1773+ * This program is free software: you can redistribute it and/or modify it
1774+ * under the terms of the GNU General Public License version 3, as published
1775+ * by the Free Software Foundation.
1776+ *
1777+ * This program is distributed in the hope that it will be useful, but
1778+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1779+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1780+ * PURPOSE. See the GNU General Public License for more details.
1781+ *
1782+ * You should have received a copy of the GNU General Public License along
1783+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1784+ *
1785+ * Authors:
1786+ * Ted Gould <ted.gould@canonical.com>
1787+ */
1788+
1789+#include <list>
1790+#include <memory>
1791+#include <sys/types.h>
1792+#include <vector>
1793+
1794+#include "appid.h"
1795+#include "type-tagger.h"
1796+
1797+#pragma once
1798+#pragma GCC visibility push(default)
1799+
1800+namespace ubuntu
1801+{
1802+namespace app_launch
1803+{
1804+
1805+class Registry;
1806+
1807+class Application
1808+{
1809+public:
1810+ struct URLTag;
1811+ typedef TypeTagger<URLTag, std::string> URL;
1812+
1813+ static std::shared_ptr<Application> create(const AppID& appid, const std::shared_ptr<Registry>& registry);
1814+
1815+ /* System level info */
1816+ virtual AppID appId() = 0;
1817+
1818+ class Info
1819+ {
1820+ public:
1821+ /* Basic information */
1822+ struct NameTag;
1823+ struct DescriptionTag;
1824+ struct IconPathTag;
1825+
1826+ typedef TypeTagger<NameTag, std::string> Name;
1827+ typedef TypeTagger<DescriptionTag, std::string> Description;
1828+ typedef TypeTagger<IconPathTag, std::string> IconPath;
1829+
1830+ virtual const Name& name() = 0;
1831+ virtual const Description& description() = 0;
1832+ virtual const IconPath& iconPath() = 0;
1833+
1834+ /* Splash information */
1835+ struct Splash
1836+ {
1837+ struct TitleTag;
1838+ struct ImageTag;
1839+ struct ColorTag;
1840+ struct ShowHeaderTag;
1841+
1842+ typedef TypeTagger<TitleTag, std::string> Title;
1843+ typedef TypeTagger<ImageTag, std::string> Image;
1844+ typedef TypeTagger<ColorTag, std::string> Color;
1845+ typedef TypeTagger<ShowHeaderTag, bool> ShowHeader;
1846+
1847+ Title title;
1848+ Image image;
1849+ Color backgroundColor;
1850+ Color headerColor;
1851+ Color footerColor;
1852+ ShowHeader showHeader;
1853+ };
1854+
1855+ virtual Splash splash() = 0;
1856+
1857+ /* Orientation and placement */
1858+ struct Orientations
1859+ {
1860+ bool portrait;
1861+ bool landscape;
1862+ bool invertedPortrait;
1863+ bool invertedLandscape;
1864+
1865+ bool operator==(const Orientations& b) const
1866+ {
1867+ return portrait == b.portrait && landscape == b.landscape && invertedPortrait == b.invertedPortrait &&
1868+ invertedLandscape == b.invertedLandscape;
1869+ }
1870+ };
1871+
1872+ struct RotatesWindowTag;
1873+
1874+ typedef TypeTagger<RotatesWindowTag, bool> RotatesWindow;
1875+
1876+ virtual Orientations supportedOrientations() = 0;
1877+ virtual RotatesWindow rotatesWindowContents() = 0;
1878+
1879+ /* Lifecycle */
1880+ struct UbuntuLifecycleTag;
1881+
1882+ typedef TypeTagger<UbuntuLifecycleTag, bool> UbuntuLifecycle;
1883+
1884+ virtual UbuntuLifecycle supportsUbuntuLifecycle() = 0;
1885+ };
1886+
1887+ virtual std::shared_ptr<Info> info() = 0;
1888+
1889+ class Instance
1890+ {
1891+ public:
1892+ /* Query lifecycle */
1893+ virtual bool isRunning() = 0;
1894+
1895+ /* Instance Info */
1896+ virtual std::string logPath() = 0;
1897+
1898+ /* PIDs */
1899+ virtual pid_t primaryPid() = 0;
1900+ virtual bool hasPid(pid_t pid) = 0;
1901+ virtual std::vector<pid_t> pids() = 0;
1902+
1903+ /* Manage lifecycle */
1904+ virtual void pause() = 0;
1905+ virtual void resume() = 0;
1906+ virtual void stop() = 0;
1907+ };
1908+
1909+ virtual bool hasInstances() = 0;
1910+ virtual std::vector<std::shared_ptr<Instance>> instances() = 0;
1911+
1912+ virtual std::shared_ptr<Instance> launch(const std::vector<URL>& urls = {}) = 0;
1913+ virtual std::shared_ptr<Instance> launchTest(const std::vector<URL>& urls = {}) = 0;
1914+};
1915+
1916+}; // namespace app_launch
1917+}; // namespace ubuntu
1918+
1919+#pragma GCC visibility pop
1920
1921=== modified file 'libubuntu-app-launch/desktop-exec.c'
1922--- libubuntu-app-launch/desktop-exec.c 2015-08-12 02:52:05 +0000
1923+++ libubuntu-app-launch/desktop-exec.c 2016-03-04 11:34:19 +0000
1924@@ -186,7 +186,7 @@
1925 /* This string is quoted using desktop file quoting:
1926 http://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#exec-variables */
1927 gchar * execline = desktop_to_exec(keyfile, app_id);
1928- g_return_val_if_fail(execline != NULL, 1);
1929+ g_return_val_if_fail(execline != NULL, FALSE);
1930
1931 if (is_libertine) {
1932 static const gchar * libertine_launch = NULL;
1933
1934=== added file 'libubuntu-app-launch/glib-thread.cpp'
1935--- libubuntu-app-launch/glib-thread.cpp 1970-01-01 00:00:00 +0000
1936+++ libubuntu-app-launch/glib-thread.cpp 2016-03-04 11:34:19 +0000
1937@@ -0,0 +1,156 @@
1938+/*
1939+ * Copyright © 2015 Canonical Ltd.
1940+ *
1941+ * This program is free software: you can redistribute it and/or modify it
1942+ * under the terms of the GNU General Public License version 3, as published
1943+ * by the Free Software Foundation.
1944+ *
1945+ * This program is distributed in the hope that it will be useful, but
1946+ * WITHOUT ANY WARRANTY; without even the implied warranties of
1947+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1948+ * PURPOSE. See the GNU General Public License for more details.
1949+ *
1950+ * You should have received a copy of the GNU General Public License along
1951+ * with this program. If not, see <http://www.gnu.org/licenses/>.
1952+ *
1953+ * Authors:
1954+ * Ted Gould <ted.gould@canonical.com>
1955+ */
1956+
1957+#include "glib-thread.h"
1958+
1959+namespace GLib
1960+{
1961+
1962+ContextThread::ContextThread(std::function<void()> beforeLoop, std::function<void()> afterLoop)
1963+{
1964+ _cancel = std::shared_ptr<GCancellable>(g_cancellable_new(), [](GCancellable* cancel) {
1965+ if (cancel != nullptr)
1966+ {
1967+ g_cancellable_cancel(cancel);
1968+ g_object_unref(cancel);
1969+ }
1970+ });
1971+ std::promise<std::pair<std::shared_ptr<GMainContext>, std::shared_ptr<GMainLoop>>> context_promise;
1972+
1973+ /* NOTE: We copy afterLoop but reference beforeLoop. We're blocking so we
1974+ know that beforeLoop will stay valid long enough, but we can't say the
1975+ same for afterLoop */
1976+ _thread = std::thread([&context_promise, &beforeLoop, afterLoop, this]() {
1977+ /* Build up the context and loop for the async events and a place
1978+ for GDBus to send its events back to */
1979+ auto context = std::shared_ptr<GMainContext>(
1980+ g_main_context_new(), [](GMainContext* context) { g_clear_pointer(&context, g_main_context_unref); });
1981+ auto loop = std::shared_ptr<GMainLoop>(g_main_loop_new(context.get(), FALSE),
1982+ [](GMainLoop* loop) { g_clear_pointer(&loop, g_main_loop_unref); });
1983+
1984+ g_main_context_push_thread_default(context.get());
1985+
1986+ beforeLoop();
1987+
1988+ /* Free's the constructor to continue */
1989+ auto pair = std::pair<std::shared_ptr<GMainContext>, std::shared_ptr<GMainLoop>>(context, loop);
1990+ context_promise.set_value(pair);
1991+
1992+ if (!g_cancellable_is_cancelled(_cancel.get()))
1993+ {
1994+ g_main_loop_run(loop.get());
1995+ }
1996+
1997+ afterLoop();
1998+ });
1999+
2000+ /* We need to have the context and the mainloop ready before
2001+ other functions on this object can work properly. So we wait
2002+ for them and set them on this thread. */
2003+ auto context_future = context_promise.get_future();
2004+ context_future.wait();
2005+ auto context_value = context_future.get();
2006+
2007+ _context = context_value.first;
2008+ _loop = context_value.second;
2009+
2010+ if (!_context || !_loop)
2011+ {
2012+ throw std::runtime_error("Unable to create GLib Thread");
2013+ }
2014+}
2015+
2016+ContextThread::~ContextThread()
2017+{
2018+ quit();
2019+}
2020+
2021+void ContextThread::quit()
2022+{
2023+ g_cancellable_cancel(_cancel.get()); /* Force the cancellation on ongoing tasks */
2024+ if (_loop)
2025+ {
2026+ g_main_loop_quit(_loop.get()); /* Quit the loop */
2027+ }
2028+
2029+ /* Joining here because we want to ensure that the final afterLoop()
2030+ function is run before returning */
2031+ if (std::this_thread::get_id() != _thread.get_id())
2032+ {
2033+ if (_thread.joinable())
2034+ {
2035+ _thread.join();
2036+ }
2037+ }
2038+}
2039+
2040+bool ContextThread::isCancelled()
2041+{
2042+ return g_cancellable_is_cancelled(_cancel.get()) == TRUE;
2043+}
2044+
2045+std::shared_ptr<GCancellable> ContextThread::getCancellable()
2046+{
2047+ return _cancel;
2048+}
2049+
2050+void ContextThread::simpleSource(std::function<GSource*()> srcBuilder, std::function<void()> work)
2051+{
2052+ if (isCancelled())
2053+ {
2054+ throw std::runtime_error("Trying to execute work on a GLib thread that is shutting down.");
2055+ }
2056+
2057+ /* Copy the work so that we can reuse it */
2058+ /* Lifecycle is handled with the source pointer when we attach
2059+ it to the context. */
2060+ auto heapWork = new std::function<void()>(work);
2061+
2062+ auto source = std::shared_ptr<GSource>(srcBuilder(), [](GSource* src) { g_clear_pointer(&src, g_source_unref); });
2063+ g_source_set_callback(source.get(),
2064+ [](gpointer data) {
2065+ auto heapWork = static_cast<std::function<void()>*>(data);
2066+ (*heapWork)();
2067+ return G_SOURCE_REMOVE;
2068+ },
2069+ heapWork,
2070+ [](gpointer data) {
2071+ auto heapWork = static_cast<std::function<void()>*>(data);
2072+ delete heapWork;
2073+ });
2074+
2075+ g_source_attach(source.get(), _context.get());
2076+}
2077+
2078+void ContextThread::executeOnThread(std::function<void()> work)
2079+{
2080+ simpleSource(g_idle_source_new, work);
2081+}
2082+
2083+void ContextThread::timeout(const std::chrono::milliseconds& length, std::function<void()> work)
2084+{
2085+ simpleSource([length]() { return g_timeout_source_new(length.count()); }, work);
2086+}
2087+
2088+void ContextThread::timeoutSeconds(const std::chrono::seconds& length, std::function<void()> work)
2089+{
2090+ simpleSource([length]() { return g_timeout_source_new_seconds(length.count()); }, work);
2091+}
2092+
2093+} // ns GLib
2094
2095=== added file 'libubuntu-app-launch/glib-thread.h'
2096--- libubuntu-app-launch/glib-thread.h 1970-01-01 00:00:00 +0000
2097+++ libubuntu-app-launch/glib-thread.h 2016-03-04 11:34:19 +0000
2098@@ -0,0 +1,90 @@
2099+/*
2100+ * Copyright © 2015 Canonical Ltd.
2101+ *
2102+ * This program is free software: you can redistribute it and/or modify it
2103+ * under the terms of the GNU General Public License version 3, as published
2104+ * by the Free Software Foundation.
2105+ *
2106+ * This program is distributed in the hope that it will be useful, but
2107+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2108+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2109+ * PURPOSE. See the GNU General Public License for more details.
2110+ *
2111+ * You should have received a copy of the GNU General Public License along
2112+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2113+ *
2114+ * Authors:
2115+ * Ted Gould <ted.gould@canonical.com>
2116+ */
2117+
2118+#include <thread>
2119+#include <future>
2120+
2121+#include <gio/gio.h>
2122+
2123+namespace GLib
2124+{
2125+
2126+class ContextThread
2127+{
2128+ std::thread _thread;
2129+ std::shared_ptr<GMainContext> _context;
2130+ std::shared_ptr<GMainLoop> _loop;
2131+ std::shared_ptr<GCancellable> _cancel;
2132+
2133+public:
2134+ ContextThread(std::function<void()> beforeLoop =
2135+ []
2136+ {
2137+ },
2138+ std::function<void()> afterLoop =
2139+ []
2140+ {
2141+ });
2142+ ~ContextThread();
2143+
2144+ void quit();
2145+ bool isCancelled();
2146+ std::shared_ptr<GCancellable> getCancellable();
2147+
2148+ void executeOnThread(std::function<void()> work);
2149+ template <typename T>
2150+ auto executeOnThread(std::function<T()> work) -> T
2151+ {
2152+ if (std::this_thread::get_id() == _thread.get_id())
2153+ {
2154+ /* Don't block if we're on the same thread */
2155+ return work();
2156+ }
2157+
2158+ std::promise<T> promise;
2159+ std::function<void()> magicFunc = [&promise, &work]()
2160+ {
2161+ promise.set_value(work());
2162+ };
2163+
2164+ executeOnThread(magicFunc);
2165+
2166+ auto future = promise.get_future();
2167+ future.wait();
2168+ return future.get();
2169+ }
2170+
2171+ void timeout(const std::chrono::milliseconds& length, std::function<void()> work);
2172+ template <class Rep, class Period>
2173+ void timeout(const std::chrono::duration<Rep, Period>& length, std::function<void()> work)
2174+ {
2175+ return timeout(std::chrono::duration_cast<std::chrono::milliseconds>(length), work);
2176+ }
2177+
2178+ void timeoutSeconds(const std::chrono::seconds& length, std::function<void()> work);
2179+ template <class Rep, class Period>
2180+ void timeoutSeconds(const std::chrono::duration<Rep, Period>& length, std::function<void()> work)
2181+ {
2182+ return timeoutSeconds(std::chrono::duration_cast<std::chrono::seconds>(length), work);
2183+ }
2184+
2185+private:
2186+ void simpleSource(std::function<GSource*()> srcBuilder, std::function<void()> work);
2187+};
2188+}
2189
2190=== added file 'libubuntu-app-launch/helper-impl-click.cpp'
2191--- libubuntu-app-launch/helper-impl-click.cpp 1970-01-01 00:00:00 +0000
2192+++ libubuntu-app-launch/helper-impl-click.cpp 2016-03-04 11:34:19 +0000
2193@@ -0,0 +1,175 @@
2194+/*
2195+ * Copyright © 2016 Canonical Ltd.
2196+ *
2197+ * This program is free software: you can redistribute it and/or modify it
2198+ * under the terms of the GNU General Public License version 3, as published
2199+ * by the Free Software Foundation.
2200+ *
2201+ * This program is distributed in the hope that it will be useful, but
2202+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2203+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2204+ * PURPOSE. See the GNU General Public License for more details.
2205+ *
2206+ * You should have received a copy of the GNU General Public License along
2207+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2208+ *
2209+ * Authors:
2210+ * Ted Gould <ted.gould@canonical.com>
2211+ */
2212+
2213+#include "helper-impl-click.h"
2214+#include "registry-impl.h"
2215+
2216+#include "ubuntu-app-launch.h"
2217+
2218+namespace ubuntu
2219+{
2220+namespace app_launch
2221+{
2222+namespace helper_impls
2223+{
2224+
2225+bool Click::hasInstances()
2226+{
2227+ return _registry->impl->thread.executeOnThread<bool>([this]() {
2228+ auto instances = ubuntu_app_launch_list_helper_instances(_type.value().c_str(), ((std::string)_appid).c_str());
2229+ auto retval = (g_strv_length(instances) != 0);
2230+
2231+ g_strfreev(instances);
2232+
2233+ return retval;
2234+ });
2235+}
2236+
2237+class ClickInstance : public Helper::Instance
2238+{
2239+public: /* all one file, no one to hide from */
2240+ AppID _appid;
2241+ Helper::Type _type;
2242+ std::string _instanceid;
2243+ std::shared_ptr<Registry> _registry;
2244+
2245+ ClickInstance(const AppID& appid,
2246+ const Helper::Type& type,
2247+ const std::string& instanceid,
2248+ std::shared_ptr<Registry> registry)
2249+ : _appid(appid)
2250+ , _type(type)
2251+ , _instanceid(instanceid)
2252+ , _registry(registry)
2253+ {
2254+ }
2255+
2256+ bool isRunning() override
2257+ {
2258+ return _registry->impl->thread.executeOnThread<bool>([this]() {
2259+ bool found = false;
2260+
2261+ auto instances =
2262+ ubuntu_app_launch_list_helper_instances(_type.value().c_str(), ((std::string)_appid).c_str());
2263+ for (int i = 0; instances[i] != nullptr; i++)
2264+ {
2265+ if (_instanceid == std::string(instances[i]))
2266+ {
2267+ found = true;
2268+ break;
2269+ }
2270+ }
2271+
2272+ g_strfreev(instances);
2273+
2274+ return found;
2275+ });
2276+ }
2277+
2278+ void stop() override
2279+ {
2280+ _registry->impl->thread.executeOnThread<bool>([this]() {
2281+ return ubuntu_app_launch_stop_multiple_helper(_type.value().c_str(), ((std::string)_appid).c_str(),
2282+ _instanceid.c_str()) == TRUE;
2283+ });
2284+ }
2285+};
2286+
2287+std::vector<std::shared_ptr<Click::Instance>> Click::instances()
2288+{
2289+ return _registry->impl->thread.executeOnThread<std::vector<std::shared_ptr<Click::Instance>>>(
2290+ [this]() -> std::vector<std::shared_ptr<Click::Instance>> {
2291+ std::vector<std::shared_ptr<Click::Instance>> vect;
2292+ auto instances =
2293+ ubuntu_app_launch_list_helper_instances(_type.value().c_str(), ((std::string)_appid).c_str());
2294+ for (int i = 0; instances[i] != nullptr; i++)
2295+ {
2296+ auto inst = std::make_shared<ClickInstance>(_appid, _type, instances[i], _registry);
2297+ vect.push_back(inst);
2298+ }
2299+
2300+ g_strfreev(instances);
2301+
2302+ return vect;
2303+ });
2304+}
2305+
2306+std::shared_ptr<gchar*> urlsToStrv(std::vector<Helper::URL> urls)
2307+{
2308+ if (urls.size() == 0)
2309+ {
2310+ return {};
2311+ }
2312+
2313+ auto array = g_array_new(TRUE, FALSE, sizeof(gchar*));
2314+
2315+ for (auto url : urls)
2316+ {
2317+ auto str = g_strdup(url.value().c_str());
2318+ g_array_append_val(array, str);
2319+ }
2320+
2321+ return std::shared_ptr<gchar*>((gchar**)g_array_free(array, FALSE), g_strfreev);
2322+}
2323+
2324+std::shared_ptr<Click::Instance> Click::launch(std::vector<Helper::URL> urls)
2325+{
2326+ auto urlstrv = urlsToStrv(urls);
2327+
2328+ return _registry->impl->thread.executeOnThread<std::shared_ptr<Click::Instance>>([this, urlstrv]() {
2329+ auto instanceid = ubuntu_app_launch_start_multiple_helper(_type.value().c_str(), ((std::string)_appid).c_str(),
2330+ urlstrv.get());
2331+
2332+ return std::make_shared<ClickInstance>(_appid, _type, instanceid, _registry);
2333+ });
2334+}
2335+
2336+std::shared_ptr<Click::Instance> Click::launch(MirPromptSession* session, std::vector<Helper::URL> urls)
2337+{
2338+ auto urlstrv = urlsToStrv(urls);
2339+
2340+ return _registry->impl->thread.executeOnThread<std::shared_ptr<Click::Instance>>([this, session, urlstrv]() {
2341+ auto instanceid = ubuntu_app_launch_start_session_helper(_type.value().c_str(), session,
2342+ ((std::string)_appid).c_str(), urlstrv.get());
2343+
2344+ return std::make_shared<ClickInstance>(_appid, _type, instanceid, _registry);
2345+ });
2346+}
2347+
2348+std::list<std::shared_ptr<Helper>> Click::running(Helper::Type type, std::shared_ptr<Registry> registry)
2349+{
2350+ return registry->impl->thread.executeOnThread<std::list<std::shared_ptr<Helper>>>([type, registry]() {
2351+ std::list<std::shared_ptr<Helper>> helpers;
2352+
2353+ auto appidv = ubuntu_app_launch_list_helpers(type.value().c_str());
2354+ for (int i = 0; appidv[i] != nullptr; i++)
2355+ {
2356+ auto helper = std::make_shared<Click>(type, AppID::parse(appidv[i]), registry);
2357+ helpers.push_back(helper);
2358+ }
2359+
2360+ g_strfreev(appidv);
2361+
2362+ return helpers;
2363+ });
2364+}
2365+
2366+}; // namespace helper_impl
2367+}; // namespace app_launch
2368+}; // namespace ubuntu
2369
2370=== added file 'libubuntu-app-launch/helper-impl-click.h'
2371--- libubuntu-app-launch/helper-impl-click.h 1970-01-01 00:00:00 +0000
2372+++ libubuntu-app-launch/helper-impl-click.h 2016-03-04 11:34:19 +0000
2373@@ -0,0 +1,62 @@
2374+/*
2375+ * Copyright © 2016 Canonical Ltd.
2376+ *
2377+ * This program is free software: you can redistribute it and/or modify it
2378+ * under the terms of the GNU General Public License version 3, as published
2379+ * by the Free Software Foundation.
2380+ *
2381+ * This program is distributed in the hope that it will be useful, but
2382+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2383+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2384+ * PURPOSE. See the GNU General Public License for more details.
2385+ *
2386+ * You should have received a copy of the GNU General Public License along
2387+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2388+ *
2389+ * Authors:
2390+ * Ted Gould <ted.gould@canonical.com>
2391+ */
2392+
2393+#include <list>
2394+
2395+#include "helper.h"
2396+
2397+namespace ubuntu
2398+{
2399+namespace app_launch
2400+{
2401+namespace helper_impls
2402+{
2403+
2404+class Click : public Helper
2405+{
2406+public:
2407+ Click(const Helper::Type& type, const AppID& appid, const std::shared_ptr<Registry>& registry)
2408+ : _type(type)
2409+ , _appid(appid)
2410+ , _registry(registry)
2411+ {
2412+ }
2413+
2414+ AppID appId() override
2415+ {
2416+ return _appid;
2417+ }
2418+
2419+ bool hasInstances() override;
2420+ std::vector<std::shared_ptr<Helper::Instance>> instances() override;
2421+
2422+ std::shared_ptr<Helper::Instance> launch(std::vector<Helper::URL> urls = {}) override;
2423+ std::shared_ptr<Helper::Instance> launch(MirPromptSession* session, std::vector<Helper::URL> urls = {}) override;
2424+
2425+ static std::list<std::shared_ptr<Helper>> running(Helper::Type type, std::shared_ptr<Registry> registry);
2426+
2427+private:
2428+ Helper::Type _type;
2429+ AppID _appid;
2430+ std::shared_ptr<Registry> _registry;
2431+};
2432+
2433+}; // namespace helper_impl
2434+}; // namespace app_launch
2435+}; // namespace ubuntu
2436
2437=== added file 'libubuntu-app-launch/helper.cpp'
2438--- libubuntu-app-launch/helper.cpp 1970-01-01 00:00:00 +0000
2439+++ libubuntu-app-launch/helper.cpp 2016-03-04 11:34:19 +0000
2440@@ -0,0 +1,36 @@
2441+/*
2442+ * Copyright © 2016 Canonical Ltd.
2443+ *
2444+ * This program is free software: you can redistribute it and/or modify it
2445+ * under the terms of the GNU General Public License version 3, as published
2446+ * by the Free Software Foundation.
2447+ *
2448+ * This program is distributed in the hope that it will be useful, but
2449+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2450+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2451+ * PURPOSE. See the GNU General Public License for more details.
2452+ *
2453+ * You should have received a copy of the GNU General Public License along
2454+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2455+ *
2456+ * Authors:
2457+ * Ted Gould <ted.gould@canonical.com>
2458+ */
2459+
2460+#include "helper.h"
2461+
2462+#include "helper-impl-click.h"
2463+
2464+namespace ubuntu
2465+{
2466+namespace app_launch
2467+{
2468+
2469+std::shared_ptr<Helper> Helper::create(Type type, AppID appid, std::shared_ptr<Registry> registry)
2470+{
2471+ /* Only one type today */
2472+ return std::make_shared<helper_impls::Click>(type, appid, registry);
2473+}
2474+
2475+}; // namespace AppLaunch
2476+}; // namespace Ubuntu
2477
2478=== added file 'libubuntu-app-launch/helper.h'
2479--- libubuntu-app-launch/helper.h 1970-01-01 00:00:00 +0000
2480+++ libubuntu-app-launch/helper.h 2016-03-04 11:34:19 +0000
2481@@ -0,0 +1,71 @@
2482+/*
2483+ * Copyright © 2016 Canonical Ltd.
2484+ *
2485+ * This program is free software: you can redistribute it and/or modify it
2486+ * under the terms of the GNU General Public License version 3, as published
2487+ * by the Free Software Foundation.
2488+ *
2489+ * This program is distributed in the hope that it will be useful, but
2490+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2491+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2492+ * PURPOSE. See the GNU General Public License for more details.
2493+ *
2494+ * You should have received a copy of the GNU General Public License along
2495+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2496+ *
2497+ * Authors:
2498+ * Ted Gould <ted.gould@canonical.com>
2499+ */
2500+
2501+#include <memory>
2502+#include <vector>
2503+
2504+#include <mir_toolkit/mir_prompt_session.h>
2505+
2506+#include "appid.h"
2507+#include "type-tagger.h"
2508+
2509+#pragma once
2510+#pragma GCC visibility push(default)
2511+
2512+namespace ubuntu
2513+{
2514+namespace app_launch
2515+{
2516+
2517+class Registry;
2518+
2519+class Helper
2520+{
2521+public:
2522+ struct TypeTag;
2523+ struct URLTag;
2524+
2525+ typedef TypeTagger<TypeTag, std::string> Type;
2526+ typedef TypeTagger<URLTag, std::string> URL;
2527+
2528+ static std::shared_ptr<Helper> create(Type type, AppID appid, std::shared_ptr<Registry> registry);
2529+
2530+ virtual AppID appId() = 0;
2531+
2532+ class Instance
2533+ {
2534+ public:
2535+ /* Query lifecycle */
2536+ virtual bool isRunning() = 0;
2537+
2538+ /* Manage lifecycle */
2539+ virtual void stop() = 0;
2540+ };
2541+
2542+ virtual bool hasInstances() = 0;
2543+ virtual std::vector<std::shared_ptr<Instance>> instances() = 0;
2544+
2545+ virtual std::shared_ptr<Instance> launch(std::vector<URL> urls = {}) = 0;
2546+ virtual std::shared_ptr<Instance> launch(MirPromptSession* session, std::vector<URL> urls = {}) = 0;
2547+};
2548+
2549+}; // namespace app_launch
2550+}; // namespace ubuntu
2551+
2552+#pragma GCC visibility pop
2553
2554=== modified file 'libubuntu-app-launch/libubuntu-app-launch.map'
2555--- libubuntu-app-launch/libubuntu-app-launch.map 2014-11-21 15:57:08 +0000
2556+++ libubuntu-app-launch/libubuntu-app-launch.map 2016-03-04 11:34:19 +0000
2557@@ -1,6 +1,23 @@
2558 {
2559 global:
2560 ubuntu_app_launch_*;
2561+ extern "C++" {
2562+ ubuntu::app_launch::Registry::[!I]*;
2563+ typeinfo?for?ubuntu::app_launch::Registry;
2564+ typeinfo?name?for?ubuntu::app_launch::Registry;
2565+ ubuntu::app_launch::Application::*;
2566+ typeinfo?for?ubuntu::app_launch::Application;
2567+ typeinfo?name?for?ubuntu::app_launch::Application;
2568+ ubuntu::app_launch::AppID::*;
2569+ typeinfo?for?ubuntu::app_launch::AppID;
2570+ typeinfo?name?for?ubuntu::app_launch::AppID;
2571+ ubuntu::app_launch::Helper::*;
2572+ typeinfo?for?ubuntu::app_launch::Helper;
2573+ typeinfo?name?for?ubuntu::app_launch::Helper;
2574+ };
2575 local:
2576 *;
2577+ extern "C++" {
2578+ *;
2579+ };
2580 };
2581
2582=== added file 'libubuntu-app-launch/registry-impl.cpp'
2583--- libubuntu-app-launch/registry-impl.cpp 1970-01-01 00:00:00 +0000
2584+++ libubuntu-app-launch/registry-impl.cpp 2016-03-04 11:34:19 +0000
2585@@ -0,0 +1,185 @@
2586+/*
2587+ * Copyright © 2016 Canonical Ltd.
2588+ *
2589+ * This program is free software: you can redistribute it and/or modify it
2590+ * under the terms of the GNU General Public License version 3, as published
2591+ * by the Free Software Foundation.
2592+ *
2593+ * This program is distributed in the hope that it will be useful, but
2594+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2595+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2596+ * PURPOSE. See the GNU General Public License for more details.
2597+ *
2598+ * You should have received a copy of the GNU General Public License along
2599+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2600+ *
2601+ * Authors:
2602+ * Ted Gould <ted.gould@canonical.com>
2603+ */
2604+
2605+#include "registry-impl.h"
2606+
2607+namespace ubuntu
2608+{
2609+namespace app_launch
2610+{
2611+
2612+Registry::Impl::Impl(Registry* registry)
2613+ : thread([]() {},
2614+ [this]() {
2615+ _clickUser.reset();
2616+ _clickDB.reset();
2617+
2618+ g_dbus_connection_flush_sync(_dbus.get(), nullptr, nullptr);
2619+ _dbus.reset();
2620+ })
2621+ , _registry(registry)
2622+// _manager(nullptr)
2623+{
2624+ auto cancel = thread.getCancellable();
2625+ _dbus = thread.executeOnThread<std::shared_ptr<GDBusConnection>>([cancel]() {
2626+ return std::shared_ptr<GDBusConnection>(g_bus_get_sync(G_BUS_TYPE_SESSION, cancel.get(), nullptr),
2627+ [](GDBusConnection* bus) { g_clear_object(&bus); });
2628+ });
2629+}
2630+
2631+void Registry::Impl::initClick()
2632+{
2633+ if (_clickDB && _clickUser)
2634+ {
2635+ return;
2636+ }
2637+
2638+ auto init = thread.executeOnThread<bool>([this]() {
2639+ GError* error = nullptr;
2640+
2641+ if (!_clickDB)
2642+ {
2643+ _clickDB = std::shared_ptr<ClickDB>(click_db_new(), [](ClickDB* db) { g_clear_object(&db); });
2644+ /* If TEST_CLICK_DB is unset, this reads the system database. */
2645+ click_db_read(_clickDB.get(), g_getenv("TEST_CLICK_DB"), &error);
2646+
2647+ if (error != nullptr)
2648+ {
2649+ auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
2650+ throw std::runtime_error(perror->message);
2651+ }
2652+ }
2653+
2654+ if (!_clickUser)
2655+ {
2656+ _clickUser =
2657+ std::shared_ptr<ClickUser>(click_user_new_for_user(_clickDB.get(), g_getenv("TEST_CLICK_USER"), &error),
2658+ [](ClickUser* user) { g_clear_object(&user); });
2659+
2660+ if (error != nullptr)
2661+ {
2662+ auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
2663+ throw std::runtime_error(perror->message);
2664+ }
2665+ }
2666+
2667+ return true;
2668+ });
2669+
2670+ if (!init)
2671+ {
2672+ throw std::runtime_error("Unable to initialize the Click Database");
2673+ }
2674+}
2675+
2676+std::shared_ptr<JsonObject> Registry::Impl::getClickManifest(const std::string& package)
2677+{
2678+ initClick();
2679+
2680+ auto retval = thread.executeOnThread<std::shared_ptr<JsonObject>>([this, package]() {
2681+ GError* error = nullptr;
2682+ auto retval = std::shared_ptr<JsonObject>(click_user_get_manifest(_clickUser.get(), package.c_str(), &error),
2683+ [](JsonObject* obj) {
2684+ if (obj != nullptr)
2685+ {
2686+ json_object_unref(obj);
2687+ }
2688+ });
2689+
2690+ if (error != nullptr)
2691+ {
2692+ auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
2693+ throw std::runtime_error(perror->message);
2694+ }
2695+
2696+ return retval;
2697+ });
2698+
2699+ if (!retval)
2700+ throw std::runtime_error("Unable to get Click manifest for package: " + package);
2701+
2702+ return retval;
2703+}
2704+
2705+std::list<AppID::Package> Registry::Impl::getClickPackages()
2706+{
2707+ initClick();
2708+
2709+ return thread.executeOnThread<std::list<AppID::Package>>([this]() {
2710+ GError* error = nullptr;
2711+ GList* pkgs = click_db_get_packages(_clickDB.get(), FALSE, &error);
2712+
2713+ if (error != nullptr)
2714+ {
2715+ auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
2716+ throw std::runtime_error(perror->message);
2717+ }
2718+
2719+ std::list<AppID::Package> list;
2720+ for (GList* item = pkgs; item != NULL; item = g_list_next(item))
2721+ {
2722+ list.emplace_back(AppID::Package::from_raw((gchar*)item->data));
2723+ }
2724+
2725+ g_list_free_full(pkgs, g_free);
2726+ return list;
2727+ });
2728+}
2729+
2730+std::string Registry::Impl::getClickDir(const std::string& package)
2731+{
2732+ initClick();
2733+
2734+ return thread.executeOnThread<std::string>([this, package]() {
2735+ GError* error = nullptr;
2736+ auto dir = click_user_get_path(_clickUser.get(), package.c_str(), &error);
2737+
2738+ if (error != nullptr)
2739+ {
2740+ auto perror = std::shared_ptr<GError>(error, [](GError* error) { g_error_free(error); });
2741+ throw std::runtime_error(perror->message);
2742+ }
2743+
2744+ std::string cppdir(dir);
2745+ g_free(dir);
2746+ return cppdir;
2747+ });
2748+}
2749+
2750+#if 0
2751+void
2752+Registry::Impl::setManager (Registry::Manager* manager)
2753+{
2754+ if (_manager != nullptr)
2755+ {
2756+ throw std::runtime_error("Already have a manager and trying to set another");
2757+ }
2758+
2759+ _manager = manager;
2760+}
2761+
2762+void
2763+Registry::Impl::clearManager ()
2764+{
2765+ _manager = nullptr;
2766+}
2767+#endif
2768+
2769+}; // namespace app_launch
2770+}; // namespace ubuntu
2771
2772=== added file 'libubuntu-app-launch/registry-impl.h'
2773--- libubuntu-app-launch/registry-impl.h 1970-01-01 00:00:00 +0000
2774+++ libubuntu-app-launch/registry-impl.h 2016-03-04 11:34:19 +0000
2775@@ -0,0 +1,69 @@
2776+/*
2777+ * Copyright © 2016 Canonical Ltd.
2778+ *
2779+ * This program is free software: you can redistribute it and/or modify it
2780+ * under the terms of the GNU General Public License version 3, as published
2781+ * by the Free Software Foundation.
2782+ *
2783+ * This program is distributed in the hope that it will be useful, but
2784+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2785+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2786+ * PURPOSE. See the GNU General Public License for more details.
2787+ *
2788+ * You should have received a copy of the GNU General Public License along
2789+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2790+ *
2791+ * Authors:
2792+ * Ted Gould <ted.gould@canonical.com>
2793+ */
2794+
2795+#include "registry.h"
2796+#include "glib-thread.h"
2797+
2798+#include <json-glib/json-glib.h>
2799+#include <click.h>
2800+#include <gio/gio.h>
2801+
2802+#pragma once
2803+
2804+namespace ubuntu
2805+{
2806+namespace app_launch
2807+{
2808+
2809+class Registry::Impl
2810+{
2811+public:
2812+ Impl(Registry* registry);
2813+ virtual ~Impl()
2814+ {
2815+ thread.quit();
2816+ }
2817+
2818+ std::shared_ptr<JsonObject> getClickManifest(const std::string& package);
2819+ std::list<AppID::Package> getClickPackages();
2820+ std::string getClickDir(const std::string& package);
2821+
2822+#if 0
2823+ void setManager (Registry::Manager* manager);
2824+ void clearManager ();
2825+#endif
2826+
2827+ GLib::ContextThread thread;
2828+
2829+private:
2830+ Registry* _registry;
2831+#if 0
2832+ Registry::Manager* _manager;
2833+#endif
2834+
2835+ std::shared_ptr<ClickDB> _clickDB;
2836+ std::shared_ptr<ClickUser> _clickUser;
2837+
2838+ std::shared_ptr<GDBusConnection> _dbus;
2839+
2840+ void initClick();
2841+};
2842+
2843+}; // namespace app_launch
2844+}; // namespace ubuntu
2845
2846=== added file 'libubuntu-app-launch/registry.cpp'
2847--- libubuntu-app-launch/registry.cpp 1970-01-01 00:00:00 +0000
2848+++ libubuntu-app-launch/registry.cpp 2016-03-04 11:34:19 +0000
2849@@ -0,0 +1,99 @@
2850+/*
2851+ * Copyright © 2016 Canonical Ltd.
2852+ *
2853+ * This program is free software: you can redistribute it and/or modify it
2854+ * under the terms of the GNU General Public License version 3, as published
2855+ * by the Free Software Foundation.
2856+ *
2857+ * This program is distributed in the hope that it will be useful, but
2858+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2859+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2860+ * PURPOSE. See the GNU General Public License for more details.
2861+ *
2862+ * You should have received a copy of the GNU General Public License along
2863+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2864+ *
2865+ * Authors:
2866+ * Ted Gould <ted.gould@canonical.com>
2867+ */
2868+
2869+#include "registry.h"
2870+#include "registry-impl.h"
2871+
2872+#include "application-impl-click.h"
2873+#include "application-impl-legacy.h"
2874+#include "application-impl-libertine.h"
2875+
2876+#include "helper-impl-click.h"
2877+
2878+namespace ubuntu
2879+{
2880+namespace app_launch
2881+{
2882+
2883+Registry::Registry()
2884+{
2885+ impl = std::unique_ptr<Impl>(new Impl(this));
2886+}
2887+
2888+Registry::~Registry()
2889+{
2890+}
2891+
2892+std::list<std::shared_ptr<Application>> Registry::runningApps(std::shared_ptr<Registry> connection)
2893+{
2894+ return connection->impl->thread.executeOnThread<std::list<std::shared_ptr<Application>>>(
2895+ [connection]() -> std::list<std::shared_ptr<Application>> {
2896+ auto strv = ubuntu_app_launch_list_running_apps();
2897+ if (strv == nullptr)
2898+ {
2899+ return {};
2900+ }
2901+
2902+ std::list<std::shared_ptr<Application>> list;
2903+ for (int i = 0; strv[i] != nullptr; i++)
2904+ {
2905+ auto appid = AppID::find(strv[i]);
2906+ auto app = Application::create(appid, connection);
2907+ list.push_back(app);
2908+ }
2909+
2910+ g_strfreev(strv);
2911+
2912+ return list;
2913+ });
2914+}
2915+
2916+std::list<std::shared_ptr<Application>> Registry::installedApps(std::shared_ptr<Registry> connection)
2917+{
2918+ std::list<std::shared_ptr<Application>> list;
2919+
2920+ list.splice(list.begin(), app_impls::Click::list(connection));
2921+ list.splice(list.begin(), app_impls::Legacy::list(connection));
2922+ list.splice(list.begin(), app_impls::Libertine::list(connection));
2923+
2924+ return list;
2925+}
2926+
2927+std::list<std::shared_ptr<Helper>> Registry::runningHelpers(Helper::Type type, std::shared_ptr<Registry> connection)
2928+{
2929+ std::list<std::shared_ptr<Helper>> list;
2930+
2931+ list.splice(list.begin(), helper_impls::Click::running(type, connection));
2932+
2933+ return list;
2934+}
2935+
2936+std::shared_ptr<Registry> defaultRegistry;
2937+std::shared_ptr<Registry> Registry::getDefault()
2938+{
2939+ if (!defaultRegistry)
2940+ {
2941+ defaultRegistry = std::make_shared<Registry>();
2942+ }
2943+
2944+ return defaultRegistry;
2945+}
2946+
2947+}; // namespace app_launch
2948+}; // namespace ubuntu
2949
2950=== added file 'libubuntu-app-launch/registry.h'
2951--- libubuntu-app-launch/registry.h 1970-01-01 00:00:00 +0000
2952+++ libubuntu-app-launch/registry.h 2016-03-04 11:34:19 +0000
2953@@ -0,0 +1,91 @@
2954+/*
2955+ * Copyright © 2016 Canonical Ltd.
2956+ *
2957+ * This program is free software: you can redistribute it and/or modify it
2958+ * under the terms of the GNU General Public License version 3, as published
2959+ * by the Free Software Foundation.
2960+ *
2961+ * This program is distributed in the hope that it will be useful, but
2962+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2963+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2964+ * PURPOSE. See the GNU General Public License for more details.
2965+ *
2966+ * You should have received a copy of the GNU General Public License along
2967+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2968+ *
2969+ * Authors:
2970+ * Ted Gould <ted.gould@canonical.com>
2971+ */
2972+
2973+#include <core/signal.h>
2974+#include <functional>
2975+#include <list>
2976+#include <memory>
2977+
2978+#include "application.h"
2979+#include "helper.h"
2980+
2981+#pragma once
2982+#pragma GCC visibility push(default)
2983+
2984+namespace ubuntu
2985+{
2986+namespace app_launch
2987+{
2988+
2989+class Registry
2990+{
2991+public:
2992+ enum class FailureType
2993+ {
2994+ CRASH,
2995+ START_FAILURE,
2996+ };
2997+
2998+ Registry();
2999+ virtual ~Registry();
3000+
3001+ /* Lots of application lists */
3002+ static std::list<std::shared_ptr<Application>> runningApps(std::shared_ptr<Registry> registry = getDefault());
3003+ static std::list<std::shared_ptr<Application>> installedApps(std::shared_ptr<Registry> registry = getDefault());
3004+
3005+#if 0 /* TODO -- In next MR */
3006+ /* Signals to discover what is happening to apps */
3007+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appStarted;
3008+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appStopped;
3009+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType> appFailed;
3010+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appPaused;
3011+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appResumed;
3012+
3013+ /* The Application Manager, almost always if you're not Unity8, don't
3014+ use this API. Testing is a special case. */
3015+ class Manager
3016+ {
3017+ virtual bool focusRequest (std::shared_ptr<Application> app, std::shared_ptr<Application::Instance> instance) = 0;
3018+ virtual bool startingRequest (std::shared_ptr<Application> app, std::shared_ptr<Application::Instance> instance) = 0;
3019+
3020+ protected:
3021+ Manager() = default;
3022+ };
3023+
3024+ void setManager (Manager* manager);
3025+ void clearManager ();
3026+#endif
3027+
3028+ /* Helper Lists */
3029+ static std::list<std::shared_ptr<Helper>> runningHelpers(Helper::Type type,
3030+ std::shared_ptr<Registry> registry = getDefault());
3031+
3032+ /* Default Junk */
3033+ static std::shared_ptr<Registry> getDefault();
3034+ static void clearDefault();
3035+
3036+ /* Hide our implementation */
3037+ class Impl;
3038+ std::unique_ptr<Impl> impl;
3039+};
3040+
3041+}; // namespace app_launch
3042+}; // namespace ubuntu
3043+
3044+#pragma GCC visibility pop
3045
3046=== added file 'libubuntu-app-launch/type-tagger.h'
3047--- libubuntu-app-launch/type-tagger.h 1970-01-01 00:00:00 +0000
3048+++ libubuntu-app-launch/type-tagger.h 2016-03-04 11:34:19 +0000
3049@@ -0,0 +1,37 @@
3050+#pragma once
3051+
3052+namespace ubuntu
3053+{
3054+namespace app_launch
3055+{
3056+
3057+template <typename Tag, typename T>
3058+class TypeTagger
3059+{
3060+public:
3061+ static TypeTagger<Tag, T> from_raw(const T& value)
3062+ {
3063+ return TypeTagger<Tag, T>(value);
3064+ }
3065+ const T& value() const
3066+ {
3067+ return _value;
3068+ }
3069+ operator T() const
3070+ {
3071+ return _value;
3072+ }
3073+ ~TypeTagger()
3074+ {
3075+ }
3076+
3077+private:
3078+ TypeTagger(const T& value)
3079+ : _value(value)
3080+ {
3081+ }
3082+ T _value;
3083+};
3084+
3085+}; // namespace app_launch
3086+}; // namespace ubuntu
3087
3088=== modified file 'libubuntu-app-launch/ubuntu-app-launch.c'
3089--- libubuntu-app-launch/ubuntu-app-launch.c 2015-08-17 21:38:29 +0000
3090+++ libubuntu-app-launch/ubuntu-app-launch.c 2016-03-04 11:34:19 +0000
3091@@ -38,7 +38,6 @@
3092
3093 static void apps_for_job (GDBusConnection * con, const gchar * name, GArray * apps, gboolean truncate_legacy);
3094 static void free_helper (gpointer value);
3095-static GList * pids_for_appid (const gchar * appid);
3096 int kill (pid_t pid, int signal);
3097 static gchar * escape_dbus_string (const gchar * input);
3098
3099@@ -588,7 +587,7 @@
3100
3101 do {
3102 hash_table_size = g_hash_table_size(pidssignaled);
3103- GList * pidlist = pids_for_appid(appid);
3104+ GList * pidlist = ubuntu_app_launch_get_pids(appid);
3105 GList * iter;
3106
3107 if (pidlist == NULL) {
3108@@ -1506,9 +1505,10 @@
3109 /* Get the PIDs for an AppID. If it's click or legacy single instance that's
3110 a simple call to the helper. But if it's not, we have to make a call for
3111 each instance of the app that we have running. */
3112-static GList *
3113-pids_for_appid (const gchar * appid)
3114+GList *
3115+ubuntu_app_launch_get_pids (const gchar * appid)
3116 {
3117+ g_return_val_if_fail(appid != NULL, NULL);
3118 ual_tracepoint(pids_list_start, appid);
3119
3120 GDBusConnection * cgmanager = cgroup_manager_connection();
3121@@ -1522,7 +1522,7 @@
3122
3123 ual_tracepoint(pids_list_finished, appid, g_list_length(pids));
3124 return pids;
3125- } else if (!is_libertine(appid) && legacy_single_instance(appid)) {
3126+ } else if (is_libertine(appid) || legacy_single_instance(appid)) {
3127 gchar * jobname = g_strdup_printf("%s-", appid);
3128 GList * pids = pids_from_cgroup(cgmanager, "application-legacy", jobname);
3129 g_free(jobname);
3130@@ -1572,7 +1572,7 @@
3131 return FALSE;
3132 }
3133
3134- GList * pidlist = pids_for_appid(appid);
3135+ GList * pidlist = ubuntu_app_launch_get_pids(appid);
3136 GList * head;
3137
3138 for (head = pidlist; head != NULL; head = g_list_next(head)) {
3139@@ -2403,7 +2403,7 @@
3140 const gchar * job_name = g_getenv("UPSTART_JOB");
3141 const gchar * instance_name = g_getenv("UPSTART_INSTANCE");
3142 const gchar * demangler = g_getenv("UBUNTU_APP_LAUNCH_DEMANGLE_NAME");
3143- g_return_if_fail(job_name != NULL);
3144+ g_return_val_if_fail(job_name != NULL, FALSE);
3145
3146 GError * error = NULL;
3147 GDBusConnection * bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error);
3148
3149=== modified file 'libubuntu-app-launch/ubuntu-app-launch.h'
3150--- libubuntu-app-launch/ubuntu-app-launch.h 2015-08-10 14:19:05 +0000
3151+++ libubuntu-app-launch/ubuntu-app-launch.h 2016-03-04 11:34:19 +0000
3152@@ -388,6 +388,18 @@
3153 GPid ubuntu_app_launch_get_primary_pid (const gchar * appid);
3154
3155 /**
3156+ * ubuntu_app_launch_get_pids:
3157+ * @appid: ID of the application to look for
3158+ *
3159+ * Checks to see if an application is running and returns
3160+ * the PIDs associated with it.
3161+ *
3162+ * Return Value: (transfer full) (element-type GLib.Pid): A list
3163+ * of PIDs associated with @appid, empty if not running.
3164+ */
3165+GList * ubuntu_app_launch_get_pids (const gchar * appid);
3166+
3167+/**
3168 * ubuntu_app_launch_pid_in_app_id:
3169 * @pid: Process ID to check on
3170 * @appid: ID of the application to look in
3171
3172=== modified file 'tests/CMakeLists.txt'
3173--- tests/CMakeLists.txt 2015-06-26 17:26:50 +0000
3174+++ tests/CMakeLists.txt 2016-03-04 11:34:19 +0000
3175@@ -42,6 +42,11 @@
3176 mir-mock.cpp)
3177 target_link_libraries (libual-test gtest ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
3178
3179+add_executable (libual-cpp-test
3180+ libual-cpp-test.cc
3181+ mir-mock.cpp)
3182+target_link_libraries (libual-cpp-test gtest ${GTEST_LIBS} ${LIBUPSTART_LIBRARIES} ${DBUSTEST_LIBRARIES} ubuntu-launcher)
3183+
3184 add_executable (data-spew
3185 data-spew.c)
3186 target_link_libraries (data-spew ${GLIB2_LIBRARIES})
3187@@ -50,6 +55,16 @@
3188 socket-tool.c)
3189
3190 add_test (NAME libual-test COMMAND libual-test)
3191+add_test (NAME libual-cpp-test COMMAND libual-cpp-test)
3192+
3193+# Application Info Desktop
3194+
3195+add_executable (application-info-desktop-test
3196+ application-info-desktop.cpp
3197+ ${CMAKE_SOURCE_DIR}/libubuntu-app-launch/application-info-desktop.cpp)
3198+target_link_libraries (application-info-desktop-test gtest ${GTEST_LIBS} ubuntu-launcher)
3199+
3200+add_test (NAME application-info-desktop-test COMMAND application-info-desktop-test)
3201
3202 # Failure Test
3203
3204@@ -101,3 +116,8 @@
3205 configure_file ("xmir-helper-test.in" "${CMAKE_CURRENT_BINARY_DIR}/xmir-helper-test" @ONLY)
3206 add_test (xmir-helper-test xmir-helper-test)
3207
3208+# Formatted code
3209+
3210+add_custom_target(format-tests
3211+ COMMAND clang-format -i -style=file libual-cpp-test.cc
3212+)
3213
3214=== added file 'tests/application-info-desktop.cpp'
3215--- tests/application-info-desktop.cpp 1970-01-01 00:00:00 +0000
3216+++ tests/application-info-desktop.cpp 2016-03-04 11:34:19 +0000
3217@@ -0,0 +1,148 @@
3218+/*
3219+ * Copyright © 2016 Canonical Ltd.
3220+ *
3221+ * This program is free software: you can redistribute it and/or modify it
3222+ * under the terms of the GNU General Public License version 3, as published
3223+ * by the Free Software Foundation.
3224+ *
3225+ * This program is distributed in the hope that it will be useful, but
3226+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3227+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3228+ * PURPOSE. See the GNU General Public License for more details.
3229+ *
3230+ * You should have received a copy of the GNU General Public License along
3231+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3232+ *
3233+ * Authors:
3234+ * Ted Gould <ted.gould@canonical.com>
3235+ */
3236+
3237+#include "application-info-desktop.h"
3238+
3239+#include <gtest/gtest.h>
3240+
3241+class ApplicationInfoDesktop : public ::testing::Test
3242+{
3243+ virtual void SetUp()
3244+ {
3245+ }
3246+
3247+ virtual void TearDown()
3248+ {
3249+ }
3250+};
3251+
3252+#define DESKTOP "Desktop Entry"
3253+
3254+TEST_F(ApplicationInfoDesktop, DefaultState)
3255+{
3256+ auto keyfile = std::shared_ptr<GKeyFile>(g_key_file_new(), g_key_file_free);
3257+ g_key_file_set_string(keyfile.get(), DESKTOP, "Name", "Foo App");
3258+ g_key_file_set_string(keyfile.get(), DESKTOP, "Exec", "foo");
3259+ g_key_file_set_string(keyfile.get(), DESKTOP, "Icon", "foo.png");
3260+
3261+ auto appinfo = ubuntu::app_launch::app_info::Desktop(keyfile, "/");
3262+
3263+ EXPECT_EQ("Foo App", appinfo.name().value());
3264+ EXPECT_EQ("", appinfo.description().value());
3265+ EXPECT_EQ("/foo.png", appinfo.iconPath().value());
3266+
3267+ EXPECT_EQ("", appinfo.splash().title.value());
3268+ EXPECT_EQ("", appinfo.splash().image.value());
3269+ EXPECT_EQ("", appinfo.splash().backgroundColor.value());
3270+ EXPECT_EQ("", appinfo.splash().headerColor.value());
3271+ EXPECT_EQ("", appinfo.splash().footerColor.value());
3272+ EXPECT_FALSE( appinfo.splash().showHeader.value());
3273+
3274+ EXPECT_TRUE(appinfo.supportedOrientations().portrait);
3275+ EXPECT_TRUE(appinfo.supportedOrientations().landscape);
3276+ EXPECT_TRUE(appinfo.supportedOrientations().invertedPortrait);
3277+ EXPECT_TRUE(appinfo.supportedOrientations().invertedLandscape);
3278+
3279+ EXPECT_FALSE(appinfo.rotatesWindowContents().value());
3280+
3281+ EXPECT_FALSE(appinfo.supportsUbuntuLifecycle().value());
3282+}
3283+
3284+TEST_F(ApplicationInfoDesktop, KeyfileErrors)
3285+{
3286+ EXPECT_THROW(ubuntu::app_launch::app_info::Desktop({}, "/"), std::runtime_error);
3287+
3288+ auto noname = std::shared_ptr<GKeyFile>(g_key_file_new(), g_key_file_free);
3289+ g_key_file_set_string(noname.get(), DESKTOP, "Comment", "This is a comment");
3290+ g_key_file_set_string(noname.get(), DESKTOP, "Exec", "foo");
3291+ g_key_file_set_string(noname.get(), DESKTOP, "Icon", "foo.png");
3292+
3293+ EXPECT_THROW(ubuntu::app_launch::app_info::Desktop(noname, "/"), std::runtime_error);
3294+
3295+ auto noicon = std::shared_ptr<GKeyFile>(g_key_file_new(), g_key_file_free);
3296+ g_key_file_set_string(noicon.get(), DESKTOP, "Name", "Foo App");
3297+ g_key_file_set_string(noicon.get(), DESKTOP, "Comment", "This is a comment");
3298+ g_key_file_set_string(noicon.get(), DESKTOP, "Exec", "foo");
3299+
3300+ EXPECT_THROW(ubuntu::app_launch::app_info::Desktop(noicon, "/"), std::runtime_error);
3301+}
3302+
3303+TEST_F(ApplicationInfoDesktop, Orientations)
3304+{
3305+ ubuntu::app_launch::Application::Info::Orientations defaultOrientations =
3306+ {
3307+portrait:
3308+ true,
3309+landscape:
3310+ true,
3311+invertedPortrait:
3312+ true,
3313+invertedLandscape:
3314+ true
3315+ };
3316+
3317+ auto keyfile = std::shared_ptr<GKeyFile>(g_key_file_new(), g_key_file_free);
3318+ g_key_file_set_string(keyfile.get(), DESKTOP, "Name", "Foo App");
3319+ g_key_file_set_string(keyfile.get(), DESKTOP, "Exec", "foo");
3320+ g_key_file_set_string(keyfile.get(), DESKTOP, "Icon", "foo.png");
3321+
3322+ EXPECT_EQ(defaultOrientations, ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3323+
3324+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "this should not parse");
3325+ EXPECT_EQ(defaultOrientations, ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3326+
3327+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "this;should;not;parse;");
3328+ EXPECT_EQ(defaultOrientations, ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3329+
3330+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "portrait;");
3331+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: true, landscape: false, invertedPortrait: false, invertedLandscape: false}),
3332+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3333+
3334+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "landscape;portrait;");
3335+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: true, landscape: true, invertedPortrait: false, invertedLandscape: false}),
3336+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3337+
3338+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "landscape ; portrait; invertedPortrait");
3339+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: true, landscape: true, invertedPortrait: true, invertedLandscape: false}),
3340+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3341+
3342+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "portrait;landscape;");
3343+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: true, landscape: true, invertedPortrait: false, invertedLandscape: false}),
3344+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3345+
3346+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "portrait;landscape;invertedportrait;invertedlandscape;");
3347+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: true, landscape: true, invertedPortrait: true, invertedLandscape: true}),
3348+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3349+
3350+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "PORTRAIT;");
3351+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: true, landscape: false, invertedPortrait: false, invertedLandscape: false}),
3352+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3353+
3354+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "pOrTraIt;lANDscApE;inVErtEDpORtrAit;iNVErtEDLAnDsCapE;");
3355+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: true, landscape: true, invertedPortrait: true, invertedLandscape: true}),
3356+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3357+
3358+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "primary;");
3359+ EXPECT_EQ((ubuntu::app_launch::Application::Info::Orientations {portrait: false, landscape: false, invertedPortrait: false, invertedLandscape: false}),
3360+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3361+
3362+ g_key_file_set_string(keyfile.get(), DESKTOP, "X-Ubuntu-Supported-Orientations", "foobar;primary;");
3363+ EXPECT_EQ(defaultOrientations,
3364+ ubuntu::app_launch::app_info::Desktop(keyfile, "/").supportedOrientations());
3365+}
3366
3367=== modified file 'tests/click-app-dir/application.desktop'
3368--- tests/click-app-dir/application.desktop 2013-09-24 03:39:27 +0000
3369+++ tests/click-app-dir/application.desktop 2016-03-04 11:34:19 +0000
3370@@ -1,4 +1,6 @@
3371 [Desktop Entry]
3372+Version=1.0
3373 Name=Application
3374 Type=Application
3375-Exec=foo
3376+Exec=grep
3377+Icon=foo
3378
3379=== modified file 'tests/exec-util-test.cc'
3380--- tests/exec-util-test.cc 2015-08-11 19:11:15 +0000
3381+++ tests/exec-util-test.cc 2016-03-04 11:34:19 +0000
3382@@ -172,7 +172,7 @@
3383 {"APP_DIR", [](const gchar * value) {
3384 EXPECT_STREQ(APP_DIR, value); }},
3385 {"APP_EXEC", [](const gchar * value) {
3386- EXPECT_STREQ("foo", value); }},
3387+ EXPECT_STREQ("grep", value); }},
3388 {"APP_ID", [](const gchar * value) {
3389 EXPECT_STREQ("com.test.good_application_1.2.3", value); }},
3390 {"APP_LAUNCHER_PID", [](const gchar * value) {
3391
3392=== modified file 'tests/helper-handshake-test.cc'
3393--- tests/helper-handshake-test.cc 2014-04-30 16:18:29 +0000
3394+++ tests/helper-handshake-test.cc 2016-03-04 11:34:19 +0000
3395@@ -103,6 +103,7 @@
3396 {
3397 bool * reached = static_cast<bool *>(user_data);
3398 *reached = true;
3399+ return true;
3400 }
3401
3402 TEST_F(HelperHandshakeTest, HandshakeTimeout)
3403@@ -114,6 +115,8 @@
3404
3405 starting_handshake_wait(handshake);
3406
3407+ g_source_remove(outertimeout);
3408+
3409 ASSERT_FALSE(timeout_reached);
3410
3411 return;
3412
3413=== modified file 'tests/helper-test.cc'
3414--- tests/helper-test.cc 2014-11-11 22:05:30 +0000
3415+++ tests/helper-test.cc 2016-03-04 11:34:19 +0000
3416@@ -297,8 +297,6 @@
3417 ASSERT_NE(calls, nullptr);
3418 ASSERT_STREQ("SetEnvList", calls[0].name);
3419
3420- unsigned int i;
3421-
3422 bool got_app_isolation = false;
3423 bool got_cache_home = false;
3424 bool got_config_home = false;
3425
3426=== modified file 'tests/libertine-data/libertine-container/container-name/rootfs/usr/share/applications/test.desktop'
3427--- tests/libertine-data/libertine-container/container-name/rootfs/usr/share/applications/test.desktop 2015-08-06 21:38:31 +0000
3428+++ tests/libertine-data/libertine-container/container-name/rootfs/usr/share/applications/test.desktop 2016-03-04 11:34:19 +0000
3429@@ -2,3 +2,4 @@
3430 Name=Test
3431 Type=Application
3432 Exec=test
3433+Icon=test
3434
3435=== added file 'tests/libual-cpp-test.cc'
3436--- tests/libual-cpp-test.cc 1970-01-01 00:00:00 +0000
3437+++ tests/libual-cpp-test.cc 2016-03-04 11:34:19 +0000
3438@@ -0,0 +1,1599 @@
3439+/*
3440+ * Copyright 2013 Canonical Ltd.
3441+ *
3442+ * This program is free software: you can redistribute it and/or modify it
3443+ * under the terms of the GNU General Public License version 3, as published
3444+ * by the Free Software Foundation.
3445+ *
3446+ * This program is distributed in the hope that it will be useful, but
3447+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3448+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3449+ * PURPOSE. See the GNU General Public License for more details.
3450+ *
3451+ * You should have received a copy of the GNU General Public License along
3452+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3453+ *
3454+ * Authors:
3455+ * Ted Gould <ted.gould@canonical.com>
3456+ */
3457+
3458+#include <future>
3459+#include <thread>
3460+
3461+#include <gtest/gtest.h>
3462+#include <gio/gio.h>
3463+#include <zeitgeist.h>
3464+#include "mir-mock.h"
3465+
3466+#include "registry.h"
3467+#include "application.h"
3468+#include "helper.h"
3469+
3470+extern "C" {
3471+#include "ubuntu-app-launch.h"
3472+#include "libdbustest/dbus-test.h"
3473+#include <fcntl.h>
3474+}
3475+
3476+class LibUAL : public ::testing::Test
3477+{
3478+protected:
3479+ DbusTestService* service = NULL;
3480+ DbusTestDbusMock* mock = NULL;
3481+ DbusTestDbusMock* cgmock = NULL;
3482+ GDBusConnection* bus = NULL;
3483+ std::string last_focus_appid;
3484+ std::string last_resume_appid;
3485+ guint resume_timeout = 0;
3486+ std::shared_ptr<ubuntu::app_launch::Registry> registry;
3487+
3488+private:
3489+ static void focus_cb(const gchar* appid, gpointer user_data)
3490+ {
3491+ g_debug("Focus Callback: %s", appid);
3492+ LibUAL* _this = static_cast<LibUAL*>(user_data);
3493+ _this->last_focus_appid = appid;
3494+ }
3495+
3496+ static void resume_cb(const gchar* appid, gpointer user_data)
3497+ {
3498+ g_debug("Resume Callback: %s", appid);
3499+ LibUAL* _this = static_cast<LibUAL*>(user_data);
3500+ _this->last_resume_appid = appid;
3501+
3502+ if (_this->resume_timeout > 0)
3503+ {
3504+ _this->pause(_this->resume_timeout);
3505+ }
3506+ }
3507+
3508+protected:
3509+ /* Useful debugging stuff, but not on by default. You really want to
3510+ not get all this noise typically */
3511+ void debugConnection()
3512+ {
3513+ if (true)
3514+ {
3515+ return;
3516+ }
3517+
3518+ DbusTestBustle* bustle = dbus_test_bustle_new("test.bustle");
3519+ dbus_test_service_add_task(service, DBUS_TEST_TASK(bustle));
3520+ g_object_unref(bustle);
3521+
3522+ DbusTestProcess* monitor = dbus_test_process_new("dbus-monitor");
3523+ dbus_test_service_add_task(service, DBUS_TEST_TASK(monitor));
3524+ g_object_unref(monitor);
3525+ }
3526+
3527+ virtual void SetUp()
3528+ {
3529+ /* Click DB test mode */
3530+ g_setenv("TEST_CLICK_DB", "click-db-dir", TRUE);
3531+ g_setenv("TEST_CLICK_USER", "test-user", TRUE);
3532+
3533+ gchar* linkfarmpath = g_build_filename(CMAKE_SOURCE_DIR, "link-farm", NULL);
3534+ g_setenv("UBUNTU_APP_LAUNCH_LINK_FARM", linkfarmpath, TRUE);
3535+ g_free(linkfarmpath);
3536+
3537+ g_setenv("XDG_DATA_DIRS", CMAKE_SOURCE_DIR, TRUE);
3538+ g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
3539+ g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
3540+
3541+ service = dbus_test_service_new(NULL);
3542+
3543+ debugConnection();
3544+
3545+ mock = dbus_test_dbus_mock_new("com.ubuntu.Upstart");
3546+
3547+ DbusTestDbusMockObject* obj =
3548+ dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", NULL);
3549+
3550+ dbus_test_dbus_mock_object_add_method(mock, obj, "EmitEvent", G_VARIANT_TYPE("(sasb)"), NULL, "", NULL);
3551+
3552+ dbus_test_dbus_mock_object_add_method(mock, obj, "GetJobByName", G_VARIANT_TYPE("s"), G_VARIANT_TYPE("o"),
3553+ "if args[0] == 'application-click':\n"
3554+ " ret = dbus.ObjectPath('/com/test/application_click')\n"
3555+ "elif args[0] == 'application-legacy':\n"
3556+ " ret = dbus.ObjectPath('/com/test/application_legacy')\n"
3557+ "elif args[0] == 'untrusted-helper':\n"
3558+ " ret = dbus.ObjectPath('/com/test/untrusted/helper')\n",
3559+ NULL);
3560+
3561+ dbus_test_dbus_mock_object_add_method(mock, obj, "SetEnv", G_VARIANT_TYPE("(assb)"), NULL, "", NULL);
3562+
3563+ /* Click App */
3564+ DbusTestDbusMockObject* jobobj =
3565+ dbus_test_dbus_mock_get_object(mock, "/com/test/application_click", "com.ubuntu.Upstart0_6.Job", NULL);
3566+
3567+ dbus_test_dbus_mock_object_add_method(
3568+ mock, jobobj, "Start", G_VARIANT_TYPE("(asb)"), NULL,
3569+ "if args[0][0] == 'APP_ID=com.test.good_application_1.2.3':"
3570+ " raise dbus.exceptions.DBusException('Foo running', name='com.ubuntu.Upstart0_6.Error.AlreadyStarted')",
3571+ NULL);
3572+
3573+ dbus_test_dbus_mock_object_add_method(mock, jobobj, "Stop", G_VARIANT_TYPE("(asb)"), NULL, "", NULL);
3574+
3575+ dbus_test_dbus_mock_object_add_method(mock, jobobj, "GetAllInstances", NULL, G_VARIANT_TYPE("ao"),
3576+ "ret = [ dbus.ObjectPath('/com/test/app_instance') ]", NULL);
3577+
3578+ DbusTestDbusMockObject* instobj =
3579+ dbus_test_dbus_mock_get_object(mock, "/com/test/app_instance", "com.ubuntu.Upstart0_6.Instance", NULL);
3580+ dbus_test_dbus_mock_object_add_property(mock, instobj, "name", G_VARIANT_TYPE_STRING,
3581+ g_variant_new_string("com.test.good_application_1.2.3"), NULL);
3582+ gchar* process_var = g_strdup_printf("[('main', %d)]", getpid());
3583+ dbus_test_dbus_mock_object_add_property(mock, instobj, "processes", G_VARIANT_TYPE("a(si)"),
3584+ g_variant_new_parsed(process_var), NULL);
3585+ g_free(process_var);
3586+
3587+ /* Legacy App */
3588+ DbusTestDbusMockObject* ljobobj =
3589+ dbus_test_dbus_mock_get_object(mock, "/com/test/application_legacy", "com.ubuntu.Upstart0_6.Job", NULL);
3590+
3591+ dbus_test_dbus_mock_object_add_method(mock, ljobobj, "Start", G_VARIANT_TYPE("(asb)"), NULL, "", NULL);
3592+
3593+ dbus_test_dbus_mock_object_add_method(mock, ljobobj, "Stop", G_VARIANT_TYPE("(asb)"), NULL, "", NULL);
3594+
3595+ dbus_test_dbus_mock_object_add_method(mock, ljobobj, "GetAllInstances", NULL, G_VARIANT_TYPE("ao"),
3596+ "ret = [ dbus.ObjectPath('/com/test/legacy_app_instance') ]", NULL);
3597+
3598+ DbusTestDbusMockObject* linstobj = dbus_test_dbus_mock_get_object(mock, "/com/test/legacy_app_instance",
3599+ "com.ubuntu.Upstart0_6.Instance", NULL);
3600+ dbus_test_dbus_mock_object_add_property(mock, linstobj, "name", G_VARIANT_TYPE_STRING,
3601+ g_variant_new_string("multiple-2342345"), NULL);
3602+ dbus_test_dbus_mock_object_add_property(mock, linstobj, "processes", G_VARIANT_TYPE("a(si)"),
3603+ g_variant_new_parsed("[('main', 5678)]"), NULL);
3604+
3605+ /* Untrusted Helper */
3606+ DbusTestDbusMockObject* uhelperobj =
3607+ dbus_test_dbus_mock_get_object(mock, "/com/test/untrusted/helper", "com.ubuntu.Upstart0_6.Job", NULL);
3608+
3609+ dbus_test_dbus_mock_object_add_method(mock, uhelperobj, "Start", G_VARIANT_TYPE("(asb)"), NULL, "", NULL);
3610+
3611+ dbus_test_dbus_mock_object_add_method(mock, uhelperobj, "Stop", G_VARIANT_TYPE("(asb)"), NULL, "", NULL);
3612+
3613+ dbus_test_dbus_mock_object_add_method(mock, uhelperobj, "GetAllInstances", NULL, G_VARIANT_TYPE("ao"),
3614+ "ret = [ dbus.ObjectPath('/com/test/untrusted/helper/instance'), "
3615+ "dbus.ObjectPath('/com/test/untrusted/helper/multi_instance') ]",
3616+ NULL);
3617+
3618+ DbusTestDbusMockObject* uhelperinstance = dbus_test_dbus_mock_get_object(
3619+ mock, "/com/test/untrusted/helper/instance", "com.ubuntu.Upstart0_6.Instance", NULL);
3620+ dbus_test_dbus_mock_object_add_property(mock, uhelperinstance, "name", G_VARIANT_TYPE_STRING,
3621+ g_variant_new_string("untrusted-type::com.foo_bar_43.23.12"), NULL);
3622+
3623+ DbusTestDbusMockObject* unhelpermulti = dbus_test_dbus_mock_get_object(
3624+ mock, "/com/test/untrusted/helper/multi_instance", "com.ubuntu.Upstart0_6.Instance", NULL);
3625+ dbus_test_dbus_mock_object_add_property(
3626+ mock, unhelpermulti, "name", G_VARIANT_TYPE_STRING,
3627+ g_variant_new_string("untrusted-type:24034582324132:com.bar_foo_8432.13.1"), NULL);
3628+
3629+ /* Create the cgroup manager mock */
3630+ cgmock = dbus_test_dbus_mock_new("org.test.cgmock");
3631+ g_setenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME", "org.test.cgmock", TRUE);
3632+
3633+ DbusTestDbusMockObject* cgobject = dbus_test_dbus_mock_get_object(cgmock, "/org/linuxcontainers/cgmanager",
3634+ "org.linuxcontainers.cgmanager0_0", NULL);
3635+ dbus_test_dbus_mock_object_add_method(cgmock, cgobject, "GetTasksRecursive", G_VARIANT_TYPE("(ss)"),
3636+ G_VARIANT_TYPE("ai"), "ret = [100, 200, 300]", NULL);
3637+
3638+ /* Put it together */
3639+ dbus_test_service_add_task(service, DBUS_TEST_TASK(mock));
3640+ dbus_test_service_add_task(service, DBUS_TEST_TASK(cgmock));
3641+ dbus_test_service_start_tasks(service);
3642+
3643+ bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
3644+ g_dbus_connection_set_exit_on_close(bus, FALSE);
3645+ g_object_add_weak_pointer(G_OBJECT(bus), (gpointer*)&bus);
3646+
3647+ /* Make sure we pretend the CG manager is just on our bus */
3648+ g_setenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS", "YES", TRUE);
3649+
3650+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_focus(focus_cb, this));
3651+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_resume(resume_cb, this));
3652+
3653+ registry = std::make_shared<ubuntu::app_launch::Registry>();
3654+ }
3655+
3656+ virtual void TearDown()
3657+ {
3658+ registry.reset();
3659+
3660+ ubuntu_app_launch_observer_delete_app_focus(focus_cb, this);
3661+ ubuntu_app_launch_observer_delete_app_resume(resume_cb, this);
3662+
3663+ g_clear_object(&mock);
3664+ g_clear_object(&cgmock);
3665+ g_clear_object(&service);
3666+
3667+ g_object_unref(bus);
3668+
3669+ unsigned int cleartry = 0;
3670+ while (bus != NULL && cleartry < 100)
3671+ {
3672+ pause(100);
3673+ cleartry++;
3674+ }
3675+ ASSERT_EQ(nullptr, bus);
3676+ }
3677+
3678+ GVariant* find_env(GVariant* env_array, const gchar* var)
3679+ {
3680+ unsigned int i;
3681+ GVariant* retval = nullptr;
3682+
3683+ for (i = 0; i < g_variant_n_children(env_array); i++)
3684+ {
3685+ GVariant* child = g_variant_get_child_value(env_array, i);
3686+ const gchar* envvar = g_variant_get_string(child, nullptr);
3687+
3688+ if (g_str_has_prefix(envvar, var))
3689+ {
3690+ if (retval != nullptr)
3691+ {
3692+ g_warning("Found the env var more than once!");
3693+ g_variant_unref(retval);
3694+ return nullptr;
3695+ }
3696+
3697+ retval = child;
3698+ }
3699+ else
3700+ {
3701+ g_variant_unref(child);
3702+ }
3703+ }
3704+
3705+ if (!retval)
3706+ {
3707+ gchar* envstr = g_variant_print(env_array, FALSE);
3708+ g_warning("Unable to find '%s' in '%s'", var, envstr);
3709+ g_free(envstr);
3710+ }
3711+
3712+ return retval;
3713+ }
3714+
3715+ bool check_env(GVariant* env_array, const gchar* var, const gchar* value)
3716+ {
3717+ bool found = false;
3718+ GVariant* val = find_env(env_array, var);
3719+ if (val == nullptr)
3720+ {
3721+ return false;
3722+ }
3723+
3724+ const gchar* envvar = g_variant_get_string(val, nullptr);
3725+
3726+ gchar* combined = g_strdup_printf("%s=%s", var, value);
3727+ if (g_strcmp0(envvar, combined) == 0)
3728+ {
3729+ found = true;
3730+ }
3731+
3732+ g_variant_unref(val);
3733+
3734+ return found;
3735+ }
3736+
3737+ void pause(guint time = 0)
3738+ {
3739+ if (time > 0)
3740+ {
3741+ GMainLoop* mainloop = g_main_loop_new(NULL, FALSE);
3742+
3743+ g_timeout_add(time,
3744+ [](gpointer pmainloop) -> gboolean
3745+ {
3746+ g_main_loop_quit(static_cast<GMainLoop*>(pmainloop));
3747+ return G_SOURCE_REMOVE;
3748+ },
3749+ mainloop);
3750+
3751+ g_main_loop_run(mainloop);
3752+
3753+ g_main_loop_unref(mainloop);
3754+ }
3755+
3756+ while (g_main_pending())
3757+ {
3758+ g_main_iteration(TRUE);
3759+ }
3760+ }
3761+};
3762+
3763+TEST_F(LibUAL, DISABLED_StartApplication)
3764+{
3765+ DbusTestDbusMockObject* obj =
3766+ dbus_test_dbus_mock_get_object(mock, "/com/test/application_click", "com.ubuntu.Upstart0_6.Job", NULL);
3767+
3768+ /* Basic make sure we can send the event */
3769+ auto appid = ubuntu::app_launch::AppID::parse("com.test.multiple_first_1.2.3");
3770+ auto app = ubuntu::app_launch::Application::create(appid, registry);
3771+ app->launch();
3772+
3773+ EXPECT_EQ(1, dbus_test_dbus_mock_object_check_method_call(mock, obj, "Start", NULL, NULL));
3774+
3775+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
3776+
3777+ /* Now look at the details of the call */
3778+ app->launch();
3779+
3780+ guint len = 0;
3781+ const DbusTestDbusMockCall* calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
3782+ EXPECT_NE(nullptr, calls);
3783+ EXPECT_EQ(1, len);
3784+
3785+ EXPECT_STREQ("Start", calls->name);
3786+ EXPECT_EQ(2, g_variant_n_children(calls->params));
3787+
3788+ GVariant* block = g_variant_get_child_value(calls->params, 1);
3789+ EXPECT_TRUE(g_variant_get_boolean(block));
3790+ g_variant_unref(block);
3791+
3792+ GVariant* env = g_variant_get_child_value(calls->params, 0);
3793+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.multiple_first_1.2.3"));
3794+ g_variant_unref(env);
3795+
3796+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
3797+
3798+ /* Let's pass some URLs */
3799+ std::vector<ubuntu::app_launch::Application::URL> urls{
3800+ ubuntu::app_launch::Application::URL::from_raw("http://ubuntu.com/"),
3801+ ubuntu::app_launch::Application::URL::from_raw("https://ubuntu.com/"),
3802+ ubuntu::app_launch::Application::URL::from_raw("file:///home/phablet/test.txt")};
3803+
3804+ app->launch(urls);
3805+
3806+ len = 0;
3807+ calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
3808+ EXPECT_NE(nullptr, calls);
3809+ EXPECT_EQ(1, len);
3810+
3811+ env = g_variant_get_child_value(calls->params, 0);
3812+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.multiple_first_1.2.3"));
3813+ EXPECT_TRUE(
3814+ check_env(env, "APP_URIS", "'http://ubuntu.com/' 'https://ubuntu.com/' 'file:///home/phablet/test.txt'"));
3815+ g_variant_unref(env);
3816+
3817+ return;
3818+}
3819+
3820+TEST_F(LibUAL, DISABLED_StartApplicationTest)
3821+{
3822+ DbusTestDbusMockObject* obj =
3823+ dbus_test_dbus_mock_get_object(mock, "/com/test/application_click", "com.ubuntu.Upstart0_6.Job", NULL);
3824+
3825+ /* Basic make sure we can send the event */
3826+ auto appid = ubuntu::app_launch::AppID::parse("com.test.multiple_first_1.2.3");
3827+ auto app = ubuntu::app_launch::Application::create(appid, registry);
3828+ app->launchTest();
3829+
3830+ guint len = 0;
3831+ const DbusTestDbusMockCall* calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
3832+ EXPECT_NE(nullptr, calls);
3833+ EXPECT_EQ(1, len);
3834+
3835+ EXPECT_STREQ("Start", calls->name);
3836+ EXPECT_EQ(2, g_variant_n_children(calls->params));
3837+
3838+ GVariant* block = g_variant_get_child_value(calls->params, 1);
3839+ EXPECT_TRUE(g_variant_get_boolean(block));
3840+ g_variant_unref(block);
3841+
3842+ GVariant* env = g_variant_get_child_value(calls->params, 0);
3843+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.multiple_first_1.2.3"));
3844+ EXPECT_TRUE(check_env(env, "QT_LOAD_TESTABILITY", "1"));
3845+ g_variant_unref(env);
3846+}
3847+
3848+TEST_F(LibUAL, DISABLED_StopApplication)
3849+{
3850+ DbusTestDbusMockObject* obj =
3851+ dbus_test_dbus_mock_get_object(mock, "/com/test/application_click", "com.ubuntu.Upstart0_6.Job", NULL);
3852+
3853+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
3854+ auto app = ubuntu::app_launch::Application::create(appid, registry);
3855+
3856+ ASSERT_TRUE(app->hasInstances());
3857+ EXPECT_EQ(1, app->instances().size());
3858+
3859+ app->instances()[0]->stop();
3860+
3861+ ASSERT_EQ(dbus_test_dbus_mock_object_check_method_call(mock, obj, "Stop", NULL, NULL), 1);
3862+}
3863+
3864+/* NOTE: The fact that there is 'libertine-data' in these strings is because
3865+ we're using one CACHE_HOME for this test suite and the libertine functions
3866+ need to pull things from there, where these are only comparisons. It's just
3867+ what value is in the environment variable */
3868+TEST_F(LibUAL, DISABLED_ApplicationLog)
3869+{
3870+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
3871+ auto app = ubuntu::app_launch::Application::create(appid, registry);
3872+
3873+ EXPECT_EQ(
3874+ std::string(CMAKE_SOURCE_DIR "/libertine-data/upstart/application-click-com.test.good_application_1.2.3.log"),
3875+ app->instances()[0]->logPath());
3876+
3877+ appid = ubuntu::app_launch::AppID::find("single");
3878+ app = ubuntu::app_launch::Application::create(appid, registry);
3879+
3880+ EXPECT_EQ(std::string(CMAKE_SOURCE_DIR "/libertine-data/upstart/application-legacy-single-.log"),
3881+ app->instances()[0]->logPath());
3882+
3883+ appid = ubuntu::app_launch::AppID::find("multiple");
3884+ app = ubuntu::app_launch::Application::create(appid, registry);
3885+
3886+ EXPECT_EQ(std::string(CMAKE_SOURCE_DIR "/libertine-data/upstart/application-legacy-multiple-2342345.log"),
3887+ app->instances()[0]->logPath());
3888+}
3889+
3890+TEST_F(LibUAL, DISABLED_ApplicationPid)
3891+{
3892+ /* Check bad params */
3893+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
3894+ auto app = ubuntu::app_launch::Application::create(appid, registry);
3895+
3896+ EXPECT_FALSE(app->instances()[0]->hasPid(0));
3897+
3898+ /* Check primary pid, which comes from Upstart */
3899+ EXPECT_EQ(getpid(), app->instances()[0]->primaryPid());
3900+
3901+ auto multiappid = ubuntu::app_launch::AppID::find("multiple");
3902+ auto multiapp = ubuntu::app_launch::Application::create(multiappid, registry);
3903+ EXPECT_EQ(5678, multiapp->instances()[0]->primaryPid());
3904+
3905+ /* Look at the full PID list from CG Manager */
3906+ DbusTestDbusMockObject* cgobject = dbus_test_dbus_mock_get_object(cgmock, "/org/linuxcontainers/cgmanager",
3907+ "org.linuxcontainers.cgmanager0_0", NULL);
3908+ const DbusTestDbusMockCall* calls = NULL;
3909+ guint len = 0;
3910+
3911+ /* Click in the set */
3912+ EXPECT_TRUE(app->instances()[0]->hasPid(100));
3913+ calls = dbus_test_dbus_mock_object_get_method_calls(cgmock, cgobject, "GetTasksRecursive", &len, NULL);
3914+ EXPECT_EQ(1, len);
3915+ EXPECT_STREQ("GetTasksRecursive", calls->name);
3916+ EXPECT_TRUE(g_variant_equal(
3917+ calls->params, g_variant_new("(ss)", "freezer", "upstart/application-click-com.test.good_application_1.2.3")));
3918+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(cgmock, cgobject, NULL));
3919+
3920+ /* Click out of the set */
3921+ EXPECT_FALSE(app->instances()[0]->hasPid(101));
3922+ calls = dbus_test_dbus_mock_object_get_method_calls(cgmock, cgobject, "GetTasksRecursive", &len, NULL);
3923+ EXPECT_EQ(1, len);
3924+ EXPECT_STREQ("GetTasksRecursive", calls->name);
3925+ EXPECT_TRUE(g_variant_equal(
3926+ calls->params, g_variant_new("(ss)", "freezer", "upstart/application-click-com.test.good_application_1.2.3")));
3927+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(cgmock, cgobject, NULL));
3928+
3929+ /* Legacy Single Instance */
3930+ auto singleappid = ubuntu::app_launch::AppID::find("single");
3931+ auto singleapp = ubuntu::app_launch::Application::create(singleappid, registry);
3932+
3933+ EXPECT_TRUE(singleapp->instances()[0]->hasPid(100));
3934+
3935+ calls = dbus_test_dbus_mock_object_get_method_calls(cgmock, cgobject, "GetTasksRecursive", &len, NULL);
3936+ EXPECT_EQ(1, len);
3937+ EXPECT_STREQ("GetTasksRecursive", calls->name);
3938+ EXPECT_TRUE(g_variant_equal(calls->params, g_variant_new("(ss)", "freezer", "upstart/application-legacy-single-")));
3939+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(cgmock, cgobject, NULL));
3940+
3941+ /* Legacy Multi Instance */
3942+ EXPECT_TRUE(multiapp->instances()[0]->hasPid(100));
3943+ calls = dbus_test_dbus_mock_object_get_method_calls(cgmock, cgobject, "GetTasksRecursive", &len, NULL);
3944+ EXPECT_EQ(1, len);
3945+ EXPECT_STREQ("GetTasksRecursive", calls->name);
3946+ EXPECT_TRUE(g_variant_equal(calls->params,
3947+ g_variant_new("(ss)", "freezer", "upstart/application-legacy-multiple-2342345")));
3948+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(cgmock, cgobject, NULL));
3949+}
3950+
3951+TEST_F(LibUAL, DISABLED_ApplicationId)
3952+{
3953+ g_setenv("TEST_CLICK_DB", "click-db-dir", TRUE);
3954+ g_setenv("TEST_CLICK_USER", "test-user", TRUE);
3955+
3956+ /* Test with current-user-version, should return the version in the manifest */
3957+ EXPECT_EQ("com.test.good_application_1.2.3",
3958+ (std::string)ubuntu::app_launch::AppID::discover("com.test.good", "application"));
3959+
3960+ /* Test with version specified, shouldn't even read the manifest */
3961+ EXPECT_EQ("com.test.good_application_1.2.4",
3962+ (std::string)ubuntu::app_launch::AppID::discover("com.test.good", "application", "1.2.4"));
3963+
3964+ /* Test with out a version or app, should return the version in the manifest */
3965+ EXPECT_EQ("com.test.good_application_1.2.3", (std::string)ubuntu::app_launch::AppID::discover(
3966+ "com.test.good", "first-listed-app", "current-user-version"));
3967+
3968+ /* Make sure we can select the app from a list correctly */
3969+ EXPECT_EQ("com.test.multiple_first_1.2.3",
3970+ (std::string)ubuntu::app_launch::AppID::discover(
3971+ "com.test.multiple", ubuntu::app_launch::AppID::ApplicationWildcard::FIRST_LISTED));
3972+ EXPECT_EQ("com.test.multiple_first_1.2.3", (std::string)ubuntu::app_launch::AppID::discover("com.test.multiple"));
3973+ EXPECT_EQ("com.test.multiple_fifth_1.2.3",
3974+ (std::string)ubuntu::app_launch::AppID::discover(
3975+ "com.test.multiple", ubuntu::app_launch::AppID::ApplicationWildcard::LAST_LISTED));
3976+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover(
3977+ "com.test.multiple", ubuntu::app_launch::AppID::ApplicationWildcard::ONLY_LISTED));
3978+ EXPECT_EQ("com.test.good_application_1.2.3",
3979+ (std::string)ubuntu::app_launch::AppID::discover(
3980+ "com.test.good", ubuntu::app_launch::AppID::ApplicationWildcard::ONLY_LISTED));
3981+
3982+ /* A bunch that should be NULL */
3983+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover("com.test.no-hooks"));
3984+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover("com.test.no-json"));
3985+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover("com.test.no-object"));
3986+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover("com.test.no-version"));
3987+
3988+ /* Libertine tests */
3989+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover("container-name"));
3990+ EXPECT_EQ("", (std::string)ubuntu::app_launch::AppID::discover("container-name", "not-exist"));
3991+ EXPECT_EQ("container-name_test_0.0", (std::string)ubuntu::app_launch::AppID::discover("container-name", "test"));
3992+ EXPECT_EQ("container-name_user-app_0.0",
3993+ (std::string)ubuntu::app_launch::AppID::discover("container-name", "user-app"));
3994+}
3995+
3996+TEST_F(LibUAL, AppIdParse)
3997+{
3998+ EXPECT_FALSE(ubuntu::app_launch::AppID::parse("com.ubuntu.test_test_123").empty());
3999+ EXPECT_FALSE(ubuntu::app_launch::AppID::find("inkscape").empty());
4000+
4001+ auto id = ubuntu::app_launch::AppID::parse("com.ubuntu.test_test_123");
4002+
4003+ ASSERT_FALSE(id.empty());
4004+ EXPECT_EQ("com.ubuntu.test", id.package.value());
4005+ EXPECT_EQ("test", id.appname.value());
4006+ EXPECT_EQ("123", id.version.value());
4007+
4008+ return;
4009+}
4010+
4011+TEST_F(LibUAL, DISABLED_ApplicationList)
4012+{
4013+ auto apps = ubuntu::app_launch::Registry::runningApps(registry);
4014+
4015+ ASSERT_EQ(2, apps.size());
4016+
4017+ apps.sort([](const std::shared_ptr<ubuntu::app_launch::Application>& a,
4018+ const std::shared_ptr<ubuntu::app_launch::Application>& b)
4019+ {
4020+ std::string sa = a->appId();
4021+ std::string sb = b->appId();
4022+
4023+ return sa < sb;
4024+ });
4025+
4026+ EXPECT_EQ("com.test.good_application_1.2.3", (std::string)apps.front()->appId());
4027+ EXPECT_EQ("multiple", (std::string)apps.back()->appId());
4028+}
4029+
4030+typedef struct
4031+{
4032+ unsigned int count;
4033+ const gchar* name;
4034+} observer_data_t;
4035+
4036+static void observer_cb(const gchar* appid, gpointer user_data)
4037+{
4038+ observer_data_t* data = (observer_data_t*)user_data;
4039+
4040+ if (data->name == NULL)
4041+ {
4042+ data->count++;
4043+ }
4044+ else if (g_strcmp0(data->name, appid) == 0)
4045+ {
4046+ data->count++;
4047+ }
4048+}
4049+
4050+TEST_F(LibUAL, StartStopObserver)
4051+{
4052+ observer_data_t start_data = {.count = 0, .name = nullptr};
4053+ observer_data_t stop_data = {.count = 0, .name = nullptr};
4054+
4055+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_started(observer_cb, &start_data));
4056+ ASSERT_TRUE(ubuntu_app_launch_observer_add_app_stop(observer_cb, &stop_data));
4057+
4058+ DbusTestDbusMockObject* obj =
4059+ dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", NULL);
4060+
4061+ /* Basic start */
4062+ dbus_test_dbus_mock_object_emit_signal(
4063+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4064+ g_variant_new_parsed("('started', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
4065+ NULL);
4066+
4067+ g_usleep(100000);
4068+ while (g_main_pending())
4069+ {
4070+ g_main_iteration(TRUE);
4071+ }
4072+
4073+ ASSERT_EQ(start_data.count, 1);
4074+
4075+ /* Basic stop */
4076+ dbus_test_dbus_mock_object_emit_signal(
4077+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4078+ g_variant_new_parsed("('stopped', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
4079+ NULL);
4080+
4081+ g_usleep(100000);
4082+ while (g_main_pending())
4083+ {
4084+ g_main_iteration(TRUE);
4085+ }
4086+
4087+ ASSERT_EQ(stop_data.count, 1);
4088+
4089+ /* Start legacy */
4090+ start_data.count = 0;
4091+ start_data.name = "multiple";
4092+
4093+ dbus_test_dbus_mock_object_emit_signal(
4094+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4095+ g_variant_new_parsed("('started', ['JOB=application-legacy', 'INSTANCE=multiple-234235'])"), NULL);
4096+
4097+ g_usleep(100000);
4098+ while (g_main_pending())
4099+ {
4100+ g_main_iteration(TRUE);
4101+ }
4102+
4103+ ASSERT_EQ(start_data.count, 1);
4104+
4105+ /* Legacy stop */
4106+ stop_data.count = 0;
4107+ stop_data.name = "bar";
4108+
4109+ dbus_test_dbus_mock_object_emit_signal(
4110+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4111+ g_variant_new_parsed("('stopped', ['JOB=application-legacy', 'INSTANCE=bar-9344321'])"), NULL);
4112+
4113+ g_usleep(100000);
4114+ while (g_main_pending())
4115+ {
4116+ g_main_iteration(TRUE);
4117+ }
4118+
4119+ ASSERT_EQ(stop_data.count, 1);
4120+
4121+ /* Test Noise Start */
4122+ start_data.count = 0;
4123+ start_data.name = "com.test.good_application_1.2.3";
4124+ stop_data.count = 0;
4125+ stop_data.name = "com.test.good_application_1.2.3";
4126+
4127+ /* A full lifecycle */
4128+ dbus_test_dbus_mock_object_emit_signal(
4129+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4130+ g_variant_new_parsed("('starting', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
4131+ NULL);
4132+ dbus_test_dbus_mock_object_emit_signal(
4133+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4134+ g_variant_new_parsed("('started', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
4135+ NULL);
4136+ dbus_test_dbus_mock_object_emit_signal(
4137+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4138+ g_variant_new_parsed("('stopping', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
4139+ NULL);
4140+ dbus_test_dbus_mock_object_emit_signal(
4141+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4142+ g_variant_new_parsed("('stopped', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
4143+ NULL);
4144+
4145+ g_usleep(100000);
4146+ while (g_main_pending())
4147+ {
4148+ g_main_iteration(TRUE);
4149+ }
4150+
4151+ /* Ensure we just signaled once for each */
4152+ ASSERT_EQ(start_data.count, 1);
4153+ ASSERT_EQ(stop_data.count, 1);
4154+
4155+ /* Remove */
4156+ ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_started(observer_cb, &start_data));
4157+ ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_stop(observer_cb, &stop_data));
4158+}
4159+
4160+static GDBusMessage* filter_starting(GDBusConnection* conn,
4161+ GDBusMessage* message,
4162+ gboolean incomming,
4163+ gpointer user_data)
4164+{
4165+ if (g_strcmp0(g_dbus_message_get_member(message), "UnityStartingSignal") == 0)
4166+ {
4167+ unsigned int* count = static_cast<unsigned int*>(user_data);
4168+ (*count)++;
4169+ g_object_unref(message);
4170+ return NULL;
4171+ }
4172+
4173+ return message;
4174+}
4175+
4176+static void starting_observer(const gchar* appid, gpointer user_data)
4177+{
4178+ std::string* last = static_cast<std::string*>(user_data);
4179+ *last = appid;
4180+ return;
4181+}
4182+
4183+TEST_F(LibUAL, StartingResponses)
4184+{
4185+ std::string last_observer;
4186+ unsigned int starting_count = 0;
4187+ GDBusConnection* session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
4188+ guint filter = g_dbus_connection_add_filter(session, filter_starting, &starting_count, NULL);
4189+
4190+ EXPECT_TRUE(ubuntu_app_launch_observer_add_app_starting(starting_observer, &last_observer));
4191+
4192+ g_dbus_connection_emit_signal(session, NULL, /* destination */
4193+ "/", /* path */
4194+ "com.canonical.UbuntuAppLaunch", /* interface */
4195+ "UnityStartingBroadcast", /* signal */
4196+ g_variant_new("(s)", "com.test.good_application_1.2.3"), /* params, the same */
4197+ NULL);
4198+
4199+ pause(100);
4200+
4201+ EXPECT_EQ("com.test.good_application_1.2.3", last_observer);
4202+ EXPECT_EQ(1, starting_count);
4203+
4204+ EXPECT_TRUE(ubuntu_app_launch_observer_delete_app_starting(starting_observer, &last_observer));
4205+
4206+ g_dbus_connection_remove_filter(session, filter);
4207+ g_object_unref(session);
4208+}
4209+
4210+TEST_F(LibUAL, AppIdTest)
4211+{
4212+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
4213+ auto app = ubuntu::app_launch::Application::create(appid, registry);
4214+ app->launch();
4215+
4216+ pause(50); /* Ensure all the events come through */
4217+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
4218+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
4219+}
4220+
4221+GDBusMessage* filter_func_good(GDBusConnection* conn, GDBusMessage* message, gboolean incomming, gpointer user_data)
4222+{
4223+ if (!incomming)
4224+ {
4225+ return message;
4226+ }
4227+
4228+ if (g_strcmp0(g_dbus_message_get_path(message), (gchar*)user_data) == 0)
4229+ {
4230+ GDBusMessage* reply = g_dbus_message_new_method_reply(message);
4231+ g_dbus_connection_send_message(conn, reply, G_DBUS_SEND_MESSAGE_FLAGS_NONE, NULL, NULL);
4232+ g_object_unref(message);
4233+ return NULL;
4234+ }
4235+
4236+ return message;
4237+}
4238+
4239+TEST_F(LibUAL, UrlSendTest)
4240+{
4241+ GDBusConnection* session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
4242+ guint filter = g_dbus_connection_add_filter(session, filter_func_good,
4243+ (gpointer) "/com_2etest_2egood_5fapplication_5f1_2e2_2e3", NULL);
4244+
4245+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
4246+ auto app = ubuntu::app_launch::Application::create(appid, registry);
4247+ std::vector<ubuntu::app_launch::Application::URL> uris = {
4248+ ubuntu::app_launch::Application::URL::from_raw("http://www.test.com")};
4249+
4250+ app->launch(uris);
4251+
4252+ pause(100); /* Ensure all the events come through */
4253+
4254+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
4255+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
4256+
4257+ g_dbus_connection_remove_filter(session, filter);
4258+
4259+ /* Send multiple resume responses to ensure we unsubscribe */
4260+ /* Multiple to increase our chance of hitting a bad free in the middle,
4261+ fun with async! */
4262+ int i;
4263+ for (i = 0; i < 5; i++)
4264+ {
4265+ g_dbus_connection_emit_signal(session, NULL, /* destination */
4266+ "/", /* path */
4267+ "com.canonical.UbuntuAppLaunch", /* interface */
4268+ "UnityResumeResponse", /* signal */
4269+ g_variant_new("(s)", "com.test.good_application_1.2.3"), /* params, the same */
4270+ NULL);
4271+
4272+ pause(50); /* Ensure all the events come through */
4273+ }
4274+
4275+ g_object_unref(session);
4276+}
4277+
4278+TEST_F(LibUAL, UrlSendNoObjectTest)
4279+{
4280+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
4281+ auto app = ubuntu::app_launch::Application::create(appid, registry);
4282+ std::vector<ubuntu::app_launch::Application::URL> uris = {
4283+ ubuntu::app_launch::Application::URL::from_raw("http://www.test.com")};
4284+
4285+ app->launch(uris);
4286+
4287+ pause(100); /* Ensure all the events come through */
4288+
4289+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
4290+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
4291+}
4292+
4293+TEST_F(LibUAL, UnityTimeoutTest)
4294+{
4295+ this->resume_timeout = 100;
4296+
4297+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
4298+ auto app = ubuntu::app_launch::Application::create(appid, registry);
4299+
4300+ app->launch();
4301+
4302+ pause(1000); /* Ensure all the events come through */
4303+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
4304+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
4305+}
4306+
4307+TEST_F(LibUAL, UnityTimeoutUriTest)
4308+{
4309+ this->resume_timeout = 200;
4310+
4311+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
4312+ auto app = ubuntu::app_launch::Application::create(appid, registry);
4313+ std::vector<ubuntu::app_launch::Application::URL> uris = {
4314+ ubuntu::app_launch::Application::URL::from_raw("http://www.test.com")};
4315+
4316+ app->launch(uris);
4317+
4318+ pause(1000); /* Ensure all the events come through */
4319+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
4320+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
4321+}
4322+
4323+GDBusMessage* filter_respawn(GDBusConnection* conn, GDBusMessage* message, gboolean incomming, gpointer user_data)
4324+{
4325+ if (g_strcmp0(g_dbus_message_get_member(message), "UnityResumeResponse") == 0)
4326+ {
4327+ g_object_unref(message);
4328+ return NULL;
4329+ }
4330+
4331+ return message;
4332+}
4333+
4334+TEST_F(LibUAL, UnityLostTest)
4335+{
4336+ GDBusConnection* session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
4337+ guint filter = g_dbus_connection_add_filter(session, filter_respawn, NULL, NULL);
4338+
4339+ guint start = g_get_monotonic_time();
4340+
4341+ auto appid = ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3");
4342+ auto app = ubuntu::app_launch::Application::create(appid, registry);
4343+ std::vector<ubuntu::app_launch::Application::URL> uris = {
4344+ ubuntu::app_launch::Application::URL::from_raw("http://www.test.com")};
4345+
4346+ app->launch(uris);
4347+
4348+ guint end = g_get_monotonic_time();
4349+
4350+ g_debug("Start call time: %d ms", (end - start) / 1000);
4351+ EXPECT_LT(end - start, 2000 * 1000);
4352+
4353+ pause(1000); /* Ensure all the events come through */
4354+
4355+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
4356+ EXPECT_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
4357+
4358+ g_dbus_connection_remove_filter(session, filter);
4359+ g_object_unref(session);
4360+}
4361+
4362+TEST_F(LibUAL, LegacySingleInstance)
4363+{
4364+ DbusTestDbusMockObject* obj =
4365+ dbus_test_dbus_mock_get_object(mock, "/com/test/application_legacy", "com.ubuntu.Upstart0_6.Job", NULL);
4366+
4367+ /* Check for a single-instance app */
4368+ auto singleappid = ubuntu::app_launch::AppID::find("single");
4369+ auto singleapp = ubuntu::app_launch::Application::create(singleappid, registry);
4370+
4371+ singleapp->launch();
4372+
4373+ guint len = 0;
4374+ const DbusTestDbusMockCall* calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
4375+ EXPECT_NE(nullptr, calls);
4376+ EXPECT_EQ(1, len);
4377+
4378+ EXPECT_STREQ("Start", calls->name);
4379+ EXPECT_EQ(2, g_variant_n_children(calls->params));
4380+
4381+ GVariant* block = g_variant_get_child_value(calls->params, 1);
4382+ EXPECT_TRUE(g_variant_get_boolean(block));
4383+ g_variant_unref(block);
4384+
4385+ GVariant* env = g_variant_get_child_value(calls->params, 0);
4386+ EXPECT_TRUE(check_env(env, "APP_ID", "single"));
4387+ EXPECT_TRUE(check_env(env, "INSTANCE_ID", ""));
4388+ g_variant_unref(env);
4389+
4390+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
4391+
4392+ /* Check for a multi-instance app */
4393+ auto multipleappid = ubuntu::app_launch::AppID::find("multiple");
4394+ auto multipleapp = ubuntu::app_launch::Application::create(multipleappid, registry);
4395+
4396+ multipleapp->launch();
4397+
4398+ len = 0;
4399+ calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
4400+ EXPECT_NE(nullptr, calls);
4401+ EXPECT_EQ(1, len);
4402+
4403+ EXPECT_STREQ("Start", calls->name);
4404+ EXPECT_EQ(2, g_variant_n_children(calls->params));
4405+
4406+ block = g_variant_get_child_value(calls->params, 1);
4407+ EXPECT_TRUE(g_variant_get_boolean(block));
4408+ g_variant_unref(block);
4409+
4410+ env = g_variant_get_child_value(calls->params, 0);
4411+ EXPECT_TRUE(check_env(env, "APP_ID", "multiple"));
4412+ EXPECT_FALSE(check_env(env, "INSTANCE_ID", ""));
4413+ g_variant_unref(env);
4414+}
4415+
4416+static void failed_observer(const gchar* appid, UbuntuAppLaunchAppFailed reason, gpointer user_data)
4417+{
4418+ if (reason == UBUNTU_APP_LAUNCH_APP_FAILED_CRASH)
4419+ {
4420+ std::string* last = static_cast<std::string*>(user_data);
4421+ *last = appid;
4422+ }
4423+ return;
4424+}
4425+
4426+TEST_F(LibUAL, FailingObserver)
4427+{
4428+ std::string last_observer;
4429+ GDBusConnection* session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
4430+
4431+ EXPECT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
4432+
4433+ g_dbus_connection_emit_signal(
4434+ session, NULL, /* destination */
4435+ "/", /* path */
4436+ "com.canonical.UbuntuAppLaunch", /* interface */
4437+ "ApplicationFailed", /* signal */
4438+ g_variant_new("(ss)", "com.test.good_application_1.2.3", "crash"), /* params, the same */
4439+ NULL);
4440+
4441+ pause(100);
4442+
4443+ EXPECT_EQ("com.test.good_application_1.2.3", last_observer);
4444+
4445+ last_observer.clear();
4446+
4447+ g_dbus_connection_emit_signal(
4448+ session, NULL, /* destination */
4449+ "/", /* path */
4450+ "com.canonical.UbuntuAppLaunch", /* interface */
4451+ "ApplicationFailed", /* signal */
4452+ g_variant_new("(ss)", "com.test.good_application_1.2.3", "blahblah"), /* params, the same */
4453+ NULL);
4454+
4455+ pause(100);
4456+
4457+ EXPECT_EQ("com.test.good_application_1.2.3", last_observer);
4458+
4459+ last_observer.clear();
4460+
4461+ g_dbus_connection_emit_signal(
4462+ session, NULL, /* destination */
4463+ "/", /* path */
4464+ "com.canonical.UbuntuAppLaunch", /* interface */
4465+ "ApplicationFailed", /* signal */
4466+ g_variant_new("(ss)", "com.test.good_application_1.2.3", "start-failure"), /* params, the same */
4467+ NULL);
4468+
4469+ pause(100);
4470+
4471+ EXPECT_TRUE(last_observer.empty());
4472+
4473+ EXPECT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
4474+
4475+ g_object_unref(session);
4476+}
4477+
4478+TEST_F(LibUAL, StartHelper)
4479+{
4480+ DbusTestDbusMockObject* obj =
4481+ dbus_test_dbus_mock_get_object(mock, "/com/test/untrusted/helper", "com.ubuntu.Upstart0_6.Job", NULL);
4482+
4483+ auto untrusted = ubuntu::app_launch::Helper::Type::from_raw("untrusted-type");
4484+
4485+ /* Basic make sure we can send the event */
4486+ auto appid = ubuntu::app_launch::AppID::parse("com.test.multiple_first_1.2.3");
4487+ auto helper = ubuntu::app_launch::Helper::create(untrusted, appid, registry);
4488+
4489+ helper->launch();
4490+
4491+ EXPECT_EQ(1, dbus_test_dbus_mock_object_check_method_call(mock, obj, "Start", NULL, NULL));
4492+
4493+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
4494+
4495+ /* Now check a multi out */
4496+ helper->launch();
4497+
4498+ guint len = 0;
4499+ auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
4500+ EXPECT_NE(nullptr, calls);
4501+ EXPECT_EQ(1, len);
4502+
4503+ EXPECT_STREQ("Start", calls->name);
4504+ EXPECT_EQ(2, g_variant_n_children(calls->params));
4505+
4506+ auto block = g_variant_get_child_value(calls->params, 1);
4507+ EXPECT_TRUE(g_variant_get_boolean(block));
4508+ g_variant_unref(block);
4509+
4510+ auto env = g_variant_get_child_value(calls->params, 0);
4511+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.multiple_first_1.2.3"));
4512+ EXPECT_TRUE(check_env(env, "HELPER_TYPE", "untrusted-type"));
4513+ g_variant_unref(env);
4514+
4515+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
4516+
4517+ /* Let's pass some URLs */
4518+ std::vector<ubuntu::app_launch::Helper::URL> urls = {
4519+ ubuntu::app_launch::Helper::URL::from_raw("http://ubuntu.com/"),
4520+ ubuntu::app_launch::Helper::URL::from_raw("https://ubuntu.com/"),
4521+ ubuntu::app_launch::Helper::URL::from_raw("file:///home/phablet/test.txt")};
4522+ helper->launch(urls);
4523+
4524+ len = 0;
4525+ calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
4526+ EXPECT_NE(nullptr, calls);
4527+ EXPECT_EQ(1, len);
4528+
4529+ env = g_variant_get_child_value(calls->params, 0);
4530+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.multiple_first_1.2.3"));
4531+ EXPECT_TRUE(
4532+ check_env(env, "APP_URIS", "'http://ubuntu.com/' 'https://ubuntu.com/' 'file:///home/phablet/test.txt'"));
4533+ EXPECT_TRUE(check_env(env, "HELPER_TYPE", "untrusted-type"));
4534+ EXPECT_FALSE(check_env(env, "INSTANCE_ID", NULL));
4535+ g_variant_unref(env);
4536+
4537+ return;
4538+}
4539+
4540+TEST_F(LibUAL, StopHelper)
4541+{
4542+ DbusTestDbusMockObject* obj =
4543+ dbus_test_dbus_mock_get_object(mock, "/com/test/untrusted/helper", "com.ubuntu.Upstart0_6.Job", NULL);
4544+
4545+ /* Multi helper */
4546+ auto untrusted = ubuntu::app_launch::Helper::Type::from_raw("untrusted-type");
4547+
4548+ auto appid = ubuntu::app_launch::AppID::parse("com.bar_foo_8432.13.1");
4549+ auto helper = ubuntu::app_launch::Helper::create(untrusted, appid, registry);
4550+
4551+ ASSERT_TRUE(helper->hasInstances());
4552+
4553+ auto instances = helper->instances();
4554+
4555+ EXPECT_EQ(1, instances.size());
4556+
4557+ instances[0]->stop();
4558+
4559+ ASSERT_EQ(dbus_test_dbus_mock_object_check_method_call(mock, obj, "Stop", NULL, NULL), 1);
4560+
4561+ guint len = 0;
4562+ auto calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Stop", &len, NULL);
4563+ EXPECT_NE(nullptr, calls);
4564+ EXPECT_EQ(1, len);
4565+
4566+ EXPECT_STREQ("Stop", calls->name);
4567+ EXPECT_EQ(2, g_variant_n_children(calls->params));
4568+
4569+ auto block = g_variant_get_child_value(calls->params, 1);
4570+ EXPECT_TRUE(g_variant_get_boolean(block));
4571+ g_variant_unref(block);
4572+
4573+ auto env = g_variant_get_child_value(calls->params, 0);
4574+ EXPECT_TRUE(check_env(env, "APP_ID", "com.bar_foo_8432.13.1"));
4575+ EXPECT_TRUE(check_env(env, "HELPER_TYPE", "untrusted-type"));
4576+ EXPECT_TRUE(check_env(env, "INSTANCE_ID", "24034582324132"));
4577+ g_variant_unref(env);
4578+
4579+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
4580+
4581+ return;
4582+}
4583+
4584+TEST_F(LibUAL, HelperList)
4585+{
4586+ auto nothelper = ubuntu::app_launch::Helper::Type::from_raw("not-a-type");
4587+ auto notlist = ubuntu::app_launch::Registry::runningHelpers(nothelper, registry);
4588+
4589+ EXPECT_EQ(0, notlist.size());
4590+
4591+ auto goodhelper = ubuntu::app_launch::Helper::Type::from_raw("untrusted-type");
4592+ auto goodlist = ubuntu::app_launch::Registry::runningHelpers(goodhelper, registry);
4593+
4594+ EXPECT_EQ(2, goodlist.size());
4595+
4596+ goodlist.sort(
4597+ [](const std::shared_ptr<ubuntu::app_launch::Helper>& a, const std::shared_ptr<ubuntu::app_launch::Helper>& b)
4598+ {
4599+ std::string sa = a->appId();
4600+ std::string sb = b->appId();
4601+
4602+ return sa < sb;
4603+ });
4604+
4605+ EXPECT_EQ("com.bar_foo_8432.13.1", (std::string)goodlist.front()->appId());
4606+ EXPECT_EQ("com.foo_bar_43.23.12", (std::string)goodlist.back()->appId());
4607+
4608+ EXPECT_TRUE(goodlist.front()->hasInstances());
4609+ EXPECT_TRUE(goodlist.back()->hasInstances());
4610+
4611+ EXPECT_EQ(1, goodlist.front()->instances().size());
4612+ EXPECT_EQ(1, goodlist.back()->instances().size());
4613+
4614+ EXPECT_TRUE(goodlist.front()->instances()[0]->isRunning());
4615+ EXPECT_TRUE(goodlist.back()->instances()[0]->isRunning());
4616+}
4617+
4618+typedef struct
4619+{
4620+ unsigned int count;
4621+ const gchar* appid;
4622+ const gchar* type;
4623+ const gchar* instance;
4624+} helper_observer_data_t;
4625+
4626+static void helper_observer_cb(const gchar* appid, const gchar* instance, const gchar* type, gpointer user_data)
4627+{
4628+ helper_observer_data_t* data = (helper_observer_data_t*)user_data;
4629+
4630+ if (g_strcmp0(data->appid, appid) == 0 && g_strcmp0(data->type, type) == 0 &&
4631+ g_strcmp0(data->instance, instance) == 0)
4632+ {
4633+ data->count++;
4634+ }
4635+}
4636+
4637+TEST_F(LibUAL, StartStopHelperObserver)
4638+{
4639+ helper_observer_data_t start_data = {
4640+ .count = 0, .appid = "com.foo_foo_1.2.3", .type = "my-type-is-scorpio", .instance = nullptr};
4641+ helper_observer_data_t stop_data = {
4642+ .count = 0, .appid = "com.bar_bar_44.32", .type = "my-type-is-libra", .instance = "1234"};
4643+
4644+ ASSERT_TRUE(ubuntu_app_launch_observer_add_helper_started(helper_observer_cb, "my-type-is-scorpio", &start_data));
4645+ ASSERT_TRUE(ubuntu_app_launch_observer_add_helper_stop(helper_observer_cb, "my-type-is-libra", &stop_data));
4646+
4647+ DbusTestDbusMockObject* obj =
4648+ dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", NULL);
4649+
4650+ /* Basic start */
4651+ dbus_test_dbus_mock_object_emit_signal(
4652+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4653+ g_variant_new_parsed("('started', ['JOB=untrusted-helper', 'INSTANCE=my-type-is-scorpio::com.foo_foo_1.2.3'])"),
4654+ NULL);
4655+
4656+ g_usleep(100000);
4657+ while (g_main_pending())
4658+ {
4659+ g_main_iteration(TRUE);
4660+ }
4661+
4662+ ASSERT_EQ(start_data.count, 1);
4663+
4664+ /* Basic stop */
4665+ dbus_test_dbus_mock_object_emit_signal(
4666+ mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
4667+ g_variant_new_parsed(
4668+ "('stopped', ['JOB=untrusted-helper', 'INSTANCE=my-type-is-libra:1234:com.bar_bar_44.32'])"),
4669+ NULL);
4670+
4671+ g_usleep(100000);
4672+ while (g_main_pending())
4673+ {
4674+ g_main_iteration(TRUE);
4675+ }
4676+
4677+ ASSERT_EQ(stop_data.count, 1);
4678+
4679+ /* Remove */
4680+ ASSERT_TRUE(
4681+ ubuntu_app_launch_observer_delete_helper_started(helper_observer_cb, "my-type-is-scorpio", &start_data));
4682+ ASSERT_TRUE(ubuntu_app_launch_observer_delete_helper_stop(helper_observer_cb, "my-type-is-libra", &stop_data));
4683+}
4684+
4685+gboolean datain(GIOChannel* source, GIOCondition cond, gpointer data)
4686+{
4687+ gsize* datacnt = static_cast<gsize*>(data);
4688+ gchar* str = NULL;
4689+ gsize len = 0;
4690+ GError* error = NULL;
4691+
4692+ g_io_channel_read_line(source, &str, &len, NULL, &error);
4693+ g_free(str);
4694+
4695+ if (error != NULL)
4696+ {
4697+ g_warning("Unable to read from channel: %s", error->message);
4698+ g_error_free(error);
4699+ }
4700+
4701+ *datacnt += len;
4702+
4703+ return TRUE;
4704+}
4705+
4706+static void signal_increment(GDBusConnection* connection,
4707+ const gchar* sender,
4708+ const gchar* path,
4709+ const gchar* interface,
4710+ const gchar* signal,
4711+ GVariant* params,
4712+ gpointer user_data)
4713+{
4714+ guint* count = (guint*)user_data;
4715+ g_debug("Count incremented to: %d", *count + 1);
4716+ *count = *count + 1;
4717+}
4718+
4719+TEST_F(LibUAL, PauseResume)
4720+{
4721+ g_setenv("UBUNTU_APP_LAUNCH_OOM_PROC_PATH", CMAKE_BINARY_DIR "/libual-proc", 1);
4722+
4723+ /* Setup some spew */
4724+ GPid spewpid = 0;
4725+ gint spewstdout = 0;
4726+ const gchar* spewline[] = {SPEW_UTILITY, NULL};
4727+ ASSERT_TRUE(g_spawn_async_with_pipes(NULL, (gchar**)spewline, NULL, /* environment */
4728+ G_SPAWN_DEFAULT, NULL, NULL, /* child setup */
4729+ &spewpid, NULL, /* stdin */
4730+ &spewstdout, NULL, /* stderr */
4731+ NULL)); /* error */
4732+
4733+ gsize datacnt = 0;
4734+ GIOChannel* spewoutchan = g_io_channel_unix_new(spewstdout);
4735+ g_io_channel_set_flags(spewoutchan, G_IO_FLAG_NONBLOCK, NULL);
4736+ g_io_add_watch(spewoutchan, G_IO_IN, datain, &datacnt);
4737+
4738+ /* Setup our OOM adjust file */
4739+ gchar* procdir = g_strdup_printf(CMAKE_BINARY_DIR "/libual-proc/%d", spewpid);
4740+ ASSERT_EQ(0, g_mkdir_with_parents(procdir, 0700));
4741+ gchar* oomadjfile = g_strdup_printf("%s/oom_score_adj", procdir);
4742+ g_free(procdir);
4743+ ASSERT_TRUE(g_file_set_contents(oomadjfile, "0", -1, NULL));
4744+
4745+ /* Setup the cgroup */
4746+ g_setenv("UBUNTU_APP_LAUNCH_CG_MANAGER_NAME", "org.test.cgmock2", TRUE);
4747+ DbusTestDbusMock* cgmock2 = dbus_test_dbus_mock_new("org.test.cgmock2");
4748+ DbusTestDbusMockObject* cgobject = dbus_test_dbus_mock_get_object(cgmock2, "/org/linuxcontainers/cgmanager",
4749+ "org.linuxcontainers.cgmanager0_0", NULL);
4750+ gchar* pypids = g_strdup_printf("ret = [%d]", spewpid);
4751+ dbus_test_dbus_mock_object_add_method(cgmock, cgobject, "GetTasksRecursive", G_VARIANT_TYPE("(ss)"),
4752+ G_VARIANT_TYPE("ai"), pypids, NULL);
4753+ g_free(pypids);
4754+
4755+ dbus_test_service_add_task(service, DBUS_TEST_TASK(cgmock2));
4756+ dbus_test_task_run(DBUS_TEST_TASK(cgmock2));
4757+ g_object_unref(G_OBJECT(cgmock2));
4758+
4759+ /* Setup ZG Mock */
4760+ DbusTestDbusMock* zgmock = dbus_test_dbus_mock_new("org.gnome.zeitgeist.Engine");
4761+ DbusTestDbusMockObject* zgobj =
4762+ dbus_test_dbus_mock_get_object(zgmock, "/org/gnome/zeitgeist/log/activity", "org.gnome.zeitgeist.Log", NULL);
4763+
4764+ dbus_test_dbus_mock_object_add_method(zgmock, zgobj, "InsertEvents", G_VARIANT_TYPE("a(asaasay)"),
4765+ G_VARIANT_TYPE("au"), "ret = [ 0 ]", NULL);
4766+
4767+ dbus_test_service_add_task(service, DBUS_TEST_TASK(zgmock));
4768+ dbus_test_task_run(DBUS_TEST_TASK(zgmock));
4769+ g_object_unref(G_OBJECT(zgmock));
4770+
4771+ /* Give things a chance to start */
4772+ do
4773+ {
4774+ g_debug("Giving mocks a chance to start");
4775+ pause(200);
4776+ } while (dbus_test_task_get_state(DBUS_TEST_TASK(cgmock2)) != DBUS_TEST_TASK_STATE_RUNNING &&
4777+ dbus_test_task_get_state(DBUS_TEST_TASK(zgmock)) != DBUS_TEST_TASK_STATE_RUNNING);
4778+
4779+ /* Setup signal handling */
4780+ guint paused_count = 0;
4781+ guint resumed_count = 0;
4782+ guint paused_signal =
4783+ g_dbus_connection_signal_subscribe(bus, nullptr, "com.canonical.UbuntuAppLaunch", "ApplicationPaused", "/",
4784+ nullptr, G_DBUS_SIGNAL_FLAGS_NONE, signal_increment, &paused_count, nullptr);
4785+ guint resumed_signal = g_dbus_connection_signal_subscribe(
4786+ bus, nullptr, "com.canonical.UbuntuAppLaunch", "ApplicationResumed", "/", nullptr, G_DBUS_SIGNAL_FLAGS_NONE,
4787+ signal_increment, &resumed_count, nullptr);
4788+
4789+ /* Test it */
4790+ EXPECT_NE(0, datacnt);
4791+ paused_count = 0;
4792+
4793+ /* Pause the app */
4794+ EXPECT_TRUE(ubuntu_app_launch_pause_application("com.test.good_application_1.2.3"));
4795+
4796+ pause(0); /* Flush queued events */
4797+ datacnt = 0; /* clear it */
4798+
4799+ pause(200);
4800+
4801+ /* Check data coming out */
4802+ EXPECT_EQ(1, paused_count);
4803+ EXPECT_EQ(0, datacnt);
4804+
4805+ /* Check to make sure we sent the event to ZG */
4806+ guint numcalls = 0;
4807+ const DbusTestDbusMockCall* calls =
4808+ dbus_test_dbus_mock_object_get_method_calls(zgmock, zgobj, "InsertEvents", &numcalls, NULL);
4809+
4810+ EXPECT_NE(nullptr, calls);
4811+ EXPECT_EQ(1, numcalls);
4812+
4813+ dbus_test_dbus_mock_object_clear_method_calls(zgmock, zgobj, NULL);
4814+
4815+ /* Check to ensure we set the OOM score */
4816+ gchar* pauseoomscore = NULL;
4817+ ASSERT_TRUE(g_file_get_contents(oomadjfile, &pauseoomscore, NULL, NULL));
4818+ EXPECT_STREQ("900", pauseoomscore);
4819+ g_free(pauseoomscore);
4820+ resumed_count = 0;
4821+
4822+ /* Now Resume the App */
4823+ EXPECT_TRUE(ubuntu_app_launch_resume_application("com.test.good_application_1.2.3"));
4824+
4825+ pause(200);
4826+
4827+ EXPECT_NE(0, datacnt);
4828+ EXPECT_EQ(1, resumed_count);
4829+
4830+ /* Check to make sure we sent the event to ZG */
4831+ numcalls = 0;
4832+ calls = dbus_test_dbus_mock_object_get_method_calls(zgmock, zgobj, "InsertEvents", &numcalls, NULL);
4833+
4834+ EXPECT_NE(nullptr, calls);
4835+ EXPECT_EQ(1, numcalls);
4836+
4837+ /* Check to ensure we set the OOM score */
4838+ gchar* resumeoomscore = NULL;
4839+ ASSERT_TRUE(g_file_get_contents(oomadjfile, &resumeoomscore, NULL, NULL));
4840+ EXPECT_STREQ("100", resumeoomscore);
4841+ g_free(resumeoomscore);
4842+
4843+ /* Clean up */
4844+ gchar* killstr = g_strdup_printf("kill -9 %d", spewpid);
4845+ ASSERT_TRUE(g_spawn_command_line_sync(killstr, NULL, NULL, NULL, NULL));
4846+ g_free(killstr);
4847+
4848+ g_io_channel_unref(spewoutchan);
4849+
4850+ g_spawn_command_line_sync("rm -rf " CMAKE_BINARY_DIR "/libual-proc", NULL, NULL, NULL, NULL);
4851+
4852+ g_dbus_connection_signal_unsubscribe(bus, paused_signal);
4853+ g_dbus_connection_signal_unsubscribe(bus, resumed_signal);
4854+
4855+ /* Kill ZG default instance :-( */
4856+ ZeitgeistLog* log = zeitgeist_log_get_default();
4857+ g_object_unref(log);
4858+ g_object_unref(log);
4859+
4860+ g_free(oomadjfile);
4861+}
4862+
4863+TEST_F(LibUAL, StartSessionHelper)
4864+{
4865+ DbusTestDbusMockObject* obj =
4866+ dbus_test_dbus_mock_get_object(mock, "/com/test/untrusted/helper", "com.ubuntu.Upstart0_6.Job", NULL);
4867+ MirConnection* conn = mir_connect_sync("libual-test", "start-session-helper"); // Mocked, doesn't need cleaning up
4868+ MirPromptSession* msession = mir_connection_create_prompt_session_sync(conn, 5, nullptr, nullptr);
4869+
4870+ /* Building a temporary file and making an FD for it */
4871+ const char* filedata = "This is some data that we should get on the other side\n";
4872+ ASSERT_TRUE(g_file_set_contents(SESSION_TEMP_FILE, filedata, strlen(filedata), nullptr) == TRUE);
4873+ int mirfd = open(SESSION_TEMP_FILE, 0);
4874+ mir_mock_set_trusted_fd(mirfd);
4875+
4876+ /* Basic make sure we can send the event */
4877+ auto untrusted = ubuntu::app_launch::Helper::Type::from_raw("untrusted-type");
4878+ auto appid = ubuntu::app_launch::AppID::parse("com.test.multiple_first_1.2.3");
4879+ auto helper = ubuntu::app_launch::Helper::create(untrusted, appid, registry);
4880+
4881+ helper->launch(msession);
4882+
4883+ guint len = 0;
4884+ const DbusTestDbusMockCall* calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "Start", &len, NULL);
4885+ EXPECT_NE(nullptr, calls);
4886+ EXPECT_EQ(1, len);
4887+
4888+ EXPECT_STREQ("Start", calls->name);
4889+ EXPECT_EQ(2, g_variant_n_children(calls->params));
4890+
4891+ GVariant* block = g_variant_get_child_value(calls->params, 1);
4892+ EXPECT_TRUE(g_variant_get_boolean(block));
4893+ g_variant_unref(block);
4894+
4895+ /* Check the environment */
4896+ GVariant* env = g_variant_get_child_value(calls->params, 0);
4897+ EXPECT_TRUE(check_env(env, "APP_ID", "com.test.multiple_first_1.2.3"));
4898+ EXPECT_TRUE(check_env(env, "HELPER_TYPE", "untrusted-type"));
4899+
4900+ GVariant* mnamev = find_env(env, "UBUNTU_APP_LAUNCH_DEMANGLE_NAME");
4901+ ASSERT_NE(nullptr, mnamev); /* Have to assert because, eh, GVariant */
4902+ EXPECT_STREQ(g_dbus_connection_get_unique_name(bus),
4903+ g_variant_get_string(mnamev, nullptr) + strlen("UBUNTU_APP_LAUNCH_DEMANGLE_NAME="));
4904+ GVariant* mpathv = find_env(env, "UBUNTU_APP_LAUNCH_DEMANGLE_PATH");
4905+ ASSERT_NE(nullptr, mpathv); /* Have to assert because, eh, GVariant */
4906+
4907+ g_variant_unref(env);
4908+
4909+ /* Setup environment for call */
4910+ const gchar* mname = g_variant_get_string(mnamev, nullptr);
4911+ mname += strlen("UBUNTU_APP_LAUNCH_DEMANGLE_NAME=");
4912+ g_setenv("UBUNTU_APP_LAUNCH_DEMANGLE_NAME", mname, TRUE);
4913+ g_variant_unref(mnamev);
4914+
4915+ const gchar* mpath = g_variant_get_string(mpathv, nullptr);
4916+ mpath += strlen("UBUNTU_APP_LAUNCH_DEMANGLE_PATH=");
4917+ g_setenv("UBUNTU_APP_LAUNCH_DEMANGLE_PATH", mpath, TRUE);
4918+ g_variant_unref(mpathv);
4919+
4920+ /* Exec our tool */
4921+ std::promise<std::string> outputpromise;
4922+ std::thread t(
4923+ [&outputpromise]()
4924+ {
4925+ gchar* socketstdout = nullptr;
4926+ GError* error = nullptr;
4927+ g_unsetenv("G_MESSAGES_DEBUG");
4928+
4929+ g_spawn_command_line_sync(SOCKET_DEMANGLER " " SOCKET_TOOL, &socketstdout, nullptr, nullptr, &error);
4930+
4931+ if (error != nullptr)
4932+ {
4933+ fprintf(stderr, "Unable to spawn '" SOCKET_DEMANGLER " " SOCKET_TOOL "': %s\n", error->message);
4934+ g_error_free(error);
4935+ outputpromise.set_value(std::string(""));
4936+ }
4937+ else
4938+ {
4939+ outputpromise.set_value(std::string(socketstdout));
4940+ g_free(socketstdout);
4941+ }
4942+ });
4943+ t.detach();
4944+
4945+ auto outputfuture = outputpromise.get_future();
4946+ while (outputfuture.wait_for(std::chrono::milliseconds{1}) != std::future_status::ready)
4947+ {
4948+ pause();
4949+ }
4950+
4951+ ASSERT_STREQ(filedata, outputfuture.get().c_str());
4952+
4953+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
4954+
4955+ return;
4956+}
4957+
4958+TEST_F(LibUAL, SetExec)
4959+{
4960+ DbusTestDbusMockObject* obj =
4961+ dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", NULL);
4962+
4963+ const char* exec = "lets exec this";
4964+
4965+ g_setenv("UPSTART_JOB", "fubar", TRUE);
4966+ g_unsetenv("UBUNTU_APP_LAUNCH_DEMANGLE_NAME");
4967+ EXPECT_TRUE(ubuntu_app_launch_helper_set_exec(exec, NULL));
4968+
4969+ guint len = 0;
4970+ const DbusTestDbusMockCall* calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "SetEnv", &len, NULL);
4971+ ASSERT_NE(nullptr, calls);
4972+ EXPECT_EQ(1, len);
4973+
4974+ gchar* appexecstr = g_strdup_printf("APP_EXEC=%s", exec);
4975+ GVariant* appexecenv = g_variant_get_child_value(calls[0].params, 1);
4976+ EXPECT_STREQ(appexecstr, g_variant_get_string(appexecenv, nullptr));
4977+ g_variant_unref(appexecenv);
4978+ g_free(appexecstr);
4979+
4980+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
4981+
4982+ /* Now check for the demangler */
4983+ g_setenv("UBUNTU_APP_LAUNCH_DEMANGLE_NAME", g_dbus_connection_get_unique_name(bus), TRUE);
4984+ EXPECT_TRUE(ubuntu_app_launch_helper_set_exec(exec, NULL));
4985+
4986+ calls = dbus_test_dbus_mock_object_get_method_calls(mock, obj, "SetEnv", &len, NULL);
4987+ ASSERT_NE(nullptr, calls);
4988+ EXPECT_EQ(1, len);
4989+
4990+ gchar* demangleexecstr = g_strdup_printf("APP_EXEC=%s %s", SOCKET_DEMANGLER_INSTALL, exec);
4991+ appexecenv = g_variant_get_child_value(calls[0].params, 1);
4992+ EXPECT_STREQ(demangleexecstr, g_variant_get_string(appexecenv, nullptr));
4993+ g_variant_unref(appexecenv);
4994+ g_free(demangleexecstr);
4995+
4996+ ASSERT_TRUE(dbus_test_dbus_mock_object_clear_method_calls(mock, obj, NULL));
4997+
4998+ /* Now check for the directory */
4999+ g_setenv("UBUNTU_APP_LAUNCH_DEMANGLE_NAME", g_dbus_connection_get_unique_name(bus), TRUE);
5000+ EXPECT_TRUE(ubuntu_app_launch_helper_set_exec(exec, "/not/a/real/directory"));
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches