1 #include "unix/guts.h"
2 #include "Window.h"
3 #include "Application.h"
4
5 /* See specs at https://www.freedesktop.org/wiki/Specifications/XDND/ */
6
7 #define dndAsk 0x100
8
9 static int
xdnd_atom_to_constant(Atom atom)10 xdnd_atom_to_constant( Atom atom )
11 {
12 if ( atom == XdndActionMove)
13 return dndMove;
14 else if ( atom == XdndActionCopy)
15 return dndCopy;
16 else if ( atom == XdndActionLink)
17 return dndLink;
18 else if ( atom == XdndActionAsk)
19 return dndAsk;
20 return dndNone;
21 }
22
23 static int
xdnd_constant_to_atom(int cmd)24 xdnd_constant_to_atom( int cmd )
25 {
26 switch ( cmd ) {
27 case dndCopy : return XdndActionCopy;
28 case dndMove : return XdndActionMove;
29 case dndLink : return XdndActionLink;
30 case dndAsk : return XdndActionAsk;
31 default : return None;
32 }
33 }
34
35 static XWindow
query_xdnd_target(XWindow w)36 query_xdnd_target(XWindow w)
37 {
38 Bool found = false;
39 XWindow foo, child;
40 unsigned mask;
41 int i, bar, nprops;
42 Atom *atoms;
43
44 atoms = XListProperties(DISP, w, &nprops);
45 for (i = 0; i < nprops; i++)
46 if (atoms[i] == XdndAware) {
47 found = true;
48 break;
49 }
50 if ( nprops) XFree(atoms);
51 if ( found ) return w;
52
53 if ( !XQueryPointer( DISP, w, &foo, &child, &bar, &bar, &bar, &bar, &mask))
54 return None;
55 if ( child == None )
56 return None;
57 return query_xdnd_target(child);
58 }
59
60 static int
query_pointer(XWindow * receiver,Point * p)61 query_pointer(XWindow * receiver, Point *p)
62 {
63 XWindow foo;
64 int bar, x, y, ret;
65 unsigned mask;
66
67 XQueryPointer( DISP, guts.root, &foo, &foo, &x, &y, &bar, &bar, &mask);
68 ret =
69 (( mask & Button1Mask) ? mb1 : 0) |
70 (( mask & Button2Mask) ? mb2 : 0) |
71 (( mask & Button3Mask) ? mb3 : 0) |
72 (( mask & Button4Mask) ? mb4 : 0) |
73 (( mask & Button5Mask) ? mb5 : 0) |
74 (( mask & Button6Mask) ? mb6 : 0) |
75 (( mask & Button7Mask) ? mb7 : 0) |
76 (( mask & ShiftMask) ? kmShift : 0) |
77 (( mask & ControlMask) ? kmCtrl : 0) |
78 (( mask & Mod1Mask) ? kmAlt : 0);
79
80 if ( p ) {
81 p->x = x;
82 p->y = y;
83 }
84
85 if ( receiver )
86 *receiver = query_xdnd_target(guts.root);
87
88 return ret;
89 }
90
91
92 static int
xdnd_read_ask_actions(void)93 xdnd_read_ask_actions(void)
94 {
95 Atom type;
96 int i, n_list, rps, format, ret = 0;
97 unsigned long list_size;
98 unsigned char *list_data;
99
100 if ( guts. xdndr_action_list_cache > 0 )
101 return guts. xdndr_action_list_cache;
102
103 list_data = NULL;
104
105 list_size = 0;
106 list_data = malloc(0);
107 rps = prima_read_property( guts.xdndr_source, XdndActionList, &type, &format,
108 &list_size, &list_data, 0);
109 if ( rps != RPS_OK || type != XA_ATOM || format != CF_32) {
110 free(list_data);
111 return guts. xdndr_action_list_cache = dndCopy;
112 }
113 n_list = list_size / sizeof(Atom);
114
115 for ( i = 0; i < n_list; i++) {
116 int action = xdnd_atom_to_constant(((Atom*)list_data)[i]);
117 if (( action & dndMask ) != 0)
118 ret |= action;
119 }
120
121 free(list_data);
122 return guts. xdndr_action_list_cache = ret;
123 }
124
125 static void
xdnd_send_message(XWindow source,Atom cmd,long l0,long l1,long l2,long l3,long l4)126 xdnd_send_message( XWindow source, Atom cmd, long l0, long l1, long l2, long l3, long l4 )
127 {
128 XClientMessageEvent m;
129 memset(&m, 0, sizeof(m));
130 m.type = ClientMessage;
131 m.display = DISP;
132 m.window = source;
133 m.message_type = cmd;
134 m.format = 32;
135 m.data.l[0] = l0;
136 m.data.l[1] = l1;
137 m.data.l[2] = l2;
138 m.data.l[3] = l3;
139 m.data.l[4] = l4;
140 XCHECKPOINT;
141 XSendEvent(DISP, source, False, NoEventMask, (XEvent*)&m);
142 XSync( DISP, false);
143 XCHECKPOINT;
144 }
145
146 static void
xdnd_send_message_ev(XWindow w,XEvent * ev)147 xdnd_send_message_ev( XWindow w, XEvent* ev)
148 {
149 ev->type = ClientMessage;
150 ev->xclient.display = DISP;
151 ev->xclient.format = 32;
152 ev->xclient.window = w;
153 XSendEvent(DISP, w, False, NoEventMask, ev);
154 XSync( DISP, false);
155 XCHECKPOINT;
156 }
157
158 static Bool
handle_xdnd_leave(Handle self)159 handle_xdnd_leave( Handle self)
160 {
161 if (guts.xdnd_disabled) return false;
162
163 if ( guts.xdndr_receiver != self )
164 self = guts.xdndr_receiver;
165 guts.xdndr_receiver = NULL_HANDLE;
166 guts.xdndr_source = None;
167
168 if ( guts.xdnd_clipboard )
169 C(guts.xdnd_clipboard)-> xdnd_receiving = false;
170
171 if ( guts.xdndr_widget ) {
172 XWindow dummy;
173 Event ev = { cmDragEnd };
174 ev.dnd.allow = 0;
175 ev.dnd.clipboard = NULL_HANDLE;
176 ev.dnd.modmap = query_pointer(NULL,&ev.dnd.where);
177 ev.dnd.action = dndNone;
178 ev.dnd.counterpart = guts. xdnds_widget;
179 XTranslateCoordinates(DISP, guts.root, X(guts.xdndr_widget)->client,
180 ev.dnd.where.x, ev.dnd.where.y,
181 &ev.dnd.where.x, &ev.dnd.where.y,
182 &dummy);
183 guts.xdnd_disabled = true;
184 CComponent(guts.xdndr_widget)-> message(guts.xdndr_widget, &ev);
185 guts.xdnd_disabled = false;
186 guts.xdndr_widget = NULL_HANDLE;
187 }
188
189 return true;
190 }
191
192
193 static const char *
atom_name(Atom atom)194 atom_name(Atom atom)
195 {
196 return atom ? XGetAtomName(DISP,atom) : "None";
197 }
198
199
200 static void
update_pointer(Handle self,int dnd)201 update_pointer(Handle self, int dnd)
202 {
203 int pointer = crDragNone;
204 switch ( dnd ) {
205 case dndNone: pointer = crDragNone; break;
206 case dndCopy: pointer = crDragCopy; break;
207 case dndMove: pointer = crDragMove; break;
208 case dndLink: pointer = crDragLink; break;
209 }
210 apc_pointer_set_shape(self, pointer);
211 }
212
213 static Bool
handle_xdnd_status(Handle self,XEvent * xev)214 handle_xdnd_status( Handle self, XEvent* xev)
215 {
216 int old_response;
217 Event ev = { cmDragResponse };
218
219 if ( xev->xclient.data.l[0] != guts.xdnds_target)
220 return false;
221
222 guts.xdnds_last_drop_response = xev->xclient.data.l[1] & 1;
223 if ( xev->xclient.data.l[1] & 2 ) {
224 bzero(&guts.xdnds_suppress_events_within, sizeof(Box));
225 } else {
226 guts.xdnds_suppress_events_within.x = xev->xclient.data.l[2] >> 16;
227 guts.xdnds_suppress_events_within.y = xev->xclient.data.l[2] & 0xffff;
228 guts.xdnds_suppress_events_within.width = xev->xclient.data.l[3] >> 16;
229 guts.xdnds_suppress_events_within.height = xev->xclient.data.l[3] & 0xffff;
230 }
231
232 ev.dnd.allow = guts.xdnds_last_drop_response;
233
234 old_response = guts. xdnds_last_action_response;
235 guts. xdnds_last_action_response = (guts.xdnds_version > 1) ?
236 xdnd_atom_to_constant(xev->xclient.data.l[4]) : dndCopy;
237 ev.dnd.action = guts.xdnds_last_action_response;
238 if ( ev.dnd.action == dndAsk )
239 ev.dnd.action = guts.xdnds_last_action;
240 ev.dnd.counterpart = guts. xdndr_widget;
241
242 if (guts.xdnds_widget) {
243 guts.xdnd_disabled = true;
244 CComponent(guts.xdnds_widget)-> message(guts.xdnds_widget, &ev);
245 guts.xdnd_disabled = false;
246 }
247
248 if ( guts.xdnds_default_pointers && old_response != guts.xdnds_last_action_response)
249 update_pointer(guts.xdnds_widget, guts.xdnds_last_action_response );
250
251 return true;
252 }
253
254 static Bool
handle_xdnd_finished(Handle self,XEvent * xev)255 handle_xdnd_finished( Handle self, XEvent* xev)
256 {
257 Cdebug("dnd:finished disabled=%d/%x %x\n",guts.xdnd_disabled,xev->xclient.data.l[0],guts.xdnds_target);
258 if ( guts.xdnd_disabled )
259 return false;
260 if ( xev->xclient.data.l[0] != guts.xdnds_target)
261 return false;
262 if ( guts. xdnds_version > 4 ) {
263 guts.xdnds_last_drop_response = xev->xclient.data.l[1] & 1;
264 guts.xdnds_last_action_response = (guts.xdnds_last_drop_response ?
265 xdnd_atom_to_constant(xev->xclient.data.l[2]) : dndNone);
266 } else {
267 guts.xdnds_last_drop_response = 1;
268 }
269 Cdebug("dnd:finish with %d\n", guts.xdnds_last_action_response);
270 guts.xdnds_finished = true;
271 return true;
272 }
273
274 static Bool
handle_xdnd_enter(Handle self,XEvent * xev)275 handle_xdnd_enter( Handle self, XEvent* xev)
276 {
277 int i;
278 PClipboardSysData CC;
279
280 if (guts.xdnd_disabled || !guts. xdnd_clipboard) return false;
281
282 /* consistency */
283 if ( guts.xdndr_receiver != NULL_HANDLE ) {
284 handle_xdnd_leave(guts. xdndr_receiver);
285 guts. xdndr_receiver = NULL_HANDLE;
286 }
287 CC = C(guts. xdnd_clipboard);
288 CC-> xdnd_receiving = true;
289 guts. xdndr_receiver = self;
290 guts. xdndr_action_list_cache = 0;
291
292 /* pre-read available formats */
293 guts.xdndr_source = xev->xclient.data.l[0];
294 guts.xdndr_version = xev->xclient.data.l[1] >> 24;
295
296 if (guts.xdndr_source == guts.xdnds_sender) {
297 Cdebug("dnd:enter local\n");
298 return true;
299 }
300
301 Cdebug("dnd:enter %08x v%d %d %s %s %s\n", guts.xdndr_source, guts.xdndr_version,
302 xev->xclient.data.l[1] & 1,
303 atom_name(xev->xclient.data.l[2]),
304 atom_name(xev->xclient.data.l[3]),
305 atom_name(xev->xclient.data.l[4])
306 );
307 for ( i = 0; i < guts. clipboard_formats_count; i++) {
308 prima_detach_xfers( CC, i, true);
309 if ( !CC-> xdnd_sending )
310 prima_clipboard_kill_item( CC-> internal, i);
311 prima_clipboard_kill_item( CC-> external, i);
312 }
313
314 /* prefill 1-3 targets as if CF_TARGETS exists */
315 if ((xev->xclient.data.l[1] & 1) == 0) {
316 int i, size = 0;
317 Atom atoms[3];
318 for ( i = 2; i <= 4; i++)
319 if ( xev->xclient.data.l[i] != None )
320 atoms[size++] = xev->xclient.data.l[i];
321 if ( !( CC-> external[cfTargets].data = malloc(size * sizeof(Atom))))
322 return false;
323 memcpy( CC-> external[cfTargets].data, &atoms, size * sizeof(Atom));
324 CC-> external[cfTargets].size = size * sizeof(Atom);
325 } else {
326 Atom type;
327 int rps, format;
328 unsigned long size = 0;
329 unsigned char * data = malloc(0);
330 rps = prima_read_property( guts.xdndr_source, XdndTypeList, &type, &format, &size, &data, 0);
331 if ( rps != RPS_OK ) {
332 free(data);
333 return false;
334 }
335 CC-> external[cfTargets].size = size;
336 CC-> external[cfTargets].data = data;
337 if (pguts->debug & DEBUG_CLIP) {
338 int i;
339 Atom * types = (Atom *) data;
340 _debug("dnd clipboard formats:\n");
341 for ( i = 0; i < size / sizeof(Atom); i++, types++)
342 _debug("%d:%x %s\n", i, *types, XGetAtomName(DISP, *types));
343 }
344 }
345 CC-> external[cfTargets].name = CF_TARGETS;
346 prima_clipboard_query_targets(guts. xdnd_clipboard);
347
348 return true;
349 }
350
351 static Bool
handle_xdnd_position(Handle self,XEvent * xev)352 handle_xdnd_position( Handle self, XEvent* xev)
353 {
354 Box box;
355 Bool ret = false;
356 int x, y, dx, dy, action, modmap;
357 XWindow from, to, child = None;
358 Handle h = NULL_HANDLE;
359 XEvent xr;
360
361 bzero(&xr, sizeof(xr));
362
363 /* Cdebug("xdnd:position disabled=%d\n",guts.xdnd_disabled); */
364 if (guts.xdnd_disabled)
365 goto FAIL;
366
367 if ( guts. xdndr_receiver != self ) {
368 handle_xdnd_leave(guts. xdndr_receiver);
369 goto FAIL;
370 }
371 guts.xdndr_source = xev->xclient.data.l[0];
372
373 /* find target window */
374 x = xev->xclient.data.l[2] >> 16;
375 y = xev->xclient.data.l[2] & 0xffff;
376 XTranslateCoordinates(DISP, guts.root, X(self)->client, x, y, &x, &y, &child);
377 from = to = X(self)->client;
378 while (XTranslateCoordinates(DISP, from, to, x, y, &x, &y, &child)) {
379 if (child) {
380 from = to;
381 to = child;
382 } else if ( to == from)
383 to = X(self)->client;
384
385 h = (Handle) hash_fetch( guts.windows, (void*)&to, sizeof(to));
386 if (
387 !h ||
388 h == application ||
389 !X(h)->flags.enabled
390 )
391 break;
392
393 if ( !child )
394 break;
395 }
396 if ( h == application || !X(h)->flags.enabled)
397 h = NULL_HANDLE;
398 if ( !h )
399 goto FAIL;
400
401 /* Cdebug("dnd:position old widget %08x, new %08x\n", guts.xdndr_widget, h); */
402 XCHECKPOINT;
403 modmap = query_pointer(NULL,NULL);
404 dx = x = xev->xclient.data.l[2] >> 16;
405 dy = y = xev->xclient.data.l[2] & 0xffff;
406 XTranslateCoordinates(DISP, guts.root, X(h)->client, x, y, &x, &y, &to);
407 dx -= x;
408 dy -= y;
409 y = X(h)->size.y - y - 1;
410 /* Cdebug("xdnd:final position %d %d for %08x/%08x\n",x,y,h); */
411
412 /* send enter/leave messages */
413 if ( guts. xdndr_widget != h && guts. xdndr_widget != NULL_HANDLE ) {
414 Event ev = { cmDragEnd };
415 ev.dnd.allow = 0;
416 ev.dnd.clipboard = NULL_HANDLE;
417 ev.dnd.modmap = modmap;
418 ev.dnd.where.x = x;
419 ev.dnd.where.y = y;
420 ev.dnd.action = dndNone;
421 if ( h )
422 protect_object(h);
423 guts.xdnd_disabled = true;
424 CComponent(guts.xdndr_widget)-> message(guts.xdndr_widget, &ev);
425 guts.xdndr_widget = NULL_HANDLE;
426 guts.xdnd_disabled = false;
427 if ( h ) {
428 int h_stage = PObject(h)-> stage;
429 unprotect_object(h);
430 if (h_stage == csDead) goto FAIL;
431 }
432 if ( !guts.xdnd_clipboard ) goto FAIL;
433 }
434 if ( !h ||
435 PObject(h)->stage != csNormal ||
436 !X(h)->flags.dnd_aware
437 )
438 goto FAIL;
439
440 action = dndCopy;
441 if (guts.xdndr_version > 1)
442 action = xdnd_atom_to_constant(xev->xclient.data.l[4]);
443
444 if ( guts.xdndr_widget != h ) {
445 Event ev = { cmDragBegin };
446 ev.dnd.clipboard = guts.xdnd_clipboard;
447 ev.dnd.modmap = modmap;
448 ev.dnd.where.x = x;
449 ev.dnd.where.y = y;
450 ev.dnd.action = action;
451 ev.dnd.counterpart = guts. xdnds_widget;
452 guts.xdnd_disabled = true;
453 CComponent(h)-> message(h, &ev);
454 guts.xdnd_disabled = false;
455 if (PObject(h)->stage != csNormal || !guts.xdnd_clipboard)
456 goto FAIL;
457
458 guts.xdndr_widget = h;
459 bzero(&guts. xdndr_suppress_events_within, sizeof(Box));
460 guts. xdndr_last_action = dndCopy;
461 }
462
463 if (!(
464 action == guts.xdndr_last_action &&
465 guts.xdndr_suppress_events_within.width > 0 &&
466 guts.xdndr_suppress_events_within.height > 0 &&
467 (
468 x < guts.xdndr_suppress_events_within.x ||
469 x > guts.xdndr_suppress_events_within.width + guts.xdndr_suppress_events_within.x ||
470 y < guts.xdndr_suppress_events_within.y ||
471 y > guts.xdndr_suppress_events_within.height + guts.xdndr_suppress_events_within.y
472 )
473 )) {
474 Event ev = { cmDragOver };
475
476 if ( action == dndAsk )
477 action = xdnd_read_ask_actions();
478 if ( action == dndNone )
479 action = dndCopy;
480
481 ev.dnd.clipboard = guts.xdnd_clipboard;
482 ev.dnd.where.x = x;
483 ev.dnd.where.y = y;
484 ev.dnd.action = action;
485 ev.dnd.modmap = query_pointer(NULL,NULL);
486 ev.dnd.counterpart = guts. xdnds_widget;
487 guts.xdnd_disabled = true;
488 CComponent(h)-> message(h, &ev);
489 guts.xdnd_disabled = false;
490
491 if (ev.dnd.pad.x < 0) ev.dnd.pad.x = 0;
492 if (ev.dnd.pad.y < 0) ev.dnd.pad.y = 0;
493 if (ev.dnd.pad.x + ev.dnd.pad.width > X(self)->size.x)
494 ev.dnd.pad.width = X(self)->size.x - ev.dnd.pad.x;
495 if (ev.dnd.pad.y + ev.dnd.pad.height > X(self)->size.y)
496 ev.dnd.pad.height = X(self)->size.y - ev.dnd.pad.y;
497 if (ev.dnd.pad.width < 0 || ev.dnd.pad.height < 0)
498 bzero(&ev.dnd.pad, sizeof(ev.dnd.pad));
499
500 guts.xdndr_suppress_events_within = ev.dnd.pad;
501 guts.xdndr_last_drop_response = ev.dnd.allow;
502 guts.xdndr_last_action = ev.dnd.action;
503 }
504
505 box. x = dx + guts.xdndr_suppress_events_within.x;
506 box. y = dy + X(guts.xdndr_widget)->size.y - guts.xdndr_suppress_events_within.y -
507 guts.xdndr_suppress_events_within.height - 1;
508 box. width = guts. xdndr_suppress_events_within. width;
509 box. height = guts. xdndr_suppress_events_within. height;
510 if (box.x < 0) box.x = 0;
511 if (box.x > 0xffff) box.x = 0xffff;
512 if (box.y < 0) box.y = 0;
513 if (box.y > 0xffff) box.y = 0xffff;
514 if (box.width < 0) box.width = 0;
515 if (box.width > 0xffff) box.width = 0xffff;
516 if (box.height < 0) box.height = 0;
517 if (box.height > 0xffff) box.height = 0xffff;
518
519 XCHECKPOINT;
520 xr.xclient.data.l[1] =
521 guts.xdndr_last_drop_response |
522 ((guts.xdndr_suppress_events_within.width > 0 &&
523 guts.xdndr_suppress_events_within.height > 0) ? 0 : 2),
524 xr.xclient.data.l[2] = (box.x << 16 ) | box.y;
525 xr.xclient.data.l[3] = (box.width << 16 ) | box.height;
526 xr.xclient.data.l[4] = guts.xdndr_last_drop_response ? xdnd_constant_to_atom( guts.xdndr_last_action) : None;
527 ret = true;
528
529 FAIL:
530 xr.xclient.data.l[0] = X_WINDOW;
531 xr.xclient.message_type = XdndStatus;
532 if (guts.xdndr_source == guts.xdnds_sender)
533 handle_xdnd_status(guts.xdndr_receiver, &xr);
534 else
535 xdnd_send_message_ev(guts.xdndr_source, &xr);
536
537 return ret;
538 }
539
540 static Bool
handle_xdnd_drop(Handle self,XEvent * xev)541 handle_xdnd_drop( Handle self, XEvent* xev)
542 {
543 Event ev;
544 Atom action = None;
545 XEvent xr;
546 XWindow last_source;
547
548 if (!guts.xdnd_clipboard || guts.xdnd_disabled) return false;
549
550 if (
551 guts.xdndr_receiver != self ||
552 guts.xdndr_widget == NULL_HANDLE
553 ) {
554 handle_xdnd_leave(guts. xdndr_receiver);
555 return false;
556 }
557
558 Cdebug("dnd:drop from %08x\n", guts.xdndr_source);
559 if ( X(guts.xdndr_widget)->flags.dnd_aware) {
560 XWindow dummy;
561 ev.cmd = cmDragEnd;
562 ev.dnd.modmap = query_pointer(NULL,&ev.dnd.where);
563 XTranslateCoordinates(DISP, guts.root, X(guts.xdndr_widget)->client,
564 ev.dnd.where.x, ev.dnd.where.y,
565 &ev.dnd.where.x, &ev.dnd.where.y,
566 &dummy);
567 guts.xdndr_source = xev->xclient.data.l[0];
568 guts.xdndr_timestamp = (guts.xdndr_version >= 1) ? xev-> xclient.data.l[2] : CurrentTime;
569 ev.dnd.action = guts.xdndr_last_action;
570 ev.dnd.allow = guts.xdndr_last_action != dndNone;
571 ev.dnd.clipboard = ev.dnd.allow ? guts.xdnd_clipboard : NULL_HANDLE;
572 ev.dnd.counterpart = guts.xdnds_widget;
573 guts.xdnd_disabled = true;
574 CComponent(guts.xdndr_widget)-> message(guts.xdndr_widget, &ev);
575 guts.xdnd_disabled = false;
576 guts.xdndr_last_target = guts.xdndr_widget;
577 } else {
578 ev.dnd.allow = 0;
579 ev.dnd.action = dndCopy;
580 }
581
582 /* cleanup */
583 guts.xdndr_widget = NULL_HANDLE;
584 last_source = guts.xdndr_source;
585 guts.xdndr_source = None;
586 guts.xdndr_receiver = NULL_HANDLE;
587 if ( guts. xdnd_clipboard )
588 C(guts.xdnd_clipboard)->xdnd_receiving = false;
589
590 /* respond */
591 if (ev.dnd.allow) {
592 if (( ev.dnd.action & dndMask ) == 0) ev.dnd.action = None;
593 /* never return a combination, if DragEnd never bothered to ask */
594 if ( ev.dnd.action & dndCopy ) ev.dnd.action = dndCopy;
595 else if ( ev.dnd.action & dndMove ) ev.dnd.action = dndMove;
596 else if ( ev.dnd.action & dndLink ) ev.dnd.action = dndLink;
597 else ev.dnd.action = dndNone;
598 if ( ev.dnd.action == dndNone ) ev.dnd.allow = false;
599 action = xdnd_constant_to_atom( ev.dnd.action );
600 }
601 XCHECKPOINT;
602
603 bzero(&xr, sizeof(xr));
604 xr.xclient.message_type = XdndFinished;
605 xr.xclient.data.l[0] = X_WINDOW;
606 xr.xclient.data.l[1] = ev.dnd.allow;
607 xr.xclient.data.l[2] = ev.dnd.allow ? action : None;
608 if (last_source == guts.xdnds_sender)
609 handle_xdnd_finished(guts.xdndr_receiver, &xr);
610 else
611 xdnd_send_message_ev(last_source, &xr);
612
613 return true;
614 }
615
616 Bool
prima_handle_dnd_event(Handle self,XEvent * xev)617 prima_handle_dnd_event( Handle self, XEvent *xev)
618 {
619 if ( xev-> xclient. message_type == XdndEnter)
620 return handle_xdnd_enter( self, xev);
621 else if ( xev-> xclient. message_type == XdndPosition)
622 return handle_xdnd_position( self, xev);
623 else if ( xev-> xclient. message_type == XdndLeave) {
624 Cdebug("dnd:leave %08x\n",guts.xdndr_widget);
625 return handle_xdnd_leave( self);
626 }
627 else if ( xev-> xclient. message_type == XdndDrop)
628 return handle_xdnd_drop( self, xev);
629 else if ( xev-> xclient. message_type == XdndStatus)
630 return handle_xdnd_status( self, xev);
631 else if ( xev-> xclient. message_type == XdndFinished)
632 return handle_xdnd_finished( self, xev);
633 return false;
634 }
635
636 /* make sure it's synchronous when talking local */
637 static void
xdnd_send_leave_message(void)638 xdnd_send_leave_message(void)
639 {
640 if (guts.xdndr_source == guts.xdnds_sender)
641 handle_xdnd_leave( guts.xdndr_receiver );
642 else
643 xdnd_send_message( guts.xdnds_target, XdndLeave, 0, 0, 0, 0, None);
644 }
645
646 static void
xdnd_send_drop_message(void)647 xdnd_send_drop_message(void)
648 {
649 XEvent ev;
650 bzero(&ev, sizeof(ev));
651 ev.xclient.message_type = XdndDrop;
652 ev.xclient.data.l[0] = guts.xdnds_sender;
653 ev.xclient.data.l[2] = CurrentTime;
654 if (guts.xdndr_source == guts.xdnds_sender)
655 handle_xdnd_drop( guts.xdndr_receiver, &ev );
656 else
657 xdnd_send_message_ev(guts.xdnds_target, &ev);
658 }
659
660 static void
xdnds_send_position_message(Point ptr,int action)661 xdnds_send_position_message(Point ptr, int action)
662 {
663 XEvent ev;
664 bzero(&ev, sizeof(ev));
665 ev.xclient.message_type = XdndPosition;
666 ev.xclient.data.l[0] = guts.xdnds_sender;
667 ev.xclient.data.l[2] = (ptr.x << 16)|ptr.y;
668 ev.xclient.data.l[3] = CurrentTime;
669 ev.xclient.data.l[4] = action;
670 if (guts.xdndr_source == guts.xdnds_sender)
671 handle_xdnd_position( guts.xdndr_receiver, &ev );
672 else
673 xdnd_send_message_ev(guts.xdnds_target, &ev);
674 }
675
676 static void
xdnds_send_enter_message(int rps,Atom * targets)677 xdnds_send_enter_message(int rps, Atom * targets)
678 {
679 Handle local;
680 XEvent ev;
681 bzero(&ev, sizeof(ev));
682 ev.xclient.message_type = XdndEnter;
683 ev.xclient.data.l[0] = guts.xdnds_sender;
684 ev.xclient.data.l[1] = (((guts.xdnds_version < 5) ? guts.xdnds_version : 5) << 24) | (rps > 3);
685 ev.xclient.data.l[2] = (rps > 0) ? targets[0] : None;
686 ev.xclient.data.l[3] = (rps > 1) ? targets[1] : None;
687 ev.xclient.data.l[4] = (rps > 2) ? targets[2] : None;
688 local = ( Handle) hash_fetch( guts. windows, (void*)&(guts.xdnds_target), sizeof(XWindow));
689 if (local)
690 handle_xdnd_enter( local, &ev );
691 else
692 xdnd_send_message_ev(guts.xdnds_target, &ev);
693 }
694
695 Bool
apc_dnd_get_aware(Handle self)696 apc_dnd_get_aware( Handle self )
697 {
698 return X(self)->flags. dnd_aware;
699 }
700
701 static Bool
find_dnd_aware(Handle self,Handle widget,void * cmd)702 find_dnd_aware( Handle self, Handle widget, void * cmd)
703 {
704 if ( X(widget)->flags. dnd_aware) return true;
705 return CWidget(widget)->first_that(widget, find_dnd_aware, NULL) != NULL_HANDLE;
706 }
707
708 void
prima_update_dnd_aware(Handle self)709 prima_update_dnd_aware( Handle self )
710 {
711 DEFXX;
712 Bool has_property = XX->flags. drop_target;
713 Bool has_drop_target = find_dnd_aware(self, self, NULL);
714
715 if ( has_drop_target == has_property ) return;
716 XX->flags. drop_target = has_drop_target;
717
718 if ( has_drop_target ) {
719 Atom version = 5;
720 XChangeProperty(DISP, X_WINDOW, XdndAware, XA_ATOM, 32,
721 PropModeReplace, (unsigned char*)&version, 1);
722 } else
723 XDeleteProperty( DISP, X_WINDOW, XdndAware);
724 }
725
726 static Handle
get_top_window(Handle from)727 get_top_window(Handle from)
728 {
729 while ( from) {
730 if (
731 kind_of( from, CWindow) ||
732 ( PWidget( from)-> owner == application) ||
733 !CWidget( from)-> get_clipOwner(from)
734 )
735 return from;
736 from = PWidget( from)-> owner;
737 }
738 return application;
739 }
740
741 Bool
apc_dnd_set_aware(Handle self,Bool is_target)742 apc_dnd_set_aware( Handle self, Bool is_target )
743 {
744 DEFXX;
745 Bool src = XX->flags. dnd_aware ? 1 : 0;
746 Bool dst = is_target ? 1 : 0;
747 Handle top_level;
748
749 if ( src == dst ) return true;
750 top_level = get_top_window(self);
751 if ( top_level == application ) return false;
752
753 XX->flags. dnd_aware = dst;
754 prima_update_dnd_aware( top_level );
755 return true;
756 }
757
758 Handle
apc_dnd_get_clipboard(Handle self)759 apc_dnd_get_clipboard( Handle self )
760 {
761 return guts. xdnd_clipboard;
762 }
763
764 static Bool
send_drag_response(Handle self,Bool allow,int action)765 send_drag_response(Handle self, Bool allow, int action)
766 {
767 Event ev = { cmDragResponse };
768 ev.dnd.allow = allow;
769 ev.dnd.action = action;
770 ev.dnd.counterpart = guts.xdndr_widget;
771 guts.xdnd_disabled = true;
772 CComponent(self)-> message(self, &ev);
773 guts.xdnd_disabled = false;
774 return PObject(self)->stage == csNormal;
775 }
776
777 int
apc_dnd_start(Handle self,int actions,Bool default_pointers,Handle * counterpart)778 apc_dnd_start( Handle self, int actions, Bool default_pointers, Handle * counterpart)
779 {
780 Bool got_session = false;
781 Point ptr, last_ptr = { -1, -1 };
782 int ret = dndNone, i, modmap, first_modmap, n_actions = 0;
783 Atom actions_list[3], curr_action, last_action = -1;
784 char *ac_ptr, actions_descriptions[16] = ""; /* Copy Move Link */
785 PClipboardSysData CC;
786 Handle top_level, banned_receiver = None;
787 XWindow last_xdndr_source = None;
788 int old_pointer;
789
790 if ( guts.xdnd_disabled || guts.xdnds_widget || !guts.xdnd_clipboard ) {
791 Cdebug("dnd:already is action\n");
792 return -1;
793 }
794 if ((actions & dndMask) == 0) {
795 Cdebug("dnd:bad actions\n");
796 return -1;
797 }
798 top_level = get_top_window(self);
799 if ( top_level == application ) {
800 Cdebug("dnd:no toplevel window\n");
801 return -1;
802 }
803
804 if ( counterpart ) *counterpart = NULL_HANDLE;
805 guts.xdndr_last_target = NULL_HANDLE;
806
807 ac_ptr = actions_descriptions;
808 for ( i = 0x1; i <= dndLink; i <<= 1) {
809 int action = actions & i;
810 if ( actions == 0 ) continue;
811
812 actions_list[n_actions++] = xdnd_constant_to_atom(action);
813 switch ( action ) {
814 case dndCopy: strncpy(ac_ptr, "Copy", 5); break;
815 case dndMove: strncpy(ac_ptr, "Move", 5); break;
816 case dndLink: strncpy(ac_ptr, "Link", 5); break;
817 default : continue;
818 }
819 ac_ptr += 5;
820 }
821 if ( n_actions == 0) {
822 Cdebug("dnd:no actions\n");
823 return -1;
824 }
825
826 if ( prima_clipboard_fill_targets(guts.xdnd_clipboard) == 0) {
827 Cdebug("dnd:no clipboard data\n");
828 return -1; /* nothing to drag */
829 }
830
831 guts. xdnds_default_pointers = default_pointers;
832 guts. xdnds_sender = PWidget(top_level)->handle;
833 guts. xdnds_widget = self;
834 guts. xdnds_finished = false;
835 modmap = query_pointer(NULL,NULL);
836 first_modmap = modmap & 0xffff;
837 guts.xdnds_escape_key = false;
838 guts.xdnds_last_drop_response = false;
839 guts.xdnds_target = None;
840 guts.xdnds_version = 0;
841 guts.xdnds_last_action = guts.xdnds_last_action_response = dndNone;
842 bzero( &guts.xdnds_suppress_events_within, sizeof(Box));
843 protect_object(self);
844 Cdebug("dnd:begin\n");
845
846 CC = C(guts.xdnd_clipboard);
847 prima_detach_xfers( CC, cfTargets, true);
848 prima_clipboard_kill_item( CC-> internal, cfTargets);
849 CC->internal[cfTargets].name = XdndTypeList;
850
851 old_pointer = apc_pointer_get_shape(self);
852 apc_pointer_set_shape(self, crDragNone);
853 apc_widget_set_capture(self, true, NULL_HANDLE);
854
855 if ( !default_pointers && !X(self)->flags. dnd_aware) {
856 if ( !send_drag_response(self, false, dndNone)) goto EXIT;
857 }
858
859 XChangeProperty(DISP, guts. xdnds_sender, XdndActionList, XA_ATOM, 32,
860 PropModeReplace, (unsigned char*)&actions_list, n_actions);
861 XChangeProperty(DISP, guts. xdnds_sender, XdndActionDescription, XA_STRING, 8,
862 PropModeReplace, (unsigned char*)&actions_descriptions, ac_ptr - actions_descriptions);
863 XCHECKPOINT;
864
865 guts.xdnds_last_action = actions;
866 curr_action = (n_actions > 1) ? XdndActionAsk : actions_list[0];
867 while ( prima_one_loop_round( WAIT_ALWAYS, true)) {
868 int new_modmap;
869 XWindow new_receiver;
870
871 if ( !guts.xdnds_widget || !guts.xdnd_clipboard ) {
872 Cdebug("dnd:objects killed\n");
873 ret = dndNone;
874 goto EXIT;
875 }
876
877 new_modmap = query_pointer(&new_receiver, &ptr);
878 if ( new_receiver == banned_receiver && banned_receiver != None )
879 new_receiver = guts.xdnds_target;
880
881 if ( guts.xdnds_escape_key )
882 new_modmap |= kmEscape;
883 if ( new_modmap == modmap && new_receiver == guts.xdnds_target && last_ptr.x == ptr.x && last_ptr.y == ptr.y)
884 continue;
885 last_ptr = ptr;
886
887 if ( new_receiver != guts.xdnds_target ) {
888 banned_receiver = None;
889 Cdebug("dnd:target %08x => %08x\n", guts.xdnds_target, new_receiver);
890 if ( got_session && guts.xdnds_target ) {
891 xdnd_send_leave_message();
892 guts. xdnds_target = None;
893 got_session = false;
894 }
895
896 if ( new_receiver ) {
897 Atom type, *targets;
898 int rps, format;
899 unsigned long size = 0;
900 unsigned char * data = malloc(0);
901
902 rps = prima_read_property( new_receiver, XdndAware, &type, &format,
903 &size, &data, 0);
904 if ( rps != RPS_OK || type != XA_ATOM || format != CF_32 || size != sizeof(Atom)) {
905 free(data);
906 Cdebug("dnd:bad XdndAware\n");
907 banned_receiver = new_receiver;
908 continue;
909 }
910 guts.xdnds_version = *((Atom*)data);
911 free(data);
912 rps = prima_clipboard_fill_targets(guts.xdnd_clipboard);
913 if ( rps == 0 ) {
914 Cdebug("dnd:failed to query clipboard targets\n");
915 ret = -1;
916 goto EXIT;
917 }
918
919 got_session = true;
920 guts.xdnds_target = new_receiver;
921 targets = (Atom*) CC->internal[cfTargets].data;
922 Cdebug("dnd:send enter to %08x\n",guts.xdnds_target);
923 xdnds_send_enter_message(rps, targets);
924 }
925 }
926
927 if (got_session) {
928 Box b = guts.xdnds_suppress_events_within;
929 XCHECKPOINT;
930 if (!(
931 b.width > 0 && b.height > 0 &&
932 ptr.x >= b.x && ptr.y >= b.y &&
933 ptr.x <= b.x + b.width && ptr.y <= b.y + b.height &&
934 curr_action == last_action
935 )) {
936 xdnds_send_position_message(ptr, curr_action);
937 last_action = curr_action;
938 }
939 } else {
940 if ( default_pointers)
941 apc_pointer_set_shape(self, crDragNone);
942 guts.xdnds_last_drop_response = false;
943 guts.xdnds_last_action_response = dndNone;
944 if ( !send_drag_response(self, false, dndNone)) goto EXIT;
945 }
946
947 if ( new_modmap != modmap) {
948 Event ev = { cmDragQuery };
949 Cdebug("dnd:modmap %08x => %08x\n", modmap, new_modmap);
950
951 /* suggest allow action */
952 ev.dnd.modmap = modmap = new_modmap;
953 if ( modmap & kmEscape )
954 ev.dnd.allow = -1;
955 else if (first_modmap != (modmap & first_modmap))
956 ev.dnd.allow = got_session ? 1 : -1;
957 else
958 ev.dnd.allow = 0;
959 ev.dnd.counterpart = guts.xdndr_widget;
960
961 guts.xdnd_disabled = true;
962 CComponent(guts.xdnds_widget)-> message(guts.xdnds_widget, &ev);
963 guts.xdnd_disabled = false;
964 guts.xdnds_escape_key = false;
965 ev.dnd.action &= dndMask;
966
967 if ( ev.dnd.action != 0 ) {
968 last_action = curr_action;
969 curr_action = xdnd_constant_to_atom(ev.dnd.action);
970 if ( curr_action == None )
971 curr_action = XdndActionAsk;
972 }
973 if ( ev.dnd.allow != 0 ) {
974 XCHECKPOINT;
975 if (
976 got_session &&
977 ev.dnd.allow > 0 &&
978 guts. xdnds_last_drop_response &&
979 guts. xdnds_last_action_response != None
980 ) {
981 last_xdndr_source = guts.xdndr_source;
982 xdnd_send_drop_message();
983 guts.xdnds_widget = NULL_HANDLE;
984 ret = guts.xdnds_last_action_response;
985 Cdebug("dnd:drop\n");
986 } else {
987 xdnd_send_leave_message();
988 guts.xdnds_widget = NULL_HANDLE;
989 got_session = false;
990 Cdebug("dnd:leave\n");
991 }
992 break;
993 } else {
994 xdnds_send_position_message(ptr, curr_action);
995 }
996 }
997
998 XSync( DISP, false);
999 }
1000
1001 if (ret == dndNone) goto EXIT;
1002
1003 if (guts. xdnds_version < 2 ) {
1004 if (ret == dndAsk) ret = dndCopy; /* shouldn't happen */
1005 goto EXIT;
1006 }
1007
1008 /* wait for XdndFinished */
1009 if (last_xdndr_source != guts.xdnds_sender) {
1010 XCHECKPOINT;
1011 guts.xdnds_finished = false;
1012 guts.xdnds_widget = self;
1013 guts.xdnds_escape_key = false;
1014 Cdebug("dnd:wait for finalization\n");
1015 while (
1016 prima_one_loop_round( WAIT_ALWAYS, true) &&
1017 !guts.xdnds_finished &&
1018 !guts.xdnds_escape_key
1019 ) {
1020 XSync( DISP, false);
1021 }
1022 Cdebug("dnd:finalize by %s\n", guts.xdnds_escape_key ? "escape" : "event");
1023 guts.xdnds_widget = NULL_HANDLE;
1024 guts.xdnds_escape_key = false;
1025 }
1026
1027 if (guts.xdnds_last_drop_response) {
1028 ret = guts. xdnds_last_action_response;
1029 if (ret == dndAsk) ret = dndCopy; /* shouldn't happen */
1030 } else
1031 ret = dndNone;
1032
1033 EXIT:
1034 if ( PObject(self)->stage == csNormal ) {
1035 apc_widget_set_capture(self, 0, NULL_HANDLE);
1036 apc_pointer_set_shape(self, old_pointer);
1037 }
1038 unprotect_object(self);
1039 guts.xdnds_widget = NULL_HANDLE;
1040 guts.xdnds_target = None;
1041 if ( counterpart ) *counterpart = guts.xdndr_last_target;
1042 Cdebug("dnd:stop\n");
1043 return ret;
1044 }
1045