1 /*
2 * Copyright (C) 2016 Alberts Muktupāvels
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program 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
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <config.h>
19
20 #include "sn-dbus-menu.h"
21 #include "sn-dbus-menu-gen.h"
22 #include "sn-dbus-menu-item.h"
23
24 struct _SnDBusMenu
25 {
26 GtkMenu parent;
27
28 GHashTable *items;
29
30 GCancellable *cancellable;
31
32 gchar *bus_name;
33 gchar *object_path;
34 guint name_id;
35
36 SnDBusMenuGen *proxy;
37 };
38
39 enum
40 {
41 PROP_0,
42
43 PROP_BUS_NAME,
44 PROP_OBJECT_PATH,
45
46 LAST_PROP
47 };
48
49 static GParamSpec *properties[LAST_PROP] = { NULL };
50
51 static const gchar *property_names[] =
52 {
53 "accessible-desc",
54 "children-display",
55 "disposition",
56 "enabled",
57 "icon-data",
58 "icon-name",
59 "label",
60 "shortcut",
61 "toggle-type",
62 "toggle-state",
63 "type",
64 "visible",
65 NULL
66 };
67
G_DEFINE_TYPE(SnDBusMenu,sn_dbus_menu,GTK_TYPE_MENU)68 G_DEFINE_TYPE (SnDBusMenu, sn_dbus_menu, GTK_TYPE_MENU)
69
70 static void
71 activate_cb (GtkWidget *widget,
72 SnDBusMenu *menu)
73 {
74 guint id;
75
76 if (gtk_menu_item_get_submenu (GTK_MENU_ITEM (widget)) != NULL)
77 return;
78
79 id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "item-id"));
80 sn_dbus_menu_gen_call_event_sync (menu->proxy, id, "clicked",
81 g_variant_new ("v", g_variant_new_int32 (0)),
82 gtk_get_current_event_time (), NULL, NULL);
83 }
84
85 static GtkMenu *
layout_update_item(SnDBusMenu * menu,GtkMenu * gtk_menu,guint id,GVariant * props)86 layout_update_item (SnDBusMenu *menu,
87 GtkMenu *gtk_menu,
88 guint id,
89 GVariant *props)
90 {
91 SnDBusMenuItem *item;
92
93 if (id == 0)
94 return gtk_menu;
95
96 item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
97
98 if (item == NULL)
99 {
100 item = sn_dbus_menu_item_new (props);
101
102 g_object_set_data (G_OBJECT (item->item), "item-id", GUINT_TO_POINTER (id));
103 gtk_menu_shell_append (GTK_MENU_SHELL (gtk_menu), item->item);
104
105 item->activate_id = g_signal_connect (item->item, "activate",
106 G_CALLBACK (activate_cb), menu);
107
108 g_hash_table_replace (menu->items, GUINT_TO_POINTER (id), item);
109 }
110 else
111 {
112 sn_dbus_menu_item_update_props (item, props);
113 }
114 return item->submenu;
115 }
116
117 static void
layout_parse(SnDBusMenu * menu,GVariant * layout,GtkMenu * gtk_menu)118 layout_parse (SnDBusMenu *menu,
119 GVariant *layout,
120 GtkMenu *gtk_menu)
121 {
122 guint id;
123 GVariant *props;
124 GVariant *items;
125 GtkMenu *submenu;
126 GVariantIter iter;
127 GVariant *child;
128
129 if (!g_variant_is_of_type (layout, G_VARIANT_TYPE ("(ia{sv}av)")))
130 {
131 g_warning ("Type of return value for 'layout' property in "
132 "'GetLayout' call should be '(ia{sv}av)' but got '%s'",
133 g_variant_get_type_string (layout));
134
135 return;
136 }
137
138 g_variant_get (layout, "(i@a{sv}@av)", &id, &props, &items);
139
140 submenu = layout_update_item (menu, gtk_menu, id, props);
141 g_variant_unref (props);
142
143 g_variant_iter_init (&iter, items);
144 while ((child = g_variant_iter_next_value (&iter)))
145 {
146 GVariant *value;
147
148 value = g_variant_get_variant (child);
149
150 layout_parse (menu, value, submenu);
151 g_variant_unref (value);
152
153 g_variant_unref (child);
154 }
155
156 g_variant_unref (items);
157 }
158
159 static void
get_layout_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)160 get_layout_cb (GObject *source_object,
161 GAsyncResult *res,
162 gpointer user_data)
163 {
164 GVariant *layout;
165 guint revision;
166 GError *error;
167 SnDBusMenu *menu;
168
169 error = NULL;
170 sn_dbus_menu_gen_call_get_layout_finish (SN_DBUS_MENU_GEN (source_object),
171 &revision, &layout, res, &error);
172
173 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
174 {
175 g_error_free (error);
176 return;
177 }
178
179 menu = SN_DBUS_MENU (user_data);
180
181 if (error != NULL)
182 {
183 g_warning ("%s", error->message);
184 g_error_free (error);
185 return;
186 }
187
188 g_hash_table_remove_all (menu->items);
189 layout_parse (menu, layout, GTK_MENU (menu));
190
191 /* Reposition menu to accomodate any size changes */
192 /* Menu size never changes with GTK 3.20 or earlier */
193 gtk_menu_reposition(GTK_MENU(menu));
194
195 g_variant_unref (layout);
196 }
197
198 static void
update_layout(SnDBusMenu * menu,gint parent)199 update_layout (SnDBusMenu *menu,
200 gint parent)
201 {
202 gint depth;
203
204 parent = 0;
205 depth = -1;
206
207 sn_dbus_menu_gen_call_get_layout (menu->proxy, parent, depth,
208 property_names, menu->cancellable,
209 get_layout_cb, menu);
210 }
211
212 static void
items_properties_updated_cb(SnDBusMenuGen * proxy,GVariant * updated_props,GVariant * removed_props,SnDBusMenu * menu)213 items_properties_updated_cb (SnDBusMenuGen *proxy,
214 GVariant *updated_props,
215 GVariant *removed_props,
216 SnDBusMenu *menu)
217 {
218 GVariantIter iter;
219 guint id;
220 GVariant *props;
221 SnDBusMenuItem *item;
222
223 g_variant_iter_init (&iter, updated_props);
224 while (g_variant_iter_next (&iter, "(i@a{sv})", &id, &props))
225 {
226 item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
227
228 if (item != NULL)
229 sn_dbus_menu_item_update_props (item, props);
230
231 g_variant_unref (props);
232 }
233
234 g_variant_iter_init (&iter, removed_props);
235 while (g_variant_iter_next (&iter, "(i@as)", &id, &props))
236 {
237 item = g_hash_table_lookup (menu->items, GUINT_TO_POINTER (id));
238
239 if (item != NULL)
240 sn_dbus_menu_item_remove_props (item, props);
241
242 g_variant_unref (props);
243 }
244 }
245
246 static void
layout_updated_cb(SnDBusMenuGen * proxy,guint revision,gint parent,SnDBusMenu * menu)247 layout_updated_cb (SnDBusMenuGen *proxy,
248 guint revision,
249 gint parent,
250 SnDBusMenu *menu)
251 {
252 update_layout (menu, parent);
253 }
254
255 static void
item_activation_requested_cb(SnDBusMenuGen * proxy,gint id,guint timestamp,SnDBusMenu * menu)256 item_activation_requested_cb (SnDBusMenuGen *proxy,
257 gint id,
258 guint timestamp,
259 SnDBusMenu *menu)
260 {
261 g_debug ("activation requested: id - %d, timestamp - %d", id, timestamp);
262 }
263
264 static void
map_cb(GtkWidget * widget,SnDBusMenu * menu)265 map_cb (GtkWidget *widget,
266 SnDBusMenu *menu)
267 {
268 gboolean need_update;
269
270 sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "opened",
271 g_variant_new ("v", g_variant_new_int32 (0)),
272 gtk_get_current_event_time (),
273 NULL, NULL);
274
275 sn_dbus_menu_gen_call_about_to_show_sync (menu->proxy, 0, &need_update,
276 NULL, NULL);
277
278 if (need_update)
279 {
280 update_layout (menu, 0);
281 }
282 }
283
284 static void
unmap_cb(GtkWidget * widget,SnDBusMenu * menu)285 unmap_cb (GtkWidget *widget,
286 SnDBusMenu *menu)
287 {
288 sn_dbus_menu_gen_call_event_sync (menu->proxy, 0, "closed",
289 g_variant_new ("v", g_variant_new_int32 (0)),
290 gtk_get_current_event_time (),
291 NULL, NULL);
292 }
293
294 static void
proxy_ready_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)295 proxy_ready_cb (GObject *source_object,
296 GAsyncResult *res,
297 gpointer user_data)
298 {
299 SnDBusMenuGen *proxy;
300 GError *error;
301 SnDBusMenu *menu;
302
303 error = NULL;
304 proxy = sn_dbus_menu_gen_proxy_new_finish (res, &error);
305
306 if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
307 {
308 g_error_free (error);
309 return;
310 }
311
312 menu = SN_DBUS_MENU (user_data);
313 menu->proxy = proxy;
314
315 if (error)
316 {
317 g_warning ("%s", error->message);
318 g_error_free (error);
319 return;
320 }
321
322 g_signal_connect (proxy, "items-properties-updated",
323 G_CALLBACK (items_properties_updated_cb), menu);
324
325 g_signal_connect (proxy, "layout-updated",
326 G_CALLBACK (layout_updated_cb), menu);
327
328 g_signal_connect (proxy, "item-activation-requested",
329 G_CALLBACK (item_activation_requested_cb), menu);
330
331 g_signal_connect (menu, "map", G_CALLBACK (map_cb), menu);
332 g_signal_connect (menu, "unmap", G_CALLBACK (unmap_cb), menu);
333
334 update_layout (menu, 0);
335 }
336
337 static void
name_appeared_cb(GDBusConnection * connection,const gchar * name,const gchar * name_owner,gpointer user_data)338 name_appeared_cb (GDBusConnection *connection,
339 const gchar *name,
340 const gchar *name_owner,
341 gpointer user_data)
342 {
343 SnDBusMenu *menu;
344
345 menu = SN_DBUS_MENU (user_data);
346
347 sn_dbus_menu_gen_proxy_new (connection, G_DBUS_PROXY_FLAGS_NONE,
348 menu->bus_name, menu->object_path,
349 menu->cancellable, proxy_ready_cb, menu);
350 }
351
352 static void
name_vanished_cb(GDBusConnection * connection,const gchar * name,gpointer user_data)353 name_vanished_cb (GDBusConnection *connection,
354 const gchar *name,
355 gpointer user_data)
356 {
357 SnDBusMenu *menu;
358
359 menu = SN_DBUS_MENU (user_data);
360
361 g_clear_object (&menu->proxy);
362 }
363
364 static void
sn_dbus_menu_constructed(GObject * object)365 sn_dbus_menu_constructed (GObject *object)
366 {
367 SnDBusMenu *menu;
368 GtkWidget *toplevel;
369 GdkScreen *screen;
370 GdkVisual *visual;
371 GtkStyleContext *context;
372
373 G_OBJECT_CLASS (sn_dbus_menu_parent_class)->constructed (object);
374 menu = SN_DBUS_MENU (object);
375
376 /*Set up theme and transparency support*/
377 toplevel = gtk_widget_get_toplevel(GTK_WIDGET(menu));
378 /* Fix any failures of compiz/other wm's to communicate with gtk for transparency */
379 screen = gtk_widget_get_screen(GTK_WIDGET(toplevel));
380 visual = gdk_screen_get_rgba_visual(screen);
381 gtk_widget_set_visual(GTK_WIDGET(toplevel), visual);
382 /* Set menu and it's toplevel window to follow panel theme */
383 context = gtk_widget_get_style_context (GTK_WIDGET(toplevel));
384 gtk_style_context_add_class(context,"gnome-panel-menu-bar");
385 gtk_style_context_add_class(context,"mate-panel-menu-bar");
386
387 menu->name_id = g_bus_watch_name (G_BUS_TYPE_SESSION, menu->bus_name,
388 G_BUS_NAME_WATCHER_FLAGS_NONE,
389 name_appeared_cb, name_vanished_cb,
390 menu, NULL);
391 }
392
393 static void
sn_dbus_menu_dispose(GObject * object)394 sn_dbus_menu_dispose (GObject *object)
395 {
396 SnDBusMenu *menu;
397
398 menu = SN_DBUS_MENU (object);
399
400 if (menu->name_id > 0)
401 {
402 g_bus_unwatch_name (menu->name_id);
403 menu->name_id = 0;
404 }
405
406 g_clear_pointer (&menu->items, g_hash_table_destroy);
407
408 g_cancellable_cancel (menu->cancellable);
409 g_clear_object (&menu->cancellable);
410 g_clear_object (&menu->proxy);
411
412 G_OBJECT_CLASS (sn_dbus_menu_parent_class)->dispose (object);
413 }
414
415 static void
sn_dbus_menu_finalize(GObject * object)416 sn_dbus_menu_finalize (GObject *object)
417 {
418 SnDBusMenu *menu;
419
420 menu = SN_DBUS_MENU (object);
421
422 g_free (menu->bus_name);
423 g_free (menu->object_path);
424
425 G_OBJECT_CLASS (sn_dbus_menu_parent_class)->finalize (object);
426 }
427
428 static void
sn_dbus_menu_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)429 sn_dbus_menu_set_property (GObject *object,
430 guint property_id,
431 const GValue *value,
432 GParamSpec *pspec)
433 {
434 SnDBusMenu *menu;
435
436 menu = SN_DBUS_MENU (object);
437
438 switch (property_id)
439 {
440 case PROP_BUS_NAME:
441 menu->bus_name = g_value_dup_string (value);
442 break;
443
444 case PROP_OBJECT_PATH:
445 menu->object_path = g_value_dup_string (value);
446 break;
447
448 default:
449 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
450 break;
451 }
452 }
453
454 static void
install_properties(GObjectClass * object_class)455 install_properties (GObjectClass *object_class)
456 {
457 properties[PROP_BUS_NAME] =
458 g_param_spec_string ("bus-name", "bus-name", "bus-name", NULL,
459 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
460 G_PARAM_STATIC_STRINGS);
461
462 properties[PROP_OBJECT_PATH] =
463 g_param_spec_string ("object-path", "object-path", "object-path", NULL,
464 G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE |
465 G_PARAM_STATIC_STRINGS);
466
467 g_object_class_install_properties (object_class, LAST_PROP, properties);
468 }
469
470 static void
sn_dbus_menu_class_init(SnDBusMenuClass * menu_class)471 sn_dbus_menu_class_init (SnDBusMenuClass *menu_class)
472 {
473 GObjectClass *object_class;
474
475 object_class = G_OBJECT_CLASS (menu_class);
476
477 object_class->constructed = sn_dbus_menu_constructed;
478 object_class->dispose = sn_dbus_menu_dispose;
479 object_class->finalize = sn_dbus_menu_finalize;
480 object_class->set_property = sn_dbus_menu_set_property;
481
482 install_properties (object_class);
483 }
484
485 static void
sn_dbus_menu_init(SnDBusMenu * menu)486 sn_dbus_menu_init (SnDBusMenu *menu)
487 {
488 menu->items = g_hash_table_new_full (NULL, NULL, NULL, sn_dubs_menu_item_free);
489 menu->cancellable = g_cancellable_new ();
490 }
491
492 GtkMenu *
sn_dbus_menu_new(const gchar * bus_name,const gchar * object_path)493 sn_dbus_menu_new (const gchar *bus_name,
494 const gchar *object_path)
495 {
496 return g_object_new (SN_TYPE_DBUS_MENU,
497 "bus-name", bus_name,
498 "object-path", object_path,
499 NULL);
500 }
501