1 /* dnd.c - drag and drop support
2    Copyright (C) 1996-2017 Paul Sheer
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307, USA.
18  */
19 
20 /*
21    The cursor bitmaps and the cursor initialisation routines are from
22    the DND package which can be found with the OffiX package at
23    sunsite.unc.edu. These are Copyright (C) 1996 C�sar Crusius.
24    I also took a look at his code to get a better idea how get my Dnd
25    working the same, and small parts of the code are modifications
26    from there.
27  */
28 
29 
30 #include <config.h>
31 
32 #ifdef HAVE_DND
33 
34 #include <my_string.h>
35 #include <stdlib.h>
36 #include <stdio.h>
37 #include "pool.h"
38 
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 
42 #ifdef HAVE_UNISTD_H
43 #include <unistd.h>
44 #endif
45 
46 
47 #include <X11/Xlib.h>
48 #include <X11/Xutil.h>
49 #include <X11/Xresource.h>
50 #include <X11/Xatom.h>
51 #include <X11/cursorfont.h>
52 
53 #include "cursor/file.xbm"
54 #include "cursor/file_mask.xbm"
55 #include "cursor/files.xbm"
56 #include "cursor/files_mask.xbm"
57 #include "cursor/dir.xbm"
58 #include "cursor/dir_mask.xbm"
59 #include "cursor/text.xbm"
60 #include "cursor/text_mask.xbm"
61 #include "cursor/grey.xbm"
62 #include "cursor/grey_mask.xbm"
63 #include "cursor/link.xbm"
64 #include "cursor/link_mask.xbm"
65 #include "cursor/app.xbm"
66 #include "cursor/app_mask.xbm"
67 #include "cursor/url.xbm"
68 #include "cursor/url_mask.xbm"
69 #include "cursor/mime.xbm"
70 #include "cursor/mime_mask.xbm"
71 
72 #include "app_glob.c"
73 #include "coolwidget.h"
74 #include "mad.h"
75 
76 /* #define DND_DEBUG */
77 
78 /* what dnd version for sending drops */
79 /* (recieving drops we support both versions simultaneously) */
80 int option_dnd_version = 1;
81 
82 Atom DndProtocol, DndSelection, DndAcknowledge;
83 Atom OldDndProtocol, OldDndSelection;
84 
85 typedef struct {
86     int Width, Height;
87     unsigned char *ImageData, *MaskData;
88     int HotSpotX, HotSpotY;
89     Pixmap ImagePixmap, MaskPixmap;
90     Cursor CursorID;
91 } CursorData;
92 
93 static CursorData DndCursor[] =
94 {
95     {0, 0, NULL, NULL, 0, 0, 0},
96     {grey_width, grey_height, grey_bits, grey_mask_bits,
97      grey_x_hot, grey_y_hot},
98     {file_width, file_height, file_bits, file_mask_bits,
99      file_x_hot, file_y_hot},
100     {files_width, files_height, files_bits, files_mask_bits,
101      files_x_hot, files_y_hot},
102     {text_width, text_height, text_bits, text_mask_bits,
103      text_x_hot, text_y_hot},
104     {dir_width, dir_height, dir_bits, dir_mask_bits,
105      dir_x_hot, dir_y_hot},
106     {link_width, link_height, link_bits, link_mask_bits,
107      link_x_hot, link_y_hot},
108     {app_width, app_height, app_bits, app_mask_bits,
109      app_x_hot, app_y_hot},
110     {url_width, url_height, url_bits, url_mask_bits,
111      url_x_hot, url_y_hot},
112     {mime_width, mime_height, mime_bits, mime_mask_bits,
113      mime_x_hot, mime_y_hot}
114 };
115 
116 static int num_cursors = sizeof (DndCursor) / sizeof (CursorData);
117 
118 static char dnd_directory[MAX_PATH_LEN] = "";
119 
120 /* return the prepending directory (see CDndFileList() below) */
CDndDirectory(void)121 char *CDndDirectory (void)
122 {
123     return dnd_directory;
124 }
125 
striptrailing(char * s,int c)126 char *striptrailing (char *s, int c)
127 {
128     int i;
129     i = strlen (s) - 1;
130 
131     while (i >= 0) {
132 	if (s[i] == c) {
133 	    s[i--] = 0;
134 	    continue;
135 	}
136 	break;
137     }
138     return s;
139 }
140 
141 /*
142    Sets the directory, must be a null terminated complete path.
143    Strips trailing slashes.
144  */
CSetDndDirectory(char * d)145 void CSetDndDirectory (char *d)
146 {
147     if (!d)
148 	return;
149     strcpy (dnd_directory, d);
150     striptrailing (dnd_directory, '/');
151     if (*dnd_directory)
152 	return;
153     *dnd_directory = '/';
154 }
155 
156 /*
157    Takes a newline separate list of files,
158    returns a null separate list of complete path names
159    by prepending dnd_directory to each file name.
160    returns 0 if no files in list.
161    result must always be free'd.
162    returns l as the total length of data.
163    returns num_files as the number of files in the list.
164    Alters t
165  */
CDndFileList(char * t,int * l,int * num_files)166 char *CDndFileList (char *t, int *l, int *num_files)
167 {
168     char *p, *q, *r, *result;
169     int i;
170     int path_len, len;
171 
172 /* strip leading newlines */
173     while (*t == '\n')
174 	t++;
175 
176 /* strip trailing newlines */
177     striptrailing (t, '\n');
178 
179     if (!*t)
180 	return 0;
181 
182 /* count files */
183     for (i = 1, p = t; *p; p++)
184 	if (*p == '\n')
185 	    i++;
186 
187     *num_files = i;
188 
189     len = (unsigned long) p - (unsigned long) t;
190     path_len = strlen (dnd_directory);
191     result = CMalloc ((path_len + 2) * i + len + 2);
192 
193     r = result;
194     p = t;
195     for (;;) {
196 	q = strchr (p, '\n');
197 	if (!q)
198 	    q = t + len;
199 	*q = 0;
200 	strcpy (r, dnd_directory);
201 	r += path_len;
202 	*r++ = '/';
203 	strcpy (r, p);
204 	r += (unsigned long) q - (unsigned long) p;
205 	*r++ = 0;
206 	if ((unsigned long) q == (unsigned long) t + len)
207 	    break;
208 	p = ++q;
209     }
210     *r = 0;
211     *l = (unsigned long) r - (unsigned long) result;
212     return result;
213 }
214 
215 
216 /* Dnd initialisation */
initialise_drag_n_drop(void)217 void initialise_drag_n_drop (void)
218 {
219     int screen, i;
220     Colormap colormap;
221     Window root;
222     XColor Black, White;
223 
224     DndAcknowledge = XInternAtom (CDisplay, "_DND_ACKNOWLEDGE", False);
225     DndProtocol = XInternAtom (CDisplay, "_DND_PROTOCOL", False);
226     DndSelection = XInternAtom (CDisplay, "_DND_SELECTION", False);
227     OldDndProtocol = XInternAtom (CDisplay, "DndProtocol", False);
228     OldDndSelection = XInternAtom (CDisplay, "DndSelection", False);
229 
230     screen = DefaultScreen (CDisplay);
231     colormap = DefaultColormap (CDisplay, screen);
232     root = CRoot;
233 
234     Black.pixel = BlackPixel (CDisplay, screen);
235     White.pixel = WhitePixel (CDisplay, screen);
236     XQueryColor (CDisplay, colormap, &Black);
237     XQueryColor (CDisplay, colormap, &White);
238 
239     for (i = 1; i < num_cursors; i++) {
240 	DndCursor[i].ImagePixmap =
241 	    XCreateBitmapFromData (CDisplay, root,
242 				   (char *) DndCursor[i].ImageData,
243 				   DndCursor[i].Width,
244 				   DndCursor[i].Height);
245 	DndCursor[i].MaskPixmap =
246 	    XCreateBitmapFromData (CDisplay, root,
247 				   (char *) DndCursor[i].MaskData,
248 				   DndCursor[i].Width,
249 				   DndCursor[i].Height);
250 	DndCursor[i].CursorID =
251 	    XCreatePixmapCursor (CDisplay, DndCursor[i].ImagePixmap,
252 				 DndCursor[i].MaskPixmap,
253 				 &Black, &White,
254 				 DndCursor[i].HotSpotX,
255 				 DndCursor[i].HotSpotY);
256     }
257     DndCursor[0].CursorID = XCreateFontCursor (CDisplay, XC_question_arrow);
258 }
259 
change_file_to_directory(int * data_type,char * data)260 static void change_file_to_directory (int *data_type, char *data)
261 {
262     struct stat stats;
263     if (*data_type != DndFile)
264 	return;
265     if (lstat (data, &stats))
266 	return;
267     if (S_ISDIR (stats.st_mode))
268 	*data_type = DndDir;
269 }
270 
271 /*
272    This does a drag.
273    This is copied somewhat from the dnd Xt code by C�sar Crusius
274    so it should behave almost exactly the way his does.
275    From is the sending window. data_type is a dnd type in coolwidget.h,
276    data is the actual data, and length is its length.
277    pointer state is the state of the pointer.
278    I set the pointer state to that *during* the drag, not before.
279    See eh_editor in editwidget.c for details.
280    The routines doesn't do anything until the mouse has moved more than
281    five pixels. If the user releases the button before dragging,
282    the button release event is sent back to the calling window.
283  */
CDrag(Window from,int data_type,unsigned char * data,int length,unsigned long pointer_state)284 void CDrag (Window from, int data_type, unsigned char *data, int length, unsigned long pointer_state)
285 {
286     XEvent e;
287     long x, y;
288     float x_mouse, y_mouse;
289     Window root;
290     Window target, dispatch;
291 
292     change_file_to_directory (&data_type, (char *) data);
293 
294 #ifdef DND_DEBUG
295 /* NLS ? */
296     printf ("Drag start\n");
297 #endif
298 
299 /* first wait until the mouse moves more than ten pixels */
300     do {
301 	XNextEvent (CDisplay, &e);
302 	if (e.type == ButtonRelease) {
303 	    XSendEvent (CDisplay, e.xany.window, 0, ButtonReleaseMask, &e);
304 	    return;
305 	}
306     } while (e.type != MotionNotify);
307 
308     x_mouse = (float) e.xmotion.x_root;
309     y_mouse = (float) e.xmotion.y_root;
310 
311     for (;;) {
312 	XNextEvent (CDisplay, &e);
313 	if (e.type == MotionNotify)
314 	    if (my_sqrt ((x_mouse - e.xmotion.x_root) * (x_mouse - e.xmotion.x_root) +
315 			 (y_mouse - e.xmotion.y_root) * (y_mouse - e.xmotion.y_root)) > 5.0)
316 		break;
317 	if (e.type == ButtonRelease) {
318 	    XSendEvent (CDisplay, e.xany.window, 0, ButtonReleaseMask, &e);
319 	    return;
320 	}
321     }
322 
323 /* the mouse has been dragged a little, so this is a drag proper */
324     root = CRoot;
325 
326     XChangeProperty (CDisplay, root, option_dnd_version ? DndSelection : OldDndSelection, XA_STRING, 8,
327 		     PropModeReplace, data, length);
328 
329     XGrabPointer (CDisplay, root, False,
330 		  ButtonMotionMask | ButtonPressMask | ButtonReleaseMask,
331 		  GrabModeSync, GrabModeAsync, root,
332 		  DndCursor[data_type].CursorID, CurrentTime);
333 
334     do {
335 	XAllowEvents (CDisplay, SyncPointer, CurrentTime);
336 	XNextEvent (CDisplay, &e);
337     } while (e.type != ButtonRelease);
338 
339     XUngrabPointer (CDisplay, CurrentTime);
340 
341     if (!e.xbutton.subwindow) {
342 #ifdef DND_DEBUG
343 /* NLS ? */
344 	printf ("Pointer on root window, Drag end\n");
345 #endif
346 	return;
347     }
348     target = my_XmuClientWindow (CDisplay, e.xbutton.subwindow);
349     if (target == e.xbutton.subwindow)
350 	dispatch = target;
351     else
352 	dispatch = PointerWindow;
353 
354     x = e.xbutton.x_root;
355     y = e.xbutton.y_root;
356 
357     e.xclient.type = ClientMessage;
358     e.xclient.display = CDisplay;
359     e.xclient.message_type = option_dnd_version ? DndProtocol : OldDndProtocol;
360     e.xclient.format = 32;
361     e.xclient.window = target;
362     e.xclient.data.l[0] = data_type;
363     e.xclient.data.l[1] = pointer_state;
364     e.xclient.data.l[2] = from;
365     e.xclient.data.l[3] = x + y * 65536L;
366     e.xclient.data.l[4] = option_dnd_version;
367 
368     /* Send the drop message */
369     XSendEvent (CDisplay, dispatch, True, NoEventMask, &e);
370 
371 #ifdef DND_DEBUG
372 /* NLS ? */
373     printf ("Drop send to window %ld from %ld, Drag end\n", target, from);
374 #endif
375 }
376 
this_pool_insert(void * p,int c)377 void this_pool_insert (void *p, int c)
378 {
379     unsigned char ch;
380     ch = c;
381     pool_write ((POOL *) p, &ch, 1);
382 }
383 
384 void paste_prop (void *data, void (*insert) (void *, int), Window win, unsigned prop, int delete);
385 
386 /*
387    Returns data_type on success, DndNotDnd if not a Dnd drop.
388    Unneeded values can be passed as null.
389  */
CGetDrop(XEvent * xe,unsigned char ** data,unsigned long * size,int * x,int * y)390 int CGetDrop (XEvent * xe, unsigned char **data, unsigned long *size, int *x, int *y)
391 {
392     POOL *p;
393 
394     if (xe->type != ClientMessage) {
395 #ifdef DND_DEBUG
396 /* NLS ? */
397 	printf ("None ClientMessage event to window %ld\n", xe->xany.window);
398 #endif
399 	return DndNotDnd;
400     }
401 #ifdef DND_DEBUG
402 /* NLS ? */
403     printf ("ClientMessage event to window %ld\n", xe->xany.window);
404 #endif
405     if ((xe->xclient.message_type == DndProtocol && xe->xclient.data.l[4] == 1)
406 	||
407 	(xe->xclient.message_type == OldDndProtocol && xe->xclient.data.l[4] == 0)) {
408 	/* continue */
409     } else {
410 	return DndNotDnd;
411     }
412 
413 #ifdef DND_DEBUG
414 /* NLS ? */
415     printf ("Drop recieved at window %ld from window %ld\n", xe->xclient.window, xe->xclient.data.l[2]);
416 #endif
417 
418     p = pool_init ();
419     paste_prop (p, this_pool_insert, CRoot, xe->xclient.data.l[4] ? DndSelection : OldDndSelection, False);
420     if (size)
421 	*size = pool_length (p);
422     *data = pool_break (p);
423 
424     if (x)
425 	*x = xe->xclient.data.l[3] & 0xFFFF;
426     if (y)
427 	*y = xe->xclient.data.l[3] >> 16;
428 
429     return xe->xclient.data.l[0];
430 }
431 
432 /*
433    Sends an acknowledge event that the drop was recieved. This is my
434    extension to the protocol, but cooledit doesn't depend on
435    an acknowledge event being recieved, so everything will
436    work as usual when communicating with apps that don't
437    support this. xe is the drop event that you recived and
438    is not be modified.
439  */
CDropAcknowledge(XEvent * xe)440 void CDropAcknowledge (XEvent * xe)
441 {
442     XEvent e;
443     e.xclient.type = ClientMessage;
444     e.xclient.display = CDisplay;
445     e.xclient.message_type = DndAcknowledge;
446     e.xclient.format = 32;
447     e.xclient.window = xe->xclient.data.l[2];	/* to window that send the drop */
448     e.xclient.data.l[0] = xe->xclient.data.l[0];	/* data type */
449     e.xclient.data.l[1] = xe->xclient.data.l[1];	/* pointer state of the pointer when the drop was send */
450     e.xclient.data.l[2] = xe->xclient.window;	/* from this window */
451     e.xclient.data.l[3] = 0;	/* not used */
452     e.xclient.data.l[4] = 1;	/* same dnd version, since this extension can be
453 				   ignored, there is no need to have a new version */
454 
455     XSendEvent (CDisplay, e.xclient.window, True, NoEventMask, &e);
456 
457 #ifdef DND_DEBUG
458 /* NLS ? */
459     printf ("acknowledge send to window %ld from %ld, return\n", xe->xclient.data.l[2], xe->xclient.window);
460 #endif
461 }
462 
463 /*
464    Checks if xe is an acknowledge event.
465    Returns data_type if so and the state that the pointer was in
466    when the drag was initiated.
467    Returns DndNotDnd if not an acknowledge event.
468  */
CIsDropAcknowledge(XEvent * xe,unsigned int * state)469 int CIsDropAcknowledge (XEvent * xe, unsigned int *state)
470 {
471     if (xe->type != ClientMessage)
472 	return DndNotDnd;
473     if (xe->xclient.message_type != DndAcknowledge || xe->xclient.data.l[4] != 1)
474 	return DndNotDnd;
475     if (state)
476 	*state = xe->xclient.data.l[1];
477     return xe->xclient.data.l[0];
478 }
479 
480 #endif				/* HAVE_DND */
481 
482