blob: 4ced272c04ff931c7fcc5dd66991e9912e611a1a [file] [log] [blame]
Stepan Salenikovich76c33e62015-05-22 12:24:07 -04001/*
2 * Copyright (C) 2015 Savoir-Faire Linux Inc.
3 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (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 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
24 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
25 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30
31#include "ringnotify.h"
32#include "config.h"
33
34#if USE_LIBNOTIFY
35#include <libnotify/notify.h>
36#include <memory>
37#include "delegates/pixbufdelegate.h"
38#include <call.h>
39#include <QtCore/QSize>
Stepan Salenikovich67112d12015-06-16 16:57:06 -040040#include <media/text.h>
41#include <callmodel.h>
Stepan Salenikovich76c33e62015-05-22 12:24:07 -040042#endif
43
44void
45ring_notify_init()
46{
47#if USE_LIBNOTIFY
48 notify_init("Ring");
49#endif
50}
51
52void
53ring_notify_uninit()
54{
55#if USE_LIBNOTIFY
56 if (notify_is_initted())
57 notify_uninit();
58#endif
59}
60
61gboolean
62ring_notify_is_initted()
63{
64#if USE_LIBNOTIFY
65 return notify_is_initted();
66#else
67 return FALSE;
68#endif
69}
70
71gboolean
72ring_notify_incoming_call(
73#if !USE_LIBNOTIFY
74 G_GNUC_UNUSED
75#endif
76 Call* call)
77{
Stepan Salenikovich9675db32015-06-16 14:36:54 -040078 gboolean success = FALSE;
Stepan Salenikovich76c33e62015-05-22 12:24:07 -040079#if USE_LIBNOTIFY
80 g_return_val_if_fail(call, FALSE);
81
82 gchar *body = g_strdup_printf("%s", call->formattedName().toUtf8().constData());
Stepan Salenikovich9675db32015-06-16 14:36:54 -040083 std::shared_ptr<NotifyNotification> notification(
84 notify_notification_new("Incoming call", body, NULL), g_object_unref);
Stepan Salenikovichc2e44262015-06-16 12:46:33 -040085 g_free(body);
Stepan Salenikovich76c33e62015-05-22 12:24:07 -040086
87 /* get photo */
88 QVariant var_p = PixbufDelegate::instance()->callPhoto(
89 call->peerContactMethod(), QSize(50, 50), false);
90 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
Stepan Salenikovich9675db32015-06-16 14:36:54 -040091 notify_notification_set_image_from_pixbuf(notification.get(), photo.get());
Stepan Salenikovich76c33e62015-05-22 12:24:07 -040092
93 /* calls have highest urgency */
Stepan Salenikovich9675db32015-06-16 14:36:54 -040094 notify_notification_set_urgency(notification.get(), NOTIFY_URGENCY_CRITICAL);
95 notify_notification_set_timeout(notification.get(), NOTIFY_EXPIRES_DEFAULT);
Stepan Salenikovich76c33e62015-05-22 12:24:07 -040096
97 GError *error = NULL;
Stepan Salenikovich9675db32015-06-16 14:36:54 -040098 success = notify_notification_show(notification.get(), &error);
99
100 if (success) {
101 /* monitor the life cycle of the call and try to close the notification
102 * once the call has been aswered */
103 auto state_changed_conn = std::make_shared<QMetaObject::Connection>();
104 *state_changed_conn = QObject::connect(
105 call,
106 &Call::lifeCycleStateChanged,
107 [notification, state_changed_conn] (Call::LifeCycleState newState, G_GNUC_UNUSED Call::LifeCycleState previousState)
108 {
109 g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification.get()));
110 if (newState > Call::LifeCycleState::INITIALIZATION) {
111 /* note: not all systems will actually close the notification
112 * even if the above function returns as true */
113 if (!notify_notification_close(notification.get(), NULL))
114 g_warning("could not close notification");
115
116 /* once we (try to) close the notification, we can
117 * disconnect from this signal; this should also destroy
118 * the notification shared_ptr as its ref count will
119 * drop to 0 */
120 QObject::disconnect(*state_changed_conn);
121 }
122 }
123 );
Stepan Salenikovich76c33e62015-05-22 12:24:07 -0400124 } else {
Stepan Salenikovich67112d12015-06-16 16:57:06 -0400125 g_warning("failed to show notification: %s", error->message);
Stepan Salenikovich76c33e62015-05-22 12:24:07 -0400126 g_clear_error(&error);
Stepan Salenikovich76c33e62015-05-22 12:24:07 -0400127 }
Stepan Salenikovich76c33e62015-05-22 12:24:07 -0400128#endif
Stepan Salenikovich9675db32015-06-16 14:36:54 -0400129 return success;
Stepan Salenikovich76c33e62015-05-22 12:24:07 -0400130}
Stepan Salenikovich67112d12015-06-16 16:57:06 -0400131
132#if USE_LIBNOTIFY
133
134GHashTable *
135ring_notify_get_chat_table()
136{
137 static std::unique_ptr<GHashTable, decltype(g_hash_table_destroy)&> chat_table(
138 nullptr, g_hash_table_destroy);
139
140 if (chat_table.get() == nullptr)
141 chat_table.reset(g_hash_table_new_full(NULL, NULL, NULL, g_object_unref));
142
143 return chat_table.get();
144}
145
146static void
147notification_closed(NotifyNotification *notification, Call *call)
148{
149 g_return_if_fail(call);
150
151 if (!g_hash_table_remove(ring_notify_get_chat_table(), call)) {
152 g_warning("could not find notification associated with the given call");
153 /* normally removing the notification from the hash table will unref it,
154 * but if it was not found we should do it here */
155 g_object_unref(notification);
156 }
157}
158
159static gboolean
160ring_notify_message_recieved(Call *call, const QString& msg)
161{
162 g_return_val_if_fail(call, FALSE);
163 gboolean success = FALSE;
164
165 GHashTable *chat_table = ring_notify_get_chat_table();
166
167 gchar *title = g_strdup_printf("%s says:", call->formattedName().toUtf8().constData());
168 gchar *body = g_strdup_printf("%s", msg.toUtf8().constData());
169
170 /* check if a notification already exists for this call */
171 NotifyNotification *notification = (NotifyNotification *)g_hash_table_lookup(chat_table, call);
172 if (notification) {
173 /* update notification; append the new message to the old */
174 GValue body_value = G_VALUE_INIT;
175 g_value_init(&body_value, G_TYPE_STRING);
176 g_object_get_property(G_OBJECT(notification), "body", &body_value);
177 const gchar* body_old = g_value_get_string(&body_value);
178 if (body_old && (strlen(body_old) > 0)) {
179 gchar *body_new = g_strconcat(body_old, "\n", body, NULL);
180 g_free(body);
181 body = body_new;
182 }
183 notify_notification_update(notification, title, body, NULL);
184 } else {
185 /* create new notification object and associate it with the call in the
186 * hash table; also store the pointer of the call in the notification
187 * object so that it knows it's key in the hash table */
188 notification = notify_notification_new(title, body, NULL);
189 g_hash_table_insert(chat_table, call, notification);
190 g_object_set_data(G_OBJECT(notification), "call", call);
191
192 /* get photo */
193 QVariant var_p = PixbufDelegate::instance()->callPhoto(
194 call->peerContactMethod(), QSize(50, 50), false);
195 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
196 notify_notification_set_image_from_pixbuf(notification, photo.get());
197
198 /* normal priority for messages */
199 notify_notification_set_urgency(notification, NOTIFY_URGENCY_NORMAL);
200
201 /* remove the key and value from the hash table once the notification is
202 * closed; note that this will also unref the notification */
203 g_signal_connect(notification, "closed", G_CALLBACK(notification_closed), call);
204 }
205
206 g_free(title);
207 g_free(body);
208
209 GError *error = NULL;
210 success = notify_notification_show(notification, &error);
211 if (!success) {
212 g_warning("failed to show notification: %s", error->message);
213 g_clear_error(&error);
214 g_hash_table_remove(chat_table, call);
215 }
216
217 return success;
218}
219
220static void
221ring_notify_call_messages(Call *call, Media::Text *media, RingClient *client)
222{
223 QObject::connect(
224 media,
225 &Media::Text::messageReceived,
226 [call, client] (const QString& message) {
227 g_return_if_fail(call && client);
228 GtkWindow *main_window = ring_client_get_main_windw(client);
229 if ( main_window && gtk_window_is_active(main_window)) {
230 /* only notify about messages not in the currently selected call */
231 if (CallModel::instance()->selectedCall() != call) {
232 ring_notify_message_recieved(call, message);
233 }
234 } else {
235 /* send notifications for all messages if the window is not visibles*/
236 ring_notify_message_recieved(call, message);
237 }
238 }
239 );
240}
241#endif
242
243void
244ring_notify_monitor_chat_notifications(
245#if !USE_LIBNOTIFY
246 G_GNUC_UNUSED
247#endif
248 RingClient *client)
249{
250#if USE_LIBNOTIFY
251
252 QObject::connect(
253 CallModel::instance(),
254 &QAbstractItemModel::rowsInserted,
255 [client] (const QModelIndex &parent, int first, int last)
256 {
257 g_return_if_fail(client);
258
259 for (int row = first; row <= last; ++row) {
260 QModelIndex idx = CallModel::instance()->index(row, 0, parent);
261 auto call = CallModel::instance()->getCall(idx);
262 if (call) {
263
264 /* check if text media is already present */
265 if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
266 Media::Text *media = call->firstMedia<Media::Text>(Media::Media::Direction::IN);
267 ring_notify_call_messages(call, media, client);
268 } else if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
269 Media::Text *media = call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
270 ring_notify_call_messages(call, media, client);
271 } else {
272 /* monitor media for messaging text messaging */
273 QObject::connect(
274 call,
275 &Call::mediaAdded,
276 [call, client] (Media::Media* media) {
277 if (media->type() == Media::Media::Type::TEXT)
278 ring_notify_call_messages(call, (Media::Text *)media, client);
279 }
280 );
281 }
282 }
283 }
284 }
285 );
286#endif
287 }
288
289gboolean
290ring_notify_close_chat_notification(
291#if !USE_LIBNOTIFY
292 G_GNUC_UNUSED
293#endif
294 Call *call)
295{
296 gboolean notification_existed = FALSE;
297
298#if USE_LIBNOTIFY
299 /* checks if there exists a chat notification associated with the given call
300 * and tries to close it; if it did exist, then the function returns TRUE */
301 g_return_val_if_fail(call, FALSE);
302
303
304 GHashTable *chat_table = ring_notify_get_chat_table();
305
306 NotifyNotification *notification = (NotifyNotification *)g_hash_table_lookup(chat_table, call);
307
308 if (notification) {
309 notification_existed = TRUE;
310
311 GError *error = NULL;
312 if (!notify_notification_close(notification, &error)) {
313 g_warning("could not close notification: %s", error->message);
314 g_clear_error(&error);
315
316 /* closing should remove and free the notification from the hash table
317 * since it failed to close, try to remove the notification from the
318 * table manually */
319 g_hash_table_remove(chat_table, call);
320 }
321 }
322#endif
323
324 return notification_existed;
325}