1 /*
2 SPDX-FileCopyrightText: 2016 Martin Graesslin <mgraesslin@kde.org>
3
4 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
5
6 */
7
8 #include "onscreennotification.h"
9 #include "input.h"
10 #include "input_event.h"
11 #include "input_event_spy.h"
12 #include <config-kwin.h>
13
14 #include <QPropertyAnimation>
15 #include <QStandardPaths>
16 #include <QTimer>
17 #include <QQmlComponent>
18 #include <QQmlContext>
19 #include <QQmlEngine>
20 #include <QQuickWindow>
21
22 #include <KConfigGroup>
23
24 #include <functional>
25
26 namespace KWin
27 {
28
29 class OnScreenNotificationInputEventSpy : public InputEventSpy
30 {
31 public:
32 explicit OnScreenNotificationInputEventSpy(OnScreenNotification *parent);
33
34 void pointerEvent(MouseEvent *event) override;
35 private:
36 OnScreenNotification *m_parent;
37 };
38
OnScreenNotificationInputEventSpy(OnScreenNotification * parent)39 OnScreenNotificationInputEventSpy::OnScreenNotificationInputEventSpy(OnScreenNotification *parent)
40 : m_parent(parent)
41 {
42 }
43
pointerEvent(MouseEvent * event)44 void OnScreenNotificationInputEventSpy::pointerEvent(MouseEvent *event)
45 {
46 if (event->type() != QEvent::MouseMove) {
47 return;
48 }
49
50 m_parent->setContainsPointer(m_parent->geometry().contains(event->globalPos()));
51 }
52
53
OnScreenNotification(QObject * parent)54 OnScreenNotification::OnScreenNotification(QObject *parent)
55 : QObject(parent)
56 , m_timer(new QTimer(this))
57 {
58 m_timer->setSingleShot(true);
59 connect(m_timer, &QTimer::timeout, this, std::bind(&OnScreenNotification::setVisible, this, false));
60 connect(this, &OnScreenNotification::visibleChanged, this,
61 [this] {
62 if (m_visible) {
63 show();
64 } else {
65 m_timer->stop();
66 m_spy.reset();
67 m_containsPointer = false;
68 }
69 }
70 );
71 }
72
~OnScreenNotification()73 OnScreenNotification::~OnScreenNotification()
74 {
75 if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_mainItem.data())) {
76 w->hide();
77 w->destroy();
78 }
79 }
80
setConfig(KSharedConfigPtr config)81 void OnScreenNotification::setConfig(KSharedConfigPtr config)
82 {
83 m_config = config;
84 }
85
setEngine(QQmlEngine * engine)86 void OnScreenNotification::setEngine(QQmlEngine *engine)
87 {
88 m_qmlEngine = engine;
89 }
90
isVisible() const91 bool OnScreenNotification::isVisible() const
92 {
93 return m_visible;
94 }
95
setVisible(bool visible)96 void OnScreenNotification::setVisible(bool visible)
97 {
98 if (m_visible == visible) {
99 return;
100 }
101 m_visible = visible;
102 Q_EMIT visibleChanged();
103 }
104
message() const105 QString OnScreenNotification::message() const
106 {
107 return m_message;
108 }
109
setMessage(const QString & message)110 void OnScreenNotification::setMessage(const QString &message)
111 {
112 if (m_message == message) {
113 return;
114 }
115 m_message = message;
116 Q_EMIT messageChanged();
117 }
118
iconName() const119 QString OnScreenNotification::iconName() const
120 {
121 return m_iconName;
122 }
123
setIconName(const QString & iconName)124 void OnScreenNotification::setIconName(const QString &iconName)
125 {
126 if (m_iconName == iconName) {
127 return;
128 }
129 m_iconName = iconName;
130 Q_EMIT iconNameChanged();
131 }
132
timeout() const133 int OnScreenNotification::timeout() const
134 {
135 return m_timer->interval();
136 }
137
setTimeout(int timeout)138 void OnScreenNotification::setTimeout(int timeout)
139 {
140 if (m_timer->interval() == timeout) {
141 return;
142 }
143 m_timer->setInterval(timeout);
144 Q_EMIT timeoutChanged();
145 }
146
show()147 void OnScreenNotification::show()
148 {
149 Q_ASSERT(m_visible);
150 ensureQmlContext();
151 ensureQmlComponent();
152 createInputSpy();
153 if (m_timer->interval() != 0) {
154 m_timer->start();
155 }
156 }
157
ensureQmlContext()158 void OnScreenNotification::ensureQmlContext()
159 {
160 Q_ASSERT(m_qmlEngine);
161 if (!m_qmlContext.isNull()) {
162 return;
163 }
164 m_qmlContext.reset(new QQmlContext(m_qmlEngine));
165 m_qmlContext->setContextProperty(QStringLiteral("osd"), this);
166 }
167
ensureQmlComponent()168 void OnScreenNotification::ensureQmlComponent()
169 {
170 Q_ASSERT(m_config);
171 Q_ASSERT(m_qmlEngine);
172 if (!m_qmlComponent.isNull()) {
173 return;
174 }
175 m_qmlComponent.reset(new QQmlComponent(m_qmlEngine));
176 const QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
177 m_config->group(QStringLiteral("OnScreenNotification")).readEntry("QmlPath", QStringLiteral(KWIN_NAME "/onscreennotification/plasma/main.qml")));
178 if (fileName.isEmpty()) {
179 return;
180 }
181 m_qmlComponent->loadUrl(QUrl::fromLocalFile(fileName));
182 if (!m_qmlComponent->isError()) {
183 m_mainItem.reset(m_qmlComponent->create(m_qmlContext.data()));
184 } else {
185 m_qmlComponent.reset();
186 }
187 }
188
createInputSpy()189 void OnScreenNotification::createInputSpy()
190 {
191 Q_ASSERT(m_spy.isNull());
192 if (auto w = qobject_cast<QQuickWindow*>(m_mainItem.data())) {
193 m_spy.reset(new OnScreenNotificationInputEventSpy(this));
194 input()->installInputEventSpy(m_spy.data());
195 if (!m_animation) {
196 m_animation = new QPropertyAnimation(w, "opacity", this);
197 m_animation->setStartValue(1.0);
198 m_animation->setEndValue(0.0);
199 m_animation->setDuration(250);
200 m_animation->setEasingCurve(QEasingCurve::InOutCubic);
201 }
202 }
203 }
204
geometry() const205 QRect OnScreenNotification::geometry() const
206 {
207 if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_mainItem.data())) {
208 return w->geometry();
209 }
210 return QRect();
211 }
212
setContainsPointer(bool contains)213 void OnScreenNotification::setContainsPointer(bool contains)
214 {
215 if (m_containsPointer == contains) {
216 return;
217 }
218 m_containsPointer = contains;
219 if (!m_animation) {
220 return;
221 }
222 m_animation->setDirection(m_containsPointer ? QAbstractAnimation::Forward : QAbstractAnimation::Backward);
223 m_animation->start();
224 }
225
setSkipCloseAnimation(bool skip)226 void OnScreenNotification::setSkipCloseAnimation(bool skip)
227 {
228 if (QQuickWindow *w = qobject_cast<QQuickWindow*>(m_mainItem.data())) {
229 w->setProperty("KWIN_SKIP_CLOSE_ANIMATION", skip);
230 }
231 }
232
233 } // namespace KWin
234