Back to index

indicator-appmenu  12.10.0
hudmenumodelcollector.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  * Author: Ryan Lortie <desrt@desrt.ca>
00017  */
00018 
00019 #include "hudmenumodelcollector.h"
00020 
00021 #include "hudsource.h"
00022 #include "hudresult.h"
00023 #include "huditem.h"
00024 
00025 #include <libbamf/libbamf.h>
00026 #include <gio/gio.h>
00027 
00050 struct _HudMenuModelCollector
00051 {
00052   GObject parent_instance;
00053 
00054   GSList *models;
00055   guint refresh_id;
00056   /* stuff... */
00057   GDBusMenuModel *app_menu;
00058   GDBusMenuModel *menubar;
00059   GDBusActionGroup *application;
00060   GDBusActionGroup *window;
00061 
00062   gchar *desktop_file;
00063   gchar *icon;
00064   GPtrArray *items;
00065   gint use_count;
00066 };
00067 
00068 typedef struct
00069 {
00070   HudItem parent_instance;
00071 
00072   GRemoteActionGroup *group;
00073   gchar *action_name;
00074   GVariant *target;
00075 } HudModelItem;
00076 
00077 typedef HudItemClass HudModelItemClass;
00078 
00079 G_DEFINE_TYPE (HudModelItem, hud_model_item, HUD_TYPE_ITEM)
00080 
00081 static void
00082 hud_model_item_activate (HudItem  *hud_item,
00083                          GVariant *platform_data)
00084 {
00085   HudModelItem *item = (HudModelItem *) hud_item;
00086 
00087   g_remote_action_group_activate_action_full (item->group, item->action_name, item->target, platform_data);
00088 }
00089 
00090 static void
00091 hud_model_item_finalize (GObject *object)
00092 {
00093   HudModelItem *item = (HudModelItem *) object;
00094 
00095   g_object_unref (item->group);
00096   g_free (item->action_name);
00097 
00098   if (item->target)
00099     g_variant_unref (item->target);
00100 
00101   G_OBJECT_CLASS (hud_model_item_parent_class)
00102     ->finalize (object);
00103 }
00104 
00105 static void
00106 hud_model_item_init (HudModelItem *item)
00107 {
00108 }
00109 
00110 static void
00111 hud_model_item_class_init (HudModelItemClass *class)
00112 {
00113   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
00114 
00115   gobject_class->finalize = hud_model_item_finalize;
00116 
00117   class->activate = hud_model_item_activate;
00118 }
00119 
00120 static HudItem *
00121 hud_model_item_new (HudStringList      *tokens,
00122                     const gchar        *desktop_file,
00123                     const gchar        *icon,
00124                     GRemoteActionGroup *action_group,
00125                     const gchar        *action_name,
00126                     GVariant           *target)
00127 {
00128   HudModelItem *item;
00129 
00130   item = hud_item_construct (hud_model_item_get_type (), tokens, desktop_file, icon, TRUE);
00131   item->group = g_object_ref (action_group);
00132   item->action_name = g_strdup (action_name);
00133   item->target = target ? g_variant_ref_sink (target) : NULL;
00134 
00135   return HUD_ITEM (item);
00136 }
00137 
00138 typedef GObjectClass HudMenuModelCollectorClass;
00139 
00140 static void hud_menu_model_collector_iface_init (HudSourceInterface *iface);
00141 G_DEFINE_TYPE_WITH_CODE (HudMenuModelCollector, hud_menu_model_collector, G_TYPE_OBJECT,
00142                          G_IMPLEMENT_INTERFACE (HUD_TYPE_SOURCE, hud_menu_model_collector_iface_init))
00143 
00144 /* XXX: There is a potential for unbounded recursion here if a hostile
00145  * client were to feed us corrupted data.  We should figure out a way to
00146  * address that.
00147  *
00148  * It's not really a security problem for as long as we generally trust
00149  * the programs that we run (ie: under the same UID).  If we ever start
00150  * receiving menus from untrusted sources, we need to take another look,
00151  * though.
00152  */
00153 static void hud_menu_model_collector_add_model  (HudMenuModelCollector *collector,
00154                                                  GMenuModel            *model);
00155 static void hud_menu_model_collector_disconnect (gpointer               data,
00156                                                  gpointer               user_data);
00157 
00158 static gboolean
00159 hud_menu_model_collector_refresh (gpointer user_data)
00160 {
00161   HudMenuModelCollector *collector = user_data;
00162   GSList *free_list;
00163 
00164   g_ptr_array_set_size (collector->items, 0);
00165   free_list = collector->models;
00166   collector->models = NULL;
00167 
00168   if (collector->app_menu)
00169     hud_menu_model_collector_add_model (collector, G_MENU_MODEL (collector->app_menu));
00170   if (collector->menubar)
00171     hud_menu_model_collector_add_model (collector, G_MENU_MODEL (collector->menubar));
00172 
00173   g_slist_foreach (free_list, hud_menu_model_collector_disconnect, collector);
00174   g_slist_free_full (free_list, g_object_unref);
00175 
00176   return G_SOURCE_REMOVE;
00177 }
00178 
00179 static void
00180 hud_menu_model_collector_model_changed (GMenuModel *model,
00181                                         gint        position,
00182                                         gint        removed,
00183                                         gint        added,
00184                                         gpointer    user_data)
00185 {
00186   HudMenuModelCollector *collector = user_data;
00187   static GQuark context_quark;
00188   HudStringList *context;
00189   gboolean changed;
00190   gint i;
00191 
00192   if (collector->refresh_id)
00193     /* We have a refresh scheduled already.  Ignore. */
00194     return;
00195 
00196   if (removed)
00197     {
00198       /* Items being removed is an unusual case.  Instead of having some
00199        * fancy algorithms for figuring out how to deal with that we just
00200        * start over again.
00201        *
00202        * ps: refresh_id is never set at this point (look up)
00203        */
00204       collector->refresh_id = g_idle_add (hud_menu_model_collector_refresh, collector);
00205       return;
00206     }
00207 
00208   if (!context_quark)
00209     context_quark = g_quark_from_string ("menu item context");
00210 
00211   /* The 'context' is the list of strings that got us up to where we are
00212    * now, like "View > Toolbars".  We hang this on the GMenuModel with
00213    * qdata.
00214    *
00215    * Strictly speaking, GMenuModel structures are DAGs, but we more or
00216    * less assume that they are trees here and replace the data
00217    * unconditionally when we visit it the second time (which will be
00218    * more or less never, because really, a menu is a tree).
00219    */
00220   context = g_object_get_qdata (G_OBJECT (model), context_quark);
00221 
00222   changed = FALSE;
00223   for (i = position; i < position + added; i++)
00224     {
00225       HudStringList *tokens;
00226       GMenuModel *link;
00227       gchar *value;
00228 
00229       /* If this item has a label then we add it onto the context to get
00230        * our 'tokens'.  For example, if context is 'File' then we will
00231        * have 'File > New'.
00232        *
00233        * If there is no label (which only really makes sense for
00234        * sections) then we just reuse the existing context by taking a
00235        * ref to it.
00236        *
00237        * Either way, we need to free it at the end of the loop.
00238        */
00239       if (g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_LABEL, "s", &value))
00240         {
00241           tokens = hud_string_list_cons_label (value, context);
00242           g_free (value);
00243         }
00244       else
00245         tokens = hud_string_list_ref (context);
00246 
00247       /* Check if this is an action.  Here's where we may end up
00248        * creating a HudItem.
00249        */
00250       if (g_menu_model_get_item_attribute (model, i, G_MENU_ATTRIBUTE_ACTION, "s", &value))
00251         {
00252           GDBusActionGroup *action_group = NULL;
00253 
00254           /* It's an action, so add it. */
00255           if (g_str_has_prefix (value, "app."))
00256             action_group = collector->application;
00257           else if (g_str_has_prefix (value, "win."))
00258             action_group = collector->window;
00259 
00260           if (action_group)
00261             {
00262               GVariant *target;
00263               HudItem *item;
00264 
00265               target = g_menu_model_get_item_attribute_value (model, i, G_MENU_ATTRIBUTE_TARGET, NULL);
00266 
00267               item = hud_model_item_new (tokens, collector->desktop_file, collector->icon,
00268                                          G_REMOTE_ACTION_GROUP (action_group),
00269                                          value + 4, target);
00270               g_ptr_array_add (collector->items, item);
00271 
00272               if (target)
00273                 g_variant_unref (target);
00274 
00275               changed = TRUE;
00276             }
00277 
00278           g_free (value);
00279         }
00280 
00281       /* For 'section' and 'submenu' links, we should recurse.  This is
00282        * where the danger comes in (due to the possibility of unbounded
00283        * recursion).
00284        */
00285       if ((link = g_menu_model_get_item_link (model, i, G_MENU_LINK_SECTION)))
00286         {
00287           g_object_set_qdata_full (G_OBJECT (link), context_quark,
00288                                    hud_string_list_ref (tokens),
00289                                    (GDestroyNotify) hud_string_list_unref);
00290           hud_menu_model_collector_add_model (collector, link);
00291           g_object_unref (link);
00292         }
00293 
00294       if ((link = g_menu_model_get_item_link (model, i, G_MENU_LINK_SUBMENU)))
00295         {
00296           /* for submenus, we add the submenu label to the context */
00297           g_object_set_qdata_full (G_OBJECT (link), context_quark,
00298                                    hud_string_list_ref (tokens),
00299                                    (GDestroyNotify) hud_string_list_unref);
00300           hud_menu_model_collector_add_model (collector, link);
00301           g_object_unref (link);
00302         }
00303 
00304       hud_string_list_unref (tokens);
00305     }
00306 
00307   if (changed)
00308     hud_source_changed (HUD_SOURCE (collector));
00309 }
00310 
00311 static void
00312 hud_menu_model_collector_add_model (HudMenuModelCollector *search_data,
00313                                     GMenuModel            *model)
00314 {
00315   gint n_items;
00316 
00317   g_signal_connect (model, "items-changed", G_CALLBACK (hud_menu_model_collector_model_changed), search_data);
00318   search_data->models = g_slist_prepend (search_data->models, g_object_ref (model));
00319 
00320   n_items = g_menu_model_get_n_items (model);
00321   if (n_items > 0)
00322     hud_menu_model_collector_model_changed (model, 0, 0, n_items, search_data);
00323 }
00324 
00325 static void
00326 hud_menu_model_collector_disconnect (gpointer data,
00327                                      gpointer user_data)
00328 {
00329   g_signal_handlers_disconnect_by_func (data, hud_menu_model_collector_model_changed, user_data);
00330 }
00331 
00332 static void
00333 hud_menu_model_collector_use (HudSource *source)
00334 {
00335   HudMenuModelCollector *collector = HUD_MENU_MODEL_COLLECTOR (source);
00336 
00337   collector->use_count++;
00338 }
00339 
00340 static void
00341 hud_menu_model_collector_unuse (HudSource *source)
00342 {
00343   HudMenuModelCollector *collector = HUD_MENU_MODEL_COLLECTOR (source);
00344 
00345   collector->use_count--;
00346 }
00347 
00348 static void
00349 hud_menu_model_collector_search (HudSource    *source,
00350                                  GPtrArray    *results_array,
00351                                  HudTokenList *search_string)
00352 {
00353   HudMenuModelCollector *collector = HUD_MENU_MODEL_COLLECTOR (source);
00354   GPtrArray *items;
00355   gint i;
00356 
00357   items = collector->items;
00358 
00359   for (i = 0; i < items->len; i++)
00360     {
00361       HudResult *result;
00362       HudItem *item;
00363 
00364       item = g_ptr_array_index (items, i);
00365       result = hud_result_get_if_matched (item, search_string, 0);
00366       if (result)
00367         g_ptr_array_add (results_array, result);
00368     }
00369 }
00370 static void
00371 hud_menu_model_collector_finalize (GObject *object)
00372 {
00373   HudMenuModelCollector *collector = HUD_MENU_MODEL_COLLECTOR (object);
00374 
00375   if (collector->refresh_id)
00376     g_source_remove (collector->refresh_id);
00377 
00378   g_slist_free_full (collector->models, g_object_unref);
00379   g_clear_object (&collector->app_menu);
00380   g_clear_object (&collector->menubar);
00381   g_clear_object (&collector->application);
00382   g_clear_object (&collector->window);
00383 
00384   g_free (collector->desktop_file);
00385   g_free (collector->icon);
00386 
00387   g_ptr_array_unref (collector->items);
00388 
00389   G_OBJECT_CLASS (hud_menu_model_collector_parent_class)
00390     ->finalize (object);
00391 }
00392 
00393 static void
00394 hud_menu_model_collector_init (HudMenuModelCollector *collector)
00395 {
00396   collector->items = g_ptr_array_new_with_free_func (g_object_unref);
00397 }
00398 
00399 static void
00400 hud_menu_model_collector_iface_init (HudSourceInterface *iface)
00401 {
00402   iface->use = hud_menu_model_collector_use;
00403   iface->unuse = hud_menu_model_collector_unuse;
00404   iface->search = hud_menu_model_collector_search;
00405 }
00406 
00407 static void
00408 hud_menu_model_collector_class_init (HudMenuModelCollectorClass *class)
00409 {
00410   class->finalize = hud_menu_model_collector_finalize;
00411 }
00412 
00426 HudMenuModelCollector *
00427 hud_menu_model_collector_get (BamfWindow  *window,
00428                               const gchar *desktop_file,
00429                               const gchar *icon)
00430 {
00431   HudMenuModelCollector *collector;
00432   gchar *unique_bus_name;
00433   gchar *app_menu_object_path;
00434   gchar *menubar_object_path;
00435   gchar *application_object_path;
00436   gchar *window_object_path;
00437   GDBusConnection *session;
00438 
00439   unique_bus_name = bamf_window_get_utf8_prop (window, "_GTK_UNIQUE_BUS_NAME");
00440 
00441   if (!unique_bus_name)
00442     /* If this isn't set, we won't get very far... */
00443     return NULL;
00444 
00445   collector = g_object_new (HUD_TYPE_MENU_MODEL_COLLECTOR, NULL);
00446 
00447   app_menu_object_path = bamf_window_get_utf8_prop (window, "_GTK_APP_MENU_OBJECT_PATH");
00448   menubar_object_path = bamf_window_get_utf8_prop (window, "_GTK_MENUBAR_OBJECT_PATH");
00449   application_object_path = bamf_window_get_utf8_prop (window, "_GTK_APPLICATION_OBJECT_PATH");
00450   window_object_path = bamf_window_get_utf8_prop (window, "_GTK_WINDOW_OBJECT_PATH");
00451 
00452   session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
00453 
00454   if (app_menu_object_path)
00455     {
00456       collector->app_menu = g_dbus_menu_model_get (session, unique_bus_name, app_menu_object_path);
00457       hud_menu_model_collector_add_model (collector, G_MENU_MODEL (collector->app_menu));
00458     }
00459 
00460   if (menubar_object_path)
00461     {
00462       collector->menubar = g_dbus_menu_model_get (session, unique_bus_name, menubar_object_path);
00463       hud_menu_model_collector_add_model (collector, G_MENU_MODEL (collector->menubar));
00464     }
00465 
00466   if (application_object_path)
00467     collector->application = g_dbus_action_group_get (session, unique_bus_name, application_object_path);
00468 
00469   if (window_object_path)
00470     collector->window = g_dbus_action_group_get (session, unique_bus_name, window_object_path);
00471 
00472   collector->desktop_file = g_strdup (desktop_file);
00473   collector->icon = g_strdup (icon);
00474 
00475   /* when the action groups change, we could end up having items
00476    * enabled/disabled.  how to deal with that?
00477    */
00478 
00479   g_free (unique_bus_name);
00480   g_free (app_menu_object_path);
00481   g_free (menubar_object_path);
00482   g_free (application_object_path);
00483   g_free (window_object_path);
00484 
00485   g_object_unref (session);
00486 
00487   return collector;
00488 }