Back to index

indicator-appmenu  12.10.0
huddbusmenucollector.c
Go to the documentation of this file.
00001 /*
00002  * Copyright © 2012 Canonical Ltd.
00003  *
00004  * This program is free software: you can redistribute it and/or modify it
00005  * under the terms of the GNU General Public License version 3, as
00006  * published by the Free Software Foundation.
00007  *
00008  * This program is distributed in the hope that it will be useful, but
00009  * WITHOUT ANY WARRANTY; without even the implied warranties of
00010  * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
00011  * PURPOSE.  See the GNU General Public License for more details.
00012  *
00013  * You should have received a copy of the GNU General Public License along
00014  * with this program.  If not, see <http://www.gnu.org/licenses/>.
00015  *
00016  * Authors: Ryan Lortie <desrt@desrt.ca>
00017  *          Ted Gould <ted@canonical.com>
00018  */
00019 
00020 #define G_LOG_DOMAIN "huddbusmenucollector"
00021 
00022 #include "huddbusmenucollector.h"
00023 
00024 #include <libdbusmenu-glib/client.h>
00025 #include <string.h>
00026 
00027 #include "hudappmenuregistrar.h"
00028 #include "hudresult.h"
00029 #include "hudsource.h"
00030 
00059 typedef struct
00060 {
00061   HudItem parent_instance;
00062 
00063   DbusmenuMenuitem *menuitem;
00064   gboolean is_opened;
00065 } HudDbusmenuItem;
00066 
00067 typedef HudItemClass HudDbusmenuItemClass;
00068 
00069 G_DEFINE_TYPE (HudDbusmenuItem, hud_dbusmenu_item, HUD_TYPE_ITEM)
00070 
00071 static void
00072 hud_dbusmenu_item_activate (HudItem  *hud_item,
00073                             GVariant *platform_data)
00074 {
00075   HudDbusmenuItem *item = (HudDbusmenuItem *) hud_item;
00076   const gchar *startup_id;
00077   guint32 timestamp = 0;
00078 
00079   if (g_variant_lookup (platform_data, "desktop-startup-id", "&s", &startup_id))
00080     {
00081       const gchar *time_tag;
00082 
00083       if ((time_tag = strstr (startup_id, "_TIME")))
00084         {
00085           gint64 result;
00086 
00087           result = g_ascii_strtoll (time_tag + 5, NULL, 10);
00088 
00089           if (0 <= result && result <= G_MAXINT32)
00090            timestamp = result;
00091         }
00092     }
00093 
00094   dbusmenu_menuitem_handle_event(item->menuitem, DBUSMENU_MENUITEM_EVENT_ACTIVATED, NULL, timestamp);
00095 }
00096 
00097 static void
00098 hud_dbusmenu_item_finalize (GObject *object)
00099 {
00100   HudDbusmenuItem *item = (HudDbusmenuItem *) object;
00101 
00102   g_object_unref (item->menuitem);
00103 
00104   G_OBJECT_CLASS (hud_dbusmenu_item_parent_class)
00105     ->finalize (object);
00106 }
00107 
00108 static void
00109 hud_dbusmenu_item_init (HudDbusmenuItem *item)
00110 {
00111 }
00112 
00113 static void
00114 hud_dbusmenu_item_class_init (HudDbusmenuItemClass *class)
00115 {
00116   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
00117 
00118   gobject_class->finalize = hud_dbusmenu_item_finalize;
00119   class->activate = hud_dbusmenu_item_activate;
00120 }
00121 
00122 static const gchar *
00123 hud_dbusmenu_item_get_label_property (const gchar *type)
00124 {
00125   static const gchar * const property_table[][2] =
00126   {
00127     { DBUSMENU_CLIENT_TYPES_DEFAULT,                 DBUSMENU_MENUITEM_PROP_LABEL                         },
00128     /* Indicator Messages */
00129     { "application-item",                            DBUSMENU_MENUITEM_PROP_LABEL                         },
00130     { "indicator-item",                              "indicator-label"                                    },
00131     /* Indicator Datetime */
00132     { "appointment-item",                            "appointment-label"                                  },
00133     { "timezone-item",                               "timezone-name"                                      },
00134     /* Indicator Sound */
00135     { "x-canonical-sound-menu-player-metadata-type", "x-canonical-sound-menu-player-metadata-player-name" },
00136     { "x-canonical-sound-menu-mute-type",            DBUSMENU_MENUITEM_PROP_LABEL                         },
00137     /* Indicator User */
00138     { "x-canonical-user-item",                       "user-item-name"                                     }
00139   };
00140   static GHashTable *property_hash;
00141 
00142   if G_UNLIKELY (property_hash == NULL)
00143     {
00144       gint i;
00145 
00146       property_hash = g_hash_table_new (g_str_hash, g_str_equal);
00147 
00148       for (i = 0; i < G_N_ELEMENTS (property_table); i++)
00149         g_hash_table_insert (property_hash, (gpointer) property_table[i][0], (gpointer) property_table[i][1]);
00150     }
00151 
00152   if (type == NULL)
00153     return DBUSMENU_MENUITEM_PROP_LABEL;
00154 
00155   return g_hash_table_lookup (property_hash, type);
00156 }
00157 
00158 
00159 static HudDbusmenuItem *
00160 hud_dbusmenu_item_new (HudStringList    *context,
00161                        const gchar      *desktop_file,
00162                        const gchar      *icon,
00163                        DbusmenuMenuitem *menuitem)
00164 {
00165   HudStringList *tokens;
00166   HudDbusmenuItem *item;
00167   const gchar *type;
00168   const gchar *prop;
00169   gboolean enabled;
00170 
00171   type = dbusmenu_menuitem_property_get (menuitem, DBUSMENU_MENUITEM_PROP_TYPE);
00172   prop = hud_dbusmenu_item_get_label_property (type);
00173 
00174   if (prop && dbusmenu_menuitem_property_exist (menuitem, prop))
00175     {
00176       const gchar *label;
00177 
00178       label = dbusmenu_menuitem_property_get (menuitem, prop);
00179       tokens = hud_string_list_cons_label (label, context);
00180       enabled = TRUE;
00181     }
00182   else
00183     {
00184       tokens = hud_string_list_ref (context);
00185       enabled = FALSE;
00186     }
00187 
00188   if (enabled)
00189     enabled &= !dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_VISIBLE) ||
00190                dbusmenu_menuitem_property_get_bool (menuitem, DBUSMENU_MENUITEM_PROP_VISIBLE);
00191 
00192   if (enabled)
00193     enabled &= !dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_ENABLED) ||
00194                dbusmenu_menuitem_property_get_bool (menuitem, DBUSMENU_MENUITEM_PROP_ENABLED);
00195 
00196   if (enabled)
00197     enabled &= !dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY);
00198 
00199   item = hud_item_construct (hud_dbusmenu_item_get_type (), tokens, desktop_file, icon, enabled);
00200   item->menuitem = g_object_ref (menuitem);
00201 
00202   hud_string_list_unref (tokens);
00203 
00204   return item;
00205 }
00206 
00207 struct _HudDbusmenuCollector
00208 {
00209   GObject parent_instance;
00210 
00211   DbusmenuClient *client;
00212   DbusmenuMenuitem *root;
00213   gchar *application_id;
00214   HudStringList *prefix;
00215   gchar *icon;
00216   GHashTable *items;
00217   guint penalty;
00218   guint xid;
00219   gboolean alive;
00220   gint use_count;
00221   gboolean reentrance_check;
00222 };
00223 
00224 typedef GObjectClass HudDbusmenuCollectorClass;
00225 
00226 static void hud_dbusmenu_collector_iface_init (HudSourceInterface *iface);
00227 G_DEFINE_TYPE_WITH_CODE (HudDbusmenuCollector, hud_dbusmenu_collector, G_TYPE_OBJECT,
00228                          G_IMPLEMENT_INTERFACE (HUD_TYPE_SOURCE, hud_dbusmenu_collector_iface_init))
00229 
00230 static void
00231 hud_dbusmenu_collector_open_submenu (gpointer key,
00232                                      gpointer value,
00233                                      gpointer user_data)
00234 {
00235   DbusmenuMenuitem *menuitem = key;
00236   HudDbusmenuItem *item = value;
00237 
00238   if (dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY))
00239     {
00240       dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_OPENED, NULL, 0);
00241       item->is_opened = TRUE;
00242     }
00243 }
00244 
00245 static void
00246 hud_dbusmenu_collector_close_submenu (gpointer key,
00247                                       gpointer value,
00248                                       gpointer user_data)
00249 {
00250   DbusmenuMenuitem *menuitem = key;
00251   HudDbusmenuItem *item = value;
00252 
00253   if (item->is_opened)
00254     {
00255       dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_CLOSED, NULL, 0);
00256       item->is_opened = FALSE;
00257     }
00258 }
00259 
00260 static void
00261 hud_dbusmenu_collector_use (HudSource *source)
00262 {
00263   HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (source);
00264 
00265   collector->reentrance_check = TRUE;
00266 
00267   if (collector->use_count == 0)
00268     g_hash_table_foreach (collector->items, hud_dbusmenu_collector_open_submenu, NULL);
00269 
00270   collector->use_count++;
00271 
00272   collector->reentrance_check = FALSE;
00273 }
00274 
00275 static void
00276 hud_dbusmenu_collector_unuse (HudSource *source)
00277 {
00278   HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (source);
00279 
00280   g_return_if_fail (collector->use_count > 0);
00281 
00282   collector->reentrance_check = TRUE;
00283 
00284   collector->use_count--;
00285 
00286   if (collector->use_count == 0)
00287     g_hash_table_foreach (collector->items, hud_dbusmenu_collector_close_submenu, NULL);
00288 
00289   collector->reentrance_check = FALSE;
00290 }
00291 
00292 static void
00293 hud_dbusmenu_collector_search (HudSource    *source,
00294                                GPtrArray    *results_array,
00295                                HudTokenList *search_string)
00296 {
00297   HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (source);
00298   GHashTableIter iter;
00299   gpointer item;
00300 
00301   g_hash_table_iter_init (&iter, collector->items);
00302   while (g_hash_table_iter_next (&iter, NULL, &item))
00303     {
00304       HudResult *result;
00305 
00306       result = hud_result_get_if_matched (item, search_string, collector->penalty);
00307       if (result)
00308         g_ptr_array_add (results_array, result);
00309     }
00310 }
00311 
00312 static void
00313 hud_dbusmenu_collector_add_item (HudDbusmenuCollector *collector,
00314                                  HudStringList        *context,
00315                                  DbusmenuMenuitem     *menuitem);
00316 static void
00317 hud_dbusmenu_collector_remove_item (HudDbusmenuCollector *collector,
00318                                     DbusmenuMenuitem     *menuitem);
00319 
00320 static void
00321 hud_dbusmenu_collector_child_added (DbusmenuMenuitem *menuitem,
00322                                     DbusmenuMenuitem *child,
00323                                     guint             position,
00324                                     gpointer          user_data)
00325 {
00326   HudDbusmenuCollector *collector = user_data;
00327   HudStringList *context;
00328   HudItem *item;
00329 
00330   g_assert (!collector->reentrance_check);
00331 
00332   item = g_hash_table_lookup (collector->items, menuitem);
00333   g_assert (item != NULL);
00334 
00335   context = hud_item_get_tokens (item);
00336 
00337   hud_dbusmenu_collector_add_item (collector, context, child);
00338 }
00339 
00340 static void
00341 hud_dbusmenu_collector_child_removed (DbusmenuMenuitem *menuitem,
00342                                       DbusmenuMenuitem *child,
00343                                       gpointer          user_data)
00344 {
00345   HudDbusmenuCollector *collector = user_data;
00346 
00347   g_assert (!collector->reentrance_check);
00348 
00349   hud_dbusmenu_collector_remove_item (collector, child);
00350 }
00351 
00352 static void
00353 hud_dbusmenu_collector_property_changed (DbusmenuMenuitem *menuitem,
00354                                          const gchar      *property_name,
00355                                          GVariant         *new_value,
00356                                          gpointer          user_data)
00357 {
00358   HudDbusmenuCollector *collector = user_data;
00359   DbusmenuMenuitem *parent;
00360   HudStringList *context;
00361   HudDbusmenuItem *item;
00362   gboolean was_open;
00363 
00364   g_assert (!collector->reentrance_check);
00365 
00366   parent = dbusmenu_menuitem_get_parent (menuitem);
00367 
00368   if (parent)
00369     {
00370       HudItem *parentitem;
00371 
00372       parentitem = g_hash_table_lookup (collector->items, parent);
00373       context = hud_item_get_tokens (parentitem);
00374     }
00375   else
00376     context = collector->prefix;
00377 
00378   item = g_hash_table_lookup (collector->items, menuitem);
00379   was_open = item->is_opened;
00380   g_hash_table_remove (collector->items, menuitem);
00381 
00382   item = hud_dbusmenu_item_new (context, collector->application_id, collector->icon, menuitem);
00383 
00384   if (collector->use_count && !was_open && dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY))
00385     {
00386       dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_OPENED, NULL, 0);
00387       item->is_opened = TRUE;
00388     }
00389 
00390   g_hash_table_insert (collector->items, menuitem, item);
00391 
00392   hud_source_changed (HUD_SOURCE (collector));
00393 }
00394 
00395 static void
00396 hud_dbusmenu_collector_add_item (HudDbusmenuCollector *collector,
00397                                  HudStringList        *context,
00398                                  DbusmenuMenuitem     *menuitem)
00399 {
00400   HudDbusmenuItem *item;
00401   GList *child;
00402 
00403   item = hud_dbusmenu_item_new (context, collector->application_id, collector->icon, menuitem);
00404   context = hud_item_get_tokens (HUD_ITEM (item));
00405 
00406   g_signal_connect (menuitem, "property-changed", G_CALLBACK (hud_dbusmenu_collector_property_changed), collector);
00407   g_signal_connect (menuitem, "child-added", G_CALLBACK (hud_dbusmenu_collector_child_added), collector);
00408   g_signal_connect (menuitem, "child-removed", G_CALLBACK (hud_dbusmenu_collector_child_removed), collector);
00409 
00410   /* If we're actively being queried and we add a new submenu item, open it. */
00411   if (collector->use_count && dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY))
00412     {
00413       dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_OPENED, NULL, 0);
00414       item->is_opened = TRUE;
00415     }
00416 
00417   g_hash_table_insert (collector->items, menuitem, item);
00418 
00419   for (child = dbusmenu_menuitem_get_children (menuitem); child; child = child->next)
00420     hud_dbusmenu_collector_add_item (collector, context, child->data);
00421 
00422   if (collector->alive)
00423     hud_source_changed (HUD_SOURCE (collector));
00424 }
00425 
00426 static void
00427 hud_dbusmenu_collector_remove_item (HudDbusmenuCollector *collector,
00428                                     DbusmenuMenuitem     *menuitem)
00429 {
00430   GList *child;
00431 
00432   g_signal_handlers_disconnect_by_func (menuitem, hud_dbusmenu_collector_property_changed, collector);
00433   g_signal_handlers_disconnect_by_func (menuitem, hud_dbusmenu_collector_child_added, collector);
00434   g_signal_handlers_disconnect_by_func (menuitem, hud_dbusmenu_collector_child_removed, collector);
00435   g_hash_table_remove (collector->items, menuitem);
00436 
00437   for (child = dbusmenu_menuitem_get_children (menuitem); child; child = child->next)
00438     hud_dbusmenu_collector_remove_item (collector, child->data);
00439 
00440   if (collector->alive)
00441     hud_source_changed (HUD_SOURCE (collector));
00442 }
00443 
00444 static void
00445 hud_dbusmenu_collector_setup_root (HudDbusmenuCollector *collector,
00446                                    DbusmenuMenuitem     *root)
00447 {
00448   if (collector->root)
00449     {
00450       /* If the collector has the submenus opened, close them before we
00451        * remove them all.  The use_count being non-zero will cause them
00452        * to be reopened as they are added back below (if they will be).
00453        */
00454       if (collector->use_count > 0)
00455         g_hash_table_foreach (collector->items, hud_dbusmenu_collector_close_submenu, NULL);
00456 
00457       hud_dbusmenu_collector_remove_item (collector, collector->root);
00458       g_clear_object (&collector->root);
00459     }
00460 
00461   if (root)
00462     {
00463       hud_dbusmenu_collector_add_item (collector, collector->prefix, root);
00464       collector->root = g_object_ref (root);
00465     }
00466 }
00467 
00468 static void
00469 hud_dbusmenu_collector_root_changed (DbusmenuClient   *client,
00470                                      DbusmenuMenuitem *root,
00471                                      gpointer          user_data)
00472 {
00473   HudDbusmenuCollector *collector = user_data;
00474 
00475   g_assert (!collector->reentrance_check);
00476 
00477   hud_dbusmenu_collector_setup_root (collector, root);
00478 }
00479 
00480 static void
00481 hud_dbusmenu_collector_setup_endpoint (HudDbusmenuCollector *collector,
00482                                        const gchar          *bus_name,
00483                                        const gchar          *object_path)
00484 {
00485   g_debug ("endpoint is %s %s", bus_name, object_path);
00486 
00487   if (collector->client)
00488     {
00489       g_signal_handlers_disconnect_by_func (collector->client, hud_dbusmenu_collector_root_changed, collector);
00490       hud_dbusmenu_collector_setup_root (collector, NULL);
00491       g_clear_object (&collector->client);
00492     }
00493 
00494   if (bus_name && object_path)
00495     {
00496       collector->client = dbusmenu_client_new (bus_name, object_path);
00497       g_signal_connect_object (collector->client, "root-changed",
00498                                G_CALLBACK (hud_dbusmenu_collector_root_changed), collector, 0);
00499       hud_dbusmenu_collector_setup_root (collector, dbusmenu_client_get_root (collector->client));
00500     }
00501 }
00502 
00503 static void
00504 hud_dbusmenu_collector_registrar_observer_func (HudAppMenuRegistrar *registrar,
00505                                                 guint                xid,
00506                                                 const gchar         *bus_name,
00507                                                 const gchar         *object_path,
00508                                                 gpointer             user_data)
00509 {
00510   HudDbusmenuCollector *collector = user_data;
00511 
00512   hud_dbusmenu_collector_setup_endpoint (collector, bus_name, object_path);
00513 }
00514 
00515 
00516 static void
00517 hud_dbusmenu_collector_finalize (GObject *object)
00518 {
00519   HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (object);
00520 
00521   if (collector->xid)
00522     hud_app_menu_registrar_remove_observer (hud_app_menu_registrar_get (), collector->xid,
00523                                             hud_dbusmenu_collector_registrar_observer_func, collector);
00524 
00525   /* remove all the items without firing change signals */
00526   collector->alive = FALSE;
00527   hud_dbusmenu_collector_setup_endpoint (collector, NULL, NULL);
00528 
00529   /* make sure the table is empty before we free it */
00530   g_assert (g_hash_table_size (collector->items) == 0);
00531   g_hash_table_unref (collector->items);
00532 
00533   g_free (collector->application_id);
00534   g_free (collector->icon);
00535 
00536   hud_string_list_unref (collector->prefix);
00537   g_clear_object (&collector->client);
00538 
00539   G_OBJECT_CLASS (hud_dbusmenu_collector_parent_class)
00540     ->finalize (object);
00541 }
00542 
00543 static void
00544 hud_dbusmenu_collector_init (HudDbusmenuCollector *collector)
00545 {
00546   collector->items = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
00547 }
00548 
00549 static void
00550 hud_dbusmenu_collector_iface_init (HudSourceInterface *iface)
00551 {
00552   iface->use = hud_dbusmenu_collector_use;
00553   iface->unuse = hud_dbusmenu_collector_unuse;
00554   iface->search = hud_dbusmenu_collector_search;
00555 }
00556 
00557 static void
00558 hud_dbusmenu_collector_class_init (HudDbusmenuCollectorClass *class)
00559 {
00560   class->finalize = hud_dbusmenu_collector_finalize;
00561 }
00562 
00589 HudDbusmenuCollector *
00590 hud_dbusmenu_collector_new_for_endpoint (const gchar *application_id,
00591                                          const gchar *prefix,
00592                                          const gchar *icon,
00593                                          guint        penalty,
00594                                          const gchar *bus_name,
00595                                          const gchar *object_path)
00596 {
00597   HudDbusmenuCollector *collector;
00598 
00599   collector = g_object_new (HUD_TYPE_DBUSMENU_COLLECTOR, NULL);
00600   collector->application_id = g_strdup (application_id);
00601   collector->icon = g_strdup (icon);
00602   if (prefix)
00603     collector->prefix = hud_string_list_cons (prefix, NULL);
00604   collector->penalty = penalty;
00605   hud_dbusmenu_collector_setup_endpoint (collector, bus_name, object_path);
00606 
00607   collector->alive = TRUE;
00608 
00609   return collector;
00610 }
00611 
00623 HudDbusmenuCollector *
00624 hud_dbusmenu_collector_new_for_window (BamfWindow  *window,
00625                                        const gchar *desktop_file,
00626                                        const gchar *icon)
00627 {
00628   HudDbusmenuCollector *collector;
00629 
00630   collector = g_object_new (HUD_TYPE_DBUSMENU_COLLECTOR, NULL);
00631   collector->application_id = g_strdup (desktop_file);
00632   collector->icon = g_strdup (icon);
00633   collector->xid = bamf_window_get_xid (window);
00634   g_debug ("dbusmenu on %d", collector->xid);
00635   hud_app_menu_registrar_add_observer (hud_app_menu_registrar_get (), collector->xid,
00636                                        hud_dbusmenu_collector_registrar_observer_func, collector);
00637 
00638   collector->alive = TRUE;
00639 
00640   return collector;
00641 }
00642 
00653 void
00654 hud_dbusmenu_collector_set_prefix (HudDbusmenuCollector *collector,
00655                                    const gchar          *prefix)
00656 {
00657   hud_string_list_unref (collector->prefix);
00658   collector->prefix = hud_string_list_cons (prefix, NULL);
00659   hud_dbusmenu_collector_setup_root (collector, collector->root);
00660 }
00661 
00672 void
00673 hud_dbusmenu_collector_set_icon (HudDbusmenuCollector *collector,
00674                                  const gchar          *icon)
00675 {
00676   g_free (collector->icon);
00677   collector->icon = g_strdup (icon);
00678   hud_dbusmenu_collector_setup_root (collector, collector->root);
00679 }