3fba27e09462a6b0dae79d9945fa727ca0322784
[framework/uifw/e17.git] / src / modules / systray / e_mod_main.c
1 /**
2  * systray implementation following freedesktop.org specification.
3  *
4  * @see: http://standards.freedesktop.org/systemtray-spec/latest/
5  *
6  * @todo: implement xembed, mostly done, at least relevant parts are done.
7  *        http://standards.freedesktop.org/xembed-spec/latest/
8  *
9  * @todo: implement messages/popup part of the spec (anyone using this at all?)
10  */
11
12 #include "e.h"
13 #include <X11/Xlib.h>
14 #include <X11/Xatom.h>
15 #include "e_mod_main.h"
16
17 #define RETRY_TIMEOUT 2.0
18
19 #define SYSTEM_TRAY_REQUEST_DOCK 0
20 #define SYSTEM_TRAY_BEGIN_MESSAGE 1
21 #define SYSTEM_TRAY_CANCEL_MESSAGE 2
22 #define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
23 #define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
24
25 /* XEMBED messages */
26 #define XEMBED_EMBEDDED_NOTIFY          0
27 #define XEMBED_WINDOW_ACTIVATE          1
28 #define XEMBED_WINDOW_DEACTIVATE        2
29 #define XEMBED_REQUEST_FOCUS            3
30 #define XEMBED_FOCUS_IN                 4
31 #define XEMBED_FOCUS_OUT                5
32 #define XEMBED_FOCUS_NEXT               6
33 #define XEMBED_FOCUS_PREV               7
34 /* 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */
35 #define XEMBED_MODALITY_ON              10
36 #define XEMBED_MODALITY_OFF             11
37 #define XEMBED_REGISTER_ACCELERATOR     12
38 #define XEMBED_UNREGISTER_ACCELERATOR   13
39 #define XEMBED_ACTIVATE_ACCELERATOR     14
40
41 /* Details for  XEMBED_FOCUS_IN: */
42 #define XEMBED_FOCUS_CURRENT            0
43 #define XEMBED_FOCUS_FIRST              1
44 #define XEMBED_FOCUS_LAST               2
45
46 typedef struct _Instance Instance;
47 typedef struct _Icon Icon;
48
49 struct _Icon
50 {
51    Ecore_X_Window win;
52    Evas_Object *o;
53    Instance *inst;
54 };
55
56 struct _Instance
57 {
58    E_Gadcon_Client *gcc;
59    E_Container *con;
60    Evas *evas;
61    struct
62    {
63       Ecore_X_Window parent;
64       Ecore_X_Window base;
65       Ecore_X_Window selection;
66    } win;
67    struct
68    {
69       Evas_Object *gadget;
70    } ui;
71    struct
72    {
73       Ecore_Event_Handler *message;
74       Ecore_Event_Handler *destroy;
75       Ecore_Event_Handler *show;
76       Ecore_Event_Handler *reparent;
77       Ecore_Event_Handler *sel_clear;
78       Ecore_Event_Handler *configure;
79    } handler;
80    struct
81    {
82       Ecore_Timer *retry;
83    } timer;
84    struct
85    {
86       Ecore_Job *size_apply;
87    } job;
88    Eina_List *icons;
89    E_Menu *menu;
90 };
91
92 static const char _Name[] = "Systray";
93 static const char _name[] = "systray";
94 static const char _group_gadget[] = "e/modules/systray/main";
95 static const char _part_box[] = "e.box";
96 static const char _part_size[] = "e.size";
97 static const char _sig_source[] = "e";
98 static const char _sig_enable[] = "e,action,enable";
99 static const char _sig_disable[] = "e,action,disable";
100
101 static Ecore_X_Atom _atom_manager = 0;
102 static Ecore_X_Atom _atom_st_orient = 0;
103 static Ecore_X_Atom _atom_st_visual = 0;
104 static Ecore_X_Atom _atom_st_op_code = 0;
105 static Ecore_X_Atom _atom_st_msg_data = 0;
106 static Ecore_X_Atom _atom_xembed = 0;
107 static Ecore_X_Atom _atom_xembed_info = 0;
108 static Ecore_X_Atom _atom_st_num = 0;
109 static int _last_st_num = -1;
110
111 static E_Module *systray_mod = NULL;
112 static Instance *instance = NULL; /* only one systray ever possible */
113 static char tmpbuf[PATH_MAX]; /* general purpose buffer, just use immediately */
114
115 static const char *
116 _systray_theme_path(void)
117 {
118 #define TF "/e-module-systray.edj"
119    unsigned int dirlen;
120    const char *moddir = e_module_dir_get(systray_mod);
121
122    dirlen = strlen(moddir);
123    if (dirlen >= sizeof(tmpbuf) - sizeof(TF))
124      return NULL;
125
126    memcpy(tmpbuf, moddir, dirlen);
127    memcpy(tmpbuf + dirlen, TF, sizeof(TF));
128
129    return tmpbuf;
130 #undef TF
131 }
132
133 static void
134 _systray_menu_cb_post(void *data, E_Menu *menu __UNUSED__)
135 {
136    Instance *inst = data;
137    if (!inst->menu) return;
138    e_object_del(E_OBJECT(inst->menu));
139    inst->menu = NULL;
140 }
141
142 static void
143 _systray_menu_new(Instance *inst, Evas_Event_Mouse_Down *ev)
144 {
145    E_Zone *zone;
146    E_Menu *m;
147    int x, y;
148
149    zone = e_util_zone_current_get(e_manager_current_get());
150
151    m = e_menu_new();
152    m = e_gadcon_client_util_menu_items_append(inst->gcc, m, 0);
153    e_menu_post_deactivate_callback_set(m, _systray_menu_cb_post, inst);
154    inst->menu = m;
155    e_gadcon_canvas_zone_geometry_get(inst->gcc->gadcon, &x, &y, NULL, NULL);
156    e_menu_activate_mouse(m, zone, x + ev->output.x, y + ev->output.y,
157                          1, 1, E_MENU_POP_DIRECTION_AUTO, ev->timestamp);
158 }
159
160 static void
161 _systray_cb_mouse_down(void *data, Evas *evas __UNUSED__, Evas_Object *obj __UNUSED__, void *event __UNUSED__)
162 {
163    Instance *inst = data;
164    Evas_Event_Mouse_Down *ev = event;
165
166    if ((ev->button == 3) && (!inst->menu))
167      _systray_menu_new(inst, ev);
168 }
169
170 static void
171 _systray_size_apply_do(Instance *inst)
172 {
173    const Evas_Object *o;
174    Evas_Coord x, y, w, h;
175
176    edje_object_message_signal_process(inst->ui.gadget);
177    o = edje_object_part_object_get(inst->ui.gadget, _part_box);
178    if (!o) return;
179    evas_object_size_hint_min_get(o, &w, &h);
180
181    if (w < 1) w = 1;
182    if (h < 1) h = 1;
183
184    if (eina_list_count(inst->icons) == 0)
185       ecore_x_window_hide(inst->win.base);
186    else
187       ecore_x_window_show(inst->win.base);
188
189    e_gadcon_client_aspect_set(inst->gcc, w, h);
190    e_gadcon_client_min_size_set(inst->gcc, w, h);
191
192    evas_object_geometry_get(o, &x, &y, &w, &h);
193    ecore_x_window_move_resize(inst->win.base, x, y, w, h);
194 }
195
196 static void
197 _systray_size_apply_delayed(void *data)
198 {
199    Instance *inst = data;
200    _systray_size_apply_do(inst);
201    inst->job.size_apply = NULL;
202 }
203
204 static void
205 _systray_size_apply(Instance *inst)
206 {
207    if (inst->job.size_apply) return;
208    inst->job.size_apply = ecore_job_add(_systray_size_apply_delayed, inst);
209 }
210
211 static void
212 _systray_cb_move(void *data, Evas *evas __UNUSED__, Evas_Object *o __UNUSED__, void *event __UNUSED__)
213 {
214    Instance *inst = data;
215    _systray_size_apply(inst);
216 }
217
218 static void
219 _systray_cb_resize(void *data, Evas *evas __UNUSED__, Evas_Object *o __UNUSED__, void *event __UNUSED__)
220 {
221    Instance *inst = data;
222    _systray_size_apply(inst);
223 }
224
225 static void
226 _systray_icon_geometry_apply(Icon *icon)
227 {
228    const Evas_Object *o;
229    Evas_Coord x, y, w, h, wx, wy;
230
231    o = edje_object_part_object_get(icon->inst->ui.gadget, _part_size);
232    if (!o) return;
233
234    evas_object_geometry_get(icon->o, &x, &y, &w, &h);
235    evas_object_geometry_get(o, &wx, &wy, NULL, NULL);
236    ecore_x_window_move_resize(icon->win, x - wx, y - wy, w, h);
237 }
238
239 static void
240 _systray_icon_cb_move(void *data, Evas *evas __UNUSED__, Evas_Object *o __UNUSED__, void *event __UNUSED__)
241 {
242    Icon *icon = data;
243    _systray_icon_geometry_apply(icon);
244 }
245
246 static void
247 _systray_icon_cb_resize(void *data, Evas *evas __UNUSED__, Evas_Object *o __UNUSED__, void *event __UNUSED__)
248 {
249    Icon *icon = data;
250    _systray_icon_geometry_apply(icon);
251 }
252
253 static Ecore_X_Gravity
254 _systray_gravity(const Instance *inst)
255 {
256    switch (inst->gcc->gadcon->orient)
257      {
258       case E_GADCON_ORIENT_FLOAT:
259          return ECORE_X_GRAVITY_STATIC;
260       case E_GADCON_ORIENT_HORIZ:
261          return ECORE_X_GRAVITY_CENTER;
262       case E_GADCON_ORIENT_VERT:
263          return ECORE_X_GRAVITY_CENTER;
264       case E_GADCON_ORIENT_LEFT:
265          return ECORE_X_GRAVITY_CENTER;
266       case E_GADCON_ORIENT_RIGHT:
267          return ECORE_X_GRAVITY_CENTER;
268       case E_GADCON_ORIENT_TOP:
269          return ECORE_X_GRAVITY_CENTER;
270       case E_GADCON_ORIENT_BOTTOM:
271          return ECORE_X_GRAVITY_CENTER;
272       case E_GADCON_ORIENT_CORNER_TL:
273          return ECORE_X_GRAVITY_S;
274       case E_GADCON_ORIENT_CORNER_TR:
275          return ECORE_X_GRAVITY_S;
276       case E_GADCON_ORIENT_CORNER_BL:
277          return ECORE_X_GRAVITY_N;
278       case E_GADCON_ORIENT_CORNER_BR:
279          return ECORE_X_GRAVITY_N;
280       case E_GADCON_ORIENT_CORNER_LT:
281          return ECORE_X_GRAVITY_E;
282       case E_GADCON_ORIENT_CORNER_RT:
283          return ECORE_X_GRAVITY_W;
284       case E_GADCON_ORIENT_CORNER_LB:
285          return ECORE_X_GRAVITY_E;
286       case E_GADCON_ORIENT_CORNER_RB:
287          return ECORE_X_GRAVITY_W;
288       default:
289          return ECORE_X_GRAVITY_STATIC;
290      }
291 }
292
293 static Evas_Coord
294 _systray_icon_size_normalize(Evas_Coord size)
295 {
296    const Evas_Coord *itr, sizes[] = {
297      16, 22, 24, 32, 36, 48, 64, 72, 96, 128, 192, 256, -1
298    };
299    for (itr = sizes; *itr > 0; itr++)
300      if (*itr == size)
301        return size;
302      else if (*itr > size)
303        {
304           if (itr > sizes)
305             return itr[-1];
306           else
307             return sizes[0];
308        }
309    return sizes[0];
310 }
311
312 static Icon *
313 _systray_icon_add(Instance *inst, const Ecore_X_Window win)
314 {
315    Ecore_X_Gravity gravity;
316    Evas_Object *o;
317    Evas_Coord w, h;
318    Icon *icon;
319
320    edje_object_part_geometry_get(inst->ui.gadget, _part_size,
321                                  NULL, NULL, &w, &h);
322    if (w > h)
323      w = h;
324    else
325      h = w;
326
327    w = h = _systray_icon_size_normalize(w);
328
329    o = evas_object_rectangle_add(inst->evas);
330    if (!o)
331      return NULL;
332    evas_object_color_set(o, 0, 0, 0, 0);
333    evas_object_resize(o, w, h);
334    evas_object_show(o);
335
336    icon = malloc(sizeof(*icon));
337    if (!icon)
338      {
339         evas_object_del(o);
340         return NULL;
341      }
342    icon->win = win;
343    icon->inst = inst;
344    icon->o = o;
345
346    gravity = _systray_gravity(inst);
347    ecore_x_icccm_size_pos_hints_set(win, 1, gravity,
348                                     w, h, w, h, w, h, 0, 0,
349                                     1.0, (double)w / (double)h);
350
351    ecore_x_window_reparent(win, inst->win.base, 0, 0);
352    ecore_x_window_resize(win, w, h);
353    ecore_x_window_raise(win);
354    ecore_x_window_client_manage(win);
355    ecore_x_window_save_set_add(win);
356    ecore_x_window_shape_events_select(win, 1);
357
358    //ecore_x_window_geometry_get(win, NULL, NULL, &w, &h);
359
360    evas_object_event_callback_add
361      (o, EVAS_CALLBACK_MOVE, _systray_icon_cb_move, icon);
362    evas_object_event_callback_add
363      (o, EVAS_CALLBACK_RESIZE, _systray_icon_cb_resize, icon);
364
365    inst->icons = eina_list_append(inst->icons, icon);
366    edje_object_part_box_append(inst->ui.gadget, _part_box, o);
367    _systray_size_apply_do(inst);
368    _systray_icon_geometry_apply(icon);
369
370    ecore_x_window_show(win);
371
372    return icon;
373 }
374
375 static void
376 _systray_icon_del_list(Instance *inst, Eina_List *l, Icon *icon)
377 {
378    inst->icons = eina_list_remove_list(inst->icons, l);
379
380    ecore_x_window_save_set_del(icon->win);
381    ecore_x_window_reparent(icon->win, None, 0, 0);
382    evas_object_del(icon->o);
383    free(icon);
384
385    _systray_size_apply(inst);
386 }
387
388 static Ecore_X_Atom
389 _systray_atom_st_get(int screen_num)
390 {
391    if ((_last_st_num == -1) || (_last_st_num != screen_num))
392      {
393         char buf[32];
394         snprintf(buf, sizeof(buf), "_NET_SYSTEM_TRAY_S%d", screen_num);
395         _atom_st_num = ecore_x_atom_get(buf);
396         _last_st_num = screen_num;
397      }
398
399    return _atom_st_num;
400 }
401
402 /* XXX TODO: should be in ecore_x */
403 static Eina_Bool
404 _systray_selection_owner_set(int screen_num, Ecore_X_Window win)
405 {
406    Ecore_X_Atom atom;
407    Ecore_X_Display *disp = ecore_x_display_get();
408    Ecore_X_Window cur_selection;
409    Eina_Bool ret;
410
411    atom = _systray_atom_st_get(screen_num);
412    XSetSelectionOwner(disp, atom, win, ecore_x_current_time_get());
413    ecore_x_sync();
414    cur_selection = XGetSelectionOwner(disp, atom);
415
416    ret = (cur_selection == win);
417    if (!ret)
418      fprintf(stderr, "SYSTRAY: tried to set selection to %#x, but got %#x\n",
419              win, cur_selection);
420
421    return ret;
422 }
423
424 static Eina_Bool
425 _systray_selection_owner_set_current(Instance *inst)
426 {
427    return _systray_selection_owner_set
428      (inst->con->manager->num, inst->win.selection);
429 }
430
431 static void
432 _systray_deactivate(Instance *inst)
433 {
434    Ecore_X_Window old;
435
436    if (inst->win.selection == None) return;
437
438    edje_object_signal_emit(inst->ui.gadget, _sig_disable, _sig_source);
439
440    while (inst->icons)
441      _systray_icon_del_list(inst, inst->icons, inst->icons->data);
442
443    old = inst->win.selection;
444    inst->win.selection = None;
445    _systray_selection_owner_set_current(inst);
446    ecore_x_sync();
447    ecore_x_window_free(old);
448    ecore_x_window_free(inst->win.base);
449    inst->win.base = None;
450 }
451
452 static Eina_Bool
453 _systray_base_create(Instance *inst)
454 {
455    const Evas_Object *o;
456    Evas_Coord x, y, w, h;
457    unsigned short r, g, b;
458    const char *color;
459
460    color = edje_object_data_get(inst->ui.gadget, inst->gcc->style);
461    if (!color)
462      color = edje_object_data_get(inst->ui.gadget, "default");
463
464    if (color && (sscanf(color, "%hu %hu %hu", &r, &g, &b) == 3))
465      {
466         r = (65535 * (unsigned int)r) / 255;
467         g = (65535 * (unsigned int)g) / 255;
468         b = (65535 * (unsigned int)b) / 255;
469      }
470    else
471      r = g = b = (unsigned short)65535;
472
473    o = edje_object_part_object_get(inst->ui.gadget, _part_size);
474    if (!o)
475      return 0;
476
477    evas_object_geometry_get(o, &x, &y, &w, &h);
478    if (w < 1) w = 1;
479    if (h < 1) h = 1;
480    inst->win.base = ecore_x_window_new(0, 0, 0, w, h);
481    ecore_x_window_reparent(inst->win.base, inst->win.parent, x, y); 
482    ecore_x_window_background_color_set(inst->win.base, r, g, b);
483    ecore_x_window_show(inst->win.base);
484    return 1;
485 }
486
487 static Eina_Bool
488 _systray_activate(Instance *inst)
489 {
490    unsigned int visual;
491    Ecore_X_Atom atom;
492    Ecore_X_Window old_win;
493    Ecore_X_Window_Attributes attr;
494    Ecore_X_Display *dpy;
495
496    if (inst->win.selection != None) return 1;
497
498    atom = _systray_atom_st_get(inst->con->manager->num);
499    dpy = ecore_x_display_get();
500    old_win = XGetSelectionOwner(dpy, atom);
501    if (old_win != None) return 0;
502
503    if (inst->win.base == None)
504      {
505         if (!_systray_base_create(inst))
506           return 0;
507      }
508
509    inst->win.selection = ecore_x_window_input_new(inst->win.base, 0, 0, 1, 1);
510    if (inst->win.selection == None)
511      {
512         ecore_x_window_free(inst->win.base);
513         inst->win.base = None;
514         return 0;
515      }
516
517    if (!_systray_selection_owner_set_current(inst))
518      {
519         ecore_x_window_free(inst->win.selection);
520         inst->win.selection = None;
521         ecore_x_window_free(inst->win.base);
522         inst->win.base = None;
523         return 0;
524      }
525
526    ecore_x_window_attributes_get(inst->win.base, &attr);
527
528    visual = XVisualIDFromVisual(attr.visual);
529    XChangeProperty(dpy, inst->win.selection, _atom_st_visual, XA_VISUALID,
530                    32, PropModeReplace, (void *)&visual, 1);
531
532    ecore_x_client_message32_send(inst->con->manager->root, _atom_manager,
533                                  ECORE_X_EVENT_MASK_WINDOW_CONFIGURE,
534                                  ecore_x_current_time_get(), atom,
535                                  inst->win.selection, 0, 0);
536
537    edje_object_signal_emit(inst->ui.gadget, _sig_enable, _sig_source);
538
539    return 1;
540 }
541
542 static Eina_Bool
543 _systray_activate_retry(void *data)
544 {
545    Instance *inst = data;
546    Eina_Bool ret;
547
548    fputs("SYSTRAY: reactivate...\n", stderr);
549    ret = _systray_activate(inst);
550    if (ret)
551      fputs("SYSTRAY: activate success!\n", stderr);
552    else
553      fprintf(stderr, "SYSTRAY: activate failure! retrying in %0.1f seconds\n",
554              RETRY_TIMEOUT);
555
556    if (!ret)
557      return ECORE_CALLBACK_RENEW;
558
559    inst->timer.retry = NULL;
560    return ECORE_CALLBACK_CANCEL;
561 }
562
563 static void
564 _systray_retry(Instance *inst)
565 {
566    if (inst->timer.retry) return;
567    inst->timer.retry = ecore_timer_add
568      (RETRY_TIMEOUT, _systray_activate_retry, inst);
569 }
570
571 static Eina_Bool
572 _systray_activate_retry_first(void *data)
573 {
574    Instance *inst = data;
575    Eina_Bool ret;
576
577    fputs("SYSTRAY: reactivate (first time)...\n", stderr);
578    ret = _systray_activate(inst);
579    if (ret)
580      {
581         fputs("SYSTRAY: activate success!\n", stderr);
582         inst->timer.retry = NULL;
583         return ECORE_CALLBACK_CANCEL;
584      }
585
586    edje_object_signal_emit(inst->ui.gadget, _sig_disable, _sig_source);
587
588    fprintf(stderr, "SYSTRAY: activate failure! retrying in %0.1f seconds\n",
589            RETRY_TIMEOUT);
590
591    inst->timer.retry = NULL;
592    _systray_retry(inst);
593    return ECORE_CALLBACK_CANCEL;
594 }
595
596 static void
597 _systray_handle_request_dock(Instance *inst, Ecore_X_Event_Client_Message *ev)
598 {
599    Ecore_X_Window win = (Ecore_X_Window)ev->data.l[2];
600    Ecore_X_Time time;
601    Ecore_X_Window_Attributes attr;
602    const Eina_List *l;
603    Icon *icon;
604    unsigned int val[2];
605    int r;
606
607    EINA_LIST_FOREACH(inst->icons, l, icon)
608      if (icon->win == win)
609        return;
610
611    if (!ecore_x_window_attributes_get(win, &attr))
612      {
613         fprintf(stderr, "SYSTRAY: could not get attributes of win %#x\n", win);
614         return;
615      }
616
617    icon = _systray_icon_add(inst, win);
618    if (!icon)
619      return;
620
621    r  = ecore_x_window_prop_card32_get(win, _atom_xembed_info, val, 2);
622    if (r < 2)
623      {
624         /*
625         fprintf(stderr, "SYSTRAY: win %#x does not support _XEMBED_INFO (%d)\n",
626                 win, r);
627         */
628         return;
629      }
630
631    time = ecore_x_current_time_get();
632    ecore_x_client_message32_send(win, _atom_xembed,
633                                  ECORE_X_EVENT_MASK_NONE,
634                                  time, XEMBED_EMBEDDED_NOTIFY, 0,
635                                  inst->win.selection, 0);
636 }
637
638 static void
639 _systray_handle_op_code(Instance *inst, Ecore_X_Event_Client_Message *ev)
640 {
641    unsigned long message = ev->data.l[1];
642
643    switch (message)
644      {
645       case SYSTEM_TRAY_REQUEST_DOCK:
646          _systray_handle_request_dock(inst, ev);
647          break;
648       case SYSTEM_TRAY_BEGIN_MESSAGE:
649       case SYSTEM_TRAY_CANCEL_MESSAGE:
650          fputs("SYSTRAY TODO: handle messages (anyone uses this?)\n", stderr);
651          break;
652       default:
653          fprintf(stderr,
654                  "SYSTRAY: error, unknown message op code: %ld, win: %#lx\n",
655                  message, ev->data.l[2]);
656      }
657 }
658
659 static void
660 _systray_handle_message(Instance *inst __UNUSED__, Ecore_X_Event_Client_Message *ev)
661 {
662    fprintf(stderr, "SYSTRAY TODO: message op: %ld, data: %ld, %ld, %ld\n",
663            ev->data.l[1], ev->data.l[2], ev->data.l[3], ev->data.l[4]);
664 }
665
666 static void
667 _systray_handle_xembed(Instance *inst __UNUSED__, Ecore_X_Event_Client_Message *ev __UNUSED__)
668 {
669    unsigned long message = ev->data.l[1];
670
671    switch (message)
672      {
673       case XEMBED_EMBEDDED_NOTIFY:
674       case XEMBED_WINDOW_ACTIVATE:
675       case XEMBED_WINDOW_DEACTIVATE:
676       case XEMBED_REQUEST_FOCUS:
677       case XEMBED_FOCUS_IN:
678       case XEMBED_FOCUS_OUT:
679       case XEMBED_FOCUS_NEXT:
680       case XEMBED_FOCUS_PREV:
681       case XEMBED_MODALITY_ON:
682       case XEMBED_MODALITY_OFF:
683       case XEMBED_REGISTER_ACCELERATOR:
684       case XEMBED_UNREGISTER_ACCELERATOR:
685       case XEMBED_ACTIVATE_ACCELERATOR:
686       default:
687          fprintf(stderr,
688                  "SYSTRAY: unsupported xembed: %#lx, %#lx, %#lx, %#lx\n",
689                  ev->data.l[1], ev->data.l[2], ev->data.l[3], ev->data.l[4]);
690      }
691 }
692
693 static Eina_Bool
694 _systray_cb_client_message(void *data, int type __UNUSED__, void *event)
695 {
696    Ecore_X_Event_Client_Message *ev = event;
697    Instance *inst = data;
698
699    if (ev->message_type == _atom_st_op_code)
700      _systray_handle_op_code(inst, ev);
701    else if (ev->message_type == _atom_st_msg_data)
702      _systray_handle_message(inst, ev);
703    else if (ev->message_type == _atom_xembed)
704      _systray_handle_xembed(inst, ev);
705
706    return ECORE_CALLBACK_PASS_ON;
707 }
708
709 static Eina_Bool
710 _systray_cb_window_destroy(void *data, int type __UNUSED__, void *event)
711 {
712    Ecore_X_Event_Window_Destroy *ev = event;
713    Instance *inst = data;
714    Icon *icon;
715    Eina_List *l;
716
717    EINA_LIST_FOREACH(inst->icons, l, icon)
718      if (icon->win == ev->win)
719        {
720           _systray_icon_del_list(inst, l, icon);
721           break;
722        }
723
724    return ECORE_CALLBACK_PASS_ON;
725 }
726
727 static Eina_Bool
728 _systray_cb_window_show(void *data, int type __UNUSED__, void *event)
729 {
730    Ecore_X_Event_Window_Show *ev = event;
731    Instance *inst = data;
732    Icon *icon;
733    Eina_List *l;
734
735    EINA_LIST_FOREACH(inst->icons, l, icon)
736      if (icon->win == ev->win)
737        {
738           _systray_icon_geometry_apply(icon);
739           break;
740        }
741
742    return ECORE_CALLBACK_PASS_ON;
743 }
744
745 static Eina_Bool
746 _systray_cb_window_configure(void *data, int type __UNUSED__, void *event)
747 {
748    Ecore_X_Event_Window_Configure *ev = event;
749    Instance *inst = data;
750    Icon *icon;
751    const Eina_List *l;
752
753    EINA_LIST_FOREACH(inst->icons, l, icon)
754      if (icon->win == ev->win)
755        {
756           _systray_icon_geometry_apply(icon);
757           break;
758        }
759
760    return ECORE_CALLBACK_PASS_ON;
761 }
762
763 static Eina_Bool
764 _systray_cb_reparent_notify(void *data, int type __UNUSED__, void *event)
765 {
766    Ecore_X_Event_Window_Reparent *ev = event;
767    Instance *inst = data;
768    Icon *icon;
769    Eina_List *l;
770
771    EINA_LIST_FOREACH(inst->icons, l, icon)
772      if ((icon->win == ev->win) && (ev->parent != inst->win.base))
773        {
774           _systray_icon_del_list(inst, l, icon);
775           break;
776        }
777
778    return ECORE_CALLBACK_PASS_ON;
779 }
780
781 static Eina_Bool
782 _systray_cb_selection_clear(void *data, int type __UNUSED__, void *event)
783 {
784    Ecore_X_Event_Selection_Clear *ev = event;
785    Instance *inst = data;
786
787    if ((ev->win == inst->win.selection) && (inst->win.selection != None) &&
788        (ev->atom == _systray_atom_st_get(inst->con->manager->num)))
789      {
790         edje_object_signal_emit(inst->ui.gadget, _sig_disable, _sig_source);
791
792         while (inst->icons)
793           _systray_icon_del_list(inst, inst->icons, inst->icons->data);
794
795         ecore_x_window_free(inst->win.selection);
796         inst->win.selection = None;
797         ecore_x_window_free(inst->win.base);
798         inst->win.base = None;
799         _systray_retry(inst);
800      }
801    return ECORE_CALLBACK_PASS_ON;
802 }
803
804 static void
805 _systray_theme(Evas_Object *o, const char *shelf_style, const char *gc_style)
806 {
807    const char base_theme[] = "base/theme/modules/systray";
808    const char *path = _systray_theme_path();
809    char buf[128], *p;
810    size_t len, avail;
811
812    len = eina_strlcpy(buf, _group_gadget, sizeof(buf));
813    if (len >= sizeof(buf))
814      goto fallback;
815    p = buf + len;
816    *p = '/';
817    p++;
818    avail = sizeof(buf) - len - 1;
819
820    if (shelf_style && gc_style)
821      {
822         size_t r;
823         r = snprintf(p, avail, "%s/%s", shelf_style, gc_style);
824         if (r < avail && e_theme_edje_object_set(o, base_theme, buf))
825           return;
826      }
827
828    if (shelf_style)
829      {
830         size_t r;
831         r = eina_strlcpy(p, shelf_style, avail);
832         if (r < avail && e_theme_edje_object_set(o, base_theme, buf))
833           return;
834      }
835
836    if (gc_style)
837      {
838         size_t r;
839         r = eina_strlcpy(p, gc_style, avail);
840         if (r < avail && e_theme_edje_object_set(o, base_theme, buf))
841           return;
842      }
843
844    if (e_theme_edje_object_set(o, base_theme, _group_gadget))
845      return;
846
847    if (shelf_style && gc_style)
848      {
849         size_t r;
850         r = snprintf(p, avail, "%s/%s", shelf_style, gc_style);
851         if (r < avail && edje_object_file_set(o, path, buf))
852           return;
853      }
854
855    if (shelf_style)
856      {
857         size_t r;
858         r = eina_strlcpy(p, shelf_style, avail);
859         if (r < avail && edje_object_file_set(o, path, buf))
860           return;
861      }
862
863    if (gc_style)
864      {
865         size_t r;
866         r = eina_strlcpy(p, gc_style, avail);
867         if (r < avail && edje_object_file_set(o, path, buf))
868           return;
869      }
870
871  fallback:
872    edje_object_file_set(o, path, _group_gadget);
873 }
874
875
876 static E_Gadcon_Client *
877 _gc_init(E_Gadcon *gc, const char *name, const char *id, const char *style)
878 {
879    Instance *inst;
880
881    // fprintf(stderr, "SYSTRAY: init name=%s, id=%s, style=%s\n", name, id, style);
882
883    if (!systray_mod)
884      return NULL;
885    if ((!id) || (instance))
886      {
887         e_util_dialog_internal
888           (_("Another systray exists"),
889            _("There can be only one systray gadget and "
890              "another one already exists."));
891         return NULL;
892      }
893
894    inst = E_NEW(Instance, 1);
895    if (!inst)
896      return NULL;
897    inst->evas = gc->evas;
898    inst->con = e_container_current_get(e_manager_current_get());
899    if (!inst->con)
900      {
901         E_FREE(inst);
902         return NULL;
903      }
904
905    if ((gc->shelf) && (gc->shelf->popup))
906      inst->win.parent = gc->shelf->popup->evas_win;
907    else
908      inst->win.parent = (Ecore_X_Window) ecore_evas_window_get(gc->ecore_evas);
909
910    inst->win.base = None;
911    inst->win.selection = None;
912
913    inst->ui.gadget = edje_object_add(inst->evas);
914
915    _systray_theme(inst->ui.gadget, gc->shelf ? gc->shelf->style : NULL, style);
916
917    inst->gcc = e_gadcon_client_new(gc, name, id, style, inst->ui.gadget);
918    if (!inst->gcc)
919      {
920         evas_object_del(inst->ui.gadget);
921         E_FREE(inst);
922         return NULL;
923      }
924
925    inst->gcc->data = inst;
926
927    if (!_systray_activate(inst))
928      {
929         if (!inst->timer.retry)
930           inst->timer.retry = ecore_timer_add
931             (0.1, _systray_activate_retry_first, inst);
932         else
933           edje_object_signal_emit(inst->ui.gadget, _sig_disable, _sig_source);
934      }
935
936    evas_object_event_callback_add(inst->ui.gadget, EVAS_CALLBACK_MOUSE_DOWN,
937                                   _systray_cb_mouse_down, inst);
938    evas_object_event_callback_add(inst->ui.gadget, EVAS_CALLBACK_MOVE,
939                                   _systray_cb_move, inst);
940    evas_object_event_callback_add(inst->ui.gadget, EVAS_CALLBACK_RESIZE,
941                                   _systray_cb_resize, inst);
942
943    inst->handler.message = ecore_event_handler_add
944      (ECORE_X_EVENT_CLIENT_MESSAGE, _systray_cb_client_message, inst);
945    inst->handler.destroy = ecore_event_handler_add
946      (ECORE_X_EVENT_WINDOW_DESTROY, _systray_cb_window_destroy, inst);
947    inst->handler.show = ecore_event_handler_add
948      (ECORE_X_EVENT_WINDOW_SHOW, _systray_cb_window_show, inst);
949    inst->handler.reparent = ecore_event_handler_add
950      (ECORE_X_EVENT_WINDOW_REPARENT, _systray_cb_reparent_notify, inst);
951    inst->handler.sel_clear = ecore_event_handler_add
952      (ECORE_X_EVENT_SELECTION_CLEAR, _systray_cb_selection_clear, inst);
953    inst->handler.configure = ecore_event_handler_add
954      (ECORE_X_EVENT_WINDOW_CONFIGURE, _systray_cb_window_configure, inst);
955
956    instance = inst;
957    return inst->gcc;
958 }
959
960 /* Called when Gadget_Container says stop */
961 static void
962 _gc_shutdown(E_Gadcon_Client *gcc)
963 {
964    Instance *inst = gcc->data;
965
966    // fprintf(stderr, "SYSTRAY: shutdown %p, inst=%p\n", gcc, inst);
967
968    if (!inst)
969      return;
970
971    if (inst->menu)
972      {
973         e_menu_post_deactivate_callback_set(inst->menu, NULL, NULL);
974         e_object_del(E_OBJECT(inst->menu));
975      }
976
977    _systray_deactivate(inst);
978    evas_object_del(inst->ui.gadget);
979
980    if (inst->handler.message)
981      ecore_event_handler_del(inst->handler.message);
982    if (inst->handler.destroy)
983      ecore_event_handler_del(inst->handler.destroy);
984    if (inst->handler.show)
985      ecore_event_handler_del(inst->handler.show);
986    if (inst->handler.reparent)
987      ecore_event_handler_del(inst->handler.reparent);
988    if (inst->handler.sel_clear)
989      ecore_event_handler_del(inst->handler.sel_clear);
990    if (inst->handler.configure)
991      ecore_event_handler_del(inst->handler.configure);
992    if (inst->timer.retry)
993      ecore_timer_del(inst->timer.retry);
994    if (inst->job.size_apply)
995      ecore_job_del(inst->job.size_apply);
996
997    if (instance == inst)
998      instance = NULL;
999
1000    E_FREE(inst);
1001    gcc->data = NULL;
1002 }
1003
1004 static void
1005 _gc_orient(E_Gadcon_Client *gcc, E_Gadcon_Orient orient)
1006 {
1007    Instance *inst = gcc->data;
1008    const char *signal;
1009    unsigned int systray_orient;
1010
1011    if (!inst)
1012      return;
1013
1014    switch (orient)
1015      {
1016       case E_GADCON_ORIENT_FLOAT:
1017          signal = "e,action,orient,float";
1018          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1019          break;
1020       case E_GADCON_ORIENT_HORIZ:
1021          signal = "e,action,orient,horiz";
1022          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1023          break;
1024       case E_GADCON_ORIENT_VERT:
1025          signal = "e,action,orient,vert";
1026          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_VERT;
1027          break;
1028       case E_GADCON_ORIENT_LEFT:
1029          signal = "e,action,orient,left";
1030          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_VERT;
1031          break;
1032       case E_GADCON_ORIENT_RIGHT:
1033          signal = "e,action,orient,right";
1034          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_VERT;
1035          break;
1036       case E_GADCON_ORIENT_TOP:
1037          signal = "e,action,orient,top";
1038          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1039          break;
1040       case E_GADCON_ORIENT_BOTTOM:
1041          signal = "e,action,orient,bottom";
1042          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1043          break;
1044       case E_GADCON_ORIENT_CORNER_TL:
1045          signal = "e,action,orient,corner_tl";
1046          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1047          break;
1048       case E_GADCON_ORIENT_CORNER_TR:
1049          signal = "e,action,orient,corner_tr";
1050          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1051          break;
1052       case E_GADCON_ORIENT_CORNER_BL:
1053          signal = "e,action,orient,corner_bl";
1054          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1055          break;
1056       case E_GADCON_ORIENT_CORNER_BR:
1057          signal = "e,action,orient,corner_br";
1058          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1059          break;
1060       case E_GADCON_ORIENT_CORNER_LT:
1061          signal = "e,action,orient,corner_lt";
1062          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_VERT;
1063          break;
1064       case E_GADCON_ORIENT_CORNER_RT:
1065          signal = "e,action,orient,corner_rt";
1066          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_VERT;
1067          break;
1068       case E_GADCON_ORIENT_CORNER_LB:
1069          signal = "e,action,orient,corner_lb";
1070          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_VERT;
1071          break;
1072       case E_GADCON_ORIENT_CORNER_RB:
1073          signal = "e,action,orient,corner_rb";
1074          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_VERT;
1075          break;
1076       default:
1077          signal = "e,action,orient,horiz";
1078          systray_orient = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
1079      }
1080
1081    ecore_x_window_prop_card32_set
1082      (inst->win.selection, _atom_st_orient, &systray_orient, 1);
1083
1084    edje_object_signal_emit(inst->ui.gadget, signal, _sig_source);
1085    edje_object_message_signal_process(inst->ui.gadget);
1086    _systray_size_apply(inst);
1087 }
1088
1089 static char *
1090 _gc_label(E_Gadcon_Client_Class *client_class __UNUSED__)
1091 {
1092    return _("Systray");
1093 }
1094
1095 static Evas_Object *
1096 _gc_icon(E_Gadcon_Client_Class *client_class __UNUSED__, Evas *evas)
1097 {
1098    Evas_Object *o;
1099
1100    o = edje_object_add(evas);
1101    edje_object_file_set(o, _systray_theme_path(), "icon");
1102    return o;
1103 }
1104
1105 static const char *
1106 _gc_id_new(E_Gadcon_Client_Class *client_class __UNUSED__)
1107 {
1108    if (!instance)
1109      return _name;
1110    else
1111      return NULL;
1112 }
1113
1114 static const E_Gadcon_Client_Class _gc_class =
1115 {
1116   GADCON_CLIENT_CLASS_VERSION, _name,
1117   {
1118      _gc_init, _gc_shutdown, _gc_orient, _gc_label, _gc_icon, _gc_id_new, NULL,
1119      e_gadcon_site_is_shelf
1120   },
1121   E_GADCON_CLIENT_STYLE_INSET
1122 };
1123
1124 EAPI E_Module_Api e_modapi = {E_MODULE_API_VERSION, _Name};
1125
1126 EAPI void *
1127 e_modapi_init(E_Module *m)
1128 {
1129    systray_mod = m;
1130
1131    e_gadcon_provider_register(&_gc_class);
1132
1133    if (!_atom_manager)
1134      _atom_manager = ecore_x_atom_get("MANAGER");
1135    if (!_atom_st_orient)
1136      _atom_st_orient = ecore_x_atom_get("_NET_SYSTEM_TRAY_ORIENTATION");
1137    if (!_atom_st_visual)
1138      _atom_st_visual = ecore_x_atom_get("_NET_SYSTEM_TRAY_VISUAL");
1139    if (!_atom_st_op_code)
1140      _atom_st_op_code = ecore_x_atom_get("_NET_SYSTEM_TRAY_OPCODE");
1141    if (!_atom_st_msg_data)
1142      _atom_st_msg_data = ecore_x_atom_get("_NET_SYSTEM_TRAY_MESSAGE_DATA");
1143    if (!_atom_xembed)
1144      _atom_xembed = ecore_x_atom_get("_XEMBED");
1145    if (!_atom_xembed_info)
1146      _atom_xembed_info = ecore_x_atom_get("_XEMBED_INFO");
1147
1148    return m;
1149 }
1150
1151 EAPI int
1152 e_modapi_shutdown(E_Module *m __UNUSED__)
1153 {
1154    e_gadcon_provider_unregister(&_gc_class);
1155    systray_mod = NULL;
1156    return 1;
1157 }
1158
1159 EAPI int
1160 e_modapi_save(E_Module *m __UNUSED__)
1161 {
1162    return 1;
1163 }