on xenial help sign in
First attempt at saving/loading screeshots
489b028Albert Astals Cid
debian/unity8-private.install+1
@@ -6,6 +6,7 @@ usr/lib/*/unity8/qml/LightDM
6usr/lib/*/unity8/qml/Lights
6usr/lib/*/unity8/qml/Lights
7usr/lib/*/unity8/qml/Powerd
7usr/lib/*/unity8/qml/Powerd
8usr/lib/*/unity8/qml/SessionBroadcast
8usr/lib/*/unity8/qml/SessionBroadcast
9+usr/lib/*/unity8/qml/SessionScreenshoter
9usr/lib/*/unity8/qml/ScreenGrabber
10usr/lib/*/unity8/qml/ScreenGrabber
10usr/lib/*/unity8/qml/Ubuntu
11usr/lib/*/unity8/qml/Ubuntu
11usr/lib/*/unity8/qml/Unity
12usr/lib/*/unity8/qml/Unity
plugins/CMakeLists.txt+1
@@ -18,6 +18,7 @@ add_subdirectory(Lights)
18add_subdirectory(Dash)
18add_subdirectory(Dash)
19add_subdirectory(Powerd)
19add_subdirectory(Powerd)
20add_subdirectory(SessionBroadcast)
20add_subdirectory(SessionBroadcast)
21+add_subdirectory(SessionScreenshoter)
21add_subdirectory(ScreenGrabber)
22add_subdirectory(ScreenGrabber)
22add_subdirectory(Ubuntu)
23add_subdirectory(Ubuntu)
23add_subdirectory(Unity)
24add_subdirectory(Unity)
plugins/SessionScreenshoter/CMakeLists.txtnew file+13
@@ -0,0 +1,13 @@
1+include_directories(
2+ ${CMAKE_CURRENT_BINARY_DIR}
3+)
4+
5+set(SOURCES
6+ sessionscreenshoter.cpp
7+ plugin.cpp
8+)
9+
10+add_library(SessionScreenshoter-qml MODULE ${SOURCES})
11+qt5_use_modules(SessionScreenshoter-qml Qml Gui Quick Concurrent)
12+
13+add_unity8_plugin(SessionScreenshoter 0.1 SessionScreenshoter TARGETS SessionScreenshoter-qml)
plugins/SessionScreenshoter/plugin.cppnew file+27
@@ -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 "sessionscreenshoter.h"
20+
21+#include <QtQml/qqml.h>
22+
23+void SessionScreenshoterPlugin::registerTypes(const char *uri)
24+{
25+ Q_ASSERT(uri == QLatin1String("SessionScreenshoter"));
26+ qmlRegisterType<SessionScreenshoter>(uri, 0, 1, "SessionScreenshoter");
27+}
plugins/SessionScreenshoter/plugin.hnew file+32
@@ -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 SESSIONSCREENSHOTER_PLUGIN_H
19+#define SESSIONSCREENSHOTER_PLUGIN_H
20+
21+#include <QtQml/QQmlExtensionPlugin>
22+
23+class SessionScreenshoterPlugin : public QQmlExtensionPlugin
24+{
25+ Q_OBJECT
26+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
27+
28+public:
29+ void registerTypes(const char *uri);
30+};
31+
32+#endif
plugins/SessionScreenshoter/qmldirnew file+2
@@ -0,0 +1,2 @@
1+module SessionScreenshoter
2+plugin SessionScreenshoter-qml
plugins/SessionScreenshoter/sessionscreenshoter.cppnew file+130
@@ -0,0 +1,130 @@
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 "sessionscreenshoter.h"
19+
20+#include <QDebug>
21+#include <QQuickItem>
22+#include <QQuickItemGrabResult>
23+#include <QtConcurrent>
24+
25+static QString cachePath()
26+{
27+ return QDir::homePath() + "/.cache/app_shots/";
28+}
29+
30+SessionScreenshoter::SessionScreenshoter(QObject *parent)
31+ : QObject(parent),
32+ m_target(nullptr)
33+{
34+ // TODO tests
35+}
36+
37+QString SessionScreenshoter::appId() const
38+{
39+ return m_appId;
40+}
41+
42+void SessionScreenshoter::setAppdId(const QString &appId)
43+{
44+ if (appId != m_appId) {
45+ m_appId = appId;
46+ const QString path = cachePath() + appId + ".png";
47+ if (QFile::exists(path)) {
48+ setPath(path);
49+ }
50+ }
51+}
52+
53+QQuickItem *SessionScreenshoter::target() const
54+{
55+ return m_target;
56+}
57+
58+void SessionScreenshoter::setTarget(QQuickItem *target)
59+{
60+ if (target != m_target) {
61+ m_target = target;
62+ Q_EMIT targetChanged();
63+ }
64+}
65+
66+QString SessionScreenshoter::path() const
67+{
68+ return m_path;
69+}
70+
71+void SessionScreenshoter::setPath(const QString &path)
72+{
73+ if (path != m_path) {
74+ m_path = path;
75+ Q_EMIT pathChanged();
76+ }
77+}
78+
79+static QString saveScreenshot(const QString &appId, const QSharedPointer<QQuickItemGrabResult> grabResult)
80+{
81+ const QImage image = grabResult->image();
82+ const QString cPath = cachePath();
83+ QDir d;
84+ d.mkpath(cPath);
85+ const QString path = cPath + appId + ".png";
86+ bool b = image.save(path);
87+ if (b) {
88+ return path;
89+ } else {
90+ return QString();
91+ }
92+}
93+
94+void SessionScreenshoter::take()
95+{
96+ if (!m_grabResult.isNull()) {
97+ qWarning() << "Asked to take a screenshot when there's one already being taken, ignoring";
98+ return;
99+ }
100+
101+ if (m_target && !m_appId.isEmpty()) {
102+ // grabToImage runs in a separate thread
103+ m_grabResult = m_target->grabToImage();
104+ connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, &SessionScreenshoter::grabReady);
105+ } else {
106+ qWarning() << "Can't take screnshot: appId:" << m_appId << " target:" << m_target;
107+ }
108+}
109+
110+void SessionScreenshoter::removeScreenshot()
111+{
112+ const QString path = cachePath() + m_appId + ".png";
113+ QFile::remove(path);
114+}
115+
116+void SessionScreenshoter::grabReady()
117+{
118+ QFuture<QString> f = QtConcurrent::run(saveScreenshot, m_appId, m_grabResult);
119+ m_watcher.reset(new QFutureWatcher<QString>(this));
120+ m_watcher->setFuture(f);
121+ connect(m_watcher.data(), &QFutureWatcher<QString>::finished, this, &SessionScreenshoter::saveFinished);
122+}
123+
124+void SessionScreenshoter::saveFinished()
125+{
126+ setPath(m_watcher->future().result());
127+ m_watcher.clear();
128+ m_grabResult.clear();
129+ Q_EMIT screenshotTaken();
130+}
plugins/SessionScreenshoter/sessionscreenshoter.hnew file+70
@@ -0,0 +1,70 @@
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 SESSIONSCREENSHOTER_H
19+#define SESSIONSCREENSHOTER_H
20+
21+#include <QObject>
22+#include <QSharedPointer>
23+#include <QFutureWatcher>
24+
25+class QQuickItem;
26+class QQuickItemGrabResult;
27+
28+class SessionScreenshoter : public QObject
29+{
30+ Q_OBJECT
31+
32+ Q_PROPERTY(QString appId READ appId WRITE setAppdId NOTIFY appdIdChanged)
33+ Q_PROPERTY(QString path READ path NOTIFY pathChanged)
34+ Q_PROPERTY(QQuickItem *target READ target WRITE setTarget NOTIFY targetChanged)
35+
36+public:
37+ explicit SessionScreenshoter(QObject *parent = 0);
38+
39+ QString appId() const;
40+ void setAppdId(const QString &appId);
41+
42+ QQuickItem *target() const;
43+ void setTarget(QQuickItem *target);
44+
45+ QString path() const;
46+
47+ Q_INVOKABLE void take();
48+ Q_INVOKABLE void removeScreenshot();
49+
50+Q_SIGNALS:
51+ void appdIdChanged();
52+ void targetChanged();
53+ void pathChanged();
54+ void screenshotTaken();
55+
56+private Q_SLOTS:
57+ void grabReady();
58+ void saveFinished();
59+
60+private:
61+ void setPath(const QString &path);
62+
63+ QString m_appId;
64+ QString m_path;
65+ QQuickItem *m_target;
66+ QSharedPointer<QQuickItemGrabResult> m_grabResult;
67+ QSharedPointer<QFutureWatcher<QString>> m_watcher;
68+};
69+
70+#endif
qml/Stages/ApplicationWindow.qml+25−11
@@ -17,6 +17,7 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 1.1
18import Ubuntu.Components 1.1
19import Unity.Application 0.1
19import Unity.Application 0.1
20+import SessionScreenshoter 0.1
20
21
21FocusScope {
22FocusScope {
22 id: root
23 id: root
@@ -29,11 +30,16 @@ FocusScope {
29 property QtObject application
30 property QtObject application
30 property int orientation
31 property int orientation
31
32
33+ function removeScreenshot() {
34+ screenshoter.removeScreenshot();
35+ }
36+
32 QtObject {
37 QtObject {
33 id: d
38 id: d
34
39
35 // helpers so that we don't have to check for the existence of an application everywhere
40 // helpers so that we don't have to check for the existence of an application everywhere
36 // (in order to avoid breaking qml binding due to a javascript exception)
41 // (in order to avoid breaking qml binding due to a javascript exception)
42+ readonly property string appId: root.application ? root.application.appId : ""
37 readonly property string name: root.application ? root.application.name : ""
43 readonly property string name: root.application ? root.application.name : ""
38 readonly property url icon: root.application ? root.application.icon : ""
44 readonly property url icon: root.application ? root.application.icon : ""
39 readonly property int applicationState: root.application ? root.application.state : -1
45 readonly property int applicationState: root.application ? root.application.state : -1
@@ -49,11 +55,10 @@ FocusScope {
49 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
55 property bool hadSurface: sessionContainer.surfaceContainer.hadSurface
50
56
51 property bool needToTakeScreenshot:
57 property bool needToTakeScreenshot:
52− sessionContainer.surface && d.surfaceInitialized && screenshotImage.status === Image.Null
53− && d.applicationState === ApplicationInfoInterface.Stopped
58+ sessionContainer.surface && d.surfaceInitialized && (d.applicationState === ApplicationInfoInterface.Stopped || d.applicationState === ApplicationInfoInterface.Suspended)
54 onNeedToTakeScreenshotChanged: {
59 onNeedToTakeScreenshotChanged: {
55 if (needToTakeScreenshot) {
60 if (needToTakeScreenshot) {
56− screenshotImage.take();
61+ screenshoter.take();
57 }
62 }
58 }
63 }
59
64
@@ -73,22 +78,28 @@ FocusScope {
73 Image {
78 Image {
74 id: screenshotImage
79 id: screenshotImage
75 objectName: "screenshotImage"
80 objectName: "screenshotImage"
76− source: d.defaultScreenshot
81+ source: screenshoter.path != "" ? screenshoter.path : d.defaultScreenshot
77 anchors.fill: parent
82 anchors.fill: parent
78 antialiasing: !root.interactive
83 antialiasing: !root.interactive
79−
84+ cache: false
80− function take() {
85+ asynchronous: true
81− // Format: "image://application/$APP_ID/$CURRENT_TIME_MS"
82− // eg: "image://application/calculator-app/123456"
83− var timeMs = new Date().getTime();
84− source = "image://application/" + root.application.appId + "/" + timeMs;
85− }
86
86
87 // Save memory by using a half-resolution (thus quarter size) screenshot
87 // Save memory by using a half-resolution (thus quarter size) screenshot
88 sourceSize.width: root.width / 2
88 sourceSize.width: root.width / 2
89 sourceSize.height: root.height / 2
89 sourceSize.height: root.height / 2
90 }
90 }
91
91
92+ SessionScreenshoter {
93+ id: screenshoter
94+ appId: d.appId
95+ target: root
96+
97+ onScreenshotTaken: {
98+ screenshotImage.source = "";
99+ screenshotImage.source = screenshoter.path;
100+ }
101+ }
102+
92 Loader {
103 Loader {
93 id: splashLoader
104 id: splashLoader
94 visible: active
105 visible: active
@@ -104,6 +115,9 @@ FocusScope {
104 backgroundColor: d.splashColor
115 backgroundColor: d.splashColor
105 headerColor: d.splashColorHeader
116 headerColor: d.splashColorHeader
106 footerColor: d.splashColorFooter
117 footerColor: d.splashColorFooter
118+ // TODO show .desktop file splash screen also if
119+ // we do not have a screenshot but obviously without spinner
120+ activeSpinner: true
107 }
121 }
108 }
122 }
109 }
123 }
qml/Stages/Splash.qml+2
@@ -31,6 +31,7 @@ Item {
31 property url icon
31 property url icon
32 property alias title: header.title
32 property alias title: header.title
33 property alias showHeader: header.visible
33 property alias showHeader: header.visible
34+ property alias activeSpinner: activityIndicator.running
34
35
35 Ambiance.Palette {
36 Ambiance.Palette {
36 id: ambiancePalette
37 id: ambiancePalette
@@ -144,6 +145,7 @@ Item {
144 }
145 }
145
146
146 ActivityIndicator {
147 ActivityIndicator {
148+ id: activityIndicator
147 anchors.centerIn: header.visible ? parent : undefined
149 anchors.centerIn: header.visible ? parent : undefined
148 anchors.verticalCenterOffset: header.visible ? header.height / 2 : 0
150 anchors.verticalCenterOffset: header.visible ? header.height / 2 : 0
149
151
qml/Stages/SpreadDelegate.qml+1
@@ -157,6 +157,7 @@ FocusScope {
157 dragArea.moving = false;
157 dragArea.moving = false;
158 if (requestClose) {
158 if (requestClose) {
159 root.closed();
159 root.closed();
160+ appWindow.removeScreenshot();
160 } else {
161 } else {
161 dragArea.distance = 0;
162 dragArea.distance = 0;
162 }
163 }