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