2
* Copyright © 2012 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 3, as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranties of
10
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
* PURPOSE. See the GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License along
14
* with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Authors: Ryan Lortie <desrt@desrt.ca>
17
* Ted Gould <ted@canonical.com>
20
#define G_LOG_DOMAIN "huddbusmenucollector"
22
#include "huddbusmenucollector.h"
24
#include <libdbusmenu-glib/client.h>
27
#include "hudappmenuregistrar.h"
28
#include "hudresult.h"
29
#include "hudsource.h"
32
* SECTION:huddbusmenucollector
33
* @title: HudDbusmenuCollector
34
* @short_description: a #HudSource that collects #HudItems from
37
* The #HudDbusmenuCollector collects menu items from a #DbusmenuClient.
39
* There are two modes of operation.
41
* In the simple mode, the collector is created with a specified
42
* endpoint using hud_dbusmenu_collector_new_for_endpoint(). A
43
* #DbusmenuClient is constructed using this endpoint and the collector
44
* constructs #HudItems for the contents of the menu found there. This
45
* mode is intended for use with indicators.
47
* For menus associated with application windows (ie: menubars), we must
48
* consult the AppMenu registrar in order to discover the endpoint to
49
* use. This second mode of the collector is used by calling
50
* hud_dbusmenu_collector_new_for_window().
54
* HudDbusmenuCollector:
56
* This is an opaque structure type.
61
HudItem parent_instance;
63
DbusmenuMenuitem *menuitem;
67
typedef HudItemClass HudDbusmenuItemClass;
69
G_DEFINE_TYPE (HudDbusmenuItem, hud_dbusmenu_item, HUD_TYPE_ITEM)
72
hud_dbusmenu_item_activate (HudItem *hud_item,
73
GVariant *platform_data)
75
HudDbusmenuItem *item = (HudDbusmenuItem *) hud_item;
76
const gchar *startup_id;
77
guint32 timestamp = 0;
79
if (g_variant_lookup (platform_data, "desktop-startup-id", "&s", &startup_id))
81
const gchar *time_tag;
83
if ((time_tag = strstr (startup_id, "_TIME")))
87
result = g_ascii_strtoll (time_tag + 5, NULL, 10);
89
if (0 <= result && result <= G_MAXINT32)
94
dbusmenu_menuitem_handle_event(item->menuitem, DBUSMENU_MENUITEM_EVENT_ACTIVATED, NULL, timestamp);
98
hud_dbusmenu_item_finalize (GObject *object)
100
HudDbusmenuItem *item = (HudDbusmenuItem *) object;
102
g_object_unref (item->menuitem);
104
G_OBJECT_CLASS (hud_dbusmenu_item_parent_class)
109
hud_dbusmenu_item_init (HudDbusmenuItem *item)
114
hud_dbusmenu_item_class_init (HudDbusmenuItemClass *class)
116
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
118
gobject_class->finalize = hud_dbusmenu_item_finalize;
119
class->activate = hud_dbusmenu_item_activate;
123
hud_dbusmenu_item_get_label_property (const gchar *type)
125
static const gchar * const property_table[][2] =
127
{ DBUSMENU_CLIENT_TYPES_DEFAULT, DBUSMENU_MENUITEM_PROP_LABEL },
128
/* Indicator Messages */
129
{ "application-item", DBUSMENU_MENUITEM_PROP_LABEL },
130
{ "indicator-item", "indicator-label" },
131
/* Indicator Datetime */
132
{ "appointment-item", "appointment-label" },
133
{ "timezone-item", "timezone-name" },
134
/* Indicator Sound */
135
{ "x-canonical-sound-menu-player-metadata-type", "x-canonical-sound-menu-player-metadata-player-name" },
136
{ "x-canonical-sound-menu-mute-type", DBUSMENU_MENUITEM_PROP_LABEL },
138
{ "x-canonical-user-item", "user-item-name" }
140
static GHashTable *property_hash;
142
if G_UNLIKELY (property_hash == NULL)
146
property_hash = g_hash_table_new (g_str_hash, g_str_equal);
148
for (i = 0; i < G_N_ELEMENTS (property_table); i++)
149
g_hash_table_insert (property_hash, (gpointer) property_table[i][0], (gpointer) property_table[i][1]);
153
return DBUSMENU_MENUITEM_PROP_LABEL;
155
return g_hash_table_lookup (property_hash, type);
159
static HudDbusmenuItem *
160
hud_dbusmenu_item_new (HudStringList *context,
161
const gchar *desktop_file,
163
DbusmenuMenuitem *menuitem)
165
HudStringList *tokens;
166
HudDbusmenuItem *item;
171
type = dbusmenu_menuitem_property_get (menuitem, DBUSMENU_MENUITEM_PROP_TYPE);
172
prop = hud_dbusmenu_item_get_label_property (type);
174
if (prop && dbusmenu_menuitem_property_exist (menuitem, prop))
178
label = dbusmenu_menuitem_property_get (menuitem, prop);
179
tokens = hud_string_list_cons_label (label, context);
184
tokens = hud_string_list_ref (context);
189
enabled &= !dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_VISIBLE) ||
190
dbusmenu_menuitem_property_get_bool (menuitem, DBUSMENU_MENUITEM_PROP_VISIBLE);
193
enabled &= !dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_ENABLED) ||
194
dbusmenu_menuitem_property_get_bool (menuitem, DBUSMENU_MENUITEM_PROP_ENABLED);
197
enabled &= !dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY);
199
item = hud_item_construct (hud_dbusmenu_item_get_type (), tokens, desktop_file, icon, enabled);
200
item->menuitem = g_object_ref (menuitem);
202
hud_string_list_unref (tokens);
207
struct _HudDbusmenuCollector
209
GObject parent_instance;
211
DbusmenuClient *client;
212
DbusmenuMenuitem *root;
213
gchar *application_id;
214
HudStringList *prefix;
221
gboolean reentrance_check;
224
typedef GObjectClass HudDbusmenuCollectorClass;
226
static void hud_dbusmenu_collector_iface_init (HudSourceInterface *iface);
227
G_DEFINE_TYPE_WITH_CODE (HudDbusmenuCollector, hud_dbusmenu_collector, G_TYPE_OBJECT,
228
G_IMPLEMENT_INTERFACE (HUD_TYPE_SOURCE, hud_dbusmenu_collector_iface_init))
231
hud_dbusmenu_collector_open_submenu (gpointer key,
235
DbusmenuMenuitem *menuitem = key;
236
HudDbusmenuItem *item = value;
238
if (dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY))
240
dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_OPENED, NULL, 0);
241
item->is_opened = TRUE;
246
hud_dbusmenu_collector_close_submenu (gpointer key,
250
DbusmenuMenuitem *menuitem = key;
251
HudDbusmenuItem *item = value;
255
dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_CLOSED, NULL, 0);
256
item->is_opened = FALSE;
261
hud_dbusmenu_collector_use (HudSource *source)
263
HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (source);
265
collector->reentrance_check = TRUE;
267
if (collector->use_count == 0)
268
g_hash_table_foreach (collector->items, hud_dbusmenu_collector_open_submenu, NULL);
270
collector->use_count++;
272
collector->reentrance_check = FALSE;
276
hud_dbusmenu_collector_unuse (HudSource *source)
278
HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (source);
280
g_return_if_fail (collector->use_count > 0);
282
collector->reentrance_check = TRUE;
284
collector->use_count--;
286
if (collector->use_count == 0)
287
g_hash_table_foreach (collector->items, hud_dbusmenu_collector_close_submenu, NULL);
289
collector->reentrance_check = FALSE;
293
hud_dbusmenu_collector_search (HudSource *source,
294
GPtrArray *results_array,
295
HudTokenList *search_string)
297
HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (source);
301
g_hash_table_iter_init (&iter, collector->items);
302
while (g_hash_table_iter_next (&iter, NULL, &item))
306
result = hud_result_get_if_matched (item, search_string, collector->penalty);
308
g_ptr_array_add (results_array, result);
313
hud_dbusmenu_collector_add_item (HudDbusmenuCollector *collector,
314
HudStringList *context,
315
DbusmenuMenuitem *menuitem);
317
hud_dbusmenu_collector_remove_item (HudDbusmenuCollector *collector,
318
DbusmenuMenuitem *menuitem);
321
hud_dbusmenu_collector_child_added (DbusmenuMenuitem *menuitem,
322
DbusmenuMenuitem *child,
326
HudDbusmenuCollector *collector = user_data;
327
HudStringList *context;
330
g_assert (!collector->reentrance_check);
332
item = g_hash_table_lookup (collector->items, menuitem);
333
g_assert (item != NULL);
335
context = hud_item_get_tokens (item);
337
hud_dbusmenu_collector_add_item (collector, context, child);
341
hud_dbusmenu_collector_child_removed (DbusmenuMenuitem *menuitem,
342
DbusmenuMenuitem *child,
345
HudDbusmenuCollector *collector = user_data;
347
g_assert (!collector->reentrance_check);
349
hud_dbusmenu_collector_remove_item (collector, child);
353
hud_dbusmenu_collector_property_changed (DbusmenuMenuitem *menuitem,
354
const gchar *property_name,
358
HudDbusmenuCollector *collector = user_data;
359
DbusmenuMenuitem *parent;
360
HudStringList *context;
361
HudDbusmenuItem *item;
364
g_assert (!collector->reentrance_check);
366
parent = dbusmenu_menuitem_get_parent (menuitem);
372
parentitem = g_hash_table_lookup (collector->items, parent);
373
context = hud_item_get_tokens (parentitem);
376
context = collector->prefix;
378
item = g_hash_table_lookup (collector->items, menuitem);
379
was_open = item->is_opened;
380
g_hash_table_remove (collector->items, menuitem);
382
item = hud_dbusmenu_item_new (context, collector->application_id, collector->icon, menuitem);
384
if (collector->use_count && !was_open && dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY))
386
dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_OPENED, NULL, 0);
387
item->is_opened = TRUE;
390
g_hash_table_insert (collector->items, menuitem, item);
392
hud_source_changed (HUD_SOURCE (collector));
396
hud_dbusmenu_collector_add_item (HudDbusmenuCollector *collector,
397
HudStringList *context,
398
DbusmenuMenuitem *menuitem)
400
HudDbusmenuItem *item;
403
item = hud_dbusmenu_item_new (context, collector->application_id, collector->icon, menuitem);
404
context = hud_item_get_tokens (HUD_ITEM (item));
406
g_signal_connect (menuitem, "property-changed", G_CALLBACK (hud_dbusmenu_collector_property_changed), collector);
407
g_signal_connect (menuitem, "child-added", G_CALLBACK (hud_dbusmenu_collector_child_added), collector);
408
g_signal_connect (menuitem, "child-removed", G_CALLBACK (hud_dbusmenu_collector_child_removed), collector);
410
/* If we're actively being queried and we add a new submenu item, open it. */
411
if (collector->use_count && dbusmenu_menuitem_property_exist (menuitem, DBUSMENU_MENUITEM_PROP_CHILD_DISPLAY))
413
dbusmenu_menuitem_handle_event (menuitem, DBUSMENU_MENUITEM_EVENT_OPENED, NULL, 0);
414
item->is_opened = TRUE;
417
g_hash_table_insert (collector->items, menuitem, item);
419
for (child = dbusmenu_menuitem_get_children (menuitem); child; child = child->next)
420
hud_dbusmenu_collector_add_item (collector, context, child->data);
422
if (collector->alive)
423
hud_source_changed (HUD_SOURCE (collector));
427
hud_dbusmenu_collector_remove_item (HudDbusmenuCollector *collector,
428
DbusmenuMenuitem *menuitem)
432
g_signal_handlers_disconnect_by_func (menuitem, hud_dbusmenu_collector_property_changed, collector);
433
g_signal_handlers_disconnect_by_func (menuitem, hud_dbusmenu_collector_child_added, collector);
434
g_signal_handlers_disconnect_by_func (menuitem, hud_dbusmenu_collector_child_removed, collector);
435
g_hash_table_remove (collector->items, menuitem);
437
for (child = dbusmenu_menuitem_get_children (menuitem); child; child = child->next)
438
hud_dbusmenu_collector_remove_item (collector, child->data);
440
if (collector->alive)
441
hud_source_changed (HUD_SOURCE (collector));
445
hud_dbusmenu_collector_setup_root (HudDbusmenuCollector *collector,
446
DbusmenuMenuitem *root)
450
/* If the collector has the submenus opened, close them before we
451
* remove them all. The use_count being non-zero will cause them
452
* to be reopened as they are added back below (if they will be).
454
if (collector->use_count > 0)
455
g_hash_table_foreach (collector->items, hud_dbusmenu_collector_close_submenu, NULL);
457
hud_dbusmenu_collector_remove_item (collector, collector->root);
458
g_clear_object (&collector->root);
463
hud_dbusmenu_collector_add_item (collector, collector->prefix, root);
464
collector->root = g_object_ref (root);
469
hud_dbusmenu_collector_root_changed (DbusmenuClient *client,
470
DbusmenuMenuitem *root,
473
HudDbusmenuCollector *collector = user_data;
475
g_assert (!collector->reentrance_check);
477
hud_dbusmenu_collector_setup_root (collector, root);
481
hud_dbusmenu_collector_setup_endpoint (HudDbusmenuCollector *collector,
482
const gchar *bus_name,
483
const gchar *object_path)
485
g_debug ("endpoint is %s %s", bus_name, object_path);
487
if (collector->client)
489
g_signal_handlers_disconnect_by_func (collector->client, hud_dbusmenu_collector_root_changed, collector);
490
hud_dbusmenu_collector_setup_root (collector, NULL);
491
g_clear_object (&collector->client);
494
if (bus_name && object_path)
496
collector->client = dbusmenu_client_new (bus_name, object_path);
497
g_signal_connect_object (collector->client, "root-changed",
498
G_CALLBACK (hud_dbusmenu_collector_root_changed), collector, 0);
499
hud_dbusmenu_collector_setup_root (collector, dbusmenu_client_get_root (collector->client));
504
hud_dbusmenu_collector_registrar_observer_func (HudAppMenuRegistrar *registrar,
506
const gchar *bus_name,
507
const gchar *object_path,
510
HudDbusmenuCollector *collector = user_data;
512
hud_dbusmenu_collector_setup_endpoint (collector, bus_name, object_path);
517
hud_dbusmenu_collector_finalize (GObject *object)
519
HudDbusmenuCollector *collector = HUD_DBUSMENU_COLLECTOR (object);
522
hud_app_menu_registrar_remove_observer (hud_app_menu_registrar_get (), collector->xid,
523
hud_dbusmenu_collector_registrar_observer_func, collector);
525
/* remove all the items without firing change signals */
526
collector->alive = FALSE;
527
hud_dbusmenu_collector_setup_endpoint (collector, NULL, NULL);
529
/* make sure the table is empty before we free it */
530
g_assert (g_hash_table_size (collector->items) == 0);
531
g_hash_table_unref (collector->items);
533
g_free (collector->application_id);
534
g_free (collector->icon);
536
hud_string_list_unref (collector->prefix);
537
g_clear_object (&collector->client);
539
G_OBJECT_CLASS (hud_dbusmenu_collector_parent_class)
544
hud_dbusmenu_collector_init (HudDbusmenuCollector *collector)
546
collector->items = g_hash_table_new_full (NULL, NULL, NULL, g_object_unref);
550
hud_dbusmenu_collector_iface_init (HudSourceInterface *iface)
552
iface->use = hud_dbusmenu_collector_use;
553
iface->unuse = hud_dbusmenu_collector_unuse;
554
iface->search = hud_dbusmenu_collector_search;
558
hud_dbusmenu_collector_class_init (HudDbusmenuCollectorClass *class)
560
class->finalize = hud_dbusmenu_collector_finalize;
564
* hud_dbusmenu_collector_new_for_endpoint:
565
* @application_id: a unique identifier for the application
566
* @prefix: the title to prefix to all items
567
* @icon: the icon for the appliction
568
* @penalty: the penalty to apply to all results
569
* @bus_name: a D-Bus bus name
570
* @object_path: an object path at the destination given by @bus_name
572
* Creates a new #HudDbusmenuCollector for the specified endpoint.
574
* Internally, a #DbusmenuClient is created for this endpoint. Searches
575
* are performed against the contents of those menus.
577
* This call is intended to be used for indicators.
579
* If @prefix is non-%NULL (which, for indicators, it ought to be), then
580
* it is prefixed to every item created by the collector.
582
* If @penalty is non-zero then all results returned from the collector
583
* have their distance increased by a percentage equal to the penalty.
584
* This allows items from indicators to score lower than they would
587
* Returns: a new #HudDbusmenuCollector
589
HudDbusmenuCollector *
590
hud_dbusmenu_collector_new_for_endpoint (const gchar *application_id,
594
const gchar *bus_name,
595
const gchar *object_path)
597
HudDbusmenuCollector *collector;
599
collector = g_object_new (HUD_TYPE_DBUSMENU_COLLECTOR, NULL);
600
collector->application_id = g_strdup (application_id);
601
collector->icon = g_strdup (icon);
603
collector->prefix = hud_string_list_cons (prefix, NULL);
604
collector->penalty = penalty;
605
hud_dbusmenu_collector_setup_endpoint (collector, bus_name, object_path);
607
collector->alive = TRUE;
613
* hud_dbusmenu_collector_new_for_window:
614
* @window: a #BamfWindow
616
* Creates a new #HudDbusmenuCollector for the endpoint indicated by the
617
* #HudAppMenuRegistrar for @window.
619
* This call is intended to be used for application menus.
621
* Returns: a new #HudDbusmenuCollector
623
HudDbusmenuCollector *
624
hud_dbusmenu_collector_new_for_window (BamfWindow *window,
625
const gchar *desktop_file,
628
HudDbusmenuCollector *collector;
630
collector = g_object_new (HUD_TYPE_DBUSMENU_COLLECTOR, NULL);
631
collector->application_id = g_strdup (desktop_file);
632
collector->icon = g_strdup (icon);
633
collector->xid = bamf_window_get_xid (window);
634
g_debug ("dbusmenu on %d", collector->xid);
635
hud_app_menu_registrar_add_observer (hud_app_menu_registrar_get (), collector->xid,
636
hud_dbusmenu_collector_registrar_observer_func, collector);
638
collector->alive = TRUE;
644
* hud_dbusmenu_collector_set_prefix:
645
* @collector: a #HudDbusmenuCollector
646
* @prefix: the new prefix to use
648
* Changes the prefix applied to all items of the collector.
650
* This will involve destroying all of the items and recreating them
651
* (since each item's prefix has to be changed).
654
hud_dbusmenu_collector_set_prefix (HudDbusmenuCollector *collector,
657
hud_string_list_unref (collector->prefix);
658
collector->prefix = hud_string_list_cons (prefix, NULL);
659
hud_dbusmenu_collector_setup_root (collector, collector->root);
663
* hud_dbusmenu_collector_set_icon:
664
* @collector: a #HudDbusmenuCollector
665
* @icon: the application icon
667
* Changes the application icon used for all items of the collector.
669
* This will involve destroying all of the items and recreating them
670
* (since each item's icon has to be changed).
673
hud_dbusmenu_collector_set_icon (HudDbusmenuCollector *collector,
676
g_free (collector->icon);
677
collector->icon = g_strdup (icon);
678
hud_dbusmenu_collector_setup_root (collector, collector->root);