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

Proposed by Stephen M. Webb
Status: Rejected
Rejected by: Ted Gould
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 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

Removing signals from the API for this MR

230. By Ted Gould

Revert back to 'C' versions of these tools

231. By Ted Gould

Fix symbols

232. By Ted Gould

Switch to a different arbitrary style

233. By Ted Gould

Switch from symbols to shlibs

234. By Ted Gould

Namespace bike shedding

235. By Ted Gould

Remove as member functions

236. By Ted Gould

Adding the class keyword to enums

237. By Ted Gould

Optimize constructors

238. By Ted Gould

Explicit constructor

239. By Ted Gould

Check to ensure cpath isn't null

240. By Ted Gould

Make URL vectors const references

241. By Ted Gould

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

242. By Ted Gould

Passing the manifests by const reference

243. By Ted Gould

Adding a format target

244. By Ted Gould

Exception on manifest failures and ensure that we have it early

245. By Ted Gould

Stop incrementing ref counts to make the code faster

246. By Ted Gould

Check for keyfile in constructor

247. By Ted Gould

Check for keyfile in constructor

248. By Ted Gould

Move prototype

249. By Ted Gould

Making voids implicit instead of explicit

250. By Ted Gould

Move from one implementation only location to another implementation only location

251. By Ted Gould

Constexpr

252. By Ted Gould

Adding extra commas

253. By Ted Gould

Less splash, more namespacing

254. By Ted Gould

Change to supportsUbuntuLifecycle

255. By Stephen M. Webb

added missing build-dep on libproperties-cpp-dev

256. By Stephen M. Webb

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

257. By Stephen M. Webb

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

258. By Stephen M. Webb

Disabled a bunch of chronically-failing tests.

259. By Stephen M. Webb

merge latest ted upstream branch

Unmerged revisions

259. By Stephen M. Webb

merge latest ted upstream branch

258. By Stephen M. Webb

Disabled a bunch of chronically-failing tests.

257. By Stephen M. Webb

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

256. By Stephen M. Webb

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

255. By Stephen M. Webb

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