Merge lp:~mzanetti/unity-mir/screenshotting-focusing-api into lp:unity-mir

Proposed by Michael Zanetti
Status: Merged
Approved by: Gerry Boland
Approved revision: 188
Merged at revision: 207
Proposed branch: lp:~mzanetti/unity-mir/screenshotting-focusing-api
Merge into: lp:unity-mir
Diff against target: 672 lines (+270/-103)
13 files modified
debian/control (+3/-1)
debian/libunity-mir1.install (+1/-1)
src/modules/Unity/Application/ApplicationImage.qml (+0/-44)
src/modules/Unity/Application/CMakeLists.txt (+10/-7)
src/modules/Unity/Application/application.cpp (+28/-0)
src/modules/Unity/Application/application.h (+6/-0)
src/modules/Unity/Application/application_manager.cpp (+110/-1)
src/modules/Unity/Application/application_manager.h (+10/-0)
src/modules/Unity/Application/applicationscreenshotprovider.cpp (+15/-45)
src/modules/Unity/Application/applicationscreenshotprovider.h (+0/-2)
src/modules/Unity/Application/plugin.cpp (+1/-1)
tests/CMakeLists.txt (+2/-1)
tests/application_manager_test.cpp (+84/-0)
To merge this branch: bzr merge lp:~mzanetti/unity-mir/screenshotting-focusing-api
Reviewer Review Type Date Requested Status
Michał Sawicz Approve
Gerry Boland (community) Approve
PS Jenkins bot (community) continuous-integration Needs Fixing
Ubuntu Unity PS integration team packaging Pending
Review via email: mp+199811@code.launchpad.net

Commit message

Implement API changes for the screenshotting and focusing api

Description of the change

* Are there any related MPs required for this MP to build/function as expected? Please list.

https://code.launchpad.net/~mzanetti/unity-api/new-screenshot-and-focusing-api/+merge/199810
https://code.launchpad.net/~mzanetti/unity8/right-edge-2/+merge/204798

 * Did you perform an exploratory manual test run of your code change and any related functionality?

Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?

Yes

 * If you changed the UI, has there been a design review?

N/A

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :

=== removed file 'src/modules/Unity/Application/ApplicationImage.qml'
This will break SurfaceFlinger support. We good with that?

review: Needs Information
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> === removed file 'src/modules/Unity/Application/ApplicationImage.qml'
> This will break SurfaceFlinger support. We good with that?

I asked around about that in January and answer from Kevin was yes. I'll get a confirmation that we're good to merge this now (effectively breaking SF support).

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

+void ApplicationManager::screenshotUpdated()
+ if (!m_nextFocusedAppId.isEmpty()) {
+ Q_EMIT focusRequested(m_nextFocusedAppId);
+ m_nextFocusedAppId.clear();
+ }
I don't follow this. Why emit a focusRequest after screenshot is updated? Is it just the best time to do it?

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

+ engine->addImageProvider(QLatin1String("application"), new ApplicationScreenshotProvider(appManager));

If I see a QUrl like "application://calculator-app" - is that a url for a url dispatcher? Or a url for that app's screenshot? I'm just a tad worried about duplicate purposes of urls of that form. Thought?

180. By Michael Zanetti

update according to latest changes in unity-api

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> > === removed file 'src/modules/Unity/Application/ApplicationImage.qml'
> > This will break SurfaceFlinger support. We good with that?
>
> I asked around about that in January and answer from Kevin was yes. I'll get a
> confirmation that we're good to merge this now (effectively breaking SF
> support).

Confirmed. Good to go.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> +void ApplicationManager::screenshotUpdated()
> + if (!m_nextFocusedAppId.isEmpty()) {
> + Q_EMIT focusRequested(m_nextFocusedAppId);
> + m_nextFocusedAppId.clear();
> + }
> I don't follow this. Why emit a focusRequest after screenshot is updated? Is
> it just the best time to do it?

In order to make the QML code less complex, when someone calls requestFocusApplication() (formerly activateApplication()), I first update the screenshot, and then emit the requestFocus() signal. So the QML code has everything in place to start animating, instead of having to do the updateScreenshot() stuff from there.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> + engine->addImageProvider(QLatin1String("application"), new
> ApplicationScreenshotProvider(appManager));
>
> If I see a QUrl like "application://calculator-app" - is that a url for a url
> dispatcher? Or a url for that app's screenshot?

that's a url-dispatcher url :)

the imageprovider's url is

image://application/calculator-app/12345678

> I'm just a tad worried about
> duplicate purposes of urls of that form. Thought?

After clearing the confusion, does it still sound too similar to you?

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

> After clearing the confusion, does it still sound too similar to you?
/me idiot. You're right. Plz ignore

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

> > +void ApplicationManager::screenshotUpdated()
> > + if (!m_nextFocusedAppId.isEmpty()) {
> > + Q_EMIT focusRequested(m_nextFocusedAppId);
> > + m_nextFocusedAppId.clear();
> > + }
> > I don't follow this. Why emit a focusRequest after screenshot is updated? Is
> > it just the best time to do it?
>
> In order to make the QML code less complex, when someone calls
> requestFocusApplication() (formerly activateApplication()), I first update the
> screenshot, and then emit the requestFocus() signal. So the QML code has
> everything in place to start animating, instead of having to do the
> updateScreenshot() stuff from there.

Okay am understanding the why. But this method is strange to read, so it needs at least a comment justifying it. Might a second slot, connected to Application::screenshotChanged make the logic a little more obvious?

Also using "sender()" to get the signal originator always feels dangerous to me, I'm not definite if the pointer returned always exists, does it?

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

improve comments, make sure we don't crash if the app disappears before the screenshotUpdated() signal is delivered

Revision history for this message
Michael Zanetti (mzanetti) wrote :

> Okay am understanding the why. But this method is strange to read, so it needs
> at least a comment justifying it. Might a second slot, connected to
> Application::screenshotChanged make the logic a little more obvious?
>
> Also using "sender()" to get the signal originator always feels dangerous to
> me, I'm not definite if the pointer returned always exists, does it?

I've added some comments describing why this happens here. You are right about the sender() thing. Thinking about it, it could happen that the Application is gone by the time this signal is delivered, given that the signal emission happens in a mir thread and hence the connection a queued one. - I've fixed it to make sure the we don't crash.

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

Ok, that'll do. I just need to test this with your unity8 work, before I can approve

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

merge trunk

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

make it compile again

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

since I managed to break ApplicationManager easily in the past - it would be nice if you could add tests that cover suspend / resume and the changes to focusing..

184. By Michael Zanetti

ouch...

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

add a test that checks if calling applicationManager.setSuspended(true) suspends the running app

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

> since I managed to break ApplicationManager easily in the past - it would be
> nice if you could add tests that cover suspend / resume and the changes to
> focusing..

Absolutely. Test for the suspended added.

Btw. I noticed that the QtTests in there are not executed and broken/outdated. I tried to add a test for the requestFocus, but as we're using google test to test a Qt api instead of QtTest here I can't use QSignalSpy to verify it emits focusRequested.

186. By Michael Zanetti

merge trunk

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

Bump?

187. By Michael Zanetti

depend on latest libunity-api-dev package

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

added a test for focusRequested

Revision history for this message
Michael Zanetti (mzanetti) wrote :

turns out, QSignalSpy does work after all. No idea why I failed to get anything into it on my first attempt.

All the comments are fixed now. Good to go?

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

LGTM

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

* Did you perform an exploratory manual test run of the code change and any related functionality?
Yes, works fine
* Did CI run pass? If not, please explain why.
MR depends on this landing first: https://code.launchpad.net/~mzanetti/unity-api/new-screenshot-and-focusing-api

189. By Michael Zanetti

update install dir for unity-application plugin

190. By Michael Zanetti

fix plugin installing

191. By Michael Zanetti

update install path

Revision history for this message
Michał Sawicz (saviq) wrote :

+1 on the plugin installation dirs.

review: Approve
192. By Michael Zanetti

also provide the virtual application-impl

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2014-03-14 20:26:18 +0000
3+++ debian/control 2014-03-31 15:12:36 +0000
4@@ -12,7 +12,7 @@
5 libmirserver-dev (>= 0.1.7),
6 libmirclient-dev (>= 0.1.7),
7 libprocess-cpp-dev,
8- libunity-api-dev,
9+ libunity-api-dev (>= 7.80.6),
10 libupstart-app-launch2-dev,
11 qt5-default,
12 qtbase5-dev,
13@@ -34,6 +34,8 @@
14 Replaces: unity-mir,
15 Conflicts: unity-mir,
16 Provides: unity-mir,
17+ unity-application-impl,
18+ unity-application-impl-2,
19 Description: Qt plugins for Unity specific Mir APIs
20 Unity-Mir provides Qt bindings for Mir features that are not exposed
21 through the QtUbuntu QPA plugins but needed for shell functionality.
22
23=== modified file 'debian/libunity-mir1.install'
24--- debian/libunity-mir1.install 2014-01-20 03:02:12 +0000
25+++ debian/libunity-mir1.install 2014-03-31 15:12:36 +0000
26@@ -1,2 +1,2 @@
27 usr/lib/*/libunity-mir.so.*
28-usr/lib/*/qt5/*
29+usr/lib/*/unity8/qml/*
30
31=== removed file 'src/modules/Unity/Application/ApplicationImage.qml'
32--- src/modules/Unity/Application/ApplicationImage.qml 2014-02-21 11:22:51 +0000
33+++ src/modules/Unity/Application/ApplicationImage.qml 1970-01-01 00:00:00 +0000
34@@ -1,44 +0,0 @@
35-/*
36- * Copyright (C) 2013 Canonical, Ltd.
37- *
38- * This program is free software: you can redistribute it and/or modify it under
39- * the terms of the GNU Lesser General Public License version 3, as published by
40- * the Free Software Foundation.
41- *
42- * This program is distributed in the hope that it will be useful, but WITHOUT
43- * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
44- * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45- * Lesser General Public License for more details.
46- *
47- * You should have received a copy of the GNU Lesser General Public License
48- * along with this program. If not, see <http://www.gnu.org/licenses/>.
49- */
50-
51-import QtQuick 2.0
52-
53-Item {
54- id: root
55- property var source: null
56- readonly property bool ready: source && (image.status == Image.Ready)
57- property alias sourceSize: image.sourceSize
58-
59- function scheduleUpdate() {
60- image.source = "";
61- if (source) {
62- image.source = Qt.binding( function() {
63- return source ? "image://screenshot/" + source.appId : "";
64- });
65- }
66- }
67-
68- function updateFromCache() {
69- scheduleUpdate();
70- }
71-
72- Image {
73- id: image
74- anchors.fill: parent
75- source: (root.source) ? "image://screenshot/" + root.source.appId : ""
76- cache: false
77- }
78-}
79
80=== modified file 'src/modules/Unity/Application/CMakeLists.txt'
81--- src/modules/Unity/Application/CMakeLists.txt 2014-03-11 01:24:36 +0000
82+++ src/modules/Unity/Application/CMakeLists.txt 2014-03-31 15:12:36 +0000
83@@ -83,16 +83,19 @@
84 ubuntu_application_api_mirserver
85 )
86
87-# Ideally, we would read the plugin installation location from cmake
88-# but this does not work currently.
89-set(PLUGIN_INSTALL_LOCATION "${CMAKE_INSTALL_LIBDIR}/qt5/imports/Unity-Mir/Unity/Application")
90
91-message(STATUS "Installing Qt5 Unity-Application plugin to: ${PLUGIN_INSTALL_LOCATION}")
92+execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} --variable=plugindir_suffix unity-shell-api OUTPUT_VARIABLE SHELL_PLUGINDIR OUTPUT_STRIP_TRAILING_WHITESPACE)
93+if(SHELL_PLUGINDIR STREQUAL "")
94+ set(SHELL_PLUGINDIR ${CMAKE_INSTALL_LIBDIR}/unity8/qml)
95+endif()
96
97 install(
98 TARGETS unityapplicationplugin
99- LIBRARY DESTINATION ${PLUGIN_INSTALL_LOCATION})
100+ ARCHIVE DESTINATION ${SHELL_PLUGINDIR}/Unity/Application
101+ LIBRARY DESTINATION ${SHELL_PLUGINDIR}/Unity/Application
102+)
103
104 install(
105- FILES qmldir ApplicationImage.qml OSKController.qml
106- DESTINATION ${PLUGIN_INSTALL_LOCATION})
107+ FILES qmldir OSKController.qml
108+ DESTINATION ${SHELL_PLUGINDIR}/Unity/Application
109+)
110
111=== modified file 'src/modules/Unity/Application/application.cpp'
112--- src/modules/Unity/Application/application.cpp 2014-03-11 01:24:36 +0000
113+++ src/modules/Unity/Application/application.cpp 2014-03-31 15:12:36 +0000
114@@ -119,6 +119,11 @@
115 return m_focused;
116 }
117
118+QUrl Application::screenshot() const
119+{
120+ return m_screenshot;
121+}
122+
123 bool Application::fullscreen() const
124 {
125 return m_fullscreen;
126@@ -172,6 +177,29 @@
127 }
128 }
129
130+QImage Application::screenshotImage() const
131+{
132+ return m_screenshotImage;
133+}
134+
135+void Application::updateScreenshot()
136+{
137+ session()->take_snapshot(
138+ [&](mir::shell::Snapshot const& snapshot)
139+ {
140+ DLOG("ApplicationScreenshotProvider - Mir snapshot ready with size %d x %d",
141+ snapshot.size.height.as_int(), snapshot.size.width.as_int());
142+
143+ m_screenshotImage = QImage( (const uchar*)snapshot.pixels, // since we mirror, no need to offset starting position
144+ snapshot.size.width.as_int(),
145+ snapshot.size.height.as_int(),
146+ QImage::Format_ARGB32_Premultiplied).mirrored();
147+
148+ m_screenshot = QString("image://application/%1/%2").arg(m_desktopData->appId()).arg(QDateTime::currentMSecsSinceEpoch());
149+ Q_EMIT screenshotChanged(m_screenshot);
150+ });
151+}
152+
153 void Application::setState(Application::State state)
154 {
155 DLOG("Application::setState (this=%p, state=%d)", this, static_cast<int>(state));
156
157=== modified file 'src/modules/Unity/Application/application.h'
158--- src/modules/Unity/Application/application.h 2014-03-11 01:24:36 +0000
159+++ src/modules/Unity/Application/application.h 2014-03-31 15:12:36 +0000
160@@ -61,12 +61,16 @@
161 Stage stage() const override;
162 State state() const override;
163 bool focused() const override;
164+ QUrl screenshot() const override;
165
166 bool visible() const;
167
168 void setStage(Stage stage);
169 void setVisible(const bool);
170
171+ QImage screenshotImage() const;
172+ void updateScreenshot();
173+
174 bool isValid() const;
175 QString desktopFile() const;
176 QString exec() const;
177@@ -98,6 +102,8 @@
178 Stage m_stage;
179 State m_state;
180 bool m_focused;
181+ QUrl m_screenshot;
182+ QImage m_screenshotImage;
183 bool m_fullscreen;
184 bool m_visible; // duplicating internal Mir data :(
185 std::shared_ptr<mir::shell::Session> m_session;
186
187=== modified file 'src/modules/Unity/Application/application_manager.cpp'
188--- src/modules/Unity/Application/application_manager.cpp 2014-03-17 17:44:24 +0000
189+++ src/modules/Unity/Application/application_manager.cpp 2014-03-31 15:12:36 +0000
190@@ -240,6 +240,8 @@
191 return QVariant::fromValue((int)application->state());
192 case RoleFocused:
193 return QVariant::fromValue(application->focused());
194+ case RoleScreenshot:
195+ return QVariant::fromValue(application->screenshot());
196 default:
197 return QVariant();
198 }
199@@ -250,6 +252,10 @@
200
201 Application* ApplicationManager::get(int index) const
202 {
203+ DLOG("ApplicationManager::get (this=%p, index=%i, count=%i)", this, index, m_applications.count());
204+ if (index < 0 || index >= m_applications.count()) {
205+ return nullptr;
206+ }
207 return m_applications.at(index);
208 }
209
210@@ -263,6 +269,33 @@
211 return nullptr;
212 }
213
214+bool ApplicationManager::requestFocusApplication(const QString &appId)
215+{
216+ DLOG("ApplicationManager::requestFocusApplication (this=%p, appId=%s)", this, qPrintable(appId));
217+ Application *application = findApplication(appId);
218+
219+ if (!application) {
220+ DLOG("No such running application '%s'", qPrintable(appId));
221+ return false;
222+ }
223+
224+ if (application == m_focusedApplication) {
225+ DLOG("Application %s is already focused", qPrintable(appId));
226+ return false;
227+ }
228+
229+ // If there is a currently focused application, first update the screenshot
230+ // for it and only emit focusRequested() when the screenshot is ready.
231+ Application *currentlyFocusedApplication = findApplication(focusedApplicationId());
232+ if (currentlyFocusedApplication) {
233+ m_nextFocusedAppId = appId;
234+ currentlyFocusedApplication->updateScreenshot();
235+ } else {
236+ Q_EMIT focusRequested(appId);
237+ }
238+ return true;
239+}
240+
241 QString ApplicationManager::focusedApplicationId() const
242 {
243 if (m_focusedApplication) {
244@@ -272,11 +305,35 @@
245 }
246 }
247
248+bool ApplicationManager::suspended() const
249+{
250+ return m_suspended;
251+}
252+
253+void ApplicationManager::setSuspended(bool suspended)
254+{
255+ if (suspended == m_suspended) {
256+ return;
257+ }
258+ m_suspended = suspended;
259+ Q_EMIT suspendedChanged();
260+
261+ if (m_suspended) {
262+ suspendApplication(m_mainStageApplication);
263+ suspendApplication(m_sideStageApplication);
264+ } else {
265+ resumeApplication(m_mainStageApplication);
266+ resumeApplication(m_sideStageApplication);
267+ }
268+}
269+
270 void ApplicationManager::suspendApplication(Application *application)
271 {
272 if (application == nullptr)
273 return;
274
275+ updateScreenshot(application->appId());
276+
277 DLOG("ApplicationManager::suspend(this=%p, application(%p)->appId(%s) )",this, application, qPrintable(application->appId()));
278 // Present in exceptions list, return.
279 if (!m_lifecycleExceptions.filter(application->appId().section('_',0,0)).empty())
280@@ -286,6 +343,15 @@
281 application->setState(Application::Suspended);
282 }
283
284+void ApplicationManager::resumeApplication(Application *application)
285+{
286+ if (application == nullptr)
287+ return;
288+
289+ if (application->state() != Application::Running)
290+ application->setState(Application::Running);
291+}
292+
293 bool ApplicationManager::focusApplication(const QString &appId)
294 {
295 Application *application = findApplication(appId);
296@@ -311,8 +377,11 @@
297 int from = m_applications.indexOf(application);
298 move(from, m_applications.length()-1);
299 } else {
300- if (application->session())
301+ if (application->session()) {
302 m_focusController->set_focus_to(application->session());
303+ int from = m_applications.indexOf(application);
304+ move(from, 0);
305+ }
306 }
307
308 // FIXME(dandrader): lying here. The operation is async. So we will only know whether
309@@ -436,6 +505,21 @@
310 return result;
311 }
312
313+bool ApplicationManager::updateScreenshot(const QString &appId)
314+{
315+ Application *application = findApplication(appId);
316+
317+ if (!application) {
318+ DLOG("No such running application '%s'", qPrintable(appId));
319+ return false;
320+ }
321+
322+ application->updateScreenshot();
323+ QModelIndex appIndex = findIndex(application);
324+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
325+ return true;
326+}
327+
328 void ApplicationManager::stopStartingApplication(const QString &appId)
329 {
330 Application *application = findApplication(appId);
331@@ -512,6 +596,26 @@
332 }
333 }
334
335+void ApplicationManager::screenshotUpdated()
336+{
337+ if (sender()) {
338+ Application *application = static_cast<Application*>(sender());
339+ QModelIndex appIndex = findIndex(application);
340+ Q_EMIT dataChanged(appIndex, appIndex, QVector<int>() << RoleScreenshot);
341+
342+ DLOG("ApplicationManager::screenshotUpdated: Received new screenshot for %s", qPrintable(application->appId()));
343+ } else {
344+ DLOG("ApplicationManager::screenshotUpdated: Received screenshotUpdated signal but application has disappeard.");
345+ }
346+
347+ // Now that the screenshot is ready, check if there
348+ // is a pending app focusing request.
349+ if (!m_nextFocusedAppId.isEmpty()) {
350+ Q_EMIT focusRequested(m_nextFocusedAppId);
351+ m_nextFocusedAppId.clear();
352+ }
353+}
354+
355 /************************************* Mir-side methods *************************************/
356
357 void ApplicationManager::authorizeSession(const quint64 pid, bool &authorized)
358@@ -765,6 +869,7 @@
359 m_focusedApplication = application;
360 m_focusedApplication->setFocused(true);
361 m_focusedApplication->setState(Application::Running);
362+ move(m_applications.indexOf(application), 0);
363 Q_EMIT focusedApplicationIdChanged();
364 m_dbusWindowStack->FocusedWindowChanged(0, application->appId(), application->stage());
365 }
366@@ -812,10 +917,13 @@
367 DASSERT(application != nullptr);
368 DLOG("ApplicationManager::add (this=%p, application='%s')", this, qPrintable(application->name()));
369
370+ connect(application, &Application::screenshotChanged, this, &ApplicationManager::screenshotUpdated);
371+
372 beginInsertRows(QModelIndex(), m_applications.size(), m_applications.size());
373 m_applications.append(application);
374 endInsertRows();
375 Q_EMIT countChanged();
376+ Q_EMIT applicationAdded(application->appId());
377 }
378
379 void ApplicationManager::remove(Application *application)
380@@ -833,6 +941,7 @@
381 beginRemoveRows(QModelIndex(), i, i);
382 m_applications.removeAt(i);
383 endRemoveRows();
384+ Q_EMIT applicationRemoved(application->appId());
385 Q_EMIT countChanged();
386 }
387 }
388
389=== modified file 'src/modules/Unity/Application/application_manager.h'
390--- src/modules/Unity/Application/application_manager.h 2014-03-11 14:51:12 +0000
391+++ src/modules/Unity/Application/application_manager.h 2014-03-31 15:12:36 +0000
392@@ -84,12 +84,16 @@
393
394 // ApplicationManagerInterface
395 QString focusedApplicationId() const override;
396+ bool suspended() const;
397+ void setSuspended(bool suspended);
398 Q_INVOKABLE unitymir::Application* get(int index) const override;
399 Q_INVOKABLE unitymir::Application* findApplication(const QString &appId) const override;
400+ Q_INVOKABLE bool requestFocusApplication(const QString &appId) override;
401 Q_INVOKABLE bool focusApplication(const QString &appId) override;
402 Q_INVOKABLE void unfocusCurrentApplication() override;
403 Q_INVOKABLE unitymir::Application* startApplication(const QString &appId, const QStringList &arguments) override;
404 Q_INVOKABLE bool stopApplication(const QString &appId) override;
405+ Q_INVOKABLE bool updateScreenshot(const QString &appId);
406
407 // QAbstractListModel
408 int rowCount(const QModelIndex & parent = QModelIndex()) const override;
409@@ -104,6 +108,7 @@
410
411 // Internal helpers
412 void suspendApplication(Application *application);
413+ void resumeApplication(Application *application);
414 int panelHeight();
415 QSize displaySize() const { return m_displaySize; }
416
417@@ -126,6 +131,9 @@
418 Q_SIGNALS:
419 void focusRequested(const QString &appId);
420
421+private Q_SLOTS:
422+ void screenshotUpdated();
423+
424 private:
425 void setFocused(Application *application);
426 void add(Application *application);
427@@ -152,8 +160,10 @@
428 QSharedPointer<ProcInfo> m_procInfo;
429 int m_gridUnitPx;
430 bool m_fenceNext;
431+ QString m_nextFocusedAppId;
432 QSize m_displaySize;
433 int m_panelHeight;
434+ bool m_suspended;
435
436 friend class DBusWindowStack;
437 friend class MirSurfaceManager;
438
439=== modified file 'src/modules/Unity/Application/applicationscreenshotprovider.cpp'
440--- src/modules/Unity/Application/applicationscreenshotprovider.cpp 2014-02-28 16:51:31 +0000
441+++ src/modules/Unity/Application/applicationscreenshotprovider.cpp 2014-03-31 15:12:36 +0000
442@@ -31,20 +31,18 @@
443 ApplicationScreenshotProvider::ApplicationScreenshotProvider(ApplicationManager *appManager)
444 : QQuickImageProvider(QQuickImageProvider::Image)
445 , m_appManager(appManager)
446- , m_panelHeight(m_appManager->panelHeight())
447-{
448-}
449-
450-QQmlImageProviderBase::Flags ApplicationScreenshotProvider::flags() const
451-{
452- // Force image fetching to always be async, prevent blocking main thread
453- return QQmlImageProviderBase::ForceAsynchronousImageLoading;
454-}
455-
456-QImage ApplicationScreenshotProvider::requestImage(const QString &appId, QSize * size,
457+{
458+}
459+
460+QImage ApplicationScreenshotProvider::requestImage(const QString &imageId, QSize * size,
461 const QSize &requestedSize)
462 {
463- DLOG("ApplicationScreenshotProvider::requestPixmap (this=%p, id=%s)", this, appId.toLatin1().constData());
464+ // We ignore requestedSize here intentionally to avoid keeping scaled copies around
465+ Q_UNUSED(requestedSize)
466+
467+ DLOG("ApplicationScreenshotProvider::requestImage (this=%p, id=%s)", this, imageId.toLatin1().constData());
468+
469+ QString appId = imageId.split('/').first();
470
471 Application* app = static_cast<Application*>(m_appManager->findApplication(appId));
472 if (app == NULL) {
473@@ -55,44 +53,16 @@
474 // TODO: if app not ready, return an app-provided splash image. If app has been stopped with saved state
475 // return the screenshot that was saved to disk.
476 if (!app->session() || !app->session()->default_surface()) {
477- LOG("ApplicationScreenshotProvider - app session not found - taking screenshot too early");
478- return QImage();
479- }
480-
481- if (app->state() == Application::Stopped || app->state() == Application::Starting) {
482- LOG("ApplicationScreenshotProvider - unable to take screenshot of stopped/not-ready applications");
483- return QImage();
484- }
485-
486- QMutex mutex;
487- QWaitCondition wait;
488- mutex.lock();
489-
490- QImage image;
491-
492- app->session()->take_snapshot(
493- [&](mir::shell::Snapshot const& snapshot)
494- {
495- DLOG("ApplicationScreenshotProvider - Mir snapshot ready with size %d x %d",
496- snapshot.size.height.as_int(), snapshot.size.width.as_int());
497-
498- image = QImage( (const uchar*)snapshot.pixels, // since we mirror, no need to offset starting position
499- snapshot.size.width.as_int(),
500- snapshot.size.height.as_int(),
501- QImage::Format_ARGB32_Premultiplied).mirrored();
502- wait.wakeOne();
503- });
504-
505- wait.wait(&mutex, 5000);
506+ LOG("ApplicationScreenshotProvider - app session not found - asking for screenshot too early");
507+ return QImage();
508+ }
509+
510+ QImage image = app->screenshotImage();
511
512 DLOG("ApplicationScreenshotProvider - working with size %d x %d", image.width(), image.height());
513 size->setWidth(image.width());
514 size->setHeight(image.height());
515
516- if (requestedSize.isValid()) {
517- image = image.scaled(requestedSize);
518- }
519- mutex.unlock();
520 return image;
521 }
522
523
524=== modified file 'src/modules/Unity/Application/applicationscreenshotprovider.h'
525--- src/modules/Unity/Application/applicationscreenshotprovider.h 2014-02-28 16:51:31 +0000
526+++ src/modules/Unity/Application/applicationscreenshotprovider.h 2014-03-31 15:12:36 +0000
527@@ -28,12 +28,10 @@
528 public:
529 explicit ApplicationScreenshotProvider(ApplicationManager *appManager);
530
531- Flags flags() const override;
532 QImage requestImage(const QString &appId, QSize *size, const QSize &requestedSize) override;
533
534 private:
535 ApplicationManager* m_appManager;
536- int m_panelHeight;
537 };
538
539 } // namespace unitymir
540
541=== modified file 'src/modules/Unity/Application/plugin.cpp'
542--- src/modules/Unity/Application/plugin.cpp 2014-02-28 16:51:31 +0000
543+++ src/modules/Unity/Application/plugin.cpp 2014-03-31 15:12:36 +0000
544@@ -81,7 +81,7 @@
545
546 unitymir::ApplicationManager* appManager =
547 static_cast<unitymir::ApplicationManager*>(applicationManagerSingleton(engine, NULL));
548- engine->addImageProvider(QLatin1String("screenshot"),
549+ engine->addImageProvider(QLatin1String("application"),
550 new unitymir::ApplicationScreenshotProvider(appManager));
551 }
552 };
553
554=== modified file 'tests/CMakeLists.txt'
555--- tests/CMakeLists.txt 2014-02-17 15:40:13 +0000
556+++ tests/CMakeLists.txt 2014-03-31 15:12:36 +0000
557@@ -21,6 +21,7 @@
558 ${GMOCK_INCLUDE_DIR}
559 ${GTEST_INCLUDE_DIR}
560 ${MIRSERVER_INCLUDE_DIRS}
561+ ${QT_TEST}
562 )
563
564 add_executable(
565@@ -35,7 +36,7 @@
566
567 # We should not need this line according to the Qt5/CMake docs.
568 # However, when removing it, include paths are not set and linking to Qt5 fails.
569-qt5_use_modules(application_manager_test Core Quick DBus)
570+qt5_use_modules(application_manager_test Core Quick DBus Test)
571 qt5_use_modules(taskcontroller_test Core Quick DBus)
572
573 target_link_libraries(
574
575=== modified file 'tests/application_manager_test.cpp'
576--- tests/application_manager_test.cpp 2014-03-11 03:40:26 +0000
577+++ tests/application_manager_test.cpp 2014-03-31 15:12:36 +0000
578@@ -25,6 +25,7 @@
579
580 #include <gmock/gmock.h>
581 #include <gtest/gtest.h>
582+#include <QSignalSpy>
583
584 #include "mock_application_controller.h"
585 #include "mock_desktop_file_reader.h"
586@@ -327,3 +328,86 @@
587 EXPECT_EQ(Application::Running, the_app->state());
588 EXPECT_EQ(first_session, the_app->session());
589 }
590+
591+TEST_F(ApplicationManagerTests,suspended_suspends_focused_app)
592+{
593+ using namespace ::testing;
594+ quint64 a_procId = 5921;
595+ const char an_app_id[] = "some_app";
596+ QByteArray a_cmd( "/usr/bin/app1 --desktop_file_hint=some_app");
597+ std::shared_ptr<mir::shell::Surface> aSurface(nullptr);
598+
599+ ON_CALL(procInfo,command_line(_)).WillByDefault(Return(a_cmd));
600+
601+ ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
602+
603+ bool authed = true;
604+
605+ std::shared_ptr<mir::shell::Session> first_session = std::make_shared<MockSession>("Oo", a_procId);
606+ std::shared_ptr<mir::shell::Session> second_session = std::make_shared<MockSession>("oO", a_procId);
607+ applicationManager.authorizeSession(a_procId, authed);
608+
609+ applicationManager.onSessionStarting(first_session);
610+ applicationManager.onSessionCreatedSurface(first_session.get(), aSurface);
611+ applicationManager.onSessionStarting(second_session);
612+
613+ Application * the_app = applicationManager.findApplication(an_app_id);
614+
615+ EXPECT_EQ(Application::Running, the_app->state());
616+
617+ applicationManager.setSuspended(true);
618+
619+ EXPECT_EQ(Application::Suspended, the_app->state());
620+
621+ applicationManager.setSuspended(false);
622+
623+ EXPECT_EQ(Application::Running, the_app->state());
624+
625+}
626+
627+TEST_F(ApplicationManagerTests,requestFocusApplication)
628+{
629+ using namespace ::testing;
630+ quint64 first_procId = 5921;
631+ quint64 second_procId = 5922;
632+ quint64 third_procId = 5923;
633+ std::shared_ptr<mir::shell::Surface> aSurface(nullptr);
634+ QByteArray first_cmdLine( "/usr/bin/app1 --desktop_file_hint=app1");
635+ QByteArray second_cmdLine( "/usr/bin/app2--desktop_file_hint=app2");
636+ QByteArray third_cmdLine( "/usr/bin/app3 --desktop_file_hint=app3");
637+
638+ EXPECT_CALL(procInfo,command_line(first_procId))
639+ .Times(1)
640+ .WillOnce(Return(first_cmdLine));
641+
642+ ON_CALL(appController,appIdHasProcessId(_,_)).WillByDefault(Return(false));
643+
644+ EXPECT_CALL(procInfo,command_line(second_procId))
645+ .Times(1)
646+ .WillOnce(Return(second_cmdLine));
647+
648+ EXPECT_CALL(procInfo,command_line(third_procId))
649+ .Times(1)
650+ .WillOnce(Return(third_cmdLine));
651+
652+ bool authed = true;
653+
654+ std::shared_ptr<mir::shell::Session> first_session = std::make_shared<MockSession>("Oo", first_procId);
655+ std::shared_ptr<mir::shell::Session> second_session = std::make_shared<MockSession>("oO", second_procId);
656+ std::shared_ptr<mir::shell::Session> third_session = std::make_shared<MockSession>("OO", third_procId);
657+ applicationManager.authorizeSession(first_procId, authed);
658+ applicationManager.authorizeSession(second_procId, authed);
659+ applicationManager.authorizeSession(third_procId, authed);
660+ applicationManager.onSessionStarting(first_session);
661+ applicationManager.onSessionStarting(third_session);
662+ applicationManager.onSessionStarting(second_session);
663+
664+ QSignalSpy spy(&applicationManager, SIGNAL(focusRequested(const QString &)));
665+
666+ applicationManager.requestFocusApplication("app3");
667+
668+ EXPECT_EQ(spy.count(), 1);
669+
670+ QList<QVariant> arguments = spy.takeFirst(); // take the first signal
671+ EXPECT_EQ(arguments.at(0).toString(), "app3");
672+}

Subscribers

People subscribed via source and target branches