1 /*
2  * vala-panel-appmenu
3  * Copyright (C) 2018 Konstantin Pugin <ria.freelander@gmail.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "importer.h"
20 #include "dbusmenu-interface.h"
21 #include "model.h"
22 
23 struct _DBusMenuImporter
24 {
25 	GObject parent_instance;
26 	char *bus_name;
27 	char *object_path;
28 	gulong name_id;
29 	GCancellable *cancellable;
30 	DBusMenuXml *proxy;
31 	DBusMenuModel *top_model;
32 	GSimpleActionGroup *all_actions;
33 };
34 
35 enum
36 {
37 	PROP_NULL,
38 	PROP_BUS_NAME,
39 	PROP_OBJECT_PATH,
40 	PROP_MODEL,
41 	PROP_ACTION_GROUP,
42 	LAST_PROP
43 };
44 
45 static GParamSpec *properties[LAST_PROP] = { NULL };
G_DEFINE_TYPE(DBusMenuImporter,dbus_menu_importer,G_TYPE_OBJECT)46 G_DEFINE_TYPE(DBusMenuImporter, dbus_menu_importer, G_TYPE_OBJECT)
47 
48 static bool dbus_menu_importer_check(DBusMenuImporter *menu)
49 {
50 	if (DBUS_MENU_IS_XML(menu->proxy))
51 		return dbus_menu_xml_get_version(menu->proxy) >= 2;
52 	return false;
53 }
54 
dbus_menu_importer_on_root_model_changed(GMenuModel * model,gint position,gint removed,gint added,gpointer user_data)55 static void dbus_menu_importer_on_root_model_changed(GMenuModel *model, gint position, gint removed,
56                                                      gint added, gpointer user_data)
57 {
58 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(user_data);
59 	g_object_notify_by_pspec(G_OBJECT(menu), properties[PROP_MODEL]);
60 }
61 
proxy_ready_cb(GObject * source_object,GAsyncResult * res,gpointer user_data)62 static void proxy_ready_cb(GObject *source_object, GAsyncResult *res, gpointer user_data)
63 {
64 	g_autoptr(GError) error = NULL;
65 	DBusMenuXml *proxy      = dbus_menu_xml_proxy_new_finish(res, &error);
66 
67 	if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
68 		return;
69 
70 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(user_data);
71 	menu->proxy            = proxy;
72 
73 	if (error)
74 	{
75 		g_warning("%s", error->message);
76 		return;
77 	}
78 	if (dbus_menu_importer_check(menu))
79 		g_object_set(menu->top_model, "xml", proxy, NULL);
80 	g_object_notify_by_pspec(G_OBJECT(menu), properties[PROP_MODEL]);
81 }
82 
name_appeared_cb(GDBusConnection * connection,const char * name,const char * name_owner,gpointer user_data)83 static void name_appeared_cb(GDBusConnection *connection, const char *name, const char *name_owner,
84                              gpointer user_data)
85 {
86 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(user_data);
87 
88 	dbus_menu_xml_proxy_new(connection,
89 	                        G_DBUS_PROXY_FLAGS_NONE,
90 	                        menu->bus_name,
91 	                        menu->object_path,
92 	                        menu->cancellable,
93 	                        proxy_ready_cb,
94 	                        menu);
95 }
96 
name_vanished_cb(GDBusConnection * connection,const char * name,gpointer user_data)97 static void name_vanished_cb(GDBusConnection *connection, const char *name, gpointer user_data)
98 {
99 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(user_data);
100 
101 	g_object_set(menu->top_model, "xml", NULL, NULL);
102 	g_object_notify_by_pspec(G_OBJECT(menu), properties[PROP_MODEL]);
103 	g_clear_object(&menu->proxy);
104 }
105 
dbus_menu_importer_constructed(GObject * object)106 static void dbus_menu_importer_constructed(GObject *object)
107 {
108 	G_OBJECT_CLASS(dbus_menu_importer_parent_class)->constructed(object);
109 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(object);
110 
111 	menu->name_id = g_bus_watch_name(G_BUS_TYPE_SESSION,
112 	                                 menu->bus_name,
113 	                                 G_BUS_NAME_WATCHER_FLAGS_NONE,
114 	                                 name_appeared_cb,
115 	                                 name_vanished_cb,
116 	                                 menu,
117 	                                 NULL);
118 }
119 
dbus_menu_importer_dispose(GObject * object)120 static void dbus_menu_importer_dispose(GObject *object)
121 {
122 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(object);
123 
124 	if (menu->name_id > 0)
125 	{
126 		g_bus_unwatch_name(menu->name_id);
127 		menu->name_id = 0;
128 	}
129 	g_cancellable_cancel(menu->cancellable);
130 	g_clear_object(&menu->cancellable);
131 	g_signal_handlers_disconnect_by_data(menu->top_model, menu);
132 	g_clear_object(&menu->top_model);
133 	g_clear_object(&menu->proxy);
134 	g_clear_object(&menu->all_actions);
135 
136 	G_OBJECT_CLASS(dbus_menu_importer_parent_class)->dispose(object);
137 }
138 
dbus_menu_importer_finalize(GObject * object)139 static void dbus_menu_importer_finalize(GObject *object)
140 {
141 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(object);
142 
143 	g_clear_pointer(&menu->bus_name, g_free);
144 	g_clear_pointer(&menu->object_path, g_free);
145 
146 	G_OBJECT_CLASS(dbus_menu_importer_parent_class)->finalize(object);
147 }
148 
dbus_menu_importer_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)149 static void dbus_menu_importer_set_property(GObject *object, guint property_id, const GValue *value,
150                                             GParamSpec *pspec)
151 {
152 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(object);
153 
154 	switch (property_id)
155 	{
156 	case PROP_BUS_NAME:
157 		menu->bus_name = g_value_dup_string(value);
158 		break;
159 
160 	case PROP_OBJECT_PATH:
161 		menu->object_path = g_value_dup_string(value);
162 		break;
163 
164 	default:
165 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
166 		break;
167 	}
168 }
169 
dbus_menu_importer_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)170 static void dbus_menu_importer_get_property(GObject *object, guint property_id, GValue *value,
171                                             GParamSpec *pspec)
172 {
173 	DBusMenuImporter *menu = DBUS_MENU_IMPORTER(object);
174 	switch (property_id)
175 	{
176 	case PROP_MODEL:
177 		g_value_set_object(value, menu->top_model);
178 		break;
179 	case PROP_ACTION_GROUP:
180 		g_value_set_object(value, menu->all_actions);
181 		break;
182 
183 	default:
184 		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
185 		break;
186 	}
187 }
188 
install_properties(GObjectClass * object_class)189 static void install_properties(GObjectClass *object_class)
190 {
191 	properties[PROP_BUS_NAME] =
192 	    g_param_spec_string("bus-name",
193 	                        "bus-name",
194 	                        "bus-name",
195 	                        NULL,
196 	                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
197 
198 	properties[PROP_OBJECT_PATH] =
199 	    g_param_spec_string("object-path",
200 	                        "object-path",
201 	                        "object-path",
202 	                        NULL,
203 	                        G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
204 	properties[PROP_MODEL] = g_param_spec_object("model",
205 	                                             "model",
206 	                                             "model",
207 	                                             G_TYPE_MENU_MODEL,
208 	                                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
209 	properties[PROP_ACTION_GROUP] =
210 	    g_param_spec_object("action-group",
211 	                        "action-group",
212 	                        "action-group",
213 	                        G_TYPE_ACTION_GROUP,
214 	                        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
215 
216 	g_object_class_install_properties(object_class, LAST_PROP, properties);
217 }
218 
dbus_menu_importer_class_init(DBusMenuImporterClass * menu_class)219 static void dbus_menu_importer_class_init(DBusMenuImporterClass *menu_class)
220 {
221 	GObjectClass *object_class;
222 
223 	object_class = G_OBJECT_CLASS(menu_class);
224 
225 	object_class->constructed  = dbus_menu_importer_constructed;
226 	object_class->dispose      = dbus_menu_importer_dispose;
227 	object_class->finalize     = dbus_menu_importer_finalize;
228 	object_class->set_property = dbus_menu_importer_set_property;
229 	object_class->get_property = dbus_menu_importer_get_property;
230 
231 	install_properties(object_class);
232 }
233 
dbus_menu_importer_init(DBusMenuImporter * menu)234 static void dbus_menu_importer_init(DBusMenuImporter *menu)
235 {
236 	menu->proxy       = NULL;
237 	menu->all_actions = g_simple_action_group_new();
238 	menu->top_model =
239 	    dbus_menu_model_new(0, NULL, menu->proxy, G_ACTION_GROUP(menu->all_actions));
240 	g_signal_connect(menu->top_model,
241 	                 "items-changed",
242 	                 G_CALLBACK(dbus_menu_importer_on_root_model_changed),
243 	                 menu);
244 	menu->cancellable = g_cancellable_new();
245 }
246 
dbus_menu_importer_new(const char * bus_name,const char * object_path)247 DBusMenuImporter *dbus_menu_importer_new(const char *bus_name, const char *object_path)
248 {
249 	return g_object_new(dbus_menu_importer_get_type(),
250 	                    "bus-name",
251 	                    bus_name,
252 	                    "object-path",
253 	                    object_path,
254 	                    NULL);
255 }
256