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