EFL core migration @74576
[framework/uifw/ecore.git] / src / lib / ecore_x / xlib / ecore_x_dnd.c
1 #ifdef HAVE_CONFIG_H
2 # include <config.h>
3 #endif /* ifdef HAVE_CONFIG_H */
4
5 #include <stdlib.h>
6 #include <string.h>
7
8 #include "Ecore.h"
9 #include "ecore_x_private.h"
10 #include "Ecore_X.h"
11 #include "Ecore_X_Atoms.h"
12
13 EAPI int ECORE_X_EVENT_XDND_ENTER = 0;
14 EAPI int ECORE_X_EVENT_XDND_POSITION = 0;
15 EAPI int ECORE_X_EVENT_XDND_STATUS = 0;
16 EAPI int ECORE_X_EVENT_XDND_LEAVE = 0;
17 EAPI int ECORE_X_EVENT_XDND_DROP = 0;
18 EAPI int ECORE_X_EVENT_XDND_FINISHED = 0;
19
20 static Ecore_X_DND_Source *_source = NULL;
21 static Ecore_X_DND_Target *_target = NULL;
22 static int _ecore_x_dnd_init_count = 0;
23
24 typedef struct _Version_Cache_Item
25 {
26    Ecore_X_Window win;
27    int            ver;
28 } Version_Cache_Item;
29 static Version_Cache_Item *_version_cache = NULL;
30 static int _version_cache_num = 0, _version_cache_alloc = 0;
31 static void (*_posupdatecb)(void *,
32                             Ecore_X_Xdnd_Position *);
33 static void *_posupdatedata;
34
35 void
36 _ecore_x_dnd_init(void)
37 {
38    if (!_ecore_x_dnd_init_count)
39      {
40         _source = calloc(1, sizeof(Ecore_X_DND_Source));
41         if (!_source) return;
42         _source->version = ECORE_X_DND_VERSION;
43         _source->win = None;
44         _source->dest = None;
45         _source->state = ECORE_X_DND_SOURCE_IDLE;
46         _source->prev.window = 0;
47
48         _target = calloc(1, sizeof(Ecore_X_DND_Target));
49         if (!_target)
50           {
51              free(_source);
52              _source = NULL;
53              return;
54           }
55         _target->win = None;
56         _target->source = None;
57         _target->state = ECORE_X_DND_TARGET_IDLE;
58
59         ECORE_X_EVENT_XDND_ENTER = ecore_event_type_new();
60         ECORE_X_EVENT_XDND_POSITION = ecore_event_type_new();
61         ECORE_X_EVENT_XDND_STATUS = ecore_event_type_new();
62         ECORE_X_EVENT_XDND_LEAVE = ecore_event_type_new();
63         ECORE_X_EVENT_XDND_DROP = ecore_event_type_new();
64         ECORE_X_EVENT_XDND_FINISHED = ecore_event_type_new();
65      }
66
67    _ecore_x_dnd_init_count++;
68 }
69
70 void
71 _ecore_x_dnd_shutdown(void)
72 {
73    _ecore_x_dnd_init_count--;
74    if (_ecore_x_dnd_init_count > 0)
75      return;
76
77    if (_source)
78      free(_source);
79
80    _source = NULL;
81
82    if (_target)
83      free(_target);
84
85    _target = NULL;
86
87    _ecore_x_dnd_init_count = 0;
88 }
89
90 static Eina_Bool
91 _ecore_x_dnd_converter_copy(char *target __UNUSED__,
92                             void *data,
93                             int size,
94                             void **data_ret,
95                             int *size_ret,
96                             Ecore_X_Atom *tprop __UNUSED__,
97                             int *count __UNUSED__)
98 {
99    XTextProperty text_prop;
100    char *mystr;
101    XICCEncodingStyle style = XTextStyle;
102
103    if (!data || !size)
104      return EINA_FALSE;
105
106    mystr = calloc(1, size + 1);
107    if (!mystr)
108      return EINA_FALSE;
109
110    memcpy(mystr, data, size);
111
112    if (XmbTextListToTextProperty(_ecore_x_disp, &mystr, 1, style,
113                                  &text_prop) == Success)
114      {
115         int bufsize = strlen((char *)text_prop.value) + 1;
116         *data_ret = malloc(bufsize);
117         if (!*data_ret)
118           {
119              free(mystr);
120              return EINA_FALSE;
121           }
122         memcpy(*data_ret, text_prop.value, bufsize);
123         *size_ret = bufsize;
124         XFree(text_prop.value);
125         free(mystr);
126         return EINA_TRUE;
127      }
128    else
129      {
130         free(mystr);
131         return EINA_FALSE;
132      }
133 }
134
135 EAPI void
136 ecore_x_dnd_aware_set(Ecore_X_Window win,
137                       Eina_Bool on)
138 {
139    Ecore_X_Atom prop_data = ECORE_X_DND_VERSION;
140
141    LOGFN(__FILE__, __LINE__, __FUNCTION__);
142    if (on)
143      ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_AWARE,
144                                       XA_ATOM, 32, &prop_data, 1);
145    else
146      ecore_x_window_prop_property_del(win, ECORE_X_ATOM_XDND_AWARE);
147 }
148
149 EAPI int
150 ecore_x_dnd_version_get(Ecore_X_Window win)
151 {
152    unsigned char *prop_data;
153    int num;
154    Version_Cache_Item *t;
155
156    LOGFN(__FILE__, __LINE__, __FUNCTION__);
157    // this looks hacky - and it is, but we need a way of caching info about
158    // a window while dragging, because we literally query this every mouse
159    // move and going to and from x multiple times per move is EXPENSIVE
160    // and slows things down, puts lots of load on x etc.
161    if (_source->state == ECORE_X_DND_SOURCE_DRAGGING)
162      if (_version_cache)
163        {
164           int i;
165
166           for (i = 0; i < _version_cache_num; i++)
167             {
168                if (_version_cache[i].win == win)
169                  return _version_cache[i].ver;
170             }
171        }
172
173    if (ecore_x_window_prop_property_get(win, ECORE_X_ATOM_XDND_AWARE,
174                                         XA_ATOM, 32, &prop_data, &num))
175      {
176         int version = (int)*prop_data;
177         free(prop_data);
178         if (_source->state == ECORE_X_DND_SOURCE_DRAGGING)
179           {
180              _version_cache_num++;
181              if (_version_cache_num > _version_cache_alloc)
182                _version_cache_alloc += 16;
183
184              t = realloc(_version_cache,
185                          _version_cache_alloc *
186                          sizeof(Version_Cache_Item));
187              if (!t) return 0;
188              _version_cache = t;
189              _version_cache[_version_cache_num - 1].win = win;
190              _version_cache[_version_cache_num - 1].ver = version;
191           }
192
193         return version;
194      }
195
196    if (_source->state == ECORE_X_DND_SOURCE_DRAGGING)
197      {
198         _version_cache_num++;
199         if (_version_cache_num > _version_cache_alloc)
200           _version_cache_alloc += 16;
201
202         t = realloc(_version_cache, _version_cache_alloc *
203                     sizeof(Version_Cache_Item));
204         if (!t) return 0;
205         _version_cache = t;
206         _version_cache[_version_cache_num - 1].win = win;
207         _version_cache[_version_cache_num - 1].ver = 0;
208      }
209
210    return 0;
211 }
212
213 EAPI Eina_Bool
214 ecore_x_dnd_type_isset(Ecore_X_Window win,
215                        const char *type)
216 {
217    int num, i, ret = EINA_FALSE;
218    unsigned char *data;
219    Ecore_X_Atom *atoms, atom;
220
221    LOGFN(__FILE__, __LINE__, __FUNCTION__);
222    if (!ecore_x_window_prop_property_get(win, ECORE_X_ATOM_XDND_TYPE_LIST,
223                                          XA_ATOM, 32, &data, &num))
224      return ret;
225
226    atom = ecore_x_atom_get(type);
227    atoms = (Ecore_X_Atom *)data;
228
229    for (i = 0; i < num; ++i)
230      {
231         if (atom == atoms[i])
232           {
233              ret = EINA_TRUE;
234              break;
235           }
236      }
237
238    XFree(data);
239    return ret;
240 }
241
242 EAPI void
243 ecore_x_dnd_type_set(Ecore_X_Window win,
244                      const char *type,
245                      Eina_Bool on)
246 {
247    Ecore_X_Atom atom;
248    Ecore_X_Atom *oldset = NULL, *newset = NULL;
249    int i, j = 0, num = 0;
250    unsigned char *data = NULL;
251    unsigned char *old_data = NULL;
252
253    LOGFN(__FILE__, __LINE__, __FUNCTION__);
254    atom = ecore_x_atom_get(type);
255    ecore_x_window_prop_property_get(win, ECORE_X_ATOM_XDND_TYPE_LIST,
256                                     XA_ATOM, 32, &old_data, &num);
257    oldset = (Ecore_X_Atom *)old_data;
258
259    LOGFN(__FILE__, __LINE__, __FUNCTION__);
260    if (on)
261      {
262         if (ecore_x_dnd_type_isset(win, type))
263           {
264              XFree(old_data);
265              return;
266           }
267
268         newset = calloc(num + 1, sizeof(Ecore_X_Atom));
269         if (!newset)
270           return;
271
272         data = (unsigned char *)newset;
273
274         for (i = 0; i < num; i++)
275           newset[i + 1] = oldset[i];
276         /* prepend the new type */
277         newset[0] = atom;
278
279         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_TYPE_LIST,
280                                          XA_ATOM, 32, data, num + 1);
281      }
282    else
283      {
284         if (!ecore_x_dnd_type_isset(win, type))
285           {
286              XFree(old_data);
287              return;
288           }
289
290         newset = calloc(num - 1, sizeof(Ecore_X_Atom));
291         if (!newset)
292           {
293              XFree(old_data);
294              return;
295           }
296
297         data = (unsigned char *)newset;
298         for (i = 0; i < num; i++)
299           if (oldset[i] != atom)
300             newset[j++] = oldset[i];
301
302         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_TYPE_LIST,
303                                          XA_ATOM, 32, data, num - 1);
304      }
305
306    XFree(oldset);
307    free(newset);
308 }
309
310 EAPI void
311 ecore_x_dnd_types_set(Ecore_X_Window win,
312                       const char **types,
313                       unsigned int num_types)
314 {
315    Ecore_X_Atom *newset = NULL;
316    unsigned int i;
317    unsigned char *data = NULL;
318
319    LOGFN(__FILE__, __LINE__, __FUNCTION__);
320    if (!num_types)
321      ecore_x_window_prop_property_del(win, ECORE_X_ATOM_XDND_TYPE_LIST);
322    else
323      {
324         newset = calloc(num_types, sizeof(Ecore_X_Atom));
325         if (!newset)
326           return;
327
328         data = (unsigned char *)newset;
329         for (i = 0; i < num_types; i++)
330           {
331              newset[i] = ecore_x_atom_get(types[i]);
332              ecore_x_selection_converter_atom_add(newset[i],
333                                                   _ecore_x_dnd_converter_copy);
334           }
335         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_TYPE_LIST,
336                                          XA_ATOM, 32, data, num_types);
337         free(newset);
338      }
339 }
340
341 EAPI void
342 ecore_x_dnd_actions_set(Ecore_X_Window win,
343                         Ecore_X_Atom *actions,
344                         unsigned int num_actions)
345 {
346    unsigned int i;
347    unsigned char *data = NULL;
348
349    LOGFN(__FILE__, __LINE__, __FUNCTION__);
350    if (!num_actions)
351      ecore_x_window_prop_property_del(win, ECORE_X_ATOM_XDND_ACTION_LIST);
352    else
353      {
354         data = (unsigned char *)actions;
355         for (i = 0; i < num_actions; i++)
356           {
357              ecore_x_selection_converter_atom_add(actions[i],
358                                                   _ecore_x_dnd_converter_copy);
359           }
360         ecore_x_window_prop_property_set(win, ECORE_X_ATOM_XDND_ACTION_LIST,
361                                          XA_ATOM, 32, data, num_actions);
362      }
363 }
364
365 /**
366  * The DND position update cb is called Ecore_X sends a DND position to a
367  * client.
368  *
369  * It essentially mirrors some of the data sent in the position message.
370  * Generally this cb should be set just before position update is called.
371  * Please note well you need to look after your own data pointer if someone
372  * trashes you position update cb set.
373  *
374  * It is considered good form to clear this when the dnd event finishes.
375  *
376  * @param cb Callback to updated each time ecore_x sends a position update.
377  * @param data User data.
378  */
379 EAPI void
380 ecore_x_dnd_callback_pos_update_set(
381   void (*cb)(void *,
382              Ecore_X_Xdnd_Position *data),
383   const void *data)
384 {
385    _posupdatecb = cb;
386    _posupdatedata = (void *)data; /* Discard the const early */
387 }
388
389 Ecore_X_DND_Source *
390 _ecore_x_dnd_source_get(void)
391 {
392    return _source;
393 }
394
395 Ecore_X_DND_Target *
396 _ecore_x_dnd_target_get(void)
397 {
398    return _target;
399 }
400
401 EAPI Eina_Bool
402 ecore_x_dnd_begin(Ecore_X_Window source,
403                   unsigned char *data,
404                   int size)
405 {
406    LOGFN(__FILE__, __LINE__, __FUNCTION__);
407    if (!ecore_x_dnd_version_get(source))
408      return EINA_FALSE;
409
410    /* Take ownership of XdndSelection */
411    if (!ecore_x_selection_xdnd_set(source, data, size))
412      return EINA_FALSE;
413
414    if (_version_cache)
415      {
416         free(_version_cache);
417         _version_cache = NULL;
418         _version_cache_num = 0;
419         _version_cache_alloc = 0;
420      }
421
422    ecore_x_window_shadow_tree_flush();
423
424    _source->win = source;
425    ecore_x_window_ignore_set(_source->win, 1);
426    _source->state = ECORE_X_DND_SOURCE_DRAGGING;
427    _source->time = _ecore_x_event_last_time;
428    _source->prev.window = 0;
429
430    /* Default Accepted Action: move */
431    _source->action = ECORE_X_ATOM_XDND_ACTION_MOVE;
432    _source->accepted_action = None;
433    _source->dest = None;
434
435    return EINA_TRUE;
436 }
437
438 EAPI Eina_Bool
439 ecore_x_dnd_drop(void)
440 {
441    XEvent xev;
442    int status = EINA_FALSE;
443
444    LOGFN(__FILE__, __LINE__, __FUNCTION__);
445    if (_source->dest)
446      {
447         xev.xany.type = ClientMessage;
448         xev.xany.display = _ecore_x_disp;
449         xev.xclient.format = 32;
450         xev.xclient.window = _source->dest;
451
452         if (_source->will_accept)
453           {
454              xev.xclient.message_type = ECORE_X_ATOM_XDND_DROP;
455              xev.xclient.data.l[0] = _source->win;
456              xev.xclient.data.l[1] = 0;
457              xev.xclient.data.l[2] = _source->time;
458              XSendEvent(_ecore_x_disp, _source->dest, False, 0, &xev);
459              _source->state = ECORE_X_DND_SOURCE_DROPPED;
460              status = EINA_TRUE;
461           }
462         else
463           {
464              xev.xclient.message_type = ECORE_X_ATOM_XDND_LEAVE;
465              xev.xclient.data.l[0] = _source->win;
466              xev.xclient.data.l[1] = 0;
467              XSendEvent(_ecore_x_disp, _source->dest, False, 0, &xev);
468              _source->state = ECORE_X_DND_SOURCE_IDLE;
469           }
470      }
471    else
472      {
473         /* Dropping on nothing */
474         ecore_x_selection_xdnd_clear();
475         _source->state = ECORE_X_DND_SOURCE_IDLE;
476      }
477
478    ecore_x_window_ignore_set(_source->win, 0);
479
480    _source->prev.window = 0;
481
482    return status;
483 }
484
485 EAPI void
486 ecore_x_dnd_send_status(Eina_Bool will_accept,
487                         Eina_Bool suppress,
488                         Ecore_X_Rectangle rectangle,
489                         Ecore_X_Atom action)
490 {
491    XEvent xev;
492
493    if (_target->state == ECORE_X_DND_TARGET_IDLE)
494      return;
495
496    LOGFN(__FILE__, __LINE__, __FUNCTION__);
497    memset(&xev, 0, sizeof(XEvent));
498
499    _target->will_accept = will_accept;
500
501    xev.xclient.type = ClientMessage;
502    xev.xclient.display = _ecore_x_disp;
503    xev.xclient.message_type = ECORE_X_ATOM_XDND_STATUS;
504    xev.xclient.format = 32;
505    xev.xclient.window = _target->source;
506
507    xev.xclient.data.l[0] = _target->win;
508    xev.xclient.data.l[1] = 0;
509    if (will_accept)
510      xev.xclient.data.l[1] |= 0x1UL;
511
512    if (!suppress)
513      xev.xclient.data.l[1] |= 0x2UL;
514
515    /* Set rectangle information */
516    xev.xclient.data.l[2] = rectangle.x;
517    xev.xclient.data.l[2] <<= 16;
518    xev.xclient.data.l[2] |= rectangle.y;
519    xev.xclient.data.l[3] = rectangle.width;
520    xev.xclient.data.l[3] <<= 16;
521    xev.xclient.data.l[3] |= rectangle.height;
522
523    if (will_accept)
524      {
525         xev.xclient.data.l[4] = action;
526         _target->accepted_action = action;
527      }
528    else
529      {
530         xev.xclient.data.l[4] = None;
531         _target->accepted_action = action;
532      }
533
534    XSendEvent(_ecore_x_disp, _target->source, False, 0, &xev);
535 }
536
537 EAPI void
538 ecore_x_dnd_send_finished(void)
539 {
540    XEvent xev;
541
542    if (_target->state == ECORE_X_DND_TARGET_IDLE)
543      return;
544
545    LOGFN(__FILE__, __LINE__, __FUNCTION__);
546    xev.xany.type = ClientMessage;
547    xev.xany.display = _ecore_x_disp;
548    xev.xclient.message_type = ECORE_X_ATOM_XDND_FINISHED;
549    xev.xclient.format = 32;
550    xev.xclient.window = _target->source;
551
552    xev.xclient.data.l[0] = _target->win;
553    xev.xclient.data.l[1] = 0;
554    xev.xclient.data.l[2] = 0;
555    if (_target->will_accept)
556      {
557         xev.xclient.data.l[1] |= 0x1UL;
558         xev.xclient.data.l[2] = _target->accepted_action;
559      }
560
561    XSendEvent(_ecore_x_disp, _target->source, False, 0, &xev);
562
563    _target->state = ECORE_X_DND_TARGET_IDLE;
564 }
565
566 EAPI void
567 ecore_x_dnd_source_action_set(Ecore_X_Atom action)
568 {
569    _source->action = action;
570    if (_source->prev.window)
571      _ecore_x_dnd_drag(_source->prev.window, _source->prev.x, _source->prev.y);
572 }
573
574 EAPI Ecore_X_Atom
575 ecore_x_dnd_source_action_get(void)
576 {
577    return _source->action;
578 }
579
580 void
581 _ecore_x_dnd_drag(Ecore_X_Window root,
582                   int x,
583                   int y)
584 {
585    XEvent xev;
586    Ecore_X_Window win;
587    Ecore_X_Window *skip;
588    Ecore_X_Xdnd_Position pos;
589    int num;
590
591    if (_source->state != ECORE_X_DND_SOURCE_DRAGGING)
592      return;
593
594    /* Preinitialize XEvent struct */
595    memset(&xev, 0, sizeof(XEvent));
596    xev.xany.type = ClientMessage;
597    xev.xany.display = _ecore_x_disp;
598    xev.xclient.format = 32;
599
600    /* Attempt to find a DND-capable window under the cursor */
601    skip = ecore_x_window_ignore_list(&num);
602 // WARNING - this function is HEAVY. it goes to and from x a LOT walking the
603 // window tree - use the SHADOW version - makes a 1-off tree copy, then uses
604 // that instead.
605 //   win = ecore_x_window_at_xy_with_skip_get(x, y, skip, num);
606    win = ecore_x_window_shadow_tree_at_xy_with_skip_get(root, x, y, skip, num);
607
608 // NOTE: This now uses the shadow version to find parent windows
609 //   while ((win) && !(ecore_x_dnd_version_get(win)))
610 //     win = ecore_x_window_parent_get(win);
611    while ((win) && !(ecore_x_dnd_version_get(win)))
612      win = ecore_x_window_shadow_parent_get(root, win);
613
614    /* Send XdndLeave to current destination window if we have left it */
615    if ((_source->dest) && (win != _source->dest))
616      {
617         xev.xclient.window = _source->dest;
618         xev.xclient.message_type = ECORE_X_ATOM_XDND_LEAVE;
619         xev.xclient.data.l[0] = _source->win;
620         xev.xclient.data.l[1] = 0;
621
622         XSendEvent(_ecore_x_disp, _source->dest, False, 0, &xev);
623         _source->suppress = 0;
624      }
625
626    if (win)
627      {
628         int x1, x2, y1, y2;
629
630         _source->version = MIN(ECORE_X_DND_VERSION,
631                                ecore_x_dnd_version_get(win));
632         if (win != _source->dest)
633           {
634              int i;
635              unsigned char *data;
636              Ecore_X_Atom *types;
637
638              ecore_x_window_prop_property_get(_source->win,
639                                               ECORE_X_ATOM_XDND_TYPE_LIST,
640                                               XA_ATOM,
641                                               32,
642                                               &data,
643                                               &num);
644              types = (Ecore_X_Atom *)data;
645
646              /* Entered new window, send XdndEnter */
647              xev.xclient.window = win;
648              xev.xclient.message_type = ECORE_X_ATOM_XDND_ENTER;
649              xev.xclient.data.l[0] = _source->win;
650              xev.xclient.data.l[1] = 0;
651              if (num > 3)
652                xev.xclient.data.l[1] |= 0x1UL;
653              else
654                xev.xclient.data.l[1] &= 0xfffffffeUL;
655
656              xev.xclient.data.l[1] |= ((unsigned long)_source->version) << 24;
657
658              for (i = 2; i < 5; i++)
659                xev.xclient.data.l[i] = 0;
660              for (i = 0; i < MIN(num, 3); ++i)
661                xev.xclient.data.l[i + 2] = types[i];
662              XFree(data);
663              XSendEvent(_ecore_x_disp, win, False, 0, &xev);
664              _source->await_status = 0;
665              _source->will_accept = 0;
666           }
667
668         /* Determine if we're still in the rectangle from the last status */
669         x1 = _source->rectangle.x;
670         x2 = _source->rectangle.x + _source->rectangle.width;
671         y1 = _source->rectangle.y;
672         y2 = _source->rectangle.y + _source->rectangle.height;
673
674         if ((!_source->await_status) ||
675             (!_source->suppress) ||
676             ((x < x1) || (x > x2) || (y < y1) || (y > y2)))
677           {
678              xev.xclient.window = win;
679              xev.xclient.message_type = ECORE_X_ATOM_XDND_POSITION;
680              xev.xclient.data.l[0] = _source->win;
681              xev.xclient.data.l[1] = 0; /* Reserved */
682              xev.xclient.data.l[2] = ((x << 16) & 0xffff0000) | (y & 0xffff);
683              xev.xclient.data.l[3] = _source->time; /* Version 1 */
684              xev.xclient.data.l[4] = _source->action; /* Version 2, Needs to be pre-set */
685              XSendEvent(_ecore_x_disp, win, False, 0, &xev);
686
687              _source->await_status = 1;
688           }
689      }
690
691    if (_posupdatecb)
692      {
693         pos.position.x = x;
694         pos.position.y = y;
695         pos.win = win;
696         pos.prev = _source->dest;
697         _posupdatecb(_posupdatedata, &pos);
698      }
699
700    _source->prev.x = x;
701    _source->prev.y = y;
702    _source->prev.window = root;
703    _source->dest = win;
704 }
705
706 /* vim:set ts=8 sw=3 sts=3 expandtab cino=>5n-2f0^-2{2(0W1st0 :*/