summaryrefslogtreecommitdiff
path: root/src/daemon/daemon.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon/daemon.c')
-rw-r--r--src/daemon/daemon.c422
1 files changed, 381 insertions, 41 deletions
diff --git a/src/daemon/daemon.c b/src/daemon/daemon.c
index ef83301..062e9a5 100644
--- a/src/daemon/daemon.c
+++ b/src/daemon/daemon.c
@@ -93,9 +93,19 @@ typedef struct {
GtkWindow *nw;
guint has_timeout : 1;
guint paused : 1;
+ guint resident : 1;
#ifdef HAVE_X11
Window src_window_xid;
#endif /* HAVE_X11 */
+
+ /* Data for notification history */
+ gchar *app_name;
+ gchar *app_icon;
+ gchar *summary;
+ gchar *body;
+ guint urgency;
+ gint64 timestamp;
+ NotifydClosedReason close_reason;
} NotifyTimeout;
typedef struct {
@@ -124,6 +134,11 @@ struct _NotifyDaemon {
NotifyStackLocation stack_location;
NotifyScreen* screen;
+
+ /* Notification history */
+ GQueue *notification_history;
+ guint history_max_items;
+ gboolean history_enabled;
};
typedef struct {
@@ -138,6 +153,15 @@ static void _emit_closed_signal(GtkWindow* nw, NotifydClosedReason reason);
static void _action_invoked_cb(GtkWindow* nw, const char* key);
static NotifyStackLocation get_stack_location_from_string(const gchar *slocation);
+static void _init_notification_history(NotifyDaemon* daemon);
+static void _cleanup_notification_history(NotifyDaemon* daemon);
+static void _add_notification_to_history(NotifyDaemon* daemon, guint id,
+ const gchar *app_name, const gchar *app_icon,
+ const gchar *summary, const gchar *body, guint urgency,
+ NotifydClosedReason reason);
+static void _history_item_free(NotificationHistoryItem* item);
+static void _trim_notification_history(NotifyDaemon* daemon);
+
#ifdef HAVE_X11
static GdkFilterReturn _notify_x11_filter(GdkXEvent* xevent, GdkEvent* event, NotifyDaemon* daemon);
static void sync_notification_position(NotifyDaemon* daemon, GtkWindow* nw, Window source);
@@ -148,6 +172,10 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
static gboolean notify_daemon_close_notification_handler(NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, guint arg_id, gpointer user_data);
static gboolean notify_daemon_get_capabilities( NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation);
static gboolean notify_daemon_get_server_information (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data);
+static gboolean notify_daemon_get_notification_history (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data);
+static gboolean notify_daemon_clear_notification_history (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data);
+static gboolean notify_daemon_get_notification_count (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data);
+static gboolean notify_daemon_mark_all_notifications_as_read (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data);
static GParamSpec *properties[LAST_PROP] = { NULL };
@@ -168,6 +196,10 @@ static void bus_acquired_handler_cb (GDBusConnection *connection,
g_signal_connect (daemon->skeleton, "handle-close-notification", G_CALLBACK (notify_daemon_close_notification_handler), daemon);
g_signal_connect (daemon->skeleton, "handle-get-capabilities", G_CALLBACK (notify_daemon_get_capabilities), daemon);
g_signal_connect (daemon->skeleton, "handle-get-server-information", G_CALLBACK (notify_daemon_get_server_information), daemon);
+ g_signal_connect (daemon->skeleton, "handle-get-notification-history", G_CALLBACK (notify_daemon_get_notification_history), daemon);
+ g_signal_connect (daemon->skeleton, "handle-clear-notification-history", G_CALLBACK (notify_daemon_clear_notification_history), daemon);
+ g_signal_connect (daemon->skeleton, "handle-get-notification-count", G_CALLBACK (notify_daemon_get_notification_count), daemon);
+ g_signal_connect (daemon->skeleton, "handle-mark-all-notifications-as-read", G_CALLBACK (notify_daemon_mark_all_notifications_as_read), daemon);
exported = g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (daemon->skeleton),
connection, NOTIFICATION_BUS_PATH, &error);
@@ -261,6 +293,12 @@ static void _notify_timeout_destroy(NotifyTimeout* nt)
*/
g_signal_handlers_disconnect_by_func(nt->nw, _notification_destroyed_cb, nt->daemon);
gtk_widget_destroy(GTK_WIDGET(nt->nw));
+
+ g_free(nt->app_name);
+ g_free(nt->app_icon);
+ g_free(nt->summary);
+ g_free(nt->body);
+
g_free(nt);
}
@@ -490,6 +528,20 @@ static void on_popup_location_changed(GSettings *settings, gchar *key, NotifyDae
}
}
+static void on_history_enabled_changed(GSettings *settings, gchar *key, NotifyDaemon* daemon)
+{
+ gboolean history_enabled;
+
+ history_enabled = g_settings_get_boolean(daemon->gsettings, key);
+ daemon->history_enabled = history_enabled;
+
+ if (!history_enabled && daemon->notification_history) {
+ /* Wipe the history when disabled to preserve privacy */
+ g_queue_free_full(daemon->notification_history, (GDestroyNotify)_history_item_free);
+ daemon->notification_history = g_queue_new();
+ }
+}
+
static void notify_daemon_init(NotifyDaemon* daemon)
{
gchar *location;
@@ -503,6 +555,7 @@ static void notify_daemon_init(NotifyDaemon* daemon)
daemon->gsettings = g_settings_new (GSETTINGS_SCHEMA);
g_signal_connect (daemon->gsettings, "changed::" GSETTINGS_KEY_POPUP_LOCATION, G_CALLBACK (on_popup_location_changed), daemon);
+ g_signal_connect (daemon->gsettings, "changed::" GSETTINGS_KEY_HISTORY_ENABLED, G_CALLBACK (on_history_enabled_changed), daemon);
location = g_settings_get_string (daemon->gsettings, GSETTINGS_KEY_POPUP_LOCATION);
daemon->stack_location = get_stack_location_from_string(location);
@@ -510,6 +563,8 @@ static void notify_daemon_init(NotifyDaemon* daemon)
daemon->screen = NULL;
+ _init_notification_history(daemon);
+
create_screen(daemon);
daemon->idle_reposition_notify_ids = g_hash_table_new(NULL, NULL);
@@ -582,6 +637,8 @@ static void notify_daemon_finalize(GObject* object)
destroy_screen(daemon);
+ _cleanup_notification_history(daemon);
+
if (daemon->bus_name_id > 0)
{
g_bus_unown_name (daemon->bus_name_id);
@@ -647,6 +704,12 @@ static void _close_notification(NotifyDaemon* daemon, guint id, gboolean hide_no
if (nt != NULL)
{
+ /* Add notification to history right before closing. This way we can access it later (e.g. from the applet) */
+ _add_notification_to_history(daemon, nt->id,
+ nt->app_name, nt->app_icon,
+ nt->summary, nt->body, nt->urgency,
+ reason);
+
_emit_closed_signal(nt->nw, reason);
if (hide_notification)
@@ -687,6 +750,13 @@ typedef struct {
gint id;
} IdleRepositionData;
+static void idle_reposition_data_destroy(gpointer user_data)
+{
+ IdleRepositionData* data = (IdleRepositionData*) user_data;
+ g_object_unref(data->daemon);
+ g_free(data);
+}
+
static gboolean idle_reposition_notification(IdleRepositionData* data)
{
NotifyDaemon* daemon;
@@ -705,8 +775,6 @@ static gboolean idle_reposition_notification(IdleRepositionData* data)
}
g_hash_table_remove(daemon->idle_reposition_notify_ids, GINT_TO_POINTER(notify_id));
- g_object_unref(daemon);
- g_free(data);
return FALSE;
}
@@ -729,7 +797,7 @@ static void _queue_idle_reposition_notification(NotifyDaemon* daemon, gint notif
data->id = notify_id;
/* We do this as a short timeout to avoid repositioning spam */
- idle_id = g_timeout_add_full(G_PRIORITY_LOW, 50, (GSourceFunc) idle_reposition_notification, data, NULL);
+ idle_id = g_timeout_add_full(G_PRIORITY_LOW, 50, (GSourceFunc) idle_reposition_notification, data, idle_reposition_data_destroy);
g_hash_table_insert(daemon->idle_reposition_notify_ids, GINT_TO_POINTER(notify_id), GUINT_TO_POINTER(idle_id));
}
@@ -833,6 +901,13 @@ static gboolean _is_expired(gpointer key, NotifyTimeout* nt, gboolean* phas_more
if (time_span <= 0)
{
theme_notification_tick(nt->nw, 0);
+
+ /* Add to history before removing (same as in _close_notification, since that function won't be called from here) */
+ _add_notification_to_history(nt->daemon, nt->id,
+ nt->app_name, nt->app_icon,
+ nt->summary, nt->body, nt->urgency,
+ NOTIFYD_CLOSED_EXPIRED);
+
_emit_closed_signal(nt->nw, NOTIFYD_CLOSED_EXPIRED);
return TRUE;
}
@@ -869,9 +944,22 @@ static gboolean _check_expiration(NotifyDaemon* daemon)
return has_more_timeouts;
}
-static void _calculate_timeout(NotifyDaemon* daemon, NotifyTimeout* nt, int timeout)
+static void _calculate_timeout(NotifyDaemon* daemon, NotifyTimeout* nt, int timeout, gboolean resident)
{
- if (timeout == 0)
+ GSettings *gsettings = g_settings_new ("org.mate.NotificationDaemon");
+ gboolean persistence_enabled = g_settings_get_boolean (gsettings, "enable-persistence");
+ g_object_unref (gsettings);
+
+ nt->resident = resident && persistence_enabled;
+
+ if (timeout == -1)
+ {
+ GSettings *gsettings = g_settings_new ("org.mate.NotificationDaemon");
+ timeout = g_settings_get_int (gsettings, "default-timeout");
+ g_object_unref (gsettings);
+ }
+
+ if (timeout == 0 || nt->resident)
{
nt->has_timeout = FALSE;
}
@@ -881,11 +969,6 @@ static void _calculate_timeout(NotifyDaemon* daemon, NotifyTimeout* nt, int time
nt->has_timeout = TRUE;
- if (timeout == -1)
- {
- timeout = NOTIFY_DAEMON_DEFAULT_TIMEOUT;
- }
-
theme_set_notification_timeout(nt->nw, timeout);
usec = (gint64) timeout * G_TIME_SPAN_MILLISECOND; /* convert from msec to usec */
@@ -925,7 +1008,10 @@ static guint _generate_id(NotifyDaemon* daemon)
return id;
}
-static NotifyTimeout* _store_notification(NotifyDaemon* daemon, GtkWindow* nw, int timeout)
+static NotifyTimeout* _store_notification(NotifyDaemon* daemon, GtkWindow* nw,
+ int timeout, gboolean resident,
+ const gchar *app_name, const gchar *app_icon,
+ const gchar *summary, const gchar *body, guint urgency)
{
NotifyTimeout* nt;
guint id = _generate_id(daemon);
@@ -935,7 +1021,16 @@ static NotifyTimeout* _store_notification(NotifyDaemon* daemon, GtkWindow* nw, i
nt->nw = nw;
nt->daemon = daemon;
- _calculate_timeout(daemon, nt, timeout);
+ /* Store notification data for history */
+ nt->app_name = g_strdup(app_name ? app_name : "");
+ nt->app_icon = g_strdup(app_icon ? app_icon : "");
+ nt->summary = g_strdup(summary ? summary : "");
+ nt->body = g_strdup(body ? body : "");
+ nt->urgency = urgency;
+ nt->timestamp = g_get_real_time();
+ nt->close_reason = NOTIFYD_CLOSED_EXPIRED; /* Default to expired, since we don't care about active notifications */
+
+ _calculate_timeout(daemon, nt, timeout, resident);
#if GLIB_CHECK_VERSION (2, 68, 0)
g_hash_table_insert(daemon->notification_hash, g_memdup2(&id, sizeof(guint)), nt);
@@ -1183,22 +1278,29 @@ static gboolean screensaver_active(GtkWidget* nw)
#ifdef HAVE_X11
static gboolean fullscreen_window_exists(GtkWidget* nw)
{
+ WnckHandle* wnck_handle;
WnckScreen* wnck_screen;
WnckWorkspace* wnck_workspace;
GList* l;
+ gboolean retval = FALSE;
g_return_val_if_fail (GDK_IS_X11_DISPLAY (gdk_display_get_default ()), FALSE);
- wnck_screen = wnck_screen_get (GDK_SCREEN_XNUMBER (gdk_window_get_screen (gtk_widget_get_window (nw))));
+ wnck_handle = wnck_handle_new (WNCK_CLIENT_TYPE_APPLICATION);
+
+ g_return_val_if_fail (WNCK_IS_HANDLE (wnck_handle), FALSE);
+
+ wnck_screen = wnck_handle_get_screen (wnck_handle, GDK_SCREEN_XNUMBER (gdk_window_get_screen (gtk_widget_get_window (nw))));
+
+ if (!wnck_screen)
+ goto cleanup;
wnck_screen_force_update (wnck_screen);
wnck_workspace = wnck_screen_get_active_workspace (wnck_screen);
if (!wnck_workspace)
- {
- return FALSE;
- }
+ goto cleanup;
for (l = wnck_screen_get_windows_stacked (wnck_screen); l != NULL; l = l->next)
{
@@ -1219,12 +1321,15 @@ static gboolean fullscreen_window_exists(GtkWidget* nw)
if (sw == w && sh == h)
{
- return TRUE;
+ retval = TRUE;
+ goto cleanup;
}
}
}
- return FALSE;
+cleanup:
+ g_clear_object (&wnck_handle);
+ return retval;
}
static Window get_window_parent(GdkDisplay* display, Window window, Window* root)
@@ -1363,6 +1468,12 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
GdkPixbuf* pixbuf;
GSettings* gsettings;
gboolean fullscreen_window;
+ gboolean resident = FALSE;
+ gboolean transient = FALSE;
+ const gchar *desktop_entry = NULL;
+ const gchar *category = NULL;
+ gchar *resolved_icon = NULL; /* Store resolved icon name in history */
+ guint urgency = URGENCY_NORMAL; /* Default to normal urgency */
#ifdef HAVE_X11
Window window_xid = None;
@@ -1371,7 +1482,7 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
if (g_hash_table_size (daemon->notification_hash) > MAX_NOTIFICATIONS)
{
g_dbus_method_invocation_return_error (invocation, notify_daemon_error_quark(), 1, _("Exceeded maximum number of notifications"));
- return FALSE;
+ return TRUE;
}
/* Grab the settings */
@@ -1380,14 +1491,6 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
do_not_disturb = g_settings_get_boolean (gsettings, GSETTINGS_KEY_DO_NOT_DISTURB);
g_object_unref (gsettings);
- /* If we are in do-not-disturb mode, just grab a new id and close the notification */
- if (do_not_disturb)
- {
- return_id = _generate_id (daemon);
- notify_daemon_notifications_complete_notify (object, invocation, return_id);
- return TRUE;
- }
-
if (id > 0)
{
nt = (NotifyTimeout *) g_hash_table_lookup (daemon->notification_hash, &id);
@@ -1472,6 +1575,27 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
}
}
+ /* process resident and transient hints for persistence */
+ g_variant_lookup(hints, "resident", "b", &resident);
+ g_variant_lookup(hints, "transient", "b", &transient);
+
+ g_variant_lookup(hints, "desktop-entry", "s", &desktop_entry);
+ // TODO: Use 'category' for future category-based notification settings
+ g_variant_lookup(hints, "category", "s", &category);
+
+ /* Get urgency from hints */
+ if (g_variant_lookup(hints, "urgency", "@*", &data))
+ {
+ if (g_variant_is_of_type (data, G_VARIANT_TYPE_BYTE))
+ {
+ urgency = g_variant_get_byte(data);
+ /* Make sure it's in a valid range */
+ if (urgency > URGENCY_CRITICAL)
+ urgency = URGENCY_CRITICAL;
+ }
+ g_variant_unref(data);
+ }
+
/* set up action buttons */
for (i = 0; actions[i] != NULL; i += 2)
{
@@ -1490,31 +1614,76 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
pixbuf = NULL;
- if (g_variant_lookup(hints, "image_data", "@(iiibiiay)", &data))
+ /* According to the Desktop Notifications Specification 1.3 spec, icons have the following precedence:
+ * 1. image-data (or image_data)
+ * 2. image-path (or image_path)
+ * 3. icon (which falls back to the desktop entry)
+ * 4. icon_data (for backward compatibility)
+ */
+ if (g_variant_lookup(hints, "image-data", "@(iiibiiay)", &data))
{
pixbuf = _notify_daemon_pixbuf_from_data_hint (data);
g_variant_unref(data);
}
- else if (g_variant_lookup(hints, "image-data", "@(iiibiiay)", &data))
+ else if (g_variant_lookup(hints, "image_data", "@(iiibiiay)", &data))
{
pixbuf = _notify_daemon_pixbuf_from_data_hint (data);
g_variant_unref(data);
}
- else if (g_variant_lookup(hints, "image_path", "@s", &data))
+ else if (g_variant_lookup(hints, "image-path", "@s", &data))
{
const char *path = g_variant_get_string (data, NULL);
pixbuf = _notify_daemon_pixbuf_from_path (path);
+ resolved_icon = g_strdup(path);
g_variant_unref(data);
}
- else if (g_variant_lookup(hints, "image-path", "@s", &data))
+ else if (g_variant_lookup(hints, "image_path", "@s", &data))
{
const char *path = g_variant_get_string (data, NULL);
pixbuf = _notify_daemon_pixbuf_from_path (path);
+ resolved_icon = g_strdup(path);
g_variant_unref(data);
}
else if (*icon != '\0')
{
pixbuf = _notify_daemon_pixbuf_from_path (icon);
+ resolved_icon = g_strdup(icon);
+ }
+ else if (desktop_entry != NULL && *desktop_entry != '\0')
+ {
+ /* Use desktop-entry to resolve application icon as fallback */
+ gchar *desktop_file = g_strdup_printf("%s.desktop", desktop_entry);
+ gchar *icon_name = NULL;
+
+ /* Try to get icon from desktop file */
+ GKeyFile *key_file = g_key_file_new();
+ const gchar * const *search_dirs = g_get_system_data_dirs();
+
+ for (const gchar * const *dir = search_dirs; *dir != NULL; dir++)
+ {
+ gchar *desktop_path = g_build_filename(*dir, "applications", desktop_file, NULL);
+ if (g_key_file_load_from_file(key_file, desktop_path, G_KEY_FILE_NONE, NULL))
+ {
+ icon_name = g_key_file_get_string(key_file, "Desktop Entry", "Icon", NULL);
+ g_free(desktop_path);
+ break;
+ }
+ g_free(desktop_path);
+ }
+
+ if (icon_name != NULL)
+ {
+ pixbuf = _notify_daemon_pixbuf_from_path (icon_name);
+
+ if (!resolved_icon && (*icon == '\0' || icon == NULL)) {
+ resolved_icon = g_strdup(icon_name);
+ }
+
+ g_free(icon_name);
+ }
+
+ g_key_file_free(key_file);
+ g_free(desktop_file);
}
else if (g_variant_lookup(hints, "icon_data", "@(iiibiiay)", &data))
{
@@ -1592,19 +1761,32 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
g_settings_get_int(daemon->gsettings, GSETTINGS_KEY_MONITOR_NUMBER));
}
- if (_gtk_get_monitor_num (monitor_id) >= daemon->screen->n_stacks)
+ /* If the monitor was disconnected or invalid, fall back to the last available monitor */
+ if (monitor_id == NULL && daemon->screen->n_stacks > 0)
+ {
+ monitor_id = gdk_display_get_monitor (gdk_display_get_default(), (int) daemon->screen->n_stacks - 1);
+ }
+
+ if (monitor_id != NULL && _gtk_get_monitor_num (monitor_id) >= daemon->screen->n_stacks)
{
/* screw it - dump it on the last one we'll get
a monitors-changed signal soon enough*/
monitor_id = gdk_display_get_monitor (gdk_display_get_default(), (int) daemon->screen->n_stacks - 1);
}
- notify_stack_add_window (daemon->screen->stacks[_gtk_get_monitor_num (monitor_id)], nw, new_notification);
+ /* If we still don't have a valid monitor, something is seriously wrong */
+ if (monitor_id != NULL && daemon->screen->n_stacks > 0)
+ {
+ notify_stack_add_window (daemon->screen->stacks[_gtk_get_monitor_num (monitor_id)], nw, new_notification);
+ }
}
if (id == 0)
{
- nt = _store_notification (daemon, nw, timeout);
+ nt = _store_notification (daemon, nw,
+ timeout, resident && !transient,
+ app_name, resolved_icon ? resolved_icon : icon,
+ summary, body, urgency);
return_id = nt->id;
}
else
@@ -1639,11 +1821,14 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
*/
if (!nt->has_timeout || (!screensaver_active (GTK_WIDGET (nw)) && !fullscreen_window))
{
- theme_show_notification (nw);
-
- if (sound_file != NULL)
+ if (!do_not_disturb)
{
- sound_play_file (GTK_WIDGET (nw), sound_file);
+ theme_show_notification (nw);
+
+ if (sound_file != NULL)
+ {
+ sound_play_file (GTK_WIDGET (nw), sound_file);
+ }
}
}
else
@@ -1664,7 +1849,11 @@ static gboolean notify_daemon_notify_handler(NotifyDaemonNotifications *object,
if (nt)
{
- _calculate_timeout (daemon, nt, timeout);
+ _calculate_timeout (daemon, nt, timeout, resident && !transient);
+ }
+
+ if (resolved_icon) {
+ g_free(resolved_icon);
}
notify_daemon_notifications_complete_notify ( object, invocation, return_id);
@@ -1676,7 +1865,7 @@ static gboolean notify_daemon_close_notification_handler(NotifyDaemonNotificatio
if (id == 0)
{
g_dbus_method_invocation_return_error (invocation, notify_daemon_error_quark(), 100, _("%u is not a valid notification ID"), id);
- return FALSE;
+ return TRUE;
}
else
{
@@ -1701,6 +1890,7 @@ static gboolean notify_daemon_get_capabilities( NotifyDaemonNotifications *objec
g_variant_builder_add (builder, "s", "body-markup");
g_variant_builder_add (builder, "s", "icon-static");
g_variant_builder_add (builder, "s", "sound");
+ g_variant_builder_add (builder, "s", "persistence");
value = g_variant_new ("as", builder);
g_variant_builder_unref (builder);
notify_daemon_notifications_complete_get_capabilities (
@@ -1718,10 +1908,160 @@ static gboolean notify_daemon_get_server_information (NotifyDaemonNotifications
"Notification Daemon",
"MATE",
PACKAGE_VERSION,
- "1.1");
+ "1.3");
return TRUE;
}
+static gboolean notify_daemon_get_notification_history (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data)
+{
+ NotifyDaemon *daemon = NOTIFY_DAEMON (user_data);
+ GVariantBuilder *builder;
+ GList *items;
+
+ if (!daemon->notification_history || !daemon->history_enabled) {
+ /* Return empty array if history is disabled or not initialized */
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ussssxxuub)"));
+ notify_daemon_notifications_complete_get_notification_history (object, invocation, g_variant_new ("a(ussssxxuub)", builder));
+ g_variant_builder_unref (builder);
+ return TRUE;
+ }
+
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ussssxxuub)"));
+
+ for (items = g_queue_peek_head_link(daemon->notification_history); items != NULL; items = items->next) {
+ NotificationHistoryItem *item = (NotificationHistoryItem *) items->data;
+
+ /* Mark notification as read when reading through history */
+ item->read = TRUE;
+
+ g_variant_builder_add (builder, "(ussssxxuub)",
+ item->id,
+ item->app_name,
+ item->app_icon,
+ item->summary,
+ item->body,
+ item->timestamp,
+ item->closed_timestamp,
+ (guint) item->reason,
+ item->urgency,
+ item->read);
+ }
+
+ notify_daemon_notifications_complete_get_notification_history (object, invocation, g_variant_new ("a(ussssxxuub)", builder));
+ g_variant_builder_unref (builder);
+ return TRUE;
+}
+
+static gboolean notify_daemon_clear_notification_history (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data)
+{
+ NotifyDaemon *daemon = NOTIFY_DAEMON (user_data);
+
+ if (daemon->notification_history) {
+ g_queue_free_full(daemon->notification_history, (GDestroyNotify)_history_item_free);
+ daemon->notification_history = g_queue_new();
+ }
+
+ notify_daemon_notifications_complete_clear_notification_history (object, invocation);
+ return TRUE;
+}
+
+static gboolean notify_daemon_mark_all_notifications_as_read (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data)
+{
+ NotifyDaemon *daemon = NOTIFY_DAEMON (user_data);
+ GList *items;
+
+ if (daemon->notification_history && daemon->history_enabled) {
+ for (items = g_queue_peek_head_link(daemon->notification_history); items != NULL; items = items->next) {
+ NotificationHistoryItem *item = (NotificationHistoryItem *) items->data;
+ item->read = TRUE;
+ }
+ }
+
+ notify_daemon_notifications_complete_mark_all_notifications_as_read (object, invocation);
+ return TRUE;
+}
+
+static gboolean notify_daemon_get_notification_count (NotifyDaemonNotifications *object, GDBusMethodInvocation *invocation, gpointer user_data)
+{
+ NotifyDaemon *daemon = NOTIFY_DAEMON (user_data);
+ guint count = 0;
+ GList *items;
+
+ if (daemon->notification_history && daemon->history_enabled) {
+ /* Count unread notifications */
+ for (items = g_queue_peek_head_link(daemon->notification_history); items != NULL; items = items->next) {
+ NotificationHistoryItem *item = (NotificationHistoryItem *) items->data;
+ if (!item->read) {
+ count++;
+ }
+ }
+ }
+
+ notify_daemon_notifications_complete_get_notification_count (object, invocation, count);
+ return TRUE;
+}
+
+static void _history_item_free(NotificationHistoryItem* item)
+{
+ if (item) {
+ g_free(item->app_name);
+ g_free(item->app_icon);
+ g_free(item->summary);
+ g_free(item->body);
+ g_free(item);
+ }
+}
+
+static void _init_notification_history(NotifyDaemon* daemon)
+{
+ daemon->notification_history = g_queue_new();
+ daemon->history_max_items = 100; /* Default max items */
+ daemon->history_enabled = g_settings_get_boolean(daemon->gsettings, GSETTINGS_KEY_HISTORY_ENABLED);
+}
+
+static void _cleanup_notification_history(NotifyDaemon* daemon)
+{
+ if (daemon->notification_history) {
+ g_queue_free_full(daemon->notification_history, (GDestroyNotify)_history_item_free);
+ daemon->notification_history = NULL;
+ }
+}
+
+static void _trim_notification_history(NotifyDaemon* daemon)
+{
+ if (!daemon->notification_history)
+ return;
+
+ while (g_queue_get_length(daemon->notification_history) > daemon->history_max_items) {
+ NotificationHistoryItem* item = g_queue_pop_tail(daemon->notification_history);
+ _history_item_free(item);
+ }
+}
+
+static void _add_notification_to_history(NotifyDaemon* daemon, guint id,
+ const gchar *app_name, const gchar *app_icon,
+ const gchar *summary, const gchar *body, guint urgency,
+ NotifydClosedReason reason)
+{
+ if (!daemon->history_enabled || !daemon->notification_history)
+ return;
+
+ NotificationHistoryItem* item = g_new0(NotificationHistoryItem, 1);
+ item->id = id;
+ item->app_name = g_strdup(app_name ? app_name : "");
+ item->app_icon = g_strdup(app_icon ? app_icon : "");
+ item->summary = g_strdup(summary ? summary : "");
+ item->body = g_strdup(body ? body : "");
+ item->timestamp = g_get_real_time();
+ item->closed_timestamp = g_get_real_time();
+ item->reason = reason;
+ item->urgency = urgency;
+ item->read = FALSE; /* Mark as unread initially */
+
+ g_queue_push_head(daemon->notification_history, item);
+ _trim_notification_history(daemon);
+}
+
NotifyDaemon* notify_daemon_new (gboolean replace, gboolean idle_exit)
{
return g_object_new (NOTIFY_TYPE_DAEMON,