2
* Copyright © 2012 Canonical Limited
4
* This library is free software; you can redistribute it and/or
5
* modify it under the terms of the GNU Lesser General Public
6
* License as published by the Free Software Foundation; either
7
* version 2 of the licence, or (at your option) any later version.
9
* This library is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
* Lesser General Public License for more details.
14
* You should have received a copy of the GNU Lesser General Public
15
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
17
* Author: Ted Gould <ted.gould@canonical.com>
24
#include <libbamf/libbamf.h>
27
#include <glib/gi18n.h>
28
#include <gio/gdesktopappinfo.h>
30
#include "window-menu-model.h"
32
struct _WindowMenuModelPrivate {
35
GtkAccelGroup * accel_group;
36
GActionGroup * app_actions;
37
GActionGroup * win_actions;
39
/* Application Menu */
40
GDBusMenuModel * app_menu_model;
41
IndicatorObjectEntry application_menu;
42
gboolean has_application_menu;
45
GDBusMenuModel * win_menu_model;
47
gulong win_menu_insert;
48
gulong win_menu_remove;
51
#define WINDOW_MENU_MODEL_GET_PRIVATE(o) \
52
(G_TYPE_INSTANCE_GET_PRIVATE ((o), WINDOW_MENU_MODEL_TYPE, WindowMenuModelPrivate))
54
/* Base class stuff */
55
static void window_menu_model_class_init (WindowMenuModelClass *klass);
56
static void window_menu_model_init (WindowMenuModel *self);
57
static void window_menu_model_dispose (GObject *object);
58
static void window_menu_model_finalize (GObject *object);
60
/* Window Menu subclassin' */
61
static GList * get_entries (WindowMenu * wm);
62
static guint get_location (WindowMenu * wm,
63
IndicatorObjectEntry * entry);
64
static WindowMenuStatus get_status (WindowMenu * wm);
65
static gboolean get_error_state (WindowMenu * wm);
66
static guint get_xid (WindowMenu * wm);
68
/* GLib boilerplate */
69
G_DEFINE_TYPE (WindowMenuModel, window_menu_model, WINDOW_MENU_TYPE);
71
/* Prefixes to the action muxer */
72
#define ACTION_MUX_PREFIX_WIN "win"
73
#define ACTION_MUX_PREFIX_APP "app"
75
/* Entry data on the menuitem */
76
#define ENTRY_DATA "window-menu-model-menuitem-entry"
79
window_menu_model_class_init (WindowMenuModelClass *klass)
81
GObjectClass *object_class = G_OBJECT_CLASS (klass);
83
g_type_class_add_private (klass, sizeof (WindowMenuModelPrivate));
85
object_class->dispose = window_menu_model_dispose;
86
object_class->finalize = window_menu_model_finalize;
88
WindowMenuClass * wm_class = WINDOW_MENU_CLASS(klass);
90
wm_class->get_entries = get_entries;
91
wm_class->get_location = get_location;
92
wm_class->get_status = get_status;
93
wm_class->get_error_state = get_error_state;
94
wm_class->get_xid = get_xid;
100
window_menu_model_init (WindowMenuModel *self)
102
self->priv = WINDOW_MENU_MODEL_GET_PRIVATE(self);
104
self->priv->accel_group = gtk_accel_group_new();
110
window_menu_model_dispose (GObject *object)
112
WindowMenuModel * menu = WINDOW_MENU_MODEL(object);
114
g_clear_object(&menu->priv->accel_group);
116
/* Application Menu */
117
g_clear_object(&menu->priv->app_menu_model);
118
g_clear_object(&menu->priv->application_menu.label);
119
g_clear_object(&menu->priv->application_menu.menu);
122
if (menu->priv->win_menu_insert != 0) {
123
g_signal_handler_disconnect(menu->priv->win_menu, menu->priv->win_menu_insert);
124
menu->priv->win_menu_insert = 0;
127
if (menu->priv->win_menu_remove != 0) {
128
g_signal_handler_disconnect(menu->priv->win_menu, menu->priv->win_menu_remove);
129
menu->priv->win_menu_remove = 0;
132
g_clear_object(&menu->priv->win_menu_model);
133
g_clear_object(&menu->priv->win_menu);
135
g_clear_object(&menu->priv->win_actions);
136
g_clear_object(&menu->priv->app_actions);
138
G_OBJECT_CLASS (window_menu_model_parent_class)->dispose (object);
143
window_menu_model_finalize (GObject *object)
146
G_OBJECT_CLASS (window_menu_model_parent_class)->finalize (object);
150
/* Adds the application menu and turns the whole thing into an object
151
entry that can be used elsewhere */
153
add_application_menu (WindowMenuModel * menu, const gchar * appname, GMenuModel * model)
155
g_return_if_fail(G_IS_MENU_MODEL(model));
157
menu->priv->app_menu_model = g_object_ref(model);
159
if (appname != NULL) {
160
menu->priv->application_menu.label = GTK_LABEL(gtk_label_new(appname));
162
menu->priv->application_menu.label = GTK_LABEL(gtk_label_new(_("Unknown Application Name")));
164
g_object_ref_sink(menu->priv->application_menu.label);
165
gtk_widget_show(GTK_WIDGET(menu->priv->application_menu.label));
167
menu->priv->application_menu.menu = GTK_MENU(gtk_menu_new_from_model(model));
168
if (menu->priv->app_actions) {
169
gtk_widget_insert_action_group(GTK_WIDGET(menu->priv->application_menu.menu), ACTION_MUX_PREFIX_APP, menu->priv->app_actions);
171
if (menu->priv->win_actions) {
172
gtk_widget_insert_action_group(GTK_WIDGET(menu->priv->application_menu.menu), ACTION_MUX_PREFIX_WIN, menu->priv->win_actions);
175
gtk_widget_show(GTK_WIDGET(menu->priv->application_menu.menu));
176
g_object_ref_sink(menu->priv->application_menu.menu);
178
menu->priv->has_application_menu = TRUE;
183
/* Find the label in a GTK MenuItem */
185
mi_find_label (GtkWidget * mi)
187
if (GTK_IS_LABEL(mi)) {
188
return GTK_LABEL(mi);
191
GtkLabel * retval = NULL;
193
if (GTK_IS_CONTAINER(mi)) {
194
GList * children = gtk_container_get_children(GTK_CONTAINER(mi));
195
GList * child = children;
197
while (child != NULL && retval == NULL) {
198
if (GTK_IS_WIDGET(child->data)) {
199
retval = mi_find_label(GTK_WIDGET(child->data));
201
child = g_list_next(child);
204
g_list_free(children);
210
/* Find the icon in a GTK MenuItem */
212
mi_find_icon (GtkWidget * mi)
214
if (GTK_IS_IMAGE(mi)) {
215
return GTK_IMAGE(mi);
218
GtkImage * retval = NULL;
220
if (GTK_IS_CONTAINER(mi)) {
221
GList * children = gtk_container_get_children(GTK_CONTAINER(mi));
222
GList * child = children;
224
while (child != NULL && retval == NULL) {
225
if (GTK_IS_WIDGET(child->data)) {
226
retval = mi_find_icon(GTK_WIDGET(child->data));
228
child = g_list_next(child);
231
g_list_free(children);
237
/* Check the menu and make sure we return it if it's a menu
238
all proper like that */
240
mi_find_menu (GtkMenuItem * mi)
242
GtkWidget * retval = gtk_menu_item_get_submenu(mi);
243
if (GTK_IS_MENU(retval)) {
244
return GTK_MENU(retval);
250
typedef struct _WindowMenuEntry WindowMenuEntry;
251
struct _WindowMenuEntry {
252
IndicatorObjectEntry entry;
257
gulong sensitive_sig;
261
/* Destroy and unref the items of the object entry */
263
entry_object_free (gpointer inentry)
265
WindowMenuEntry * entry = (WindowMenuEntry *)inentry;
267
if (entry->label_sig != 0) {
268
g_signal_handler_disconnect(entry->gmi, entry->label_sig);
271
if (entry->sensitive_sig != 0) {
272
g_signal_handler_disconnect(entry->gmi, entry->sensitive_sig);
275
if (entry->visible_sig != 0) {
276
g_signal_handler_disconnect(entry->gmi, entry->visible_sig);
279
g_clear_object(&entry->entry.label);
280
g_clear_object(&entry->entry.image);
281
g_clear_object(&entry->entry.menu);
287
/* Sync the menu label changing to the label object */
289
entry_label_notify (GObject * obj, GParamSpec * pspec, gpointer user_data)
291
g_return_if_fail(GTK_IS_MENU_ITEM(obj));
293
GtkMenuItem * gmi = GTK_MENU_ITEM(obj);
294
WindowMenuEntry * entry = (WindowMenuEntry *)user_data;
296
if (entry->entry.label != NULL) {
297
const gchar * label = gtk_menu_item_get_label(gmi);
298
gtk_label_set_label(entry->entry.label, label);
304
/* Watch for visible changes and ensure they can be picked up by the
305
indicator object host */
307
entry_visible_notify (GObject * obj, GParamSpec * pspec, gpointer user_data)
309
g_return_if_fail(GTK_IS_WIDGET(obj));
310
GtkWidget * widget = GTK_WIDGET(obj);
311
WindowMenuEntry * entry = (WindowMenuEntry *)user_data;
312
gboolean visible = gtk_widget_get_visible(widget);
314
if (entry->entry.label != NULL) {
315
gtk_widget_set_visible(GTK_WIDGET(entry->entry.label), visible);
318
if (entry->entry.image != NULL) {
319
gtk_widget_set_visible(GTK_WIDGET(entry->entry.image), visible);
325
/* Watch for sensitive changes and ensure they can be picked up by the
326
indicator object host */
328
entry_sensitive_notify (GObject * obj, GParamSpec * pspec, gpointer user_data)
330
g_return_if_fail(GTK_IS_WIDGET(obj));
331
GtkWidget * widget = GTK_WIDGET(obj);
332
WindowMenuEntry * entry = (WindowMenuEntry *)user_data;
333
gboolean sensitive = gtk_widget_get_sensitive(widget);
335
if (entry->entry.label != NULL) {
336
gtk_widget_set_sensitive(GTK_WIDGET(entry->entry.label), sensitive);
339
if (entry->entry.image != NULL) {
340
gtk_widget_set_sensitive(GTK_WIDGET(entry->entry.image), sensitive);
346
/* Put an entry on a menu item */
348
entry_on_menuitem (WindowMenuModel * menu, GtkMenuItem * gmi)
350
WindowMenuEntry * entry = g_new0(WindowMenuEntry, 1);
352
if (menu->priv->app_actions) {
353
gtk_widget_insert_action_group(GTK_WIDGET(gmi), ACTION_MUX_PREFIX_APP, menu->priv->app_actions);
355
if (menu->priv->win_actions) {
356
gtk_widget_insert_action_group(GTK_WIDGET(gmi), ACTION_MUX_PREFIX_WIN, menu->priv->win_actions);
362
entry->entry.label = mi_find_label(GTK_WIDGET(gmi));
363
entry->entry.image = mi_find_icon(GTK_WIDGET(gmi));
364
entry->entry.menu = mi_find_menu(gmi);
366
if (entry->entry.label == NULL && entry->entry.image == NULL) {
367
const gchar * label = gtk_menu_item_get_label(gmi);
369
g_warning("Item doesn't have a label or an image, aborting");
373
entry->entry.label = GTK_LABEL(gtk_label_new(label));
374
gtk_widget_show(GTK_WIDGET(entry->entry.label));
375
entry->label_sig = g_signal_connect(G_OBJECT(gmi), "notify::label", G_CALLBACK(entry_label_notify), entry->entry.label);
378
if (entry->entry.label != NULL) {
379
g_object_ref_sink(entry->entry.label);
382
if (entry->entry.image != NULL) {
383
g_object_ref_sink(entry->entry.image);
386
if (entry->entry.menu != NULL) {
387
g_object_ref_sink(entry->entry.menu);
390
entry->sensitive_sig = g_signal_connect(G_OBJECT(gmi), "notify::sensitive", G_CALLBACK(entry_sensitive_notify), entry);
391
entry->visible_sig = g_signal_connect(G_OBJECT(gmi), "notify::visible", G_CALLBACK(entry_visible_notify), entry);
393
g_object_set_data_full(G_OBJECT(gmi), ENTRY_DATA, entry, entry_object_free);
398
/* A child item was added to a menu we're watching. Let's try to integrate it. */
400
item_inserted_cb (GtkContainer *menu,
405
if (g_object_get_data(G_OBJECT(widget), ENTRY_DATA) == NULL) {
406
entry_on_menuitem(WINDOW_MENU_MODEL(data), GTK_MENU_ITEM(widget));
409
if (g_object_get_data(G_OBJECT(widget), ENTRY_DATA) != NULL) {
410
g_signal_emit_by_name(data, WINDOW_MENU_SIGNAL_ENTRY_ADDED, g_object_get_data(G_OBJECT(widget), ENTRY_DATA));
416
/* A child item was removed from a menu we're watching. */
418
item_removed_cb (GtkContainer *menu, GtkWidget *widget, gpointer data)
420
g_signal_emit_by_name(data, WINDOW_MENU_SIGNAL_ENTRY_REMOVED, g_object_get_data(G_OBJECT(widget), ENTRY_DATA));
424
/* Adds the window menu and turns it into a set of IndicatorObjectEntries
425
that can be used elsewhere */
427
add_window_menu (WindowMenuModel * menu, GMenuModel * model)
429
menu->priv->win_menu_model = g_object_ref(model);
431
menu->priv->win_menu = GTK_MENU(gtk_menu_new_from_model(model));
432
g_assert(menu->priv->win_menu != NULL);
433
g_object_ref_sink(menu->priv->win_menu);
435
menu->priv->win_menu_insert = g_signal_connect(G_OBJECT (menu->priv->win_menu),
437
G_CALLBACK (item_inserted_cb),
439
menu->priv->win_menu_remove = g_signal_connect (G_OBJECT (menu->priv->win_menu),
441
G_CALLBACK (item_removed_cb),
444
GList * children = gtk_container_get_children(GTK_CONTAINER(menu->priv->win_menu));
446
for (child = children; child != NULL; child = g_list_next(child)) {
447
GtkMenuItem * gmi = GTK_MENU_ITEM(child->data);
453
entry_on_menuitem(menu, gmi);
455
g_list_free(children);
460
/* Builds the menu model from the window for the application */
462
window_menu_model_new (BamfApplication * app, BamfWindow * window)
464
g_return_val_if_fail(BAMF_IS_APPLICATION(app), NULL);
465
g_return_val_if_fail(BAMF_IS_WINDOW(window), NULL);
467
WindowMenuModel * menu = g_object_new(WINDOW_MENU_MODEL_TYPE, NULL);
469
menu->priv->xid = bamf_window_get_xid(window);
471
gchar *unique_bus_name;
472
gchar *app_menu_object_path;
473
gchar *menubar_object_path;
474
gchar *application_object_path;
475
gchar *window_object_path;
476
GDBusConnection *session;
478
unique_bus_name = bamf_window_get_utf8_prop (window, "_GTK_UNIQUE_BUS_NAME");
480
if (unique_bus_name == NULL) {
481
/* If this isn't set, we won't get very far... */
485
app_menu_object_path = bamf_window_get_utf8_prop (window, "_GTK_APP_MENU_OBJECT_PATH");
486
menubar_object_path = bamf_window_get_utf8_prop (window, "_GTK_MENUBAR_OBJECT_PATH");
487
application_object_path = bamf_window_get_utf8_prop (window, "_GTK_APPLICATION_OBJECT_PATH");
488
window_object_path = bamf_window_get_utf8_prop (window, "_GTK_WINDOW_OBJECT_PATH");
490
session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
493
if (application_object_path != NULL) {
494
menu->priv->app_actions = G_ACTION_GROUP(g_dbus_action_group_get (session, unique_bus_name, application_object_path));
497
if (window_object_path != NULL) {
498
menu->priv->win_actions = G_ACTION_GROUP(g_dbus_action_group_get (session, unique_bus_name, window_object_path));
501
/* Build us some menus */
502
if (app_menu_object_path != NULL) {
503
const gchar * desktop_path = bamf_application_get_desktop_file(app);
504
gchar * app_name = NULL;
506
if (desktop_path != NULL) {
507
GDesktopAppInfo * desktop = g_desktop_app_info_new_from_filename(desktop_path);
509
if (desktop != NULL) {
510
app_name = g_strdup(g_app_info_get_name(G_APP_INFO(desktop)));
512
g_object_unref(desktop);
516
GMenuModel * model = G_MENU_MODEL(g_dbus_menu_model_get (session, unique_bus_name, app_menu_object_path));
518
add_application_menu(menu, app_name, model);
520
g_object_unref(model);
524
if (menubar_object_path != NULL) {
525
GMenuModel * model = G_MENU_MODEL(g_dbus_menu_model_get (session, unique_bus_name, menubar_object_path));
527
add_window_menu(menu, model);
529
g_object_unref(model);
532
/* when the action groups change, we could end up having items
533
* enabled/disabled. how to deal with that?
536
g_free (unique_bus_name);
537
g_free (app_menu_object_path);
538
g_free (menubar_object_path);
539
g_free (application_object_path);
540
g_free (window_object_path);
542
g_object_unref (session);
547
/* Get the list of entries */
549
get_entries (WindowMenu * wm)
551
g_return_val_if_fail(IS_WINDOW_MENU_MODEL(wm), NULL);
552
WindowMenuModel * menu = WINDOW_MENU_MODEL(wm);
556
if (menu->priv->has_application_menu) {
557
ret = g_list_append(ret, &menu->priv->application_menu);
560
if (menu->priv->win_menu != NULL) {
561
GList * children = gtk_container_get_children(GTK_CONTAINER(menu->priv->win_menu));
563
for (child = children; child != NULL; child = g_list_next(child)) {
564
gpointer entry = g_object_get_data(child->data, ENTRY_DATA);
567
/* Try to build the entry, it is possible (but unlikely) that
568
we could beat the signal that this isn't created. So we'll
569
just handle that race here */
570
entry_on_menuitem(menu, GTK_MENU_ITEM(child->data));
571
entry = g_object_get_data(child->data, ENTRY_DATA);
575
ret = g_list_append(ret, entry);
579
g_list_free(children);
585
/* Find the location of an entry */
587
get_location (WindowMenu * wm, IndicatorObjectEntry * entry)
589
g_return_val_if_fail(IS_WINDOW_MENU_MODEL(wm), 0);
590
WindowMenuModel * menu = WINDOW_MENU_MODEL(wm);
592
gboolean found = FALSE;
595
if (menu->priv->has_application_menu) {
596
if (entry == &menu->priv->application_menu) {
600
/* We need to put a shift in if there is an application
601
menu and we're not looking for that one */
606
if (menu->priv->win_menu != NULL) {
607
GList * children = gtk_container_get_children(GTK_CONTAINER(menu->priv->win_menu));
609
for (child = children; child != NULL; child = g_list_next(child), pos++) {
610
gpointer lentry = g_object_get_data(child->data, ENTRY_DATA);
612
if (entry == lentry) {
618
g_list_free(children);
622
/* NOTE: Not printing any of the values here because there's
623
a pretty good chance that they're not valid. Let's not crash
625
g_warning("Unable to find entry: %p", entry);
631
/* Get's the status of the application to whether underlines should be
632
shown to the application. GMenuModel doesn't give us this info. */
633
static WindowMenuStatus
634
get_status (WindowMenu * wm)
636
return WINDOW_MENU_STATUS_NORMAL;
639
/* Says whether the application is in error, GMenuModel doesn't give us this
640
information on the app */
642
get_error_state (WindowMenu * wm)
647
/* Get the XID of this guy */
649
get_xid (WindowMenu * wm)
651
g_return_val_if_fail(IS_WINDOW_MENU_MODEL(wm), 0);
652
return WINDOW_MENU_MODEL(wm)->priv->xid;