blob: c765b0dffabeee03234c676fa3e8d698d72c6943 [file] [log] [blame]
/*
* Copyright (C) 2015 Savoir-faire Linux Inc.
* Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Additional permission under GNU GPL version 3 section 7:
*
* If you modify this program, or any covered work, by linking or
* combining it with the OpenSSL project's OpenSSL library (or a
* modified version of that library), containing parts covered by the
* terms of the OpenSSL or SSLeay licenses, Savoir-faire Linux Inc.
* grants you additional permission to convey the resulting work.
* Corresponding Source for a non-source form of such a combination
* shall include the source code for the parts of OpenSSL used as well
* as that of the covered work.
*/
#include "ringnotify.h"
#include "config.h"
#if USE_LIBNOTIFY
#include <glib/gi18n.h>
#include <libnotify/notify.h>
#include <memory>
#include <globalinstances.h>
#include "native/pixbufmanipulator.h"
#include <call.h>
#include <QtCore/QSize>
#include <media/text.h>
#include <callmodel.h>
#endif
void
ring_notify_init()
{
#if USE_LIBNOTIFY
notify_init("Ring");
#endif
}
void
ring_notify_uninit()
{
#if USE_LIBNOTIFY
if (notify_is_initted())
notify_uninit();
#endif
}
gboolean
ring_notify_is_initted()
{
#if USE_LIBNOTIFY
return notify_is_initted();
#else
return FALSE;
#endif
}
gboolean
ring_notify_incoming_call(
#if !USE_LIBNOTIFY
G_GNUC_UNUSED
#endif
Call* call)
{
gboolean success = FALSE;
#if USE_LIBNOTIFY
g_return_val_if_fail(call, FALSE);
gchar *body = g_strdup_printf("%s", call->formattedName().toUtf8().constData());
std::shared_ptr<NotifyNotification> notification(
notify_notification_new(_("Incoming call"), body, NULL), g_object_unref);
g_free(body);
/* get photo */
QVariant var_p = GlobalInstances::pixmapManipulator().callPhoto(
call->peerContactMethod(), QSize(50, 50), false);
std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
notify_notification_set_image_from_pixbuf(notification.get(), photo.get());
/* calls have highest urgency */
notify_notification_set_urgency(notification.get(), NOTIFY_URGENCY_CRITICAL);
notify_notification_set_timeout(notification.get(), NOTIFY_EXPIRES_DEFAULT);
GError *error = NULL;
success = notify_notification_show(notification.get(), &error);
if (success) {
/* monitor the life cycle of the call and try to close the notification
* once the call has been aswered */
auto state_changed_conn = std::make_shared<QMetaObject::Connection>();
*state_changed_conn = QObject::connect(
call,
&Call::lifeCycleStateChanged,
[notification, state_changed_conn] (Call::LifeCycleState newState, G_GNUC_UNUSED Call::LifeCycleState previousState)
{
g_return_if_fail(NOTIFY_IS_NOTIFICATION(notification.get()));
if (newState > Call::LifeCycleState::INITIALIZATION) {
/* note: not all systems will actually close the notification
* even if the above function returns as true */
if (!notify_notification_close(notification.get(), NULL))
g_warning("could not close notification");
/* once we (try to) close the notification, we can
* disconnect from this signal; this should also destroy
* the notification shared_ptr as its ref count will
* drop to 0 */
QObject::disconnect(*state_changed_conn);
}
}
);
} else {
g_warning("failed to show notification: %s", error->message);
g_clear_error(&error);
}
#endif
return success;
}
#if USE_LIBNOTIFY
GHashTable *
ring_notify_get_chat_table()
{
static std::unique_ptr<GHashTable, decltype(g_hash_table_destroy)&> chat_table(
nullptr, g_hash_table_destroy);
if (chat_table.get() == nullptr)
chat_table.reset(g_hash_table_new_full(NULL, NULL, NULL, g_object_unref));
return chat_table.get();
}
static void
notification_closed(NotifyNotification *notification, Call *call)
{
g_return_if_fail(call);
if (!g_hash_table_remove(ring_notify_get_chat_table(), call)) {
g_warning("could not find notification associated with the given call");
/* normally removing the notification from the hash table will unref it,
* but if it was not found we should do it here */
g_object_unref(notification);
}
}
static gboolean
ring_notify_message_recieved(Call *call, const QMap<QString,QString>& msg)
{
g_return_val_if_fail(call, FALSE);
gboolean success = FALSE;
GHashTable *chat_table = ring_notify_get_chat_table();
gchar *title = g_strdup_printf(C_("Text message notification", "%s says:"), call->formattedName().toUtf8().constData());
gchar *body = g_strdup_printf("%s", msg["text/plain"].toUtf8().constData());
/* check if a notification already exists for this call */
NotifyNotification *notification = (NotifyNotification *)g_hash_table_lookup(chat_table, call);
if (notification) {
/* update notification; append the new message to the old */
GValue body_value = G_VALUE_INIT;
g_value_init(&body_value, G_TYPE_STRING);
g_object_get_property(G_OBJECT(notification), "body", &body_value);
const gchar* body_old = g_value_get_string(&body_value);
if (body_old && (strlen(body_old) > 0)) {
gchar *body_new = g_strconcat(body_old, "\n", body, NULL);
g_free(body);
body = body_new;
}
notify_notification_update(notification, title, body, NULL);
} else {
/* create new notification object and associate it with the call in the
* hash table; also store the pointer of the call in the notification
* object so that it knows it's key in the hash table */
notification = notify_notification_new(title, body, NULL);
g_hash_table_insert(chat_table, call, notification);
g_object_set_data(G_OBJECT(notification), "call", call);
/* get photo */
QVariant var_p = GlobalInstances::pixmapManipulator().callPhoto(
call->peerContactMethod(), QSize(50, 50), false);
std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
notify_notification_set_image_from_pixbuf(notification, photo.get());
/* normal priority for messages */
notify_notification_set_urgency(notification, NOTIFY_URGENCY_NORMAL);
/* remove the key and value from the hash table once the notification is
* closed; note that this will also unref the notification */
g_signal_connect(notification, "closed", G_CALLBACK(notification_closed), call);
}
g_free(title);
g_free(body);
GError *error = NULL;
success = notify_notification_show(notification, &error);
if (!success) {
g_warning("failed to show notification: %s", error->message);
g_clear_error(&error);
g_hash_table_remove(chat_table, call);
}
return success;
}
static void
ring_notify_call_messages(Call *call, Media::Text *media, RingClient *client)
{
QObject::connect(
media,
&Media::Text::messageReceived,
[call, client] (const QMap<QString,QString>& message) {
g_return_if_fail(call && client);
GtkWindow *main_window = ring_client_get_main_window(client);
if ( main_window && gtk_window_is_active(main_window)) {
/* only notify about messages not in the currently selected call */
if (CallModel::instance()->selectedCall() != call) {
ring_notify_message_recieved(call, message);
}
} else {
/* send notifications for all messages if the window is not visibles*/
ring_notify_message_recieved(call, message);
}
}
);
}
#endif
void
ring_notify_monitor_chat_notifications(
#if !USE_LIBNOTIFY
G_GNUC_UNUSED
#endif
RingClient *client)
{
#if USE_LIBNOTIFY
QObject::connect(
CallModel::instance(),
&QAbstractItemModel::rowsInserted,
[client] (const QModelIndex &parent, int first, int last)
{
g_return_if_fail(client);
for (int row = first; row <= last; ++row) {
QModelIndex idx = CallModel::instance()->index(row, 0, parent);
auto call = CallModel::instance()->getCall(idx);
if (call) {
/* check if text media is already present */
if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::IN)) {
Media::Text *media = call->firstMedia<Media::Text>(Media::Media::Direction::IN);
ring_notify_call_messages(call, media, client);
} else if (call->hasMedia(Media::Media::Type::TEXT, Media::Media::Direction::OUT)) {
Media::Text *media = call->firstMedia<Media::Text>(Media::Media::Direction::OUT);
ring_notify_call_messages(call, media, client);
} else {
/* monitor media for messaging text messaging */
QObject::connect(
call,
&Call::mediaAdded,
[call, client] (Media::Media* media) {
if (media->type() == Media::Media::Type::TEXT)
ring_notify_call_messages(call, (Media::Text *)media, client);
}
);
}
}
}
}
);
#endif
}
gboolean
ring_notify_close_chat_notification(
#if !USE_LIBNOTIFY
G_GNUC_UNUSED
#endif
Call *call)
{
gboolean notification_existed = FALSE;
#if USE_LIBNOTIFY
/* checks if there exists a chat notification associated with the given call
* and tries to close it; if it did exist, then the function returns TRUE */
g_return_val_if_fail(call, FALSE);
GHashTable *chat_table = ring_notify_get_chat_table();
NotifyNotification *notification = (NotifyNotification *)g_hash_table_lookup(chat_table, call);
if (notification) {
notification_existed = TRUE;
GError *error = NULL;
if (!notify_notification_close(notification, &error)) {
g_warning("could not close notification: %s", error->message);
g_clear_error(&error);
/* closing should remove and free the notification from the hash table
* since it failed to close, try to remove the notification from the
* table manually */
g_hash_table_remove(chat_table, call);
}
}
#endif
return notification_existed;
}