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