diff options
author | Ted Gould <ted@canonical.com> | 2009-09-04 09:11:59 -0500 |
---|---|---|
committer | Ted Gould <ted@canonical.com> | 2009-09-04 09:11:59 -0500 |
commit | 506e943af17db275f1c83c7a4726d2d15897a09e (patch) | |
tree | 16028144fa8f3bddb4a176ed093fc093ecabd654 /src/messages-service.c | |
parent | 10327267daa9e4ba36ace96f637beaa9045e0895 (diff) | |
parent | a04a5e11668a7068f1307b28911df1097ab983cf (diff) | |
download | ayatana-indicator-messages-506e943af17db275f1c83c7a4726d2d15897a09e.tar.gz ayatana-indicator-messages-506e943af17db275f1c83c7a4726d2d15897a09e.tar.bz2 ayatana-indicator-messages-506e943af17db275f1c83c7a4726d2d15897a09e.zip |
Merging in the dbusmenu011 branch. I think I got other trunk updates as well.
Diffstat (limited to 'src/messages-service.c')
-rw-r--r-- | src/messages-service.c | 1065 |
1 files changed, 1065 insertions, 0 deletions
diff --git a/src/messages-service.c b/src/messages-service.c new file mode 100644 index 0000000..754021b --- /dev/null +++ b/src/messages-service.c @@ -0,0 +1,1065 @@ +/* +An indicator to show information that is in messaging applications +that the user is using. + +Copyright 2009 Canonical Ltd. + +Authors: + 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 <string.h> +#include <pango/pango-utils.h> +#include <dbus/dbus-glib-bindings.h> +#include <libindicate/listener.h> +#include <gio/gio.h> + +#include <libdbusmenu-glib/server.h> + +#include "im-menu-item.h" +#include "app-menu-item.h" +#include "launcher-menu-item.h" +#include "dbus-data.h" +#include "dirs.h" +#include "messages-service-dbus.h" + +static IndicateListener * listener; +static GList * serverList = NULL; +static GList * launcherList = NULL; + +static DbusmenuMenuitem * root_menuitem = NULL; +static GMainLoop * mainloop = NULL; + +static MessageServiceDbus * dbus_interface = NULL; + + +static void server_count_changed (AppMenuItem * appitem, guint count, gpointer data); +static void server_name_changed (AppMenuItem * appitem, gchar * name, gpointer data); +static void im_time_changed (ImMenuItem * imitem, glong seconds, gpointer data); +static void resort_menu (DbusmenuMenuitem * menushell); +static void indicator_removed (IndicateListener * listener, IndicateListenerServer * server, IndicateListenerIndicator * indicator, gchar * type, gpointer data); +static void check_eclipses (AppMenuItem * ai); +static void remove_eclipses (AppMenuItem * ai); +static gboolean build_launcher (gpointer data); +static gboolean build_launchers (gpointer data); +static gboolean blacklist_init (gpointer data); +static gboolean blacklist_add (gpointer data); +static gboolean blacklist_remove (gpointer data); +static void blacklist_dir_changed (GFileMonitor * monitor, GFile * file, GFile * other_file, GFileMonitorEvent event_type, gpointer user_data); +static void app_dir_changed (GFileMonitor * monitor, GFile * file, GFile * other_file, GFileMonitorEvent event_type, gpointer user_data); +static gboolean destroy_launcher (gpointer data); +static void check_hidden (void); + + +/* + * Server List + */ + +typedef struct _serverList_t serverList_t; +struct _serverList_t { + IndicateListenerServer * server; + AppMenuItem * menuitem; + GList * imList; +}; + +static gint +serverList_equal (gconstpointer a, gconstpointer b) +{ + serverList_t * pa, * pb; + + pa = (serverList_t *)a; + pb = (serverList_t *)b; + + const gchar * pas = INDICATE_LISTENER_SERVER_DBUS_NAME(pa->server); + const gchar * pbs = INDICATE_LISTENER_SERVER_DBUS_NAME(pb->server); + + return g_strcmp0(pas, pbs); +} + +static gint +serverList_sort (gconstpointer a, gconstpointer b) +{ + serverList_t * pa, * pb; + + pa = (serverList_t *)a; + pb = (serverList_t *)b; + + const gchar * pan = app_menu_item_get_name(pa->menuitem); + const gchar * pbn = app_menu_item_get_name(pb->menuitem); + + return g_strcmp0(pan, pbn); +} + +/* + * Item List + */ + +typedef struct _imList_t imList_t; +struct _imList_t { + IndicateListenerServer * server; + IndicateListenerIndicator * indicator; + DbusmenuMenuitem * menuitem; + gulong timechange_cb; +}; + +static gboolean +imList_equal (gconstpointer a, gconstpointer b) +{ + imList_t * pa, * pb; + + pa = (imList_t *)a; + pb = (imList_t *)b; + + const gchar * pas = INDICATE_LISTENER_SERVER_DBUS_NAME(pa->server); + const gchar * pbs = INDICATE_LISTENER_SERVER_DBUS_NAME(pb->server); + + guint pai = INDICATE_LISTENER_INDICATOR_ID(pa->indicator); + guint pbi = INDICATE_LISTENER_INDICATOR_ID(pb->indicator); + + g_debug("\tComparing (%s %d) to (%s %d)", pas, pai, pbs, pbi); + + return !((!g_strcmp0(pas, pbs)) && (pai == pbi)); +} + +static gint +imList_sort (gconstpointer a, gconstpointer b) +{ + imList_t * pa, * pb; + + pa = (imList_t *)a; + pb = (imList_t *)b; + + return (gint)(im_menu_item_get_seconds(IM_MENU_ITEM(pb->menuitem)) - im_menu_item_get_seconds(IM_MENU_ITEM(pa->menuitem))); +} + +/* + * Launcher List + */ + +typedef struct _launcherList_t launcherList_t; +struct _launcherList_t { + LauncherMenuItem * menuitem; + GList * appdiritems; +}; + +static gint +launcherList_sort (gconstpointer a, gconstpointer b) +{ + launcherList_t * pa, * pb; + + pa = (launcherList_t *)a; + pb = (launcherList_t *)b; + + const gchar * pan = launcher_menu_item_get_name(pa->menuitem); + const gchar * pbn = launcher_menu_item_get_name(pb->menuitem); + + return g_strcmp0(pan, pbn); +} + +static void +launcherList_count_helper (gpointer data, gpointer user_data) +{ + guint * count = (guint *)user_data; + launcherList_t * li = (launcherList_t *)data; + + if (!launcher_menu_item_get_eclipsed(li->menuitem)) { + *count = *count + 1; + } + + return; +} + +static guint +launcherList_count (void) +{ + guint count = 0; + + g_list_foreach(launcherList, launcherList_count_helper, &count); + + return count; +} + +/* + * Black List + */ + +static GHashTable * blacklist = NULL; +static GFileMonitor * blacklistdirmon = NULL; + +/* Initialize the black list and start to setup + handlers for it. */ +static gboolean +blacklist_init (gpointer data) +{ + blacklist = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, g_free); + + gchar * blacklistdir = g_build_filename(g_get_user_config_dir(), USER_BLACKLIST_DIR, NULL); + g_debug("Looking at blacklist: %s", blacklistdir); + if (!g_file_test(blacklistdir, G_FILE_TEST_IS_DIR)) { + g_free(blacklistdir); + return FALSE; + } + + GFile * filedir = g_file_new_for_path(blacklistdir); + blacklistdirmon = g_file_monitor_directory(filedir, G_FILE_MONITOR_NONE, NULL, NULL); + if (blacklistdirmon != NULL) { + g_signal_connect(G_OBJECT(blacklistdirmon), "changed", G_CALLBACK(blacklist_dir_changed), NULL); + } + + GError * error = NULL; + GDir * dir = g_dir_open(blacklistdir, 0, &error); + if (dir == NULL) { + g_warning("Unable to open blacklist directory (%s): %s", blacklistdir, error == NULL ? "No Message" : error->message); + g_error_free(error); + g_free(blacklistdir); + return FALSE; + } + + const gchar * filename = NULL; + while ((filename = g_dir_read_name(dir)) != NULL) { + g_debug("Found file: %s", filename); + gchar * path = g_build_filename(blacklistdir, filename, NULL); + g_idle_add(blacklist_add, path); + } + + g_dir_close(dir); + g_free(blacklistdir); + + return FALSE; +} + +/* Add a definition file into the black list and eclipse + and launchers that have the same file. */ +static gboolean +blacklist_add (gpointer udata) +{ + gchar * definition_file = (gchar *)udata; + /* Dump the file */ + gchar * desktop; + g_file_get_contents(definition_file, &desktop, NULL, NULL); + if (desktop == NULL) { + g_warning("Couldn't get data out of: %s", definition_file); + return FALSE; + } + + /* Clean up the data */ + gchar * trimdesktop = pango_trim_string(desktop); + g_free(desktop); + + /* Check for conflicts */ + gpointer data = g_hash_table_lookup(blacklist, trimdesktop); + if (data != NULL) { + gchar * oldfile = (gchar *)data; + if (!g_strcmp0(oldfile, definition_file)) { + g_warning("Already added file '%s'", oldfile); + } else { + g_warning("Already have desktop file '%s' in blacklist file '%s' not adding from '%s'", trimdesktop, oldfile, definition_file); + } + + g_free(trimdesktop); + g_free(definition_file); + return FALSE; + } + + /* Actually blacklist this thing */ + g_hash_table_insert(blacklist, trimdesktop, definition_file); + g_debug("Adding Blacklist item '%s' for desktop '%s'", definition_file, trimdesktop); + + /* Go through and eclipse folks */ + GList * launcher; + for (launcher = launcherList; launcher != NULL; launcher = launcher->next) { + launcherList_t * item = (launcherList_t *)launcher->data; + if (!g_strcmp0(trimdesktop, launcher_menu_item_get_desktop(item->menuitem))) { + launcher_menu_item_set_eclipsed(item->menuitem, TRUE); + } + } + + check_hidden(); + + return FALSE; +} + +/* Remove a black list item based on the definition file + and uneclipse those launchers blocked by it. */ +static gboolean +blacklist_remove (gpointer data) +{ + gchar * definition_file = (gchar *)data; + g_debug("Removing: %s", definition_file); + + GHashTableIter iter; + gpointer key, value; + gboolean found = FALSE; + + g_hash_table_iter_init(&iter, blacklist); + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (!g_strcmp0((gchar *)value, definition_file)) { + found = TRUE; + break; + } + } + + if (!found) { + g_debug("\tNot found!"); + return FALSE; + } + + GList * launcheritem; + for (launcheritem = launcherList; launcheritem != NULL; launcheritem = launcheritem->next) { + launcherList_t * li = (launcherList_t *)launcheritem->data; + if (!g_strcmp0(launcher_menu_item_get_desktop(li->menuitem), (gchar *)key)) { + GList * serveritem; + for (serveritem = serverList; serveritem != NULL; serveritem = serveritem->next) { + serverList_t * si = (serverList_t *)serveritem->data; + if (!g_strcmp0(app_menu_item_get_desktop(si->menuitem), (gchar *)key)) { + break; + } + } + if (serveritem == NULL) { + launcher_menu_item_set_eclipsed(li->menuitem, FALSE); + } + } + } + + if (!g_hash_table_remove(blacklist, key)) { + g_warning("Unable to remove '%s' with value '%s'", definition_file, (gchar *)key); + } + + check_hidden(); + + return FALSE; +} + +/* Check to see if a particular desktop file is + in the blacklist. */ +static gboolean +blacklist_check (const gchar * desktop_file) +{ + g_debug("Checking blacklist for: %s", desktop_file); + if (blacklist == NULL) return FALSE; + + if (g_hash_table_lookup(blacklist, desktop_file)) { + g_debug("\tFound!"); + return TRUE; + } + + return FALSE; +} + +/* A callback everytime the blacklist directory changes + in some way. It needs to handle that. */ +static void +blacklist_dir_changed (GFileMonitor * monitor, GFile * file, GFile * other_file, GFileMonitorEvent event_type, gpointer user_data) +{ + g_debug("Blacklist directory changed!"); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: { + gchar * path = g_file_get_path(file); + g_debug("\tDelete: %s", path); + g_idle_add(blacklist_remove, path); + break; + } + case G_FILE_MONITOR_EVENT_CREATED: { + gchar * path = g_file_get_path(file); + g_debug("\tCreate: %s", path); + g_idle_add(blacklist_add, path); + break; + } + default: + break; + } + + return; +} + +/* + * More code + */ + +static void +server_added (IndicateListener * listener, IndicateListenerServer * server, gchar * type, gpointer data) +{ + g_debug("Server Added '%s' of type '%s'.", INDICATE_LISTENER_SERVER_DBUS_NAME(server), type); + if (type == NULL) { + return; + } + + if (type[0] == '\0') { + return; + } + + if (strncmp(type, "message", strlen("message"))) { + g_debug("\tServer type '%s' is not a message based type.", type); + return; + } + + DbusmenuMenuitem * menushell = DBUSMENU_MENUITEM(data); + if (menushell == NULL) { + g_error("\tData in callback is not a menushell"); + return; + } + + AppMenuItem * menuitem = app_menu_item_new(listener, server); + g_signal_connect(G_OBJECT(menuitem), APP_MENU_ITEM_SIGNAL_COUNT_CHANGED, G_CALLBACK(server_count_changed), NULL); + g_signal_connect(G_OBJECT(menuitem), APP_MENU_ITEM_SIGNAL_NAME_CHANGED, G_CALLBACK(server_name_changed), menushell); + + serverList_t * sl_item = g_new0(serverList_t, 1); + sl_item->server = server; + sl_item->menuitem = menuitem; + sl_item->imList = NULL; + + /* Incase we got an indicator first */ + GList * alreadythere = g_list_find_custom(serverList, sl_item, serverList_equal); + if (alreadythere != NULL) { + g_free(sl_item); + sl_item = (serverList_t *)alreadythere->data; + sl_item->menuitem = menuitem; + serverList = g_list_sort(serverList, serverList_sort); + } else { + serverList = g_list_insert_sorted(serverList, sl_item, serverList_sort); + } + + dbusmenu_menuitem_child_append(menushell, DBUSMENU_MENUITEM(menuitem)); + /* Should be prepend ^ */ + + resort_menu(menushell); + check_hidden(); + + return; +} + +static void +server_name_changed (AppMenuItem * appitem, gchar * name, gpointer data) +{ + serverList = g_list_sort(serverList, serverList_sort); + check_eclipses(appitem); + resort_menu(DBUSMENU_MENUITEM(data)); + return; +} + +static void +server_count_changed (AppMenuItem * appitem, guint count, gpointer data) +{ + static gboolean showing_new_icon = FALSE; + + /* Quick check for a common case */ + if (count != 0 && showing_new_icon) { + return; + } + + /* Odd that we'd get a signal in this case, but let's + take it out of the mix too */ + if (count == 0 && !showing_new_icon) { + return; + } + + if (count != 0) { + g_debug("Setting image to 'new'"); + showing_new_icon = TRUE; + message_service_dbus_set_attention(dbus_interface, TRUE); + return; + } + + /* Okay, now at this point the count is zero and it + might result in a switching of the icon back to being + the plain one. Let's check. */ + + gboolean we_have_indicators = FALSE; + GList * appitems = serverList; + for (; appitems != NULL; appitems = appitems->next) { + AppMenuItem * appitem = ((serverList_t *)appitems->data)->menuitem; + if (app_menu_item_get_count(appitem) != 0) { + we_have_indicators = TRUE; + break; + } + } + + if (!we_have_indicators) { + g_debug("Setting image to boring"); + showing_new_icon = FALSE; + message_service_dbus_set_attention(dbus_interface, FALSE); + } + + return; +} + +static void +im_time_changed (ImMenuItem * imitem, glong seconds, gpointer data) +{ + serverList_t * sl = (serverList_t *)data; + sl->imList = g_list_sort(sl->imList, imList_sort); + resort_menu(root_menuitem); + return; +} + +static void +server_removed (IndicateListener * listener, IndicateListenerServer * server, gchar * type, gpointer data) +{ + g_debug("Removing server: %s", INDICATE_LISTENER_SERVER_DBUS_NAME(server)); + serverList_t slt; + slt.server = server; + GList * lookup = g_list_find_custom(serverList, &slt, serverList_equal); + + if (lookup == NULL) { + g_debug("\tUnable to find server: %s", INDICATE_LISTENER_SERVER_DBUS_NAME(server)); + return; + } + + serverList_t * sltp = (serverList_t *)lookup->data; + + remove_eclipses(sltp->menuitem); + + while (sltp->imList) { + imList_t * imitem = (imList_t *)sltp->imList->data; + indicator_removed(listener, server, imitem->indicator, "message", data); + } + + serverList = g_list_remove(serverList, sltp); + + if (sltp->menuitem != NULL) { + dbusmenu_menuitem_property_set(DBUSMENU_MENUITEM(sltp->menuitem), DBUSMENU_MENUITEM_PROP_VISIBLE, "false"); + dbusmenu_menuitem_child_delete(DBUSMENU_MENUITEM(data), DBUSMENU_MENUITEM(sltp->menuitem)); + g_object_unref(G_OBJECT(sltp->menuitem)); + } + + g_free(sltp); + + /* Simulate a server saying zero to recalculate icon */ + server_count_changed(NULL, 0, NULL); + check_hidden(); + + return; +} + +typedef struct _menushell_location menushell_location_t; +struct _menushell_location { + const IndicateListenerServer * server; + gint position; + gboolean found; +}; + +static void +menushell_foreach_cb (DbusmenuMenuitem * data_mi, gpointer data_ms) { + menushell_location_t * msl = (menushell_location_t *)data_ms; + + if (msl->found) return; + + msl->position++; + + if (!IS_APP_MENU_ITEM(data_mi)) { + return; + } + + AppMenuItem * appmenu = APP_MENU_ITEM(data_mi); + if (!g_strcmp0(INDICATE_LISTENER_SERVER_DBUS_NAME((IndicateListenerServer*)msl->server), INDICATE_LISTENER_SERVER_DBUS_NAME(app_menu_item_get_server(appmenu)))) { + msl->found = TRUE; + } + + return; +} + +static void +check_hidden (void) +{ + g_debug("Checking Hidden..."); + gboolean hide = FALSE; + if (launcherList_count() == 0) { + g_debug("\tZero Launchers"); + /* If we don't have visible launchers we need to look more */ + if (g_list_length(serverList) == 0) { + g_debug("\tZero Applications"); + hide = TRUE; + } + } + + message_service_dbus_set_icon(dbus_interface, hide); + return; +} + +static void +resort_menu (DbusmenuMenuitem * menushell) +{ + guint position = 0; + GList * serverentry; + GList * launcherentry = launcherList; + + g_debug("Reordering Menu:"); + + for (serverentry = serverList; serverentry != NULL; serverentry = serverentry->next) { + serverList_t * si = (serverList_t *)serverentry->data; + + if (launcherentry != NULL) { + launcherList_t * li = (launcherList_t *)launcherentry->data; + while (launcherentry != NULL && g_strcmp0(launcher_menu_item_get_name(li->menuitem), app_menu_item_get_name(si->menuitem)) < 0) { + g_debug("\tMoving launcher '%s' to position %d", launcher_menu_item_get_name(li->menuitem), position); + dbusmenu_menuitem_child_reorder(DBUSMENU_MENUITEM(menushell), DBUSMENU_MENUITEM(li->menuitem), position); + + position++; + launcherentry = launcherentry->next; + if (launcherentry != NULL) { + li = (launcherList_t *)launcherentry->data; + } + } + } + + if (si->menuitem != NULL) { + g_debug("\tMoving app %s to position %d", INDICATE_LISTENER_SERVER_DBUS_NAME(si->server), position); + dbusmenu_menuitem_child_reorder(DBUSMENU_MENUITEM(menushell), DBUSMENU_MENUITEM(si->menuitem), position); + position++; + } + + GList * imentry; + for (imentry = si->imList; imentry != NULL; imentry = imentry->next) { + imList_t * imi = (imList_t *)imentry->data; + + if (imi->menuitem != NULL) { + g_debug("\tMoving indicator on %s id %d to position %d", INDICATE_LISTENER_SERVER_DBUS_NAME(imi->server), INDICATE_LISTENER_INDICATOR_ID(imi->indicator), position); + dbusmenu_menuitem_child_reorder(DBUSMENU_MENUITEM(menushell), DBUSMENU_MENUITEM(imi->menuitem), position); + position++; + } + } + } + + while (launcherentry != NULL) { + launcherList_t * li = (launcherList_t *)launcherentry->data; + g_debug("\tMoving launcher '%s' to position %d", launcher_menu_item_get_name(li->menuitem), position); + dbusmenu_menuitem_child_reorder(DBUSMENU_MENUITEM(menushell), DBUSMENU_MENUITEM(li->menuitem), position); + + position++; + launcherentry = launcherentry->next; + } + + return; +} + +static void +subtype_cb (IndicateListener * listener, IndicateListenerServer * server, IndicateListenerIndicator * indicator, gchar * property, gchar * propertydata, gpointer data) +{ + DbusmenuMenuitem * menushell = DBUSMENU_MENUITEM(data); + if (menushell == NULL) { + g_error("Data in callback is not a menushell"); + return; + } + + if (property == NULL || g_strcmp0(property, "subtype")) { + /* We should only ever get subtypes, but just in case */ + g_warning("Subtype callback got a property '%s'", property); + return; + } + + if (propertydata == NULL || propertydata[0] == '\0') { + /* It's possible that this message didn't have a subtype. That's + * okay, but we don't want to display those */ + g_debug("No subtype"); + return; + } + + g_debug("Message subtype: %s", propertydata); + + if (!g_strcmp0(propertydata, "im") || !g_strcmp0(propertydata, "login")) { + imList_t * listItem = g_new0(imList_t, 1); + listItem->server = server; + listItem->indicator = indicator; + + g_debug("Building IM Item"); + ImMenuItem * menuitem = im_menu_item_new(listener, server, indicator, !g_strcmp0(propertydata, "im")); + g_object_ref(G_OBJECT(menuitem)); + listItem->menuitem = DBUSMENU_MENUITEM(menuitem); + + g_debug("Finding the server entry"); + serverList_t sl_item_local; + serverList_t * sl_item = NULL; + sl_item_local.server = server; + GList * serverentry = g_list_find_custom(serverList, &sl_item_local, serverList_equal); + + if (serverentry == NULL) { + /* This sucks, we got an indicator before the server. I guess + that's the joy of being asynchronous */ + serverList_t * sl_item = g_new0(serverList_t, 1); + sl_item->server = server; + sl_item->menuitem = NULL; + sl_item->imList = NULL; + + serverList = g_list_insert_sorted(serverList, sl_item, serverList_sort); + } else { + sl_item = (serverList_t *)serverentry->data; + } + + g_debug("Adding to IM List"); + sl_item->imList = g_list_insert_sorted(sl_item->imList, listItem, imList_sort); + listItem->timechange_cb = g_signal_connect(G_OBJECT(menuitem), IM_MENU_ITEM_SIGNAL_TIME_CHANGED, G_CALLBACK(im_time_changed), sl_item); + + g_debug("Placing in Shell"); + menushell_location_t msl; + msl.found = FALSE; + msl.position = 0; + msl.server = server; + + dbusmenu_menuitem_foreach(DBUSMENU_MENUITEM(menushell), menushell_foreach_cb, &msl); + if (msl.found) { + dbusmenu_menuitem_child_add_position(menushell, DBUSMENU_MENUITEM(menuitem), msl.position); + } else { + g_warning("Unable to find server menu item"); + dbusmenu_menuitem_child_append(menushell, DBUSMENU_MENUITEM(menuitem)); + } + } + + return; +} + +static void +indicator_added (IndicateListener * listener, IndicateListenerServer * server, IndicateListenerIndicator * indicator, gchar * type, gpointer data) +{ + if (type == NULL || g_strcmp0(type, "message")) { + /* We only care about message type indicators + all of the others can go to the bit bucket */ + g_debug("Ignoreing indicator of type '%s'", type); + return; + } + g_debug("Got a message"); + + indicate_listener_get_property(listener, server, indicator, "subtype", subtype_cb, data); + return; +} + +static void +indicator_removed (IndicateListener * listener, IndicateListenerServer * server, IndicateListenerIndicator * indicator, gchar * type, gpointer data) +{ + g_debug("Removing %s %d", INDICATE_LISTENER_SERVER_DBUS_NAME(server), INDICATE_LISTENER_INDICATOR_ID(indicator)); + if (type == NULL || g_strcmp0(type, "message")) { + /* We only care about message type indicators + all of the others can go to the bit bucket */ + g_debug("Ignoreing indicator of type '%s'", type); + return; + } + + gboolean removed = FALSE; + + serverList_t sl_item_local; + serverList_t * sl_item = NULL; + sl_item_local.server = server; + GList * serverentry = g_list_find_custom(serverList, &sl_item_local, serverList_equal); + if (serverentry == NULL) { + return; + } + sl_item = (serverList_t *)serverentry->data; + + /* Look in the IM Hash Table */ + imList_t listData; + listData.server = server; + listData.indicator = indicator; + + GList * listItem = g_list_find_custom(sl_item->imList, &listData, imList_equal); + DbusmenuMenuitem * menuitem = NULL; + imList_t * ilt = NULL; + if (listItem != NULL) { + ilt = (imList_t *)listItem->data; + menuitem = ilt->menuitem; + } + + if (!removed && menuitem != NULL) { + sl_item->imList = g_list_remove(sl_item->imList, ilt); + g_signal_handler_disconnect(menuitem, ilt->timechange_cb); + g_free(ilt); + + dbusmenu_menuitem_property_set(menuitem, DBUSMENU_MENUITEM_PROP_VISIBLE, "false"); + dbusmenu_menuitem_child_delete(DBUSMENU_MENUITEM(data), menuitem); + removed = TRUE; + } + + if (!removed) { + g_warning("We were asked to remove %s %d but we didn't.", INDICATE_LISTENER_SERVER_DBUS_NAME(server), INDICATE_LISTENER_INDICATOR_ID(indicator)); + } + + return; +} + +static void +app_dir_changed (GFileMonitor * monitor, GFile * file, GFile * other_file, GFileMonitorEvent event_type, gpointer user_data) +{ + gchar * directory = (gchar *)user_data; + g_debug("Application directory changed: %s", directory); + + switch (event_type) { + case G_FILE_MONITOR_EVENT_DELETED: { + gchar * path = g_file_get_path(file); + g_debug("\tDelete: %s", path); + g_idle_add(destroy_launcher, path); + break; + } + case G_FILE_MONITOR_EVENT_CREATED: { + gchar * path = g_file_get_path(file); + g_debug("\tCreate: %s", path); + g_idle_add(build_launcher, path); + break; + } + default: + break; + } + + return; +} + +/* Check to see if a new desktop file causes + any of the launchers to be eclipsed by a running + process */ +static void +check_eclipses (AppMenuItem * ai) +{ + g_debug("Checking eclipsing"); + const gchar * aidesktop = app_menu_item_get_desktop(ai); + if (aidesktop == NULL) return; + g_debug("\tApp desktop: %s", aidesktop); + + GList * llitem; + for (llitem = launcherList; llitem != NULL; llitem = llitem->next) { + launcherList_t * ll = (launcherList_t *)llitem->data; + const gchar * lidesktop = launcher_menu_item_get_desktop(ll->menuitem); + g_debug("\tLauncher desktop: %s", lidesktop); + + if (!g_strcmp0(aidesktop, lidesktop)) { + launcher_menu_item_set_eclipsed(ll->menuitem, TRUE); + break; + } + } + + return; +} + +/* Remove any eclipses that might have been caused + by this app item that is now retiring */ +static void +remove_eclipses (AppMenuItem * ai) +{ + const gchar * aidesktop = app_menu_item_get_desktop(ai); + if (aidesktop == NULL) return; + + if (blacklist_check(aidesktop)) return; + + GList * llitem; + for (llitem = launcherList; llitem != NULL; llitem = llitem->next) { + launcherList_t * ll = (launcherList_t *)llitem->data; + const gchar * lidesktop = launcher_menu_item_get_desktop(ll->menuitem); + + if (!g_strcmp0(aidesktop, lidesktop)) { + launcher_menu_item_set_eclipsed(ll->menuitem, FALSE); + break; + } + } + + return; +} + +/* Remove a launcher from the system. We need to figure + out what it's up to! */ +static gboolean +destroy_launcher (gpointer data) +{ + gchar * appdirentry = (gchar *)data; + + GList * listitem; + GList * direntry; + launcherList_t * li; + gchar * appdir; + + for (listitem = launcherList; listitem != NULL; listitem = listitem->next) { + li = (launcherList_t *)listitem->data; + for (direntry = li->appdiritems; direntry != NULL; direntry = direntry->next) { + appdir = (gchar *)direntry->data; + if (!g_strcmp0(appdir, appdirentry)) { + break; + } + } + + if (direntry != NULL) { + break; + } + } + + if (listitem == NULL) { + g_warning("Removed '%s' by the way of it not seeming to exist anywhere.", appdirentry); + return FALSE; + } + + if (g_list_length(li->appdiritems) > 1) { + /* Just remove this item, and we can move on */ + g_debug("Just removing file entry: %s", appdir); + li->appdiritems = g_list_remove(li->appdiritems, appdir); + g_free(appdir); + return FALSE; + } + + /* Full Destroy */ + g_free(appdir); + g_list_free(li->appdiritems); + + if (li->menuitem != NULL) { + dbusmenu_menuitem_property_set(DBUSMENU_MENUITEM(li->menuitem), DBUSMENU_MENUITEM_PROP_VISIBLE, "false"); + dbusmenu_menuitem_child_delete(root_menuitem, DBUSMENU_MENUITEM(li->menuitem)); + g_object_unref(G_OBJECT(li->menuitem)); + li->menuitem = NULL; + } + + launcherList = g_list_remove(launcherList, li); + g_free(li); + + return FALSE; +} + +/* This function turns a specific file into a menu + item and registers it appropriately with everyone */ +static gboolean +build_launcher (gpointer data) +{ + /* Read the file get the data */ + gchar * path = (gchar *)data; + g_debug("\tpath: %s", path); + gchar * desktop = NULL; + + g_file_get_contents(path, &desktop, NULL, NULL); + + if (desktop == NULL) { + return FALSE; + } + + gchar * trimdesktop = pango_trim_string(desktop); + g_free(desktop); + g_debug("\tcontents: %s", trimdesktop); + + /* Check to see if we already have a launcher */ + GList * listitem; + for (listitem = launcherList; listitem != NULL; listitem = listitem->next) { + launcherList_t * li = (launcherList_t *)listitem->data; + if (!g_strcmp0(launcher_menu_item_get_desktop(li->menuitem), trimdesktop)) { + break; + } + } + + if (listitem == NULL) { + /* If not */ + /* Build the item */ + launcherList_t * ll = g_new0(launcherList_t, 1); + ll->menuitem = launcher_menu_item_new(trimdesktop); + g_free(trimdesktop); + ll->appdiritems = g_list_append(NULL, path); + + /* Add it to the list */ + launcherList = g_list_insert_sorted(launcherList, ll, launcherList_sort); + + /* Add it to the menu */ + dbusmenu_menuitem_child_append(root_menuitem, DBUSMENU_MENUITEM(ll->menuitem)); + resort_menu(root_menuitem); + + if (blacklist_check(launcher_menu_item_get_desktop(ll->menuitem))) { + launcher_menu_item_set_eclipsed(ll->menuitem, TRUE); + } + } else { + /* If so add ourselves */ + launcherList_t * ll = (launcherList_t *)listitem->data; + ll->appdiritems = g_list_append(ll->appdiritems, path); + } + + return FALSE; +} + +/* This function goes through all the launchers that we're + supposed to be grabbing and decides to show turn them + into menu items or not. It doens't do the work, but it + makes the decision. */ +static gboolean +build_launchers (gpointer data) +{ + gchar * directory = (gchar *)data; + + if (!g_file_test(directory, G_FILE_TEST_IS_DIR)) { + return FALSE; + } + + GFile * filedir = g_file_new_for_path(directory); + GFileMonitor * dirmon = g_file_monitor_directory(filedir, G_FILE_MONITOR_NONE, NULL, NULL); + if (dirmon != NULL) { + g_signal_connect(G_OBJECT(dirmon), "changed", G_CALLBACK(app_dir_changed), directory); + } + + GError * error = NULL; + GDir * dir = g_dir_open(directory, 0, &error); + if (dir == NULL) { + g_warning("Unable to open system apps directory: %s", error->message); + g_error_free(error); + return FALSE; + } + + const gchar * filename = NULL; + while ((filename = g_dir_read_name(dir)) != NULL) { + g_debug("Found file: %s", filename); + gchar * path = g_build_filename(directory, filename, NULL); + g_idle_add(build_launcher, path); + } + + g_dir_close(dir); + launcherList = g_list_sort(launcherList, launcherList_sort); + return FALSE; +} + +/* Oh, if you don't know what main() is for + we really shouldn't be talking. */ +int +main (int argc, char ** argv) +{ + g_type_init(); + + DBusGConnection * connection = dbus_g_bus_get(DBUS_BUS_SESSION, NULL); + DBusGProxy * bus_proxy = dbus_g_proxy_new_for_name(connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); + GError * error = NULL; + guint nameret = 0; + + if (!org_freedesktop_DBus_request_name(bus_proxy, INDICATOR_MESSAGES_DBUS_NAME, 0, &nameret, &error)) { + g_error("Unable to call to request name"); + return 1; + } + + if (nameret != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + g_error("Unable to get name"); + return 1; + } + + dbus_interface = message_service_dbus_new(); + + listener = indicate_listener_ref_default(); + serverList = NULL; + + root_menuitem = dbusmenu_menuitem_new(); + DbusmenuServer * server = dbusmenu_server_new(INDICATOR_MESSAGES_DBUS_OBJECT); + dbusmenu_server_set_root(server, root_menuitem); + + g_signal_connect(listener, INDICATE_LISTENER_SIGNAL_INDICATOR_ADDED, G_CALLBACK(indicator_added), root_menuitem); + g_signal_connect(listener, INDICATE_LISTENER_SIGNAL_INDICATOR_REMOVED, G_CALLBACK(indicator_removed), root_menuitem); + g_signal_connect(listener, INDICATE_LISTENER_SIGNAL_SERVER_ADDED, G_CALLBACK(server_added), root_menuitem); + g_signal_connect(listener, INDICATE_LISTENER_SIGNAL_SERVER_REMOVED, G_CALLBACK(server_removed), root_menuitem); + + g_idle_add(blacklist_init, NULL); + g_idle_add(build_launchers, SYSTEM_APPS_DIR); + gchar * userdir = g_build_filename(g_get_user_config_dir(), USER_APPS_DIR, NULL); + g_idle_add(build_launchers, userdir); + + mainloop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(mainloop); + + g_free(userdir); + + return 0; +} |