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"
31
#include "gactionmuxer.h"
32
#include "gtkmodelmenu.h"
34
struct _WindowMenuModelPrivate {
38
GActionMuxer * action_mux;
39
GtkAccelGroup * accel_group;
41
/* Application Menu */
42
GDBusMenuModel * app_menu_model;
43
IndicatorObjectEntry application_menu;
44
gboolean has_application_menu;
47
GDBusMenuModel * win_menu_model;
49
gulong win_menu_insert;
50
gulong win_menu_remove;
53
#define WINDOW_MENU_MODEL_GET_PRIVATE(o) \
54
(G_TYPE_INSTANCE_GET_PRIVATE ((o), WINDOW_MENU_MODEL_TYPE, WindowMenuModelPrivate))
56
/* Base class stuff */
57
static void window_menu_model_class_init (WindowMenuModelClass *klass);
58
static void window_menu_model_init (WindowMenuModel *self);
59
static void window_menu_model_dispose (GObject *object);
60
static void window_menu_model_finalize (GObject *object);
62
/* Window Menu subclassin' */
63
static GList * get_entries (WindowMenu * wm);
64
static guint get_location (WindowMenu * wm,
65
IndicatorObjectEntry * entry);
66
static WindowMenuStatus get_status (WindowMenu * wm);
67
static gboolean get_error_state (WindowMenu * wm);
68
static guint get_xid (WindowMenu * wm);
70
/* GLib boilerplate */
71
G_DEFINE_TYPE (WindowMenuModel, window_menu_model, WINDOW_MENU_TYPE);
73
/* Prefixes to the action muxer */
74
#define ACTION_MUX_PREFIX_WIN "win"
75
#define ACTION_MUX_PREFIX_APP "app"
77
/* Entry data on the menuitem */
78
#define ENTRY_DATA "window-menu-model-menuitem-entry"
81
window_menu_model_class_init (WindowMenuModelClass *klass)
83
GObjectClass *object_class = G_OBJECT_CLASS (klass);
85
g_type_class_add_private (klass, sizeof (WindowMenuModelPrivate));
87
object_class->dispose = window_menu_model_dispose;
88
object_class->finalize = window_menu_model_finalize;
90
WindowMenuClass * wm_class = WINDOW_MENU_CLASS(klass);
92
wm_class->get_entries = get_entries;
93
wm_class->get_location = get_location;
94
wm_class->get_status = get_status;
95
wm_class->get_error_state = get_error_state;
96
wm_class->get_xid = get_xid;
102
window_menu_model_init (WindowMenuModel *self)
104
self->priv = WINDOW_MENU_MODEL_GET_PRIVATE(self);
106
self->priv->action_mux = g_action_muxer_new();
107
self->priv->accel_group = gtk_accel_group_new();
113
window_menu_model_dispose (GObject *object)
115
WindowMenuModel * menu = WINDOW_MENU_MODEL(object);
117
g_clear_object(&menu->priv->action_mux);
118
g_clear_object(&menu->priv->accel_group);
120
/* Application Menu */
121
g_clear_object(&menu->priv->app_menu_model);
122
g_clear_object(&menu->priv->application_menu.label);
123
g_clear_object(&menu->priv->application_menu.menu);
126
if (menu->priv->win_menu_insert != 0) {
127
g_signal_handler_disconnect(menu->priv->win_menu, menu->priv->win_menu_insert);
128
menu->priv->win_menu_insert = 0;
131
if (menu->priv->win_menu_remove != 0) {
132
g_signal_handler_disconnect(menu->priv->win_menu, menu->priv->win_menu_remove);
133
menu->priv->win_menu_remove = 0;
136
g_clear_object(&menu->priv->win_menu_model);
137
g_clear_object(&menu->priv->win_menu);
139
G_OBJECT_CLASS (window_menu_model_parent_class)->dispose (object);
144
window_menu_model_finalize (GObject *object)
147
G_OBJECT_CLASS (window_menu_model_parent_class)->finalize (object);
151
/* Adds the application menu and turns the whole thing into an object
152
entry that can be used elsewhere */
154
add_application_menu (WindowMenuModel * menu, const gchar * appname, GMenuModel * model)
156
g_return_if_fail(G_IS_MENU_MODEL(model));
158
menu->priv->app_menu_model = g_object_ref(model);
160
if (appname != NULL) {
161
menu->priv->application_menu.label = GTK_LABEL(gtk_label_new(appname));
163
menu->priv->application_menu.label = GTK_LABEL(gtk_label_new(_("Unknown Application Name")));
165
g_object_ref_sink(menu->priv->application_menu.label);
166
gtk_widget_show(GTK_WIDGET(menu->priv->application_menu.label));
168
menu->priv->application_menu.menu = GTK_MENU(gtk_model_menu_create_menu(model, G_ACTION_OBSERVABLE(menu->priv->action_mux), menu->priv->accel_group));
170
gtk_widget_show(GTK_WIDGET(menu->priv->application_menu.menu));
171
g_object_ref_sink(menu->priv->application_menu.menu);
173
menu->priv->has_application_menu = TRUE;
178
/* Find the label in a GTK MenuItem */
180
mi_find_label (GtkWidget * mi)
182
if (GTK_IS_LABEL(mi)) {
183
return GTK_LABEL(mi);
186
GtkLabel * retval = NULL;
188
if (GTK_IS_CONTAINER(mi)) {
189
GList * children = gtk_container_get_children(GTK_CONTAINER(mi));
190
GList * child = children;
192
while (child != NULL && retval == NULL) {
193
if (GTK_IS_WIDGET(child->data)) {
194
retval = mi_find_label(GTK_WIDGET(child->data));
196
child = g_list_next(child);
199
g_list_free(children);
205
/* Find the icon in a GTK MenuItem */
207
mi_find_icon (GtkWidget * mi)
209
if (GTK_IS_IMAGE(mi)) {
210
return GTK_IMAGE(mi);
213
GtkImage * retval = NULL;
215
if (GTK_IS_CONTAINER(mi)) {
216
GList * children = gtk_container_get_children(GTK_CONTAINER(mi));
217
GList * child = children;
219
while (child != NULL && retval == NULL) {
220
if (GTK_IS_WIDGET(child->data)) {
221
retval = mi_find_icon(GTK_WIDGET(child->data));
223
child = g_list_next(child);
226
g_list_free(children);
232
/* Check the menu and make sure we return it if it's a menu
233
all proper like that */
235
mi_find_menu (GtkMenuItem * mi)
237
GtkWidget * retval = gtk_menu_item_get_submenu(mi);
238
if (GTK_IS_MENU(retval)) {
239
return GTK_MENU(retval);
245
typedef struct _WindowMenuEntry WindowMenuEntry;
246
struct _WindowMenuEntry {
247
IndicatorObjectEntry entry;
252
gulong sensitive_sig;
256
/* Destroy and unref the items of the object entry */
258
entry_object_free (gpointer inentry)
260
WindowMenuEntry * entry = (WindowMenuEntry *)inentry;
262
if (entry->label_sig != 0) {
263
g_signal_handler_disconnect(entry->gmi, entry->label_sig);
266
if (entry->sensitive_sig != 0) {
267
g_signal_handler_disconnect(entry->gmi, entry->sensitive_sig);
270
if (entry->visible_sig != 0) {
271
g_signal_handler_disconnect(entry->gmi, entry->visible_sig);
274
g_clear_object(&entry->entry.label);
275
g_clear_object(&entry->entry.image);
276
g_clear_object(&entry->entry.menu);
282
/* Sync the menu label changing to the label object */
284
entry_label_notify (GObject * obj, GParamSpec * pspec, gpointer user_data)
286
g_return_if_fail(GTK_IS_MENU_ITEM(obj));
288
GtkMenuItem * gmi = GTK_MENU_ITEM(obj);
289
WindowMenuEntry * entry = (WindowMenuEntry *)user_data;
291
if (entry->entry.label != NULL) {
292
const gchar * label = gtk_menu_item_get_label(gmi);
293
gtk_label_set_label(entry->entry.label, label);
299
/* Watch for visible changes and ensure they can be picked up by the
300
indicator object host */
302
entry_visible_notify (GObject * obj, GParamSpec * pspec, gpointer user_data)
304
g_return_if_fail(GTK_IS_WIDGET(obj));
305
GtkWidget * widget = GTK_WIDGET(obj);
306
WindowMenuEntry * entry = (WindowMenuEntry *)user_data;
307
gboolean visible = gtk_widget_get_visible(widget);
309
if (entry->entry.label != NULL) {
310
gtk_widget_set_visible(GTK_WIDGET(entry->entry.label), visible);
313
if (entry->entry.image != NULL) {
314
gtk_widget_set_visible(GTK_WIDGET(entry->entry.image), visible);
320
/* Watch for sensitive changes and ensure they can be picked up by the
321
indicator object host */
323
entry_sensitive_notify (GObject * obj, GParamSpec * pspec, gpointer user_data)
325
g_return_if_fail(GTK_IS_WIDGET(obj));
326
GtkWidget * widget = GTK_WIDGET(obj);
327
WindowMenuEntry * entry = (WindowMenuEntry *)user_data;
328
gboolean sensitive = gtk_widget_get_sensitive(widget);
330
if (entry->entry.label != NULL) {
331
gtk_widget_set_sensitive(GTK_WIDGET(entry->entry.label), sensitive);
334
if (entry->entry.image != NULL) {
335
gtk_widget_set_sensitive(GTK_WIDGET(entry->entry.image), sensitive);
341
/* Put an entry on a menu item */
343
entry_on_menuitem (WindowMenuModel * menu, GtkMenuItem * gmi)
345
WindowMenuEntry * entry = g_new0(WindowMenuEntry, 1);
349
entry->entry.label = mi_find_label(GTK_WIDGET(gmi));
350
entry->entry.image = mi_find_icon(GTK_WIDGET(gmi));
351
entry->entry.menu = mi_find_menu(gmi);
353
if (entry->entry.label == NULL && entry->entry.image == NULL) {
354
const gchar * label = gtk_menu_item_get_label(gmi);
356
g_warning("Item doesn't have a label or an image, aborting");
360
entry->entry.label = GTK_LABEL(gtk_label_new(label));
361
gtk_widget_show(GTK_WIDGET(entry->entry.label));
362
entry->label_sig = g_signal_connect(G_OBJECT(gmi), "notify::label", G_CALLBACK(entry_label_notify), entry->entry.label);
365
if (entry->entry.label != NULL) {
366
g_object_ref_sink(entry->entry.label);
369
if (entry->entry.image != NULL) {
370
g_object_ref_sink(entry->entry.image);
373
if (entry->entry.menu != NULL) {
374
g_object_ref_sink(entry->entry.menu);
377
entry->sensitive_sig = g_signal_connect(G_OBJECT(gmi), "notify::sensitive", G_CALLBACK(entry_sensitive_notify), entry);
378
entry->visible_sig = g_signal_connect(G_OBJECT(gmi), "notify::visible", G_CALLBACK(entry_visible_notify), entry);
380
g_object_set_data_full(G_OBJECT(gmi), ENTRY_DATA, entry, entry_object_free);
385
/* A child item was added to a menu we're watching. Let's try to integrate it. */
387
item_inserted_cb (GtkContainer *menu,
394
if (g_object_get_data(G_OBJECT(widget), ENTRY_DATA) == NULL) {
395
entry_on_menuitem(WINDOW_MENU_MODEL(data), GTK_MENU_ITEM(widget));
398
if (g_object_get_data(G_OBJECT(widget), ENTRY_DATA) != NULL) {
399
g_signal_emit_by_name(data, WINDOW_MENU_SIGNAL_ENTRY_ADDED, g_object_get_data(G_OBJECT(widget), ENTRY_DATA));
405
/* A child item was removed from a menu we're watching. */
407
item_removed_cb (GtkContainer *menu, GtkWidget *widget, gpointer data)
409
g_signal_emit_by_name(data, WINDOW_MENU_SIGNAL_ENTRY_REMOVED, g_object_get_data(G_OBJECT(widget), ENTRY_DATA));
413
/* Adds the window menu and turns it into a set of IndicatorObjectEntries
414
that can be used elsewhere */
416
add_window_menu (WindowMenuModel * menu, GMenuModel * model)
418
menu->priv->win_menu_model = g_object_ref(model);
420
menu->priv->win_menu = GTK_MENU(gtk_model_menu_create_menu(model, G_ACTION_OBSERVABLE(menu->priv->action_mux), menu->priv->accel_group));
421
g_assert(menu->priv->win_menu != NULL);
422
g_object_ref_sink(menu->priv->win_menu);
424
menu->priv->win_menu_insert = g_signal_connect(G_OBJECT (menu->priv->win_menu),
430
G_CALLBACK (item_inserted_cb),
432
menu->priv->win_menu_remove = g_signal_connect (G_OBJECT (menu->priv->win_menu),
434
G_CALLBACK (item_removed_cb),
437
GList * children = gtk_container_get_children(GTK_CONTAINER(menu->priv->win_menu));
439
for (child = children; child != NULL; child = g_list_next(child)) {
440
GtkMenuItem * gmi = GTK_MENU_ITEM(child->data);
446
entry_on_menuitem(menu, gmi);
448
g_list_free(children);
453
/* Builds the menu model from the window for the application */
455
window_menu_model_new (BamfApplication * app, BamfWindow * window)
457
g_return_val_if_fail(BAMF_IS_APPLICATION(app), NULL);
458
g_return_val_if_fail(BAMF_IS_WINDOW(window), NULL);
460
WindowMenuModel * menu = g_object_new(WINDOW_MENU_MODEL_TYPE, NULL);
462
menu->priv->xid = bamf_window_get_xid(window);
464
gchar *unique_bus_name;
465
gchar *app_menu_object_path;
466
gchar *menubar_object_path;
467
gchar *application_object_path;
468
gchar *window_object_path;
469
GDBusConnection *session;
471
unique_bus_name = bamf_window_get_utf8_prop (window, "_GTK_UNIQUE_BUS_NAME");
473
if (unique_bus_name == NULL) {
474
/* If this isn't set, we won't get very far... */
478
app_menu_object_path = bamf_window_get_utf8_prop (window, "_GTK_APP_MENU_OBJECT_PATH");
479
menubar_object_path = bamf_window_get_utf8_prop (window, "_GTK_MENUBAR_OBJECT_PATH");
480
application_object_path = bamf_window_get_utf8_prop (window, "_GTK_APPLICATION_OBJECT_PATH");
481
window_object_path = bamf_window_get_utf8_prop (window, "_GTK_WINDOW_OBJECT_PATH");
483
session = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
486
if (application_object_path != NULL) {
487
g_action_muxer_insert(menu->priv->action_mux, ACTION_MUX_PREFIX_APP, G_ACTION_GROUP(g_dbus_action_group_get (session, unique_bus_name, application_object_path)));
490
if (window_object_path != NULL) {
491
g_action_muxer_insert(menu->priv->action_mux, ACTION_MUX_PREFIX_WIN, G_ACTION_GROUP(g_dbus_action_group_get (session, unique_bus_name, window_object_path)));
494
/* Build us some menus */
495
if (app_menu_object_path != NULL) {
496
const gchar * desktop_path = bamf_application_get_desktop_file(app);
497
gchar * app_name = NULL;
499
if (desktop_path != NULL) {
500
GDesktopAppInfo * desktop = g_desktop_app_info_new_from_filename(desktop_path);
502
if (desktop != NULL) {
503
app_name = g_strdup(g_app_info_get_name(G_APP_INFO(desktop)));
505
g_object_unref(desktop);
509
GMenuModel * model = G_MENU_MODEL(g_dbus_menu_model_get (session, unique_bus_name, app_menu_object_path));
511
add_application_menu(menu, app_name, model);
513
g_object_unref(model);
517
if (menubar_object_path != NULL) {
518
GMenuModel * model = G_MENU_MODEL(g_dbus_menu_model_get (session, unique_bus_name, menubar_object_path));
520
add_window_menu(menu, model);
522
g_object_unref(model);
525
/* when the action groups change, we could end up having items
526
* enabled/disabled. how to deal with that?
529
g_free (unique_bus_name);
530
g_free (app_menu_object_path);
531
g_free (menubar_object_path);
532
g_free (application_object_path);
533
g_free (window_object_path);
535
g_object_unref (session);
540
/* Get the list of entries */
542
get_entries (WindowMenu * wm)
544
g_return_val_if_fail(IS_WINDOW_MENU_MODEL(wm), NULL);
545
WindowMenuModel * menu = WINDOW_MENU_MODEL(wm);
549
if (menu->priv->has_application_menu) {
550
ret = g_list_append(ret, &menu->priv->application_menu);
553
if (menu->priv->win_menu != NULL) {
554
GList * children = gtk_container_get_children(GTK_CONTAINER(menu->priv->win_menu));
556
for (child = children; child != NULL; child = g_list_next(child)) {
557
gpointer entry = g_object_get_data(child->data, ENTRY_DATA);
560
/* Try to build the entry, it is possible (but unlikely) that
561
we could beat the signal that this isn't created. So we'll
562
just handle that race here */
563
entry_on_menuitem(menu, GTK_MENU_ITEM(child->data));
564
entry = g_object_get_data(child->data, ENTRY_DATA);
568
ret = g_list_append(ret, entry);
572
g_list_free(children);
578
/* Find the location of an entry */
580
get_location (WindowMenu * wm, IndicatorObjectEntry * entry)
582
g_return_val_if_fail(IS_WINDOW_MENU_MODEL(wm), 0);
583
WindowMenuModel * menu = WINDOW_MENU_MODEL(wm);
585
gboolean found = FALSE;
588
if (menu->priv->has_application_menu) {
589
if (entry == &menu->priv->application_menu) {
593
/* We need to put a shift in if there is an application
594
menu and we're not looking for that one */
599
if (menu->priv->win_menu != NULL) {
600
GList * children = gtk_container_get_children(GTK_CONTAINER(menu->priv->win_menu));
602
for (child = children; child != NULL; child = g_list_next(child), pos++) {
603
gpointer lentry = g_object_get_data(child->data, ENTRY_DATA);
605
if (entry == lentry) {
611
g_list_free(children);
615
/* NOTE: Not printing any of the values here because there's
616
a pretty good chance that they're not valid. Let's not crash
618
g_warning("Unable to find entry: %p", entry);
624
/* Get's the status of the application to whether underlines should be
625
shown to the application. GMenuModel doesn't give us this info. */
626
static WindowMenuStatus
627
get_status (WindowMenu * wm)
629
return WINDOW_MENU_STATUS_NORMAL;
632
/* Says whether the application is in error, GMenuModel doesn't give us this
633
information on the app */
635
get_error_state (WindowMenu * wm)
640
/* Get the XID of this guy */
642
get_xid (WindowMenu * wm)
644
g_return_val_if_fail(IS_WINDOW_MENU_MODEL(wm), 0);
645
return WINDOW_MENU_MODEL(wm)->priv->xid;