1 /*
2 An object to ferry over properties and signals between two different
3 dbusmenu instances. Useful for services.
4
5 Copyright 2010 Canonical Ltd.
6
7 Authors:
8 Ted Gould <ted@canonical.com>
9
10 This program is free software: you can redistribute it and/or modify it
11 under the terms of either or both of the following licenses:
12
13 1) the GNU Lesser General Public License version 3, as published by the
14 Free Software Foundation; and/or
15 2) the GNU Lesser General Public License version 2.1, as published by
16 the Free Software Foundation.
17
18 This program is distributed in the hope that it will be useful, but
19 WITHOUT ANY WARRANTY; without even the implied warranties of
20 MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
21 PURPOSE. See the applicable version of the GNU Lesser General Public
22 License for more details.
23
24 You should have received a copy of both the GNU Lesser General Public
25 License version 3 and version 2.1 along with this program. If not, see
26 <http://www.gnu.org/licenses/>
27 */
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include "menuitem-proxy.h"
34
35 struct _DbusmenuMenuitemProxyPrivate {
36 DbusmenuMenuitem * mi;
37 gulong sig_property_changed;
38 gulong sig_child_added;
39 gulong sig_child_removed;
40 gulong sig_child_moved;
41 };
42
43 /* Properties */
44 enum {
45 PROP_0,
46 PROP_MENU_ITEM
47 };
48
49 #define PROP_MENU_ITEM_S "menu-item"
50
51 #define DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(o) (DBUSMENU_MENUITEM_PROXY(o)->priv)
52
53 static void dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass);
54 static void dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self);
55 static void dbusmenu_menuitem_proxy_dispose (GObject *object);
56 static void dbusmenu_menuitem_proxy_finalize (GObject *object);
57 static void set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec);
58 static void get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec);
59 static void handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp);
60 static void add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi);
61 static void remove_menuitem (DbusmenuMenuitemProxy * pmi);
62
63 G_DEFINE_TYPE (DbusmenuMenuitemProxy, dbusmenu_menuitem_proxy, DBUSMENU_TYPE_MENUITEM);
64
65 static void
dbusmenu_menuitem_proxy_class_init(DbusmenuMenuitemProxyClass * klass)66 dbusmenu_menuitem_proxy_class_init (DbusmenuMenuitemProxyClass *klass)
67 {
68 GObjectClass *object_class = G_OBJECT_CLASS (klass);
69
70 g_type_class_add_private (klass, sizeof (DbusmenuMenuitemProxyPrivate));
71
72 object_class->dispose = dbusmenu_menuitem_proxy_dispose;
73 object_class->finalize = dbusmenu_menuitem_proxy_finalize;
74 object_class->set_property = set_property;
75 object_class->get_property = get_property;
76
77 DbusmenuMenuitemClass * miclass = DBUSMENU_MENUITEM_CLASS(klass);
78
79 miclass->handle_event = handle_event;
80
81 g_object_class_install_property (object_class, PROP_MENU_ITEM,
82 g_param_spec_object(PROP_MENU_ITEM_S, "The Menuitem we're proxying",
83 "An instance of the DbusmenuMenuitem class that this menuitem will mimic.",
84 DBUSMENU_TYPE_MENUITEM,
85 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
86
87 return;
88 }
89
90 static void
dbusmenu_menuitem_proxy_init(DbusmenuMenuitemProxy * self)91 dbusmenu_menuitem_proxy_init (DbusmenuMenuitemProxy *self)
92 {
93 self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), DBUSMENU_TYPE_MENUITEM_PROXY, DbusmenuMenuitemProxyPrivate);
94
95 DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(self);
96
97 priv->mi = NULL;
98
99 priv->sig_property_changed = 0;
100 priv->sig_child_added = 0;
101 priv->sig_child_removed = 0;
102 priv->sig_child_moved = 0;
103
104 return;
105 }
106
107 /* Remove references to objects */
108 static void
dbusmenu_menuitem_proxy_dispose(GObject * object)109 dbusmenu_menuitem_proxy_dispose (GObject *object)
110 {
111 remove_menuitem(DBUSMENU_MENUITEM_PROXY(object));
112
113 G_OBJECT_CLASS (dbusmenu_menuitem_proxy_parent_class)->dispose (object);
114 return;
115 }
116
117 /* Free any memory that we've allocated */
118 static void
dbusmenu_menuitem_proxy_finalize(GObject * object)119 dbusmenu_menuitem_proxy_finalize (GObject *object)
120 {
121
122 G_OBJECT_CLASS (dbusmenu_menuitem_proxy_parent_class)->finalize (object);
123 return;
124 }
125
126 /* Set a property using the generic GObject interface */
127 static void
set_property(GObject * obj,guint id,const GValue * value,GParamSpec * pspec)128 set_property (GObject * obj, guint id, const GValue * value, GParamSpec * pspec)
129 {
130 switch (id) {
131 case PROP_MENU_ITEM: {
132 GObject * lobj = g_value_get_object(value);
133 add_menuitem(DBUSMENU_MENUITEM_PROXY(obj), DBUSMENU_MENUITEM(lobj));
134 break;
135 }
136 default:
137 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
138 break;
139 }
140
141 return;
142 }
143
144 /* Get a property using the generic GObject interface */
145 static void
get_property(GObject * obj,guint id,GValue * value,GParamSpec * pspec)146 get_property (GObject * obj, guint id, GValue * value, GParamSpec * pspec)
147 {
148 DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(obj);
149
150 switch (id) {
151 case PROP_MENU_ITEM:
152 g_value_set_object(value, priv->mi);
153 break;
154 default:
155 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, id, pspec);
156 break;
157 }
158
159 return;
160 }
161
162 /* Takes the event and passes it along to the item that we're
163 playing proxy for. */
164 static void
handle_event(DbusmenuMenuitem * mi,const gchar * name,GVariant * variant,guint timestamp)165 handle_event (DbusmenuMenuitem * mi, const gchar * name, GVariant * variant, guint timestamp)
166 {
167 g_return_if_fail(DBUSMENU_IS_MENUITEM_PROXY(mi));
168 DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(mi);
169 g_return_if_fail(priv->mi != NULL);
170 return dbusmenu_menuitem_handle_event(priv->mi, name, variant, timestamp);
171 }
172
173 /* Watches a property change and makes sure to put that value
174 into our property list. */
175 static void
proxy_item_property_changed(DbusmenuMenuitem * mi,gchar * property,GVariant * variant,gpointer user_data)176 proxy_item_property_changed (DbusmenuMenuitem * mi, gchar * property, GVariant * variant, gpointer user_data)
177 {
178 DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
179 dbusmenu_menuitem_property_set_variant(DBUSMENU_MENUITEM(pmi), property, variant);
180 return;
181 }
182
183 /* Looks for a child getting added and wraps it and places it
184 in our list of children. */
185 static void
proxy_item_child_added(DbusmenuMenuitem * parent,DbusmenuMenuitem * child,guint position,gpointer user_data)186 proxy_item_child_added (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint position, gpointer user_data)
187 {
188 DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
189 DbusmenuMenuitemProxy * child_pmi = dbusmenu_menuitem_proxy_new(child);
190 dbusmenu_menuitem_child_add_position(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(child_pmi), position);
191 g_object_unref (child_pmi);
192 return;
193 }
194
195 /* Find the wrapper for this child and remove it as well. */
196 static void
proxy_item_child_removed(DbusmenuMenuitem * parent,DbusmenuMenuitem * child,gpointer user_data)197 proxy_item_child_removed (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, gpointer user_data)
198 {
199 DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
200 GList * children = dbusmenu_menuitem_get_children(DBUSMENU_MENUITEM(pmi));
201 DbusmenuMenuitemProxy * finalpmi = NULL;
202 GList * childitem;
203
204 for (childitem = children; childitem != NULL; childitem = g_list_next(childitem)) {
205 DbusmenuMenuitemProxy * childpmi = (DbusmenuMenuitemProxy *)childitem->data;
206 DbusmenuMenuitem * childmi = dbusmenu_menuitem_proxy_get_wrapped(childpmi);
207 if (childmi == child) {
208 finalpmi = childpmi;
209 break;
210 }
211 }
212
213 if (finalpmi != NULL) {
214 dbusmenu_menuitem_child_delete(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(finalpmi));
215 }
216
217 return;
218 }
219
220 /* Find the wrapper for the item and move it in our child list */
221 static void
proxy_item_child_moved(DbusmenuMenuitem * parent,DbusmenuMenuitem * child,guint newpos,guint oldpos,gpointer user_data)222 proxy_item_child_moved (DbusmenuMenuitem * parent, DbusmenuMenuitem * child, guint newpos, guint oldpos, gpointer user_data)
223 {
224 DbusmenuMenuitemProxy * pmi = DBUSMENU_MENUITEM_PROXY(user_data);
225 GList * children = dbusmenu_menuitem_get_children(DBUSMENU_MENUITEM(pmi));
226 DbusmenuMenuitemProxy * finalpmi = NULL;
227 GList * childitem;
228
229 for (childitem = children; childitem != NULL; childitem = g_list_next(childitem)) {
230 DbusmenuMenuitemProxy * childpmi = (DbusmenuMenuitemProxy *)childitem->data;
231 DbusmenuMenuitem * childmi = dbusmenu_menuitem_proxy_get_wrapped(childpmi);
232 if (childmi == child) {
233 finalpmi = childpmi;
234 break;
235 }
236 }
237
238 if (finalpmi != NULL) {
239 dbusmenu_menuitem_child_reorder(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(finalpmi), newpos);
240 }
241
242 return;
243 }
244
245 /* Making g_object_unref into a GFunc */
246 static void
func_g_object_unref(gpointer data,gpointer user_data)247 func_g_object_unref (gpointer data, gpointer user_data)
248 {
249 return g_object_unref(G_OBJECT(data));
250 }
251
252 /* References all of the things we need for talking to this menuitem
253 including signals and other data. If the menuitem already has
254 properties we need to signal that they've changed for us. */
255 static void
add_menuitem(DbusmenuMenuitemProxy * pmi,DbusmenuMenuitem * mi)256 add_menuitem (DbusmenuMenuitemProxy * pmi, DbusmenuMenuitem * mi)
257 {
258 /* Put it in private */
259 DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi);
260 if (priv->mi != NULL) {
261 remove_menuitem(pmi);
262 }
263 priv->mi = mi;
264 g_object_ref(G_OBJECT(priv->mi));
265
266 /* Attach signals */
267 priv->sig_property_changed = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_PROPERTY_CHANGED, G_CALLBACK(proxy_item_property_changed), pmi);
268 priv->sig_child_added = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_ADDED, G_CALLBACK(proxy_item_child_added), pmi);
269 priv->sig_child_removed = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_REMOVED, G_CALLBACK(proxy_item_child_removed), pmi);
270 priv->sig_child_moved = g_signal_connect(G_OBJECT(priv->mi), DBUSMENU_MENUITEM_SIGNAL_CHILD_MOVED, G_CALLBACK(proxy_item_child_moved), pmi);
271
272 /* Grab (cache) Properties */
273 GList * props = dbusmenu_menuitem_properties_list(priv->mi);
274 GList * prop;
275 for (prop = props; prop != NULL; prop = g_list_next(prop)) {
276 gchar * prop_name = (gchar *)prop->data;
277 dbusmenu_menuitem_property_set_variant(DBUSMENU_MENUITEM(pmi), prop_name, dbusmenu_menuitem_property_get_variant(priv->mi, prop_name));
278 }
279 g_list_free(props);
280
281 /* Go through children and wrap them */
282 GList * children = dbusmenu_menuitem_get_children(priv->mi);
283 GList * child;
284 for (child = children; child != NULL; child = g_list_next(child)) {
285 DbusmenuMenuitemProxy * child_pmi = dbusmenu_menuitem_proxy_new(DBUSMENU_MENUITEM(child->data));
286 dbusmenu_menuitem_child_append(DBUSMENU_MENUITEM(pmi), DBUSMENU_MENUITEM(child_pmi));
287 g_object_unref (child_pmi);
288 }
289
290 return;
291 }
292
293 /* Removes the menuitem from being our proxy. Typically this isn't
294 done until this object is destroyed, but who knows?!? */
295 static void
remove_menuitem(DbusmenuMenuitemProxy * pmi)296 remove_menuitem (DbusmenuMenuitemProxy * pmi)
297 {
298 DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi);
299 if (priv->mi == NULL) {
300 return;
301 }
302
303 /* Remove signals */
304 if (priv->sig_property_changed != 0) {
305 g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_property_changed);
306 }
307 if (priv->sig_child_added != 0) {
308 g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_added);
309 }
310 if (priv->sig_child_removed != 0) {
311 g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_removed);
312 }
313 if (priv->sig_child_moved != 0) {
314 g_signal_handler_disconnect(G_OBJECT(priv->mi), priv->sig_child_moved);
315 }
316
317 /* Unref */
318 g_object_unref(G_OBJECT(priv->mi));
319 priv->mi = NULL;
320
321 /* Remove our own children */
322 GList * children = dbusmenu_menuitem_take_children(DBUSMENU_MENUITEM(pmi));
323 g_list_foreach(children, func_g_object_unref, NULL);
324 g_list_free(children);
325
326 return;
327 }
328
329 /**
330 * dbusmenu_menuitem_proxy_new:
331 * @mi: The #DbusmenuMenuitem to proxy
332 *
333 * Builds a new #DbusmenuMenuitemProxy object that proxies
334 * all of the values for @mi.
335 *
336 * Return value: A new #DbusmenuMenuitemProxy object.
337 */
338 DbusmenuMenuitemProxy *
dbusmenu_menuitem_proxy_new(DbusmenuMenuitem * mi)339 dbusmenu_menuitem_proxy_new (DbusmenuMenuitem * mi)
340 {
341 DbusmenuMenuitemProxy * pmi = g_object_new(DBUSMENU_TYPE_MENUITEM_PROXY,
342 PROP_MENU_ITEM_S, mi,
343 NULL);
344
345 return pmi;
346 }
347
348 /**
349 * dbusmenu_menuitem_proxy_get_wrapped:
350 * @pmi: #DbusmenuMenuitemProxy to look into
351 *
352 * Accesses the private variable of which #DbusmenuMenuitem
353 * we are doing the proxying for.
354 *
355 * Return value: (transfer none): A #DbusmenuMenuitem object or a #NULL if we
356 * don't have one or there is an error.
357 */
358 DbusmenuMenuitem *
dbusmenu_menuitem_proxy_get_wrapped(DbusmenuMenuitemProxy * pmi)359 dbusmenu_menuitem_proxy_get_wrapped (DbusmenuMenuitemProxy * pmi)
360 {
361 g_return_val_if_fail(DBUSMENU_MENUITEM_PROXY(pmi), NULL);
362 DbusmenuMenuitemProxyPrivate * priv = DBUSMENU_MENUITEM_PROXY_GET_PRIVATE(pmi);
363 return priv->mi;
364 }
365