Merge lp:~ricmm/unity-mir/extend_lifecycle_mir into lp:unity-mir

Proposed by Ricardo Mendoza
Status: Merged
Approved by: Gerry Boland
Approved revision: 53
Merged at revision: 62
Proposed branch: lp:~ricmm/unity-mir/extend_lifecycle_mir
Merge into: lp:unity-mir
Diff against target: 492 lines (+244/-16)
9 files modified
src/modules/Unity/ApplicationManager/ApplicationManager.pro (+4/-2)
src/modules/Unity/ApplicationManager/application.cpp (+64/-2)
src/modules/Unity/ApplicationManager/application.h (+14/-2)
src/modules/Unity/ApplicationManager/application_list_model.cpp (+9/-0)
src/modules/Unity/ApplicationManager/application_list_model.h (+1/-0)
src/modules/Unity/ApplicationManager/application_manager.cpp (+52/-9)
src/modules/Unity/ApplicationManager/application_manager.h (+4/-1)
src/modules/Unity/ApplicationManager/taskcontroller.cpp (+57/-0)
src/modules/Unity/ApplicationManager/taskcontroller.h (+39/-0)
To merge this branch: bzr merge lp:~ricmm/unity-mir/extend_lifecycle_mir
Reviewer Review Type Date Requested Status
Gerry Boland (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Albert Astals Cid (community) Needs Fixing
Review via email: mp+180942@code.launchpad.net

Commit message

* Add TaskController class for lower level process control
* Extend Application states to support the full lifecycle model
* Implement respawning applications (Stopped state) as part of the AppMgr
* Implement delayed process suspension
* Fix FTBFS due to Mir API change (SurfacePlacementStrategy)

To post a comment you must log in.
44. By Ricardo Mendoza

Set application to Running when it is actually focused, not when it has created its surfaces

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

merge trunk

46. By Ricardo Mendoza

Add missing files

47. By Ricardo Mendoza

Remove irrelevant file

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

src/unity-mir/initialsurfaceplacementstrategy.* seem to not merge cleanly

QTimer::singleShot(3000, this, SLOT(suspend()));
looks dangerous, what if the application gets back to Running state before those 3000 ms? You should stop the timer, in that case, shouldn't you?

the debug log in ApplicationListModel::indexOf is wrong

May make sense to abstract the qprocess code into a function so both startProcess and respawnProcess use it?

I don't understand why you moved m_mainStageApplications->remove(application); into the if, is it because now you can only stop the running app? Wouldn't it make sense to leave it out of the if as a safety mesure?

 QStringList arguments -> const QStringList &arguments

79 + }
80 + else if (m_state == Application::Stopped) {
looks like needs going one line up

Probably you don't need these includes in the header

454 +#include "application_manager.h"
455 +#include "mircommon/mir_toolkit/common.h"
456 +#include <mir/shell/application_session.h>

If you are going to ApplicationManager* appMgr = static_cast<ApplicationManager*>(parent()); in TaskController, i'd feel better if you changed the constructor from TaskController(QObject *parent) to TaskController(ApplicationManager *parent) :

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

QTimer::singleShot(3000, this, SLOT(suspend()));
Yep, it is risky. What if you switch to away from the app, then switch back to it, then switch away, all within 2900ms. Then app only gets 100ms before is frozen.

I would not bother abstracting the QProcess code just yet, as it will be replaced by upstart launch code soon, and that will provide the refactoring.

48. By Ricardo Mendoza

Dont use a singleShot timer, instead keep track of the timer object

49. By Ricardo Mendoza

Merge trunk

50. By Ricardo Mendoza

Build errors

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

You're still not stopping the timer, if application is set to running again, or if application stops while timer is running. So add

m_suspendTimer->stop() inside the "case Application::Running" and Stopped.

Albert has a few comments too, can you address those please.

51. By Ricardo Mendoza

* Fix logging in list model
* Make TaskController parent more explicitly typed
* Fix argument passing in application ctor

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) wrote :

As Gerry says it seems that you never stop the timer, i.e. m_suspendTimer->stop() inside the "case Application::Running" and Stopped.

Also the timer should be probably set to singleshot not to suspend it every 3 seconds

review: Needs Fixing
52. By Ricardo Mendoza

Stop timer if caught active when transitioning to the Stopped state

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
53. By Ricardo Mendoza

Make timer single shot

Revision history for this message
Albert Astals Cid (aacid) wrote :

Ok, looks good enough to me, it's a bit "brain hard" to realize we don't need the stop in Running because when the timer triggers it'll be ignored, it's also a bit of "a waste" of resources to have the timer running if we're not going to use it, but oh well :D

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

Looks good to me, approving

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/modules/Unity/ApplicationManager/ApplicationManager.pro'
2--- src/modules/Unity/ApplicationManager/ApplicationManager.pro 2013-08-21 09:36:24 +0000
3+++ src/modules/Unity/ApplicationManager/ApplicationManager.pro 2013-08-27 14:37:14 +0000
4@@ -23,14 +23,16 @@
5 desktopfilereader.cpp \
6 plugin.cpp \
7 applicationscreenshotprovider.cpp \
8- dbuswindowstack.cpp
9+ dbuswindowstack.cpp \
10+ taskcontroller.cpp
11
12 HEADERS += application_manager.h \
13 application.h \
14 application_list_model.h \
15 desktopfilereader.h \
16 applicationscreenshotprovider.h \
17- dbuswindowstack.h
18+ dbuswindowstack.h \
19+ taskcontroller.h
20
21 installPath = $$[QT_INSTALL_IMPORTS]/$$replace(uri, \\., /)
22
23
24=== modified file 'src/modules/Unity/ApplicationManager/application.cpp'
25--- src/modules/Unity/ApplicationManager/application.cpp 2013-08-16 15:54:51 +0000
26+++ src/modules/Unity/ApplicationManager/application.cpp 2013-08-27 14:37:14 +0000
27@@ -26,17 +26,24 @@
28 #include <mir/shell/application_session.h>
29
30 Application::Application(DesktopFileReader* desktopData, qint64 pid,
31- Application::Stage stage, Application::State state)
32+ Application::Stage stage, Application::State state,
33+ const QStringList& arguments, TaskController* taskController)
34 : m_desktopData(desktopData)
35 , m_pid(pid)
36 , m_stage(stage)
37 , m_state(state)
38 , m_focus(false)
39 , m_fullscreen(false)
40+ , m_arguments(arguments)
41+ , m_taskController(taskController)
42+ , m_suspendTimer(new QTimer(this))
43 {
44 DASSERT(desktopData != NULL);
45 DLOG("Application::Application (this=%p, desktopData=%p, pid=%lld, stage=%d, state=%d",
46 this, desktopData, pid, static_cast<int>(stage), static_cast<int>(state));
47+
48+ m_suspendTimer->setSingleShot(true);
49+ connect(m_suspendTimer, SIGNAL(timeout()), this, SLOT(suspend()));
50 }
51
52 Application::~Application()
53@@ -100,6 +107,16 @@
54 return m_session;
55 }
56
57+pid_t Application::pid() const
58+{
59+ return m_pid;
60+}
61+
62+void Application::setPid(pid_t pid)
63+{
64+ m_pid = pid;
65+}
66+
67 void Application::setSession(const std::shared_ptr<mir::shell::ApplicationSession>& session)
68 {
69 // TODO(greyback) what if called with new surface?
70@@ -129,7 +146,31 @@
71 {
72 DLOG("Application::setState (this=%p, state=%d)", this, static_cast<int>(state));
73 if (m_state != state) {
74- m_state = state;
75+ switch (state)
76+ {
77+ case Application::Suspended:
78+ if (m_state == Application::Running) {
79+ session()->set_lifecycle_state(mir_lifecycle_state_will_suspend);
80+ m_suspendTimer->start(3000);
81+ }
82+ break;
83+ case Application::Running:
84+ if (m_state == Application::Suspended) {
85+ resume();
86+ session()->set_lifecycle_state(mir_lifecycle_state_resumed);
87+ } else if (m_state == Application::Stopped) {
88+ respawn();
89+ state = Application::Starting;
90+ }
91+ break;
92+ case Application::Stopped:
93+ if (m_suspendTimer->isActive())
94+ m_suspendTimer->stop();
95+ break;
96+ default:
97+ break;
98+ }
99+ m_state = state;
100 emit stateChanged();
101 }
102 }
103@@ -151,3 +192,24 @@
104 emit fullscreenChanged();
105 }
106 }
107+
108+void Application::suspend()
109+{
110+ DLOG("Application::suspend (this=%p)", this);
111+
112+ m_taskController->do_suspend(this);
113+}
114+
115+void Application::resume()
116+{
117+ DLOG("Application::resume (this=%p)", this);
118+
119+ m_taskController->do_resume(this);
120+}
121+
122+void Application::respawn()
123+{
124+ DLOG("Application::respawn (this=%p)", this);
125+
126+ m_taskController->do_respawn(this);
127+}
128
129=== modified file 'src/modules/Unity/ApplicationManager/application.h'
130--- src/modules/Unity/ApplicationManager/application.h 2013-08-14 14:24:17 +0000
131+++ src/modules/Unity/ApplicationManager/application.h 2013-08-27 14:37:14 +0000
132@@ -29,6 +29,7 @@
133
134 class QImage;
135 class DesktopFileReader;
136+class TaskController;
137 namespace mir { namespace shell { class ApplicationSession; }}
138
139 class Application : public QObject {
140@@ -48,9 +49,9 @@
141
142 public:
143 enum Stage { MainStage, SideStage };
144- enum State { Starting, Running };
145+ enum State { Starting, Running, Suspended, Stopped };
146
147- Application(DesktopFileReader* desktopData, qint64 pid, Stage stage, State state);
148+ Application(DesktopFileReader* desktopData, qint64 pid, Stage stage, State state, const QStringList& arguments, TaskController* taskController);
149 ~Application();
150
151 QString desktopFile() const;
152@@ -65,6 +66,11 @@
153 bool fullscreen() const;
154 std::shared_ptr<mir::shell::ApplicationSession> session() const;
155
156+public slots:
157+ void suspend();
158+ void resume();
159+ void respawn();
160+
161 Q_SIGNALS:
162 void stageChanged();
163 void stateChanged();
164@@ -72,6 +78,8 @@
165 void fullscreenChanged();
166
167 private:
168+ pid_t pid() const;
169+ void setPid(pid_t pid);
170 void setStage(Stage stage);
171 void setState(State state);
172 void setFocus(bool focus);
173@@ -87,7 +95,11 @@
174 bool m_fullscreen;
175 std::shared_ptr<mir::shell::ApplicationSession> m_session;
176 QString m_sessionName;
177+ QStringList m_arguments;
178+ TaskController* m_taskController;
179+ QTimer* m_suspendTimer;
180
181+ friend class TaskController;
182 friend class ApplicationManager;
183 friend class ApplicationListModel;
184 };
185
186=== modified file 'src/modules/Unity/ApplicationManager/application_list_model.cpp'
187--- src/modules/Unity/ApplicationManager/application_list_model.cpp 2013-08-15 15:37:00 +0000
188+++ src/modules/Unity/ApplicationManager/application_list_model.cpp 2013-08-27 14:37:14 +0000
189@@ -64,6 +64,15 @@
190 return data(index(row), 0);
191 }
192
193+int ApplicationListModel::indexOf(Application* application) const
194+{
195+ DASSERT(application != NULL);
196+ DLOG("ApplicationListModel::indexOf (this=%p, application='%s')", this,
197+ application->name().toLatin1().data());
198+
199+ return m_applications.indexOf(application);
200+}
201+
202 void ApplicationListModel::move(int from, int to)
203 {
204 DLOG("ApplicationListModel::move (this=%p, from=%d, to=%d)", this, from, to);
205
206=== modified file 'src/modules/Unity/ApplicationManager/application_list_model.h'
207--- src/modules/Unity/ApplicationManager/application_list_model.h 2013-08-14 14:24:17 +0000
208+++ src/modules/Unity/ApplicationManager/application_list_model.h 2013-08-27 14:37:14 +0000
209@@ -37,6 +37,7 @@
210 int rowCount(const QModelIndex& parent = QModelIndex()) const;
211 QVariant data(const QModelIndex& index, int role) const;
212 QHash<int,QByteArray> roleNames() const { return m_roleNames; }
213+ int indexOf(Application* application) const;
214 Q_INVOKABLE QVariant get(int index) const;
215 Q_INVOKABLE void move(int from, int to);
216
217
218=== modified file 'src/modules/Unity/ApplicationManager/application_manager.cpp'
219--- src/modules/Unity/ApplicationManager/application_manager.cpp 2013-08-23 17:26:40 +0000
220+++ src/modules/Unity/ApplicationManager/application_manager.cpp 2013-08-27 14:37:14 +0000
221@@ -75,6 +75,7 @@
222 qsrand(QTime::currentTime().msec());
223
224 m_dbusWindowStack = new DBusWindowStack(this);
225+ m_taskController = new TaskController(this);
226 }
227
228 ApplicationManager::~ApplicationManager()
229@@ -120,6 +121,40 @@
230 m_mirServer->the_session_manager()->set_focus_to(NULL); //FIXME(greyback)
231 }
232
233+Application* ApplicationManager::respawnApplication(Application* application)
234+{
235+ DLOG("ApplicationManager::respawnApplication(this=%p, application=%p)", this, application);
236+
237+ // Start process - set correct environment
238+ setenv("QT_QPA_PLATFORM", "ubuntumirclient", 1);
239+
240+ bool result;
241+ qint64 pid = 0;
242+ struct passwd* passwd = getpwuid(getuid());
243+ DLOG("current working directory: '%s' - args='%s'", passwd ? passwd->pw_dir : "/", application->m_arguments.join(' ').toLatin1().data());
244+ QProcess builder;
245+ builder.setProcessChannelMode(QProcess::ForwardedChannels);
246+ QString exec(application->m_arguments[0]);
247+ application->m_arguments.removeFirst();
248+ result = builder.startDetached(exec, application->m_arguments, QString(passwd ? passwd->pw_dir : "/"), &pid);
249+ application->m_arguments.prepend(exec);
250+ DLOG_IF(result == false, "process failed to start");
251+ if (result) {
252+ // Set existing application's pid to new instance
253+ application->setPid(pid);
254+
255+ // Push to front
256+ if (m_mainStageApplications->rowCount() > 1)
257+ m_mainStageApplications->move(m_mainStageApplications->indexOf(application), 0);
258+
259+ DLOG("builder '%s' respawned with pid %lld", application->name().toLatin1().data(), pid);
260+ return application;
261+ } else {
262+ DLOG("builder '%s' failed to respawn", application->name().toLatin1().data());
263+ return NULL;
264+ }
265+}
266+
267 Application* ApplicationManager::startProcess(QString desktopFile, ApplicationManager::ExecFlags flags,
268 QStringList arguments)
269 {
270@@ -191,18 +226,23 @@
271 path = passwd->pw_dir;
272 }
273 DLOG("current working directory: '%s'", path.toLatin1().data());
274- result = QProcess::startDetached(exec, arguments, path, &pid);
275+ QProcess builder;
276+ builder.setProcessChannelMode(QProcess::ForwardedChannels);
277+ result = builder.startDetached(exec, arguments, path, &pid);
278+
279 DLOG_IF(result == false, "process failed to start");
280 if (result) {
281 #endif
282-
283 DLOG("process started with pid %lld, adding '%s' to application lists", pid, desktopData->name().toLatin1().data());
284+ arguments.prepend(exec);
285 Application* application = new Application(
286 desktopData, pid,
287 (flags.testFlag(ApplicationManager::ForceMainStage)
288 || desktopData->stageHint() != "SideStage")
289 ? Application::MainStage : Application::SideStage,
290- Application::Starting //FIXME(greyback): assuming running immediately
291+ Application::Starting, //FIXME(greyback): assuming running immediately
292+ arguments,
293+ m_taskController
294 );
295
296 m_mainStageApplications->add(application);
297@@ -319,7 +359,10 @@
298 }
299
300 DLOG("Existing process with pid %lld appeared, adding '%s' to application lists", pid, desktopData->name().toLatin1().data());
301- application = new Application(desktopData, pid, stage, Application::Starting);
302+
303+ QString argStr(command.data());
304+ QStringList arguments(argStr.split(' '));
305+ application = new Application(desktopData, pid, stage, Application::Starting, arguments, m_taskController);
306 m_mainStageApplications->add(application);
307 authorized = true;
308 }
309@@ -348,9 +391,10 @@
310 if (application == m_mainStageFocusedApplication) {
311 // TODO(greyback) What to do?? Focus next app, or unfocus everything??
312 m_mainStageFocusedApplication = NULL;
313+ m_mainStageApplications->remove(application);
314 Q_EMIT mainStageFocusedApplicationChanged();
315 }
316- m_mainStageApplications->remove(application);
317+ application->setState(Application::Stopped);
318 m_dbusWindowStack->WindowDestroyed(0, application->name());
319 }
320 }
321@@ -359,7 +403,7 @@
322 {
323 DLOG("ApplicationManager::sessionFocused (this=%p, application=%s)", this, session->name().c_str());
324 Application* application = m_mainStageApplications->getApplicationWithSession(session);
325-
326+
327 // Don't give application focus until it has created it's surface, when it is set as state "Running"
328 if (application && application->state() != Application::Starting
329 && application != m_mainStageFocusedApplication) {
330@@ -372,6 +416,7 @@
331 DLOG("ApplicationManager::sessionUnfocused (this=%p)", this);
332 if (NULL != m_mainStageFocusedApplication) {
333 m_mainStageFocusedApplication->setFocus(false);
334+ m_mainStageFocusedApplication->setState(Application::Suspended);
335 m_mainStageFocusedApplication = NULL;
336 Q_EMIT mainStageFocusedApplicationChanged();
337 m_dbusWindowStack->FocusedWindowChanged(0, QString(), 0);
338@@ -386,10 +431,7 @@
339
340 Application* application = m_mainStageApplications->getApplicationWithSession(session);
341 if (application && application->state() == Application::Starting) {
342- application->setState(Application::Running);
343-
344 m_dbusWindowStack->WindowCreated(0, application->name());
345-
346 // only when Session creates a Surface will we actually mark it focused
347 setFocused(application);
348 }
349@@ -399,6 +441,7 @@
350 {
351 m_mainStageFocusedApplication = application;
352 m_mainStageFocusedApplication->setFocus(true);
353+ m_mainStageFocusedApplication->setState(Application::Running);
354 Q_EMIT mainStageFocusedApplicationChanged();
355 m_dbusWindowStack->FocusedWindowChanged(0, application->name(), application->stage());
356 }
357
358=== modified file 'src/modules/Unity/ApplicationManager/application_manager.h'
359--- src/modules/Unity/ApplicationManager/application_manager.h 2013-08-15 18:10:19 +0000
360+++ src/modules/Unity/ApplicationManager/application_manager.h 2013-08-27 14:37:14 +0000
361@@ -24,7 +24,7 @@
362 #include <QObject>
363 #include <QStringList>
364
365-#include "application.h"
366+#include "taskcontroller.h"
367 #include "application_list_model.h"
368
369 class ShellServerConfiguration;
370@@ -102,13 +102,16 @@
371
372 private:
373 void setFocused(Application *application);
374+ Application* respawnApplication(Application* application);
375
376 ApplicationListModel* m_mainStageApplications;
377 Application* m_mainStageFocusedApplication; // remove as Mir has API for this
378 ShellServerConfiguration* m_mirServer;
379 DBusWindowStack* m_dbusWindowStack;
380+ TaskController* m_taskController;
381
382 friend class DBusWindowStack;
383+ friend class TaskController;
384 };
385
386 Q_DECLARE_OPERATORS_FOR_FLAGS(ApplicationManager::ExecFlags)
387
388=== added file 'src/modules/Unity/ApplicationManager/taskcontroller.cpp'
389--- src/modules/Unity/ApplicationManager/taskcontroller.cpp 1970-01-01 00:00:00 +0000
390+++ src/modules/Unity/ApplicationManager/taskcontroller.cpp 2013-08-27 14:37:14 +0000
391@@ -0,0 +1,57 @@
392+/*
393+ * Copyright (C) 2013 Canonical, Ltd.
394+ *
395+ * This program is free software: you can redistribute it and/or modify it under
396+ * the terms of the GNU Lesser General Public License version 3, as published by
397+ * the Free Software Foundation.
398+ *
399+ * This program is distributed in the hope that it will be useful, but WITHOUT
400+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
401+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
402+ * Lesser General Public License for more details.
403+ *
404+ * You should have received a copy of the GNU Lesser General Public License
405+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
406+ *
407+ * Authored by: Ricardo Mendoza <ricardo.mendoza@canonical.com>
408+ */
409+
410+#include "logging.h"
411+#include "taskcontroller.h"
412+#include "application_manager.h"
413+#include <signal.h>
414+#include <unistd.h>
415+
416+TaskController::TaskController(ApplicationManager *parent) :
417+ QObject(parent)
418+{
419+ DLOG("TaskController::TaskController (this=%p)", this);
420+}
421+
422+TaskController::~TaskController()
423+{
424+ DLOG("TaskController::~TaskController (this=%p)", this);
425+}
426+
427+void TaskController::do_suspend(Application* application)
428+{
429+ DLOG("TaskController::do_suspend (this=%p, application=%p)", this, application);
430+ if (application->state() == Application::Suspended)
431+ kill(application->pid(), SIGSTOP);
432+}
433+
434+void TaskController::do_resume(Application* application)
435+{
436+ DLOG("TaskController::do_resume (this=%p, application=%p)", this, application);
437+ if (application->state() == Application::Suspended)
438+ kill(application->pid(), SIGCONT);
439+}
440+
441+void TaskController::do_respawn(Application* application)
442+{
443+ DLOG("TaskController::do_respwn (this=%p, application=%p)", this, application);
444+ if (application->state() == Application::Stopped) {
445+ ApplicationManager *appMgr = static_cast<ApplicationManager*>(parent());
446+ appMgr->respawnApplication(application);
447+ }
448+}
449
450=== added file 'src/modules/Unity/ApplicationManager/taskcontroller.h'
451--- src/modules/Unity/ApplicationManager/taskcontroller.h 1970-01-01 00:00:00 +0000
452+++ src/modules/Unity/ApplicationManager/taskcontroller.h 2013-08-27 14:37:14 +0000
453@@ -0,0 +1,39 @@
454+/*
455+ * Copyright (C) 2013 Canonical, Ltd.
456+ *
457+ * This program is free software: you can redistribute it and/or modify it under
458+ * the terms of the GNU Lesser General Public License version 3, as published by
459+ * the Free Software Foundation.
460+ *
461+ * This program is distributed in the hope that it will be useful, but WITHOUT
462+ * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
463+ * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
464+ * Lesser General Public License for more details.
465+ *
466+ * You should have received a copy of the GNU Lesser General Public License
467+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
468+ *
469+ * Authored by: Ricardo Mendoza <ricardo.mendoza@canonical.com>
470+ */
471+
472+#ifndef TASKCONTROLLER_H
473+#define TASKCONTROLLER_H
474+
475+#include <QObject>
476+
477+#include "application.h"
478+
479+class ApplicationManager;
480+class TaskController : public QObject
481+{
482+ Q_OBJECT
483+public:
484+ explicit TaskController(ApplicationManager* parent);
485+ ~TaskController();
486+
487+ void do_resume(Application* application);
488+ void do_suspend(Application* application);
489+ void do_respawn(Application* application);
490+};
491+
492+#endif // TASKCONTROLLER_H

Subscribers

People subscribed via source and target branches