aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt41
-rw-r--r--src/Makefile.am36
-rw-r--r--src/indicator-notifications-settings.c438
-rw-r--r--src/indicator-notifications.c897
-rw-r--r--src/main.c52
-rw-r--r--src/notification-menuitem.c407
-rw-r--r--src/notification-menuitem.h52
-rw-r--r--src/service.c867
-rw-r--r--src/service.h65
-rw-r--r--src/settings.h20
10 files changed, 1025 insertions, 1850 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..eaebc58
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,41 @@
+set (SERVICE_LIB "ayatanaindicatornotificationsservice")
+set (SERVICE_EXEC "ayatana-indicator-notifications-service")
+
+add_definitions(-DG_LOG_DOMAIN="ayatana-indicator-notifications")
+
+if(URLDISPATCHER_FOUND)
+ add_definitions( -DHAS_URLDISPATCHER )
+endif()
+
+# handwritten sources
+set(SERVICE_MANUAL_SOURCES
+ urlregex.c
+ notification.c
+ dbus-spy.c
+ service.c)
+
+# generated sources
+include(GdbusCodegen)
+set(SERVICE_GENERATED_SOURCES)
+
+# add the bin dir to our include path so the code can find the generated header files
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+
+# add warnings/coverage info on handwritten files
+# but not the autogenerated ones...
+set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-bad-function-cast") # g_clear_object()
+set(C_WARNING_ARGS "${C_WARNING_ARGS} -Wno-switch-enum")
+set_source_files_properties(${SERVICE_MANUAL_SOURCES}
+ PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} -g -std=c99")
+
+# the service library for tests to link against (basically, everything except main())
+add_library(${SERVICE_LIB} STATIC ${SERVICE_MANUAL_SOURCES} ${SERVICE_GENERATED_SOURCES})
+include_directories(${CMAKE_SOURCE_DIR})
+link_directories(${SERVICE_DEPS_LIBRARY_DIRS})
+
+# the executable: lib + main()
+add_executable (${SERVICE_EXEC} main.c)
+set_source_files_properties(${SERVICE_SOURCES} main.c PROPERTIES COMPILE_FLAGS "${C_WARNING_ARGS} -std=c99")
+target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${URLDISPATCHER_LIBRARIES})
+install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_PKGLIBEXECDIR})
diff --git a/src/Makefile.am b/src/Makefile.am
deleted file mode 100644
index e89ade2..0000000
--- a/src/Makefile.am
+++ /dev/null
@@ -1,36 +0,0 @@
-ayatananotificationslibdir = $(INDICATORDIR)
-ayatananotificationslib_LTLIBRARIES = libayatana-notifications.la
-libayatana_notifications_la_SOURCES = \
- dbus-spy.c \
- dbus-spy.h \
- urlregex.c \
- urlregex.h \
- notification-menuitem.c \
- notification-menuitem.h \
- settings.h \
- indicator-notifications.c \
- notification.c \
- notification.h
-libayatana_notifications_la_CFLAGS = \
- -DSETTINGS_PATH=\""$(libexecdir)/$(PACKAGE)/indicator-notifications-settings"\" \
- $(INDICATOR_CFLAGS) \
- -Wall \
- -DG_LOG_DOMAIN=\"ayatana-indicator-notifications\"
-libayatana_notifications_la_LIBADD = \
- $(INDICATOR_LIBS)
-libayatana_notifications_la_LDFLAGS = \
- -module \
- -avoid-version
-
-pkglibexec_PROGRAMS = indicator-notifications-settings
-
-indicator_notifications_settings_SOURCES = \
- settings.h \
- indicator-notifications-settings.c
-
-indicator_notifications_settings_CFLAGS = \
- $(SETTINGS_CFLAGS) \
- -Wall
-
-indicator_notifications_settings_LDADD = \
- $(SETTINGS_LIBS)
diff --git a/src/indicator-notifications-settings.c b/src/indicator-notifications-settings.c
deleted file mode 100644
index 510a0ec..0000000
--- a/src/indicator-notifications-settings.c
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * indicator-notifications-settings.c - UI for indicator settings
- */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <glib/gi18n.h>
-#include <gtk/gtk.h>
-
-#include "settings.h"
-
-#define SCHEMA_KEY "schema-key"
-
-#define COLUMN_APPNAME 0
-
-typedef struct
-{
- GtkApplication parent_instance;
-
- GSettings *settings;
-
- GtkWidget *filter_list_treeview;
- GtkWidget *filter_list_entry;
-
- /* GtkTreeModel foreach variables */
- gboolean result;
- gchar *text;
- GPtrArray *array;
-} IndicatorNotificationsSettings;
-
-typedef GtkApplicationClass IndicatorNotificationsSettingsClass;
-
-G_DEFINE_TYPE(IndicatorNotificationsSettings, indicator_notifications_settings, GTK_TYPE_APPLICATION)
-
-/* Class Functions */
-static void indicator_notifications_settings_class_init(IndicatorNotificationsSettingsClass *klass);
-static void indicator_notifications_settings_init(IndicatorNotificationsSettings *self);
-static void indicator_notifications_settings_dispose(GObject *object);
-
-/* GtkApplication Signals */
-static void indicator_notifications_settings_activate(GApplication *app);
-
-/* Utility Functions */
-static void load_filter_list(IndicatorNotificationsSettings *self);
-static void load_filter_list_hints(IndicatorNotificationsSettings *self);
-static void save_filter_list(IndicatorNotificationsSettings *self);
-static gboolean foreach_check_duplicates(GtkTreeModel *model, GtkTreePath *path,
- GtkTreeIter *iter, gpointer user_data);
-static gboolean foreach_build_array(GtkTreeModel *model, GtkTreePath *path,
- GtkTreeIter *iter, gpointer user_data);
-
-/* Callbacks */
-static void filter_list_add_clicked_cb(GtkButton *button, gpointer user_data);
-static void filter_list_remove_clicked_cb(GtkButton *button, gpointer user_data);
-static void button_toggled_cb(GtkToggleButton *button, gpointer user_data);
-static void max_items_changed_cb(GtkSpinButton *button, gpointer user_data);
-static gboolean filter_list_entry_focus_in_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data);
-
-static void
-load_filter_list(IndicatorNotificationsSettings *self)
-{
- GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview)));
- GtkTreeIter iter;
- gchar **items;
-
- gtk_list_store_clear(list);
-
- items = g_settings_get_strv(self->settings, NOTIFICATIONS_KEY_FILTER_LIST);
-
- for (int i = 0; items[i] != NULL; i++) {
- gtk_list_store_append(list, &iter);
- gtk_list_store_set(list, &iter, COLUMN_APPNAME, items[i], -1);
- }
-
- g_strfreev(items);
-}
-
-static void
-load_filter_list_hints(IndicatorNotificationsSettings *self)
-{
- GtkEntryCompletion *completion = gtk_entry_get_completion(GTK_ENTRY(self->filter_list_entry));
- GtkListStore *list = GTK_LIST_STORE(gtk_entry_completion_get_model(completion));
- GtkTreeIter iter;
- gchar **items;
-
- gtk_list_store_clear(list);
-
- items = g_settings_get_strv(self->settings, NOTIFICATIONS_KEY_FILTER_LIST_HINTS);
-
- for (int i = 0; items[i] != NULL; i++) {
- gtk_list_store_append(list, &iter);
- gtk_list_store_set(list, &iter, 0, items[i], -1);
- }
-
- g_strfreev(items);
-}
-
-static void
-save_filter_list(IndicatorNotificationsSettings *self)
-{
- GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview)));
- gchar **items;
-
- /* build an array of the filter list items */
- self->array = g_ptr_array_new();
- gtk_tree_model_foreach(GTK_TREE_MODEL(list), foreach_build_array, self);
- g_ptr_array_add(self->array, NULL);
- items = (gchar **) g_ptr_array_free(self->array, FALSE);
- self->array = NULL;
-
- g_settings_set_strv(self->settings, NOTIFICATIONS_KEY_FILTER_LIST, (const gchar **) items);
-
- g_strfreev(items);
-}
-
-static gboolean
-foreach_check_duplicates(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
-{
- IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data;
- gchar *appname;
- gboolean result = FALSE;
-
- gtk_tree_model_get(model, iter, COLUMN_APPNAME, &appname, -1);
-
- if (g_strcmp0(appname, self->text) == 0) {
- result = TRUE;
- self->result = TRUE;
- }
-
- g_free(appname);
-
- return result;
-}
-
-static gboolean
-foreach_build_array(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
-{
- IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data;
- gchar *appname;
-
- gtk_tree_model_get(model, iter, COLUMN_APPNAME, &appname, -1);
-
- g_ptr_array_add(self->array, appname);
-
- return FALSE;
-}
-
-static void
-filter_list_add_clicked_cb(GtkButton *button, gpointer user_data)
-{
- IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data;
- GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview)));
- GtkTreeIter iter;
-
- /* strip off the leading and trailing whitespace in case of user error */
- self->text = g_strdup(gtk_entry_get_text(GTK_ENTRY(self->filter_list_entry)));
- g_strstrip(self->text);
-
- if (strlen(self->text) > 0) {
- /* check for duplicates first */
- self->result = FALSE;
- gtk_tree_model_foreach(GTK_TREE_MODEL(list), foreach_check_duplicates, self);
-
- if (self->result == FALSE) {
- gtk_list_store_append(list, &iter);
- gtk_list_store_set(list, &iter, COLUMN_APPNAME, self->text, -1);
- save_filter_list(self);
- }
- }
-
- /* clear the entry */
- gtk_entry_set_text(GTK_ENTRY(self->filter_list_entry), "");
-
- /* cleanup text */
- g_free(self->text);
- self->text = "";
-}
-
-static void
-filter_list_remove_clicked_cb(GtkButton *button, gpointer user_data)
-{
- IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data;
- GtkListStore *list = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(self->filter_list_treeview)));
- GtkTreeIter iter;
- GtkTreeSelection *selection;
-
- selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self->filter_list_treeview));
-
- if (gtk_tree_selection_get_selected(selection, NULL, &iter) == TRUE) {
- gtk_list_store_remove(list, &iter);
-
- save_filter_list(self);
- }
-}
-
-static void
-button_toggled_cb(GtkToggleButton *button, gpointer user_data)
-{
- GSettings *settings = G_SETTINGS(user_data);
- char *schema_key = (char *) g_object_get_data(G_OBJECT(button), SCHEMA_KEY);
-
- g_settings_set_boolean(settings, schema_key, gtk_toggle_button_get_active(button));
-}
-
-static void
-max_items_changed_cb(GtkSpinButton *button, gpointer user_data)
-{
- GSettings *settings = G_SETTINGS(user_data);
-
- int value = gtk_spin_button_get_value_as_int(button);
- g_settings_set_int(settings, NOTIFICATIONS_KEY_MAX_ITEMS, value);
-}
-
-static gboolean
-filter_list_entry_focus_in_cb(GtkWidget *widget, GdkEvent *event, gpointer user_data)
-{
- IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) user_data;
- load_filter_list_hints(self);
- g_signal_emit_by_name(widget, "changed", NULL);
- return FALSE;
-}
-
-static void
-indicator_notifications_settings_activate(GApplication *app)
-{
- GtkWidget *window;
- GtkWidget *frame;
- GtkWidget *vbox;
- GtkWidget *button_cmc;
- GtkWidget *button_hide_ind;
- GtkWidget *button_dnd;
- GtkWidget *button_swap_clr_s;
- GtkWidget *spin;
- GtkWidget *spin_label;
- GtkWidget *filter_list_label;
- GtkListStore *filter_list_list;
- GtkWidget *filter_list_scroll;
- GtkTreeViewColumn *column;
- GtkCellRenderer *renderer;
- GtkWidget *hbox;
- GtkWidget *button_filter_list_rem;
- GtkWidget *button_filter_list_add;
- GtkEntryCompletion *entry_completion;
- GtkListStore *entry_list;
-
- IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) app;
-
- /* Check for a pre-existing window */
- GtkWindow *old_window = gtk_application_get_active_window(GTK_APPLICATION(app));
- if (old_window != NULL) {
- gtk_window_present_with_time(old_window, GDK_CURRENT_TIME);
- return;
- }
-
- /* GSettings */
- self->settings = g_settings_new(NOTIFICATIONS_SCHEMA);
-
- /* Main Window */
- window = gtk_application_window_new(GTK_APPLICATION(app));
- gtk_window_set_title(GTK_WINDOW(window), _("Indicator Notifications Settings"));
- gtk_window_set_default_size(GTK_WINDOW(window), 400, 400);
- gtk_container_set_border_width(GTK_CONTAINER(window), 10);
- gtk_widget_show(window);
-
- /* Window Frame */
- frame = gtk_frame_new(_("Indicator Notifications Settings"));
- gtk_container_add(GTK_CONTAINER(window), frame);
- gtk_widget_show(frame);
-
- /* Main Vertical Box */
- vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
- gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
- gtk_container_add(GTK_CONTAINER(frame), vbox);
- gtk_widget_show(vbox);
-
- /* clear-on-middle-click */
- button_cmc = gtk_check_button_new_with_label(_("Clear notifications on middle click"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_cmc),
- g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_CLEAR_MC));
- g_object_set_data(G_OBJECT(button_cmc), SCHEMA_KEY, NOTIFICATIONS_KEY_CLEAR_MC);
- g_signal_connect(button_cmc, "toggled", G_CALLBACK(button_toggled_cb), self->settings);
- gtk_box_pack_start(GTK_BOX(vbox), button_cmc, FALSE, FALSE, 4);
- gtk_widget_show(button_cmc);
-
- /* hide-indicator */
- button_hide_ind = gtk_check_button_new_with_label(_("Hide indicator"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_hide_ind),
- g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_HIDE_INDICATOR));
- g_object_set_data(G_OBJECT(button_hide_ind), SCHEMA_KEY, NOTIFICATIONS_KEY_HIDE_INDICATOR);
- g_signal_connect(button_hide_ind, "toggled", G_CALLBACK(button_toggled_cb), self->settings);
- gtk_box_pack_start(GTK_BOX(vbox), button_hide_ind, FALSE, FALSE, 4);
- gtk_widget_show(button_hide_ind);
-
- /* do-not-disturb */
- button_dnd = gtk_check_button_new_with_label(_("Do not disturb"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_dnd),
- g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_DND));
- g_object_set_data(G_OBJECT(button_dnd), SCHEMA_KEY, NOTIFICATIONS_KEY_DND);
- g_signal_connect(button_dnd, "toggled", G_CALLBACK(button_toggled_cb), self->settings);
- gtk_box_pack_start(GTK_BOX(vbox), button_dnd, FALSE, FALSE, 4);
- gtk_widget_show(button_dnd);
-
- /* swap-clear-settings */
- button_swap_clr_s = gtk_check_button_new_with_label(_("Swap \"Clear\" and \"Settings\" items"));
- gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button_swap_clr_s),
- g_settings_get_boolean(self->settings, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS));
- g_object_set_data(G_OBJECT(button_swap_clr_s), SCHEMA_KEY, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS);
- g_signal_connect(button_swap_clr_s, "toggled", G_CALLBACK(button_toggled_cb), self->settings);
- gtk_box_pack_start(GTK_BOX(vbox), button_swap_clr_s, FALSE, FALSE, 4);
- gtk_widget_show(button_swap_clr_s);
-
- /* max-items */
- /* FIXME: indicator does not change max items until restart... */
- spin_label = gtk_label_new(_("Maximum number of visible notifications"));
- gtk_box_pack_start(GTK_BOX(vbox), spin_label, FALSE, FALSE, 4);
- gtk_widget_show(spin_label);
-
- spin = gtk_spin_button_new_with_range(1, 10, 1);
- gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), g_settings_get_int(self->settings, NOTIFICATIONS_KEY_MAX_ITEMS));
- g_signal_connect(spin, "value-changed", G_CALLBACK(max_items_changed_cb), self->settings);
- gtk_box_pack_start(GTK_BOX(vbox), spin, FALSE, FALSE, 4);
- gtk_widget_show(spin);
-
- /* filter-list */
- filter_list_label = gtk_label_new(_("Discard notifications by application name"));
- gtk_box_pack_start(GTK_BOX(vbox), filter_list_label, FALSE, FALSE, 4);
- gtk_widget_show(filter_list_label);
-
- filter_list_scroll = gtk_scrolled_window_new(NULL, NULL);
- gtk_box_pack_start(GTK_BOX(vbox), filter_list_scroll, TRUE, TRUE, 4);
- gtk_widget_show(filter_list_scroll);
-
- filter_list_list = gtk_list_store_new(1, G_TYPE_STRING);
-
- renderer = gtk_cell_renderer_text_new();
- column = gtk_tree_view_column_new_with_attributes("appname", renderer, "text", COLUMN_APPNAME, NULL);
-
- self->filter_list_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(filter_list_list));
- g_object_unref(filter_list_list);
- gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self->filter_list_treeview), FALSE);
- gtk_tree_view_append_column(GTK_TREE_VIEW(self->filter_list_treeview), column);
- load_filter_list(self);
- gtk_container_add(GTK_CONTAINER(filter_list_scroll), self->filter_list_treeview);
- gtk_widget_show(self->filter_list_treeview);
-
- hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
- gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
- gtk_widget_show(hbox);
-
- button_filter_list_rem = gtk_button_new_with_label(_("Remove"));
- g_signal_connect(button_filter_list_rem, "clicked", G_CALLBACK(filter_list_remove_clicked_cb), self);
- gtk_box_pack_start(GTK_BOX(hbox), button_filter_list_rem, FALSE, FALSE, 2);
- gtk_widget_show(button_filter_list_rem);
-
- button_filter_list_add = gtk_button_new_with_label(_("Add"));
- g_signal_connect(button_filter_list_add, "clicked", G_CALLBACK(filter_list_add_clicked_cb), self);
- gtk_box_pack_start(GTK_BOX(hbox), button_filter_list_add, FALSE, FALSE, 2);
- gtk_widget_show(button_filter_list_add);
-
- self->filter_list_entry = gtk_entry_new();
- gtk_box_pack_start(GTK_BOX(hbox), self->filter_list_entry, TRUE, TRUE, 0);
- gtk_widget_show(self->filter_list_entry);
-
- entry_completion = gtk_entry_completion_new();
- entry_list = gtk_list_store_new(1, G_TYPE_STRING);
- gtk_entry_completion_set_model(entry_completion, GTK_TREE_MODEL(entry_list));
- gtk_entry_completion_set_text_column(entry_completion, 0);
- gtk_entry_completion_set_minimum_key_length(entry_completion, 0);
- gtk_entry_set_completion(GTK_ENTRY(self->filter_list_entry), entry_completion);
- /* When we focus the entry, emit the changed signal so we get the hints immediately */
- /* also update the filter list hints from gsettings */
- g_signal_connect(self->filter_list_entry, "focus-in-event", G_CALLBACK(filter_list_entry_focus_in_cb), self);
-}
-
-static void
-indicator_notifications_settings_init (IndicatorNotificationsSettings *self)
-{
-}
-
-static void
-indicator_notifications_settings_class_init (IndicatorNotificationsSettingsClass *class)
-{
- GApplicationClass *application_class = G_APPLICATION_CLASS (class);
- GObjectClass *object_class = G_OBJECT_CLASS (class);
-
- application_class->activate = indicator_notifications_settings_activate;
-
- object_class->dispose = indicator_notifications_settings_dispose;
-}
-
-static void
-indicator_notifications_settings_dispose(GObject *object)
-{
- IndicatorNotificationsSettings *self = (IndicatorNotificationsSettings *) object;
-
- if(self->settings != NULL) {
- g_object_unref(G_OBJECT(self->settings));
- self->settings = NULL;
- }
-
- G_OBJECT_CLASS(indicator_notifications_settings_parent_class)->dispose(object);
-}
-
-IndicatorNotificationsSettings *
-indicator_notifications_settings_new (void)
-{
- IndicatorNotificationsSettings *self;
-
- g_set_application_name(_("Indicator Notifications Settings"));
-
- self = g_object_new(indicator_notifications_settings_get_type(),
- "application-id", NOTIFICATIONS_SCHEMA ".settings",
- "flags", G_APPLICATION_FLAGS_NONE,
- NULL);
-
- return self;
-}
-
-int
-main(int argc, char **argv)
-{
- IndicatorNotificationsSettings *self;
- int status;
-
- setlocale(LC_ALL, "");
- bindtextdomain( GETTEXT_PACKAGE, LOCALEDIR );
- textdomain( GETTEXT_PACKAGE );
-
- self = indicator_notifications_settings_new();
-
- status = g_application_run(G_APPLICATION(self), argc, argv);
-
- g_object_unref(self);
-
- return status;
-}
diff --git a/src/indicator-notifications.c b/src/indicator-notifications.c
deleted file mode 100644
index dffa57c..0000000
--- a/src/indicator-notifications.c
+++ /dev/null
@@ -1,897 +0,0 @@
-/*
-An indicator to display recent notifications.
-
-Adapted from: indicator-datetime/src/indicator-datetime.c by
- Ted Gould <ted@canonical.com>
-
-This program is free software: you can redistribute it and/or modify it
-under the terms of the GNU General Public License version 3, as published
-by the Free Software Foundation.
-
-This program is distributed in the hope that it will be useful, but
-WITHOUT ANY WARRANTY; without even the implied warranties of
-MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-/* GStuff */
-#include <glib.h>
-#include <glib/gi18n-lib.h>
-#include <gdk-pixbuf/gdk-pixbuf.h>
-
-/* Indicator Stuff */
-#include <libayatana-indicator/indicator.h>
-#include <libayatana-indicator/indicator-object.h>
-#include <libayatana-indicator/indicator-service-manager.h>
-
-#include "dbus-spy.h"
-#include "notification-menuitem.h"
-
-#define INDICATOR_NOTIFICATIONS_TYPE (indicator_notifications_get_type ())
-#define INDICATOR_NOTIFICATIONS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_NOTIFICATIONS_TYPE, IndicatorNotifications))
-#define INDICATOR_NOTIFICATIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_NOTIFICATIONS_TYPE, IndicatorNotificationsClass))
-#define IS_INDICATOR_NOTIFICATIONS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_NOTIFICATIONS_TYPE))
-#define IS_INDICATOR_NOTIFICATIONS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_NOTIFICATIONS_TYPE))
-#define INDICATOR_NOTIFICATIONS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_NOTIFICATIONS_TYPE, IndicatorNotificationsClass))
-
-typedef struct _IndicatorNotifications IndicatorNotifications;
-typedef struct _IndicatorNotificationsClass IndicatorNotificationsClass;
-typedef struct _IndicatorNotificationsPrivate IndicatorNotificationsPrivate;
-
-struct _IndicatorNotificationsClass {
- IndicatorObjectClass parent_class;
-};
-
-struct _IndicatorNotifications {
- IndicatorObject parent;
- IndicatorNotificationsPrivate *priv;
-};
-
-struct _IndicatorNotificationsPrivate {
- GtkImage *image;
-
- GList *visible_items;
- GList *hidden_items;
-
- gboolean clear_on_middle_click;
- gboolean do_not_disturb;
- gboolean have_unread;
- gboolean hide_indicator;
- gboolean swap_clear_settings;
-
- gint max_items;
-
- GtkMenu *menu;
- GtkWidget *clear_item;
- GtkWidget *clear_item_label;
- GtkWidget *settings_item;
- GtkWidget *pSeparator;
-
- gchar *accessible_desc;
-
- DBusSpy *spy;
-
- GHashTable *filter_list;
-
- GList *filter_list_hints;
-
- GSettings *settings;
-};
-
-#include "settings.h"
-
-#define INDICATOR_ICON_SIZE 22
-#define INDICATOR_ICON_READ "ayatana-indicator-notification-read"
-#define INDICATOR_ICON_UNREAD "ayatana-indicator-notification-unread"
-#define INDICATOR_ICON_READ_DND "ayatana-indicator-notification-read-dnd"
-#define INDICATOR_ICON_UNREAD_DND "ayatana-indicator-notification-unread-dnd"
-
-#define HINT_MAX 10
-
-GType indicator_notifications_get_type(void);
-
-/* Indicator Class Functions */
-static void indicator_notifications_class_init(IndicatorNotificationsClass *klass);
-static void indicator_notifications_init(IndicatorNotifications *self);
-static void indicator_notifications_dispose(GObject *object);
-static void indicator_notifications_finalize(GObject *object);
-
-/* Indicator Standard Methods */
-static GtkImage *get_image(IndicatorObject *io);
-static GtkMenu *get_menu(IndicatorObject *io);
-static const gchar *get_accessible_desc(IndicatorObject *io);
-static void indicator_notifications_middle_click(IndicatorObject *io,
- IndicatorObjectEntry *entry,
- guint time,
- gpointer user_data);
-
-/* Utility Functions */
-static void clear_menuitems(IndicatorNotifications *self);
-static void insert_menuitem(IndicatorNotifications *self, GtkWidget *item);
-static void remove_menuitem(IndicatorNotifications *self, GtkWidget *item);
-static void set_unread(IndicatorNotifications *self, gboolean unread);
-static void update_unread(IndicatorNotifications *self);
-static void update_filter_list(IndicatorNotifications *self);
-static void update_clear_item_markup(IndicatorNotifications *self);
-static void update_indicator_visibility(IndicatorNotifications *self);
-static void load_filter_list_hints(IndicatorNotifications *self);
-static void save_filter_list_hints(IndicatorNotifications *self);
-static void update_filter_list_hints(IndicatorNotifications *self, Notification *notification);
-static void update_do_not_disturb(IndicatorNotifications *self);
-static void settings_try_set_boolean(const gchar *schema, const gchar *key, gboolean value);
-static void swap_clear_settings_items(IndicatorNotifications *self);
-
-/* Callbacks */
-static void clear_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data);
-static void menu_visible_notify_cb(GtkWidget *menu, GParamSpec *pspec, gpointer user_data);
-static void message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data);
-static void notification_clicked_cb(NotificationMenuItem *menuitem, guint button, gpointer user_data);
-static void setting_changed_cb(GSettings *settings, gchar *key, gpointer user_data);
-static void settings_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data);
-
-/* Indicator Module Config */
-INDICATOR_SET_VERSION
-INDICATOR_SET_TYPE(INDICATOR_NOTIFICATIONS_TYPE)
-
-G_DEFINE_TYPE_WITH_PRIVATE(IndicatorNotifications, indicator_notifications, INDICATOR_OBJECT_TYPE);
-
-static void
-indicator_notifications_class_init(IndicatorNotificationsClass *klass)
-{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- setlocale(LC_ALL, "");
- bindtextdomain( GETTEXT_PACKAGE, LOCALEDIR );
- textdomain( GETTEXT_PACKAGE );
-
- object_class->dispose = indicator_notifications_dispose;
- object_class->finalize = indicator_notifications_finalize;
-
- IndicatorObjectClass *io_class = INDICATOR_OBJECT_CLASS(klass);
-
- io_class->get_image = get_image;
- io_class->get_menu = get_menu;
- io_class->get_accessible_desc = get_accessible_desc;
- io_class->secondary_activate = indicator_notifications_middle_click;
-
- return;
-}
-
-static void
-indicator_notifications_init(IndicatorNotifications *self)
-{
- self->priv = indicator_notifications_get_instance_private(self);
-
- self->priv->menu = NULL;
-
- self->priv->image = NULL;
-
- self->priv->have_unread = FALSE;
-
- self->priv->accessible_desc = _("Notifications");
-
- self->priv->visible_items = NULL;
- self->priv->hidden_items = NULL;
-
- self->priv->menu = GTK_MENU(gtk_menu_new());
- g_signal_connect(self->priv->menu, "notify::visible", G_CALLBACK(menu_visible_notify_cb), self);
-
- /* Create the settings menuitem */
- self->priv->settings_item = gtk_menu_item_new_with_label(_("Settingsā€¦"));
- g_signal_connect(self->priv->settings_item, "activate", G_CALLBACK(settings_item_activated_cb), NULL);
- gtk_widget_show(self->priv->settings_item);
-
- gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), self->priv->settings_item);
-
- /* Create the clear menuitem */
- self->priv->clear_item_label = gtk_label_new(NULL);
- gtk_label_set_xalign(GTK_LABEL(self->priv->clear_item_label), 0);
- gtk_label_set_yalign(GTK_LABEL(self->priv->clear_item_label), 0);
- gtk_label_set_use_markup(GTK_LABEL(self->priv->clear_item_label), TRUE);
- update_clear_item_markup(self);
- gtk_widget_show(self->priv->clear_item_label);
-
- self->priv->clear_item = gtk_menu_item_new();
- g_signal_connect(self->priv->clear_item, "activate", G_CALLBACK(clear_item_activated_cb), self);
- gtk_container_add(GTK_CONTAINER(self->priv->clear_item), self->priv->clear_item_label);
- gtk_widget_show(self->priv->clear_item);
-
- gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), self->priv->clear_item);
-
- self->priv->pSeparator = gtk_separator_menu_item_new();
- gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), self->priv->pSeparator);
-
- /* Watch for notifications from dbus */
- self->priv->spy = dbus_spy_new();
- g_signal_connect(self->priv->spy, DBUS_SPY_SIGNAL_MESSAGE_RECEIVED, G_CALLBACK(message_received_cb), self);
-
- /* Initialize an empty filter list */
- self->priv->filter_list = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
-
- /* Connect to GSettings */
- self->priv->settings = g_settings_new(NOTIFICATIONS_SCHEMA);
- self->priv->clear_on_middle_click = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_CLEAR_MC);
- self->priv->do_not_disturb = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_DND);
- self->priv->hide_indicator = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_HIDE_INDICATOR);
- self->priv->max_items = g_settings_get_int(self->priv->settings, NOTIFICATIONS_KEY_MAX_ITEMS);
- update_filter_list(self);
- self->priv->swap_clear_settings = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS);
- if(self->priv->swap_clear_settings)
- swap_clear_settings_items(self);
- g_signal_connect(self->priv->settings, "changed", G_CALLBACK(setting_changed_cb), self);
-
- /* Set up filter-list hints */
- self->priv->filter_list_hints = NULL;
- load_filter_list_hints(self);
-}
-
-static void
-indicator_notifications_dispose(GObject *object)
-{
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(object);
-
- if(self->priv->image != NULL) {
- g_object_unref(G_OBJECT(self->priv->image));
- self->priv->image = NULL;
- }
-
- if(self->priv->visible_items != NULL) {
- g_list_free_full(self->priv->visible_items, g_object_unref);
- self->priv->visible_items = NULL;
- }
-
- if(self->priv->hidden_items != NULL) {
- g_list_free_full(self->priv->hidden_items, g_object_unref);
- self->priv->hidden_items = NULL;
- }
-
- if(self->priv->menu != NULL) {
- g_object_unref(G_OBJECT(self->priv->menu));
- self->priv->menu = NULL;
- }
-
- if(self->priv->spy != NULL) {
- g_object_unref(G_OBJECT(self->priv->spy));
- self->priv->spy = NULL;
- }
-
- if(self->priv->settings != NULL) {
- g_object_unref(G_OBJECT(self->priv->settings));
- self->priv->settings = NULL;
- }
-
- if(self->priv->filter_list != NULL) {
- g_hash_table_unref(self->priv->filter_list);
- self->priv->filter_list = NULL;
- }
-
- if(self->priv->filter_list_hints != NULL) {
- g_list_free_full(self->priv->filter_list_hints, g_free);
- self->priv->filter_list_hints = NULL;
- }
-
- G_OBJECT_CLASS (indicator_notifications_parent_class)->dispose (object);
- return;
-}
-
-static void
-indicator_notifications_finalize(GObject *object)
-{
- G_OBJECT_CLASS (indicator_notifications_parent_class)->finalize (object);
- return;
-}
-
-static GtkImage *
-get_image(IndicatorObject *io)
-{
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io);
-
- if(self->priv->image == NULL) {
- self->priv->image = GTK_IMAGE(gtk_image_new());
- /* We have to wait until the image is created to update do-not-disturb the first time */
- update_do_not_disturb(self);
- update_indicator_visibility(self);
- }
-
- return self->priv->image;
-}
-
-static GtkMenu *
-get_menu(IndicatorObject *io)
-{
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io);
-
- return GTK_MENU(self->priv->menu);
-}
-
-static const gchar *
-get_accessible_desc(IndicatorObject *io)
-{
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io);
-
- return self->priv->accessible_desc;
-}
-
-static void
-indicator_notifications_middle_click(IndicatorObject *io, IndicatorObjectEntry *entry,
- guint time, gpointer user_data)
-{
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(io);
-
- /* Clear the notifications */
- if(self->priv->clear_on_middle_click) {
- clear_menuitems(self);
- set_unread(self, FALSE);
- }
- /* Otherwise toggle unread status */
- else {
- if(g_list_length(self->priv->visible_items) > 0)
- set_unread(self, !self->priv->have_unread);
- }
-}
-
-/**
- * clear_menuitems:
- * @self: the indicator
- *
- * Clear all notification menuitems from the menu and the visible/hidden lists.
- **/
-static void
-clear_menuitems(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
- GList *item;
-
- /* Remove each visible item from the menu */
- for(item = self->priv->visible_items; item; item = item->next) {
- gtk_container_remove(GTK_CONTAINER(self->priv->menu), GTK_WIDGET(item->data));
- }
-
- /* Clear the lists */
- g_list_free_full(self->priv->visible_items, g_object_unref);
- self->priv->visible_items = NULL;
-
- g_list_free_full(self->priv->hidden_items, g_object_unref);
- self->priv->hidden_items = NULL;
-
- update_clear_item_markup(self);
-}
-
-/**
- * insert_menuitem:
- * @self: the indicator
- * @item: the menuitem to insert
- *
- * Inserts a menuitem into the indicator's menu and updates the visible and
- * hidden lists.
- **/
-static void
-insert_menuitem(IndicatorNotifications *self, GtkWidget *item)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
- g_return_if_fail(GTK_IS_MENU_ITEM(item));
- GList *last_item;
- GtkWidget *last_widget;
-
- /* List holds a ref to the menuitem */
- self->priv->visible_items = g_list_prepend(self->priv->visible_items, g_object_ref(item));
- gtk_menu_shell_prepend(GTK_MENU_SHELL(self->priv->menu), item);
-
- /* Move items that overflow to the hidden list */
- while(g_list_length(self->priv->visible_items) > self->priv->max_items) {
- last_item = g_list_last(self->priv->visible_items);
- last_widget = GTK_WIDGET(last_item->data);
- /* Steal the ref from the visible list */
- self->priv->visible_items = g_list_delete_link(self->priv->visible_items, last_item);
- self->priv->hidden_items = g_list_prepend(self->priv->hidden_items, last_widget);
- gtk_container_remove(GTK_CONTAINER(self->priv->menu), last_widget);
- last_item = NULL;
- last_widget = NULL;
- }
-
- update_clear_item_markup(self);
-}
-
-/**
- * remove_menuitem:
- * @self: the indicator object
- * @item: the menuitem
- *
- * Removes a menuitem from the indicator menu and the visible list.
- **/
-static void
-remove_menuitem(IndicatorNotifications *self, GtkWidget *item)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
- g_return_if_fail(GTK_IS_MENU_ITEM(item));
-
- GList *list_item = g_list_find(self->priv->visible_items, item);
-
- if(list_item == NULL) {
- g_warning("Attempt to remove menuitem not in visible list");
- return;
- }
-
- /* Remove the item */
- gtk_container_remove(GTK_CONTAINER(self->priv->menu), item);
- self->priv->visible_items = g_list_delete_link(self->priv->visible_items, list_item);
- g_object_unref(item);
-
- /* Add an item from the hidden list, if available */
- if(g_list_length(self->priv->hidden_items) > 0) {
- list_item = g_list_first(self->priv->hidden_items);
- GtkWidget *list_widget = GTK_WIDGET(list_item->data);
- self->priv->hidden_items = g_list_delete_link(self->priv->hidden_items, list_item);
- gtk_menu_shell_insert(GTK_MENU_SHELL(self->priv->menu), list_widget,
- g_list_length(self->priv->visible_items));
- /* Steal the ref back from the hidden list */
- self->priv->visible_items = g_list_append(self->priv->visible_items, list_widget);
- }
-
- update_clear_item_markup(self);
-}
-
-/**
- * set_unread:
- * @self: the indicator object
- * @unread: the unread status
- *
- * Sets the unread status of the indicator and updates the icons.
- **/
-static void
-set_unread(IndicatorNotifications *self, gboolean unread)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
-
- self->priv->have_unread = unread;
- update_unread(self);
-}
-
-/**
- * update_unread:
- * @self: the indicator object
- *
- * Updates the indicator icons.
- **/
-static void
-update_unread(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
-
- if(self->priv->have_unread) {
- if (self->priv->do_not_disturb) {
- gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_UNREAD_DND, GTK_ICON_SIZE_MENU);
- }
- else {
- gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_UNREAD, GTK_ICON_SIZE_MENU);
- }
- }
- else {
- if (self->priv->do_not_disturb) {
- gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_READ_DND, GTK_ICON_SIZE_MENU);
- }
- else {
- gtk_image_set_from_icon_name(self->priv->image, INDICATOR_ICON_READ, GTK_ICON_SIZE_MENU);
- }
- }
-}
-
-/**
- * update_filter_list:
- * @self: the indicator object
- *
- * Updates the filter list from GSettings. This currently does not filter already
- * allowed messages. It only applies to messages received in the future.
- **/
-static void
-update_filter_list(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
- g_return_if_fail(self->priv->filter_list != NULL);
-
- g_hash_table_remove_all(self->priv->filter_list);
- gchar **items = g_settings_get_strv(self->priv->settings, NOTIFICATIONS_KEY_FILTER_LIST);
- int i;
-
- for(i = 0; items[i] != NULL; i++) {
- g_hash_table_insert(self->priv->filter_list, g_strdup(items[i]), NULL);
- }
-
- g_strfreev(items);
-}
-
-/**
- * update_clear_item_markup:
- * @self: the indicator object
- *
- * Updates the clear menuitem's label markup based on the number of
- * notifications available.
- **/
-static void
-update_clear_item_markup(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
- guint visible_length = g_list_length(self->priv->visible_items);
- guint hidden_length = g_list_length(self->priv->hidden_items);
- guint total_length = visible_length + hidden_length;
-
- gchar *markup = g_strdup_printf(ngettext(
- "Clear <small>(%d Notification)</small>",
- "Clear <small>(%d Notifications)</small>",
- total_length),
- total_length);
-
- gtk_label_set_markup(GTK_LABEL(self->priv->clear_item_label), markup);
- g_free(markup);
-
- if (total_length == 0)
- {
- gtk_widget_hide(self->priv->pSeparator);
- gtk_menu_shell_deactivate(GTK_MENU_SHELL(self->priv->menu));
- }
- else
- {
- gtk_widget_show(self->priv->pSeparator);
- }
-}
-
-/**
- * update_indicator_visibility:
- * @self: the indicator object
- *
- * Changes the visibility of the indicator image based on the value
- * of hide_indicator.
- **/
-static void
-update_indicator_visibility(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
-
- if(self->priv->image != NULL) {
- if(self->priv->hide_indicator)
- gtk_widget_hide(GTK_WIDGET(self->priv->image));
- else
- gtk_widget_show(GTK_WIDGET(self->priv->image));
- }
-}
-
-/**
- * load_filter_list_hints:
- * @self: the indicator object
- *
- * Loads the filter list hints from gsettings
- **/
-static void
-load_filter_list_hints(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
- g_return_if_fail(self->priv->filter_list_hints == NULL);
-
- gchar **items = g_settings_get_strv(self->priv->settings, NOTIFICATIONS_KEY_FILTER_LIST_HINTS);
- int i;
-
- for (i = 0; items[i] != NULL; i++) {
- self->priv->filter_list_hints = g_list_prepend(self->priv->filter_list_hints, items[i]);
- }
-
- g_free(items);
-}
-
-/**
- * save_filter_list_hints:
- * @self: the indicator object
- *
- * Saves the filter list hints to gsettings
- **/
-static void
-save_filter_list_hints(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
-
- gchar *hints[HINT_MAX + 1];
- int i = 0;
-
- GList *l;
- for (l = self->priv->filter_list_hints; (l != NULL) && (i < HINT_MAX); l = l->next, i++) {
- hints[i] = (gchar *) l->data;
- }
-
- hints[i] = NULL;
-
- g_settings_set_strv(self->priv->settings, NOTIFICATIONS_KEY_FILTER_LIST_HINTS, (const gchar **) hints);
-}
-
-/**
- * update_filter_list_hints:
- * @self: the indicator object
- *
- * Adds an application name to the hints
- **/
-static void
-update_filter_list_hints(IndicatorNotifications *self, Notification *notification)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
- g_return_if_fail(IS_NOTIFICATION(notification));
-
- const gchar *appname = notification_get_app_name(notification);
-
- /* Avoid duplicates */
- GList *l;
- for (l = self->priv->filter_list_hints; l != NULL; l = l->next) {
- if (g_strcmp0(appname, (const gchar *) l->data) == 0)
- return;
- }
-
- /* Add the appname */
- self->priv->filter_list_hints = g_list_prepend(self->priv->filter_list_hints, g_strdup(appname));
-
- /* Keep only a reasonable number */
- while (g_list_length(self->priv->filter_list_hints) > HINT_MAX) {
- GList *last = g_list_last(self->priv->filter_list_hints);
- g_free(last->data);
- self->priv->filter_list_hints = g_list_delete_link(self->priv->filter_list_hints, last);
- }
-
- /* Save the hints */
- /* FIXME: maybe don't do this every update */
- save_filter_list_hints(self);
-}
-
-/**
- * update_do_not_disturb:
- * @self: the indicator object
- *
- * Updates the icon with the do-not-disturb version and sets do-not-disturb options
- * on external notification daemons that are supported.
- **/
-static void
-update_do_not_disturb(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
-
- update_unread(self);
-
- /* Mate do-not-disturb support */
- settings_try_set_boolean(MATE_SCHEMA, MATE_KEY_DND, self->priv->do_not_disturb);
-}
-
-/**
- * settings_try_set_boolean:
- * @schema: the GSettings schema
- * @key: the GSettings key
- * @value: the boolean value
- *
- * Checks to see if the schema and key exist before setting the value.
- */
-static void
-settings_try_set_boolean(const gchar *schema, const gchar *key, gboolean value)
-{
- /* Check if we can access the schema */
- GSettingsSchemaSource *source = g_settings_schema_source_get_default();
- if (source == NULL) {
- return;
- }
-
- /* Lookup the schema */
- GSettingsSchema *source_schema = g_settings_schema_source_lookup(source, schema, TRUE);
-
- /* Couldn't find the schema */
- if (source_schema == NULL) {
- return;
- }
-
- /* Found the schema, make sure we have the key */
- if (g_settings_schema_has_key(source_schema, key)) {
- /* Make sure the key is of boolean type */
- GSettingsSchemaKey *source_key = g_settings_schema_get_key(source_schema, key);
-
- if (g_variant_type_equal(g_settings_schema_key_get_value_type(source_key), G_VARIANT_TYPE_BOOLEAN)) {
- /* Set the value */
- GSettings *settings = g_settings_new(schema);
- g_settings_set_boolean(settings, key, value);
- g_object_unref(settings);
- }
-
- g_settings_schema_key_unref(source_key);
- }
- g_settings_schema_unref(source_schema);
-}
-
-/**
- * swap_clear_settings_items:
- * @self: the indicator object
- *
- * Swaps the position of the clear and settings items.
- **/
-static void
-swap_clear_settings_items(IndicatorNotifications *self)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(self));
-
- // Pick which widget to move
- GtkWidget *widget = self->priv->settings_item;
- if(self->priv->swap_clear_settings)
- widget = self->priv->clear_item;
-
- gtk_container_remove(GTK_CONTAINER(self->priv->menu), g_object_ref(widget));
- gtk_menu_shell_append(GTK_MENU_SHELL(self->priv->menu), widget);
- g_object_unref(widget);
-}
-
-/**
- * clear_item_activated_cb:
- * @menuitem: the clear menuitem
- * @user_data: the indicator object
- *
- * Called when the clear menuitem is activated.
- **/
-static void
-clear_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data)
-{
- g_return_if_fail(GTK_IS_MENU_ITEM(menuitem));
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data));
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data);
-
- clear_menuitems(self);
-}
-
-/**
- * settings_item_activated_cb:
- * @menuitem: the settings menuitem
- * @user_data: the indicator object
- *
- * Called when the settings menuitem is activated.
- **/
-static void
-settings_item_activated_cb(GtkMenuItem *menuitem, gpointer user_data)
-{
- g_return_if_fail(GTK_IS_MENU_ITEM(menuitem));
-
- GError *error = NULL;
-
- gchar *argv[] = { SETTINGS_PATH, NULL };
-
- GPid pid;
-
- g_spawn_async(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, &pid, &error);
-
- if (error != NULL) {
- g_message("%s", error->message);
- g_error_free(error);
- }
- else {
- g_spawn_close_pid(pid);
- }
-}
-
-/**
- * setting_changed_cb:
- * @settings: the GSettings object
- * @key: the GSettings key
- * @user_data: the indicator object
- *
- * Called when a GSettings key is changed.
- **/
-static void
-setting_changed_cb(GSettings *settings, gchar *key, gpointer user_data)
-{
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data));
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data);
-
- if(g_strcmp0(key, NOTIFICATIONS_KEY_HIDE_INDICATOR) == 0) {
- self->priv->hide_indicator = g_settings_get_boolean(settings, NOTIFICATIONS_KEY_HIDE_INDICATOR);
- update_indicator_visibility(self);
- }
- else if(g_strcmp0(key, NOTIFICATIONS_KEY_DND) == 0) {
- self->priv->do_not_disturb = g_settings_get_boolean(settings, NOTIFICATIONS_KEY_DND);
- update_do_not_disturb(self);
- }
- else if(g_strcmp0(key, NOTIFICATIONS_KEY_CLEAR_MC) == 0) {
- self->priv->clear_on_middle_click = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_CLEAR_MC);
- }
- else if(g_strcmp0(key, NOTIFICATIONS_KEY_FILTER_LIST) == 0) {
- update_filter_list(self);
- }
- else if(g_strcmp0(key, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS) == 0) {
- self->priv->swap_clear_settings = g_settings_get_boolean(self->priv->settings, NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS);
- swap_clear_settings_items(self);
- }
- /* TODO: Trim or extend the notifications list based on "max-items" key
- * (Currently requires a restart) */
-}
-
-/**
- * menu_visible_notify_cb:
- * @menu: the menu
- * @pspec: unused
- * @user_data: the indicator object
- *
- * Called when the indicator's menu is shown or hidden.
- **/
-static void
-menu_visible_notify_cb(GtkWidget *menu, G_GNUC_UNUSED GParamSpec *pspec, gpointer user_data)
-{
- g_return_if_fail(GTK_IS_MENU(menu));
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data));
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data);
-
- gboolean visible;
- g_object_get(G_OBJECT(menu), "visible", &visible, NULL);
- if(!visible) {
- set_unread(self, FALSE);
- }
-}
-
-/**
- * message_received_cb:
- * @spy: the dbus notification monitor
- * @note: the notification received
- * @user_data: the indicator object
- *
- * Called when a notification arrives on dbus.
- **/
-static void
-message_received_cb(DBusSpy *spy, Notification *note, gpointer user_data)
-{
- g_return_if_fail(IS_DBUS_SPY(spy));
- g_return_if_fail(IS_NOTIFICATION(note));
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data));
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data);
-
- /* Discard notifications if we are hidden */
- if(self->priv->hide_indicator) {
- g_object_unref(note);
- return;
- }
-
- /* Discard useless notifications */
- if(notification_is_private(note) || notification_is_empty(note)) {
- g_object_unref(note);
- return;
- }
-
- /* Discard notifications on the filter list */
- if(self->priv->filter_list != NULL && g_hash_table_contains(self->priv->filter_list,
- notification_get_app_name(note))) {
- g_object_unref(note);
- return;
- }
-
- /* Save a hint for the appname */
- update_filter_list_hints(self, note);
-
- /* Create the menuitem */
- GtkWidget *item = notification_menuitem_new();
- notification_menuitem_set_from_notification(NOTIFICATION_MENUITEM(item), note);
- g_signal_connect(item, NOTIFICATION_MENUITEM_SIGNAL_CLICKED, G_CALLBACK(notification_clicked_cb), self);
- gtk_widget_show(item);
- g_object_unref(note);
-
- insert_menuitem(self, item);
-
- set_unread(self, TRUE);
-}
-
-/**
- * notification_clicked_cb:
- * @widget: the menuitem
- * @user_data: the indicator object
- *
- * Remove the menuitem when clicked.
- **/
-static void
-notification_clicked_cb(NotificationMenuItem *menuitem, guint button, gpointer user_data)
-{
- g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem));
- g_return_if_fail(IS_INDICATOR_NOTIFICATIONS(user_data));
- IndicatorNotifications *self = INDICATOR_NOTIFICATIONS(user_data);
-
- remove_menuitem(self, GTK_WIDGET(menuitem));
-}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..f57237a
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <locale.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include "service.h"
+
+static void on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop)
+{
+ g_message ("exiting: service couldn't acquire or lost ownership of busname");
+ g_main_loop_quit ((GMainLoop*)loop);
+}
+
+int main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED)
+{
+ IndicatorNotificationsService * service;
+ GMainLoop * loop;
+
+ /* boilerplate i18n */
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
+ textdomain (GETTEXT_PACKAGE);
+
+ /* run */
+ service = indicator_notifications_service_new (NULL);
+ loop = g_main_loop_new (NULL, FALSE);
+ g_signal_connect (service, INDICATOR_NOTIFICATIONS_SERVICE_SIGNAL_NAME_LOST, G_CALLBACK(on_name_lost), loop);
+ g_main_loop_run (loop);
+
+ /* cleanup */
+ g_main_loop_unref (loop);
+ g_clear_object (&service);
+
+ return 0;
+}
diff --git a/src/notification-menuitem.c b/src/notification-menuitem.c
deleted file mode 100644
index 392d9d3..0000000
--- a/src/notification-menuitem.c
+++ /dev/null
@@ -1,407 +0,0 @@
-/*
- * notification-menuitem.h - A menuitem to display notifications.
- */
-
-#ifdef HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-#include <glib/gi18n-lib.h>
-#include "notification-menuitem.h"
-#include "urlregex.h"
-
-#define NOTIFICATION_MENUITEM_MAX_CHARS 42
-#define NOTIFICATION_MENUITEM_CLOSE_SELECT "ayatana-indicator-notification-close-select"
-#define NOTIFICATION_MENUITEM_CLOSE_DESELECT "ayatana-indicator-notification-close-deselect"
-
-enum {
- CLICKED,
- LAST_SIGNAL
-};
-
-static void notification_menuitem_class_init(NotificationMenuItemClass *klass);
-static void notification_menuitem_init(NotificationMenuItem *self);
-
-static void notification_menuitem_activate(GtkMenuItem *menuitem);
-static gboolean notification_menuitem_motion(GtkWidget *widget, GdkEventMotion *event);
-static gboolean notification_menuitem_leave(GtkWidget *widget, GdkEventCrossing *event);
-static gboolean notification_menuitem_button_press(GtkWidget *widget, GdkEventButton *event);
-static gboolean notification_menuitem_button_release(GtkWidget *widget, GdkEventButton *event);
-static void notification_menuitem_select(GtkMenuItem *item);
-static void notification_menuitem_deselect(GtkMenuItem *item);
-
-static gboolean notification_menuitem_activate_link_cb(GtkLabel *label, gchar *uri, gpointer user_data);
-static gchar *notification_menuitem_markup_body(const gchar *body);
-
-static gboolean widget_contains_event(GtkWidget *widget, GdkEventButton *event);
-
-static guint notification_menuitem_signals[LAST_SIGNAL] = { 0 };
-
-G_DEFINE_TYPE_WITH_PRIVATE(NotificationMenuItem, notification_menuitem, GTK_TYPE_MENU_ITEM);
-
-static void
-notification_menuitem_class_init(NotificationMenuItemClass *klass)
-{
- GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
- GtkMenuItemClass *menu_item_class = GTK_MENU_ITEM_CLASS(klass);
-
- widget_class->leave_notify_event = notification_menuitem_leave;
- widget_class->motion_notify_event = notification_menuitem_motion;
- widget_class->button_press_event = notification_menuitem_button_press;
- widget_class->button_release_event = notification_menuitem_button_release;
-
- menu_item_class->hide_on_activate = FALSE;
- menu_item_class->activate = notification_menuitem_activate;
- menu_item_class->select = notification_menuitem_select;
- menu_item_class->deselect = notification_menuitem_deselect;
-
- /* Compile the urlregex patterns */
- urlregex_init();
-
- notification_menuitem_signals[CLICKED] =
- g_signal_new(NOTIFICATION_MENUITEM_SIGNAL_CLICKED,
- G_TYPE_FROM_CLASS(klass),
- G_SIGNAL_RUN_FIRST,
- G_STRUCT_OFFSET(NotificationMenuItemClass, clicked),
- NULL, NULL,
- g_cclosure_marshal_VOID__UINT,
- G_TYPE_NONE, 1, G_TYPE_UINT);
-}
-
-static void
-notification_menuitem_init(NotificationMenuItem *self)
-{
- self->priv = notification_menuitem_get_instance_private(self);
-
- self->priv->pressed_close_image = FALSE;
-
- self->priv->hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
-
- self->priv->label = gtk_label_new(NULL);
- gtk_widget_set_halign(self->priv->label, GTK_ALIGN_START);
- gtk_widget_set_valign(self->priv->label, GTK_ALIGN_START);
- gtk_label_set_use_markup(GTK_LABEL(self->priv->label), TRUE);
- gtk_label_set_line_wrap(GTK_LABEL(self->priv->label), TRUE);
- gtk_label_set_line_wrap_mode(GTK_LABEL(self->priv->label), PANGO_WRAP_WORD_CHAR);
- gtk_label_set_max_width_chars(GTK_LABEL(self->priv->label), NOTIFICATION_MENUITEM_MAX_CHARS);
- gtk_label_set_track_visited_links(GTK_LABEL(self->priv->label), TRUE);
-
- g_signal_connect(self->priv->label, "activate-link", G_CALLBACK(notification_menuitem_activate_link_cb), self);
-
- gtk_box_pack_start(GTK_BOX(self->priv->hbox), self->priv->label, TRUE, TRUE, 0);
- gtk_widget_show(self->priv->label);
-
- self->priv->close_image = gtk_image_new_from_icon_name(NOTIFICATION_MENUITEM_CLOSE_DESELECT, GTK_ICON_SIZE_MENU);
- gtk_widget_show(self->priv->close_image);
- gtk_box_pack_start(GTK_BOX(self->priv->hbox), self->priv->close_image, FALSE, FALSE, 0);
-
- gtk_container_add(GTK_CONTAINER(self), self->priv->hbox);
- gtk_widget_show(self->priv->hbox);
-}
-
-GtkWidget *
-notification_menuitem_new(void)
-{
- return g_object_new(NOTIFICATION_MENUITEM_TYPE, NULL);
-}
-
-/**
- * notification_menuitem_set_from_notification:
- * @self - the notification menuitem
- * @note - the notification object
- *
- * Sets the markup in the notification menuitem to display information about
- * the notification, as well as marking any links within the message body.
- **/
-void
-notification_menuitem_set_from_notification(NotificationMenuItem *self, Notification *note)
-{
- g_return_if_fail(IS_NOTIFICATION(note));
- gchar *unescaped_timestamp_string = notification_timestamp_for_locale(note);
-
- gchar *app_name = g_markup_escape_text(notification_get_app_name(note), -1);
- gchar *summary = g_markup_escape_text(notification_get_summary(note), -1);
- gchar *body = notification_menuitem_markup_body(notification_get_body(note));
- gchar *timestamp_string = g_markup_escape_text(unescaped_timestamp_string, -1);
-
- gchar *markup = g_strdup_printf("<b>%s</b>\n%s\n<small><i>%s %s <b>%s</b></i></small>",
- summary, body, timestamp_string, _("from"), app_name);
-
- g_free(app_name);
- g_free(summary);
- g_free(body);
- g_free(unescaped_timestamp_string);
- g_free(timestamp_string);
-
- gtk_label_set_markup(GTK_LABEL(self->priv->label), markup);
-
- g_free(markup);
-}
-
-/**
- * notification_menuitem_activate:
- * @menuitem: the menuitem
- *
- * Emit a clicked event for the case where a keyboard activates a menuitem.
- **/
-static void
-notification_menuitem_activate(GtkMenuItem *menuitem)
-{
- g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem));
-
- g_signal_emit(NOTIFICATION_MENUITEM(menuitem), notification_menuitem_signals[CLICKED], 0);
-}
-
-/**
- * notification_menuitem_leave:
- * @widget - the widget
- * @event - the event
- *
- * Handle the leave-notify-event, by simply passing it on to the GtkLabel of
- * this menuitem.
- **/
-static gboolean
-notification_menuitem_leave(GtkWidget *widget, GdkEventCrossing *event)
-{
- g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE);
-
- NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget);
-
- gtk_widget_event(self->priv->label, (GdkEvent *)event);
- return FALSE;
-}
-
-/**
- * notification_menuitem_motion:
- * @widget - the widget
- * @event - the event
- *
- * Handle the motion-notify-event. It is passed on to the GtkLabel, but the
- * event (x, y) is mapped to the whole menuitem not the label so we have to
- * shift it over a bit into the label's allocation.
- **/
-static gboolean
-notification_menuitem_motion(GtkWidget *widget, GdkEventMotion *event)
-{
- g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE);
-
- NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget);
-
- GtkAllocation self_alloc;
- GtkAllocation label_alloc;
-
- gtk_widget_get_allocation(GTK_WIDGET(self), &self_alloc);
- gtk_widget_get_allocation(self->priv->label, &label_alloc);
-
- /* The event is mapped to the menu item's allocation, so we need to shift it
- * to the label's allocation so that links are probably selected.
- */
- GdkEventMotion *e = (GdkEventMotion *)gdk_event_copy((GdkEvent *)event);
- e->x = event->x - (label_alloc.x - self_alloc.x);
- e->y = event->y - (label_alloc.y - self_alloc.y);
-
- gtk_widget_event(self->priv->label, (GdkEvent *)e);
-
- gdk_event_free((GdkEvent *)e);
- return FALSE;
-}
-
-/**
- * notification_menuitem_button_press:
- * @widget: the menuitem
- * @event: the button press event
- *
- * Override the menuitem button-press-event.
- **/
-static gboolean
-notification_menuitem_button_press(GtkWidget *widget, GdkEventButton *event)
-{
- g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE);
-
- NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget);
-
- /* The context menu breaks everything so disable it for now */
- if (event->button == GDK_BUTTON_PRIMARY && widget_contains_event(self->priv->label, event)) {
- gtk_widget_event(self->priv->label, (GdkEvent *)event);
- }
- else if (widget_contains_event(self->priv->close_image, event)) {
- self->priv->pressed_close_image = TRUE;
- }
- return TRUE;
-}
-
-/**
- * notification_menuitem_button_release:
- * @widget: the menuitem
- * @event: the button release event
- *
- * Override the menuitem button-release-event so that the menu isn't hidden
- * when the item is removed. Also the event is passed on to the label so we can
- * get an activate-link signal when a link is clicked.
- **/
-static gboolean
-notification_menuitem_button_release(GtkWidget *widget, GdkEventButton *event)
-{
- g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(widget), FALSE);
-
- NotificationMenuItem *self = NOTIFICATION_MENUITEM(widget);
-
- if (widget_contains_event(self->priv->close_image, event)) {
- if (self->priv->pressed_close_image)
- g_signal_emit(NOTIFICATION_MENUITEM(widget), notification_menuitem_signals[CLICKED], 0, event->button);
- }
- else {
- /* The context menu breaks everything so disable it for now */
- if (event->button == GDK_BUTTON_PRIMARY) {
- gtk_widget_event(self->priv->label, (GdkEvent *)event);
- }
- }
- self->priv->pressed_close_image = FALSE;
- return TRUE;
-}
-
-/**
- * notification_menuitem_select:
- * @menuitem - the menuitem
- *
- * Handle the menuitem select signal. We don't want to set PRELIGHT on the
- * menuitem because in various themes it becomes very hard to see the links.
- * Instead we set a special close image to show that the notification is
- * selected for keyboard navigation.
- **/
-static void
-notification_menuitem_select(GtkMenuItem *menuitem)
-{
- g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem));
-
- NotificationMenuItem *self = NOTIFICATION_MENUITEM(menuitem);
-
- gtk_image_set_from_icon_name(GTK_IMAGE(self->priv->close_image),
- NOTIFICATION_MENUITEM_CLOSE_SELECT,
- GTK_ICON_SIZE_MENU);
-}
-
-/**
- * notification_menuitem_deselect:
- * @menuitem - the menuitem
- *
- * Same as notification_menuitem_select, but sets the opposite close image.
- **/
-static void
-notification_menuitem_deselect(GtkMenuItem *menuitem)
-{
- g_return_if_fail(IS_NOTIFICATION_MENUITEM(menuitem));
-
- NotificationMenuItem *self = NOTIFICATION_MENUITEM(menuitem);
-
- gtk_image_set_from_icon_name(GTK_IMAGE(self->priv->close_image),
- NOTIFICATION_MENUITEM_CLOSE_DESELECT,
- GTK_ICON_SIZE_MENU);
-}
-
-/**
- * notification_menuitem_activate_link_cb:
- * @label - the label
- * @uri - the link that was activated
- * @user_data - the notification menuitem
- *
- * We override the activate-link signal of the GtkLabel because we need to
- * deactivate the menu shell when it is clicked, otherwise it stays stuck to
- * the screen as the browser loads.
- **/
-static gboolean
-notification_menuitem_activate_link_cb(GtkLabel *label, gchar *uri, gpointer user_data)
-{
- g_return_val_if_fail(IS_NOTIFICATION_MENUITEM(user_data), FALSE);
-
- NotificationMenuItem *self = NOTIFICATION_MENUITEM(user_data);
-
- /* Show the link */
- GError *error = NULL;
-
- if (!gtk_show_uri_on_window(NULL, uri, gtk_get_current_event_time(), &error))
- {
- g_warning("Unable to show '%s': %s", uri, error->message);
- g_error_free(error);
- }
-
- /* Deactivate the menu shell so it doesn't block the screen */
- GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(self));
- if (GTK_IS_MENU_SHELL(parent)) {
- gtk_menu_shell_deactivate(GTK_MENU_SHELL(parent));
- }
-
- return TRUE;
-}
-
-/**
- * notification_menuitem_markup_body:
- * @body - the body of a notification
- *
- * Scans through the body text escaping everything that isn't a link. The links
- * are marked up as anchors with hrefs.
- **/
-static gchar *
-notification_menuitem_markup_body(const gchar *body)
-{
- GList *list = urlregex_split_all(body);
- guint len = g_list_length(list);
- gchar **str_array = g_new0(gchar *, len + 1);
- guint i = 0;
- GList *item;
- gchar *escaped_text;
- gchar *escaped_expanded;
-
- for (item = list; item; item = item->next, i++) {
- MatchGroup *group = (MatchGroup *)item->data;
- if (group->type == MATCHED) {
- escaped_text = g_markup_escape_text(group->text, -1);
- escaped_expanded = g_markup_escape_text(group->expanded, -1);
- str_array[i] = g_strdup_printf("<a href=\"%s\">%s</a>", escaped_expanded, escaped_text);
- g_free(escaped_text);
- g_free(escaped_expanded);
- }
- else {
- str_array[i] = g_markup_escape_text(group->text, -1);
- }
- }
-
- urlregex_matchgroup_list_free(list);
- gchar *result = g_strjoinv(NULL, str_array);
- g_strfreev(str_array);
- return result;
-}
-
-/**
- * widget_contains_event:
- * @widget - the widget
- * @event - the event
- *
- * Determines whether the (x, y) coordinates of the event fall inside the
- * widget.
- **/
-static gboolean
-widget_contains_event(GtkWidget *widget, GdkEventButton *event)
-{
- if (gtk_widget_get_window(widget) == NULL)
- return FALSE;
-
- GtkAllocation allocation;
-
- gtk_widget_get_allocation(widget, &allocation);
-
- GdkWindow *window = gtk_widget_get_window(widget);
-
- int xwin, ywin;
-
- gdk_window_get_origin(window, &xwin, &ywin);
-
- int xmin = allocation.x;
- int xmax = allocation.x + allocation.width;
- int ymin = allocation.y;
- int ymax = allocation.y + allocation.height;
- int x = event->x_root - xwin;
- int y = event->y_root - ywin;
-
- return x >= xmin && x <= xmax && y >= ymin && y <= ymax;
-}
diff --git a/src/notification-menuitem.h b/src/notification-menuitem.h
deleted file mode 100644
index afb2b98..0000000
--- a/src/notification-menuitem.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * notification-menuitem.h - A menuitem to display notifications.
- */
-
-#ifndef __NOTIFICATION_MENUITEM_H__
-#define __NOTIFICATION_MENUITEM_H__
-
-#include <gtk/gtk.h>
-#include "notification.h"
-
-G_BEGIN_DECLS
-
-#define NOTIFICATION_MENUITEM_TYPE (notification_menuitem_get_type ())
-#define NOTIFICATION_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NOTIFICATION_MENUITEM_TYPE, NotificationMenuItem))
-#define NOTIFICATION_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NOTIFICATION_MENUITEM_TYPE, NotificationMenuItemClass))
-#define IS_NOTIFICATION_MENUITEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NOTIFICATION_MENUITEM_TYPE))
-#define IS_NOTIFICATION_MENUITEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NOTIFICATION_MENUITEM_TYPE))
-
-typedef struct _NotificationMenuItem NotificationMenuItem;
-typedef struct _NotificationMenuItemClass NotificationMenuItemClass;
-typedef struct _NotificationMenuItemPrivate NotificationMenuItemPrivate;
-
-struct _NotificationMenuItem
-{
- GtkMenuItem parent_instance;
- NotificationMenuItemPrivate *priv;
-};
-
-struct _NotificationMenuItemClass
-{
- GtkMenuItemClass parent_class;
-
- void (* clicked) (NotificationMenuItem *menuitem, guint button);
-};
-
-struct _NotificationMenuItemPrivate {
- GtkWidget *close_image;
- GtkWidget *hbox;
- GtkWidget *label;
-
- gboolean pressed_close_image;
-};
-
-#define NOTIFICATION_MENUITEM_SIGNAL_CLICKED "clicked"
-
-GType notification_menuitem_get_type(void);
-GtkWidget *notification_menuitem_new(void);
-void notification_menuitem_set_from_notification(NotificationMenuItem *self, Notification *note);
-
-G_END_DECLS
-
-#endif /* __NOTIFICATION_MENUITEM_H__ */
diff --git a/src/service.c b/src/service.c
new file mode 100644
index 0000000..2de2613
--- /dev/null
+++ b/src/service.c
@@ -0,0 +1,867 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ * Ted Gould <ted@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "service.h"
+#include "dbus-spy.h"
+#include "urlregex.h"
+
+#define BUS_NAME "org.ayatana.indicator.notifications"
+#define BUS_PATH "/org/ayatana/indicator/notifications"
+#define HINT_MAX 10
+
+static guint m_nSignal = 0;
+
+enum
+{
+ SECTION_HEADER = (1<<0),
+ SECTION_NOTIFICATIONS = (1<<1),
+ SECTION_DO_NOT_DISTURB = (1<<2),
+ SECTION_CLEAR = (1<<3)
+};
+
+enum
+{
+ PROFILE_PHONE,
+ PROFILE_DESKTOP,
+ N_PROFILES
+};
+
+static const char * const menu_names[N_PROFILES] =
+{
+ "phone",
+ "desktop"
+};
+
+struct ProfileMenuInfo
+{
+ GMenu *pMenu;
+ GMenu *pSubmenu;
+ guint nExportId;
+};
+
+struct _IndicatorNotificationsServicePrivate
+{
+ GCancellable *pCancellable;
+ GSettings *pSettings;
+ guint nOwnId;
+ guint nActionsId;
+ GDBusConnection *pConnection;
+ gboolean bMenusBuilt;
+ struct ProfileMenuInfo lMenus[N_PROFILES];
+ GSimpleActionGroup *pActionGroup;
+ GSimpleAction *pHeaderAction;
+ GSimpleAction *pClearAction;
+ GSimpleAction *pRemoveAction;
+ GSimpleAction *pDoNotDisturbAction;
+ GList *lVisibleItems;
+ GList *lHiddenItems;
+ gboolean bDoNotDisturb;
+ gboolean bHasUnread;
+ gint nMaxItems;
+ DBusSpy *pBusSpy;
+ GHashTable *lFilters;
+ GList *lHints;
+ GMenu *pNotificationsSection;
+ gboolean bHasDoNotDisturb;
+};
+
+typedef IndicatorNotificationsServicePrivate priv_t;
+
+G_DEFINE_TYPE_WITH_PRIVATE(IndicatorNotificationsService, indicator_notifications_service, G_TYPE_OBJECT)
+
+static void rebuildNow(IndicatorNotificationsService *self, guint nSections);
+static void updateFilters(IndicatorNotificationsService *self);
+
+static void saveHints(IndicatorNotificationsService *self)
+{
+ gchar *hints[HINT_MAX + 1];
+ int i = 0;
+ GList *l;
+
+ for (l = self->priv->lHints; (l != NULL) && (i < HINT_MAX); l = l->next, i++)
+ {
+ hints[i] = (gchar *) l->data;
+ }
+
+ hints[i] = NULL;
+
+ g_settings_set_strv(self->priv->pSettings, "filter-list-hints", (const gchar **) hints);
+}
+
+static void updateHints(IndicatorNotificationsService *self, Notification *notification)
+{
+ g_return_if_fail(IS_NOTIFICATION(notification));
+
+ const gchar *appname = notification_get_app_name(notification);
+
+ // Avoid duplicates
+ GList *l;
+ for (l = self->priv->lHints; l != NULL; l = l->next)
+ {
+ if (g_strcmp0(appname, (const gchar *) l->data) == 0)
+ {
+ return;
+ }
+ }
+
+ // Add the appname
+ self->priv->lHints = g_list_prepend(self->priv->lHints, g_strdup(appname));
+
+ // Keep only a reasonable number
+ while (g_list_length(self->priv->lHints) > HINT_MAX)
+ {
+ GList *last = g_list_last(self->priv->lHints);
+ g_free(last->data);
+ self->priv->lHints = g_list_delete_link(self->priv->lHints, last);
+ }
+
+ // Save the hints
+ saveHints(self);
+}
+
+static gchar *createMarkup(const gchar *body)
+{
+ GList *list = urlregex_split_all(body);
+ guint len = g_list_length(list);
+ gchar **str_array = g_new0(gchar *, len + 1);
+ guint i = 0;
+ GList *item;
+ gchar *escaped_text;
+ gchar *escaped_expanded;
+
+ for (item = list; item; item = item->next, i++)
+ {
+ MatchGroup *group = (MatchGroup *)item->data;
+
+ if (group->type == MATCHED)
+ {
+ escaped_text = g_markup_escape_text(group->text, -1);
+ escaped_expanded = g_markup_escape_text(group->expanded, -1);
+ str_array[i] = g_strdup_printf("<a href=\"%s\">%s</a>", escaped_expanded, escaped_text);
+ g_free(escaped_text);
+ g_free(escaped_expanded);
+ }
+ else
+ {
+ str_array[i] = g_markup_escape_text(group->text, -1);
+ }
+ }
+
+ urlregex_matchgroup_list_free(list);
+ gchar *result = g_strjoinv(NULL, str_array);
+ g_strfreev(str_array);
+
+ return result;
+}
+
+static void updateClearItem(IndicatorNotificationsService *self)
+{
+ guint visible_length = g_list_length(self->priv->lVisibleItems);
+ guint hidden_length = g_list_length(self->priv->lHiddenItems);
+ guint total_length = visible_length + hidden_length;
+
+ g_simple_action_set_enabled(self->priv->pClearAction, total_length != 0);
+}
+
+static void setUnread(IndicatorNotificationsService *self, gboolean unread)
+{
+ self->priv->bHasUnread = unread;
+ rebuildNow(self, SECTION_HEADER);
+}
+
+static void onMessageReceived(DBusSpy *pBusSpy, Notification *note, gpointer user_data)
+{
+ g_return_if_fail(IS_DBUS_SPY(pBusSpy));
+ g_return_if_fail(IS_NOTIFICATION(note));
+ IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data);
+
+ // Discard useless notifications
+ if(notification_is_private(note) || notification_is_empty(note))
+ {
+ g_object_unref(note);
+
+ return;
+ }
+
+ // Discard notifications on the filter list
+ if(self->priv->lFilters != NULL && g_hash_table_contains(self->priv->lFilters, notification_get_app_name(note)))
+ {
+ g_object_unref(note);
+
+ return;
+ }
+
+ updateHints(self, note);
+
+ gchar *unescaped_timestamp_string = notification_timestamp_for_locale(note);
+ gchar *app_name = g_markup_escape_text(notification_get_app_name(note), -1);
+ gchar *summary = g_markup_escape_text(notification_get_summary(note), -1);
+ gchar *body = createMarkup(notification_get_body(note));
+ g_object_unref(note);
+ gchar *timestamp_string = g_markup_escape_text(unescaped_timestamp_string, -1);
+ gchar *markup = g_strdup_printf("<b>%s</b>\n%s\n<small><i>%s %s <b>%s</b></i></small>", summary, body, timestamp_string, _("from"), app_name);
+ g_free(app_name);
+ g_free(summary);
+ g_free(body);
+ g_free(unescaped_timestamp_string);
+ g_free(timestamp_string);
+ GMenuItem * item = g_menu_item_new(markup, NULL);
+ g_free(markup);
+ gint64 nTimestamp = notification_get_timestamp(note);
+ g_menu_item_set_action_and_target_value(item, "indicator.remove-notification", g_variant_new_int64(nTimestamp));
+ g_menu_item_set_attribute_value(item, "x-ayatana-timestamp", g_variant_new_int64(nTimestamp));
+ g_menu_item_set_attribute_value(item, "x-ayatana-use-markup", g_variant_new_boolean(TRUE));
+ g_menu_item_set_attribute(item, "x-ayatana-type", "s", "org.ayatana.indicator.basic");
+
+ GList *last_item;
+ GMenuItem *last_menu_item;
+
+ // List holds a ref to the menuitem
+ self->priv->lVisibleItems = g_list_prepend(self->priv->lVisibleItems, g_object_ref(item));
+ g_menu_prepend_item (self->priv->pNotificationsSection, item);
+ g_object_unref(item);
+
+ // Move items that overflow to the hidden list
+ while (g_list_length(self->priv->lVisibleItems) > self->priv->nMaxItems)
+ {
+ last_item = g_list_last(self->priv->lVisibleItems);
+ last_menu_item = G_MENU_ITEM(last_item->data);
+
+ // Steal the ref from the visible list
+ self->priv->lVisibleItems = g_list_delete_link(self->priv->lVisibleItems, last_item);
+ self->priv->lHiddenItems = g_list_prepend(self->priv->lHiddenItems, last_menu_item);
+ last_item = NULL;
+ last_menu_item = NULL;
+ }
+
+ while (g_menu_model_get_n_items(G_MENU_MODEL(self->priv->pNotificationsSection)) > self->priv->nMaxItems)
+ {
+ g_menu_remove(self->priv->pNotificationsSection, self->priv->nMaxItems);
+ }
+
+ updateClearItem(self);
+ setUnread(self, TRUE);
+}
+
+static GVariant *createHeaderState(IndicatorNotificationsService *self)
+{
+ GVariantBuilder b;
+
+ g_variant_builder_init (&b, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add (&b, "{sv}", "title", g_variant_new_string (_("Notifications")));
+ g_variant_builder_add (&b, "{sv}", "visible", g_variant_new_boolean (TRUE));
+
+ gchar *sIcon = NULL;
+
+ if (self->priv->bHasUnread)
+ {
+ if (self->priv->bHasDoNotDisturb && self->priv->bDoNotDisturb)
+ {
+ sIcon = "ayatana-indicator-notification-unread-dnd";
+ }
+ else
+ {
+ sIcon = "ayatana-indicator-notification-unread";
+ }
+ }
+ else
+ {
+ if (self->priv->bHasDoNotDisturb && self->priv->bDoNotDisturb)
+ {
+ sIcon = "ayatana-indicator-notification-read-dnd";
+ }
+ else
+ {
+ sIcon = "ayatana-indicator-notification-read";
+ }
+ }
+
+ GIcon * icon = g_themed_icon_new_with_default_fallbacks(sIcon);
+ g_variant_builder_add (&b, "{sv}", "accessible-desc", g_variant_new_string (_("Notifications")));
+
+ if (icon)
+ {
+ GVariant * serialized_icon = g_icon_serialize (icon);
+
+ if (serialized_icon != NULL)
+ {
+ g_variant_builder_add (&b, "{sv}", "icon", serialized_icon);
+ g_variant_unref (serialized_icon);
+ }
+
+ g_object_unref (icon);
+ }
+
+ return g_variant_builder_end (&b);
+}
+
+static GMenuModel *createDesktopNotificationsSection(IndicatorNotificationsService *self, int profile)
+{
+ self->priv->pNotificationsSection = g_menu_new();
+
+ return G_MENU_MODEL(self->priv->pNotificationsSection);
+}
+
+static GMenuModel *createDesktopClearSection(IndicatorNotificationsService *self)
+{
+ GMenu * pMenu = g_menu_new ();
+ g_menu_append(pMenu, _("Clear"), "indicator.clear-notifications");
+
+ return G_MENU_MODEL(pMenu);
+}
+
+static GMenuModel *createDesktopDoNotDisturbSection(IndicatorNotificationsService *self)
+{
+ GMenu *pMenu = g_menu_new();
+
+ if (self->priv->bHasDoNotDisturb)
+ {
+ GMenuItem *item = g_menu_item_new(_("Do not disturb"), NULL);
+ g_menu_item_set_attribute(item, "x-ayatana-type", "s", "org.ayatana.indicator.switch");
+ g_menu_item_set_action_and_target(item, "indicator.do-not-disturb", NULL);
+ g_menu_append_item(pMenu, item);
+ g_object_unref(item);
+ }
+
+ return G_MENU_MODEL(pMenu);
+}
+
+static void rebuildSection(GMenu *parent, int pos, GMenuModel *new_section)
+{
+ g_menu_remove (parent, pos);
+ g_menu_insert_section (parent, pos, NULL, new_section);
+ g_object_unref (new_section);
+}
+
+static void rebuildNow(IndicatorNotificationsService *self, guint sections)
+{
+ priv_t *p = self->priv;
+ struct ProfileMenuInfo *desktop = &p->lMenus[PROFILE_DESKTOP];
+
+ if (sections & SECTION_HEADER)
+ {
+ g_simple_action_set_state (p->pHeaderAction, createHeaderState (self));
+ }
+
+ if (!p->bMenusBuilt)
+ {
+ return;
+ }
+
+ if (sections & SECTION_NOTIFICATIONS)
+ {
+ rebuildSection (desktop->pSubmenu, 0, createDesktopNotificationsSection (self, PROFILE_DESKTOP));
+ }
+
+ if (sections & SECTION_DO_NOT_DISTURB)
+ {
+ rebuildSection (desktop->pSubmenu, 1, createDesktopDoNotDisturbSection (self));
+ }
+
+ if (sections & SECTION_CLEAR)
+ {
+ rebuildSection (desktop->pSubmenu, 2, createDesktopClearSection (self));
+ }
+}
+
+static void createMenu(IndicatorNotificationsService *self, int profile)
+{
+ GMenu * pMenu;
+ GMenu * pSubmenu;
+ GMenuItem * header;
+ GMenuModel * sections[16];
+ guint i;
+ guint n = 0;
+
+ g_assert (0 <= profile && profile < N_PROFILES);
+ g_assert (self->priv->lMenus[profile].pMenu == NULL);
+
+ // Build the sections
+ switch (profile)
+ {
+ case PROFILE_PHONE:
+ case PROFILE_DESKTOP:
+ {
+ sections[n++] = createDesktopNotificationsSection(self, PROFILE_DESKTOP);
+ sections[n++] = createDesktopDoNotDisturbSection(self);
+ sections[n++] = createDesktopClearSection(self);
+
+ break;
+ }
+
+ break;
+ }
+
+ // Add sections to the submenu
+ pSubmenu = g_menu_new();
+
+ for (i=0; i<n; ++i)
+ {
+ g_menu_append_section (pSubmenu, NULL, sections[i]);
+ g_object_unref (sections[i]);
+ }
+
+ // Add submenu to the header
+ header = g_menu_item_new (NULL, "indicator._header");
+ g_menu_item_set_attribute (header, "x-ayatana-type", "s", "org.ayatana.indicator.root");
+ g_menu_item_set_submenu(header, G_MENU_MODEL (pSubmenu));
+ g_object_unref(pSubmenu);
+
+ // Add header to the menu
+ pMenu = g_menu_new ();
+ g_menu_append_item (pMenu, header);
+ g_object_unref (header);
+
+ self->priv->lMenus[profile].pMenu = pMenu;
+ self->priv->lMenus[profile].pSubmenu = pSubmenu;
+}
+
+static void clearMenuItems(IndicatorNotificationsService *self)
+{
+ // Remove each visible item from the menu
+ while (g_menu_model_get_n_items(G_MENU_MODEL(self->priv->pNotificationsSection)) > 0)
+ {
+ g_menu_remove(self->priv->pNotificationsSection, 0);
+ }
+
+ // Clear the lists
+ g_list_free_full(self->priv->lVisibleItems, g_object_unref);
+ self->priv->lVisibleItems = NULL;
+
+ g_list_free_full(self->priv->lHiddenItems, g_object_unref);
+ self->priv->lHiddenItems = NULL;
+
+ updateClearItem(self);
+}
+
+static void onRemoveNotification(GSimpleAction *a, GVariant *param, gpointer user_data)
+{
+ IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data);
+ guint nItems = g_menu_model_get_n_items(G_MENU_MODEL(self->priv->pNotificationsSection));
+ gint64 nTimestampIn = g_variant_get_int64(param);
+
+ for (guint nItem = 0; nItem < nItems; nItem++)
+ {
+ gint64 nTimestamp;
+ g_menu_model_get_item_attribute(G_MENU_MODEL(self->priv->pNotificationsSection), nItem, "x-ayatana-timestamp", "x", &nTimestamp);
+
+ if (nTimestamp == nTimestampIn)
+ {
+ g_menu_remove(self->priv->pNotificationsSection, nItem);
+
+ break;
+ }
+ }
+
+ for (GList *pItem = self->priv->lVisibleItems; pItem; pItem = pItem->next)
+ {
+ gint64 nTimestamp;
+ g_menu_item_get_attribute(G_MENU_ITEM(pItem->data), "x-ayatana-timestamp", "x", &nTimestamp);
+
+ if (nTimestamp == nTimestampIn)
+ {
+ // Remove the item
+ self->priv->lVisibleItems = g_list_delete_link(self->priv->lVisibleItems, pItem);
+
+ // Add an item from the hidden list, if available
+ if (g_list_length(self->priv->lHiddenItems) > 0)
+ {
+ pItem = g_list_first(self->priv->lHiddenItems);
+ GMenuItem *pMenuItem = G_MENU_ITEM(pItem->data);
+ self->priv->lHiddenItems = g_list_delete_link(self->priv->lHiddenItems, pItem);
+ g_menu_append_item(self->priv->pNotificationsSection, pMenuItem);
+
+ // Steal the ref back from the hidden list
+ self->priv->lVisibleItems = g_list_append(self->priv->lVisibleItems, pMenuItem);
+ }
+
+ updateClearItem(self);
+
+ break;
+ }
+ }
+}
+
+static void onClear(GSimpleAction *a, GVariant *param, gpointer user_data)
+{
+ IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data);
+
+ clearMenuItems(self);
+ setUnread(self, FALSE);
+}
+
+static void onDoNotDisturb(GSimpleAction *a, GVariant *param, gpointer user_data)
+{
+ IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data);
+
+ if (self->priv->bHasDoNotDisturb)
+ {
+ self->priv->bDoNotDisturb = g_variant_get_boolean(param);
+ GSettings *pSettings = g_settings_new("org.mate.NotificationDaemon");
+ g_settings_set_boolean(pSettings, "do-not-disturb", self->priv->bDoNotDisturb);
+ g_object_unref(pSettings);
+ g_settings_set_boolean(self->priv->pSettings, "do-not-disturb", self->priv->bDoNotDisturb);
+ rebuildNow(self, SECTION_HEADER);
+ }
+}
+
+static void onSettingsChanged(GSettings *pSettings, gchar *key, gpointer user_data)
+{
+ IndicatorNotificationsService *self = INDICATOR_NOTIFICATIONS_SERVICE(user_data);
+
+ if (g_str_equal(key, "filter-list"))
+ {
+ updateFilters(self);
+ }
+ else if (g_str_equal(key, "do-not-disturb"))
+ {
+ if (self->priv->bHasDoNotDisturb)
+ {
+ self->priv->bDoNotDisturb = g_settings_get_boolean(self->priv->pSettings, key);
+ GSettings *pSettings = g_settings_new("org.mate.NotificationDaemon");
+ g_settings_set_boolean(pSettings, key, self->priv->bDoNotDisturb);
+ g_object_unref(pSettings);
+ g_action_change_state(G_ACTION(self->priv->pDoNotDisturbAction), g_variant_new_boolean(self->priv->bDoNotDisturb));
+ rebuildNow(self, SECTION_HEADER);
+ }
+ else if (g_settings_get_boolean(self->priv->pSettings, key))
+ {
+ g_settings_set_boolean(self->priv->pSettings, key, FALSE);
+ }
+ }
+}
+
+static void initActions(IndicatorNotificationsService *self)
+{
+ GSimpleAction * a;
+ GAction *max_items_action;
+ self->priv->pActionGroup = g_simple_action_group_new();
+
+ // Add the header action
+ a = g_simple_action_new_stateful ("_header", NULL, createHeaderState(self));
+ g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a));
+ self->priv->pHeaderAction = a;
+
+ // Add the remove action
+ a = g_simple_action_new("remove-notification", G_VARIANT_TYPE_INT64);
+ g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a));
+ self->priv->pRemoveAction = a;
+ g_signal_connect(a, "activate", G_CALLBACK(onRemoveNotification), self);
+
+ a = g_simple_action_new("clear-notifications", NULL);
+ g_simple_action_set_enabled (a, FALSE);
+ g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a));
+ self->priv->pClearAction = a;
+ g_signal_connect(a, "activate", G_CALLBACK(onClear), self);
+
+ if (self->priv->bHasDoNotDisturb)
+ {
+ a = g_simple_action_new_stateful("do-not-disturb", G_VARIANT_TYPE_BOOLEAN, g_variant_new_boolean(self->priv->bDoNotDisturb));
+ g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), G_ACTION(a));
+ self->priv->pDoNotDisturbAction = a;
+ g_signal_connect(a, "activate", G_CALLBACK(onDoNotDisturb), self);
+ }
+
+ // Add the max-items action
+ max_items_action = g_settings_create_action(self->priv->pSettings, "max-items");
+ g_action_map_add_action(G_ACTION_MAP(self->priv->pActionGroup), max_items_action);
+
+ g_signal_connect(self->priv->pSettings, "changed", G_CALLBACK(onSettingsChanged), self);
+
+ rebuildNow(self, SECTION_HEADER);
+
+ g_object_unref(max_items_action);
+}
+
+static void onBusAcquired(GDBusConnection *connection, const gchar *name, gpointer gself)
+{
+ int i;
+ guint id;
+ GError * err = NULL;
+ IndicatorNotificationsService * self = INDICATOR_NOTIFICATIONS_SERVICE(gself);
+ priv_t * p = self->priv;
+ GString * path = g_string_new (NULL);
+
+ g_debug ("bus acquired: %s", name);
+
+ p->pConnection = (GDBusConnection*)g_object_ref(G_OBJECT (connection));
+
+ // Export the actions
+ if ((id = g_dbus_connection_export_action_group (connection, BUS_PATH, G_ACTION_GROUP (p->pActionGroup), &err)))
+ {
+ p->nActionsId = id;
+ }
+ else
+ {
+ g_warning ("cannot export action group: %s", err->message);
+ g_clear_error (&err);
+ }
+
+ // Export the menus
+ for (i=0; i<N_PROFILES; ++i)
+ {
+ struct ProfileMenuInfo * menu = &p->lMenus[i];
+
+ g_string_printf (path, "%s/%s", BUS_PATH, menu_names[i]);
+
+ if ((id = g_dbus_connection_export_menu_model (connection, path->str, G_MENU_MODEL (menu->pMenu), &err)))
+ {
+ menu->nExportId = id;
+ }
+ else
+ {
+ g_warning ("cannot export %s menu: %s", path->str, err->message);
+ g_clear_error (&err);
+ }
+ }
+
+ g_string_free (path, TRUE);
+}
+
+static void unexport(IndicatorNotificationsService *self)
+{
+ int i;
+ priv_t * p = self->priv;
+
+ // Unexport the menus
+ for (i=0; i<N_PROFILES; ++i)
+ {
+ guint * id = &self->priv->lMenus[i].nExportId;
+
+ if (*id)
+ {
+ g_dbus_connection_unexport_menu_model (p->pConnection, *id);
+ *id = 0;
+ }
+ }
+
+ // Unexport the actions
+ if (p->nActionsId)
+ {
+ g_dbus_connection_unexport_action_group (p->pConnection, p->nActionsId);
+ p->nActionsId = 0;
+ }
+}
+
+static void onNameLost(GDBusConnection *connection, const gchar *name, gpointer gself)
+{
+ IndicatorNotificationsService * self = INDICATOR_NOTIFICATIONS_SERVICE (gself);
+
+ g_debug("%s %s name lost %s", G_STRLOC, G_STRFUNC, name);
+
+ unexport(self);
+}
+
+static void onDispose(GObject *o)
+{
+ IndicatorNotificationsService * self = INDICATOR_NOTIFICATIONS_SERVICE(o);
+ priv_t * p = self->priv;
+
+ if (self->priv->lVisibleItems != NULL)
+ {
+ g_list_free_full(self->priv->lVisibleItems, g_object_unref);
+ self->priv->lVisibleItems = NULL;
+ }
+
+ if (self->priv->lHiddenItems != NULL)
+ {
+ g_list_free_full(self->priv->lHiddenItems, g_object_unref);
+ self->priv->lHiddenItems = NULL;
+ }
+
+ if (self->priv->pBusSpy != NULL)
+ {
+ g_object_unref(G_OBJECT(self->priv->pBusSpy));
+ self->priv->pBusSpy = NULL;
+ }
+
+ if(self->priv->lFilters != NULL)
+ {
+ g_hash_table_unref(self->priv->lFilters);
+ self->priv->lFilters = NULL;
+ }
+
+ if(self->priv->lHints != NULL)
+ {
+ g_list_free_full(self->priv->lHints, g_free);
+ self->priv->lHints = NULL;
+ }
+
+ if (p->nOwnId)
+ {
+ g_bus_unown_name (p->nOwnId);
+ p->nOwnId = 0;
+ }
+
+ unexport (self);
+
+ if (p->pCancellable != NULL)
+ {
+ g_cancellable_cancel (p->pCancellable);
+ g_clear_object (&p->pCancellable);
+ }
+
+ if (p->pSettings != NULL)
+ {
+ g_signal_handlers_disconnect_by_data (p->pSettings, self);
+ g_clear_object (&p->pSettings);
+ }
+
+ if (self->priv->bHasDoNotDisturb)
+ {
+ g_clear_object(&p->pDoNotDisturbAction);
+ }
+
+ g_clear_object (&p->pClearAction);
+ g_clear_object (&p->pRemoveAction);
+ g_clear_object (&p->pHeaderAction);
+ g_clear_object (&p->pActionGroup);
+ g_clear_object (&p->pConnection);
+
+ G_OBJECT_CLASS (indicator_notifications_service_parent_class)->dispose (o);
+}
+
+static void updateFilters(IndicatorNotificationsService *self)
+{
+ g_return_if_fail(self->priv->lFilters != NULL);
+
+ g_hash_table_remove_all(self->priv->lFilters);
+ gchar **items = g_settings_get_strv(self->priv->pSettings, "filter-list");
+ int i;
+
+ for(i = 0; items[i] != NULL; i++)
+ {
+ g_hash_table_insert(self->priv->lFilters, g_strdup(items[i]), NULL);
+ }
+
+ g_strfreev(items);
+}
+
+static void loadHints(IndicatorNotificationsService *self)
+{
+ g_return_if_fail(self->priv->lHints == NULL);
+
+ gchar **items = g_settings_get_strv(self->priv->pSettings, "filter-list-hints");
+ int i;
+
+ for (i = 0; items[i] != NULL; i++)
+ {
+ self->priv->lHints = g_list_prepend(self->priv->lHints, items[i]);
+ }
+
+ g_free(items);
+}
+
+static gboolean getDoNotDisturb()
+{
+ // Check if we can access the schema
+ GSettingsSchemaSource *source = g_settings_schema_source_get_default();
+
+ if (source == NULL)
+ {
+ return FALSE;
+ }
+
+ // Lookup the schema
+ GSettingsSchema *source_schema = g_settings_schema_source_lookup(source, "org.mate.NotificationDaemon", TRUE);
+
+ // Couldn't find the schema
+ if (source_schema == NULL)
+ {
+ return FALSE;
+ }
+
+ gboolean bResult = FALSE;
+
+ // Found the schema, make sure we have the key
+ if (g_settings_schema_has_key(source_schema, "do-not-disturb"))
+ {
+ // Make sure the key is of boolean type
+ GSettingsSchemaKey *source_key = g_settings_schema_get_key(source_schema, "do-not-disturb");
+
+ if (g_variant_type_equal(g_settings_schema_key_get_value_type(source_key), G_VARIANT_TYPE_BOOLEAN))
+ {
+ bResult = TRUE;
+ }
+
+ g_settings_schema_key_unref(source_key);
+ }
+
+ g_settings_schema_unref(source_schema);
+
+ return bResult;
+}
+
+static void indicator_notifications_service_init(IndicatorNotificationsService *self)
+{
+ int i;
+ self->priv = indicator_notifications_service_get_instance_private(self);
+ self->priv->pCancellable = g_cancellable_new();
+ self->priv->bHasDoNotDisturb = getDoNotDisturb();
+ self->priv->pSettings = g_settings_new("org.ayatana.indicator.notifications");
+ self->priv->bHasUnread = FALSE;
+ self->priv->lVisibleItems = NULL;
+ self->priv->lHiddenItems = NULL;
+
+ // Watch for notifications from dbus
+ self->priv->pBusSpy = dbus_spy_new();
+ g_signal_connect(self->priv->pBusSpy, DBUS_SPY_SIGNAL_MESSAGE_RECEIVED, G_CALLBACK(onMessageReceived), self);
+
+ // Initialize an empty filter list
+ self->priv->lFilters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+ self->priv->nMaxItems = g_settings_get_int(self->priv->pSettings, "max-items");
+
+ if (self->priv->bHasDoNotDisturb)
+ {
+ self->priv->bDoNotDisturb = g_settings_get_boolean(self->priv->pSettings, "do-not-disturb");
+ }
+
+ updateFilters(self);
+
+ // Set up filter-list hints
+ self->priv->lHints = NULL;
+ loadHints(self);
+
+ initActions(self);
+
+ for (i=0; i<N_PROFILES; ++i)
+ {
+ createMenu(self, i);
+ }
+
+ self->priv->bMenusBuilt = TRUE;
+ self->priv->nOwnId = g_bus_own_name(G_BUS_TYPE_SESSION, BUS_NAME, G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT, onBusAcquired, NULL, onNameLost, self, NULL);
+}
+
+static void indicator_notifications_service_class_init(IndicatorNotificationsServiceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+ object_class->dispose = onDispose;
+ m_nSignal = g_signal_new(INDICATOR_NOTIFICATIONS_SERVICE_SIGNAL_NAME_LOST, G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (IndicatorNotificationsServiceClass, name_lost), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
+}
+
+IndicatorNotificationsService *indicator_notifications_service_new()
+{
+ GObject *o = g_object_new(INDICATOR_TYPE_NOTIFICATIONS_SERVICE, NULL);
+
+ return INDICATOR_NOTIFICATIONS_SERVICE(o);
+}
diff --git a/src/service.h b/src/service.h
new file mode 100644
index 0000000..45621ca
--- /dev/null
+++ b/src/service.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2013 Canonical Ltd.
+ *
+ * Authors:
+ * Charles Kerr <charles.kerr@canonical.com>
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 3, as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranties of
+ * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __INDICATOR_NOTIFICATIONS_SERVICE_H__
+#define __INDICATOR_NOTIFICATIONS_SERVICE_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* standard GObject macros */
+#define INDICATOR_NOTIFICATIONS_SERVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), INDICATOR_TYPE_NOTIFICATIONS_SERVICE, IndicatorNotificationsService))
+#define INDICATOR_TYPE_NOTIFICATIONS_SERVICE (indicator_notifications_service_get_type())
+#define INDICATOR_IS_NOTIFICATIONS_SERVICE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), INDICATOR_TYPE_NOTIFICATIONS_SERVICE))
+
+typedef struct _IndicatorNotificationsService IndicatorNotificationsService;
+typedef struct _IndicatorNotificationsServiceClass IndicatorNotificationsServiceClass;
+typedef struct _IndicatorNotificationsServicePrivate IndicatorNotificationsServicePrivate;
+
+/* signal keys */
+#define INDICATOR_NOTIFICATIONS_SERVICE_SIGNAL_NAME_LOST "name-lost"
+
+/**
+ * The Indicator Notifications Service.
+ */
+struct _IndicatorNotificationsService
+{
+ /*< private >*/
+ GObject parent;
+ IndicatorNotificationsServicePrivate * priv;
+};
+
+struct _IndicatorNotificationsServiceClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+
+ void (* name_lost)(IndicatorNotificationsService * self);
+};
+
+GType indicator_notifications_service_get_type (void);
+
+IndicatorNotificationsService *indicator_notifications_service_new();
+
+G_END_DECLS
+
+#endif /* __INDICATOR_NOTIFICATIONS_SERVICE_H__ */
diff --git a/src/settings.h b/src/settings.h
deleted file mode 100644
index 9663c8d..0000000
--- a/src/settings.h
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * settings.h - Definitions of settings keys and schema.
- */
-
-#ifndef __SETTINGS_H__
-#define __SETTINGS_H__
-
-#define NOTIFICATIONS_SCHEMA "org.ayatana.indicator.notifications"
-#define NOTIFICATIONS_KEY_FILTER_LIST "filter-list"
-#define NOTIFICATIONS_KEY_FILTER_LIST_HINTS "filter-list-hints"
-#define NOTIFICATIONS_KEY_CLEAR_MC "clear-on-middle-click"
-#define NOTIFICATIONS_KEY_DND "do-not-disturb"
-#define NOTIFICATIONS_KEY_HIDE_INDICATOR "hide-indicator"
-#define NOTIFICATIONS_KEY_MAX_ITEMS "max-items"
-#define NOTIFICATIONS_KEY_SWAP_CLEAR_SETTINGS "swap-clear-settings"
-
-#define MATE_SCHEMA "org.mate.NotificationDaemon"
-#define MATE_KEY_DND "do-not-disturb"
-
-#endif