Merge lp:~ted/ubuntu-app-launch/app-object-signals into lp:ubuntu-app-launch/16.10

Proposed by Ted Gould
Status: Superseded
Proposed branch: lp:~ted/ubuntu-app-launch/app-object-signals
Merge into: lp:ubuntu-app-launch/16.10
Diff against target: 3184 lines (+1583/-880)
22 files modified
libubuntu-app-launch/application-impl-libertine.cpp (+1/-2)
libubuntu-app-launch/libubuntu-app-launch.map (+1/-0)
libubuntu-app-launch/registry-impl.cpp (+493/-11)
libubuntu-app-launch/registry-impl.h (+76/-11)
libubuntu-app-launch/registry.cpp (+42/-2)
libubuntu-app-launch/registry.h (+120/-14)
libubuntu-app-launch/ubuntu-app-launch.cpp (+339/-413)
tests/CMakeLists.txt (+1/-0)
tests/failure-test.cc (+123/-96)
tests/libual-cpp-test.cc (+186/-141)
tools/CMakeLists.txt (+21/-2)
tools/ubuntu-app-info.cpp (+18/-9)
tools/ubuntu-app-launch.cpp (+56/-68)
tools/ubuntu-app-list-pids.cpp (+7/-3)
tools/ubuntu-app-list.cpp (+1/-1)
tools/ubuntu-app-pid.cpp (+8/-4)
tools/ubuntu-app-stop.cpp (+7/-3)
tools/ubuntu-app-triplet.cpp (+1/-1)
tools/ubuntu-app-watch.cpp (+61/-88)
tools/ubuntu-helper-list.cpp (+1/-1)
tools/ubuntu-helper-start.cpp (+10/-5)
tools/ubuntu-helper-stop.cpp (+10/-5)
To merge this branch: bzr merge lp:~ted/ubuntu-app-launch/app-object-signals
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Indicator Applet Developers Pending
Review via email: mp+294807@code.launchpad.net

This proposal supersedes a proposal from 2016-02-08.

This proposal has been superseded by a proposal from 2016-11-07.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:232
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~ted/ubuntu-app-launch/app-object-signals/+merge/294807/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-ci/55/
Executed test runs:
    FAILURE: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-amd64-ci/55/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-armhf-ci/55/console
    FAILURE: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-i386-ci/55/console

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-app-launch-ci/55/rebuild

review: Needs Fixing (continuous-integration)
234. By Ted Gould

Updated to trunk

235. By Ted Gould

Make the signal access functions

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:234
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~ted/ubuntu-app-launch/app-object-signals/+merge/294807/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-ci/56/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-amd64-ci/56
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-armhf-ci/56
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-i386-ci/56

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-app-launch-ci/56/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:235
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https://code.launchpad.net/~ted/ubuntu-app-launch/app-object-signals/+merge/294807/+edit-commit-message

http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-ci/57/
Executed test runs:
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-amd64-ci/57
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-armhf-ci/57
    SUCCESS: http://jenkins.qa.ubuntu.com/job/ubuntu-app-launch-wily-i386-ci/57

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/ubuntu-app-launch-ci/57/rebuild

review: Needs Fixing (continuous-integration)
236. By Ted Gould

Adding in stubs to bring out the implementation

237. By Ted Gould

Merging trunk

238. By Ted Gould

Move the signal functions into the implementation

239. By Ted Gould

Building a place to register signals

240. By Ted Gould

Code up starting and stopping, some TODOs

241. By Ted Gould

Port over app started and stopped to C++ interfaces

242. By Ted Gould

Switch to passing in the shared pointer to the registry

243. By Ted Gould

Make sure to grab a copy of the shared pointer

244. By Ted Gould

Move the greed around in our regex

245. By Ted Gould

Switching to a weak pointer

246. By Ted Gould

Make calls with the C library use the current context to not break existing code.

247. By Ted Gould

Moving over pause/resume

248. By Ted Gould

Fixing the watcher

249. By Ted Gould

Migrating over the failed signal

250. By Ted Gould

Apply formatting rules to tools

251. By Ted Gould

Print pids for pause/resume

252. By Ted Gould

Basic foundation for manager signals

253. By Ted Gould

Start to flesh out more of the resume request

254. By Ted Gould

Restructure to use more shared pointers

255. By Ted Gould

Switch the C code to using the manager interface

256. By Ted Gould

Fleshing out the focus handler

257. By Ted Gould

Add a handler for starting

258. By Ted Gould

Refactor into a shared function to do the longer dbus calls

259. By Ted Gould

Restructure so that we can call the right signal

260. By Ted Gould

Make it so that the manager observers execute on their context

261. By Ted Gould

Switch to using a global weak pointer so that it expires with the tracking of the Registry object

262. By Ted Gould

Clean up weak_ptr<> code

263. By Ted Gould

Make sure we wait to install the signals before returning

264. By Ted Gould

Put a warning in about thread safety

265. By Ted Gould

Port tests over to using a manager for focus events

266. By Ted Gould

Fixing the start and stop observer test

267. By Ted Gould

Fix the starting test

268. By Ted Gould

Failing test ported over

269. By Ted Gould

Documentation for registry functions

270. By Ted Gould

More comments

271. By Ted Gould

Variable comments

272. By Ted Gould

A comment on why this comment is there

273. By Ted Gould

Switch failure test over to using the C++ api

274. By Ted Gould

Making sure we connect the signals before returnning

275. By Ted Gould

Fix failure test environment and values to make them work as real applications

276. By Ted Gould

Apply formatting rules to failure-test.cc

277. By Ted Gould

Make gcc6 happy

278. By Ted Gould

Putting additional checks in to make sure we don't use null pointers we don't get locks on.

279. By Ted Gould

Ensure the manager thread shutsdown before the registry to avoid a deadlock

280. By Ted Gould

Merge future trunk

281. By Ted Gould

Remove some of the sing song part of the comments

282. By Ted Gould

Additional comment on lifecycle of replies

283. By Ted Gould

Zesty formatting tools diffs

284. By Ted Gould

Curly init

285. By Ted Gould

Make sure to check for a nullappid or error for g_variant_get()

286. By Ted Gould

Names of the parameters for clarity

287. By Ted Gould

Moar auto!

288. By Ted Gould

Don't specify returning void explicitly

289. By Ted Gould

Use static_cast() for void* casts

290. By Ted Gould

Don't spell well

291. By Ted Gould

Make sure to check for a valid registry

292. By Ted Gould

Comment out unused variables

293. By Ted Gould

Sometimes life would be better if it was more constant

294. By Ted Gould

Me no spell good

295. By Ted Gould

auto auto auto

296. By Ted Gould

Some 'NULL's crept in

297. By Ted Gould

More auto's with GVariants

298. By Ted Gould

Make params constant

299. By Ted Gould

Change setManager to have const& parameters

300. By Ted Gould

Fix the API so that the signal callbacks take pointers

301. By Ted Gould

Note it is on the UAL thread only

302. By Ted Gould

Save some stack data with this context

303. By Ted Gould

Formatting fix

304. By Ted Gould

Use an ensure_cmanager() helper to remove duplicate code

305. By Ted Gould

Putting all the map handling code in a couple templates

306. By Ted Gould

Pull out the request code into individual functions

307. By Ted Gould

Rename a function and add comments

308. By Ted Gould

Expand the usage of observer_delete

309. By Ted Gould

Factor out pause/resume commonality

310. By Ted Gould

I've been overrided by charles

311. By Ted Gould

Update to trunk

312. By Ted Gould

Adding virtual destructors, acc says they're fine.

313. By Ted Gould

Putting this off for gcc 5.4

314. By Ted Gould

Block off more API breaks this time

Unmerged revisions

314. By Ted Gould

Block off more API breaks this time

313. By Ted Gould

Putting this off for gcc 5.4

312. By Ted Gould

Adding virtual destructors, acc says they're fine.

311. By Ted Gould

Update to trunk

310. By Ted Gould

I've been overrided by charles

309. By Ted Gould

Factor out pause/resume commonality

308. By Ted Gould

Expand the usage of observer_delete

307. By Ted Gould

Rename a function and add comments

306. By Ted Gould

Pull out the request code into individual functions

305. By Ted Gould

Putting all the map handling code in a couple templates

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libubuntu-app-launch/application-impl-libertine.cpp'
2--- libubuntu-app-launch/application-impl-libertine.cpp 2016-10-03 23:54:20 +0000
3+++ libubuntu-app-launch/application-impl-libertine.cpp 2016-11-07 20:56:13 +0000
4@@ -255,8 +255,7 @@
5 }
6 catch (std::runtime_error& e)
7 {
8- g_debug("Unable to create application for libertine appname '%s': %s",
9- apps.get()[j], e.what());
10+ g_debug("Unable to create application for libertine appname '%s': %s", apps.get()[j], e.what());
11 }
12 }
13 }
14
15=== modified file 'libubuntu-app-launch/libubuntu-app-launch.map'
16--- libubuntu-app-launch/libubuntu-app-launch.map 2016-05-19 16:24:11 +0000
17+++ libubuntu-app-launch/libubuntu-app-launch.map 2016-11-07 20:56:13 +0000
18@@ -14,6 +14,7 @@
19 ubuntu::app_launch::Helper::*;
20 typeinfo?for?ubuntu::app_launch::Helper;
21 typeinfo?name?for?ubuntu::app_launch::Helper;
22+ ubuntu::app_launch::operator*;
23 ubuntu::app_launch::oom::*;
24 };
25 local:
26
27=== modified file 'libubuntu-app-launch/registry-impl.cpp'
28--- libubuntu-app-launch/registry-impl.cpp 2016-09-23 22:30:51 +0000
29+++ libubuntu-app-launch/registry-impl.cpp 2016-11-07 20:56:13 +0000
30@@ -20,6 +20,7 @@
31 #include "registry-impl.h"
32 #include "application-icon-finder.h"
33 #include <cgmanager/cgmanager.h>
34+#include <regex>
35 #include <upstart.h>
36
37 namespace ubuntu
38@@ -36,13 +37,29 @@
39 zgLog_.reset();
40 cgManager_.reset();
41
42+ auto dohandle = [&](guint& handle) {
43+ if (handle != 0)
44+ {
45+ g_dbus_connection_signal_unsubscribe(_dbus.get(), handle);
46+ handle = 0;
47+ }
48+ };
49+
50+ dohandle(handle_appStarted);
51+ dohandle(handle_appStopped);
52+ dohandle(handle_appFailed);
53+ dohandle(handle_appPaused);
54+ dohandle(handle_appResumed);
55+ dohandle(handle_managerSignalFocus);
56+ dohandle(handle_managerSignalResume);
57+ dohandle(handle_managerSignalStarting);
58+
59 if (_dbus)
60 g_dbus_connection_flush_sync(_dbus.get(), nullptr, nullptr);
61 _dbus.reset();
62 })
63 , _registry(registry)
64 , _iconFinders()
65-// _manager(nullptr)
66 {
67 auto cancel = thread.getCancellable();
68 _dbus = thread.executeOnThread<std::shared_ptr<GDBusConnection>>([cancel]() {
69@@ -536,24 +553,193 @@
70 return _iconFinders[basePath];
71 }
72
73-#if 0
74-void
75-Registry::Impl::setManager (Registry::Manager* manager)
76-{
77- if (_manager != nullptr)
78+/** Structure to track the data needed for upstart events. This cleans
79+ up the lifecycle as we're passing this as a pointer through the
80+ GLib calls. */
81+struct upstartEventData
82+{
83+ /** Keeping a weak pointer because the handle is held by
84+ the registry implementation. */
85+ std::weak_ptr<Registry> weakReg;
86+};
87+
88+/** Take the GVariant of parameters and turn them into an application and
89+ and instance. Easier to read in the smaller function */
90+std::tuple<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> Registry::Impl::managerParams(
91+ const std::shared_ptr<GVariant>& params, const std::shared_ptr<Registry>& reg)
92+{
93+ std::shared_ptr<Application> app;
94+ std::shared_ptr<Application::Instance> instance;
95+
96+ const gchar* cappid = nullptr;
97+ g_variant_get(params.get(), "(&s)", &cappid);
98+
99+ auto appid = ubuntu::app_launch::AppID::find(reg, cappid);
100+ app = ubuntu::app_launch::Application::create(appid, reg);
101+
102+ return std::make_tuple(app, instance);
103+}
104+
105+/** Used to store data for manager based signal handlers. Has a link to the
106+ registry and the callback to use in a C++ style. */
107+struct managerEventData
108+{
109+ /* Keeping a weak pointer because the handle is held by
110+ the registry implementation. */
111+ std::weak_ptr<Registry> weakReg;
112+ std::function<void(const std::shared_ptr<Registry>& reg,
113+ const std::shared_ptr<Application>& app,
114+ const std::shared_ptr<Application::Instance>& instance,
115+ const std::shared_ptr<GDBusConnection>&,
116+ const std::string&,
117+ const std::shared_ptr<GVariant>&)>
118+ func;
119+};
120+
121+/** Register for a signal for the manager. All of the signals needed this same
122+ code so it got pulled out into a function. Takes the same of the signal, the registry
123+ that we're using and a function to call after we've messaged all the parameters
124+ into being something C++-ish. */
125+guint Registry::Impl::managerSignalHelper(const std::shared_ptr<Registry>& reg,
126+ const std::string& signalname,
127+ std::function<void(const std::shared_ptr<Registry>& reg,
128+ const std::shared_ptr<Application>& app,
129+ const std::shared_ptr<Application::Instance>& instance,
130+ const std::shared_ptr<GDBusConnection>&,
131+ const std::string&,
132+ const std::shared_ptr<GVariant>&)> responsefunc)
133+{
134+ managerEventData* focusdata = new managerEventData{reg, responsefunc};
135+
136+ return g_dbus_connection_signal_subscribe(
137+ reg->impl->_dbus.get(), /* bus */
138+ nullptr, /* sender */
139+ "com.canonical.UbuntuAppLaunch", /* interface */
140+ signalname.c_str(), /* signal */
141+ "/", /* path */
142+ nullptr, /* arg0 */
143+ G_DBUS_SIGNAL_FLAGS_NONE,
144+ [](GDBusConnection* cconn, const gchar* csender, const gchar*, const gchar*, const gchar*, GVariant* params,
145+ gpointer user_data) -> void {
146+ auto data = reinterpret_cast<managerEventData*>(user_data);
147+ auto reg = data->weakReg.lock();
148+
149+ /* If we're still conneted and the manager has been cleared
150+ we'll just be a no-op */
151+ if (!reg->impl->manager_)
152+ {
153+ return;
154+ }
155+
156+ auto vparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
157+ auto conn = std::shared_ptr<GDBusConnection>(reinterpret_cast<GDBusConnection*>(g_object_ref(cconn)),
158+ [](GDBusConnection* con) { g_clear_object(&con); });
159+ std::string sender = csender;
160+ std::shared_ptr<Application> app;
161+ std::shared_ptr<Application::Instance> instance;
162+
163+ std::tie(app, instance) = managerParams(vparams, reg);
164+
165+ data->func(reg, app, instance, conn, sender, vparams);
166+ },
167+ focusdata,
168+ [](gpointer user_data) {
169+ auto data = reinterpret_cast<managerEventData*>(user_data);
170+ delete data;
171+ }); /* user data destroy */
172+}
173+
174+/** Set the manager for the registry. This includes tracking the pointer
175+ as well as setting up the signals to call back into the manager. The
176+ signals are only setup once per registry even if the manager is cleared
177+ and changed again. They will just be no-op's in those cases.
178+*/
179+void Registry::Impl::setManager(std::shared_ptr<Registry::Manager> manager, std::shared_ptr<Registry> reg)
180+{
181+ if (reg->impl->manager_)
182 {
183 throw std::runtime_error("Already have a manager and trying to set another");
184 }
185
186- _manager = manager;
187+ g_debug("Setting a new manager");
188+ reg->impl->manager_ = manager;
189+
190+ std::call_once(reg->impl->flag_managerSignals, [reg]() {
191+ if (!reg->impl->thread.executeOnThread<bool>([reg]() {
192+ reg->impl->handle_managerSignalFocus = managerSignalHelper(
193+ reg, "UnityFocusRequest",
194+ [](const std::shared_ptr<Registry>& reg, const std::shared_ptr<Application>& app,
195+ const std::shared_ptr<Application::Instance>& instance,
196+ const std::shared_ptr<GDBusConnection>& conn, const std::string& sender,
197+ const std::shared_ptr<GVariant>& params) {
198+ /* Nothing to do today */
199+ reg->impl->manager_->focusRequest(app, instance, [](bool response) {
200+ /* NOTE: We have no clue what thread this is gonna be
201+ executed on, but since we're just talking to the GDBus
202+ thread it isn't an issue today. Be careful in changing
203+ this code. */
204+ });
205+ });
206+ reg->impl->handle_managerSignalStarting = managerSignalHelper(
207+ reg, "UnityStartingBroadcast",
208+ [](const std::shared_ptr<Registry>& reg, const std::shared_ptr<Application>& app,
209+ const std::shared_ptr<Application::Instance>& instance,
210+ const std::shared_ptr<GDBusConnection>& conn, const std::string& sender,
211+ const std::shared_ptr<GVariant>& params) {
212+
213+ reg->impl->manager_->startingRequest(app, instance, [conn, sender, params](bool response) {
214+ /* NOTE: We have no clue what thread this is gonna be
215+ executed on, but since we're just talking to the GDBus
216+ thread it isn't an issue today. Be careful in changing
217+ this code. */
218+ if (response)
219+ {
220+ g_dbus_connection_emit_signal(conn.get(), sender.c_str(), /* destination */
221+ "/", /* path */
222+ "com.canonical.UbuntuAppLaunch", /* interface */
223+ "UnityStartingSignal", /* signal */
224+ params.get(), /* params, the same */
225+ nullptr); /* error */
226+ }
227+ });
228+ });
229+ reg->impl->handle_managerSignalResume = managerSignalHelper(
230+ reg, "UnityResumeRequest",
231+ [](const std::shared_ptr<Registry>& reg, const std::shared_ptr<Application>& app,
232+ const std::shared_ptr<Application::Instance>& instance,
233+ const std::shared_ptr<GDBusConnection>& conn, const std::string& sender,
234+ const std::shared_ptr<GVariant>& params) {
235+ reg->impl->manager_->resumeRequest(app, instance, [conn, sender, params](bool response) {
236+ /* NOTE: We have no clue what thread this is gonna be
237+ executed on, but since we're just talking to the GDBus
238+ thread it isn't an issue today. Be careful in changing
239+ this code. */
240+ if (response)
241+ {
242+ g_dbus_connection_emit_signal(conn.get(), sender.c_str(), /* destination */
243+ "/", /* path */
244+ "com.canonical.UbuntuAppLaunch", /* interface */
245+ "UnityResumeResponse", /* signal */
246+ params.get(), /* params, the same */
247+ nullptr); /* error */
248+ }
249+ });
250+ });
251+
252+ return true;
253+ }))
254+ {
255+ g_warning("Unable to install manager signals");
256+ }
257+ });
258 }
259
260-void
261-Registry::Impl::clearManager ()
262+/** Clear the manager pointer */
263+void Registry::Impl::clearManager()
264 {
265- _manager = nullptr;
266+ g_debug("Clearing the manager");
267+ manager_.reset();
268 }
269-#endif
270
271 /** App start watching, if we're registered for the signal we
272 can't wait on it. We are making this static right now because
273@@ -576,5 +762,301 @@
274 return watchingAppStarting_;
275 }
276
277+/** Regex to parse the JOB environment variable from Upstart */
278+std::regex jobenv_regex{"^JOB=(application\\-(?:click|snap|legacy))$"};
279+/** Regex to parse the INSTANCE environment variable from Upstart */
280+std::regex instanceenv_regex{"^INSTANCE=(.*?)(?:\\-([0-9]*))?+$"};
281+
282+/** Core of most of the events that come from Upstart directly. Includes parsing of the
283+ Upstart event environment and calling the appropriate signal with the right Application
284+ object and eventually its instance */
285+void Registry::Impl::upstartEventEmitted(
286+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& signal,
287+ std::shared_ptr<GVariant> params,
288+ const std::shared_ptr<Registry>& reg)
289+{
290+ std::string jobname;
291+ std::string sappid;
292+ std::string instance;
293+
294+ gchar* env = nullptr;
295+ GVariant* envs = g_variant_get_child_value(params.get(), 1);
296+ GVariantIter iter;
297+ g_variant_iter_init(&iter, envs);
298+
299+ while (g_variant_iter_loop(&iter, "s", &env))
300+ {
301+ std::smatch match;
302+ std::string senv = env;
303+
304+ if (std::regex_match(senv, match, jobenv_regex))
305+ {
306+ jobname = match[1].str();
307+ }
308+ else if (std::regex_match(senv, match, instanceenv_regex))
309+ {
310+ sappid = match[1].str();
311+ instance = match[2].str();
312+ }
313+ }
314+
315+ g_variant_unref(envs);
316+
317+ if (jobname.empty())
318+ {
319+ return;
320+ }
321+
322+ g_debug("Upstart Event for job '%s' appid '%s' instance '%s'", jobname.c_str(), sappid.c_str(), instance.c_str());
323+
324+ auto appid = AppID::find(reg, sappid);
325+ auto app = Application::create(appid, reg);
326+
327+ // TODO: Figure otu creating instances
328+
329+ signal(app, {});
330+}
331+
332+/** Grab the signal object for application startup. If we're not already listing for
333+ those signals this sets up a listener for them. */
334+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::Impl::appStarted(
335+ const std::shared_ptr<Registry>& reg)
336+{
337+ std::call_once(flag_appStarted, [reg]() {
338+ reg->impl->thread.executeOnThread<bool>([reg]() {
339+ upstartEventData* data = new upstartEventData{reg};
340+
341+ reg->impl->handle_appStarted = g_dbus_connection_signal_subscribe(
342+ reg->impl->_dbus.get(), /* bus */
343+ nullptr, /* sender */
344+ DBUS_INTERFACE_UPSTART, /* interface */
345+ "EventEmitted", /* signal */
346+ DBUS_PATH_UPSTART, /* path */
347+ "started", /* arg0 */
348+ G_DBUS_SIGNAL_FLAGS_NONE,
349+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
350+ gpointer user_data) -> void {
351+ auto data = reinterpret_cast<upstartEventData*>(user_data);
352+ auto reg = data->weakReg.lock();
353+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
354+ reg->impl->upstartEventEmitted(reg->impl->sig_appStarted, sparams, reg);
355+ }, /* callback */
356+ data, /* user data */
357+ [](gpointer user_data) {
358+ auto data = reinterpret_cast<upstartEventData*>(user_data);
359+ delete data;
360+ }); /* user data destroy */
361+
362+ return true;
363+ });
364+ });
365+
366+ return sig_appStarted;
367+}
368+
369+/** Grab the signal object for application stopping. If we're not already listing for
370+ those signals this sets up a listener for them. */
371+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::Impl::appStopped(
372+ const std::shared_ptr<Registry>& reg)
373+{
374+ std::call_once(flag_appStopped, [reg]() {
375+ reg->impl->thread.executeOnThread<bool>([reg]() {
376+ upstartEventData* data = new upstartEventData{reg};
377+
378+ reg->impl->handle_appStopped = g_dbus_connection_signal_subscribe(
379+ reg->impl->_dbus.get(), /* bus */
380+ nullptr, /* sender */
381+ DBUS_INTERFACE_UPSTART, /* interface */
382+ "EventEmitted", /* signal */
383+ DBUS_PATH_UPSTART, /* path */
384+ "stopped", /* arg0 */
385+ G_DBUS_SIGNAL_FLAGS_NONE,
386+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
387+ gpointer user_data) -> void {
388+ auto data = reinterpret_cast<upstartEventData*>(user_data);
389+ auto reg = data->weakReg.lock();
390+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
391+ reg->impl->upstartEventEmitted(reg->impl->sig_appStopped, sparams, reg);
392+ }, /* callback */
393+ data, /* user data */
394+ [](gpointer user_data) {
395+ auto data = reinterpret_cast<upstartEventData*>(user_data);
396+ delete data;
397+ }); /* user data destroy */
398+
399+ return true;
400+ });
401+ });
402+
403+ return sig_appStopped;
404+}
405+
406+/** Grab the signal object for application failing. If we're not already listing for
407+ those signals this sets up a listener for them. */
408+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, Registry::FailureType>&
409+ Registry::Impl::appFailed(const std::shared_ptr<Registry>& reg)
410+{
411+ std::call_once(flag_appFailed, [reg]() {
412+ reg->impl->thread.executeOnThread<bool>([reg]() {
413+ upstartEventData* data = new upstartEventData{reg};
414+
415+ reg->impl->handle_appFailed = g_dbus_connection_signal_subscribe(
416+ reg->impl->_dbus.get(), /* bus */
417+ nullptr, /* sender */
418+ "com.canonical.UbuntuAppLaunch", /* interface */
419+ "ApplicationFailed", /* signal */
420+ "/", /* path */
421+ nullptr, /* arg0 */
422+ G_DBUS_SIGNAL_FLAGS_NONE,
423+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
424+ gpointer user_data) -> void {
425+ auto data = reinterpret_cast<upstartEventData*>(user_data);
426+ auto reg = data->weakReg.lock();
427+
428+ const gchar* sappid = NULL;
429+ const gchar* typestr = NULL;
430+
431+ Registry::FailureType type = Registry::FailureType::CRASH;
432+ g_variant_get(params, "(&s&s)", &sappid, &typestr);
433+
434+ if (g_strcmp0("crash", typestr) == 0)
435+ {
436+ type = Registry::FailureType::CRASH;
437+ }
438+ else if (g_strcmp0("start-failure", typestr) == 0)
439+ {
440+ type = Registry::FailureType::START_FAILURE;
441+ }
442+ else
443+ {
444+ g_warning("Application failure type '%s' unknown, reporting as a crash", typestr);
445+ }
446+
447+ auto appid = AppID::find(reg, sappid);
448+ auto app = Application::create(appid, reg);
449+
450+ /* TODO: Instance issues */
451+
452+ reg->impl->sig_appFailed(app, {}, type);
453+ }, /* callback */
454+ data, /* user data */
455+ [](gpointer user_data) {
456+ auto data = reinterpret_cast<upstartEventData*>(user_data);
457+ delete data;
458+ }); /* user data destroy */
459+
460+ return true;
461+ });
462+ });
463+
464+ return sig_appFailed;
465+}
466+
467+/** Core handler for pause and resume events. Includes turning the GVariant
468+ pid list into a std::vector and getting the application object. */
469+void Registry::Impl::pauseEventEmitted(
470+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& signal,
471+ const std::shared_ptr<GVariant>& params,
472+ const std::shared_ptr<Registry>& reg)
473+{
474+ std::vector<pid_t> pids;
475+ GVariant* vappid = g_variant_get_child_value(params.get(), 0);
476+ GVariant* vpids = g_variant_get_child_value(params.get(), 1);
477+ guint64 pid;
478+ GVariantIter thispid;
479+ g_variant_iter_init(&thispid, vpids);
480+
481+ while (g_variant_iter_loop(&thispid, "t", &pid))
482+ {
483+ pids.emplace_back(pid);
484+ }
485+
486+ auto cappid = g_variant_get_string(vappid, NULL);
487+ auto appid = ubuntu::app_launch::AppID::find(reg, cappid);
488+ auto app = Application::create(appid, reg);
489+
490+ /* TODO: Instance */
491+ signal(app, {}, pids);
492+
493+ g_variant_unref(vappid);
494+ g_variant_unref(vpids);
495+
496+ return;
497+}
498+
499+/** Grab the signal object for application paused. If we're not already listing for
500+ those signals this sets up a listener for them. */
501+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
502+ Registry::Impl::appPaused(const std::shared_ptr<Registry>& reg)
503+{
504+ std::call_once(flag_appPaused, [&]() {
505+ reg->impl->thread.executeOnThread<bool>([reg]() {
506+ upstartEventData* data = new upstartEventData{reg};
507+
508+ reg->impl->handle_appPaused = g_dbus_connection_signal_subscribe(
509+ reg->impl->_dbus.get(), /* bus */
510+ nullptr, /* sender */
511+ "com.canonical.UbuntuAppLaunch", /* interface */
512+ "ApplicationPaused", /* signal */
513+ "/", /* path */
514+ nullptr, /* arg0 */
515+ G_DBUS_SIGNAL_FLAGS_NONE,
516+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
517+ gpointer user_data) -> void {
518+ auto data = reinterpret_cast<upstartEventData*>(user_data);
519+ auto reg = data->weakReg.lock();
520+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
521+ reg->impl->pauseEventEmitted(reg->impl->sig_appPaused, sparams, reg);
522+ }, /* callback */
523+ data, /* user data */
524+ [](gpointer user_data) {
525+ auto data = reinterpret_cast<upstartEventData*>(user_data);
526+ delete data;
527+ }); /* user data destroy */
528+
529+ return true;
530+ });
531+ });
532+
533+ return sig_appPaused;
534+}
535+
536+/** Grab the signal object for application resumed. If we're not already listing for
537+ those signals this sets up a listener for them. */
538+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
539+ Registry::Impl::appResumed(const std::shared_ptr<Registry>& reg)
540+{
541+ std::call_once(flag_appResumed, [&]() {
542+ reg->impl->thread.executeOnThread<bool>([reg]() {
543+ upstartEventData* data = new upstartEventData{reg};
544+
545+ reg->impl->handle_appResumed = g_dbus_connection_signal_subscribe(
546+ reg->impl->_dbus.get(), /* bus */
547+ nullptr, /* sender */
548+ "com.canonical.UbuntuAppLaunch", /* interface */
549+ "ApplicationResumed", /* signal */
550+ "/", /* path */
551+ nullptr, /* arg0 */
552+ G_DBUS_SIGNAL_FLAGS_NONE,
553+ [](GDBusConnection*, const gchar*, const gchar*, const gchar*, const gchar*, GVariant* params,
554+ gpointer user_data) -> void {
555+ auto data = reinterpret_cast<upstartEventData*>(user_data);
556+ auto reg = data->weakReg.lock();
557+ auto sparams = std::shared_ptr<GVariant>(g_variant_ref(params), g_variant_unref);
558+ reg->impl->pauseEventEmitted(reg->impl->sig_appResumed, sparams, reg);
559+ }, /* callback */
560+ data, /* user data */
561+ [](gpointer user_data) {
562+ auto data = reinterpret_cast<upstartEventData*>(user_data);
563+ delete data;
564+ }); /* user data destroy */
565+
566+ return true;
567+ });
568+ });
569+
570+ return sig_appResumed;
571+}
572+
573 } // namespace app_launch
574 } // namespace ubuntu
575
576=== modified file 'libubuntu-app-launch/registry-impl.h'
577--- libubuntu-app-launch/registry-impl.h 2016-09-23 22:30:51 +0000
578+++ libubuntu-app-launch/registry-impl.h 2016-11-07 20:56:13 +0000
579@@ -53,10 +53,8 @@
580 std::list<AppID::Package> getClickPackages();
581 std::string getClickDir(const std::string& package);
582
583-#if 0
584- void setManager (Registry::Manager* manager);
585- void clearManager ();
586-#endif
587+ static void setManager(std::shared_ptr<Registry::Manager> manager, std::shared_ptr<Registry> registry);
588+ void clearManager();
589
590 /** Shared context thread for events and background tasks
591 that UAL subtasks are doing */
592@@ -82,6 +80,18 @@
593 static std::string printJson(std::shared_ptr<JsonObject> jsonobj);
594 static std::string printJson(std::shared_ptr<JsonNode> jsonnode);
595
596+ /* Signals to discover what is happening to apps */
597+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStarted(
598+ const std::shared_ptr<Registry>& reg);
599+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStopped(
600+ const std::shared_ptr<Registry>& reg);
601+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType>& appFailed(
602+ const std::shared_ptr<Registry>& reg);
603+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& appPaused(
604+ const std::shared_ptr<Registry>& reg);
605+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& appResumed(
606+ const std::shared_ptr<Registry>& reg);
607+
608 /* Signal Hints */
609 /* NOTE: Static because we don't have registry instances in the C
610 code right now. We want these to not be static in the future */
611@@ -89,22 +99,77 @@
612 static bool isWatchingAppStarting();
613
614 private:
615- Registry* _registry;
616-#if 0
617- Registry::Manager* _manager;
618-#endif
619-
620- std::shared_ptr<ClickDB> _clickDB;
621- std::shared_ptr<ClickUser> _clickUser;
622+ Registry* _registry; /**< The Registry that we're spawned from */
623+ std::shared_ptr<Registry::Manager> manager_; /**< Application manager if registered */
624+
625+ std::shared_ptr<ClickDB> _clickDB; /**< Shared instance of the Click Database */
626+ std::shared_ptr<ClickUser> _clickUser; /**< Click database filtered by the current user */
627+
628+ /** Signal object for applications started */
629+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> sig_appStarted;
630+ /** Signal object for applications stopped */
631+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> sig_appStopped;
632+ /** Signal object for applications failed */
633+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType> sig_appFailed;
634+ /** Signal object for applications paused */
635+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>
636+ sig_appPaused;
637+ /** Signal object for applications resumed */
638+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>
639+ sig_appResumed;
640+
641+ guint handle_appStarted{0}; /**< GDBus signal watcher handle for app started signal */
642+ guint handle_appStopped{0}; /**< GDBus signal watcher handle for app stopped signal */
643+ guint handle_appFailed{0}; /**< GDBus signal watcher handle for app failed signal */
644+ guint handle_appPaused{0}; /**< GDBus signal watcher handle for app paused signal */
645+ guint handle_appResumed{0}; /**< GDBus signal watcher handle for app resumed signal */
646+ guint handle_managerSignalFocus{0}; /**< GDBus signal watcher handle for app focused signal */
647+ guint handle_managerSignalResume{0}; /**< GDBus signal watcher handle for app resumed signal */
648+ guint handle_managerSignalStarting{0}; /**< GDBus signal watcher handle for app starting signal */
649+
650+ std::once_flag flag_appStarted; /**< Variable to track to see if signal handlers are installed for application
651+ started */
652+ std::once_flag flag_appStopped; /**< Variable to track to see if signal handlers are installed for application
653+ stopped */
654+ std::once_flag
655+ flag_appFailed; /**< Variable to track to see if signal handlers are installed for application failed */
656+ std::once_flag
657+ flag_appPaused; /**< Variable to track to see if signal handlers are installed for application paused */
658+ std::once_flag flag_appResumed; /**< Variable to track to see if signal handlers are installed for application
659+ resumed */
660+ std::once_flag flag_managerSignals; /**< Variable to track to see if signal handlers are installed for the manager
661+ signals of focused, resumed and starting */
662+
663+ void upstartEventEmitted(core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& signal,
664+ std::shared_ptr<GVariant> params,
665+ const std::shared_ptr<Registry>& reg);
666+ void pauseEventEmitted(
667+ core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>& signal,
668+ const std::shared_ptr<GVariant>& params,
669+ const std::shared_ptr<Registry>& reg);
670+ static std::tuple<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> managerParams(
671+ const std::shared_ptr<GVariant>& params, const std::shared_ptr<Registry>& reg);
672+ static guint managerSignalHelper(const std::shared_ptr<Registry>& reg,
673+ const std::string& signalname,
674+ std::function<void(const std::shared_ptr<Registry>& reg,
675+ const std::shared_ptr<Application>& app,
676+ const std::shared_ptr<Application::Instance>& instance,
677+ const std::shared_ptr<GDBusConnection>&,
678+ const std::string&,
679+ const std::shared_ptr<GVariant>&)> responsefunc);
680
681 void initClick();
682
683+ /** Shared instance of the Zeitgeist Log */
684 std::shared_ptr<ZeitgeistLog> zgLog_;
685
686+ /** Shared connection to CGManager */
687 std::shared_ptr<GDBusConnection> cgManager_;
688
689 void initCGManager();
690
691+ /** All of our icon finders based on the path that they're looking
692+ into */
693 std::unordered_map<std::string, std::shared_ptr<IconFinder>> _iconFinders;
694
695 /** Getting the Upstart job path is relatively expensive in
696
697=== modified file 'libubuntu-app-launch/registry.cpp'
698--- libubuntu-app-launch/registry.cpp 2016-08-26 17:33:34 +0000
699+++ libubuntu-app-launch/registry.cpp 2016-11-07 20:56:13 +0000
700@@ -57,7 +57,7 @@
701 instances.splice(instances.begin(), connection->impl->upstartInstancesForJob("application-snap"));
702
703 /* Remove the instance ID */
704- std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string &instancename) -> std::string {
705+ std::transform(instances.begin(), instances.end(), instances.begin(), [](std::string& instancename) -> std::string {
706 static const std::regex instanceregex("^(.*)-[0-9]*$");
707 std::smatch match;
708 if (std::regex_match(instancename, match, instanceregex))
709@@ -87,7 +87,7 @@
710
711 g_debug("Overall there are %d instances: %s", int(instanceset.size()),
712 std::accumulate(instanceset.begin(), instanceset.end(), std::string{},
713- [](const std::string &instr, std::string instance) {
714+ [](const std::string& instr, std::string instance) {
715 return instr.empty() ? instance : instr + ", " + instance;
716 })
717 .c_str());
718@@ -127,6 +127,16 @@
719 return list;
720 }
721
722+void Registry::setManager(std::shared_ptr<Manager> manager, std::shared_ptr<Registry> registry)
723+{
724+ Registry::Impl::setManager(manager, registry);
725+}
726+
727+void Registry::clearManager()
728+{
729+ impl->clearManager();
730+}
731+
732 std::shared_ptr<Registry> defaultRegistry;
733 std::shared_ptr<Registry> Registry::getDefault()
734 {
735@@ -143,5 +153,35 @@
736 defaultRegistry.reset();
737 }
738
739+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::appStarted(
740+ const std::shared_ptr<Registry>& reg)
741+{
742+ return reg->impl->appStarted(reg);
743+}
744+
745+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& Registry::appStopped(
746+ const std::shared_ptr<Registry>& reg)
747+{
748+ return reg->impl->appStopped(reg);
749+}
750+
751+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, Registry::FailureType>&
752+ Registry::appFailed(const std::shared_ptr<Registry>& reg)
753+{
754+ return reg->impl->appFailed(reg);
755+}
756+
757+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
758+ Registry::appPaused(const std::shared_ptr<Registry>& reg)
759+{
760+ return reg->impl->appPaused(reg);
761+}
762+
763+core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
764+ Registry::appResumed(const std::shared_ptr<Registry>& reg)
765+{
766+ return reg->impl->appResumed(reg);
767+}
768+
769 } // namespace app_launch
770 } // namespace ubuntu
771
772=== modified file 'libubuntu-app-launch/registry.h'
773--- libubuntu-app-launch/registry.h 2016-06-09 14:55:34 +0000
774+++ libubuntu-app-launch/registry.h 2016-11-07 20:56:13 +0000
775@@ -73,28 +73,134 @@
776 */
777 static std::list<std::shared_ptr<Application>> installedApps(std::shared_ptr<Registry> registry = getDefault());
778
779-#if 0 /* TODO -- In next MR */
780 /* Signals to discover what is happening to apps */
781- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appStarted;
782- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appStopped;
783- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType> appFailed;
784- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appPaused;
785- core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>> appResumed;
786-
787- /* The Application Manager, almost always if you're not Unity8, don't
788- use this API. Testing is a special case. */
789+ /** Get the signal object that is signaled when an application has been
790+ started.
791+
792+ \note This singal handler is activated on the UAL thread, if you want
793+ to execute on a different thread you'll need to move the work.
794+
795+ \param reg Registry to get the handler from
796+ */
797+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStarted(
798+ const std::shared_ptr<Registry>& reg = getDefault());
799+
800+ /** Get the signal object that is signaled when an application has stopped.
801+
802+ \note This singal handler is activated on the UAL thread, if you want
803+ to execute on a different thread you'll need to move the work.
804+
805+ \param reg Registry to get the handler from
806+ */
807+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>>& appStopped(
808+ const std::shared_ptr<Registry>& reg = getDefault());
809+
810+ /** Get the signal object that is signaled when an application has failed.
811+
812+ \note This singal handler is activated on the UAL thread, if you want
813+ to execute on a different thread you'll need to move the work.
814+
815+ \param reg Registry to get the handler from
816+ */
817+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, FailureType>& appFailed(
818+ const std::shared_ptr<Registry>& reg = getDefault());
819+
820+ /** Get the signal object that is signaled when an application has been
821+ paused.
822+
823+ \note This singal handler is activated on the UAL thread, if you want
824+ to execute on a different thread you'll need to move the work.
825+
826+ \param reg Registry to get the handler from
827+ */
828+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
829+ appPaused(const std::shared_ptr<Registry>& reg = getDefault());
830+
831+ /** Get the signal object that is signaled when an application has been
832+ resumed.
833+
834+ \note This singal handler is activated on the UAL thread, if you want
835+ to execute on a different thread you'll need to move the work.
836+
837+ \param reg Registry to get the handler from
838+ */
839+ static core::Signal<std::shared_ptr<Application>, std::shared_ptr<Application::Instance>, std::vector<pid_t>&>&
840+ appResumed(const std::shared_ptr<Registry>& reg = getDefault());
841+
842+ /** The Application Manager, almost always if you're not Unity8, don't
843+ use this API. Testing is a special case. Subclass this interface and
844+ implement these functions.
845+
846+ Each function here is being passed a function object that takes a boolean
847+ to reply. This will accept or reject the request. The function object
848+ can be copied to another thread and executed if needed.
849+ */
850 class Manager
851 {
852- virtual bool focusRequest (std::shared_ptr<Application> app, std::shared_ptr<Application::Instance> instance) = 0;
853- virtual bool startingRequest (std::shared_ptr<Application> app, std::shared_ptr<Application::Instance> instance) = 0;
854+ public:
855+ /** Application wishes to startup
856+
857+ \note This singal handler is activated on the UAL thread, if you want
858+ to execute on a different thread you'll need to move the work.
859+
860+ \param app Application requesting startup
861+ \param instance Instance of the app, always valid but not useful
862+ unless mulit-instance app.
863+ \param reply Function object to reply if it is allowed to start
864+ */
865+ virtual void startingRequest(std::shared_ptr<Application> app,
866+ std::shared_ptr<Application::Instance> instance,
867+ std::function<void(bool)> reply) = 0;
868+
869+ /** Application wishes to have focus. Usually this occurs when
870+ a URL for the application is activated and the running app is
871+ requested.
872+
873+ \note This singal handler is activated on the UAL thread, if you want
874+ to execute on a different thread you'll need to move the work.
875+
876+ \param app Application requesting focus
877+ \param instance Instance of the app, always valid but not useful
878+ unless mulit-instance app.
879+ \param reply Function object to reply if it is allowed to focus
880+ */
881+ virtual void focusRequest(std::shared_ptr<Application> app,
882+ std::shared_ptr<Application::Instance> instance,
883+ std::function<void(bool)> reply) = 0;
884+
885+ /** Application wishes to resume. Usually this occurs when
886+ a URL for the application is activated and the running app is
887+ requested.
888+
889+ \note This singal handler is activated on the UAL thread, if you want
890+ to execute on a different thread you'll need to move the work.
891+
892+ \param app Application requesting resume
893+ \param instance Instance of the app, always valid but not useful
894+ unless mulit-instance app.
895+ \param reply Function object to reply if it is allowed to resume
896+ */
897+ virtual void resumeRequest(std::shared_ptr<Application> app,
898+ std::shared_ptr<Application::Instance> instance,
899+ std::function<void(bool)> reply) = 0;
900
901 protected:
902 Manager() = default;
903 };
904
905- void setManager (Manager* manager);
906- void clearManager ();
907-#endif
908+ /** Set the manager of applications, which gives permissions for them to
909+ start and gain focus. In almost all cases this should be Unity8 as it
910+ will be controlling applications.
911+
912+ This function will failure if there is already a manager set.
913+
914+ \param manager A reference to the Manager object to call
915+ \param registry Registry to register the manager on
916+ */
917+ static void setManager(std::shared_ptr<Manager> manager, std::shared_ptr<Registry> registry);
918+
919+ /** Remove the current manager on the registry */
920+ void clearManager();
921
922 /* Helper Lists */
923 /** Get a list of all the helpers for a given helper type
924
925=== modified file 'libubuntu-app-launch/ubuntu-app-launch.cpp'
926--- libubuntu-app-launch/ubuntu-app-launch.cpp 2016-09-14 21:58:39 +0000
927+++ libubuntu-app-launch/ubuntu-app-launch.cpp 2016-11-07 20:56:13 +0000
928@@ -39,6 +39,7 @@
929 #include "appid.h"
930 #include "registry.h"
931 #include "registry-impl.h"
932+#include <algorithm>
933
934 static void free_helper (gpointer value);
935 int kill (pid_t pid, int signal) noexcept;
936@@ -271,155 +272,210 @@
937 gpointer user_data;
938 };
939
940-/* The data we keep for each failed observer */
941-typedef struct _paused_resumed_observer_t paused_resumed_observer_t;
942-struct _paused_resumed_observer_t {
943- GDBusConnection * conn;
944- guint sighandle;
945- UbuntuAppLaunchAppPausedResumedObserver func;
946- gpointer user_data;
947- const gchar * lttng_signal;
948-};
949-
950-/* The lists of Observers */
951-static GList * starting_array = NULL;
952-static GList * started_array = NULL;
953-static GList * stop_array = NULL;
954-static GList * focus_array = NULL;
955-static GList * resume_array = NULL;
956-static GList * failed_array = NULL;
957-static GList * paused_array = NULL;
958-static GList * resumed_array = NULL;
959-
960-static void
961-observer_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
962-{
963- observer_t * observer = (observer_t *)user_data;
964-
965- const gchar * signalname = NULL;
966- g_variant_get_child(params, 0, "&s", &signalname);
967-
968- ual_tracepoint(observer_start, signalname);
969-
970- gchar * env = NULL;
971- GVariant * envs = g_variant_get_child_value(params, 1);
972- GVariantIter iter;
973- g_variant_iter_init(&iter, envs);
974-
975- gboolean job_found = FALSE;
976- gboolean job_legacy = FALSE;
977- gchar * instance = NULL;
978-
979- while (g_variant_iter_loop(&iter, "s", &env)) {
980- if (g_strcmp0(env, "JOB=application-click") == 0) {
981- job_found = TRUE;
982- } else if (g_strcmp0(env, "JOB=application-legacy") == 0) {
983- job_found = TRUE;
984- job_legacy = TRUE;
985- } else if (g_strcmp0(env, "JOB=application-snap") == 0) {
986- job_found = TRUE;
987- job_legacy = TRUE;
988- } else if (g_str_has_prefix(env, "INSTANCE=")) {
989- instance = g_strdup(env + strlen("INSTANCE="));
990- }
991- }
992-
993- g_variant_unref(envs);
994-
995- if (job_legacy && instance != NULL) {
996- gchar * dash = g_strrstr(instance, "-");
997- if (dash != NULL) {
998- dash[0] = '\0';
999- }
1000- }
1001-
1002- if (job_found && instance != NULL) {
1003- observer->func(instance, observer->user_data);
1004- }
1005-
1006- ual_tracepoint(observer_finish, signalname);
1007-
1008- g_free(instance);
1009-}
1010-
1011-/* Creates the observer structure and registers for the signal with
1012- GDBus so that we can get a callback */
1013-static gboolean
1014-add_app_generic (UbuntuAppLaunchAppObserver observer, gpointer user_data, const gchar * signal, GList ** list)
1015-{
1016- GDBusConnection * conn = gdbus_upstart_ref();
1017-
1018- if (conn == NULL) {
1019- return FALSE;
1020- }
1021-
1022- observer_t * observert = g_new0(observer_t, 1);
1023-
1024- observert->conn = conn;
1025- observert->func = observer;
1026- observert->user_data = user_data;
1027-
1028- *list = g_list_prepend(*list, observert);
1029-
1030- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
1031- NULL, /* sender */
1032- DBUS_INTERFACE_UPSTART, /* interface */
1033- "EventEmitted", /* signal */
1034- DBUS_PATH_UPSTART, /* path */
1035- signal, /* arg0 */
1036- G_DBUS_SIGNAL_FLAGS_NONE,
1037- observer_cb,
1038- observert,
1039- NULL); /* user data destroy */
1040-
1041- return TRUE;
1042-}
1043+/* Function to take a work function and have it execute on a given
1044+ GMainContext */
1045+static void executeOnContext (std::shared_ptr<GMainContext> context, std::function<void()> work)
1046+{
1047+ if (!context) {
1048+ work();
1049+ return;
1050+ }
1051+
1052+ auto heapWork = new std::function<void()>(work);
1053+
1054+ auto source = std::shared_ptr<GSource>(g_idle_source_new(), [](GSource* src) { g_clear_pointer(&src, g_source_unref); });
1055+ g_source_set_callback(source.get(),
1056+ [](gpointer data) {
1057+ auto heapWork = static_cast<std::function<void()>*>(data);
1058+ (*heapWork)();
1059+ return G_SOURCE_REMOVE;
1060+ },
1061+ heapWork,
1062+ [](gpointer data) {
1063+ auto heapWork = static_cast<std::function<void()>*>(data);
1064+ delete heapWork;
1065+ });
1066+
1067+ g_source_attach(source.get(), context.get());
1068+}
1069+
1070+/* Map of all the observers listening for app started */
1071+static std::map<std::pair<UbuntuAppLaunchAppObserver, gpointer>, core::ScopedConnection> appStartedObservers;
1072
1073 gboolean
1074 ubuntu_app_launch_observer_add_app_started (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1075 {
1076- return add_app_generic(observer, user_data, "started", &started_array);
1077-}
1078+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
1079+
1080+ appStartedObservers.emplace(std::make_pair(
1081+ std::make_pair(observer, user_data),
1082+ core::ScopedConnection(
1083+ ubuntu::app_launch::Registry::appStarted().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
1084+ std::string appid = app->appId();
1085+ executeOnContext(context, [appid, observer, user_data]() {
1086+ observer(appid.c_str(), user_data);
1087+ });
1088+ })
1089+ )
1090+ ));
1091+
1092+ return TRUE;
1093+}
1094+
1095+gboolean
1096+ubuntu_app_launch_observer_delete_app_started (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1097+{
1098+ auto iter = appStartedObservers.find(std::make_pair(observer, user_data));
1099+
1100+ if (iter == appStartedObservers.end()) {
1101+ return FALSE;
1102+ }
1103+
1104+ appStartedObservers.erase(iter);
1105+ return TRUE;
1106+}
1107+
1108+/* Map of all the observers listening for app stopped */
1109+static std::map<std::pair<UbuntuAppLaunchAppObserver, gpointer>, core::ScopedConnection> appStoppedObservers;
1110
1111 gboolean
1112 ubuntu_app_launch_observer_add_app_stop (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1113 {
1114- return add_app_generic(observer, user_data, "stopped", &stop_array);
1115+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
1116+
1117+ appStoppedObservers.emplace(std::make_pair(
1118+ std::make_pair(observer, user_data),
1119+ core::ScopedConnection(
1120+ ubuntu::app_launch::Registry::appStopped().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
1121+ std::string appid = app->appId();
1122+ executeOnContext(context, [appid, observer, user_data]() {
1123+ observer(appid.c_str(), user_data);
1124+ });
1125+ })
1126+ )
1127+ ));
1128+
1129+ return TRUE;
1130 }
1131
1132-/* Creates the observer structure and registers for the signal with
1133- GDBus so that we can get a callback */
1134-static gboolean
1135-add_session_generic (UbuntuAppLaunchAppObserver observer, gpointer user_data, const gchar * signal, GList ** list, GDBusSignalCallback session_cb)
1136+gboolean
1137+ubuntu_app_launch_observer_delete_app_stop (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1138 {
1139- GDBusConnection * conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1140+ auto iter = appStoppedObservers.find(std::make_pair(observer, user_data));
1141
1142- if (conn == NULL) {
1143+ if (iter == appStoppedObservers.end()) {
1144 return FALSE;
1145 }
1146
1147- observer_t * observert = g_new0(observer_t, 1);
1148-
1149- observert->conn = conn;
1150- observert->func = observer;
1151- observert->user_data = user_data;
1152-
1153- *list = g_list_prepend(*list, observert);
1154-
1155- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
1156- NULL, /* sender */
1157- "com.canonical.UbuntuAppLaunch", /* interface */
1158- signal, /* signal */
1159- "/", /* path */
1160- NULL, /* arg0 */
1161- G_DBUS_SIGNAL_FLAGS_NONE,
1162- session_cb,
1163- observert,
1164- NULL); /* user data destroy */
1165-
1166+ appStoppedObservers.erase(iter);
1167 return TRUE;
1168 }
1169
1170+class CManager : public ubuntu::app_launch::Registry::Manager
1171+{
1172+public:
1173+ CManager () {
1174+ g_debug("Creating the CManager object");
1175+ }
1176+
1177+ void startingRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
1178+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
1179+ std::function<void(bool)> reply) override {
1180+ std::string sappid = app->appId();
1181+ g_debug("CManager starting: %s", sappid.c_str());
1182+
1183+ for (const auto &data : startingList) {
1184+ executeOnContext(data.context, [data, sappid]() {
1185+ data.observer(sappid.c_str(), data.user_data);
1186+ });
1187+ }
1188+
1189+ reply(true);
1190+ }
1191+
1192+ void focusRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
1193+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
1194+ std::function<void(bool)> reply) override {
1195+ std::string sappid = app->appId();
1196+ g_debug("CManager focus: %s", sappid.c_str());
1197+
1198+ for (const auto &data : focusList) {
1199+ executeOnContext(data.context, [data, sappid]() {
1200+ data.observer(sappid.c_str(), data.user_data);
1201+ });
1202+ }
1203+
1204+ reply(true);
1205+ }
1206+
1207+ void resumeRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
1208+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
1209+ std::function<void(bool)> reply) override {
1210+ std::string sappid = app->appId();
1211+ g_debug("CManager resume: %s", sappid.c_str());
1212+
1213+ for (const auto &data : resumeList) {
1214+ executeOnContext(data.context, [data, sappid]() {
1215+ data.observer(sappid.c_str(), data.user_data);
1216+ });
1217+ }
1218+
1219+ reply(true);
1220+ }
1221+
1222+private:
1223+ struct ObserverData {
1224+ UbuntuAppLaunchAppObserver observer;
1225+ gpointer user_data;
1226+ std::shared_ptr<GMainContext> context;
1227+
1228+ ObserverData(UbuntuAppLaunchAppObserver obs, gpointer ud)
1229+ : observer(obs)
1230+ , user_data(ud) {
1231+ context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
1232+ }
1233+ };
1234+
1235+ std::list<ObserverData> focusList;
1236+ std::list<ObserverData> resumeList;
1237+ std::list<ObserverData> startingList;
1238+
1239+ bool removeList (std::list<ObserverData> &list, UbuntuAppLaunchAppObserver observer, gpointer user_data) {
1240+ auto iter = std::find_if(list.begin(), list.end(), [observer, user_data](const ObserverData &data) {
1241+ return data.observer == observer && data.user_data == user_data;
1242+ });
1243+
1244+ if (iter == list.end()) {
1245+ return false;
1246+ }
1247+
1248+ list.erase(iter);
1249+ return true;
1250+ }
1251+
1252+public:
1253+ void addFocus (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
1254+ focusList.emplace_back(ObserverData(observer, user_data));
1255+ }
1256+ void addResume (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
1257+ resumeList.emplace_back(ObserverData(observer, user_data));
1258+ }
1259+ void addStarting (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
1260+ startingList.emplace_back(ObserverData(observer, user_data));
1261+ }
1262+ bool deleteFocus (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
1263+ return removeList(focusList, observer, user_data);
1264+ }
1265+ bool deleteResume (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
1266+ return removeList(resumeList, observer, user_data);
1267+ }
1268+ bool deleteStarting (UbuntuAppLaunchAppObserver observer, gpointer user_data) {
1269+ return removeList(startingList, observer, user_data);
1270+ }
1271+};
1272+
1273+static std::weak_ptr<CManager> cmanager;
1274+
1275 /* Generic handler for a bunch of our signals */
1276 static inline void
1277 generic_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
1278@@ -433,328 +489,158 @@
1279 }
1280 }
1281
1282-/* Handle the focus signal when it occurs, call the observer */
1283-static void
1284-focus_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
1285-{
1286- ual_tracepoint(observer_start, "focus");
1287-
1288- generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
1289-
1290- ual_tracepoint(observer_finish, "focus");
1291-}
1292-
1293 gboolean
1294 ubuntu_app_launch_observer_add_app_focus (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1295 {
1296- return add_session_generic(observer, user_data, "UnityFocusRequest", &focus_array, focus_signal_cb);
1297+ auto manager = cmanager.lock();
1298+
1299+ if (!manager) {
1300+ manager = std::make_shared<CManager>();
1301+ ubuntu::app_launch::Registry::setManager(manager, ubuntu::app_launch::Registry::getDefault());
1302+ cmanager = manager;
1303+ }
1304+
1305+ manager->addFocus(observer, user_data);
1306+ return TRUE;
1307 }
1308
1309-/* Handle the resume signal when it occurs, call the observer, then send a signal back when we're done */
1310-static void
1311-resume_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
1312+gboolean
1313+ubuntu_app_launch_observer_delete_app_focus (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1314 {
1315- ual_tracepoint(observer_start, "resume");
1316-
1317- generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
1318-
1319- GError * error = NULL;
1320- g_dbus_connection_emit_signal(conn,
1321- sender, /* destination */
1322- "/", /* path */
1323- "com.canonical.UbuntuAppLaunch", /* interface */
1324- "UnityResumeResponse", /* signal */
1325- params, /* params, the same */
1326- &error);
1327-
1328- if (error != NULL) {
1329- g_warning("Unable to emit response signal: %s", error->message);
1330- g_error_free(error);
1331+ auto manager = cmanager.lock();
1332+
1333+ if (!manager) {
1334+ return FALSE;
1335 }
1336
1337- ual_tracepoint(observer_finish, "resume");
1338+ return manager->deleteFocus(observer, user_data) ? TRUE : FALSE;
1339 }
1340
1341 gboolean
1342 ubuntu_app_launch_observer_add_app_resume (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1343 {
1344- return add_session_generic(observer, user_data, "UnityResumeRequest", &resume_array, resume_signal_cb);
1345+ auto manager = cmanager.lock();
1346+
1347+ if (!manager) {
1348+ manager = std::make_shared<CManager>();
1349+ ubuntu::app_launch::Registry::setManager(manager, ubuntu::app_launch::Registry::getDefault());
1350+ cmanager = manager;
1351+ }
1352+
1353+ manager->addResume(observer, user_data);
1354+ return TRUE;
1355 }
1356
1357-/* Handle the starting signal when it occurs, call the observer, then send a signal back when we're done */
1358-static void
1359-starting_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
1360+gboolean
1361+ubuntu_app_launch_observer_delete_app_resume (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1362 {
1363- ual_tracepoint(observer_start, "starting");
1364-
1365- generic_signal_cb(conn, sender, object, interface, signal, params, user_data);
1366-
1367- GError * error = NULL;
1368- g_dbus_connection_emit_signal(conn,
1369- sender, /* destination */
1370- "/", /* path */
1371- "com.canonical.UbuntuAppLaunch", /* interface */
1372- "UnityStartingSignal", /* signal */
1373- params, /* params, the same */
1374- &error);
1375-
1376- if (error != NULL) {
1377- g_warning("Unable to emit response signal: %s", error->message);
1378- g_error_free(error);
1379+ auto manager = cmanager.lock();
1380+
1381+ if (!manager) {
1382+ return FALSE;
1383 }
1384
1385- ual_tracepoint(observer_finish, "starting");
1386+ return manager->deleteResume(observer, user_data) ? TRUE : FALSE;
1387 }
1388
1389 gboolean
1390 ubuntu_app_launch_observer_add_app_starting (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1391 {
1392+ auto manager = cmanager.lock();
1393+
1394+ if (!manager) {
1395+ manager = std::make_shared<CManager>();
1396+ ubuntu::app_launch::Registry::setManager(manager, ubuntu::app_launch::Registry::getDefault());
1397+ cmanager = manager;
1398+ }
1399+
1400 ubuntu::app_launch::Registry::Impl::watchingAppStarting(true);
1401- return add_session_generic(observer, user_data, "UnityStartingBroadcast", &starting_array, starting_signal_cb);
1402-}
1403-
1404-/* Handle the failed signal when it occurs, call the observer */
1405-static void
1406-failed_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
1407-{
1408- failed_observer_t * observer = (failed_observer_t *)user_data;
1409- const gchar * appid = NULL;
1410- const gchar * typestr = NULL;
1411-
1412- ual_tracepoint(observer_start, "failed");
1413-
1414- if (observer->func != NULL) {
1415- UbuntuAppLaunchAppFailed type = UBUNTU_APP_LAUNCH_APP_FAILED_CRASH;
1416- g_variant_get(params, "(&s&s)", &appid, &typestr);
1417-
1418- if (g_strcmp0("crash", typestr) == 0) {
1419- type = UBUNTU_APP_LAUNCH_APP_FAILED_CRASH;
1420- } else if (g_strcmp0("start-failure", typestr) == 0) {
1421- type = UBUNTU_APP_LAUNCH_APP_FAILED_START_FAILURE;
1422- } else {
1423- g_warning("Application failure type '%s' unknown, reporting as a crash", typestr);
1424- }
1425-
1426- observer->func(appid, type, observer->user_data);
1427- }
1428-
1429- ual_tracepoint(observer_finish, "failed");
1430-}
1431-
1432-gboolean
1433-ubuntu_app_launch_observer_add_app_failed (UbuntuAppLaunchAppFailedObserver observer, gpointer user_data)
1434-{
1435- GDBusConnection * conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1436-
1437- if (conn == NULL) {
1438- return FALSE;
1439- }
1440-
1441- failed_observer_t * observert = g_new0(failed_observer_t, 1);
1442-
1443- observert->conn = conn;
1444- observert->func = observer;
1445- observert->user_data = user_data;
1446-
1447- failed_array = g_list_prepend(failed_array, observert);
1448-
1449- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
1450- NULL, /* sender */
1451- "com.canonical.UbuntuAppLaunch", /* interface */
1452- "ApplicationFailed", /* signal */
1453- "/", /* path */
1454- NULL, /* arg0 */
1455- G_DBUS_SIGNAL_FLAGS_NONE,
1456- failed_signal_cb,
1457- observert,
1458- NULL); /* user data destroy */
1459-
1460- return TRUE;
1461-}
1462-
1463-/* Handle the paused signal when it occurs, call the observer */
1464-static void
1465-paused_signal_cb (GDBusConnection * conn, const gchar * sender, const gchar * object, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
1466-{
1467- paused_resumed_observer_t * observer = (paused_resumed_observer_t *)user_data;
1468-
1469- ual_tracepoint(observer_start, observer->lttng_signal);
1470-
1471- if (observer->func != NULL) {
1472- GArray * pidarray = g_array_new(TRUE, TRUE, sizeof(GPid));
1473- GVariant * appid = g_variant_get_child_value(params, 0);
1474- GVariant * pids = g_variant_get_child_value(params, 1);
1475- guint64 pid;
1476- GVariantIter thispid;
1477- g_variant_iter_init(&thispid, pids);
1478-
1479- while (g_variant_iter_loop(&thispid, "t", &pid)) {
1480- GPid gpid = (GPid)pid; /* Should be a no-op for most architectures, but just in case */
1481- g_array_append_val(pidarray, gpid);
1482- }
1483-
1484- observer->func(g_variant_get_string(appid, NULL), (GPid *)pidarray->data, observer->user_data);
1485-
1486- g_array_free(pidarray, TRUE);
1487- g_variant_unref(appid);
1488- g_variant_unref(pids);
1489- }
1490-
1491- ual_tracepoint(observer_finish, observer->lttng_signal);
1492-}
1493-
1494-static gboolean
1495-paused_resumed_generic (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data, GList ** queue, const gchar * signal_name, const gchar * lttng_signal)
1496-{
1497- GDBusConnection * conn = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
1498-
1499- if (conn == NULL) {
1500- return FALSE;
1501- }
1502-
1503- paused_resumed_observer_t * observert = g_new0(paused_resumed_observer_t, 1);
1504-
1505- observert->conn = conn;
1506- observert->func = observer;
1507- observert->user_data = user_data;
1508- observert->lttng_signal = lttng_signal;
1509-
1510- *queue = g_list_prepend(*queue, observert);
1511-
1512- observert->sighandle = g_dbus_connection_signal_subscribe(conn,
1513- NULL, /* sender */
1514- "com.canonical.UbuntuAppLaunch", /* interface */
1515- signal_name, /* signal */
1516- "/", /* path */
1517- NULL, /* arg0 */
1518- G_DBUS_SIGNAL_FLAGS_NONE,
1519- paused_signal_cb,
1520- observert,
1521- NULL); /* user data destroy */
1522-
1523- return TRUE;
1524-}
1525-
1526-gboolean
1527-ubuntu_app_launch_observer_add_app_paused (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
1528-{
1529- return paused_resumed_generic(observer, user_data, &paused_array, "ApplicationPaused", "paused");
1530-}
1531-
1532-gboolean
1533-ubuntu_app_launch_observer_add_app_resumed (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
1534-{
1535- return paused_resumed_generic(observer, user_data, &resumed_array, "ApplicationResumed", "resumed");
1536-}
1537-
1538-static gboolean
1539-delete_app_generic (UbuntuAppLaunchAppObserver observer, gpointer user_data, GList ** list)
1540-{
1541- observer_t * observert = NULL;
1542- GList * look;
1543-
1544- for (look = *list; look != NULL; look = g_list_next(look)) {
1545- observert = (observer_t *)look->data;
1546-
1547- if (observert->func == observer && observert->user_data == user_data) {
1548- break;
1549- }
1550- }
1551-
1552- if (look == NULL) {
1553- return FALSE;
1554- }
1555-
1556- g_dbus_connection_signal_unsubscribe(observert->conn, observert->sighandle);
1557- g_object_unref(observert->conn);
1558-
1559- g_free(observert);
1560- *list = g_list_delete_link(*list, look);
1561-
1562- return TRUE;
1563-}
1564-
1565-gboolean
1566-ubuntu_app_launch_observer_delete_app_started (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1567-{
1568- return delete_app_generic(observer, user_data, &started_array);
1569-}
1570-
1571-gboolean
1572-ubuntu_app_launch_observer_delete_app_stop (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1573-{
1574- return delete_app_generic(observer, user_data, &stop_array);
1575-}
1576-
1577-gboolean
1578-ubuntu_app_launch_observer_delete_app_resume (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1579-{
1580- return delete_app_generic(observer, user_data, &resume_array);
1581-}
1582-
1583-gboolean
1584-ubuntu_app_launch_observer_delete_app_focus (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1585-{
1586- return delete_app_generic(observer, user_data, &focus_array);
1587+ manager->addStarting(observer, user_data);
1588+ return TRUE;
1589 }
1590
1591 gboolean
1592 ubuntu_app_launch_observer_delete_app_starting (UbuntuAppLaunchAppObserver observer, gpointer user_data)
1593 {
1594+ auto manager = cmanager.lock();
1595+
1596+ if (!manager) {
1597+ return FALSE;
1598+ }
1599+
1600 ubuntu::app_launch::Registry::Impl::watchingAppStarting(false);
1601- return delete_app_generic(observer, user_data, &starting_array);
1602+ return manager->deleteStarting(observer, user_data) ? TRUE : FALSE;
1603+}
1604+
1605+/* Map of all the observers listening for app stopped */
1606+static std::map<std::pair<UbuntuAppLaunchAppFailedObserver, gpointer>, core::ScopedConnection> appFailedObservers;
1607+
1608+gboolean
1609+ubuntu_app_launch_observer_add_app_failed (UbuntuAppLaunchAppFailedObserver observer, gpointer user_data)
1610+{
1611+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
1612+
1613+ appFailedObservers.emplace(std::make_pair(
1614+ std::make_pair(observer, user_data),
1615+ core::ScopedConnection(
1616+ ubuntu::app_launch::Registry::appFailed().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance, ubuntu::app_launch::Registry::FailureType type) {
1617+ std::string appid = app->appId();
1618+ executeOnContext(context, [appid, type, observer, user_data]() {
1619+ UbuntuAppLaunchAppFailed ctype;
1620+
1621+ switch (type) {
1622+ case ubuntu::app_launch::Registry::FailureType::CRASH:
1623+ ctype = UBUNTU_APP_LAUNCH_APP_FAILED_CRASH;
1624+ break;
1625+ case ubuntu::app_launch::Registry::FailureType::START_FAILURE:
1626+ ctype = UBUNTU_APP_LAUNCH_APP_FAILED_START_FAILURE;
1627+ break;
1628+ }
1629+
1630+ observer(appid.c_str(), ctype, user_data);
1631+ });
1632+ })
1633+ )
1634+ ));
1635+
1636+ return TRUE;
1637 }
1638
1639 gboolean
1640 ubuntu_app_launch_observer_delete_app_failed (UbuntuAppLaunchAppFailedObserver observer, gpointer user_data)
1641 {
1642- failed_observer_t * observert = NULL;
1643- GList * look;
1644-
1645- for (look = failed_array; look != NULL; look = g_list_next(look)) {
1646- observert = (failed_observer_t *)look->data;
1647-
1648- if (observert->func == observer && observert->user_data == user_data) {
1649- break;
1650- }
1651- }
1652-
1653- if (look == NULL) {
1654+ auto iter = appFailedObservers.find(std::make_pair(observer, user_data));
1655+
1656+ if (iter == appFailedObservers.end()) {
1657 return FALSE;
1658 }
1659
1660- g_dbus_connection_signal_unsubscribe(observert->conn, observert->sighandle);
1661- g_object_unref(observert->conn);
1662-
1663- g_free(observert);
1664- failed_array = g_list_delete_link(failed_array, look);
1665-
1666+ appFailedObservers.erase(iter);
1667 return TRUE;
1668 }
1669
1670-static gboolean
1671-paused_resumed_delete (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data, GList ** list)
1672+static std::map<std::pair<UbuntuAppLaunchAppPausedResumedObserver, gpointer>, core::ScopedConnection> appPausedObservers;
1673+
1674+gboolean
1675+ubuntu_app_launch_observer_add_app_paused (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
1676 {
1677- paused_resumed_observer_t * observert = NULL;
1678- GList * look;
1679-
1680- for (look = *list; look != NULL; look = g_list_next(look)) {
1681- observert = (paused_resumed_observer_t *)look->data;
1682-
1683- if (observert->func == observer && observert->user_data == user_data) {
1684- break;
1685- }
1686- }
1687-
1688- if (look == NULL) {
1689- return FALSE;
1690- }
1691-
1692- g_dbus_connection_signal_unsubscribe(observert->conn, observert->sighandle);
1693- g_object_unref(observert->conn);
1694-
1695- g_free(observert);
1696- *list = g_list_delete_link(*list, look);
1697+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
1698+
1699+ appPausedObservers.emplace(std::make_pair(
1700+ std::make_pair(observer, user_data),
1701+ core::ScopedConnection(
1702+ ubuntu::app_launch::Registry::appPaused().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance, std::vector<pid_t> &pids) {
1703+ std::vector<pid_t> lpids = pids;
1704+ lpids.emplace_back(0);
1705+
1706+ std::string appid = app->appId();
1707+
1708+ executeOnContext(context, [appid, observer, user_data, lpids]() {
1709+ observer(appid.c_str(), (int *)(lpids.data()), user_data);
1710+ });
1711+ })
1712+ )
1713+ ));
1714
1715 return TRUE;
1716 }
1717@@ -762,13 +648,53 @@
1718 gboolean
1719 ubuntu_app_launch_observer_delete_app_paused (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
1720 {
1721- return paused_resumed_delete(observer, user_data, &paused_array);
1722+ auto iter = appPausedObservers.find(std::make_pair(observer, user_data));
1723+
1724+ if (iter == appPausedObservers.end()) {
1725+ return FALSE;
1726+ }
1727+
1728+ appPausedObservers.erase(iter);
1729+ return TRUE;
1730+}
1731+
1732+static std::map<std::pair<UbuntuAppLaunchAppPausedResumedObserver, gpointer>, core::ScopedConnection> appResumedObservers;
1733+
1734+gboolean
1735+ubuntu_app_launch_observer_add_app_resumed (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
1736+{
1737+ auto context = std::shared_ptr<GMainContext>(g_main_context_ref_thread_default(), [](GMainContext * context) { g_clear_pointer(&context, g_main_context_unref); });
1738+
1739+ appResumedObservers.emplace(std::make_pair(
1740+ std::make_pair(observer, user_data),
1741+ core::ScopedConnection(
1742+ ubuntu::app_launch::Registry::appResumed().connect([context, observer, user_data](std::shared_ptr<ubuntu::app_launch::Application> app, std::shared_ptr<ubuntu::app_launch::Application::Instance> instance, std::vector<pid_t>& pids) {
1743+ std::vector<pid_t> lpids = pids;
1744+ lpids.emplace_back(0);
1745+
1746+ std::string appid = app->appId();
1747+
1748+ executeOnContext(context, [appid, observer, user_data, lpids]() {
1749+ observer(appid.c_str(), (int *)(lpids.data()), user_data);
1750+ });
1751+ })
1752+ )
1753+ ));
1754+
1755+ return TRUE;
1756 }
1757
1758 gboolean
1759 ubuntu_app_launch_observer_delete_app_resumed (UbuntuAppLaunchAppPausedResumedObserver observer, gpointer user_data)
1760 {
1761- return paused_resumed_delete(observer, user_data, &resumed_array);
1762+ auto iter = appResumedObservers.find(std::make_pair(observer, user_data));
1763+
1764+ if (iter == appResumedObservers.end()) {
1765+ return FALSE;
1766+ }
1767+
1768+ appResumedObservers.erase(iter);
1769+ return TRUE;
1770 }
1771
1772 typedef void (*per_instance_func_t) (GDBusConnection * con, GVariant * prop_dict, gpointer user_data);
1773
1774=== modified file 'tests/CMakeLists.txt'
1775--- tests/CMakeLists.txt 2016-09-14 16:43:36 +0000
1776+++ tests/CMakeLists.txt 2016-11-07 20:56:13 +0000
1777@@ -157,6 +157,7 @@
1778
1779 add_custom_target(format-tests
1780 COMMAND clang-format -i -style=file
1781+ failure-test.cc
1782 application-info-desktop.cpp
1783 libual-cpp-test.cc
1784 list-apps.cpp
1785
1786=== modified file 'tests/failure-test.cc'
1787--- tests/failure-test.cc 2016-08-25 18:13:44 +0000
1788+++ tests/failure-test.cc 2016-11-07 20:56:13 +0000
1789@@ -17,120 +17,147 @@
1790 * Ted Gould <ted.gould@canonical.com>
1791 */
1792
1793+#include "eventually-fixture.h"
1794+#include "registry.h"
1795+#include <gio/gio.h>
1796+#include <glib/gstdio.h>
1797 #include <gtest/gtest.h>
1798-#include <glib/gstdio.h>
1799-#include <gio/gio.h>
1800-#include <ubuntu-app-launch.h>
1801-#include "eventually-fixture.h"
1802
1803 class FailureTest : public EventuallyFixture
1804 {
1805- private:
1806- GTestDBus * testbus = NULL;
1807-
1808- protected:
1809- virtual void SetUp() {
1810- testbus = g_test_dbus_new(G_TEST_DBUS_NONE);
1811- g_test_dbus_up(testbus);
1812- }
1813-
1814- virtual void TearDown() {
1815- g_test_dbus_down(testbus);
1816- g_clear_object(&testbus);
1817- }
1818+private:
1819+ GTestDBus* testbus = NULL;
1820+
1821+protected:
1822+ std::shared_ptr<ubuntu::app_launch::Registry> registry;
1823+
1824+ virtual void SetUp()
1825+ {
1826+ /* Click DB test mode */
1827+ g_setenv("TEST_CLICK_DB", "click-db-dir", TRUE);
1828+ g_setenv("TEST_CLICK_USER", "test-user", TRUE);
1829+
1830+ gchar* linkfarmpath = g_build_filename(CMAKE_SOURCE_DIR, "link-farm", NULL);
1831+ g_setenv("UBUNTU_APP_LAUNCH_LINK_FARM", linkfarmpath, TRUE);
1832+ g_free(linkfarmpath);
1833+
1834+ g_setenv("XDG_DATA_DIRS", CMAKE_SOURCE_DIR, TRUE);
1835+ g_setenv("XDG_CACHE_HOME", CMAKE_SOURCE_DIR "/libertine-data", TRUE);
1836+ g_setenv("XDG_DATA_HOME", CMAKE_SOURCE_DIR "/libertine-home", TRUE);
1837+
1838+ testbus = g_test_dbus_new(G_TEST_DBUS_NONE);
1839+ g_test_dbus_up(testbus);
1840+
1841+ registry = std::make_shared<ubuntu::app_launch::Registry>();
1842+ }
1843+
1844+ virtual void TearDown()
1845+ {
1846+ registry.reset();
1847+
1848+ g_test_dbus_down(testbus);
1849+ g_clear_object(&testbus);
1850+ }
1851 };
1852
1853-static void
1854-failed_observer (const gchar * appid, UbuntuAppLaunchAppFailed reason, gpointer user_data)
1855-{
1856- if (reason == UBUNTU_APP_LAUNCH_APP_FAILED_CRASH) {
1857- std::string * last = static_cast<std::string *>(user_data);
1858- *last = appid;
1859- }
1860-}
1861-
1862 TEST_F(FailureTest, CrashTest)
1863 {
1864- g_setenv("EXIT_STATUS", "-100", TRUE);
1865- g_setenv("JOB", "application-click", TRUE);
1866- g_setenv("INSTANCE", "foo", TRUE);
1867-
1868- std::string last_observer;
1869- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
1870-
1871- /* Status based */
1872- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1873-
1874- EXPECT_EVENTUALLY_EQ("foo", last_observer);
1875-
1876- last_observer.clear();
1877- g_unsetenv("EXIT_STATUS");
1878- g_setenv("EXIT_SIGNAL", "KILL", TRUE);
1879-
1880- /* Signal based */
1881- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1882-
1883- EXPECT_EVENTUALLY_EQ("foo", last_observer);
1884-
1885- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
1886+ g_setenv("EXIT_STATUS", "-100", TRUE);
1887+ g_setenv("JOB", "application-click", TRUE);
1888+ g_setenv("INSTANCE", "foo", TRUE);
1889+
1890+ std::string last_observer;
1891+ ubuntu::app_launch::Registry::appFailed(registry).connect(
1892+ [&last_observer](std::shared_ptr<ubuntu::app_launch::Application> app,
1893+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
1894+ ubuntu::app_launch::Registry::FailureType type) {
1895+ if (type == ubuntu::app_launch::Registry::FailureType::CRASH)
1896+ {
1897+ last_observer = app->appId();
1898+ }
1899+ });
1900+
1901+ /* Status based */
1902+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1903+
1904+ EXPECT_EVENTUALLY_EQ("foo", last_observer);
1905+
1906+ last_observer.clear();
1907+ g_unsetenv("EXIT_STATUS");
1908+ g_setenv("EXIT_SIGNAL", "KILL", TRUE);
1909+
1910+ /* Signal based */
1911+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1912+
1913+ EXPECT_EVENTUALLY_EQ("foo", last_observer);
1914 }
1915
1916 TEST_F(FailureTest, LegacyTest)
1917 {
1918- g_setenv("EXIT_STATUS", "-100", TRUE);
1919- g_setenv("JOB", "application-legacy", TRUE);
1920- g_setenv("INSTANCE", "foo-1234", TRUE);
1921-
1922- std::string last_observer;
1923- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
1924-
1925- /* Status based */
1926- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1927-
1928- EXPECT_EVENTUALLY_EQ("foo", last_observer);
1929-
1930- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
1931+ g_setenv("EXIT_STATUS", "-100", TRUE);
1932+ g_setenv("JOB", "application-legacy", TRUE);
1933+ g_setenv("INSTANCE", "foo-1234", TRUE);
1934+
1935+ std::string last_observer;
1936+ ubuntu::app_launch::Registry::appFailed(registry).connect(
1937+ [&last_observer](std::shared_ptr<ubuntu::app_launch::Application> app,
1938+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
1939+ ubuntu::app_launch::Registry::FailureType type) {
1940+ g_debug("Signal handler called");
1941+ if (type == ubuntu::app_launch::Registry::FailureType::CRASH)
1942+ {
1943+ last_observer = app->appId();
1944+ }
1945+ });
1946+
1947+ /* Status based */
1948+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1949+
1950+ EXPECT_EVENTUALLY_EQ("foo", last_observer);
1951 }
1952
1953 TEST_F(FailureTest, SnapTest)
1954 {
1955- g_setenv("EXIT_STATUS", "-100", TRUE);
1956- g_setenv("JOB", "application-snap", TRUE);
1957- g_setenv("INSTANCE", "foo_bar_x123-1234", TRUE);
1958-
1959- std::string last_observer;
1960- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
1961-
1962- /* Status based */
1963- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1964-
1965- EXPECT_EVENTUALLY_EQ("foo_bar_x123", last_observer);
1966-
1967- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
1968-}
1969-
1970-static void
1971-failed_start_observer (const gchar * appid, UbuntuAppLaunchAppFailed reason, gpointer user_data)
1972-{
1973- if (reason == UBUNTU_APP_LAUNCH_APP_FAILED_START_FAILURE) {
1974- std::string * last = static_cast<std::string *>(user_data);
1975- *last = appid;
1976- }
1977+ g_setenv("EXIT_STATUS", "-100", TRUE);
1978+ g_setenv("JOB", "application-snap", TRUE);
1979+ g_setenv("INSTANCE", "com.test.good_application_1.2.3-1234", TRUE);
1980+
1981+ std::string last_observer;
1982+ ubuntu::app_launch::Registry::appFailed(registry).connect(
1983+ [&last_observer](std::shared_ptr<ubuntu::app_launch::Application> app,
1984+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
1985+ ubuntu::app_launch::Registry::FailureType type) {
1986+ if (type == ubuntu::app_launch::Registry::FailureType::CRASH)
1987+ {
1988+ last_observer = app->appId();
1989+ }
1990+ });
1991+
1992+ /* Status based */
1993+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
1994+
1995+ EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", last_observer);
1996 }
1997
1998 TEST_F(FailureTest, StartTest)
1999 {
2000- g_setenv("JOB", "application-click", TRUE);
2001- g_setenv("INSTANCE", "foo", TRUE);
2002- g_unsetenv("EXIT_STATUS");
2003- g_unsetenv("EXIT_SIGNAL");
2004-
2005- std::string last_observer;
2006- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_start_observer, &last_observer));
2007-
2008- ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
2009-
2010- EXPECT_EVENTUALLY_EQ("foo", last_observer);
2011-
2012- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_start_observer, &last_observer));
2013+ g_setenv("JOB", "application-click", TRUE);
2014+ g_setenv("INSTANCE", "foo", TRUE);
2015+ g_unsetenv("EXIT_STATUS");
2016+ g_unsetenv("EXIT_SIGNAL");
2017+
2018+ std::string last_observer;
2019+ ubuntu::app_launch::Registry::appFailed(registry).connect(
2020+ [&last_observer](std::shared_ptr<ubuntu::app_launch::Application> app,
2021+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
2022+ ubuntu::app_launch::Registry::FailureType type) {
2023+ if (type == ubuntu::app_launch::Registry::FailureType::START_FAILURE)
2024+ {
2025+ last_observer = app->appId();
2026+ }
2027+ });
2028+
2029+ ASSERT_TRUE(g_spawn_command_line_sync(APP_FAILED_TOOL, NULL, NULL, NULL, NULL));
2030+
2031+ EXPECT_EVENTUALLY_EQ("foo", last_observer);
2032 }
2033
2034=== modified file 'tests/libual-cpp-test.cc'
2035--- tests/libual-cpp-test.cc 2016-09-23 20:36:31 +0000
2036+++ tests/libual-cpp-test.cc 2016-11-07 20:56:13 +0000
2037@@ -49,32 +49,68 @@
2038 DbusTestDbusMock* mock = NULL;
2039 DbusTestDbusMock* cgmock = NULL;
2040 GDBusConnection* bus = NULL;
2041- std::string last_focus_appid;
2042- std::string last_resume_appid;
2043 guint resume_timeout = 0;
2044 std::shared_ptr<ubuntu::app_launch::Registry> registry;
2045
2046-private:
2047- static void focus_cb(const gchar* appid, gpointer user_data)
2048- {
2049- g_debug("Focus Callback: %s", appid);
2050- LibUAL* _this = static_cast<LibUAL*>(user_data);
2051- _this->last_focus_appid = appid;
2052- }
2053-
2054- static void resume_cb(const gchar* appid, gpointer user_data)
2055- {
2056- g_debug("Resume Callback: %s", appid);
2057- LibUAL* _this = static_cast<LibUAL*>(user_data);
2058- _this->last_resume_appid = appid;
2059-
2060- if (_this->resume_timeout > 0)
2061- {
2062- _this->pause(_this->resume_timeout);
2063- }
2064- }
2065-
2066-protected:
2067+ class ManagerMock : public ubuntu::app_launch::Registry::Manager
2068+ {
2069+ GLib::ContextThread thread;
2070+
2071+ public:
2072+ ManagerMock()
2073+ {
2074+ g_debug("Building a Manager Mock");
2075+ }
2076+
2077+ ~ManagerMock()
2078+ {
2079+ g_debug("Freeing a Manager Mock");
2080+ }
2081+
2082+ ubuntu::app_launch::AppID lastStartedApp;
2083+ ubuntu::app_launch::AppID lastFocusedApp;
2084+ ubuntu::app_launch::AppID lastResumedApp;
2085+
2086+ bool startingResponse{true};
2087+ bool focusResponse{true};
2088+ bool resumeResponse{true};
2089+
2090+ std::chrono::milliseconds startingTimeout{0};
2091+ std::chrono::milliseconds focusTimeout{0};
2092+ std::chrono::milliseconds resumeTimeout{0};
2093+
2094+ void startingRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
2095+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
2096+ std::function<void(bool)> reply) override
2097+ {
2098+ thread.timeout(startingTimeout, [this, app, instance, reply]() {
2099+ lastStartedApp = app->appId();
2100+ reply(startingResponse);
2101+ });
2102+ }
2103+
2104+ void focusRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
2105+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
2106+ std::function<void(bool)> reply) override
2107+ {
2108+ thread.timeout(focusTimeout, [this, app, instance, reply]() {
2109+ lastFocusedApp = app->appId();
2110+ reply(focusResponse);
2111+ });
2112+ }
2113+
2114+ void resumeRequest(std::shared_ptr<ubuntu::app_launch::Application> app,
2115+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
2116+ std::function<void(bool)> reply) override
2117+ {
2118+ thread.timeout(resumeTimeout, [this, app, instance, reply]() {
2119+ lastResumedApp = app->appId();
2120+ reply(resumeResponse);
2121+ });
2122+ }
2123+ };
2124+ std::weak_ptr<ManagerMock> manager;
2125+
2126 /* Useful debugging stuff, but not on by default. You really want to
2127 not get all this noise typically */
2128 void debugConnection()
2129@@ -127,13 +163,13 @@
2130
2131 dbus_test_dbus_mock_object_add_method(mock, obj, "GetJobByName", G_VARIANT_TYPE("s"), G_VARIANT_TYPE("o"),
2132 "if args[0] == 'application-click':\n"
2133- " ret = dbus.ObjectPath('/com/test/application_click')\n"
2134+ " ret = dbus.ObjectPath('/com/test/application_click')\n"
2135 "elif args[0] == 'application-snap':\n"
2136- " ret = dbus.ObjectPath('/com/test/application_snap')\n"
2137+ " ret = dbus.ObjectPath('/com/test/application_snap')\n"
2138 "elif args[0] == 'application-legacy':\n"
2139- " ret = dbus.ObjectPath('/com/test/application_legacy')\n"
2140+ " ret = dbus.ObjectPath('/com/test/application_legacy')\n"
2141 "elif args[0] == 'untrusted-helper':\n"
2142- " ret = dbus.ObjectPath('/com/test/untrusted/helper')\n",
2143+ " ret = dbus.ObjectPath('/com/test/untrusted/helper')\n",
2144 NULL);
2145
2146 dbus_test_dbus_mock_object_add_method(mock, obj, "SetEnv", G_VARIANT_TYPE("(assb)"), NULL, "", NULL);
2147@@ -272,19 +308,23 @@
2148 /* Make sure we pretend the CG manager is just on our bus */
2149 g_setenv("UBUNTU_APP_LAUNCH_CG_MANAGER_SESSION_BUS", "YES", TRUE);
2150
2151- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_focus(focus_cb, this));
2152- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_resume(resume_cb, this));
2153-
2154 registry = std::make_shared<ubuntu::app_launch::Registry>();
2155+
2156+ auto smanager = std::make_shared<ManagerMock>();
2157+ manager = smanager;
2158+ ubuntu::app_launch::Registry::setManager(smanager, registry);
2159 }
2160
2161 virtual void TearDown()
2162 {
2163- ubuntu_app_launch_observer_delete_app_focus(focus_cb, this);
2164- ubuntu_app_launch_observer_delete_app_resume(resume_cb, this);
2165-
2166 registry.reset();
2167
2168+ // NOTE: This should generally always be commented out, but
2169+ // it is useful for debugging common errors, so leaving it
2170+ // as a comment to make debugging those eaiser.
2171+ //
2172+ // ubuntu::app_launch::Registry::clearDefault();
2173+
2174 g_clear_object(&mock);
2175 g_clear_object(&cgmock);
2176 g_clear_object(&service);
2177@@ -793,33 +833,30 @@
2178 #endif
2179 }
2180
2181-typedef struct
2182-{
2183- unsigned int count;
2184- const gchar* name;
2185-} observer_data_t;
2186-
2187-static void observer_cb(const gchar* appid, gpointer user_data)
2188-{
2189- observer_data_t* data = (observer_data_t*)user_data;
2190-
2191- if (data->name == NULL)
2192- {
2193- data->count++;
2194- }
2195- else if (g_strcmp0(data->name, appid) == 0)
2196- {
2197- data->count++;
2198- }
2199-}
2200-
2201 TEST_F(LibUAL, StartStopObserver)
2202 {
2203- observer_data_t start_data = {.count = 0, .name = nullptr};
2204- observer_data_t stop_data = {.count = 0, .name = nullptr};
2205-
2206- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_started(observer_cb, &start_data));
2207- ASSERT_TRUE(ubuntu_app_launch_observer_add_app_stop(observer_cb, &stop_data));
2208+ int start_count = 0;
2209+ int stop_count = 0;
2210+ ubuntu::app_launch::AppID start_appid;
2211+ ubuntu::app_launch::AppID stop_appid;
2212+
2213+ ubuntu::app_launch::Registry::appStarted(registry).connect(
2214+ [&start_count, &start_appid](std::shared_ptr<ubuntu::app_launch::Application> app,
2215+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
2216+ if (!start_appid.empty() && !(start_appid == app->appId()))
2217+ return;
2218+
2219+ start_count++;
2220+ });
2221+
2222+ ubuntu::app_launch::Registry::appStopped(registry).connect(
2223+ [&stop_count, &stop_appid](std::shared_ptr<ubuntu::app_launch::Application> app,
2224+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
2225+ if (!stop_appid.empty() && !(stop_appid == app->appId()))
2226+ return;
2227+
2228+ stop_count++;
2229+ });
2230
2231 DbusTestDbusMockObject* obj =
2232 dbus_test_dbus_mock_get_object(mock, "/com/ubuntu/Upstart", "com.ubuntu.Upstart0_6", NULL);
2233@@ -830,7 +867,7 @@
2234 g_variant_new_parsed("('started', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
2235 NULL);
2236
2237- EXPECT_EVENTUALLY_EQ(1, start_data.count);
2238+ EXPECT_EVENTUALLY_EQ(1, start_count);
2239
2240 /* Basic stop */
2241 dbus_test_dbus_mock_object_emit_signal(
2242@@ -838,33 +875,41 @@
2243 g_variant_new_parsed("('stopped', ['JOB=application-click', 'INSTANCE=com.test.good_application_1.2.3'])"),
2244 NULL);
2245
2246- EXPECT_EVENTUALLY_EQ(1, stop_data.count);
2247+ EXPECT_EVENTUALLY_EQ(1, stop_count);
2248
2249 /* Start legacy */
2250- start_data.count = 0;
2251- start_data.name = "multiple";
2252+ start_count = 0;
2253+ start_appid = ubuntu::app_launch::AppID{ubuntu::app_launch::AppID::Package::from_raw({}),
2254+ ubuntu::app_launch::AppID::AppName::from_raw("multiple"),
2255+ ubuntu::app_launch::AppID::Version::from_raw({})};
2256
2257 dbus_test_dbus_mock_object_emit_signal(
2258 mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
2259 g_variant_new_parsed("('started', ['JOB=application-legacy', 'INSTANCE=multiple-234235'])"), NULL);
2260
2261- EXPECT_EVENTUALLY_EQ(1, start_data.count);
2262+ EXPECT_EVENTUALLY_EQ(1, start_count);
2263
2264 /* Legacy stop */
2265- stop_data.count = 0;
2266- stop_data.name = "bar";
2267+ stop_count = 0;
2268+ stop_appid = ubuntu::app_launch::AppID{ubuntu::app_launch::AppID::Package::from_raw({}),
2269+ ubuntu::app_launch::AppID::AppName::from_raw("foo"),
2270+ ubuntu::app_launch::AppID::Version::from_raw({})};
2271
2272 dbus_test_dbus_mock_object_emit_signal(
2273 mock, obj, "EventEmitted", G_VARIANT_TYPE("(sas)"),
2274- g_variant_new_parsed("('stopped', ['JOB=application-legacy', 'INSTANCE=bar-9344321'])"), NULL);
2275+ g_variant_new_parsed("('stopped', ['JOB=application-legacy', 'INSTANCE=foo-9344321'])"), NULL);
2276
2277- EXPECT_EVENTUALLY_EQ(1, stop_data.count);
2278+ EXPECT_EVENTUALLY_EQ(1, stop_count);
2279
2280 /* Test Noise Start */
2281- start_data.count = 0;
2282- start_data.name = "com.test.good_application_1.2.3";
2283- stop_data.count = 0;
2284- stop_data.name = "com.test.good_application_1.2.3";
2285+ start_count = 0;
2286+ start_appid = ubuntu::app_launch::AppID{ubuntu::app_launch::AppID::Package::from_raw("com.test.good"),
2287+ ubuntu::app_launch::AppID::AppName::from_raw("application"),
2288+ ubuntu::app_launch::AppID::Version::from_raw("1.2.3")};
2289+ stop_count = 0;
2290+ stop_appid = ubuntu::app_launch::AppID{ubuntu::app_launch::AppID::Package::from_raw("com.test.good"),
2291+ ubuntu::app_launch::AppID::AppName::from_raw("application"),
2292+ ubuntu::app_launch::AppID::Version::from_raw("1.2.3")};
2293
2294 /* A full lifecycle */
2295 dbus_test_dbus_mock_object_emit_signal(
2296@@ -885,46 +930,33 @@
2297 NULL);
2298
2299 /* Ensure we just signaled once for each */
2300- EXPECT_EVENTUALLY_EQ(1, start_data.count);
2301- EXPECT_EVENTUALLY_EQ(1, stop_data.count);
2302-
2303- /* Remove */
2304- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_started(observer_cb, &start_data));
2305- ASSERT_TRUE(ubuntu_app_launch_observer_delete_app_stop(observer_cb, &stop_data));
2306-}
2307-
2308-static GDBusMessage* filter_starting(GDBusConnection* conn,
2309- GDBusMessage* message,
2310- gboolean incomming,
2311- gpointer user_data)
2312-{
2313- if (g_strcmp0(g_dbus_message_get_member(message), "UnityStartingSignal") == 0)
2314- {
2315- unsigned int* count = static_cast<unsigned int*>(user_data);
2316- (*count)++;
2317- g_object_unref(message);
2318- return NULL;
2319- }
2320-
2321- return message;
2322-}
2323-
2324-static void starting_observer(const gchar* appid, gpointer user_data)
2325-{
2326- std::string* last = static_cast<std::string*>(user_data);
2327- *last = appid;
2328- return;
2329+ EXPECT_EVENTUALLY_EQ(1, start_count);
2330+ EXPECT_EVENTUALLY_EQ(1, stop_count);
2331 }
2332
2333 TEST_F(LibUAL, StartingResponses)
2334 {
2335- std::string last_observer;
2336+ /* Get Bus */
2337+ GDBusConnection* session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
2338+
2339+ /* Setup filter to count signals out */
2340 unsigned int starting_count = 0;
2341- GDBusConnection* session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
2342- guint filter = g_dbus_connection_add_filter(session, filter_starting, &starting_count, NULL);
2343-
2344- EXPECT_TRUE(ubuntu_app_launch_observer_add_app_starting(starting_observer, &last_observer));
2345-
2346+ guint filter = g_dbus_connection_add_filter(
2347+ session,
2348+ [](GDBusConnection* conn, GDBusMessage* message, gboolean incomming, gpointer user_data) -> GDBusMessage* {
2349+ if (g_strcmp0(g_dbus_message_get_member(message), "UnityStartingSignal") == 0)
2350+ {
2351+ unsigned int* count = static_cast<unsigned int*>(user_data);
2352+ (*count)++;
2353+ g_object_unref(message);
2354+ return NULL;
2355+ }
2356+
2357+ return message;
2358+ },
2359+ &starting_count, NULL);
2360+
2361+ /* Emit a signal */
2362 g_dbus_connection_emit_signal(session, NULL, /* destination */
2363 "/", /* path */
2364 "com.canonical.UbuntuAppLaunch", /* interface */
2365@@ -932,11 +964,15 @@
2366 g_variant_new("(s)", "com.test.good_application_1.2.3"), /* params, the same */
2367 NULL);
2368
2369- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", last_observer);
2370+ /* Make sure we run our observer */
2371+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID(ubuntu::app_launch::AppID::Package::from_raw("com.test.good"),
2372+ ubuntu::app_launch::AppID::AppName::from_raw("application"),
2373+ ubuntu::app_launch::AppID::Version::from_raw("1.2.3")),
2374+ manager.lock()->lastStartedApp);
2375+
2376+ /* Make sure we return */
2377 EXPECT_EVENTUALLY_EQ(1, starting_count);
2378
2379- EXPECT_TRUE(ubuntu_app_launch_observer_delete_app_starting(starting_observer, &last_observer));
2380-
2381 g_dbus_connection_remove_filter(session, filter);
2382 g_object_unref(session);
2383 }
2384@@ -947,8 +983,10 @@
2385 auto app = ubuntu::app_launch::Application::create(appid, registry);
2386 app->launch();
2387
2388- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
2389- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
2390+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2391+ this->manager.lock()->lastFocusedApp);
2392+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2393+ this->manager.lock()->lastResumedApp);
2394 }
2395
2396 GDBusMessage* filter_func_good(GDBusConnection* conn, GDBusMessage* message, gboolean incomming, gpointer user_data)
2397@@ -982,8 +1020,10 @@
2398
2399 app->launch(uris);
2400
2401- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
2402- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
2403+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2404+ this->manager.lock()->lastFocusedApp);
2405+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2406+ this->manager.lock()->lastResumedApp);
2407
2408 g_dbus_connection_remove_filter(session, filter);
2409
2410@@ -1015,8 +1055,10 @@
2411
2412 app->launch(uris);
2413
2414- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
2415- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
2416+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2417+ this->manager.lock()->lastFocusedApp);
2418+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2419+ this->manager.lock()->lastResumedApp);
2420 }
2421
2422 TEST_F(LibUAL, UnityTimeoutTest)
2423@@ -1028,8 +1070,10 @@
2424
2425 app->launch();
2426
2427- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
2428- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
2429+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2430+ this->manager.lock()->lastResumedApp);
2431+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2432+ this->manager.lock()->lastFocusedApp);
2433 }
2434
2435 TEST_F(LibUAL, UnityTimeoutUriTest)
2436@@ -1043,8 +1087,10 @@
2437
2438 app->launch(uris);
2439
2440- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
2441- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
2442+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2443+ this->manager.lock()->lastFocusedApp);
2444+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2445+ this->manager.lock()->lastResumedApp);
2446 }
2447
2448 GDBusMessage* filter_respawn(GDBusConnection* conn, GDBusMessage* message, gboolean incomming, gpointer user_data)
2449@@ -1077,8 +1123,10 @@
2450 g_debug("Start call time: %d ms", (end - start) / 1000);
2451 EXPECT_LT(end - start, 2000 * 1000);
2452
2453- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_focus_appid);
2454- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", this->last_resume_appid);
2455+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2456+ this->manager.lock()->lastFocusedApp);
2457+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"),
2458+ this->manager.lock()->lastResumedApp);
2459
2460 g_dbus_connection_remove_filter(session, filter);
2461 g_object_unref(session);
2462@@ -1138,23 +1186,21 @@
2463 g_variant_unref(env);
2464 }
2465
2466-static void failed_observer(const gchar* appid, UbuntuAppLaunchAppFailed reason, gpointer user_data)
2467-{
2468- if (reason == UBUNTU_APP_LAUNCH_APP_FAILED_CRASH)
2469- {
2470- std::string* last = static_cast<std::string*>(user_data);
2471- *last = appid;
2472- }
2473- return;
2474-}
2475-
2476 TEST_F(LibUAL, FailingObserver)
2477 {
2478- std::string last_observer;
2479+ ubuntu::app_launch::AppID lastFailedApp;
2480+ ubuntu::app_launch::Registry::FailureType lastFailedType;
2481+
2482+ ubuntu::app_launch::Registry::appFailed(registry).connect(
2483+ [&lastFailedApp, &lastFailedType](std::shared_ptr<ubuntu::app_launch::Application> app,
2484+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
2485+ ubuntu::app_launch::Registry::FailureType type) {
2486+ lastFailedApp = app->appId();
2487+ lastFailedType = type;
2488+ });
2489+
2490 GDBusConnection* session = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
2491
2492- EXPECT_TRUE(ubuntu_app_launch_observer_add_app_failed(failed_observer, &last_observer));
2493-
2494 g_dbus_connection_emit_signal(
2495 session, NULL, /* destination */
2496 "/", /* path */
2497@@ -1163,9 +1209,10 @@
2498 g_variant_new("(ss)", "com.test.good_application_1.2.3", "crash"), /* params, the same */
2499 NULL);
2500
2501- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", last_observer);
2502+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"), lastFailedApp);
2503+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::Registry::FailureType::CRASH, lastFailedType);
2504
2505- last_observer.clear();
2506+ lastFailedApp = ubuntu::app_launch::AppID();
2507
2508 g_dbus_connection_emit_signal(
2509 session, NULL, /* destination */
2510@@ -1175,9 +1222,9 @@
2511 g_variant_new("(ss)", "com.test.good_application_1.2.3", "blahblah"), /* params, the same */
2512 NULL);
2513
2514- EXPECT_EVENTUALLY_EQ("com.test.good_application_1.2.3", last_observer);
2515+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::AppID::parse("com.test.good_application_1.2.3"), lastFailedApp);
2516
2517- last_observer.clear();
2518+ lastFailedApp = ubuntu::app_launch::AppID();
2519
2520 g_dbus_connection_emit_signal(
2521 session, NULL, /* destination */
2522@@ -1187,9 +1234,7 @@
2523 g_variant_new("(ss)", "com.test.good_application_1.2.3", "start-failure"), /* params, the same */
2524 NULL);
2525
2526- EXPECT_EVENTUALLY_EQ(true, last_observer.empty());
2527-
2528- EXPECT_TRUE(ubuntu_app_launch_observer_delete_app_failed(failed_observer, &last_observer));
2529+ EXPECT_EVENTUALLY_EQ(ubuntu::app_launch::Registry::FailureType::START_FAILURE, lastFailedType);
2530
2531 g_object_unref(session);
2532 }
2533
2534=== modified file 'tools/CMakeLists.txt'
2535--- tools/CMakeLists.txt 2016-06-18 18:24:27 +0000
2536+++ tools/CMakeLists.txt 2016-11-07 20:56:13 +0000
2537@@ -39,7 +39,7 @@
2538 # ubuntu-app-launch
2539 ########################
2540
2541-add_executable(ubuntu-app-launch ubuntu-app-launch.c)
2542+add_executable(ubuntu-app-launch ubuntu-app-launch.cpp)
2543 set_target_properties(ubuntu-app-launch PROPERTIES OUTPUT_NAME "ubuntu-app-launch")
2544 target_link_libraries(ubuntu-app-launch ubuntu-launcher)
2545 install(TARGETS ubuntu-app-launch RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}")
2546@@ -48,7 +48,7 @@
2547 # ubuntu-app-watch
2548 ########################
2549
2550-add_executable(ubuntu-app-watch ubuntu-app-watch.c)
2551+add_executable(ubuntu-app-watch ubuntu-app-watch.cpp)
2552 set_target_properties(ubuntu-app-watch PROPERTIES OUTPUT_NAME "ubuntu-app-watch")
2553 target_link_libraries(ubuntu-app-watch ubuntu-launcher)
2554 install(TARGETS ubuntu-app-watch RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}")
2555@@ -116,3 +116,22 @@
2556 target_link_libraries(ubuntu-app-usage ubuntu-launcher)
2557 install(TARGETS ubuntu-app-usage RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}")
2558
2559+########################
2560+# Formatting
2561+########################
2562+
2563+add_custom_target(format-tools
2564+ COMMAND clang-format -i -style=file
2565+ ubuntu-app-info.cpp
2566+ ubuntu-app-launch-appids.cpp
2567+ ubuntu-app-launch.cpp
2568+ ubuntu-app-list.cpp
2569+ ubuntu-app-list-pids.cpp
2570+ ubuntu-app-pid.cpp
2571+ ubuntu-app-stop.cpp
2572+ ubuntu-app-triplet.cpp
2573+ ubuntu-app-watch.cpp
2574+ ubuntu-helper-list.cpp
2575+ ubuntu-helper-start.cpp
2576+ ubuntu-helper-stop.cpp
2577+)
2578
2579=== modified file 'tools/ubuntu-app-info.cpp'
2580--- tools/ubuntu-app-info.cpp 2016-05-04 14:09:10 +0000
2581+++ tools/ubuntu-app-info.cpp 2016-11-07 20:56:13 +0000
2582@@ -17,34 +17,40 @@
2583 * Ted Gould <ted.gould@canonical.com>
2584 */
2585
2586-#include <iostream>
2587 #include "libubuntu-app-launch/application.h"
2588 #include "libubuntu-app-launch/registry.h"
2589+#include <iostream>
2590
2591-int main(int argc, char* argv[])
2592+int main(int argc, char *argv[])
2593 {
2594- if (argc != 2) {
2595+ if (argc != 2)
2596+ {
2597 std::cerr << "Usage: " << argv[0] << " (appid)" << std::endl;
2598 exit(1);
2599 }
2600
2601 auto appid = ubuntu::app_launch::AppID::find(argv[1]);
2602- if (appid.empty()) {
2603+ if (appid.empty())
2604+ {
2605 std::cerr << "Unable to find app for appid: " << argv[1] << std::endl;
2606 return 1;
2607 }
2608
2609 std::shared_ptr<ubuntu::app_launch::Application> app;
2610- try {
2611+ try
2612+ {
2613 app = ubuntu::app_launch::Application::create(appid, ubuntu::app_launch::Registry::getDefault());
2614 if (!app)
2615 throw std::runtime_error("Application object is nullptr");
2616- } catch (std::runtime_error &e) {
2617+ }
2618+ catch (std::runtime_error &e)
2619+ {
2620 std::cerr << "Unable to find application for AppID: " << argv[1] << std::endl;
2621 exit(1);
2622 }
2623
2624- try {
2625+ try
2626+ {
2627 auto info = app->info();
2628
2629 std::cout << "Name: " << info->name().value() << std::endl;
2630@@ -64,8 +70,11 @@
2631 std::cout << " Inv Landscape: " << info->supportedOrientations().invertedLandscape << std::endl;
2632 std::cout << "Rotates: " << info->rotatesWindowContents().value() << std::endl;
2633 std::cout << "Ubuntu Lifecycle: " << info->supportsUbuntuLifecycle().value() << std::endl;
2634- } catch (std::runtime_error &e) {
2635- std::cerr << "Unable to parse Application info for application '" << std::string(appid) << "': " << e.what() << std::endl;
2636+ }
2637+ catch (std::runtime_error &e)
2638+ {
2639+ std::cerr << "Unable to parse Application info for application '" << std::string(appid) << "': " << e.what()
2640+ << std::endl;
2641 exit(1);
2642 }
2643
2644
2645=== renamed file 'tools/ubuntu-app-launch.c' => 'tools/ubuntu-app-launch.cpp'
2646--- tools/ubuntu-app-launch.c 2016-02-08 19:03:31 +0000
2647+++ tools/ubuntu-app-launch.cpp 2016-11-07 20:56:13 +0000
2648@@ -1,5 +1,5 @@
2649 /*
2650- * Copyright 2013 Canonical Ltd.
2651+ * Copyright © 2016 Canonical Ltd.
2652 *
2653 * This program is free software: you can redistribute it and/or modify it
2654 * under the terms of the GNU General Public License version 3, as published
2655@@ -17,71 +17,59 @@
2656 * Ted Gould <ted.gould@canonical.com>
2657 */
2658
2659-#include <glib.h>
2660-#include "libubuntu-app-launch/ubuntu-app-launch.h"
2661-
2662-const gchar * global_appid = NULL;
2663-int retval = 0;
2664-
2665-static void
2666-good_observer (const gchar * appid, gpointer user_data)
2667-{
2668- if (g_strcmp0(appid, global_appid) != 0) {
2669- return;
2670- }
2671-
2672- g_debug("Application '%s' running", appid);
2673- g_main_loop_quit((GMainLoop *)user_data);
2674-}
2675-
2676-static void
2677-bad_observer (const gchar * appid, UbuntuAppLaunchAppFailed failure_type, gpointer user_data)
2678-{
2679- if (g_strcmp0(appid, global_appid) != 0) {
2680- return;
2681- }
2682-
2683- g_debug("Application '%s' failed: %s", appid, failure_type == UBUNTU_APP_LAUNCH_APP_FAILED_CRASH ? "crash" : "startup failure");
2684- retval = -1;
2685- g_main_loop_quit((GMainLoop *)user_data);
2686-}
2687-
2688-int
2689-main (int argc, gchar * argv[]) {
2690- if (argc < 2) {
2691- g_printerr("Usage: %s <app id> [uris]\n", argv[0]);
2692- return 1;
2693- }
2694-
2695- global_appid = argv[1];
2696- GMainLoop * mainloop = g_main_loop_new(NULL, FALSE);
2697-
2698- gchar ** uris = NULL;
2699- if (argc > 2) {
2700- int i;
2701-
2702- uris = g_new0(gchar *, argc - 1);
2703-
2704- for (i = 2; i < argc; i++) {
2705- uris[i - 2] = argv[i];
2706- }
2707- }
2708-
2709- ubuntu_app_launch_observer_add_app_started(good_observer, mainloop);
2710- ubuntu_app_launch_observer_add_app_focus(good_observer, mainloop);
2711-
2712- ubuntu_app_launch_observer_add_app_failed(bad_observer, mainloop);
2713-
2714- ubuntu_app_launch_start_application(global_appid, (const gchar * const *)uris);
2715-
2716- g_main_loop_run(mainloop);
2717-
2718- ubuntu_app_launch_observer_delete_app_started(good_observer, mainloop);
2719- ubuntu_app_launch_observer_delete_app_focus(good_observer, mainloop);
2720- ubuntu_app_launch_observer_delete_app_failed(bad_observer, mainloop);
2721-
2722- g_main_loop_unref(mainloop);
2723- g_free(uris);
2724-
2725- return retval;
2726+#include "libubuntu-app-launch/application.h"
2727+#include "libubuntu-app-launch/registry.h"
2728+#include <csignal>
2729+#include <future>
2730+#include <iostream>
2731+
2732+ubuntu::app_launch::AppID global_appid;
2733+std::promise<int> retval;
2734+
2735+int main(int argc, char* argv[])
2736+{
2737+ if (argc < 2)
2738+ {
2739+ std::cerr << "Usage: " << argv[0] << " <app id> [uris]" << std::endl;
2740+ return 1;
2741+ }
2742+
2743+ global_appid = ubuntu::app_launch::AppID::find(argv[1]);
2744+
2745+ std::vector<ubuntu::app_launch::Application::URL> urls;
2746+ for (int i = 2; i < argc; i++)
2747+ {
2748+ urls.push_back(ubuntu::app_launch::Application::URL::from_raw(argv[i]));
2749+ }
2750+
2751+ ubuntu::app_launch::Registry::appStarted().connect(
2752+ [](std::shared_ptr<ubuntu::app_launch::Application> app,
2753+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
2754+ if (app->appId() != global_appid)
2755+ {
2756+ return;
2757+ }
2758+
2759+ std::cout << "Started: " << (std::string)app->appId() << std::endl;
2760+ retval.set_value(EXIT_SUCCESS);
2761+ });
2762+
2763+ ubuntu::app_launch::Registry::appFailed().connect(
2764+ [](std::shared_ptr<ubuntu::app_launch::Application> app,
2765+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
2766+ ubuntu::app_launch::Registry::FailureType type) {
2767+ if (app->appId() != global_appid)
2768+ {
2769+ return;
2770+ }
2771+
2772+ std::cout << "Failed: " << (std::string)app->appId() << std::endl;
2773+ retval.set_value(EXIT_FAILURE);
2774+ });
2775+
2776+ auto app = ubuntu::app_launch::Application::create(global_appid, ubuntu::app_launch::Registry::getDefault());
2777+ app->launch(urls);
2778+
2779+ std::signal(SIGTERM, [](int signal) -> void { retval.set_value(EXIT_SUCCESS); });
2780+ return retval.get_future().get();
2781 }
2782
2783=== modified file 'tools/ubuntu-app-list-pids.cpp'
2784--- tools/ubuntu-app-list-pids.cpp 2016-05-03 01:46:27 +0000
2785+++ tools/ubuntu-app-list-pids.cpp 2016-11-07 20:56:13 +0000
2786@@ -30,12 +30,14 @@
2787 }
2788
2789 auto appid = ubuntu::app_launch::AppID::find(argv[1]);
2790- if (appid.empty()) {
2791+ if (appid.empty())
2792+ {
2793 std::cerr << "Unable to find app for appid: " << argv[1] << std::endl;
2794 return 1;
2795 }
2796
2797- try {
2798+ try
2799+ {
2800 auto app = ubuntu::app_launch::Application::create(appid, ubuntu::app_launch::Registry::getDefault());
2801
2802 for (auto instance : app->instances())
2803@@ -45,7 +47,9 @@
2804 std::cout << pid << std::endl;
2805 }
2806 }
2807- } catch (std::runtime_error &e) {
2808+ }
2809+ catch (std::runtime_error& e)
2810+ {
2811 std::cerr << "Unable to find application for '" << std::string(appid) << "': " << e.what() << std::endl;
2812 return 1;
2813 }
2814
2815=== modified file 'tools/ubuntu-app-list.cpp'
2816--- tools/ubuntu-app-list.cpp 2016-02-09 21:12:54 +0000
2817+++ tools/ubuntu-app-list.cpp 2016-11-07 20:56:13 +0000
2818@@ -17,8 +17,8 @@
2819 * Ted Gould <ted.gould@canonical.com>
2820 */
2821
2822+#include "libubuntu-app-launch/registry.h"
2823 #include <iostream>
2824-#include "libubuntu-app-launch/registry.h"
2825
2826 int main(int argc, char* argv[])
2827 {
2828
2829=== modified file 'tools/ubuntu-app-pid.cpp'
2830--- tools/ubuntu-app-pid.cpp 2016-05-03 01:46:51 +0000
2831+++ tools/ubuntu-app-pid.cpp 2016-11-07 20:56:13 +0000
2832@@ -18,8 +18,8 @@
2833 */
2834
2835 #include <iostream>
2836+#include <libubuntu-app-launch/application.h>
2837 #include <libubuntu-app-launch/registry.h>
2838-#include <libubuntu-app-launch/application.h>
2839
2840 int main(int argc, char* argv[])
2841 {
2842@@ -31,12 +31,14 @@
2843 }
2844
2845 auto appid = ubuntu::app_launch::AppID::find(argv[1]);
2846- if (appid.empty()) {
2847+ if (appid.empty())
2848+ {
2849 std::cerr << "Unable to find app for appid: " << argv[1] << std::endl;
2850 return 1;
2851 }
2852
2853- try {
2854+ try
2855+ {
2856 auto app = ubuntu::app_launch::Application::create(appid, ubuntu::app_launch::Registry::getDefault());
2857 auto pid = app->instances()[0]->primaryPid();
2858
2859@@ -47,7 +49,9 @@
2860
2861 std::cout << pid << std::endl;
2862 return 0;
2863- } catch (std::runtime_error &e) {
2864+ }
2865+ catch (std::runtime_error& e)
2866+ {
2867 std::cerr << "Unable to find application for '" << std::string(appid) << "': " << e.what() << std::endl;
2868 return 1;
2869 }
2870
2871=== modified file 'tools/ubuntu-app-stop.cpp'
2872--- tools/ubuntu-app-stop.cpp 2016-05-03 01:46:27 +0000
2873+++ tools/ubuntu-app-stop.cpp 2016-11-07 20:56:13 +0000
2874@@ -30,19 +30,23 @@
2875 }
2876
2877 auto appid = ubuntu::app_launch::AppID::find(argv[1]);
2878- if (appid.empty()) {
2879+ if (appid.empty())
2880+ {
2881 std::cerr << "Unable to find app for appid: " << argv[1] << std::endl;
2882 return 1;
2883 }
2884
2885- try {
2886+ try
2887+ {
2888 auto app = ubuntu::app_launch::Application::create(appid, ubuntu::app_launch::Registry::getDefault());
2889
2890 for (auto instance : app->instances())
2891 {
2892 instance->stop();
2893 }
2894- } catch (std::runtime_error &e) {
2895+ }
2896+ catch (std::runtime_error& e)
2897+ {
2898 std::cerr << "Unable to find application for '" << std::string(appid) << "': " << e.what() << std::endl;
2899 return 1;
2900 }
2901
2902=== modified file 'tools/ubuntu-app-triplet.cpp'
2903--- tools/ubuntu-app-triplet.cpp 2016-02-09 21:12:54 +0000
2904+++ tools/ubuntu-app-triplet.cpp 2016-11-07 20:56:13 +0000
2905@@ -17,8 +17,8 @@
2906 * Ted Gould <ted.gould@canonical.com>
2907 */
2908
2909+#include "libubuntu-app-launch/application.h"
2910 #include <iostream>
2911-#include "libubuntu-app-launch/application.h"
2912
2913 int main(int argc, char* argv[])
2914 {
2915
2916=== renamed file 'tools/ubuntu-app-watch.c' => 'tools/ubuntu-app-watch.cpp'
2917--- tools/ubuntu-app-watch.c 2016-02-08 19:03:31 +0000
2918+++ tools/ubuntu-app-watch.cpp 2016-11-07 20:56:13 +0000
2919@@ -1,5 +1,5 @@
2920 /*
2921- * Copyright 2013 Canonical Ltd.
2922+ * Copyright © 2015 Canonical Ltd.
2923 *
2924 * This program is free software: you can redistribute it and/or modify it
2925 * under the terms of the GNU General Public License version 3, as published
2926@@ -17,91 +17,64 @@
2927 * Ted Gould <ted.gould@canonical.com>
2928 */
2929
2930-#include "libubuntu-app-launch/ubuntu-app-launch.h"
2931-
2932-void
2933-starting (const gchar * appid, gpointer user_data)
2934-{
2935- g_print("Starting %s\n", appid);
2936- return;
2937-}
2938-
2939-void
2940-started (const gchar * appid, gpointer user_data)
2941-{
2942- g_print("Started %s\n", appid);
2943- return;
2944-}
2945-
2946-void
2947-stopped (const gchar * appid, gpointer user_data)
2948-{
2949- g_print("Stop %s\n", appid);
2950- return;
2951-}
2952-
2953-void
2954-resumed (const gchar * appid, GPid * pids, gpointer user_data)
2955-{
2956- g_print("Resumed %s\n", appid);
2957- return;
2958-}
2959-
2960-void
2961-paused (const gchar * appid, GPid * pids, gpointer user_data)
2962-{
2963- g_print("Paused %s\n", appid);
2964- return;
2965-}
2966-
2967-void
2968-focus (const gchar * appid, gpointer user_data)
2969-{
2970- g_print("Focus %s\n", appid);
2971- return;
2972-}
2973-
2974-void
2975-fail (const gchar * appid, UbuntuAppLaunchAppFailed failhow, gpointer user_data)
2976-{
2977- const gchar * failstr = "unknown";
2978- switch (failhow) {
2979- case UBUNTU_APP_LAUNCH_APP_FAILED_CRASH:
2980- failstr = "crashed";
2981- break;
2982- case UBUNTU_APP_LAUNCH_APP_FAILED_START_FAILURE:
2983- failstr = "startup";
2984- break;
2985- }
2986-
2987- g_print("Fail %s (%s)\n", appid, failstr);
2988- return;
2989-}
2990-
2991-
2992-int
2993-main (int argc, gchar * argv[])
2994-{
2995- ubuntu_app_launch_observer_add_app_starting(starting, NULL);
2996- ubuntu_app_launch_observer_add_app_started(started, NULL);
2997- ubuntu_app_launch_observer_add_app_stop(stopped, NULL);
2998- ubuntu_app_launch_observer_add_app_focus(focus, NULL);
2999- ubuntu_app_launch_observer_add_app_resumed(resumed, NULL);
3000- ubuntu_app_launch_observer_add_app_paused(paused, NULL);
3001- ubuntu_app_launch_observer_add_app_failed(fail, NULL);
3002-
3003- GMainLoop * mainloop = g_main_loop_new(NULL, FALSE);
3004- g_main_loop_run(mainloop);
3005-
3006- ubuntu_app_launch_observer_delete_app_starting(starting, NULL);
3007- ubuntu_app_launch_observer_delete_app_started(started, NULL);
3008- ubuntu_app_launch_observer_delete_app_stop(stopped, NULL);
3009- ubuntu_app_launch_observer_delete_app_focus(focus, NULL);
3010- ubuntu_app_launch_observer_delete_app_resumed(resumed, NULL);
3011- ubuntu_app_launch_observer_delete_app_paused(paused, NULL);
3012- ubuntu_app_launch_observer_delete_app_failed(fail, NULL);
3013-
3014- g_main_loop_unref(mainloop);
3015-
3016- return 0;
3017+#include "libubuntu-app-launch/registry.h"
3018+#include <csignal>
3019+#include <future>
3020+
3021+std::promise<int> retval;
3022+
3023+int main(int argc, char *argv[])
3024+{
3025+ ubuntu::app_launch::Registry registry;
3026+
3027+ registry.appStarted().connect([](std::shared_ptr<ubuntu::app_launch::Application> app,
3028+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
3029+ std::cout << "Started: " << (std::string)app->appId() << std::endl;
3030+ });
3031+ registry.appStopped().connect([](std::shared_ptr<ubuntu::app_launch::Application> app,
3032+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance) {
3033+ std::cout << "Stopped: " << (std::string)app->appId() << std::endl;
3034+ });
3035+ registry.appPaused().connect([](std::shared_ptr<ubuntu::app_launch::Application> app,
3036+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
3037+ std::vector<pid_t> &pids) {
3038+ std::cout << "Paused: " << (std::string)app->appId() << " (";
3039+
3040+ for (auto pid : pids)
3041+ {
3042+ std::cout << std::to_string(pid) << " ";
3043+ }
3044+
3045+ std::cout << ")" << std::endl;
3046+ });
3047+ registry.appResumed().connect([](std::shared_ptr<ubuntu::app_launch::Application> app,
3048+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
3049+ std::vector<pid_t> &pids) {
3050+ std::cout << "Resumed: " << (std::string)app->appId() << " (";
3051+
3052+ for (auto pid : pids)
3053+ {
3054+ std::cout << std::to_string(pid) << " ";
3055+ }
3056+
3057+ std::cout << ")" << std::endl;
3058+ });
3059+ registry.appFailed().connect([](std::shared_ptr<ubuntu::app_launch::Application> app,
3060+ std::shared_ptr<ubuntu::app_launch::Application::Instance> instance,
3061+ ubuntu::app_launch::Registry::FailureType type) {
3062+ std::cout << "Failed: " << (std::string)app->appId();
3063+ switch (type)
3064+ {
3065+ case ubuntu::app_launch::Registry::FailureType::CRASH:
3066+ std::cout << " (crash)";
3067+ break;
3068+ case ubuntu::app_launch::Registry::FailureType::START_FAILURE:
3069+ std::cout << " (start failure)";
3070+ break;
3071+ }
3072+ std::cout << std::endl;
3073+ });
3074+
3075+ std::signal(SIGTERM, [](int signal) -> void { retval.set_value(EXIT_SUCCESS); });
3076+ return retval.get_future().get();
3077 }
3078
3079=== modified file 'tools/ubuntu-helper-list.cpp'
3080--- tools/ubuntu-helper-list.cpp 2016-02-09 21:12:54 +0000
3081+++ tools/ubuntu-helper-list.cpp 2016-11-07 20:56:13 +0000
3082@@ -17,8 +17,8 @@
3083 * Ted Gould <ted.gould@canonical.com>
3084 */
3085
3086+#include "libubuntu-app-launch/registry.h"
3087 #include <iostream>
3088-#include "libubuntu-app-launch/registry.h"
3089
3090 int main(int argc, char* argv[])
3091 {
3092
3093=== modified file 'tools/ubuntu-helper-start.cpp'
3094--- tools/ubuntu-helper-start.cpp 2016-05-03 01:46:27 +0000
3095+++ tools/ubuntu-helper-start.cpp 2016-11-07 20:56:13 +0000
3096@@ -17,9 +17,9 @@
3097 * Ted Gould <ted.gould@canonical.com>
3098 */
3099
3100-#include <iostream>
3101 #include "libubuntu-app-launch/helper.h"
3102 #include "libubuntu-app-launch/registry.h"
3103+#include <iostream>
3104
3105 int main(int argc, char* argv[])
3106 {
3107@@ -31,20 +31,25 @@
3108
3109 auto type = ubuntu::app_launch::Helper::Type::from_raw(argv[1]);
3110 auto appid = ubuntu::app_launch::AppID::find(argv[2]);
3111- if (appid.empty()) {
3112+ if (appid.empty())
3113+ {
3114 std::cerr << "Unable to find helper for appid: " << argv[1] << std::endl;
3115 return 1;
3116 }
3117
3118 auto registry = std::make_shared<ubuntu::app_launch::Registry>();
3119
3120- try {
3121+ try
3122+ {
3123 auto helper = ubuntu::app_launch::Helper::create(type, appid, registry);
3124
3125 helper->launch();
3126 return 0;
3127- } catch (std::runtime_error &e) {
3128- std::cerr << "Unable to find helper for '" << std::string(appid) << "' type '" << type.value() << "': " << e.what() << std::endl;
3129+ }
3130+ catch (std::runtime_error& e)
3131+ {
3132+ std::cerr << "Unable to find helper for '" << std::string(appid) << "' type '" << type.value()
3133+ << "': " << e.what() << std::endl;
3134 return 1;
3135 }
3136 }
3137
3138=== modified file 'tools/ubuntu-helper-stop.cpp'
3139--- tools/ubuntu-helper-stop.cpp 2016-05-03 01:46:27 +0000
3140+++ tools/ubuntu-helper-stop.cpp 2016-11-07 20:56:13 +0000
3141@@ -17,9 +17,9 @@
3142 * Ted Gould <ted.gould@canonical.com>
3143 */
3144
3145-#include <iostream>
3146 #include "libubuntu-app-launch/helper.h"
3147 #include "libubuntu-app-launch/registry.h"
3148+#include <iostream>
3149
3150 int main(int argc, char* argv[])
3151 {
3152@@ -31,14 +31,16 @@
3153
3154 auto type = ubuntu::app_launch::Helper::Type::from_raw(argv[1]);
3155 auto appid = ubuntu::app_launch::AppID::find(argv[2]);
3156- if (appid.empty()) {
3157+ if (appid.empty())
3158+ {
3159 std::cerr << "Unable to find helper for appid: " << argv[1] << std::endl;
3160 return 1;
3161 }
3162
3163 auto registry = std::make_shared<ubuntu::app_launch::Registry>();
3164
3165- try {
3166+ try
3167+ {
3168 auto helper = ubuntu::app_launch::Helper::create(type, appid, registry);
3169
3170 for (auto instance : helper->instances())
3171@@ -47,8 +49,11 @@
3172 }
3173
3174 return 0;
3175- } catch (std::runtime_error &e) {
3176- std::cerr << "Unable to find helper for '" << std::string(appid) << "' type '" << type.value() << "': " << e.what() << std::endl;
3177+ }
3178+ catch (std::runtime_error& e)
3179+ {
3180+ std::cerr << "Unable to find helper for '" << std::string(appid) << "' type '" << type.value()
3181+ << "': " << e.what() << std::endl;
3182 return 1;
3183 }
3184 }

Subscribers

People subscribed via source and target branches