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