1 #include "e.h"
2 
3 #define DBUS_MENU_IFACE "com.canonical.dbusmenu"
4 
5 struct _E_DBusMenu_Ctx
6 {
7    Eldbus_Proxy              *proxy;
8    E_DBusMenu_Item          *root_menu;
9    void                     *data;
10    E_DBusMenu_Pop_Request_Cb pop_request_cb;
11    E_DBusMenu_Update_Cb      update_cb;
12    Eina_Bool  hacks E_BITFIELD;
13 };
14 
15 static const char *Menu_Item_Type_Names[] =
16 {
17    "standard", "separator"
18 };
19 
20 static const char *Menu_Item_Toggle_Type_Names[] =
21 {
22    "", "checkmark", "radio"
23 };
24 
25 static const char *Menu_Item_Dispostion_Names[] =
26 {
27    "normal", "informative", "warning", "alert"
28 };
29 
30 static const char *Menu_Item_Event_Names[] =
31 {
32    "clicked", "hovered", "opened", "closed"
33 };
34 
35 static void proxy_init(E_DBusMenu_Ctx *ctx);
36 
37 static int
id_find(const char * text,const char * array_of_names[],unsigned max)38 id_find(const char *text, const char *array_of_names[], unsigned max)
39 {
40    unsigned int i;
41    for (i = 0; i < max; i++)
42      {
43         if (strcmp(text, array_of_names[i])) continue;
44         return i;
45      }
46    return 0;
47 }
48 
49 static void
dbus_menu_prop_dict_cb(void * data,const void * key,Eldbus_Message_Iter * var)50 dbus_menu_prop_dict_cb(void *data, const void *key, Eldbus_Message_Iter *var)
51 {
52    E_DBusMenu_Item *m = data;
53    if (!strcmp(key, "label"))
54      {
55         const char *label;
56         Eina_Strbuf *label_buf = eina_strbuf_new();
57         int i;
58 
59         eldbus_message_iter_arguments_get(var, "s", &label);
60         for (i = 0; label[i]; i++)
61           {
62              if (label[i] != '_')
63                {
64                   eina_strbuf_append_char(label_buf, label[i]);
65                   continue;
66                }
67 
68              if (label[i + 1] == '_')
69                {
70                   eina_strbuf_append_char(label_buf, label[i]);
71                   i++;
72                   continue;
73                }
74           }
75         eina_stringshare_replace(&m->label, eina_strbuf_string_get(label_buf));
76         eina_strbuf_free(label_buf);
77      }
78    else if (!strcmp(key, "type"))
79      {
80         const char *type;
81         eldbus_message_iter_arguments_get(var, "s", &type);
82         m->type = id_find(type, Menu_Item_Type_Names, E_DBUSMENU_ITEM_TYPE_LAST);
83      }
84    else if (!strcmp(key, "icon-data"))
85      {
86         Eldbus_Message_Iter *array;
87         int size;
88         const unsigned char *img_data;
89 
90         eldbus_message_iter_arguments_get(var, "ay", &array);
91         eldbus_message_iter_fixed_array_get(array, 'y', &img_data, &size);
92         if (!size) return;
93         m->icon_data = malloc(sizeof(unsigned char) * size);
94         EINA_SAFETY_ON_FALSE_RETURN(m->icon_data);
95         memcpy(m->icon_data, img_data, size);
96         m->icon_data_size = size;
97      }
98    else if (!strcmp(key, "icon-name"))
99      {
100         const char *icon_name;
101         eldbus_message_iter_arguments_get(var, "s", &icon_name);
102         eina_stringshare_replace(&m->icon_name, icon_name);
103      }
104    else if (!strcmp(key, "toggle-type"))
105      {
106         const char *toggle_type;
107         eldbus_message_iter_arguments_get(var, "s", &toggle_type);
108         m->toggle_type = id_find(toggle_type, Menu_Item_Toggle_Type_Names,
109                                  E_DBUSMENU_ITEM_TOGGLE_TYPE_LAST);
110      }
111    else if (!strcmp(key, "toggle-state"))
112      {
113         int state;
114         eldbus_message_iter_arguments_get(var, "i", &state);
115         if (state == 1) m->toggle_state = EINA_TRUE;
116         else m->toggle_state = EINA_FALSE;
117      }
118    else if (!strcmp(key, "children-display"))
119      {
120         const char *display;
121         eldbus_message_iter_arguments_get(var, "s", &display);
122         if (!strcmp(display, "submenu")) m->is_submenu = EINA_TRUE;
123         else m->is_submenu = EINA_FALSE;
124      }
125    else if (!strcmp(key, "disposition"))
126      {
127         const char *disposition;
128         eldbus_message_iter_arguments_get(var, "s", &disposition);
129         m->disposition = id_find(disposition, Menu_Item_Dispostion_Names,
130                                  E_DBUSMENU_ITEM_DISPOSTION_LAST);
131      }
132    else if (!strcmp(key, "enabled"))
133      eldbus_message_iter_arguments_get(var, "b", &m->enabled);
134    else if (!strcmp(key, "visible"))
135      eldbus_message_iter_arguments_get(var, "b", &m->visible);
136 }
137 
138 static E_DBusMenu_Item *
parse_layout(Eldbus_Message_Iter * layout,E_DBusMenu_Item * parent,E_DBusMenu_Ctx * ctx)139 parse_layout(Eldbus_Message_Iter *layout, E_DBusMenu_Item *parent, E_DBusMenu_Ctx *ctx)
140 {
141    Eldbus_Message_Iter *menu_item_prop, *sub_menu_items_prop, *var;
142    E_DBusMenu_Item *m = calloc(1, sizeof(E_DBusMenu_Item));
143    EINA_SAFETY_ON_NULL_RETURN_VAL(m, NULL);
144    m->references = 1;
145    m->ctx = ctx;
146    m->enabled = EINA_TRUE;
147    m->visible = EINA_TRUE;
148 
149    if (!eldbus_message_iter_arguments_get(layout, "ia{sv}av", &m->id,
150                                          &menu_item_prop, &sub_menu_items_prop))
151      {
152         ERR("Error reading message");
153         free(m);
154         return NULL;
155      }
156 
157    eldbus_message_iter_dict_iterate(menu_item_prop, "sv", dbus_menu_prop_dict_cb, m);
158 
159    while (eldbus_message_iter_get_and_next(sub_menu_items_prop, 'v', &var))
160      {
161         Eldbus_Message_Iter *st;
162         if (!eldbus_message_iter_arguments_get(var, "(ia{sv}av)", &st))
163           {
164              ERR("Error readding message.");
165              continue;
166           }
167         parse_layout(st, m, ctx);
168      }
169 
170    if (!parent) return m;
171 
172    parent->sub_items = eina_inlist_append(parent->sub_items, EINA_INLIST_GET(m));
173    m->parent = parent;
174    return NULL;
175 }
176 
177 static void
dbus_menu_free(E_DBusMenu_Item * m)178 dbus_menu_free(E_DBusMenu_Item *m)
179 {
180    Eina_Inlist *inlist;
181    E_DBusMenu_Item *child;
182 
183    EINA_INLIST_FOREACH_SAFE(m->sub_items, inlist, child)
184      {
185         e_dbusmenu_item_unref(child);
186      }
187    EINA_INLIST_FREE(m->sub_items, child)
188      {
189         m->sub_items = eina_inlist_remove
190           (m->sub_items, EINA_INLIST_GET(child));
191         child->parent = NULL;
192      }
193    if (m->icon_name) eina_stringshare_del(m->icon_name);
194    if (m->label) eina_stringshare_del(m->label);
195    if (m->parent) m->parent->sub_items = eina_inlist_remove
196      (m->parent->sub_items, EINA_INLIST_GET(m));
197    if (m->icon_data_size) free(m->icon_data);
198    free(m);
199 }
200 
201 static Eina_Bool
attempt_hacks(E_DBusMenu_Ctx * ctx)202 attempt_hacks(E_DBusMenu_Ctx *ctx)
203 {
204    /* https://phab.enlightenment.org/T3139 */
205    Eldbus_Object *obj;
206    Eldbus_Connection *conn;
207    const char *bus, *p;
208    int n;
209    char buf[1024] = {0}, buf2[1024 + 12] = {0};
210 
211    if (ctx->hacks) return EINA_FALSE;
212    obj = eldbus_proxy_object_get(ctx->proxy);
213    conn = eldbus_object_connection_get(obj);
214    bus = eldbus_object_bus_name_get(obj);
215    if (bus[0] != ':') return EINA_FALSE;
216    /* if this is a qt5 app, menu bus is $bus + 2
217     * ...probably
218     */
219 
220    p = strchr(bus + 1, '.');
221    if (!p) return EINA_FALSE;
222    p++;
223    if (!p[0]) return EINA_FALSE;
224    n = strtol(p, NULL, 10);
225    if (n == -1) return EINA_FALSE;
226    n += 2;
227    if ((unsigned int)(p - bus) > sizeof(buf) - 1) return EINA_FALSE;
228    strncpy(buf, bus, p - bus);
229    snprintf(buf2, sizeof(buf2), "%s%d", buf, n);
230    E_FREE_FUNC(ctx->root_menu, e_dbusmenu_item_unref);
231    eldbus_proxy_unref(ctx->proxy);
232    eldbus_object_unref(obj);
233 
234    obj = eldbus_object_get(conn, buf2, "/MenuBar");
235    ctx->proxy = eldbus_proxy_get(obj, DBUS_MENU_IFACE);
236    proxy_init(ctx);
237    ctx->hacks = 1;
238    return EINA_TRUE;
239 }
240 
241 static void
layout_get_cb(void * data,const Eldbus_Message * msg,Eldbus_Pending * pending EINA_UNUSED)242 layout_get_cb(void *data, const Eldbus_Message *msg, Eldbus_Pending *pending EINA_UNUSED)
243 {
244    E_DBusMenu_Item *m;
245    const char *error, *error_msg;
246    Eldbus_Message_Iter *layout;
247    unsigned revision;
248    E_DBusMenu_Ctx *ctx = data;
249 
250    if (eldbus_message_error_get(msg, &error, &error_msg))
251      {
252         ERR("%s %s", error, error_msg);
253         return;
254      }
255 
256    if (!eldbus_message_arguments_get(msg, "u(ia{sv}av)", &revision, &layout))
257      {
258         ERR("Error reading message");
259         return;
260      }
261 
262    m = parse_layout(layout, NULL, ctx);
263    m->revision = revision;
264    if (m->is_submenu && (!m->parent) && (!m->sub_items))
265      {
266         if (attempt_hacks(ctx))
267           {
268              e_dbusmenu_item_unref(m);
269              return;
270           }
271      }
272 
273    if (ctx->update_cb) ctx->update_cb(ctx->data, m);
274    if (ctx->root_menu) e_dbusmenu_item_unref(ctx->root_menu);
275    ctx->root_menu = m;
276 }
277 
278 static E_DBusMenu_Item *
dbus_menu_find(E_DBusMenu_Ctx * ctx,int id)279 dbus_menu_find(E_DBusMenu_Ctx *ctx, int id)
280 {
281    E_DBusMenu_Item *m;
282    if (!ctx)
283      return NULL;
284 
285    EINA_INLIST_FOREACH(ctx->root_menu, m)
286      {
287         E_DBusMenu_Item *child, *found;
288         if (m->id == id) return m;
289         EINA_INLIST_FOREACH(m->sub_items, child)
290           {
291              found = dbus_menu_find(ctx, id);
292              if (found) return found;
293           }
294      }
295    return NULL;
296 }
297 
298 static void
menu_pop_request(void * data,const Eldbus_Message * msg)299 menu_pop_request(void *data, const Eldbus_Message *msg)
300 {
301    E_DBusMenu_Ctx *ctx = data;
302    int id;
303    unsigned timestamp;
304    E_DBusMenu_Item *m;
305 
306    if (!eldbus_message_arguments_get(msg, "iu", &id, &timestamp))
307      {
308         ERR("Error reading values.");
309         return;
310      }
311 
312    m = dbus_menu_find(ctx, id);
313    if (!m) return;
314    if (ctx->pop_request_cb) ctx->pop_request_cb(ctx->data, m);
315 }
316 
317 static void
prop_changed_cb(void * data EINA_UNUSED,const Eldbus_Message * msg)318 prop_changed_cb(void *data EINA_UNUSED, const Eldbus_Message *msg)
319 {
320    const char *interface, *propname;
321    Eldbus_Message_Iter *variant, *array;
322 
323    if (!eldbus_message_arguments_get(msg, "ssv", &interface, &propname, &variant))
324      {
325         ERR("Error getting values");
326         return;
327      }
328 
329    if (strcmp(propname, "IconThemePath")) return;
330 
331    if (!eldbus_message_iter_arguments_get(variant, "as", &array))
332      {
333         //TODO
334      }
335 }
336 
337 static void
layout_update(E_DBusMenu_Ctx * ctx)338 layout_update(E_DBusMenu_Ctx *ctx)
339 {
340    Eldbus_Message *msg;
341    Eldbus_Message_Iter *main_iter, *array;
342 
343    msg = eldbus_proxy_method_call_new(ctx->proxy, "GetLayout");
344    main_iter = eldbus_message_iter_get(msg);
345    eldbus_message_iter_arguments_append(main_iter, "iias", 0, -1, &array);
346    eldbus_message_iter_container_close(main_iter, array);
347    eldbus_proxy_send(ctx->proxy, msg, layout_get_cb, ctx, -1);
348 }
349 
350 static void
layout_updated_cb(void * data,const Eldbus_Message * msg EINA_UNUSED)351 layout_updated_cb(void *data, const Eldbus_Message *msg EINA_UNUSED)
352 {
353    E_DBusMenu_Ctx *ctx = data;
354    layout_update(ctx);
355 }
356 
357 static void
proxy_init(E_DBusMenu_Ctx * ctx)358 proxy_init(E_DBusMenu_Ctx *ctx)
359 {
360    layout_update(ctx);
361    eldbus_proxy_signal_handler_add(ctx->proxy,
362                                   "ItemActivationRequested",
363                                   menu_pop_request, ctx);
364 
365    eldbus_proxy_properties_changed_callback_add(ctx->proxy,
366                                                prop_changed_cb, ctx);
367 
368    eldbus_proxy_signal_handler_add(ctx->proxy, "ItemsPropertiesUpdated",
369                                   layout_updated_cb, ctx);
370    eldbus_proxy_signal_handler_add(ctx->proxy, "LayoutUpdated",
371                                   layout_updated_cb, ctx);
372 }
373 
374 E_API E_DBusMenu_Ctx *
e_dbusmenu_load(Eldbus_Connection * conn,const char * bus,const char * path,const void * data)375 e_dbusmenu_load(Eldbus_Connection *conn, const char *bus, const char *path, const void *data)
376 {
377    Eldbus_Object *obj;
378    E_DBusMenu_Ctx *ctx;
379    EINA_SAFETY_ON_NULL_RETURN_VAL(bus, NULL);
380    EINA_SAFETY_ON_NULL_RETURN_VAL(path, NULL);
381 
382    ctx = calloc(1, sizeof(E_DBusMenu_Ctx));
383    EINA_SAFETY_ON_NULL_RETURN_VAL(ctx, NULL);
384 
385    ctx->data = (void *)data;
386 
387    eldbus_connection_ref(conn);
388    obj = eldbus_object_get(conn, bus, path);
389    ctx->proxy = eldbus_proxy_get(obj, DBUS_MENU_IFACE);
390    proxy_init(ctx);
391    return ctx;
392 }
393 
394 E_API void
e_dbusmenu_event_send(E_DBusMenu_Item * m,E_DBusMenu_Item_Event event)395 e_dbusmenu_event_send(E_DBusMenu_Item *m, E_DBusMenu_Item_Event event)
396 {
397    Eldbus_Message *msg;
398    Eldbus_Message_Iter *main_iter, *var;
399    unsigned int timestamp = (unsigned int)time(NULL);
400 
401    EINA_SAFETY_ON_NULL_RETURN(m);
402    EINA_SAFETY_ON_FALSE_RETURN(event < E_DBUSMENU_ITEM_EVENT_LAST);
403    EINA_SAFETY_ON_NULL_RETURN(m->ctx);
404    m->ctx->proxy = eldbus_proxy_ref(m->ctx->proxy);
405 
406    msg = eldbus_proxy_method_call_new(m->ctx->proxy, "Event");
407    main_iter = eldbus_message_iter_get(msg);
408    eldbus_message_iter_arguments_append(main_iter, "is", m->id,
409                                        Menu_Item_Event_Names[event]);
410 
411    var = eldbus_message_iter_container_new(main_iter, 'v', "s");
412    /* dummy data */
413    eldbus_message_iter_arguments_append(var, "s", "");
414    eldbus_message_iter_container_close(main_iter, var);
415 
416    eldbus_message_iter_arguments_append(main_iter, "u", timestamp);
417 
418    eldbus_proxy_send(m->ctx->proxy, msg, NULL, NULL, -1);
419 }
420 
421 E_API void
e_dbusmenu_item_ref(E_DBusMenu_Item * m)422 e_dbusmenu_item_ref(E_DBusMenu_Item *m)
423 {
424    m->references++;
425 }
426 
427 E_API void
e_dbusmenu_item_unref(E_DBusMenu_Item * m)428 e_dbusmenu_item_unref(E_DBusMenu_Item *m)
429 {
430    m->references--;
431    if (m->references == 0) dbus_menu_free(m);
432 }
433 
434 E_API void
e_dbusmenu_unload(E_DBusMenu_Ctx * ctx)435 e_dbusmenu_unload(E_DBusMenu_Ctx *ctx)
436 {
437    Eldbus_Connection *conn;
438    Eldbus_Object *obj;
439    EINA_SAFETY_ON_NULL_RETURN(ctx);
440 
441    if (ctx->root_menu) e_dbusmenu_item_unref(ctx->root_menu);
442    obj = eldbus_proxy_object_get(ctx->proxy);
443    conn = eldbus_object_connection_get(obj);
444    eldbus_proxy_unref(ctx->proxy);
445    eldbus_object_unref(obj);
446    eldbus_connection_unref(conn);
447    free(ctx);
448 }
449 
450 E_API void
e_dbusmenu_pop_request_cb_set(E_DBusMenu_Ctx * ctx,E_DBusMenu_Pop_Request_Cb cb)451 e_dbusmenu_pop_request_cb_set(E_DBusMenu_Ctx *ctx, E_DBusMenu_Pop_Request_Cb cb)
452 {
453    ctx->pop_request_cb = cb;
454 }
455 
456 E_API void
e_dbusmenu_update_cb_set(E_DBusMenu_Ctx * ctx,E_DBusMenu_Update_Cb cb)457 e_dbusmenu_update_cb_set(E_DBusMenu_Ctx *ctx, E_DBusMenu_Update_Cb cb)
458 {
459    ctx->update_cb = cb;
460 }
461 
462