Skip to content
Snippets Groups Projects
Commit fc79fc13 authored by Stepan Salenikovich's avatar Stepan Salenikovich Committed by gerrit2
Browse files

don't show read messages in chat notifications

This patch prevent showing already read messages in chat notifications
which was happening in certain cases. This is fixed in several
different ways depending on which notification daemon is being used
on the system.

In the case of notify-osd, even though it supports appending
notifications, we try to update the previous notification, but only
with unread messages. The issue with appending is that notify-osd
does not respond to trying to close notifications, which means messages
which have been marked as read already will continue to be displayed.

In the case of the notification daemons which don't support appending,
we simply replace the old msg text. This prevents many notifications
from the same person from building up; the new messages are also
displayed immediately instead of waiting for the notification timeout.
We also don't try to display multiple unread messages because these
daemons don't usually support multi-line message bodies.

Change-Id: Ibbd5adbdd5eb4bafadb517ac39064eaecd74228e
Tuleap: #426
parent d9a38306
Branches
No related tags found
No related merge requests found
...@@ -35,11 +35,66 @@ ...@@ -35,11 +35,66 @@
#include <media/recordingmodel.h> #include <media/recordingmodel.h>
#endif #endif
#if USE_LIBNOTIFY
static constexpr int MAX_NOTIFICATIONS = 10; // max unread chat msgs to display from the same contact
static constexpr const char* SERVER_NOTIFY_OSD = "notify-osd";
/* struct to store the parsed list of the notify server capabilities */
struct RingNotifyServerInfo
{
/* info */
char *name;
char *vendor;
char *version;
char *spec;
/* capabilities */
gboolean append;
gboolean actions;
/* the info strings must be freed */
~RingNotifyServerInfo() {
g_free(name);
g_free(vendor);
g_free(version);
g_free(spec);
}
};
static struct RingNotifyServerInfo server_info;
#endif
void void
ring_notify_init() ring_notify_init()
{ {
#if USE_LIBNOTIFY #if USE_LIBNOTIFY
notify_init("Ring"); notify_init("Ring");
/* get notify server info */
if (notify_get_server_info(&server_info.name,
&server_info.vendor,
&server_info.version,
&server_info.spec)) {
g_debug("notify server name: %s, vendor: %s, version: %s, spec: %s",
server_info.name, server_info.vendor, server_info.version, server_info.spec);
}
/* check notify server capabilities */
auto list = notify_get_server_caps();
while (list) {
if (g_strcmp0((const char *)list->data, "append") == 0 ||
g_strcmp0((const char *)list->data, "x-canonical-append") == 0) {
server_info.append = TRUE;
}
if (g_strcmp0((const char *)list->data, "actions") == 0) {
server_info.actions = TRUE;
}
list = g_list_next(list);
}
g_list_free_full(list, g_free);
#endif #endif
} }
...@@ -125,14 +180,35 @@ ring_notify_incoming_call( ...@@ -125,14 +180,35 @@ ring_notify_incoming_call(
#if USE_LIBNOTIFY #if USE_LIBNOTIFY
static void
ring_notify_free_list(gpointer, GList *value, gpointer)
{
if (value) {
g_object_unref(G_OBJECT(value->data));
g_list_free(value);
}
}
static void
ring_notify_free_chat_table(GHashTable *table) {
if (table) {
g_hash_table_foreach(table, (GHFunc)ring_notify_free_list, nullptr);
g_hash_table_destroy(table);
}
}
/**
* Returns a pointer to a GHashTable which contains key,value pairs where a ContactMethod pointer
* is the key and a GList of notifications for that CM is the vlue.
*/
GHashTable * GHashTable *
ring_notify_get_chat_table() ring_notify_get_chat_table()
{ {
static std::unique_ptr<GHashTable, decltype(g_hash_table_destroy)&> chat_table( static std::unique_ptr<GHashTable, decltype(ring_notify_free_chat_table)&> chat_table(
nullptr, g_hash_table_destroy); nullptr, ring_notify_free_chat_table);
if (chat_table.get() == nullptr) if (chat_table.get() == nullptr)
chat_table.reset(g_hash_table_new_full(NULL, NULL, NULL, g_object_unref)); chat_table.reset(g_hash_table_new(NULL, NULL));
return chat_table.get(); return chat_table.get();
} }
...@@ -142,72 +218,110 @@ notification_closed(NotifyNotification *notification, ContactMethod *cm) ...@@ -142,72 +218,110 @@ notification_closed(NotifyNotification *notification, ContactMethod *cm)
{ {
g_return_if_fail(cm); g_return_if_fail(cm);
if (!g_hash_table_remove(ring_notify_get_chat_table(), cm)) { /* remove from the list */
g_warning("could not find notification associated with the given ContactMethod"); auto chat_table = ring_notify_get_chat_table();
/* normally removing the notification from the hash table will unref it, if (auto list = (GList *)g_hash_table_lookup(chat_table, cm)) {
* but if it was not found we should do it here */ list = g_list_remove(list, notification);
g_object_unref(notification); if (list) {
// the head of the list may have changed
g_hash_table_replace(chat_table, cm, list);
} else {
g_hash_table_remove(chat_table, cm);
} }
} }
g_object_unref(notification);
}
static gboolean static gboolean
ring_notify_show_text_message(ContactMethod *cm, const QModelIndex& idx) ring_notify_show_text_message(ContactMethod *cm, const QModelIndex& idx)
{ {
g_return_val_if_fail(idx.isValid() && cm, FALSE); g_return_val_if_fail(idx.isValid() && cm, FALSE);
gboolean success = FALSE; gboolean success = FALSE;
GHashTable *chat_table = ring_notify_get_chat_table(); auto title = g_markup_printf_escaped(C_("Text message notification", "%s says:"), idx.data(static_cast<int>(Ring::Role::Name)).toString().toUtf8().constData());
auto body = g_markup_escape_text(idx.data(Qt::DisplayRole).toString().toUtf8().constData(), -1);
auto title = g_strdup_printf(C_("Text message notification", "%s says:"), idx.data(static_cast<int>(Ring::Role::Name)).toString().toUtf8().constData());
auto body = g_strdup_printf("%s", idx.data(Qt::DisplayRole).toString().toUtf8().constData()); NotifyNotification *notification_new = nullptr;
NotifyNotification *notification_old = nullptr;
/* check if a notification already exists for this CM */
NotifyNotification *notification = (NotifyNotification *)g_hash_table_lookup(chat_table, cm); /* try to get the previous notification */
if (notification) { auto chat_table = ring_notify_get_chat_table();
/* update notification; append the new message to the old */ auto list = (GList *)g_hash_table_lookup(chat_table, cm);
GValue body_value = G_VALUE_INIT; if (list)
g_value_init(&body_value, G_TYPE_STRING); notification_old = (NotifyNotification *)list->data;
g_object_get_property(G_OBJECT(notification), "body", &body_value);
const gchar* body_old = g_value_get_string(&body_value); /* we display chat notifications in different ways to suit different notification servers and
if (body_old && (strlen(body_old) > 0)) { * their capabilities:
gchar *body_new = g_strconcat(body_old, "\n", body, NULL); * 1. if the server doesn't support appending (eg: Notification Daemon) then we update the
g_free(body); * previous notification (if exists) with new text; otherwise it takes we have many
body = body_new; * notifications from the same person... we don't concatinate the old messages because
* servers which don't support append usually don't support multi line bodies
* 2. the notify-osd server supports appending; however it doesn't clear the old notifications
* on demand, which means in our case that chat messages which have already been read could
* still be displayed when a new notification is appended, thus in this case, we update
* the old notification body manually to only contain the unread messages
* 3. the 3rd case is that the server supports append but is not notify-osd, then we simply use
* the append feature
*/
if (notification_old && !server_info.append) {
/* case 1 */
notify_notification_update(notification_old, title, body, nullptr);
notification_new = notification_old;
} else if (notification_old && g_strcmp0(server_info.name, SERVER_NOTIFY_OSD) == 0) {
/* case 2 */
/* print up to MAX_NOTIFICATIONS unread messages */
int msg_count = 0;
auto idx_next = idx.sibling(idx.row() - 1, idx.column());
auto read = idx_next.data(static_cast<int>(Media::TextRecording::Role::IsRead)).toBool();
while (idx_next.isValid() && !read && msg_count < MAX_NOTIFICATIONS) {
auto body_prev = body;
body = g_markup_printf_escaped("%s\n%s", body_prev, idx_next.data(Qt::DisplayRole).toString().toUtf8().constData());
g_free(body_prev);
idx_next = idx_next.sibling(idx_next.row() - 1, idx_next.column());
read = idx_next.data(static_cast<int>(Media::TextRecording::Role::IsRead)).toBool();
++msg_count;
} }
notify_notification_update(notification, title, body, NULL);
notify_notification_update(notification_old, title, body, nullptr);
notification_new = notification_old;
} else { } else {
/* create new notification object and associate it with the CM in the /* need new notification for case 1, 2, or 3 */
* hash table; also store the pointer of the CM in the notification notification_new = notify_notification_new(title, body, nullptr);
* object so that it knows it's key in the hash table */
notification = notify_notification_new(title, body, NULL); /* track in hash table */
g_hash_table_insert(chat_table, cm, notification); auto list = (GList *)g_hash_table_lookup(chat_table, cm);
g_object_set_data(G_OBJECT(notification), "ContactMethod", cm); list = g_list_append(list, notification_new);
g_hash_table_replace(chat_table, cm, list);
/* get photo */ /* get photo */
QVariant var_p = GlobalInstances::pixmapManipulator().callPhoto( QVariant var_p = GlobalInstances::pixmapManipulator().callPhoto(
cm, QSize(50, 50), false); cm, QSize(50, 50), false);
std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>(); std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
notify_notification_set_image_from_pixbuf(notification, photo.get()); notify_notification_set_image_from_pixbuf(notification_new, photo.get());
/* normal priority for messages */ /* normal priority for messages */
notify_notification_set_urgency(notification, NOTIFY_URGENCY_NORMAL); notify_notification_set_urgency(notification_new, NOTIFY_URGENCY_NORMAL);
/* remove the key and value from the hash table once the notification is /* remove the key and value from the hash table once the notification is
* closed; note that this will also unref the notification */ * closed; note that this will also unref the notification */
g_signal_connect(notification, "closed", G_CALLBACK(notification_closed), cm); g_signal_connect(notification_new, "closed", G_CALLBACK(notification_closed), cm);
} }
g_free(title); GError *error = nullptr;
g_free(body); success = notify_notification_show(notification_new, &error);
GError *error = NULL;
success = notify_notification_show(notification, &error);
if (!success) { if (!success) {
g_warning("failed to show notification: %s", error->message); g_warning("failed to show notification: %s", error->message);
g_clear_error(&error); g_clear_error(&error);
g_hash_table_remove(chat_table, cm);
} }
g_free(title);
g_free(body);
return success; return success;
} }
...@@ -294,24 +408,23 @@ ring_notify_close_chat_notification( ...@@ -294,24 +408,23 @@ ring_notify_close_chat_notification(
g_return_val_if_fail(cm, FALSE); g_return_val_if_fail(cm, FALSE);
GHashTable *chat_table = ring_notify_get_chat_table(); auto chat_table = ring_notify_get_chat_table();
NotifyNotification *notification = (NotifyNotification *)g_hash_table_lookup(chat_table, cm);
if (notification) { if (auto list = (GList *)g_hash_table_lookup(chat_table, cm)) {
while (list) {
notification_existed = TRUE; notification_existed = TRUE;
auto notification = (NotifyNotification *)list->data;
GError *error = NULL; GError *error = NULL;
if (!notify_notification_close(notification, &error)) { if (!notify_notification_close(notification, &error)) {
g_warning("could not close notification: %s", error->message); g_warning("could not close notification: %s", error->message);
g_clear_error(&error); g_clear_error(&error);
}
/* closing should remove and free the notification from the hash table list = g_list_next(list);
* since it failed to close, try to remove the notification from the
* table manually */
g_hash_table_remove(chat_table, cm);
} }
} }
#endif #endif
return notification_existed; return notification_existed;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment