2
An object to collect the various DBusmenu objects that exist
5
Copyright 2011 Canonical Ltd.
8
Ted Gould <ted@canonical.com>
10
This program is free software: you can redistribute it and/or modify it
11
under the terms of the GNU General Public License version 3, as published
12
by the Free Software Foundation.
14
This program is distributed in the hope that it will be useful, but
15
WITHOUT ANY WARRANTY; without even the implied warranties of
16
MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
17
PURPOSE. See the GNU General Public License for more details.
19
You should have received a copy of the GNU General Public License along
20
with this program. If not, see <http://www.gnu.org/licenses/>.
29
#include <glib/gi18n.h>
31
#include <libdbusmenu-glib/client.h>
33
#include "dbusmenu-collector.h"
35
#include "indicator-tracker.h"
36
#include "shared-values.h"
38
#define GENERIC_ICON "dbusmenu-lens-panel"
40
typedef struct _DbusmenuCollectorPrivate DbusmenuCollectorPrivate;
42
struct _DbusmenuCollectorPrivate {
43
GDBusConnection * bus;
46
IndicatorTracker * tracker;
49
struct _DbusmenuCollectorFound {
54
gchar * display_string;
58
DbusmenuMenuitem * item;
62
typedef struct _menu_key_t menu_key_t;
68
typedef struct _search_item_t search_item_t;
69
struct _search_item_t {
74
static void dbusmenu_collector_class_init (DbusmenuCollectorClass *klass);
75
static void dbusmenu_collector_init (DbusmenuCollector *self);
76
static void dbusmenu_collector_dispose (GObject *object);
77
static void dbusmenu_collector_finalize (GObject *object);
78
static void update_layout_cb (GDBusConnection * connection, const gchar * sender, const gchar * path, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data);
79
static guint menu_hash_func (gconstpointer key);
80
static gboolean menu_equal_func (gconstpointer a, gconstpointer b);
81
static void menu_key_destroy (gpointer key);
82
static DbusmenuCollectorFound * dbusmenu_collector_found_new (DbusmenuClient * client, DbusmenuMenuitem * item, GStrv strings, guint distance, GStrv usedstrings, const gchar * indicator_name);
84
G_DEFINE_TYPE (DbusmenuCollector, dbusmenu_collector, G_TYPE_OBJECT);
87
dbusmenu_collector_class_init (DbusmenuCollectorClass *klass)
89
GObjectClass *object_class = G_OBJECT_CLASS (klass);
91
g_type_class_add_private (klass, sizeof (DbusmenuCollectorPrivate));
93
object_class->dispose = dbusmenu_collector_dispose;
94
object_class->finalize = dbusmenu_collector_finalize;
100
dbusmenu_collector_init (DbusmenuCollector *self)
102
self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_COLLECTOR_TYPE, DbusmenuCollectorPrivate);
104
self->priv->bus = NULL;
105
self->priv->signal = 0;
106
self->priv->hash = NULL;
107
self->priv->tracker = NULL;
109
self->priv->hash = g_hash_table_new_full(menu_hash_func, menu_equal_func,
110
menu_key_destroy, g_object_unref /* DbusmenuClient */);
112
self->priv->bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, NULL);
113
self->priv->signal = g_dbus_connection_signal_subscribe(self->priv->bus,
115
"com.canonical.dbusmenu", /* interface */
116
"LayoutUpdated", /* member */
117
NULL, /* object path */
119
G_DBUS_SIGNAL_FLAGS_NONE, /* flags */
120
update_layout_cb, /* cb */
122
NULL); /* free func */
124
GError * error = NULL;
125
g_dbus_connection_emit_signal(self->priv->bus,
126
NULL, /* destination */
128
"com.canonical.dbusmenu",
133
g_warning("Unable to emit 'FindServers': %s", error->message);
137
self->priv->tracker = indicator_tracker_new();
143
dbusmenu_collector_dispose (GObject *object)
145
DbusmenuCollector * collector = DBUSMENU_COLLECTOR(object);
147
if (collector->priv->signal != 0) {
148
g_dbus_connection_signal_unsubscribe(collector->priv->bus, collector->priv->signal);
149
collector->priv->signal = 0;
152
if (collector->priv->hash != NULL) {
153
g_hash_table_destroy(collector->priv->hash);
154
collector->priv->hash = NULL;
157
if (collector->priv->tracker != NULL) {
158
g_object_unref(collector->priv->tracker);
159
collector->priv->tracker = NULL;
162
G_OBJECT_CLASS (dbusmenu_collector_parent_class)->dispose (object);
167
dbusmenu_collector_finalize (GObject *object)
170
G_OBJECT_CLASS (dbusmenu_collector_parent_class)->finalize (object);
175
dbusmenu_collector_new (void)
177
return DBUSMENU_COLLECTOR(g_object_new(DBUSMENU_COLLECTOR_TYPE, NULL));
181
update_layout_cb (GDBusConnection * connection, const gchar * sender, const gchar * path, const gchar * interface, const gchar * signal, GVariant * params, gpointer user_data)
183
g_debug("Client updating %s:%s", sender, path);
184
DbusmenuCollector * collector = DBUSMENU_COLLECTOR(user_data);
185
g_return_if_fail(collector != NULL);
187
menu_key_t search_key = {
188
sender: (gchar *)sender,
192
gpointer found = g_hash_table_lookup(collector->priv->hash, &search_key);
194
/* Build one becasue we don't have it */
195
menu_key_t * built_key = g_new0(menu_key_t, 1);
196
built_key->sender = g_strdup(sender);
197
built_key->path = g_strdup(path);
199
DbusmenuClient * client = dbusmenu_client_new(sender, path);
201
g_hash_table_insert(collector->priv->hash, built_key, client);
204
/* Assume that dbusmenu client is doing this for us */
209
menu_hash_func (gconstpointer key)
211
const menu_key_t * mk = (const menu_key_t*)key;
213
return g_str_hash(mk->sender) + g_str_hash(mk->path) - 5381;
217
menu_equal_func (gconstpointer a, gconstpointer b)
219
const menu_key_t * ak = (const menu_key_t *)a;
220
const menu_key_t * bk = (const menu_key_t *)b;
222
if (g_strcmp0(ak->sender, bk->sender) == 0) {
226
if (g_strcmp0(ak->path, bk->path) == 0) {
234
menu_key_destroy (gpointer key)
236
menu_key_t * ikey = (menu_key_t *)key;
238
g_free(ikey->sender);
246
remove_underline (const gchar * input)
248
const gchar * in = input;
249
gchar * output = g_new0(gchar, g_utf8_strlen(input, -1) + 1);
250
gchar * out = output;
252
while (in[0] != '\0') {
266
tokens_to_children (DbusmenuMenuitem * rootitem, const gchar * search, GList * results, GStrv label_prefix, DbusmenuClient * client, const gchar * indicator_name)
268
if (search == NULL) {
272
if (rootitem == NULL) {
276
if (!dbusmenu_menuitem_property_get_bool(rootitem, DBUSMENU_MENUITEM_PROP_ENABLED)) {
280
if (!dbusmenu_menuitem_property_get_bool(rootitem, DBUSMENU_MENUITEM_PROP_VISIBLE)) {
285
if (dbusmenu_menuitem_property_exist(rootitem, DBUSMENU_MENUITEM_PROP_LABEL) &&
286
!dbusmenu_menuitem_property_exist(rootitem, DBUSMENU_MENUITEM_PROP_TYPE)) {
287
const gchar * label = dbusmenu_menuitem_property_get(rootitem, DBUSMENU_MENUITEM_PROP_LABEL);
289
if (label_prefix != NULL && label_prefix[0] != NULL) {
291
guint prefix_len = g_strv_length(label_prefix);
292
newstr = g_new(gchar *, prefix_len + 2);
294
for (i = 0; i < prefix_len; i++) {
295
newstr[i] = g_strdup(label_prefix[i]);
298
newstr[prefix_len] = g_strdup(label);
299
newstr[prefix_len + 1] = NULL;
301
newstr = g_new0(gchar *, 2);
302
newstr[0] = g_strdup(label);
307
if (!dbusmenu_menuitem_get_root(rootitem) && newstr != NULL) {
308
GStrv used_strings = NULL;
309
guint distance = calculate_distance(search, newstr, &used_strings);
310
if (distance < G_MAXUINT) {
311
// g_debug("Distance %d for '%s' in \"'%s'\" using \"'%s'\"", distance, search, g_strjoinv("' '", newstr), g_strjoinv("' '", used_strings));
312
results = g_list_prepend(results, dbusmenu_collector_found_new(client, rootitem, newstr, distance, used_strings, indicator_name));
314
g_strfreev(used_strings);
317
if (newstr == NULL) {
318
newstr = g_strdupv(label_prefix);
321
GList * children = dbusmenu_menuitem_get_children(rootitem);
324
for (child = children; child != NULL; child = g_list_next(child)) {
325
DbusmenuMenuitem * item = DBUSMENU_MENUITEM(child->data);
327
results = tokens_to_children(item, search, results, newstr, client, indicator_name);
335
process_client (DbusmenuCollector * collector, DbusmenuClient * client, const gchar * search, GList * results, const gchar * indicator_name, GStrv prefix)
337
/* Handle the case where there are no search terms */
338
if (search == NULL || search[0] == '\0') {
339
GList * children = dbusmenu_menuitem_get_children(dbusmenu_client_get_root(client));
342
for (child = children; child != NULL; child = g_list_next(child)) {
343
DbusmenuMenuitem * item = DBUSMENU_MENUITEM(child->data);
345
if (!dbusmenu_menuitem_property_exist(item, DBUSMENU_MENUITEM_PROP_LABEL)) {
349
const gchar * label = dbusmenu_menuitem_property_get(item, DBUSMENU_MENUITEM_PROP_LABEL);
350
const gchar * array[2];
354
results = g_list_prepend(results, dbusmenu_collector_found_new(client, item, (GStrv)array, calculate_distance(NULL, (GStrv)array, NULL), NULL, indicator_name));
360
results = tokens_to_children(dbusmenu_client_get_root(client), search, results, prefix, client, indicator_name);
365
just_do_it (DbusmenuCollector * collector, const gchar * dbus_addr, const gchar * dbus_path, const gchar * search, GList * results, const gchar * indicator_name, GStrv prefix)
367
g_return_val_if_fail(IS_DBUSMENU_COLLECTOR(collector), results);
369
menu_key_t search_key = {
370
sender: (gchar *)dbus_addr,
371
path: (gchar *)dbus_path
374
gpointer found = g_hash_table_lookup(collector->priv->hash, &search_key);
376
results = process_client(collector, DBUSMENU_CLIENT(found), search, results, indicator_name, prefix);
383
dbusmenu_collector_found_sort (gconstpointer a, gconstpointer b)
385
DbusmenuCollectorFound * founda;
386
DbusmenuCollectorFound * foundb;
388
founda = (DbusmenuCollectorFound *)a;
389
foundb = (DbusmenuCollectorFound *)b;
391
return dbusmenu_collector_found_get_distance(founda) - dbusmenu_collector_found_get_distance(foundb);
395
dbusmenu_collector_search (DbusmenuCollector * collector, const gchar * dbus_addr, const gchar * dbus_path, const gchar * search)
397
GList * items = NULL;
399
if (dbus_addr != NULL && dbus_path != NULL) {
400
items = just_do_it(collector, dbus_addr, dbus_path, search, NULL, NULL, NULL);
403
/* This is where we'll do the indicators if we're not
404
looking at the null search. In that case we'll let
405
the client take over. */
406
if (search != NULL && search[0] != '\0') {
407
GArray * indicators = indicator_tracker_get_indicators(collector->priv->tracker);
409
for (indicator_cnt = 0; indicator_cnt < indicators->len; indicator_cnt++) {
410
IndicatorTrackerIndicator * indicator = &g_array_index(indicators, IndicatorTrackerIndicator, indicator_cnt);
413
array[0] = indicator->prefix;
416
GList * iitems = just_do_it(collector, indicator->dbus_name, indicator->dbus_object, search, NULL, indicator->name, array);
418
/* Increase indicator's distance by 50% */
419
GList * iitem = iitems;
420
while (iitem != NULL) {
421
DbusmenuCollectorFound * found = (DbusmenuCollectorFound *)iitem->data;
422
found->distance = found->distance + (found->distance / 2);
423
iitem = g_list_next(iitem);
426
items = g_list_concat(items, iitems);
430
items = g_list_sort(items, dbusmenu_collector_found_sort);
436
dbusmenu_collector_execute (DbusmenuCollector * collector, const gchar * dbus_addr, const gchar * dbus_path, gint id, guint timestamp)
438
g_return_if_fail(IS_DBUSMENU_COLLECTOR(collector));
440
menu_key_t search_key = {
441
sender: (gchar *)dbus_addr,
442
path: (gchar *)dbus_path
445
gpointer found = g_hash_table_lookup(collector->priv->hash, &search_key);
447
g_warning("Unable to find dbusmenu client: %s:%s", dbus_addr, dbus_path);
451
DbusmenuMenuitem * root = dbusmenu_client_get_root(DBUSMENU_CLIENT(found));
453
g_warning("Dbusmenu Client %s:%s has no menuitems", dbus_addr, dbus_path);
457
DbusmenuMenuitem * item = dbusmenu_menuitem_find_id(root, id);
459
g_warning("Unable to find menuitem %d on client %s:%s", id, dbus_addr, dbus_path);
463
g_debug("Executing menuitem %d: %s", id, dbusmenu_menuitem_property_get(item, DBUSMENU_MENUITEM_PROP_LABEL));
464
dbusmenu_menuitem_handle_event(item, DBUSMENU_MENUITEM_EVENT_ACTIVATED, NULL, timestamp);
470
dbusmenu_collector_found_get_distance (DbusmenuCollectorFound * found)
472
g_return_val_if_fail(found != NULL, G_MAXUINT);
473
return found->distance;
477
dbusmenu_collector_found_get_display (DbusmenuCollectorFound * found)
479
g_return_val_if_fail(found != NULL, NULL);
480
return found->display_string;
484
dbusmenu_collector_found_get_db (DbusmenuCollectorFound * found)
486
g_return_val_if_fail(found != NULL, NULL);
487
return found->db_string;
491
dbusmenu_collector_found_list_free (GList * found_list)
493
g_list_free_full(found_list, (GDestroyNotify)dbusmenu_collector_found_free);
497
static DbusmenuCollectorFound *
498
dbusmenu_collector_found_new (DbusmenuClient * client, DbusmenuMenuitem * item, GStrv strings, guint distance, GStrv usedstrings, const gchar * indicator_name)
500
// g_debug("New Found: '%s', %d, '%s'", string, distance, indicator_name);
501
DbusmenuCollectorFound * found = g_new0(DbusmenuCollectorFound, 1);
504
DBUSMENU_CLIENT_PROP_DBUS_NAME, &found->dbus_addr,
505
DBUSMENU_CLIENT_PROP_DBUS_OBJECT, &found->dbus_path,
507
found->dbus_id = dbusmenu_menuitem_get_id(item);
509
found->db_string = g_strjoinv(DB_SEPARATOR, strings);
510
found->distance = distance;
512
found->indicator = NULL;
514
found->display_string = NULL;
515
if (strings != NULL) {
516
static gchar * connector = NULL;
518
if (connector == NULL) {
519
/* TRANSLATORS: This string is a printf format string to build
520
a string representing menu hierarchy in an application. The
521
strings are <top> <separator> <bottom>. So if the separator
522
is ">" and the item is "Open" in the "File" menu the final
523
string would be "File > Open" */
524
connector = g_markup_escape_text(_("%s > %s"), -1);
527
gchar * firstunderline = remove_underline(strings[0]);
528
found->display_string = g_markup_escape_text(firstunderline, -1);
529
g_free(firstunderline);
531
for (i = 1; strings[i] != NULL; i++) {
532
gchar * nounder = remove_underline(strings[i]);
533
gchar * escaped = g_markup_escape_text(nounder, -1);
534
gchar * tmp = g_strdup_printf(connector, found->display_string, escaped);
535
g_free(found->display_string);
538
found->display_string = tmp;
541
/* NOTE: Should probably find some way to use remalloc here, not sure
542
how to do that with the escaping and the translated connector
543
though. Will take some thinking. */
546
if (found->display_string != NULL && usedstrings != NULL) {
548
for (str = 0; usedstrings[str] != NULL; str++) {
549
if (usedstrings[str][0] == '\0') continue; // No NULL strings
550
gchar * nounder = remove_underline(usedstrings[str]);
551
GStrv split = g_strsplit(found->display_string, nounder, -1);
552
gchar * bold = g_strconcat("<b>", nounder, "</b>", NULL);
553
gchar * tmp = g_strjoinv(bold, split);
555
g_free(found->display_string);
556
found->display_string = tmp;
564
if (indicator_name != NULL) {
565
found->indicator = g_strdup(indicator_name);
568
g_object_ref(G_OBJECT(item));
574
dbusmenu_collector_found_free (DbusmenuCollectorFound * found)
576
g_return_if_fail(found != NULL);
577
g_free(found->dbus_addr);
578
g_free(found->dbus_path);
579
g_free(found->display_string);
580
g_free(found->db_string);
581
g_free(found->indicator);
582
g_object_unref(found->item);
588
dbusmenu_collector_found_get_indicator (DbusmenuCollectorFound * found)
590
// g_debug("Getting indicator for found '%s', indicator: '%s'", found->display_string, found->indicator);
591
g_return_val_if_fail(found != NULL, NULL);
592
return found->indicator;
596
dbusmenu_collector_found_get_dbus_addr (DbusmenuCollectorFound * found)
598
g_return_val_if_fail(found != NULL, NULL);
599
return found->dbus_addr;
603
dbusmenu_collector_found_get_dbus_path (DbusmenuCollectorFound * found)
605
g_return_val_if_fail(found != NULL, NULL);
606
return found->dbus_path;
610
dbusmenu_collector_found_get_dbus_id (DbusmenuCollectorFound * found)
612
g_return_val_if_fail(found != NULL, -1);
613
return found->dbus_id;