1 #include "e_mod_notifier_host_private.h"
2 
3 #define WATCHER_BUS "org.kde.StatusNotifierWatcher"
4 #define WATCHER_PATH "/StatusNotifierWatcher"
5 #define WATCHER_IFACE "org.kde.StatusNotifierWatcher"
6 
7 #define ITEM_IFACE "org.kde.StatusNotifierItem"
8 
9 const char *Category_Names[] = {
10    "unknown", "SystemServices", NULL
11 };
12 
13 const char *Status_Names[] = {
14    "unknown", "Active", "Passive", "NeedsAttention", NULL
15 };
16 
17 static Context_Notifier_Host *ctx = NULL;
18 
19 void
systray_notifier_item_free(Notifier_Item * item)20 systray_notifier_item_free(Notifier_Item *item)
21 {
22    Eldbus_Object *obj;
23    Eldbus_Signal_Handler *sig;
24    Instance_Notifier_Host *host_inst;
25    EINA_INLIST_FOREACH(ctx->instances, host_inst)
26      {
27         Notifier_Item_Icon *ii;
28         EINA_INLIST_FOREACH(host_inst->ii_list, ii)
29           {
30              if (ii->item == item)
31                break;
32           }
33         if (!ii)
34           continue;
35         host_inst->ii_list = eina_inlist_remove(host_inst->ii_list,
36                                                 EINA_INLIST_GET(ii));
37         evas_object_del(ii->icon);
38         free(ii);
39         systray_size_updated(host_inst->inst);
40      }
41    if (item->menu_path)
42      e_dbusmenu_unload(item->menu_data);
43    eina_stringshare_del(item->bus_id);
44    eina_stringshare_del(item->path);
45    free(item->imgdata);
46    free(item->attnimgdata);
47    if (item->attention_icon_name)
48      eina_stringshare_del(item->attention_icon_name);
49    if (item->icon_name)
50      eina_stringshare_del(item->icon_name);
51    if (item->icon_path)
52      eina_stringshare_del(item->icon_path);
53    if (item->id)
54      eina_stringshare_del(item->id);
55    if (item->menu_path)
56      eina_stringshare_del(item->menu_path);
57    if (item->title)
58      eina_stringshare_del(item->title);
59    EINA_LIST_FREE(item->signals, sig)
60      eldbus_signal_handler_del(sig);
61    obj = eldbus_proxy_object_get(item->proxy);
62    eldbus_proxy_unref(item->proxy);
63    eldbus_object_unref(obj);
64    ctx->item_list = eina_inlist_remove(ctx->item_list, EINA_INLIST_GET(item));
65    free(item);
66 }
67 
68 static void
image_load(const char * name,const char * path,uint32_t * imgdata,int w,int h,Evas_Object * image)69 image_load(const char *name, const char *path, uint32_t *imgdata, int w, int h, Evas_Object *image)
70 {
71    const char **ext, *exts[] =
72    {
73       ".png",
74       ".jpg",
75       NULL
76    };
77    printf("SYSTRAY: load image name=[%s] path=[%s] imgdata=[%p] size=[%ix%i]\n", name, path, imgdata, w, h);
78    if (path && path[0] && name)
79      {
80         char buf[PATH_MAX];
81         const char **theme, *themes[] =
82           {
83              e_config->icon_theme,
84              "hicolor",
85 // hmm sometimes this is there
86 //             "emblems",
87              NULL
88           };
89 
90         for (theme = themes; *theme; theme++)
91           {
92              unsigned int *i, sizes[] =
93                {
94                   512, 256, 192, 128, 96, 72, 64, 48, 40, 36, 32, 24, 22, 16, 0
95                };
96 
97              snprintf(buf, sizeof(buf), "%s/%s", path, *theme);
98              if (!ecore_file_is_dir(buf)) continue;
99              for (i = sizes; *i; i++)
100                {
101                   snprintf(buf, sizeof(buf), "%s/%s/%ux%u", path, *theme, *i, *i);
102                   if (!ecore_file_is_dir(buf)) continue;
103                   for (ext = exts; *ext; ext++)
104                     {
105                        snprintf(buf, sizeof(buf), "%s/%s/%ux%u/status/%s%s", path, *theme, *i, *i, name, *ext);
106                        if (ecore_file_exists(buf))
107                          {
108                             e_icon_file_set(image, buf);
109                             return;
110                          }
111                        snprintf(buf, sizeof(buf), "%s/%s/%ux%u/apps/%s%s", path, *theme, *i, *i, name, *ext);
112                        if (ecore_file_exists(buf))
113                          {
114                             e_icon_file_set(image, buf);
115                             return;
116                          }
117                     }
118                }
119           }
120         for (ext = exts; *ext; ext++)
121           {
122              snprintf(buf, sizeof(buf), "%s/%s%s", path, name, *ext);
123              if (ecore_file_exists(buf))
124                {
125                   e_icon_file_set(image, buf);
126                   return;
127                }
128           }
129      }
130    if (name && name[0] && e_util_icon_theme_set(image, name)) return;
131    if (imgdata)
132      {
133         Evas_Object *o;
134 
135         o = evas_object_image_filled_add(evas_object_evas_get(image));
136         evas_object_image_alpha_set(o, 1);
137         evas_object_image_size_set(o, w, h);
138         evas_object_image_data_set(o, imgdata);
139         e_icon_image_object_set(image, o);
140      }
141    else
142      e_util_icon_theme_set(image, "dialog-error");
143 }
144 
145 static void
_sub_item_clicked_cb(void * data,E_Menu * m EINA_UNUSED,E_Menu_Item * mi EINA_UNUSED)146 _sub_item_clicked_cb(void *data, E_Menu *m EINA_UNUSED, E_Menu_Item *mi EINA_UNUSED)
147 {
148    E_DBusMenu_Item *item = data;
149    e_dbusmenu_event_send(item, E_DBUSMENU_ITEM_EVENT_CLICKED);
150 }
151 
152 static void
_menu_post_deactivate(void * data,E_Menu * m)153 _menu_post_deactivate(void *data, E_Menu *m)
154 {
155    Eina_List *iter;
156    E_Menu_Item *mi;
157    E_Gadcon *gadcon = data;
158    E_DBusMenu_Item *item;
159 
160    item = e_object_data_get(E_OBJECT(m));
161    if (item)
162      {
163         e_object_data_set(E_OBJECT(m), NULL);
164         e_dbusmenu_item_unref(item);
165      }
166 
167    if (gadcon) e_gadcon_locked_set(gadcon, 0);
168    EINA_LIST_FOREACH(m->items, iter, mi)
169      {
170         item = e_object_data_get(E_OBJECT(mi));
171         if (item)
172           {
173              e_object_data_set(E_OBJECT(m), NULL);
174              e_dbusmenu_item_unref(item);
175           }
176         if (mi->submenu) e_menu_deactivate(mi->submenu);
177      }
178    e_object_del(E_OBJECT(m));
179 }
180 
181 static E_Menu *
_item_submenu_new(E_DBusMenu_Item * item,E_Menu_Item * mi)182 _item_submenu_new(E_DBusMenu_Item *item, E_Menu_Item *mi)
183 {
184    E_DBusMenu_Item *child;
185    E_Menu *m;
186    E_Menu_Item *submi;
187 
188    m = e_menu_new();
189    e_dbusmenu_item_ref(item);
190    e_object_data_set(E_OBJECT(m), item);
191    e_menu_post_deactivate_callback_set(m, _menu_post_deactivate, NULL);
192    if (mi) e_menu_item_submenu_set(mi, m);
193 
194    EINA_INLIST_FOREACH(item->sub_items, child)
195      {
196         if (!child->visible) continue;
197         submi = e_menu_item_new(m);
198         e_dbusmenu_item_ref(child);
199         e_object_data_set(E_OBJECT(submi), child);
200         if (child->type == E_DBUSMENU_ITEM_TYPE_SEPARATOR)
201           e_menu_item_separator_set(submi, 1);
202         else
203           {
204              e_menu_item_label_set(submi, child->label);
205              e_menu_item_callback_set(submi, _sub_item_clicked_cb, child);
206              if (!child->enabled) e_menu_item_disabled_set(submi, 1);
207              if (child->toggle_type == E_DBUSMENU_ITEM_TOGGLE_TYPE_CHECKMARK)
208                e_menu_item_check_set(submi, 1);
209              else if (child->toggle_type == E_DBUSMENU_ITEM_TOGGLE_TYPE_RADIO)
210                e_menu_item_radio_set(submi, 1);
211              if (child->toggle_type)
212                e_menu_item_toggle_set(submi, child->toggle_state);
213              if (child->sub_items)
214                _item_submenu_new(child, submi);
215              e_util_menu_item_theme_icon_set(submi, child->icon_name);
216           }
217      }
218    return m;
219 }
220 
221 void
_clicked_item_cb(void * data,Evas * evas,Evas_Object * obj EINA_UNUSED,void * event)222 _clicked_item_cb(void *data, Evas *evas, Evas_Object *obj EINA_UNUSED, void *event)
223 {
224    Notifier_Item_Icon *ii = data;
225    Evas_Event_Mouse_Down *ev = event;
226    E_DBusMenu_Item *root_item;
227    E_Menu *m;
228    E_Zone *zone;
229    int x, y;
230    E_Gadcon *gadcon = evas_object_data_get(ii->icon, "gadcon");
231 
232    if (ev->button != 1) return;
233 
234    EINA_SAFETY_ON_NULL_RETURN(gadcon);
235    if (!ii->item->dbus_item) return;
236    root_item = ii->item->dbus_item;
237    EINA_SAFETY_ON_FALSE_RETURN(root_item->is_submenu);
238 
239    m = _item_submenu_new(root_item, NULL);
240    e_gadcon_locked_set(gadcon, 1);
241    e_menu_post_deactivate_callback_set(m, _menu_post_deactivate, gadcon);
242 
243    zone = e_gadcon_zone_get(gadcon);
244    evas_pointer_canvas_xy_get(e_comp->evas, &x, &y);
245    e_menu_activate_mouse(m, zone, x, y, 1, 1, E_MENU_POP_DIRECTION_DOWN, ev->timestamp);
246    evas_event_feed_mouse_up(evas, ev->button,
247                          EVAS_BUTTON_NONE, ev->timestamp, NULL);
248 }
249 
250 void
systray_notifier_update_menu(void * data,E_DBusMenu_Item * new_root_item)251 systray_notifier_update_menu(void *data, E_DBusMenu_Item *new_root_item)
252 {
253    Notifier_Item *item = data;
254    item->dbus_item = new_root_item;
255 }
256 
257 static void
image_scale(Instance_Notifier_Host * notifier_inst,Notifier_Item_Icon * ii)258 image_scale(Instance_Notifier_Host *notifier_inst, Notifier_Item_Icon *ii)
259 {
260    Evas_Coord sz;
261    switch (systray_gadcon_get(notifier_inst->inst)->orient)
262      {
263       case E_GADCON_ORIENT_FLOAT:
264       case E_GADCON_ORIENT_HORIZ:
265       case E_GADCON_ORIENT_TOP:
266       case E_GADCON_ORIENT_BOTTOM:
267       case E_GADCON_ORIENT_CORNER_TL:
268       case E_GADCON_ORIENT_CORNER_TR:
269       case E_GADCON_ORIENT_CORNER_BL:
270       case E_GADCON_ORIENT_CORNER_BR:
271         if (systray_gadcon_get(notifier_inst->inst)->shelf)
272           sz = systray_gadcon_get(notifier_inst->inst)->shelf->h;
273         else
274           evas_object_geometry_get(notifier_inst->inst->gcc->o_frame ?:
275             notifier_inst->inst->gcc->o_base, NULL, NULL, NULL, &sz);
276         break;
277 
278       case E_GADCON_ORIENT_VERT:
279       case E_GADCON_ORIENT_LEFT:
280       case E_GADCON_ORIENT_RIGHT:
281       case E_GADCON_ORIENT_CORNER_LT:
282       case E_GADCON_ORIENT_CORNER_RT:
283       case E_GADCON_ORIENT_CORNER_LB:
284       case E_GADCON_ORIENT_CORNER_RB:
285       default:
286         if (systray_gadcon_get(notifier_inst->inst)->shelf)
287           sz = systray_gadcon_get(notifier_inst->inst)->shelf->w;
288         else
289           evas_object_geometry_get(notifier_inst->inst->gcc->o_frame ?:
290             notifier_inst->inst->gcc->o_base, NULL, NULL, &sz, NULL);
291         break;
292      }
293    sz = sz - 5;
294    evas_object_resize(ii->icon, sz, sz);
295 }
296 
297 static void
_systray_notifier_inst_item_update(Instance_Notifier_Host * host_inst,Notifier_Item * item,Eina_Bool search)298 _systray_notifier_inst_item_update(Instance_Notifier_Host *host_inst, Notifier_Item *item, Eina_Bool search)
299 {
300    Notifier_Item_Icon *ii = NULL;
301    if (!search)
302      goto jump_search;
303    EINA_INLIST_FOREACH(host_inst->ii_list, ii)
304      {
305         if (ii->item == item)
306           break;
307      }
308 jump_search:
309    if (!ii)
310      {
311         ii = calloc(1, sizeof(Notifier_Item_Icon));
312         ii->item = item;
313         host_inst->ii_list = eina_inlist_append(host_inst->ii_list,
314                                                       EINA_INLIST_GET(ii));
315      }
316 
317    if (!ii->icon)
318      {
319         ii->icon = e_icon_add(evas_object_evas_get(host_inst->edje));
320         EINA_SAFETY_ON_NULL_RETURN(ii->icon);
321         image_scale(host_inst, ii);
322         evas_object_data_set(ii->icon, "gadcon", host_inst->gadcon);
323         evas_object_event_callback_add(ii->icon, EVAS_CALLBACK_MOUSE_DOWN,
324                                        _clicked_item_cb, ii);
325      }
326    switch (item->status)
327      {
328       case STATUS_ACTIVE:
329         {
330            image_load(item->icon_name, item->icon_path, item->imgdata, item->imgw, item->imgh, ii->icon);
331            if (!evas_object_visible_get(ii->icon))
332              {
333                 systray_edje_box_append(host_inst->inst, ii->icon);
334                 evas_object_show(ii->icon);
335              }
336            break;
337         }
338       case STATUS_PASSIVE:
339         {
340            if (evas_object_visible_get(ii->icon))
341              {
342                 systray_edje_box_remove(host_inst->inst, ii->icon);
343                 evas_object_hide(ii->icon);
344              }
345            break;
346         }
347       case STATUS_ATTENTION:
348         {
349            image_load(item->attention_icon_name, item->icon_path, item->attnimgdata, item->attnimgw, item->attnimgh, ii->icon);
350            if (!evas_object_visible_get(ii->icon))
351              {
352                 systray_edje_box_append(host_inst->inst, ii->icon);
353                 evas_object_show(ii->icon);
354              }
355            break;
356         }
357       default:
358         {
359            WRN("unhandled status");
360            break;
361         }
362      }
363    systray_size_updated(host_inst->inst);
364 }
365 
366 void
systray_notifier_item_update(Notifier_Item * item)367 systray_notifier_item_update(Notifier_Item *item)
368 {
369    Instance_Notifier_Host *inst;
370    EINA_INLIST_FOREACH(ctx->instances, inst)
371      _systray_notifier_inst_item_update(inst, item, EINA_TRUE);
372 }
373 
374 Instance_Notifier_Host *
systray_notifier_host_new(Instance * inst,E_Gadcon * gadcon)375 systray_notifier_host_new(Instance *inst, E_Gadcon *gadcon)
376 {
377    Instance_Notifier_Host *host_inst = NULL;
378    Notifier_Item *item;
379    host_inst = calloc(1, sizeof(Instance_Notifier_Host));
380    EINA_SAFETY_ON_NULL_RETURN_VAL(host_inst, NULL);
381    host_inst->inst = inst;
382    host_inst->edje = systray_edje_get(inst);
383    host_inst->gadcon = gadcon;
384    ctx->instances = eina_inlist_append(ctx->instances, EINA_INLIST_GET(host_inst));
385 
386    EINA_INLIST_FOREACH(ctx->item_list, item)
387      _systray_notifier_inst_item_update(host_inst, item, EINA_FALSE);
388 
389    return host_inst;
390 }
391 
392 void
systray_notifier_host_free(Instance_Notifier_Host * notifier)393 systray_notifier_host_free(Instance_Notifier_Host *notifier)
394 {
395    while (notifier->ii_list)
396      {
397         Notifier_Item_Icon *ii = EINA_INLIST_CONTAINER_GET(notifier->ii_list, Notifier_Item_Icon);
398         notifier->ii_list = eina_inlist_remove(notifier->ii_list,
399                                                notifier->ii_list);
400         free(ii);
401      }
402    ctx->instances = eina_inlist_remove(ctx->instances, EINA_INLIST_GET(notifier));
403    free(notifier);
404 }
405 
406 void
systray_notifier_host_init(void)407 systray_notifier_host_init(void)
408 {
409    ctx = calloc(1, sizeof(Context_Notifier_Host));
410    EINA_SAFETY_ON_NULL_RETURN(ctx);
411    systray_notifier_dbus_init(ctx);
412 }
413 
414 void
systray_notifier_host_shutdown(void)415 systray_notifier_host_shutdown(void)
416 {
417    Eldbus_Pending *p;
418 
419    EINA_LIST_FREE(ctx->pending, p) eldbus_pending_cancel(p);
420    systray_notifier_dbus_shutdown(ctx);
421    free(ctx);
422    ctx = NULL;
423 }
424