1 /*
2 * Notifier.cpp
3 * Copyright (C) 2017 Belledonne Communications, Grenoble, France
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 * Created on: February 2, 2017
20 * Author: Ronan Abhamon
21 */
22
23 #include <QQmlComponent>
24 #include <QScreen>
25 #include <QTimer>
26
27 #include "../../app/App.hpp"
28 #include "../../utils/Utils.hpp"
29 #include "../core/CoreManager.hpp"
30
31 #include "Notifier.hpp"
32
33 #define NOTIFICATIONS_PATH "qrc:/ui/modules/Linphone/Notifications/"
34
35 // -----------------------------------------------------------------------------
36 // Notifications QML properties/methods.
37 // -----------------------------------------------------------------------------
38
39 #define NOTIFICATION_SHOW_METHOD_NAME "open"
40
41 #define NOTIFICATION_PROPERTY_DATA "notificationData"
42
43 #define NOTIFICATION_PROPERTY_X "popupX"
44 #define NOTIFICATION_PROPERTY_Y "popupY"
45
46 #define NOTIFICATION_PROPERTY_WINDOW "__internalWindow"
47
48 #define NOTIFICATION_PROPERTY_TIMER "__timer"
49
50 // -----------------------------------------------------------------------------
51 // Arbitrary hardcoded values.
52 // -----------------------------------------------------------------------------
53
54 #define NOTIFICATION_SPACING 10
55 #define N_MAX_NOTIFICATIONS 5
56 #define MAX_TIMEOUT 30000
57
58 using namespace std;
59
60 // =============================================================================
61
62 template<class T>
setProperty(QObject & object,const char * property,const T & value)63 void setProperty (QObject &object, const char *property, const T &value) {
64 if (!object.setProperty(property, QVariant(value))) {
65 qWarning() << QStringLiteral("Unable to set property: `%1`.").arg(property);
66 abort();
67 }
68 }
69
70 // =============================================================================
71 // Available notifications.
72 // =============================================================================
73
74 const QHash<int, Notifier::Notification> Notifier::mNotifications = {
75 { Notifier::ReceivedMessage, { "NotificationReceivedMessage.qml", 10 } },
76 { Notifier::ReceivedFileMessage, { "NotificationReceivedFileMessage.qml", 10 } },
77 { Notifier::ReceivedCall, { "NotificationReceivedCall.qml", 30 } },
78 { Notifier::NewVersionAvailable, { "NotificationNewVersionAvailable.qml", 30 } },
79 { Notifier::SnapshotWasTaken, { "NotificationSnapshotWasTaken.qml", 10 } },
80 { Notifier::RecordingCompleted, { "NotificationRecordingCompleted.qml", 10 } }
81 };
82
83 // -----------------------------------------------------------------------------
84
Notifier(QObject * parent)85 Notifier::Notifier (QObject *parent) : QObject(parent) {
86 const int nComponents = mNotifications.size();
87 mComponents = new QQmlComponent *[nComponents];
88
89 QQmlEngine *engine = App::getInstance()->getEngine();
90 for (const auto &key : mNotifications.keys()) {
91 QQmlComponent *component = new QQmlComponent(engine, QUrl(NOTIFICATIONS_PATH + Notifier::mNotifications[key].filename));
92 if (Q_UNLIKELY(component->isError())) {
93 qWarning() << QStringLiteral("Errors found in `Notification` component %1:").arg(key) << component->errors();
94 abort();
95 }
96 mComponents[key] = component;
97 }
98
99 mMutex = new QMutex();
100 }
101
~Notifier()102 Notifier::~Notifier () {
103 delete mMutex;
104
105 const int nComponents = mNotifications.size();
106 for (int i = 0; i < nComponents; ++i)
107 delete mComponents[i];
108 delete[] mComponents;
109 }
110
111 // -----------------------------------------------------------------------------
112
createNotification(Notifier::NotificationType type)113 QObject *Notifier::createNotification (Notifier::NotificationType type) {
114 mMutex->lock();
115
116 Q_ASSERT(mInstancesNumber <= N_MAX_NOTIFICATIONS);
117
118 // Check existing instances.
119 if (mInstancesNumber == N_MAX_NOTIFICATIONS) {
120 qWarning() << QStringLiteral("Unable to create another notification.");
121 mMutex->unlock();
122 return nullptr;
123 }
124
125 // Create instance and set attributes.
126 QObject *instance = mComponents[type]->create();
127 qInfo() << QStringLiteral("Create notification:") << instance;
128
129 mInstancesNumber++;
130
131 {
132 QQuickWindow *window = instance->findChild<QQuickWindow *>(NOTIFICATION_PROPERTY_WINDOW);
133 Q_CHECK_PTR(window);
134
135 QScreen *screen = window->screen();
136 Q_CHECK_PTR(screen);
137
138 QRect geometry = screen->availableGeometry();
139
140 // Set X/Y. (Not Pokémon games.)
141 int windowHeight = window->height();
142 int offset = geometry.y() + geometry.height() - windowHeight;
143
144 ::setProperty(*instance, NOTIFICATION_PROPERTY_X, geometry.x() + geometry.width() - window->width());
145 ::setProperty(*instance, NOTIFICATION_PROPERTY_Y, offset - (mOffset % offset));
146
147 // Update offset.
148 mOffset = (windowHeight + mOffset) + NOTIFICATION_SPACING;
149 if (mOffset - offset + geometry.y() >= 0)
150 mOffset = 0;
151 }
152
153 mMutex->unlock();
154
155 return instance;
156 }
157
158 // -----------------------------------------------------------------------------
159
showNotification(QObject * notification,int timeout)160 void Notifier::showNotification (QObject *notification, int timeout) {
161 // Display notification.
162 QMetaObject::invokeMethod(notification, NOTIFICATION_SHOW_METHOD_NAME, Qt::DirectConnection);
163
164 QTimer *timer = new QTimer(notification);
165 timer->setInterval(timeout > MAX_TIMEOUT ? MAX_TIMEOUT : timeout);
166 timer->setSingleShot(true);
167 notification->setProperty(NOTIFICATION_PROPERTY_TIMER, QVariant::fromValue(timer));
168
169 // Destroy it after timeout.
170 QObject::connect(timer, &QTimer::timeout, this, [this, notification]() {
171 deleteNotification(QVariant::fromValue(notification));
172 });
173
174 // Called explicitly (by a click on notification for example)
175 QObject::connect(notification, SIGNAL(deleteNotification(QVariant)), this, SLOT(deleteNotification(QVariant)));
176
177 timer->start();
178 }
179
180 // -----------------------------------------------------------------------------
181
deleteNotification(QVariant notification)182 void Notifier::deleteNotification (QVariant notification) {
183 mMutex->lock();
184
185 QObject *instance = notification.value<QObject *>();
186
187 // Notification marked destroyed.
188 if (instance->property("__valid").isValid()) {
189 mMutex->unlock();
190 return;
191 }
192
193 qInfo() << QStringLiteral("Delete notification:") << instance;
194
195 instance->setProperty("__valid", true);
196 instance->property(NOTIFICATION_PROPERTY_TIMER).value<QTimer *>()->stop();
197
198 mInstancesNumber--;
199 Q_ASSERT(mInstancesNumber >= 0);
200
201 if (mInstancesNumber == 0)
202 mOffset = 0;
203
204 mMutex->unlock();
205
206 instance->deleteLater();
207 }
208
209 // =============================================================================
210
211 #define CREATE_NOTIFICATION(TYPE) \
212 QObject * notification = createNotification(TYPE); \
213 if (!notification) \
214 return; \
215 const int timeout = mNotifications[TYPE].timeout * 1000;
216
217 #define SHOW_NOTIFICATION(DATA) \
218 ::setProperty(*notification, NOTIFICATION_PROPERTY_DATA, DATA); \
219 showNotification(notification, timeout);
220
221 // -----------------------------------------------------------------------------
222 // Notification functions.
223 // -----------------------------------------------------------------------------
224
notifyReceivedMessage(const shared_ptr<linphone::ChatMessage> & message)225 void Notifier::notifyReceivedMessage (const shared_ptr<linphone::ChatMessage> &message) {
226 CREATE_NOTIFICATION(Notifier::ReceivedMessage);
227
228 QVariantMap map;
229 map["message"] = message->getFileTransferInformation()
230 ? tr("newFileMessage")
231 : ::Utils::coreStringToAppString(message->getText());
232
233 map["sipAddress"] = ::Utils::coreStringToAppString(message->getFromAddress()->asStringUriOnly());
234 map["window"].setValue(App::getInstance()->getMainWindow());
235
236 SHOW_NOTIFICATION(map);
237 }
238
notifyReceivedFileMessage(const shared_ptr<linphone::ChatMessage> & message)239 void Notifier::notifyReceivedFileMessage (const shared_ptr<linphone::ChatMessage> &message) {
240 CREATE_NOTIFICATION(Notifier::ReceivedFileMessage);
241
242 QVariantMap map;
243 map["fileUri"] = ::Utils::coreStringToAppString(message->getFileTransferFilepath());
244 map["fileSize"] = static_cast<quint64>(message->getFileTransferInformation()->getSize());
245
246 SHOW_NOTIFICATION(map);
247 }
248
notifyReceivedCall(const shared_ptr<linphone::Call> & call)249 void Notifier::notifyReceivedCall (const shared_ptr<linphone::Call> &call) {
250 CREATE_NOTIFICATION(Notifier::ReceivedCall);
251
252 CallModel *callModel = &call->getData<CallModel>("call-model");
253
254 QObject::connect(callModel, &CallModel::statusChanged, notification, [this, notification](CallModel::CallStatus status) {
255 if (status == CallModel::CallStatusEnded || status == CallModel::CallStatusConnected)
256 deleteNotification(QVariant::fromValue(notification));
257 });
258
259 QVariantMap map;
260 map["call"].setValue(callModel);
261
262 SHOW_NOTIFICATION(map);
263 }
264
notifyNewVersionAvailable(const QString & version,const QString & url)265 void Notifier::notifyNewVersionAvailable (const QString &version, const QString &url) {
266 CREATE_NOTIFICATION(Notifier::NewVersionAvailable);
267
268 QVariantMap map;
269 map["message"] = tr("newVersionAvailable").arg(version);
270 map["url"] = url;
271
272 SHOW_NOTIFICATION(map);
273 }
274
notifySnapshotWasTaken(const QString & filePath)275 void Notifier::notifySnapshotWasTaken (const QString &filePath) {
276 CREATE_NOTIFICATION(Notifier::SnapshotWasTaken);
277
278 QVariantMap map;
279 map["filePath"] = filePath;
280
281 SHOW_NOTIFICATION(map);
282 }
283
notifyRecordingCompleted(const QString & filePath)284 void Notifier::notifyRecordingCompleted (const QString &filePath) {
285 CREATE_NOTIFICATION(Notifier::RecordingCompleted);
286
287 QVariantMap map;
288 map["filePath"] = filePath;
289
290 SHOW_NOTIFICATION(map);
291 }
292
293 #undef SHOW_NOTIFICATION
294 #undef CREATE_NOTIFICATION
295