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