diff options
-rw-r--r-- | debian/changelog | 108 | ||||
-rw-r--r-- | debian/libmessaging-menu0.symbols | 2 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | src/im-application-list.c | 128 | ||||
-rw-r--r-- | src/im-desktop-menu.c | 35 | ||||
-rw-r--r-- | src/im-menu.c | 15 | ||||
-rw-r--r-- | src/im-menu.h | 2 | ||||
-rw-r--r-- | src/im-phone-menu.c | 3 | ||||
-rw-r--r-- | src/indicator-desktop-shortcuts.c | 680 | ||||
-rw-r--r-- | src/indicator-desktop-shortcuts.h | 80 | ||||
-rw-r--r-- | src/messages-service.c | 13 |
11 files changed, 937 insertions, 133 deletions
diff --git a/debian/changelog b/debian/changelog index 6544d6e..e409bdc 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,14 +1,19 @@ -indicator-messages (13.10.1-0ubuntu1) UNRELEASED; urgency=low +indicator-messages (13.10.1+13.10.20130820.2-0ubuntu1) saucy; urgency=low - * Bumping version to ensure we override all PPAs when this lands + * Automatic snapshot from revision 354 + + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 20 Aug 2013 08:59:34 +0000 - -- Ted Gould <ted@ubuntu.com> Mon, 19 Aug 2013 10:09:40 -0500 +indicator-messages (13.10.1+13.10.20130820-0ubuntu1) saucy; urgency=low -indicator-messages (13.10.0phablet1) raring; urgency=low + [ Ted Gould ] + * Bumping version to ensure we override all PPAs when this lands - * Version bump to not pull from archives + [ Ubuntu daily release ] + * debian/*symbols: auto-update new symbols to released version + * Automatic snapshot from revision 352 - -- Sergio Schvezov <sergio.schvezov@canonical.com> Fri, 26 Apr 2013 13:53:53 -0300 + -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 20 Aug 2013 02:06:40 +0000 indicator-messages (12.10.6+13.10.20130702-0ubuntu1) saucy; urgency=low @@ -20,12 +25,6 @@ indicator-messages (12.10.6+13.10.20130702-0ubuntu1) saucy; urgency=low -- Ubuntu daily release <ps-jenkins@lists.canonical.com> Tue, 02 Jul 2013 02:02:36 +0000 -indicator-messages (12.10.6phablet1) quantal; urgency=low - - * Adding guards for g_type_init - - -- Sergio Schvezov <sergio.schvezov@canonical.com> Fri, 22 Mar 2013 17:23:45 -0300 - indicator-messages (12.10.6daily13.06.19-0ubuntu1) saucy; urgency=low [ Sebastien Bacher ] @@ -139,91 +138,6 @@ indicator-messages (12.10.6daily12.11.21.1-0ubuntu1) raring; urgency=low -- Automatic PS uploader <ps-jenkins@lists.canonical.com> Wed, 21 Nov 2012 10:41:37 +0000 -indicator-messages (12.10.6-0ubuntu1phablet9) quantal; urgency=low - - * add "remove-all" signal to imapplicationlist (temporarily) - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Thu, 20 Dec 2012 18:49:50 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet8) quantal; urgency=low - - * Make messaging_menu_app_remove_message() work for messages with a ref count of 1 - * Make handling of multiple processes with the same desktop id more robust - * ImPhoneMenu: sort messages by time (fixes LP #1090266) - * Don't show message sources - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Mon, 17 Dec 2012 14:42:03 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet7) quantal; urgency=low - - * Remove variant wrapper from 'parameter' argument of the "activate" signal - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Tue, 11 Dec 2012 14:28:15 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet6) quantal; urgency=low - - * Don't show sources - * Always use the "messageitem" widget type for messages - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Mon, 10 Dec 2012 14:38:16 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet5) quantal; urgency=low - - * Don't shorten the app id to seven characters (fixes LP #1086729) - * Add messaging_menu_app_get_message - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Thu, 06 Dec 2012 15:06:34 +0000 - -indicator-messages (12.10.6-0ubuntu1phablet4) quantal; urgency=low - - [Lars Uebernickel] - * Add the concept of actions to messages - * Stop using IndicatorService - * Reverse order of messages - * Expose symbolic application icon - * Change icon when there are any messages in the menu - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Tue, 04 Dec 2012 21:10:26 +0000 - -indicator-messages (12.10.6-0ubuntu1phablet3) quantal; urgency=low - - [Lars Uebernickel] - * expose root menu item of which the indicator menu is a submenu - * fix crash in im-application-list on arm - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Thu, 29 Nov 2012 21:44:19 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet2) quantal; urgency=low - - [Lars Uebernickel] - * refactor messages-service.c - * notify applications about message and source activations - * add "Clear All" menu item - * allow dismissing of messages - * include application icons on message items - - -- Lars Uebernickel <lars.uebernickel@ubuntu.com> Mon, 26 Nov 2012 22:19:51 +0100 - -indicator-messages (12.10.6-0ubuntu1phablet1) quantal; urgency=low - - [ Mathieu Trudel-Lapierre ] - * debian/rules: - - Use autogen.sh for dh_autoreconf. - - Drop the override for dh_makeshlibs. - - Add DPKG_GENSYMBOLS_CHECK_LEVEL=4. - * debian/control: - - Fix styling: add trailing commas at the end of lists. - - Update Vcs-Bzr, Vcs-Browser fields and add a notice for developers. - - Add libgtest-dev to Build-Depends. - * Automatic snapshot from revision 329 (bootstrap): - - Clear the detail (count or time) of a source when another type of detail - is set. (LP: #1071640) - - [ Ricardo Mendoza ] - * Releasing upstream for phablet - - -- Ricardo Mendoza <ricardo.mendoza@canonical.com> Tue, 20 Nov 2012 10:08:59 -0430 - indicator-messages (12.10.5-0ubuntu2) raring; urgency=low * Upload to raring diff --git a/debian/libmessaging-menu0.symbols b/debian/libmessaging-menu0.symbols index d5eaed1..6a54911 100644 --- a/debian/libmessaging-menu0.symbols +++ b/debian/libmessaging-menu0.symbols @@ -5,7 +5,7 @@ libmessaging-menu.so.0 libmessaging-menu0 #MINVER# messaging_menu_app_append_source_with_string@Base 12.10.0 messaging_menu_app_append_source_with_time@Base 12.10.0 messaging_menu_app_draw_attention@Base 12.10.0 - messaging_menu_app_get_message@Base 0replaceme + messaging_menu_app_get_message@Base 13.10.1+13.10.20130820 messaging_menu_app_get_type@Base 12.10.0 messaging_menu_app_has_source@Base 12.10.0 messaging_menu_app_insert_source@Base 12.10.0 diff --git a/src/Makefile.am b/src/Makefile.am index e03406a..9b1cc9a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -21,7 +21,9 @@ indicator_messages_service_SOURCES = \ im-desktop-menu.c \ im-desktop-menu.h \ im-application-list.c \ - im-application-list.h + im-application-list.h \ + indicator-desktop-shortcuts.c \ + indicator-desktop-shortcuts.h indicator_messages_service_CFLAGS = \ $(APPLET_CFLAGS) \ diff --git a/src/im-application-list.c b/src/im-application-list.c index 14af69b..684a2e4 100644 --- a/src/im-application-list.c +++ b/src/im-application-list.c @@ -21,10 +21,13 @@ #include "indicator-messages-application.h" #include "gactionmuxer.h" +#include "indicator-desktop-shortcuts.h" #include <gio/gdesktopappinfo.h> #include <string.h> +#include "glib/gi18n.h" + typedef GObjectClass ImApplicationListClass; struct _ImApplicationList @@ -66,12 +69,12 @@ typedef struct gchar *id; IndicatorMessagesApplication *proxy; GActionMuxer *muxer; - GSimpleActionGroup *actions; GSimpleActionGroup *source_actions; GSimpleActionGroup *message_actions; GActionMuxer *message_sub_actions; GCancellable *cancellable; gboolean draws_attention; + IndicatorDesktopShortcuts * shortcuts; } Application; @@ -108,9 +111,39 @@ application_free (gpointer data) g_object_unref (app->message_sub_actions); } + g_clear_object (&app->shortcuts); + g_slice_free (Application, app); } +/* Check to see if we have actions by getting the full list of + names and see if there is one. Not exactly efficient :-/ */ +static gboolean +_g_action_group_has_actions (GActionGroup * ag) +{ + gchar ** list = NULL; + gboolean retval = FALSE; + + list = g_action_group_list_actions(ag); + retval = (list[0] != NULL); + g_strfreev(list); + + return retval; +} + +/* Check to see if either of our action groups has any actions, if + so return TRUE so we get chosen! */ +static gboolean +application_has_items (gpointer key, + gpointer value, + gpointer user_data) +{ + Application *app = value; + + return _g_action_group_has_actions(G_ACTION_GROUP(app->source_actions)) || + _g_action_group_has_actions(G_ACTION_GROUP(app->message_actions)); +} + static gboolean application_draws_attention (gpointer key, gpointer value, @@ -124,18 +157,61 @@ application_draws_attention (gpointer key, static void im_application_list_update_draws_attention (ImApplicationList *list) { + const gchar *base_icon_name; + const gchar *accessible_name; const gchar *icon_name; + GIcon * icon; + GVariantBuilder builder; GVariant *state; - GActionGroup *main_actions; - if (g_hash_table_find (list->applications, application_draws_attention, NULL)) - icon_name = "indicator-messages-new"; - else - icon_name = "indicator-messages"; + /* Figure out what type of icon we should be drawing */ + if (g_hash_table_find (list->applications, application_draws_attention, NULL)) { + base_icon_name = "indicator-messages-new-%s"; + accessible_name = _("New Messages"); + } else { + base_icon_name = "indicator-messages-%s"; + accessible_name = _("Messages"); + } - main_actions = g_action_muxer_get_group (list->muxer, NULL); - state = g_variant_new ("(sssb)", "", icon_name, "Messages", TRUE); - g_action_group_change_action_state (main_actions, "messages", state); + /* Include the IM state in the icon */ + state = g_action_group_get_action_state(G_ACTION_GROUP(list->globalactions), "status"); + icon_name = g_strdup_printf(base_icon_name, g_variant_get_string(state, NULL)); + g_variant_unref(state); + + /* Build up the dictionary of values for the state */ + g_variant_builder_init(&builder, G_VARIANT_TYPE_DICTIONARY); + + /* icon */ + g_variant_builder_open(&builder, G_VARIANT_TYPE_DICT_ENTRY); + g_variant_builder_add_value(&builder, g_variant_new_string("icon")); + icon = g_themed_icon_new_with_default_fallbacks(icon_name); + g_variant_builder_add_value(&builder, g_variant_new_variant(g_icon_serialize(icon))); + g_object_unref(icon); + g_variant_builder_close(&builder); + + /* accessible description */ + g_variant_builder_open(&builder, G_VARIANT_TYPE_DICT_ENTRY); + g_variant_builder_add_value(&builder, g_variant_new_string("accessible-desc")); + g_variant_builder_add_value(&builder, g_variant_new_variant(g_variant_new_string(accessible_name))); + g_variant_builder_close(&builder); + + /* visibility */ + g_variant_builder_open(&builder, G_VARIANT_TYPE_DICT_ENTRY); + g_variant_builder_add_value(&builder, g_variant_new_string("visible")); + g_variant_builder_add_value(&builder, g_variant_new_variant(g_variant_new_boolean(TRUE))); + g_variant_builder_close(&builder); + + /* Set the state */ + g_action_group_change_action_state (G_ACTION_GROUP(list->globalactions), "messages", g_variant_builder_end(&builder)); + + GAction * remove_action = g_simple_action_group_lookup(list->globalactions, "remove-all"); + if (g_hash_table_find (list->applications, application_has_items, NULL)) { + g_debug("Enabling remove-all"); + g_simple_action_set_enabled(G_SIMPLE_ACTION(remove_action), TRUE); + } else { + g_debug("Disabling remove-all"); + g_simple_action_set_enabled(G_SIMPLE_ACTION(remove_action), FALSE); + } } /* Check a source action to see if it draws */ @@ -490,7 +566,6 @@ static void im_application_list_init (ImApplicationList *list) { const GActionEntry action_entries[] = { - { "messages", NULL, NULL, "('', 'indicator-messages', 'Messages', true)", NULL }, { "remove-all", im_application_list_remove_all } }; @@ -498,6 +573,10 @@ im_application_list_init (ImApplicationList *list) list->app_status = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); list->globalactions = g_simple_action_group_new (); + { + GSimpleAction * messages = g_simple_action_new_stateful("messages", G_VARIANT_TYPE("a{sv}"), g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0)); + g_simple_action_group_insert(list->globalactions, G_ACTION(messages)); + } g_simple_action_group_add_entries (list->globalactions, action_entries, G_N_ELEMENTS (action_entries), list); list->statusaction = g_simple_action_new_stateful("status", G_VARIANT_TYPE_STRING, g_variant_new_string("offline")); @@ -507,6 +586,7 @@ im_application_list_init (ImApplicationList *list) list->muxer = g_action_muxer_new (); g_action_muxer_insert (list->muxer, NULL, G_ACTION_GROUP (list->globalactions)); + im_application_list_update_draws_attention (list); } ImApplicationList * @@ -573,7 +653,7 @@ im_application_list_activate_app_action (GSimpleAction *action, { Application *app = user_data; - g_desktop_app_info_launch_action (app->info, g_action_get_name (G_ACTION (action)), NULL); + indicator_desktop_shortcuts_nick_exec_with_context (app->shortcuts, g_action_get_name (G_ACTION (action)), NULL); } gboolean @@ -585,6 +665,7 @@ im_application_list_add (ImApplicationList *list, const gchar *id; GSimpleActionGroup *actions; GSimpleAction *launch_action; + IndicatorDesktopShortcuts * shortcuts = NULL; g_return_if_fail (IM_IS_APPLICATION_LIST (list)); g_return_if_fail (desktop_id != NULL); @@ -602,6 +683,12 @@ im_application_list_add (ImApplicationList *list, id = g_app_info_get_id (G_APP_INFO (info)); g_return_if_fail (id != NULL); + { + const char * filename = g_desktop_app_info_get_filename(info); + if (filename != NULL) + shortcuts = indicator_desktop_shortcuts_new(filename, "Messaging Menu"); + } + app = g_slice_new0 (Application); app->info = info; app->id = im_application_list_canonical_id (id); @@ -611,6 +698,7 @@ im_application_list_add (ImApplicationList *list, app->message_actions = g_simple_action_group_new (); app->message_sub_actions = g_action_muxer_new (); app->draws_attention = FALSE; + app->shortcuts = shortcuts; actions = g_simple_action_group_new (); @@ -618,14 +706,14 @@ im_application_list_add (ImApplicationList *list, g_signal_connect (launch_action, "activate", G_CALLBACK (im_application_list_activate_launch), app); g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (launch_action)); - { - const gchar *const *app_actions; + if (app->shortcuts != NULL) { + const gchar ** nicks; - for (app_actions = g_desktop_app_info_list_actions (app->info); *app_actions; app_actions++) + for (nicks = indicator_desktop_shortcuts_get_nicks (app->shortcuts); *nicks; nicks++) { GSimpleAction *action; - action = g_simple_action_new (*app_actions, NULL); + action = g_simple_action_new (*nicks, NULL); g_signal_connect (action, "activate", G_CALLBACK (im_application_list_activate_app_action), app); g_action_map_add_action (G_ACTION_MAP (actions), G_ACTION (action)); @@ -715,15 +803,21 @@ im_application_list_source_changed (Application *app, gint64 time; const gchar *string; gboolean draws_attention; + gboolean old_draw; g_variant_get (source, "(&s&s&sux&sb)", &id, &label, &iconstr, &count, &time, &string, &draws_attention); + old_draw = app_source_action_check_draw(app, id); + g_action_group_change_action_state (G_ACTION_GROUP (app->source_actions), id, g_variant_new ("(uxsb)", count, time, string, draws_attention)); g_signal_emit (app->list, signals[SOURCE_CHANGED], 0, app->id, id, label, iconstr); + if (!old_draw && draws_attention) + app->draws_attention = TRUE; + im_application_list_update_draws_attention (app->list); } @@ -1091,6 +1185,8 @@ status_activated (GSimpleAction * action, GVariant * param, gpointer user_data) g_signal_emit (list, signals[STATUS_SET], 0, status); + im_application_list_update_draws_attention(list); + return; } @@ -1136,6 +1232,8 @@ im_application_list_set_status (ImApplicationList * list, const gchar * id, cons g_simple_action_set_state(list->statusaction, g_variant_new_string(status_ids[final_status])); + im_application_list_update_draws_attention(list); + return; } diff --git a/src/im-desktop-menu.c b/src/im-desktop-menu.c index 1040d62..834a8c9 100644 --- a/src/im-desktop-menu.c +++ b/src/im-desktop-menu.c @@ -18,6 +18,7 @@ */ #include "im-desktop-menu.h" +#include "indicator-desktop-shortcuts.h" #include <glib/gi18n.h> typedef ImMenuClass ImDesktopMenuClass; @@ -66,21 +67,28 @@ im_desktop_menu_app_added (ImApplicationList *applist, } /* application actions */ -#if 0 { - const gchar *const *actions; + const gchar * filename = NULL; + IndicatorDesktopShortcuts * shortcuts = NULL; + const gchar ** nicks = {NULL}; - for (actions = g_desktop_app_info_list_actions (app_info); *actions; actions++) - { - gchar *label; + filename = g_desktop_app_info_get_filename(app_info); + if (filename != NULL) + shortcuts = indicator_desktop_shortcuts_new(filename, "Messaging Menu"); - label = g_desktop_app_info_get_action_name (app_info, *actions); - g_menu_append (app_section, label, *actions); + if (shortcuts != NULL) + for (nicks = indicator_desktop_shortcuts_get_nicks(shortcuts); *nicks; nicks++) + { + gchar *label; - g_free (label); - } + label = indicator_desktop_shortcuts_nick_get_name (shortcuts, *nicks); + g_menu_append (app_section, label, *nicks); + + g_free (label); + } + + g_clear_object(&shortcuts); } -#endif source_section = g_menu_new (); @@ -89,7 +97,7 @@ im_desktop_menu_app_added (ImApplicationList *applist, g_menu_append_section (section, NULL, G_MENU_MODEL (source_section)); namespace = g_strconcat ("indicator.", app_id, NULL); - im_menu_insert_section (IM_MENU (menu), -1, namespace, G_MENU_MODEL (section)); + im_menu_insert_section (IM_MENU (menu), g_app_info_get_name(G_APP_INFO(app_info)), namespace, G_MENU_MODEL (section)); g_hash_table_insert (menu->source_sections, g_strdup (app_id), source_section); g_free (namespace); @@ -114,7 +122,8 @@ im_desktop_menu_source_added (ImApplicationList *applist, g_return_if_fail (source_section != NULL); action = g_strconcat ("src.", source_id, NULL); - item = g_menu_item_new (label, action); + item = g_menu_item_new (label, NULL); + g_menu_item_set_action_and_target_value(item, action, NULL); g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.messages.source"); if (icon && *icon) g_menu_item_set_attribute (item, "icon", "s", icon); @@ -227,7 +236,7 @@ im_desktop_menu_constructed (GObject *object) GMenu *clear_section; clear_section = g_menu_new (); - g_menu_append (clear_section, "Clear", "indicator.remove-all"); + g_menu_append (clear_section, _("Clear"), "indicator.remove-all"); im_menu_append_section (IM_MENU (menu), G_MENU_MODEL (clear_section)); g_object_unref (clear_section); diff --git a/src/im-menu.c b/src/im-menu.c index ac23a29..1aaffe3 100644 --- a/src/im-menu.c +++ b/src/im-menu.c @@ -164,10 +164,11 @@ im_menu_append_section (ImMenu *menu, void im_menu_insert_section (ImMenu *menu, - gint position, + const gchar *sort_string, const gchar *namespace, GMenuModel *section) { + int position; ImMenuPrivate *priv; GMenuItem *item; @@ -176,11 +177,17 @@ im_menu_insert_section (ImMenu *menu, priv = im_menu_get_instance_private (menu); - /* count from the back if position is < 0 */ - if (position < 0) - position = g_menu_model_get_n_items (G_MENU_MODEL (priv->menu)) + position; + for (position = 1; position < g_menu_model_get_n_items(G_MENU_MODEL (priv->menu)) - 1; position++) + { + gchar * item_sort = NULL; + if (g_menu_model_get_item_attribute(G_MENU_MODEL(priv->menu), position, "x-messaging-menu-sort-string", "s", &item_sort)) + if (g_utf8_collate(sort_string, item_sort) < 0) + break; + g_free(item_sort); + } item = g_menu_item_new_section (NULL, section); + g_menu_item_set_attribute (item, "x-messaging-menu-sort-string", "s", sort_string); if (namespace) g_menu_item_set_attribute (item, "action-namespace", "s", namespace); diff --git a/src/im-menu.h b/src/im-menu.h index d3775ad..7c15eb7 100644 --- a/src/im-menu.h +++ b/src/im-menu.h @@ -57,7 +57,7 @@ void im_menu_append_section (ImMenu GMenuModel *section); void im_menu_insert_section (ImMenu *menu, - gint position, + const gchar *sort_string, const gchar *namespace, GMenuModel *section); diff --git a/src/im-phone-menu.c b/src/im-phone-menu.c index 7381097..0ea6f76 100644 --- a/src/im-phone-menu.c +++ b/src/im-phone-menu.c @@ -20,6 +20,7 @@ #include "im-phone-menu.h" #include <string.h> +#include <glib/gi18n.h> typedef ImMenuClass ImPhoneMenuClass; @@ -72,7 +73,7 @@ im_phone_menu_constructed (GObject *object) clear_section = g_menu_new (); - item = g_menu_item_new ("Clear All", "remove-all"); + item = g_menu_item_new (_("Clear All"), "remove-all"); g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.button"); g_menu_append_item (clear_section, item); diff --git a/src/indicator-desktop-shortcuts.c b/src/indicator-desktop-shortcuts.c new file mode 100644 index 0000000..7b43630 --- /dev/null +++ b/src/indicator-desktop-shortcuts.c @@ -0,0 +1,680 @@ +/* +A small file to parse through the actions that are available +in the desktop file and making those easily usable. + +Copyright 2010 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 3.0 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License version 3.0 for more details. + +You should have received a copy of the GNU General Public +License along with this library. If not, see +<http://www.gnu.org/licenses/>. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gio/gdesktopappinfo.h> +#include "indicator-desktop-shortcuts.h" + +#define ACTIONS_KEY "Actions" +#define ACTION_GROUP_PREFIX "Desktop Action" + +#define OLD_GROUP_SUFFIX "Shortcut Group" +#define OLD_SHORTCUTS_KEY "X-Ayatana-Desktop-Shortcuts" +#define OLD_ENVIRON_KEY "TargetEnvironment" + +#define PROP_DESKTOP_FILE_S "desktop-file" +#define PROP_IDENTITY_S "identity" + +typedef enum _actions_t actions_t; +enum _actions_t { + ACTIONS_NONE, + ACTIONS_XAYATANA, + ACTIONS_DESKTOP_SPEC +}; + +typedef struct _IndicatorDesktopShortcutsPrivate IndicatorDesktopShortcutsPrivate; +struct _IndicatorDesktopShortcutsPrivate { + actions_t actions; + GKeyFile * keyfile; + gchar * identity; + GArray * nicks; + gchar * domain; +}; + +enum { + PROP_0, + PROP_DESKTOP_FILE, + PROP_IDENTITY +}; + +#define INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsPrivate)) + +static void indicator_desktop_shortcuts_class_init (IndicatorDesktopShortcutsClass *klass); +static void indicator_desktop_shortcuts_init (IndicatorDesktopShortcuts *self); +static void indicator_desktop_shortcuts_dispose (GObject *object); +static void indicator_desktop_shortcuts_finalize (GObject *object); +static void set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); +static void get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); +static void parse_keyfile (IndicatorDesktopShortcuts * ids); +static gboolean should_show (GKeyFile * keyfile, const gchar * group, const gchar * identity, gboolean should_have_target); + +G_DEFINE_TYPE (IndicatorDesktopShortcuts, indicator_desktop_shortcuts, G_TYPE_OBJECT); + +/* Build up the class */ +static void +indicator_desktop_shortcuts_class_init (IndicatorDesktopShortcutsClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (IndicatorDesktopShortcutsPrivate)); + + object_class->dispose = indicator_desktop_shortcuts_dispose; + object_class->finalize = indicator_desktop_shortcuts_finalize; + + /* Property funcs */ + object_class->set_property = set_property; + object_class->get_property = get_property; + + g_object_class_install_property(object_class, PROP_DESKTOP_FILE, + g_param_spec_string(PROP_DESKTOP_FILE_S, + "The path of the desktop file to read", + "A path to a desktop file that we'll look for shortcuts in.", + NULL, + G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property(object_class, PROP_IDENTITY, + g_param_spec_string(PROP_IDENTITY_S, + "The string that represents the identity that we're acting as.", + "Used to process ShowIn and NotShownIn fields of the desktop shortcust to get the proper list.", + NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); + + return; +} + +/* Initialize instance data */ +static void +indicator_desktop_shortcuts_init (IndicatorDesktopShortcuts *self) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(self); + + priv->keyfile = NULL; + priv->identity = NULL; + priv->domain = NULL; + priv->nicks = g_array_new(TRUE, TRUE, sizeof(gchar *)); + priv->actions = ACTIONS_NONE; + + return; +} + +/* Clear object references */ +static void +indicator_desktop_shortcuts_dispose (GObject *object) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + if (priv->keyfile) { + g_key_file_free(priv->keyfile); + priv->keyfile = NULL; + } + + G_OBJECT_CLASS (indicator_desktop_shortcuts_parent_class)->dispose (object); + return; +} + +/* Free all memory */ +static void +indicator_desktop_shortcuts_finalize (GObject *object) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + if (priv->identity != NULL) { + g_free(priv->identity); + priv->identity = NULL; + } + + if (priv->domain != NULL) { + g_free(priv->domain); + priv->domain = NULL; + } + + if (priv->nicks != NULL) { + gint i; + for (i = 0; i < priv->nicks->len; i++) { + gchar * nick = g_array_index(priv->nicks, gchar *, i); + g_free(nick); + } + g_array_free(priv->nicks, TRUE); + priv->nicks = NULL; + } + + G_OBJECT_CLASS (indicator_desktop_shortcuts_parent_class)->finalize (object); + return; +} + +/* Sets one of the two properties we have, only at construction though */ +static void +set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) +{ + g_return_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(object)); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + switch(prop_id) { + case PROP_DESKTOP_FILE: { + if (priv->keyfile != NULL) { + g_key_file_free(priv->keyfile); + priv->keyfile = NULL; + priv->actions = ACTIONS_NONE; + } + + GError * error = NULL; + GKeyFile * keyfile = g_key_file_new(); + g_key_file_load_from_file(keyfile, g_value_get_string(value), G_KEY_FILE_NONE, &error); + + if (error != NULL) { + g_warning("Unable to load keyfile from file '%s': %s", g_value_get_string(value), error->message); + g_error_free(error); + g_key_file_free(keyfile); + break; + } + + /* Always prefer the desktop spec if we can get it */ + if (priv->actions == ACTIONS_NONE && g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, ACTIONS_KEY, NULL)) { + priv->actions = ACTIONS_DESKTOP_SPEC; + } + + /* But fallback if we can't */ + if (priv->actions == ACTIONS_NONE && g_key_file_has_key(keyfile, G_KEY_FILE_DESKTOP_GROUP, OLD_SHORTCUTS_KEY, NULL)) { + priv->actions = ACTIONS_XAYATANA; + g_warning("Desktop file '%s' is using a deprecated format for its actions that will be dropped soon.", g_value_get_string(value)); + } + + if (priv->actions == ACTIONS_NONE) { + g_key_file_free(keyfile); + break; + } + + priv->keyfile = keyfile; + parse_keyfile(INDICATOR_DESKTOP_SHORTCUTS(object)); + break; + } + case PROP_IDENTITY: + if (priv->identity != NULL) { + g_warning("Identity already set to '%s' and trying to set it to '%s'.", priv->identity, g_value_get_string(value)); + return; + } + priv->identity = g_value_dup_string(value); + parse_keyfile(INDICATOR_DESKTOP_SHORTCUTS(object)); + break; + /* *********************** */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + return; +} + +/* Gets either the desktop file our the identity. */ +static void +get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) +{ + g_return_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(object)); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(object); + + switch(prop_id) { + case PROP_IDENTITY: + g_value_set_string(value, priv->identity); + break; + /* *********************** */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } + + return; +} + +/* Checks to see if we can, and if we can it goes through + and parses the keyfile entries. */ +static void +parse_keyfile (IndicatorDesktopShortcuts * ids) +{ + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + + if (priv->keyfile == NULL) { + return; + } + + if (priv->identity == NULL) { + return; + } + + /* Remove a previous translation domain if we had one + from a previously parsed file. */ + if (priv->domain != NULL) { + g_free(priv->domain); + priv->domain = NULL; + } + + /* Check to see if there is a custom translation domain that + we should take into account. */ + if (priv->domain == NULL && + g_key_file_has_key(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL)) { + priv->domain = g_key_file_get_string(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-GNOME-Gettext-Domain", NULL); + } + + if (priv->domain == NULL && + g_key_file_has_key(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL)) { + priv->domain = g_key_file_get_string(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, "X-Ubuntu-Gettext-Domain", NULL); + } + + /* We need to figure out what we're looking for and what we want to + look for in the rest of the file */ + const gchar * list_name = NULL; + const gchar * group_format = NULL; + gboolean should_have_target = FALSE; + + switch (priv->actions) { + case ACTIONS_NONE: + /* None, let's just get outta here */ + return; + case ACTIONS_XAYATANA: + list_name = OLD_SHORTCUTS_KEY; + group_format = "%s " OLD_GROUP_SUFFIX; + should_have_target = TRUE; + break; + case ACTIONS_DESKTOP_SPEC: + list_name = ACTIONS_KEY; + group_format = ACTION_GROUP_PREFIX " %s"; + should_have_target = FALSE; + break; + default: + g_assert_not_reached(); + return; + } + + /* Okay, we've got everything we need. Let's get it on! */ + gint i; + gsize num_nicks = 0; + gchar ** nicks = g_key_file_get_string_list(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, list_name, &num_nicks, NULL); + + /* If there is an error from get_string_list num_nicks should still + be zero, so this loop will drop out. */ + for (i = 0; i < num_nicks; i++) { + /* g_debug("Looking at group nick %s", nicks[i]); */ + gchar * groupname = g_strdup_printf(group_format, nicks[i]); + if (!g_key_file_has_group(priv->keyfile, groupname)) { + g_warning("Unable to find group '%s'", groupname); + g_free(groupname); + continue; + } + + if (!should_show(priv->keyfile, G_KEY_FILE_DESKTOP_GROUP, priv->identity, FALSE)) { + g_free(groupname); + continue; + } + + if (!should_show(priv->keyfile, groupname, priv->identity, should_have_target)) { + g_free(groupname); + continue; + } + + gchar * nickalloc = g_strdup(nicks[i]); + g_array_append_val(priv->nicks, nickalloc); + g_free(groupname); + } + + if (nicks != NULL) { + g_strfreev(nicks); + } + + return; +} + +/* Checks the ONLY_SHOW_IN and NOT_SHOW_IN keys for a group to + see if we should be showing ourselves. */ +static gboolean +should_show (GKeyFile * keyfile, const gchar * group, const gchar * identity, gboolean should_have_target) +{ + if (should_have_target && g_key_file_has_key(keyfile, group, OLD_ENVIRON_KEY, NULL)) { + /* If we've got this key, we're going to return here and not + process the deprecated keys. */ + gint j; + gsize num_env = 0; + gchar ** envs = g_key_file_get_string_list(keyfile, group, OLD_ENVIRON_KEY, &num_env, NULL); + + for (j = 0; j < num_env; j++) { + if (g_strcmp0(envs[j], identity) == 0) { + break; + } + } + + if (envs != NULL) { + g_strfreev(envs); + } + + if (j == num_env) { + return FALSE; + } + return TRUE; + } + + /* If there is a list of OnlyShowIn entries we need to check + to see if we're in that list. If not, we drop this nick */ + if (g_key_file_has_key(keyfile, group, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, NULL)) { + gint j; + gsize num_only = 0; + gchar ** onlies = g_key_file_get_string_list(keyfile, group, G_KEY_FILE_DESKTOP_KEY_ONLY_SHOW_IN, &num_only, NULL); + + for (j = 0; j < num_only; j++) { + if (g_strcmp0(onlies[j], identity) == 0) { + break; + } + } + + if (onlies != NULL) { + g_strfreev(onlies); + } + + if (j == num_only) { + return FALSE; + } + } + + /* If there is a NotShowIn entry we need to make sure that we're + not in that list. If we are, we need to drop out. */ + if (g_key_file_has_key(keyfile, group, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, NULL)) { + gint j; + gsize num_not = 0; + gchar ** nots = g_key_file_get_string_list(keyfile, group, G_KEY_FILE_DESKTOP_KEY_NOT_SHOW_IN, &num_not, NULL); + + for (j = 0; j < num_not; j++) { + if (g_strcmp0(nots[j], identity) == 0) { + break; + } + } + + if (nots != NULL) { + g_strfreev(nots); + } + + if (j != num_not) { + return FALSE; + } + } + + return TRUE; +} + +/* Looks through the nicks to see if this one is in the list, + and thus valid to use. */ +static gboolean +is_valid_nick (gchar ** list, const gchar * nick) +{ + if (*list == NULL) + return FALSE; + /* g_debug("Checking Nick: %s", list[0]); */ + if (g_strcmp0(list[0], nick) == 0) + return TRUE; + return is_valid_nick(&list[1], nick); +} + +/* API */ + +/** + indicator_desktop_shortcuts_new: + @file: The desktop file that would be opened to + find the actions. + @identity: This is a string that represents the identity + that should be used in searching those actions. It + relates to the ShowIn and NotShownIn properties. + + This function creates the basic object. It involves opening + the file and parsing it. It could potentially block on IO. At + the end of the day you'll have a fully functional object. + + Return value: A new #IndicatorDesktopShortcuts object. +*/ +IndicatorDesktopShortcuts * +indicator_desktop_shortcuts_new (const gchar * file, const gchar * identity) +{ + GObject * obj = g_object_new(INDICATOR_TYPE_DESKTOP_SHORTCUTS, + PROP_DESKTOP_FILE_S, file, + PROP_IDENTITY_S, identity, + NULL); + return INDICATOR_DESKTOP_SHORTCUTS(obj); +} + +/** + indicator_desktop_shortcuts_get_nicks: + @ids: The #IndicatorDesktopShortcuts object to look in + + Give you the list of commands that are available for this desktop + file given the identity that was passed in at creation. This will + filter out the various items in the desktop file. These nicks can + then be used as keys for working with the desktop file. + + Return value: A #NULL terminated list of strings. This memory + is managed by the @ids object. +*/ +const gchar ** +indicator_desktop_shortcuts_get_nicks (IndicatorDesktopShortcuts * ids) +{ + g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), NULL); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + return (const gchar **)priv->nicks->data; +} + +/** + indicator_desktop_shortcuts_nick_get_name: + @ids: The #IndicatorDesktopShortcuts object to look in + @nick: Which command that we're referencing. + + This function looks in a desktop file for a nick to find the + user visible name for that shortcut. The @nick parameter + should be gotten from #indicator_desktop_shortcuts_get_nicks + though it's not required that the exact memory location + be the same. + + Return value: A user visible string for the shortcut or + #NULL on error. +*/ +gchar * +indicator_desktop_shortcuts_nick_get_name (IndicatorDesktopShortcuts * ids, const gchar * nick) +{ + g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), NULL); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + + g_return_val_if_fail(priv->actions != ACTIONS_NONE, NULL); + g_return_val_if_fail(priv->keyfile != NULL, NULL); + g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), NULL); + + const gchar * group_format = NULL; + + switch (priv->actions) { + case ACTIONS_XAYATANA: + group_format = "%s " OLD_GROUP_SUFFIX; + break; + case ACTIONS_DESKTOP_SPEC: + group_format = ACTION_GROUP_PREFIX " %s"; + break; + default: + g_assert_not_reached(); + return NULL; + } + + gchar * groupheader = g_strdup_printf(group_format, nick); + if (!g_key_file_has_group(priv->keyfile, groupheader)) { + g_warning("The group for nick '%s' doesn't exist anymore.", nick); + g_free(groupheader); + return NULL; + } + + if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_NAME, NULL)) { + g_warning("No name available for nick '%s'", nick); + g_free(groupheader); + return NULL; + } + + gchar * name = NULL; + gchar * keyvalue = g_key_file_get_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_NAME, + NULL); + gchar * localeval = g_key_file_get_locale_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_NAME, + NULL, + NULL); + g_free(groupheader); + + if (priv->domain != NULL && g_strcmp0(keyvalue, localeval) == 0) { + name = g_strdup(g_dgettext(priv->domain, keyvalue)); + g_free(localeval); + } else { + name = localeval; + } + + g_free(keyvalue); + + return name; +} + +/** + indicator_desktop_shortcuts_nick_exec_with_context: + @ids: The #IndicatorDesktopShortcuts object to look in + @nick: Which command that we're referencing. + @launch_context: The #GAppLaunchContext to use for launching the shortcut + + Here we take a @nick and try and execute the action that is + associated with it. The @nick parameter should be gotten + from #indicator_desktop_shortcuts_get_nicks though it's not + required that the exact memory location be the same. + + Return value: #TRUE on success or #FALSE on error. +*/ +gboolean +indicator_desktop_shortcuts_nick_exec_with_context (IndicatorDesktopShortcuts * ids, const gchar * nick, GAppLaunchContext * launch_context) +{ + GError * error = NULL; + + g_return_val_if_fail(INDICATOR_IS_DESKTOP_SHORTCUTS(ids), FALSE); + IndicatorDesktopShortcutsPrivate * priv = INDICATOR_DESKTOP_SHORTCUTS_GET_PRIVATE(ids); + + g_return_val_if_fail(priv->actions != ACTIONS_NONE, FALSE); + g_return_val_if_fail(priv->keyfile != NULL, FALSE); + g_return_val_if_fail(is_valid_nick((gchar **)priv->nicks->data, nick), FALSE); + + const gchar * group_format = NULL; + + switch (priv->actions) { + case ACTIONS_XAYATANA: + group_format = "%s " OLD_GROUP_SUFFIX; + break; + case ACTIONS_DESKTOP_SPEC: + group_format = ACTION_GROUP_PREFIX " %s"; + break; + default: + g_assert_not_reached(); + return FALSE; + } + + gchar * groupheader = g_strdup_printf(group_format, nick); + if (!g_key_file_has_group(priv->keyfile, groupheader)) { + g_warning("The group for nick '%s' doesn't exist anymore.", nick); + g_free(groupheader); + return FALSE; + } + + if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_NAME, NULL)) { + g_warning("No name available for nick '%s'", nick); + g_free(groupheader); + return FALSE; + } + + if (!g_key_file_has_key(priv->keyfile, groupheader, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL)) { + g_warning("No exec available for nick '%s'", nick); + g_free(groupheader); + return FALSE; + } + + /* Grab the name and the exec entries out of our current group */ + gchar * name = g_key_file_get_locale_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_NAME, + NULL, + NULL); + + gchar * exec = g_key_file_get_locale_string(priv->keyfile, + groupheader, + G_KEY_FILE_DESKTOP_KEY_EXEC, + NULL, + NULL); + + g_free(groupheader); + + GAppInfoCreateFlags flags = G_APP_INFO_CREATE_NONE; + + if (launch_context) { + flags |= G_APP_INFO_CREATE_SUPPORTS_STARTUP_NOTIFICATION; + } + + GAppInfo * appinfo = g_app_info_create_from_commandline(exec, name, flags, &error); + g_free(name); g_free(exec); + + if (error != NULL) { + g_warning("Unable to build Command line App info: %s", error->message); + g_error_free(error); + return FALSE; + } + + if (appinfo == NULL) { + g_warning("Unable to build Command line App info (unknown)"); + return FALSE; + } + + gboolean launched = g_app_info_launch(appinfo, NULL, launch_context, &error); + + if (error != NULL) { + g_warning("Unable to launch file from nick '%s': %s", nick, error->message); + g_clear_error(&error); + } + + g_object_unref(appinfo); + + return launched; +} + +/** + indicator_desktop_shortcuts_nick_exec: + @ids: The #IndicatorDesktopShortcuts object to look in + @nick: Which command that we're referencing. + + Here we take a @nick and try and execute the action that is + associated with it. The @nick parameter should be gotten + from #indicator_desktop_shortcuts_get_nicks though it's not + required that the exact memory location be the same. + This function is deprecated and shouldn't be used in newly + written code. + + Return value: #TRUE on success or #FALSE on error. +*/ +gboolean +indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids, const gchar * nick) +{ + return indicator_desktop_shortcuts_nick_exec_with_context (ids, nick, NULL); +} diff --git a/src/indicator-desktop-shortcuts.h b/src/indicator-desktop-shortcuts.h new file mode 100644 index 0000000..fb997ea --- /dev/null +++ b/src/indicator-desktop-shortcuts.h @@ -0,0 +1,80 @@ +/* +A small file to parse through the actions that are available +in the desktop file and making those easily usable. + +Copyright 2010 Canonical Ltd. + +Authors: + Ted Gould <ted@canonical.com> + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +version 3.0 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License version 3.0 for more details. + +You should have received a copy of the GNU General Public +License along with this library. If not, see +<http://www.gnu.org/licenses/>. +*/ + +#ifndef __INDICATOR_DESKTOP_SHORTCUTS_H__ +#define __INDICATOR_DESKTOP_SHORTCUTS_H__ + +#include <gio/gio.h> +#include <glib.h> +#include <glib-object.h> + +G_BEGIN_DECLS + +#define INDICATOR_TYPE_DESKTOP_SHORTCUTS (indicator_desktop_shortcuts_get_type ()) +#define INDICATOR_DESKTOP_SHORTCUTS(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcuts)) +#define INDICATOR_DESKTOP_SHORTCUTS_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsClass)) +#define INDICATOR_IS_DESKTOP_SHORTCUTS(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS)) +#define INDICATOR_IS_DESKTOP_SHORTCUTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), INDICATOR_TYPE_DESKTOP_SHORTCUTS)) +#define INDICATOR_DESKTOP_SHORTCUTS_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), INDICATOR_TYPE_DESKTOP_SHORTCUTS, IndicatorDesktopShortcutsClass)) + +typedef struct _IndicatorDesktopShortcuts IndicatorDesktopShortcuts; +typedef struct _IndicatorDesktopShortcutsClass IndicatorDesktopShortcutsClass; + +/** + IndicatorDesktopShortcutsClass: + @parent_class: Space for #GObjectClass + + The vtable for our precious #IndicatorDesktopShortcutsClass. +*/ +struct _IndicatorDesktopShortcutsClass { + GObjectClass parent_class; +}; + +/** + IndicatorDesktopShortcuts: + @parent: The parent data from #GObject + + The public data for an instance of the class + #IndicatorDesktopShortcuts. +*/ +struct _IndicatorDesktopShortcuts { + GObject parent; +}; + +GType indicator_desktop_shortcuts_get_type (void); +IndicatorDesktopShortcuts * indicator_desktop_shortcuts_new (const gchar * file, + const gchar * identity); +const gchar ** indicator_desktop_shortcuts_get_nicks (IndicatorDesktopShortcuts * ids); +gchar * indicator_desktop_shortcuts_nick_get_name (IndicatorDesktopShortcuts * ids, + const gchar * nick); +gboolean indicator_desktop_shortcuts_nick_exec_with_context (IndicatorDesktopShortcuts * ids, + const gchar * nick, + GAppLaunchContext * launch_context); + +GLIB_DEPRECATED_FOR(indicator_desktop_shortcuts_nick_exec_with_context) +gboolean indicator_desktop_shortcuts_nick_exec (IndicatorDesktopShortcuts * ids, + const gchar * nick); + +G_END_DECLS + +#endif diff --git a/src/messages-service.c b/src/messages-service.c index 5054ebc..a8deb6a 100644 --- a/src/messages-service.c +++ b/src/messages-service.c @@ -25,6 +25,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <locale.h> #include <gio/gio.h> #include <glib/gi18n.h> +#include <glib-unix.h> #include "dbus-data.h" #include "gsettingsstrv.h" @@ -181,6 +182,16 @@ on_name_lost (GDBusConnection *bus, g_main_loop_quit (mainloop); } +static gboolean +sig_term_handler (gpointer user_data) +{ + GMainLoop *mainloop = user_data; + + g_main_loop_quit (mainloop); + + return FALSE; +} + int main (int argc, char ** argv) { @@ -237,6 +248,8 @@ main (int argc, char ** argv) g_hash_table_insert (menus, "phone", im_phone_menu_new (applications)); g_hash_table_insert (menus, "desktop", im_desktop_menu_new (applications)); + g_unix_signal_add(SIGTERM, sig_term_handler, mainloop); + g_main_loop_run(mainloop); /* Clean up */ |