Merge lp:~mzanetti/unity-mir/screenshotting-focusing-api into lp:unity-mir
- screenshotting-focusing-api
- Merge into trunk
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 |
Related bugs: |
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:/
https:/
* 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
PS Jenkins bot (ps-jenkins) wrote : | # |
Gerry Boland (gerboland) wrote : | # |
=== removed file 'src/modules/
This will break SurfaceFlinger support. We good with that?
Michael Zanetti (mzanetti) wrote : | # |
> === removed file 'src/modules/
> 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).
Gerry Boland (gerboland) wrote : | # |
+void ApplicationMana
+ if (!m_nextFocused
+ Q_EMIT focusRequested(
+ m_nextFocusedAp
+ }
I don't follow this. Why emit a focusRequest after screenshot is updated? Is it just the best time to do it?
Gerry Boland (gerboland) wrote : | # |
+ engine-
If I see a QUrl like "application:
- 180. By Michael Zanetti
-
update according to latest changes in unity-api
Michael Zanetti (mzanetti) wrote : | # |
> > === removed file 'src/modules/
> > 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.
Michael Zanetti (mzanetti) wrote : | # |
> +void ApplicationMana
> + if (!m_nextFocused
> + Q_EMIT focusRequested(
> + m_nextFocusedAp
> + }
> 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 requestFocusApp
Michael Zanetti (mzanetti) wrote : | # |
> + engine-
> ApplicationScre
>
> If I see a QUrl like "application:
> dispatcher? Or a url for that app's screenshot?
that's a url-dispatcher url :)
the imageprovider's url is
image:/
> 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?
Gerry Boland (gerboland) wrote : | # |
> After clearing the confusion, does it still sound too similar to you?
/me idiot. You're right. Plz ignore
Gerry Boland (gerboland) wrote : | # |
> > +void ApplicationMana
> > + if (!m_nextFocused
> > + Q_EMIT focusRequested(
> > + m_nextFocusedAp
> > + }
> > 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
> requestFocusApp
> 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:
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?
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:180
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 181. By Michael Zanetti
-
improve comments, make sure we don't crash if the app disappears before the screenshotUpdated() signal is delivered
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:
>
> 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.
Gerry Boland (gerboland) wrote : | # |
Ok, that'll do. I just need to test this with your unity8 work, before I can approve
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:181
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 182. By Michael Zanetti
-
merge trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:182
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 183. By Michael Zanetti
-
make it compile again
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:183
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
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...
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:184
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 185. By Michael Zanetti
-
add a test that checks if calling applicationMana
ger.setSuspende d(true) suspends the running app
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:185
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
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
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:186
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
Michał Sawicz (saviq) wrote : | # |
Bump?
- 187. By Michael Zanetti
-
depend on latest libunity-api-dev package
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:187
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 188. By Michael Zanetti
-
added a test for focusRequested
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?
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:188
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
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:/
- 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
Michał Sawicz (saviq) wrote : | # |
+1 on the plugin installation dirs.
- 192. By Michael Zanetti
-
also provide the virtual application-impl
Preview Diff
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 | +} |
FAILED: Continuous integration, rev:179 jenkins. qa.ubuntu. com/job/ unity-mir- ci/275/ jenkins. qa.ubuntu. com/job/ unity-mir- trusty- amd64-ci/ 138/console jenkins. qa.ubuntu. com/job/ unity-mir- trusty- armhf-ci/ 139/console jenkins. qa.ubuntu. com/job/ unity-mir- trusty- i386-ci/ 138/console
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- mir-ci/ 275/rebuild
http://