Merge lp:~aacid/unity8/suspend_screenshoting into lp:unity8

Proposed by Albert Astals Cid
Status: Merged
Approved by: Michael Zanetti
Approved revision: 1755
Merged at revision: 1841
Proposed branch: lp:~aacid/unity8/suspend_screenshoting
Merge into: lp:unity8
Diff against target: 735 lines (+524/-22)
17 files modified
debian/unity8-private.install (+1/-0)
plugins/CMakeLists.txt (+1/-0)
plugins/SessionGrabber/CMakeLists.txt (+8/-0)
plugins/SessionGrabber/SessionGrabber.qmltypes (+23/-0)
plugins/SessionGrabber/plugin.cpp (+27/-0)
plugins/SessionGrabber/plugin.h (+32/-0)
plugins/SessionGrabber/qmldir (+3/-0)
plugins/SessionGrabber/sessiongrabber.cpp (+135/-0)
plugins/SessionGrabber/sessiongrabber.h (+85/-0)
qml/Stages/ApplicationWindow.qml (+46/-22)
qml/Stages/Splash.qml (+2/-0)
qml/Stages/SpreadDelegate.qml (+1/-0)
tests/plugins/CMakeLists.txt (+1/-0)
tests/plugins/SessionGrabber/CMakeLists.txt (+14/-0)
tests/plugins/SessionGrabber/sessiongrabbertest.cpp (+79/-0)
tests/plugins/SessionGrabber/sessiongrabbertest.qml (+22/-0)
tests/qmltests/Stages/tst_ApplicationWindow.qml (+44/-0)
To merge this branch: bzr merge lp:~aacid/unity8/suspend_screenshoting
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Needs Fixing
Michael Zanetti (community) Abstain
Andrea Cimitan (community) Abstain
Michał Sawicz Approve
Daniel d'Andrada (community) Needs Information
Review via email: mp+257119@code.launchpad.net

Commit message

Save screenshots of suspended apps to disk

Description of the change

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

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

 * Did you make sure that your branch does not contain spurious tags?
Yes

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

 * 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
Michał Sawicz (saviq) wrote :

As mentioned, SessionScreenshoter could use a better name... For one, it could be SessionShotter (note double-t), as it's not shooting the screen, just the session ;). SessionGrabber? We already have a ScreenGrabber!

Generate qmltypes please.

Is the screenshot unloaded if app has a drawn-to surface?

Some clarification about the activity indicator - we do want it to run over either screenshot or splash screen *if* the app is active/focused, just not if it's in the background.

review: Needs Fixing
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 :

> Is the screenshot unloaded if app has a drawn-to surface?
Yes, see

           Transition {
                from: "screenshot"; to: "surface"
                SequentialAnimation {
                    ...
                    PropertyAction { target: screenshotImage; property: "source"; value: "" }
                }
            }

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 :

> Generate qmltypes please.

Done

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

> Some clarification about the activity indicator - we do want it to run over
> either screenshot or splash screen *if* the app is active/focused, just not if
> it's in the background.

This should be there now.

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 :

> I think we're unnecessarily complicating this by using the intermediate SOURCES variable, not sure why we started it ;)
Fixed

> Why not QStandardPaths::CacheLocation/app_shots/?
Utils/windowstatestorage.cpp is not using it so i did the same pattern.

> We tend to put the comma after newline.
Fixed, We need a bot to do this :D

> Any reason why not if (image.save(path))?
I think at some point i was returning b, changed it

> If you s/Screenshoter/Grabber/, likely s/take/grab/ here, too.
Done

> Maybe ::drop() or ::clear()... or just ::remove()? It feels weird to have a nice and short ::take() and then the verbose removeScreenshot().
Honestly i'd prefer not to, the thing is that we have "SessionGrabber", so it's quite clear what "grab" does, but "remove/drop/clear" seems like it may clear the grabber (i.e. for example reset parameters), not actually remove the screenshot.

> Third time, please factor out, maybe even cachePath(appId)?
Sure

> /me not totally familiar with QtConcurrent, can you explain why we need the QSharedPointer here?
You mean for the watcher or for the grab result?

> Typo "AppdId"
Fixed

> Typotoo.
Fixed

> As you rename... grabbed()?
Done

> If you rename ::removeScreenshot, likely rename this too, clearSessionGrab or so?
Not done since i didn't change the other one

> I'd probably use screenshoter.path || d.defaultScreenshot.
Ok, changed, not that it makes a difference

> Maybe add a comment why that?
Done

> Spinner active if running?
If the splash is still around (it'll go away as soon as the first frame is drawn), yes

> No spinner if stopped? Should be if stopped and !active?
How would one be stopped and active at the same time?

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 :

>> Why not QStandardPaths::CacheLocation/app_shots/?
> Utils/windowstatestorage.cpp is not using it so i did the same pattern.

Yeah, but at least it writes in ~/.cache/*unity8*/, not in ~/.cache/ directly ;)

Also, there's no transition when screenshot is loaded over splashscreen.

review: Needs Fixing
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)
Revision history for this message
Daniel d'Andrada (dandrader) wrote :

"""
added file 'plugins/SessionGrabber/sessiongrabber.cpp'
"""

Anything against using CamelCase for the file names? eg: SessionGrabber.cpp
What do we gain from making it all lower-case?

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

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Y
 * Did CI run pass? If not, please explain why.
Some flakiness, will run again before top-approving.
 * Did you make sure that the branch does not contain spurious tags?
Y

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

> """
> added file 'plugins/SessionGrabber/sessiongrabber.cpp'
> """
>
> Anything against using CamelCase for the file names? eg: SessionGrabber.cpp
> What do we gain from making it all lower-case?

Having to press the shift key fewer times :D

Honestly we have half the plugins that use lowercase and half that use CamelCase, at some points we should decide what we want to use, but not sure this time should be now. Maybe on Dallas?

Revision history for this message
Andrea Cimitan (cimi) :
review: Needs Information
Revision history for this message
Andrea Cimitan (cimi) wrote :

After discussing with Gerry, eventually we might decide to have full resolution screenshots (maybe in black and white to indicate that is suspended), but we will fix it in a different branch

Revision history for this message
Andrea Cimitan (cimi) :
review: Abstain
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 :

> After discussing with Gerry, eventually we might decide to have full
> resolution screenshots (maybe in black and white to indicate that is
> suspended), but we will fix it in a different branch

FWIW the screenshot in disk is full resolution, it's the showing only that is not full resolution so won't lose resolution or have to re-take screenshots when changing it later

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

Needs a merge

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

Merged

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

Merge unity8

1755. By Albert Astals Cid

Build++

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

> Doesn't compile any more after merging with trunk:
> https://launchpadlibrarian.net/209888034/buildlog_ubuntu-
> wily-i386.unity8_8.10%2B15.10.20150624-0ubuntu1_BUILDING.txt.gz

Fixed.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/unity8-private.install'
--- debian/unity8-private.install 2015-04-21 15:22:53 +0000
+++ debian/unity8-private.install 2015-06-30 11:41:39 +0000
@@ -8,6 +8,7 @@
8usr/lib/*/unity8/qml/Powerd8usr/lib/*/unity8/qml/Powerd
9usr/lib/*/unity8/qml/ScreenGrabber9usr/lib/*/unity8/qml/ScreenGrabber
10usr/lib/*/unity8/qml/SessionBroadcast10usr/lib/*/unity8/qml/SessionBroadcast
11usr/lib/*/unity8/qml/SessionGrabber
11usr/lib/*/unity8/qml/Ubuntu12usr/lib/*/unity8/qml/Ubuntu
12usr/lib/*/unity8/qml/Unity13usr/lib/*/unity8/qml/Unity
13usr/lib/*/unity8/qml/Utils14usr/lib/*/unity8/qml/Utils
1415
=== modified file 'plugins/CMakeLists.txt'
--- plugins/CMakeLists.txt 2014-12-12 10:00:31 +0000
+++ plugins/CMakeLists.txt 2015-06-30 11:41:39 +0000
@@ -18,6 +18,7 @@
18add_subdirectory(Dash)18add_subdirectory(Dash)
19add_subdirectory(Powerd)19add_subdirectory(Powerd)
20add_subdirectory(SessionBroadcast)20add_subdirectory(SessionBroadcast)
21add_subdirectory(SessionGrabber)
21add_subdirectory(ScreenGrabber)22add_subdirectory(ScreenGrabber)
22add_subdirectory(Ubuntu)23add_subdirectory(Ubuntu)
23add_subdirectory(Unity)24add_subdirectory(Unity)
2425
=== added directory 'plugins/SessionGrabber'
=== added file 'plugins/SessionGrabber/CMakeLists.txt'
--- plugins/SessionGrabber/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ plugins/SessionGrabber/CMakeLists.txt 2015-06-30 11:41:39 +0000
@@ -0,0 +1,8 @@
1include_directories(
2 ${CMAKE_CURRENT_BINARY_DIR}
3)
4
5add_library(SessionGrabber-qml MODULE sessiongrabber.cpp plugin.cpp)
6qt5_use_modules(SessionGrabber-qml Qml Gui Quick Concurrent)
7
8add_unity8_plugin(SessionGrabber 0.1 SessionGrabber TARGETS SessionGrabber-qml)
09
=== added file 'plugins/SessionGrabber/SessionGrabber.qmltypes'
--- plugins/SessionGrabber/SessionGrabber.qmltypes 1970-01-01 00:00:00 +0000
+++ plugins/SessionGrabber/SessionGrabber.qmltypes 2015-06-30 11:41:39 +0000
@@ -0,0 +1,23 @@
1import QtQuick.tooling 1.1
2
3// This file describes the plugin-supplied types contained in the library.
4// It is used for QML tooling purposes only.
5//
6// This file was auto-generated by:
7// 'qmlplugindump -notrelocatable SessionGrabber 0.1 plugins'
8
9Module {
10 Component {
11 name: "SessionGrabber"
12 prototype: "QObject"
13 exports: ["SessionGrabber/SessionGrabber 0.1"]
14 exportMetaObjectRevisions: [0]
15 Property { name: "appId"; type: "string" }
16 Property { name: "path"; type: "string"; isReadonly: true }
17 Property { name: "target"; type: "QQuickItem"; isPointer: true }
18 Signal { name: "appIdChanged" }
19 Signal { name: "screenshotTaken" }
20 Method { name: "take" }
21 Method { name: "removeScreenshot" }
22 }
23}
024
=== added file 'plugins/SessionGrabber/plugin.cpp'
--- plugins/SessionGrabber/plugin.cpp 1970-01-01 00:00:00 +0000
+++ plugins/SessionGrabber/plugin.cpp 2015-06-30 11:41:39 +0000
@@ -0,0 +1,27 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#include "plugin.h"
19#include "sessiongrabber.h"
20
21#include <QtQml/qqml.h>
22
23void SessionGrabberPlugin::registerTypes(const char *uri)
24{
25 Q_ASSERT(uri == QLatin1String("SessionGrabber"));
26 qmlRegisterType<SessionGrabber>(uri, 0, 1, "SessionGrabber");
27}
028
=== added file 'plugins/SessionGrabber/plugin.h'
--- plugins/SessionGrabber/plugin.h 1970-01-01 00:00:00 +0000
+++ plugins/SessionGrabber/plugin.h 2015-06-30 11:41:39 +0000
@@ -0,0 +1,32 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#ifndef SESSIONGRABBER_PLUGIN_H
19#define SESSIONGRABBER_PLUGIN_H
20
21#include <QtQml/QQmlExtensionPlugin>
22
23class SessionGrabberPlugin : public QQmlExtensionPlugin
24{
25 Q_OBJECT
26 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27
28public:
29 void registerTypes(const char *uri);
30};
31
32#endif
033
=== added file 'plugins/SessionGrabber/qmldir'
--- plugins/SessionGrabber/qmldir 1970-01-01 00:00:00 +0000
+++ plugins/SessionGrabber/qmldir 2015-06-30 11:41:39 +0000
@@ -0,0 +1,3 @@
1module SessionGrabber
2plugin SessionGrabber-qml
3typeinfo SessionGrabber.qmltypes
04
=== added file 'plugins/SessionGrabber/sessiongrabber.cpp'
--- plugins/SessionGrabber/sessiongrabber.cpp 1970-01-01 00:00:00 +0000
+++ plugins/SessionGrabber/sessiongrabber.cpp 2015-06-30 11:41:39 +0000
@@ -0,0 +1,135 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#include "sessiongrabber.h"
19
20#include <QDebug>
21#include <QQuickItem>
22#include <QQuickItemGrabResult>
23#include <QtConcurrent>
24
25static QString cacheFolder()
26{
27 return QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/app_shots/";
28}
29
30static QString cachePath(const QString &appId)
31{
32 return cacheFolder() + appId + ".png";
33}
34
35SessionGrabber::SessionGrabber(QObject *parent)
36 : QObject(parent)
37 , m_target(nullptr)
38 , m_watcher(nullptr)
39{
40}
41
42QString SessionGrabber::appId() const
43{
44 return m_appId;
45}
46
47void SessionGrabber::setAppId(const QString &appId)
48{
49 if (appId != m_appId) {
50 m_appId = appId;
51 const QString path = cachePath(appId);
52 if (QFile::exists(path)) {
53 setPath(path);
54 }
55 }
56}
57
58QQuickItem *SessionGrabber::target() const
59{
60 return m_target;
61}
62
63void SessionGrabber::setTarget(QQuickItem *target)
64{
65 if (target != m_target) {
66 m_target = target;
67 Q_EMIT targetChanged();
68 }
69}
70
71QString SessionGrabber::path() const
72{
73 return m_path;
74}
75
76void SessionGrabber::setPath(const QString &path)
77{
78 if (path != m_path) {
79 m_path = path;
80 Q_EMIT pathChanged();
81 }
82}
83
84static QString saveScreenshot(const QString &appId, const QSharedPointer<QQuickItemGrabResult> grabResult)
85{
86 const QImage image = grabResult->image();
87 const QString cPath = cacheFolder();
88 QDir d;
89 d.mkpath(cPath);
90 const QString path = cachePath(appId);
91 if (image.save(path)) {
92 return path;
93 } else {
94 return QString();
95 }
96}
97
98void SessionGrabber::grab()
99{
100 if (!m_grabResult.isNull()) {
101 qWarning() << "Asked to grab a screenshot when there's one already being grabbed, ignoring";
102 return;
103 }
104
105 if (m_target && !m_appId.isEmpty()) {
106 // grabToImage runs in a separate thread
107 m_grabResult = m_target->grabToImage();
108 connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, &SessionGrabber::grabReady);
109 } else {
110 qWarning() << "Can't grab screnshot: appId:" << m_appId << " target:" << m_target;
111 }
112}
113
114void SessionGrabber::removeScreenshot()
115{
116 const QString path = cachePath(m_appId);
117 QFile::remove(path);
118}
119
120void SessionGrabber::grabReady()
121{
122 QFuture<QString> f = QtConcurrent::run(saveScreenshot, m_appId, m_grabResult);
123 m_watcher = new QFutureWatcher<QString>(this);
124 m_watcher->setFuture(f);
125 connect(m_watcher, &QFutureWatcher<QString>::finished, this, &SessionGrabber::saveFinished);
126}
127
128void SessionGrabber::saveFinished()
129{
130 setPath(m_watcher->future().result());
131 delete m_watcher;
132 m_watcher = nullptr;
133 m_grabResult.clear();
134 Q_EMIT screenshotGrabbed();
135}
0136
=== added file 'plugins/SessionGrabber/sessiongrabber.h'
--- plugins/SessionGrabber/sessiongrabber.h 1970-01-01 00:00:00 +0000
+++ plugins/SessionGrabber/sessiongrabber.h 2015-06-30 11:41:39 +0000
@@ -0,0 +1,85 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18#ifndef SESSIONGRABBER_H
19#define SESSIONGRABBER_H
20
21#include <QObject>
22#include <QSharedPointer>
23#include <QFutureWatcher>
24
25class QQuickItem;
26class QQuickItemGrabResult;
27
28/**
29 * SessionGrabber saves to disk screenshots of the given item.
30 * Images are saved into $HOME/.cache/app_shots/appId.png
31 * It also handles giving back the screenshot path if it already exists (e.g. because a reboot)
32 */
33class SessionGrabber : public QObject
34{
35 Q_OBJECT
36
37 /// appId is the key of the screenshot name.
38 Q_PROPERTY(QString appId READ appId WRITE setAppId NOTIFY appIdChanged)
39
40 /// path where the screenshot is saved, can be empty if no screenshot has been grabbed yet.
41 Q_PROPERTY(QString path READ path NOTIFY pathChanged)
42
43 /// target item for the screenshot grabbing.
44 Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged)
45
46public:
47 explicit SessionGrabber(QObject *parent = 0);
48
49 QString appId() const;
50 void setAppId(const QString &appId);
51
52 QQuickItem *target() const;
53 void setTarget(QQuickItem *target);
54
55 QString path() const;
56
57 /// Starts grabbing a screenshot. Emits screenshotGrabbed when ready.
58 Q_INVOKABLE void grab();
59
60 /// Removes the existing screenshot
61 Q_INVOKABLE void removeScreenshot();
62
63Q_SIGNALS:
64 void appIdChanged();
65 void targetChanged();
66 void pathChanged();
67
68 /// Signals screenshot grabbing has finished.
69 void screenshotGrabbed();
70
71private Q_SLOTS:
72 void grabReady();
73 void saveFinished();
74
75private:
76 void setPath(const QString &path);
77
78 QString m_appId;
79 QString m_path;
80 QQuickItem *m_target;
81 QFutureWatcher<QString> *m_watcher;
82 QSharedPointer<QQuickItemGrabResult> m_grabResult;
83};
84
85#endif
086
=== modified file 'qml/Stages/ApplicationWindow.qml'
--- qml/Stages/ApplicationWindow.qml 2015-04-10 15:10:51 +0000
+++ qml/Stages/ApplicationWindow.qml 2015-06-30 11:41:39 +0000
@@ -17,6 +17,7 @@
17import QtQuick 2.017import QtQuick 2.0
18import Ubuntu.Components 1.118import Ubuntu.Components 1.1
19import Unity.Application 0.119import Unity.Application 0.1
20import SessionGrabber 0.1
2021
21FocusScope {22FocusScope {
22 id: root23 id: root
@@ -30,11 +31,16 @@
30 property QtObject application31 property QtObject application
31 property int surfaceOrientationAngle32 property int surfaceOrientationAngle
3233
34 function removeScreenshot() {
35 sessionGrabber.removeScreenshot();
36 }
37
33 QtObject {38 QtObject {
34 id: d39 id: d
3540
36 // helpers so that we don't have to check for the existence of an application everywhere41 // helpers so that we don't have to check for the existence of an application everywhere
37 // (in order to avoid breaking qml binding due to a javascript exception)42 // (in order to avoid breaking qml binding due to a javascript exception)
43 readonly property string appId: root.application ? root.application.appId : ""
38 readonly property string name: root.application ? root.application.name : ""44 readonly property string name: root.application ? root.application.name : ""
39 readonly property url icon: root.application ? root.application.icon : ""45 readonly property url icon: root.application ? root.application.icon : ""
40 readonly property int applicationState: root.application ? root.application.state : -146 readonly property int applicationState: root.application ? root.application.state : -1
@@ -50,11 +56,11 @@
50 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface56 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
5157
52 property bool needToTakeScreenshot:58 property bool needToTakeScreenshot:
53 sessionContainer.surface && d.surfaceInitialized && screenshotImage.status === Image.Null59 sessionContainer.surface && d.surfaceInitialized
54 && d.applicationState === ApplicationInfoInterface.Stopped60 && (d.applicationState === ApplicationInfoInterface.Stopped || d.applicationState === ApplicationInfoInterface.Suspended)
55 onNeedToTakeScreenshotChanged: {61 onNeedToTakeScreenshotChanged: {
56 if (needToTakeScreenshot) {62 if (needToTakeScreenshot) {
57 screenshotImage.take();63 sessionGrabber.grab();
58 }64 }
59 }65 }
6066
@@ -86,27 +92,9 @@
86 onTriggered: { if (stateGroup.state === "surface") { d.surfaceOldEnoughToBeResized = true; } }92 onTriggered: { if (stateGroup.state === "surface") { d.surfaceOldEnoughToBeResized = true; } }
87 }93 }
8894
89 Image {
90 id: screenshotImage
91 objectName: "screenshotImage"
92 source: d.defaultScreenshot
93 anchors.fill: parent
94 antialiasing: !root.interactive
95
96 function take() {
97 // Format: "image://application/$APP_ID/$CURRENT_TIME_MS"
98 // eg: "image://application/calculator-app/123456"
99 var timeMs = new Date().getTime();
100 source = "image://application/" + root.application.appId + "/" + timeMs;
101 }
102
103 // Save memory by using a half-resolution (thus quarter size) screenshot
104 sourceSize.width: root.width / 2
105 sourceSize.height: root.height / 2
106 }
107
108 Loader {95 Loader {
109 id: splashLoader96 id: splashLoader
97 objectName: "splashLoader"
110 visible: active98 visible: active
111 active: false99 active: false
112 anchors.fill: parent100 anchors.fill: parent
@@ -120,10 +108,46 @@
120 backgroundColor: d.splashColor108 backgroundColor: d.splashColor
121 headerColor: d.splashColorHeader109 headerColor: d.splashColorHeader
122 footerColor: d.splashColorFooter110 footerColor: d.splashColorFooter
111 activeSpinner: d.applicationState === ApplicationInfoInterface.Starting ||
112 d.applicationState === ApplicationInfoInterface.Running
123 }113 }
124 }114 }
125 }115 }
126116
117 Image {
118 id: screenshotImage
119 objectName: "screenshotImage"
120 source: sessionGrabber.path || d.defaultScreenshot
121 anchors.fill: parent
122 antialiasing: !root.interactive
123 cache: false
124 asynchronous: true
125
126 // Save memory by using a half-resolution (thus quarter size) screenshot
127 sourceSize.width: root.width / 2
128 sourceSize.height: root.height / 2
129
130 ActivityIndicator {
131 id: activityIndicator
132 anchors.centerIn: parent
133 visible: running
134 running: stateGroup.state == "screenshot" &&
135 (d.applicationState === ApplicationInfoInterface.Starting || d.applicationState === ApplicationInfoInterface.Running)
136 }
137 }
138
139 SessionGrabber {
140 id: sessionGrabber
141 appId: d.appId
142 target: root
143
144 onScreenshotGrabbed: {
145 // Need to reset to "" and back since it may be the same path with new content
146 screenshotImage.source = "";
147 screenshotImage.source = sessionGrabber.path;
148 }
149 }
150
127 SessionContainer {151 SessionContainer {
128 id: sessionContainer152 id: sessionContainer
129 // A fake application might not even have a session property.153 // A fake application might not even have a session property.
130154
=== modified file 'qml/Stages/Splash.qml'
--- qml/Stages/Splash.qml 2014-09-16 13:48:40 +0000
+++ qml/Stages/Splash.qml 2015-06-30 11:41:39 +0000
@@ -31,6 +31,7 @@
31 property url icon31 property url icon
32 property alias title: header.title32 property alias title: header.title
33 property alias showHeader: header.visible33 property alias showHeader: header.visible
34 property alias activeSpinner: activityIndicator.running
3435
35 Ambiance.Palette {36 Ambiance.Palette {
36 id: ambiancePalette37 id: ambiancePalette
@@ -144,6 +145,7 @@
144 }145 }
145146
146 ActivityIndicator {147 ActivityIndicator {
148 id: activityIndicator
147 anchors.centerIn: header.visible ? parent : undefined149 anchors.centerIn: header.visible ? parent : undefined
148 anchors.verticalCenterOffset: header.visible ? header.height / 2 : 0150 anchors.verticalCenterOffset: header.visible ? header.height / 2 : 0
149151
150152
=== modified file 'qml/Stages/SpreadDelegate.qml'
--- qml/Stages/SpreadDelegate.qml 2015-05-11 14:36:03 +0000
+++ qml/Stages/SpreadDelegate.qml 2015-06-30 11:41:39 +0000
@@ -397,6 +397,7 @@
397 if (!running) {397 if (!running) {
398 dragArea.moving = false;398 dragArea.moving = false;
399 if (requestClose) {399 if (requestClose) {
400 appWindow.removeScreenshot();
400 root.closed();401 root.closed();
401 } else {402 } else {
402 dragArea.distance = 0;403 dragArea.distance = 0;
403404
=== modified file 'tests/plugins/CMakeLists.txt'
--- tests/plugins/CMakeLists.txt 2014-12-12 10:00:31 +0000
+++ tests/plugins/CMakeLists.txt 2015-06-30 11:41:39 +0000
@@ -2,6 +2,7 @@
2add_subdirectory(Greeter)2add_subdirectory(Greeter)
3add_subdirectory(LightDM)3add_subdirectory(LightDM)
4add_subdirectory(Dash)4add_subdirectory(Dash)
5add_subdirectory(SessionGrabber)
5add_subdirectory(Ubuntu)6add_subdirectory(Ubuntu)
6add_subdirectory(Unity)7add_subdirectory(Unity)
7add_subdirectory(Utils)8add_subdirectory(Utils)
89
=== added directory 'tests/plugins/SessionGrabber'
=== added file 'tests/plugins/SessionGrabber/CMakeLists.txt'
--- tests/plugins/SessionGrabber/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/plugins/SessionGrabber/CMakeLists.txt 2015-06-30 11:41:39 +0000
@@ -0,0 +1,14 @@
1add_executable(SessionGrabberTestExec
2 sessiongrabbertest.cpp
3 ${CMAKE_SOURCE_DIR}/plugins/SessionGrabber/sessiongrabber.cpp
4 )
5qt5_use_modules(SessionGrabberTestExec Core Quick Test Concurrent)
6
7add_definitions(-DCURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
8
9include_directories(
10 ${CMAKE_CURRENT_BINARY_DIR}
11 ${CMAKE_SOURCE_DIR}/plugins/SessionGrabber
12 )
13
14add_unity8_uitest(SessionGrabber SessionGrabberTestExec)
015
=== added file 'tests/plugins/SessionGrabber/sessiongrabbertest.cpp'
--- tests/plugins/SessionGrabber/sessiongrabbertest.cpp 1970-01-01 00:00:00 +0000
+++ tests/plugins/SessionGrabber/sessiongrabbertest.cpp 2015-06-30 11:41:39 +0000
@@ -0,0 +1,79 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include <QtTestGui>
18#include <QQuickItem>
19#include <QQuickView>
20#include <QSignalSpy>
21
22#include "sessiongrabber.h"
23
24class SessionGrabberTest : public QObject
25{
26 Q_OBJECT
27
28private Q_SLOTS:
29
30 void init()
31 {
32 view = new QQuickView();
33 view->setSource(QUrl::fromLocalFile(CURRENT_SOURCE_DIR "/sessiongrabbertest.qml"));
34 view->show();
35 QTest::qWaitForWindowExposed(view);
36 }
37
38 void testSessionGrabber()
39 {
40 SessionGrabber s;
41 s.setAppId("test-app-id");
42 s.setTarget(view->rootObject());
43 QSignalSpy spy(&s, &SessionGrabber::screenshotGrabbed);
44
45 QVERIFY(!QFile::exists(s.path()));
46
47 view->rootObject()->setProperty("color", "red");
48 s.grab();
49 spy.wait();
50
51 QVERIFY(QFile::exists(s.path()));
52 QImage image(s.path());
53 QCOMPARE(image.pixel(0, 0), qRgb(255, 0, 0));
54
55 view->rootObject()->setProperty("color", "blue");
56 s.grab();
57 spy.wait();
58
59 QVERIFY(QFile::exists(s.path()));
60 image = QImage(s.path());
61 QCOMPARE(image.pixel(0, 0), qRgb(0, 0, 255));
62
63 s.removeScreenshot();
64
65 QVERIFY(!QFile::exists(s.path()));
66 }
67
68 void cleanup()
69 {
70 delete view;
71 }
72
73private:
74 QQuickView *view;
75};
76
77QTEST_MAIN(SessionGrabberTest)
78
79#include "sessiongrabbertest.moc"
080
=== added file 'tests/plugins/SessionGrabber/sessiongrabbertest.qml'
--- tests/plugins/SessionGrabber/sessiongrabbertest.qml 1970-01-01 00:00:00 +0000
+++ tests/plugins/SessionGrabber/sessiongrabbertest.qml 2015-06-30 11:41:39 +0000
@@ -0,0 +1,22 @@
1/*
2 * Copyright (C) 2015 Canonical, Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.4
18
19Rectangle {
20 width: 100
21 height: 100
22}
023
=== modified file 'tests/qmltests/Stages/tst_ApplicationWindow.qml'
--- tests/qmltests/Stages/tst_ApplicationWindow.qml 2015-03-06 04:44:11 +0000
+++ tests/qmltests/Stages/tst_ApplicationWindow.qml 2015-06-30 11:41:39 +0000
@@ -35,6 +35,9 @@
35 root.fakeApplication.manualSurfaceCreation = true;35 root.fakeApplication.manualSurfaceCreation = true;
36 root.fakeApplication.setState(ApplicationInfoInterface.Starting);36 root.fakeApplication.setState(ApplicationInfoInterface.Starting);
37 }37 }
38 Component.onDestruction: {
39 applicationWindowLoader.item.removeScreenshot();
40 }
38 property QtObject fakeApplication: null41 property QtObject fakeApplication: null
39 readonly property var fakeSession: fakeApplication ? fakeApplication.session : null42 readonly property var fakeSession: fakeApplication ? fakeApplication.session : null
4043
@@ -331,6 +334,40 @@
331 tryCompare(screenshotImage, "status", Image.Null);334 tryCompare(screenshotImage, "status", Image.Null);
332 }335 }
333336
337 function test_suspendrestartApp() {
338 var screenshotImage = findChild(applicationWindowLoader.item, "screenshotImage");
339
340 initSession();
341 setApplicationState(appRunning);
342 tryCompare(stateGroup, "state", "surface");
343 waitUntilTransitionsEnd();
344
345 setApplicationState(appSuspended);
346
347 cleanupSession();
348
349 tryCompare(stateGroup, "state", "screenshot");
350 waitUntilTransitionsEnd();
351 tryCompare(fakeApplication, "session", null);
352
353 // and restart it
354 setApplicationState(appStarting);
355
356 waitUntilTransitionsEnd();
357 verify(stateGroup.state === "screenshot");
358 verify(fakeSession === null);
359
360 setApplicationState(appRunning);
361
362 waitUntilTransitionsEnd();
363 verify(stateGroup.state === "screenshot");
364
365 initSession();
366
367 tryCompare(stateGroup, "state", "surface");
368 tryCompare(screenshotImage, "status", Image.Null);
369 }
370
334 function test_appCrashed() {371 function test_appCrashed() {
335 initSession();372 initSession();
336 setApplicationState(appRunning);373 setApplicationState(appRunning);
@@ -408,5 +445,12 @@
408 applicationWindowLoader.item.interactive = true;445 applicationWindowLoader.item.interactive = true;
409 compare(fakeSession.surface.activeFocus, true);446 compare(fakeSession.surface.activeFocus, true);
410 }447 }
448
449 function test_no_spinner_if_stopped() {
450 setApplicationState(appStopped);
451 tryCompare(stateGroup, "state", "splashScreen");
452 var splashLoader = findChild(applicationWindowLoader.item, "splashLoader");
453 verify(!splashLoader.item.activeSpinner);
454 }
411 }455 }
412}456}

Subscribers

People subscribed via source and target branches