1 /*
2  * ROX-Filer, filer for the ROX desktop project
3  * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
4  *
5  * This program is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13  * more details.
14  *
15  * You should have received a copy of the GNU General Public License along with
16  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17  * Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19 
20 /* dnd.c - code for handling drag and drop */
21 
22 #include "config.h"
23 
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <sys/param.h>
30 
31 #include <X11/Xlib.h>
32 #include <X11/Xatom.h>
33 #include <gtk/gtk.h>
34 #include <gdk/gdkx.h>
35 
36 #include "global.h"
37 
38 #include "view_iface.h"
39 #include "dnd.h"
40 #include "type.h"
41 #include "filer.h"
42 #include "action.h"
43 #include "pixmaps.h"
44 #include "gui_support.h"
45 #include "support.h"
46 #include "options.h"
47 #include "run.h"
48 #include "pinboard.h"
49 #include "dir.h"
50 #include "diritem.h"
51 #include "usericons.h"
52 #include "menu.h"
53 #include "bookmarks.h"
54 
55 #define MAXURILEN 4096		/* Longest URI to allow */
56 
57 gint drag_start_x, drag_start_y;
58 MotionType motion_state = MOTION_NONE;
59 
60 static GList *prompt_local_paths = NULL;
61 static gchar *prompt_dest_path = NULL;
62 
63 /* This keeps track of how many mouse buttons are currently down.
64  * We add a grab when it does 0->1 and release it on 1<-0.
65  *
66  * It may also be set to zero to disable the motion system (eg,
67  * when popping up a menu).
68  */
69 gint motion_buttons_pressed = 0;
70 
71 /* Static prototypes */
72 static void set_xds_prop(GdkDragContext *context, const char *text);
73 static void desktop_drag_data_received(GtkWidget      		*widget,
74 				GdkDragContext  	*context,
75 				gint            	x,
76 				gint            	y,
77 				GtkSelectionData 	*selection_data,
78 				guint               	info,
79 				guint32             	time,
80 				FilerWindow		*filer_window);
81 static void got_data_xds_reply(GtkWidget 		*widget,
82 		  		GdkDragContext 		*context,
83 				GtkSelectionData 	*selection_data,
84 				guint32             	time);
85 static void got_data_raw(GtkWidget 		*widget,
86 			GdkDragContext 		*context,
87 			GtkSelectionData 	*selection_data,
88 			guint32             	time);
89 static void got_uri_list(GtkWidget 		*widget,
90 			 GdkDragContext 	*context,
91 			 const char	 	*selection_data,
92 			 guint32             	time);
93 static gboolean drag_drop(GtkWidget 	  *widget,
94 			  GdkDragContext  *context,
95 			  gint            x,
96 			  gint            y,
97 			  guint           time,
98 			  gpointer	  data);
99 static void drag_data_received(GtkWidget      		*widget,
100 			GdkDragContext  	*context,
101 			gint            	x,
102 			gint            	y,
103 			GtkSelectionData 	*selection_data,
104 			guint               	info,
105 			guint32             	time,
106 			gpointer		user_data);
107 static gboolean spring_now(gpointer data);
108 static void spring_win_destroyed(GtkWidget *widget, gpointer data);
109 static void menuitem_response(gpointer data, guint action, GtkWidget *widget);
110 static void prompt_action(GList *paths, gchar *dest);
111 
112 typedef enum {
113 	MENU_COPY,
114 	MENU_MOVE,
115 	MENU_LINK_REL,
116 	MENU_LINK_ABS,
117 } MenuActionType;
118 
119 #undef N_
120 #define N_(x) x
121 static GtkItemFactoryEntry menu_def[] = {
122 {N_("Copy"),		NULL, menuitem_response, MENU_COPY, 	NULL},
123 {N_("Move"),		NULL, menuitem_response, MENU_MOVE, 	NULL},
124 {N_("Link (relative)"),	NULL, menuitem_response, MENU_LINK_REL, NULL},
125 {N_("Link (absolute)"),	NULL, menuitem_response, MENU_LINK_ABS,	NULL},
126 };
127 static GtkWidget *dnd_menu = NULL;
128 
129 /* Possible values for drop_dest_type (can also be NULL).
130  * In either case, drop_dest_path is the app/file/dir to use.
131  */
132 const char *drop_dest_prog = "drop_dest_prog";	/* Run a program */
133 const char *drop_dest_dir  = "drop_dest_dir";	/* Save to path */
134 const char *drop_dest_pass_through  = "drop_dest_pass";	/* Pass to parent */
135 const char *drop_dest_bookmark = "drop_dest_bookmark";	/* Add to bookmarks */
136 
137 GdkAtom XdndDirectSave0;
138 GdkAtom xa_text_plain;
139 GdkAtom text_uri_list;
140 GdkAtom text_x_moz_url;
141 GdkAtom xa_application_octet_stream;
142 GdkAtom xa_string; /* Not actually used for DnD, but the others are here! */
143 
144 int spring_in_progress = 0;	/* Non-zero changes filer_opendir slightly */
145 
146 Option o_dnd_drag_to_icons;
147 Option o_dnd_spring_open;
148 static Option o_dnd_spring_delay;
149 static Option o_dnd_middle_menu;
150 Option o_dnd_left_menu;
151 static Option o_dnd_uri_handler;
152 
dnd_init(void)153 void dnd_init(void)
154 {
155 	XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
156 	xa_text_plain = gdk_atom_intern("text/plain", FALSE);
157 	text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
158 	text_x_moz_url = gdk_atom_intern("text/x-moz-url", FALSE);
159 	xa_application_octet_stream = gdk_atom_intern("application/octet-stream",
160 			FALSE);
161 	xa_string = gdk_atom_intern("STRING", FALSE);
162 
163 	option_add_int(&o_dnd_drag_to_icons, "dnd_drag_to_icons", 1);
164 	option_add_int(&o_dnd_spring_open, "dnd_spring_open", 0);
165 	option_add_int(&o_dnd_spring_delay, "dnd_spring_delay", 400);
166 	option_add_int(&o_dnd_left_menu, "dnd_left_menu", TRUE);
167 	option_add_int(&o_dnd_middle_menu, "dnd_middle_menu", TRUE);
168 
169 	option_add_string(&o_dnd_uri_handler, "dnd_uri_handler",
170 			"xterm -e wget $1");
171 }
172 
173 /*			SUPPORT FUNCTIONS			*/
174 
175 /* Set the XdndDirectSave0 property on the source window for this context */
set_xds_prop(GdkDragContext * context,const char * text)176 static void set_xds_prop(GdkDragContext *context, const char *text)
177 {
178 	gdk_property_change(context->source_window,
179 			XdndDirectSave0,
180 			xa_text_plain, 8,
181 			GDK_PROP_MODE_REPLACE,
182 			text,
183 			strlen(text));
184 }
185 
get_xds_prop(GdkDragContext * context)186 static char *get_xds_prop(GdkDragContext *context)
187 {
188 	guchar	*prop_text;
189 	gint	length;
190 
191 	if (gdk_property_get(context->source_window,
192 			XdndDirectSave0,
193 			xa_text_plain,
194 			0, MAXURILEN,
195 			FALSE,
196 			NULL, NULL,
197 			&length, &prop_text) && prop_text)
198 	{
199 		/* Terminate the string */
200 		prop_text = g_realloc(prop_text, length + 1);
201 		prop_text[length] = '\0';
202 		/* Note: assuming UTF-8 (should convert here) */
203 		return prop_text;
204 	}
205 
206 	return NULL;
207 }
208 
209 /* Is the sender willing to supply this target type? */
provides(GdkDragContext * context,GdkAtom target)210 gboolean provides(GdkDragContext *context, GdkAtom target)
211 {
212 	GList	    *targets = context->targets;
213 
214 	while (targets && ((GdkAtom) targets->data != target))
215 		targets = targets->next;
216 
217 	return targets != NULL;
218 }
219 
220 /*			DRAGGING FROM US			*/
221 
222 /* The user has held the mouse button down over a group of item and moved -
223  * start a drag. 'uri_list' is copied, so you can delete it straight away.
224  */
drag_selection(GtkWidget * widget,GdkEventMotion * event,guchar * uri_list)225 void drag_selection(GtkWidget *widget, GdkEventMotion *event, guchar *uri_list)
226 {
227 	GdkPixbuf	*pixbuf;
228 	GdkDragContext 	*context;
229 	GdkDragAction	actions;
230 	GtkTargetList   *target_list;
231 	GtkTargetEntry 	target_table[] = {
232 		{"text/uri-list", 0, TARGET_URI_LIST},
233 		{"UTF8_STRING", 0, TARGET_UTF8},
234 	};
235 
236 	if (event->state & GDK_BUTTON1_MASK)
237 		actions = GDK_ACTION_COPY | GDK_ACTION_MOVE
238 			| GDK_ACTION_LINK | GDK_ACTION_ASK;
239 	else
240 	{
241 		if (o_dnd_middle_menu.int_value)
242 			actions = GDK_ACTION_ASK;
243 		else
244 			actions = GDK_ACTION_MOVE;
245 	}
246 
247 	target_list = gtk_target_list_new(target_table,
248 					G_N_ELEMENTS(target_table));
249 
250 	context = gtk_drag_begin(widget,
251 			target_list,
252 			actions,
253 			(event->state & GDK_BUTTON1_MASK) ? 1 :
254 			(event->state & GDK_BUTTON2_MASK) ? 2 : 3,
255 			(GdkEvent *) event);
256 
257 	g_dataset_set_data_full(context, "uri_list",
258 				g_strdup(uri_list), g_free);
259 
260 	pixbuf = gtk_widget_render_icon(widget, GTK_STOCK_DND_MULTIPLE,
261 					GTK_ICON_SIZE_DIALOG, NULL);
262 	gtk_drag_set_icon_pixbuf(context, pixbuf, 0, 0);
263 	g_object_unref(pixbuf);
264 }
265 
266 /* Copy/Load this item into another directory/application */
drag_one_item(GtkWidget * widget,GdkEventMotion * event,const guchar * full_path,DirItem * item,MaskedPixmap * image)267 void drag_one_item(GtkWidget		*widget,
268 		   GdkEventMotion	*event,
269 		   const guchar		*full_path,
270 		   DirItem		*item,
271 		   MaskedPixmap		*image)
272 {
273 	guchar		*uri, *tmp;
274 	GdkDragContext 	*context;
275 	GdkDragAction	actions;
276 	GtkTargetList   *target_list;
277 	GtkTargetEntry 	target_table[] = {
278 		{"text/uri-list", 0, TARGET_URI_LIST},
279 		{"UTF8_STRING", 0, TARGET_UTF8},
280 		{"application/octet-stream", 0, TARGET_RAW},
281 		{"", 0, TARGET_RAW},
282 	};
283 
284 	g_return_if_fail(full_path != NULL);
285 	g_return_if_fail(item != NULL);
286 
287 	if (!image)
288 		image = di_image(item);
289 
290 	if (item->base_type == TYPE_FILE)
291 	{
292 		MIME_type *t = item->mime_type;
293 
294 		target_table[3].target = g_strconcat(t->media_type, "/",
295 						     t->subtype, NULL);
296 		target_list = gtk_target_list_new(target_table,
297 					G_N_ELEMENTS(target_table));
298 		g_free(target_table[3].target);
299 	}
300 	else
301 		target_list = gtk_target_list_new(target_table, 2);
302 
303 	if (event->state & GDK_BUTTON1_MASK)
304 		actions = GDK_ACTION_COPY | GDK_ACTION_ASK
305 			| GDK_ACTION_MOVE | GDK_ACTION_LINK;
306 	else
307 	{
308 		if (o_dnd_middle_menu.int_value)
309 			actions = GDK_ACTION_ASK;
310 		else
311 			actions = GDK_ACTION_MOVE;
312 	}
313 
314 	context = gtk_drag_begin(widget,
315 			target_list,
316 			actions,
317 			(event->state & GDK_BUTTON1_MASK) ? 1 :
318 			(event->state & GDK_BUTTON2_MASK) ? 2 : 3,
319 			(GdkEvent *) event);
320 
321 	g_dataset_set_data_full(context, "full_path",
322 			g_strdup(full_path), g_free);
323 	tmp = (char *) encode_path_as_uri(full_path);
324 	uri = g_strconcat(tmp, "\r\n", NULL);
325 	/*printf("%s\n", tmp);*/
326 	g_free(tmp);
327 	g_dataset_set_data_full(context, "uri_list", uri, g_free);
328 
329 	g_return_if_fail(image != NULL);
330 
331 	gtk_drag_set_icon_pixbuf(context, image->pixbuf, 0, 0);
332 }
333 
334 /* Convert text/uri-list data to UTF8_STRING.
335  * g_free() the result.
336  */
uri_list_to_utf8(const char * uri_list)337 static gchar *uri_list_to_utf8(const char *uri_list)
338 {
339 	GString *new;
340 	GList *uris, *next_uri;
341 	char *string;
342 
343 	new = g_string_new(NULL);
344 
345 	uris = uri_list_to_glist(uri_list);
346 
347 	for (next_uri = uris; next_uri; next_uri = next_uri->next)
348 	{
349 		EscapedPath *uri = next_uri->data;
350 		char *local;
351 
352 		local = get_local_path(uri);
353 
354 		if (new->len)
355 			g_string_append_c(new, ' ');
356 
357 		if (local)
358 		{
359 			g_string_append(new, local);
360 			g_free(local);
361 		}
362 		else
363 			g_warning("Not local!\n");
364 
365 		g_free(uri);
366 	}
367 
368 	if (uris)
369 		g_list_free(uris);
370 
371 	string = new->str;
372 	g_string_free(new, FALSE);
373 
374 	return string;
375 }
376 
377 /* Called when a remote app wants us to send it some data.
378  * TODO: Maybe we should handle errors better (ie, let the remote app know
379  * the drag has failed)?
380  */
drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint32 time,gpointer data)381 void drag_data_get(GtkWidget          		*widget,
382 			GdkDragContext     	*context,
383 			GtkSelectionData   	*selection_data,
384 			guint               	info,
385 			guint32             	time,
386 			gpointer		data)
387 {
388 	char		*to_send = "E";	/* Default to sending an error */
389 	long		to_send_length = 1;
390 	gboolean	delete_once_sent = FALSE;
391 	GdkAtom		type;
392 	guchar		*path;
393 
394 	type = selection_data->target;
395 
396 	switch (info)
397 	{
398 		case	TARGET_RAW:
399 			path = g_dataset_get_data(context, "full_path");
400 			if (path && load_file(path, &to_send, &to_send_length))
401 			{
402 				delete_once_sent = TRUE;
403 				break;
404 			}
405 			g_warning("drag_data_get: Can't find path!\n");
406 			return;
407 		case	TARGET_UTF8:
408 		{
409 			char *uri_list;
410 			uri_list = g_dataset_get_data(context, "uri_list");
411 			to_send = uri_list_to_utf8(uri_list);
412 			to_send_length = strlen(to_send);
413 			delete_once_sent = TRUE;
414 			break;
415 		}
416 		case	TARGET_URI_LIST:
417 			to_send = g_dataset_get_data(context, "uri_list");
418 			to_send_length = strlen(to_send);
419 			type = text_uri_list;		/* (needed for xine) */
420 			delete_once_sent = FALSE;
421 			break;
422 		default:
423 			delayed_error("drag_data_get: %s",
424 					_("Internal error - bad info type"));
425 			break;
426 	}
427 
428 	gtk_selection_data_set(selection_data,
429 			type,
430 			8,
431 			to_send,
432 			to_send_length);
433 
434 	if (delete_once_sent)
435 		g_free(to_send);
436 }
437 
438 /*			DRAGGING TO US				*/
439 
440 /* Set up this widget as a drop-target.
441  * Does not attach any motion handlers.
442  */
make_drop_target(GtkWidget * widget,GtkDestDefaults defaults)443 void make_drop_target(GtkWidget *widget, GtkDestDefaults defaults)
444 {
445 	GtkTargetEntry 	target_table[] =
446 	{
447 		{"text/uri-list", 0, TARGET_URI_LIST},
448 		{"text/x-moz-url", 0, TARGET_MOZ_URL},
449 		{"XdndDirectSave0", 0, TARGET_XDS},
450 		{"application/octet-stream", 0, TARGET_RAW},
451 	};
452 
453 	gtk_drag_dest_set(widget,
454 			defaults,
455 			target_table,
456 			sizeof(target_table) / sizeof(*target_table),
457 			GDK_ACTION_COPY | GDK_ACTION_ASK | GDK_ACTION_MOVE
458 			| GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
459 
460 	g_signal_connect(widget, "drag_drop", G_CALLBACK(drag_drop), NULL);
461 	g_signal_connect(widget, "drag_data_received",
462 			G_CALLBACK(drag_data_received), NULL);
463 }
464 
465 /* Like drag_set_dest, but for a pinboard-type widget */
drag_set_pinboard_dest(GtkWidget * widget)466 void drag_set_pinboard_dest(GtkWidget *widget)
467 {
468 	GtkTargetEntry 	target_table[] = {
469 		{"text/uri-list", 0, TARGET_URI_LIST},
470 	};
471 
472 	gtk_drag_dest_set(widget,
473 			  GTK_DEST_DEFAULT_DROP,
474 			  target_table,
475 			  sizeof(target_table) / sizeof(*target_table),
476 			  GDK_ACTION_LINK);
477 	g_signal_connect(widget, "drag_data_received",
478 			    G_CALLBACK(desktop_drag_data_received), NULL);
479 }
480 
481 /* item is the item the file is held over, NULL for directory background.
482  * 'item' may be NULL on exit if the drop should be treated as onto the
483  * background. Disallow drags to a selected icon before calling this.
484  *
485  * Returns NULL to reject the drop, or drop_dest_prog/drop_dest_dir to
486  * accept. Build the path based on item.
487  */
dnd_motion_item(GdkDragContext * context,DirItem ** item_p)488 const guchar *dnd_motion_item(GdkDragContext *context, DirItem **item_p)
489 {
490 	DirItem	*item = *item_p;
491 
492 	if (item)
493 	{
494 		/* If we didn't drop onto a directory, application or
495 		 * executable file then act as though the drop is to the
496 		 * window background.
497 		 */
498 		if (item->base_type != TYPE_DIRECTORY && !EXECUTABLE_FILE(item))
499 		{
500 			item = NULL;
501 			*item_p = NULL;
502 		}
503 	}
504 
505 	if (!item)
506 	{
507 		/* Drop onto the window background */
508 
509 		return drop_dest_dir;
510 	}
511 
512 	/* Drop onto a program/directory of some sort */
513 
514 	if (item->base_type == TYPE_DIRECTORY &&
515 			!(item->flags & ITEM_FLAG_APPDIR))
516 	{
517 		/* A normal directory */
518 		if (provides(context, text_uri_list) ||
519 				provides(context, text_x_moz_url) ||
520 				provides(context, XdndDirectSave0))
521 			return drop_dest_dir;
522 	}
523 	else
524 	{
525 		if (provides(context, text_uri_list) ||
526 				provides(context, text_x_moz_url) ||
527 				provides(context, xa_application_octet_stream))
528 			return drop_dest_prog;
529 	}
530 
531 	return NULL;
532 }
533 
534 /* User has tried to drop some data on us. Decide what format we would
535  * like the data in.
536  */
drag_drop(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time,gpointer data)537 static gboolean drag_drop(GtkWidget 	  *widget,
538 			  GdkDragContext  *context,
539 			  gint            x,
540 			  gint            y,
541 			  guint           time,
542 			  gpointer	  data)
543 {
544 	const char	*error = NULL;
545 	char		*leafname = NULL;
546 	GdkAtom		target = GDK_NONE;
547 	char		*dest_path;
548 	char		*dest_type = NULL;
549 
550 	dest_path = g_dataset_get_data(context, "drop_dest_path");
551 	dest_type = g_dataset_get_data(context, "drop_dest_type");
552 
553 	if (dest_type == drop_dest_pass_through)
554 		return FALSE;	/* Let the parent widget handle it */
555 
556 	if (dest_type == drop_dest_bookmark)
557 	{
558 		if (provides(context, text_uri_list))
559 			gtk_drag_get_data(widget, context, text_uri_list, time);
560 		else
561 		{
562 			gtk_drag_finish(context, FALSE, FALSE, time);
563 			delayed_error(_("Drag a directory here to "
564 					"bookmark it."));
565 		}
566 		return TRUE;
567 	}
568 
569 	g_return_val_if_fail(dest_path != NULL, TRUE);
570 
571 	if (dest_type == drop_dest_dir && provides(context, XdndDirectSave0))
572 	{
573 		leafname = get_xds_prop(context);
574 		if (leafname)
575 		{
576 			if (strchr(leafname, '/'))
577 			{
578 				error = _("XDS protocol error: "
579 					"leafname may not contain '/'\n");
580 				null_g_free(&leafname);
581 			}
582 			else
583 			{
584 				char *dest_uri;
585 
586 				/* Not escaped. */
587 				dest_uri = g_strconcat("file://",
588 						our_host_name_for_dnd(),
589 						dest_path, NULL);
590 
591 				set_xds_prop(context,
592 					make_path(dest_uri, leafname));
593 
594 				g_free(dest_uri);
595 
596 				target = XdndDirectSave0;
597 				g_dataset_set_data_full(context, "leafname",
598 						leafname, g_free);
599 			}
600 		}
601 		else
602 			error = _(
603 				"XdndDirectSave0 target provided, but the atom "
604 				"XdndDirectSave0 (type text/plain) did not "
605 					"contain a leafname\n");
606 	}
607 	else if (provides(context, text_uri_list))
608 		target = text_uri_list;
609 	else if (provides(context, text_x_moz_url))
610 		target = text_x_moz_url;
611 	else if (provides(context, xa_application_octet_stream))
612 		target = xa_application_octet_stream;
613 	else
614 	{
615 		if (dest_type == drop_dest_dir)
616 			error = _("Sorry - I require a target type of "
617 				"text/uri-list or XdndDirectSave0.");
618 		else
619 			error = _("Sorry - I require a target type of "
620 				"text/uri-list or application/octet-stream.");
621 	}
622 
623 	if (error)
624 	{
625 		gtk_drag_finish(context, FALSE, FALSE, time);	/* Failure */
626 
627 		delayed_error("%s", error);
628 	}
629 	else
630 		gtk_drag_get_data(widget, context, target, time);
631 
632 	return TRUE;
633 }
634 
635 /* Called when a text/uri-list arrives */
desktop_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint32 time,FilerWindow * filer_window)636 static void desktop_drag_data_received(GtkWidget      	*widget,
637 				       GdkDragContext  	*context,
638 				       gint            	x,
639 				       gint            	y,
640 				       GtkSelectionData *selection_data,
641 				       guint            info,
642 				       guint32          time,
643 				       FilerWindow	*filer_window)
644 {
645 	GList	*uris, *next;
646 	char *error_example = NULL;
647 	gint dx, dy;
648 
649 	if (!selection_data->data)
650 	{
651 		/* Timeout? */
652 		return;
653 	}
654 
655 	if (pinboard_drag_in_progress)
656 	{
657 		pinboard_move_icons();
658 		return;
659 	}
660 
661 	gdk_window_get_position(widget->window, &dx, &dy);
662 	x += dx;
663 	y += dy;
664 
665 	uris = uri_list_to_glist(selection_data->data);
666 
667 	for (next = uris; next; next = next->next)
668 	{
669 		guchar	*path;
670 
671 		path = get_local_path((EscapedPath *) next->data);
672 		if (path)
673 		{
674 			pinboard_pin(path, NULL, x, y, NULL);
675 			x += 64;
676 			g_free(path);
677 		}
678 		else if (!error_example)
679 			error_example = g_strdup(next->data);
680 
681 		g_free(next->data);
682 	}
683 
684 	if (uris)
685 		g_list_free(uris);
686 
687 	if (error_example)
688 	{
689 		delayed_error(_("Failed to add some items to the pinboard, "
690 			"because they are on a remote machine. For example:\n"
691 			"\n%s"), error_example);
692 		g_free(error_example);
693 	}
694 }
695 
696 /* Convert Mozilla's text/x-moz-uri into a text/uri-list */
got_moz_uri(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint32 time)697 static void got_moz_uri(GtkWidget 		*widget,
698 			GdkDragContext 		*context,
699 			GtkSelectionData	*selection_data,
700 			guint32        		time)
701 {
702 	gchar *utf8, *uri_list, *eol;
703 
704 	utf8 = g_utf16_to_utf8((gunichar2 *) selection_data->data,
705 			(glong) selection_data->length,
706 			NULL, NULL, NULL);
707 
708 	eol = utf8 ? strchr(utf8, '\n') : NULL;
709 	if (!eol)
710 	{
711 		delayed_error("Invalid UTF16 from text/x-moz-url target");
712 		g_free(utf8);
713 		gtk_drag_finish(context, FALSE, FALSE, time);
714 		return;
715 	}
716 
717 	*eol = '\0';
718 	uri_list = g_strconcat(utf8, "\r\n", NULL);
719 	g_free(utf8);
720 
721 	got_uri_list(widget, context, uri_list, time);
722 
723 	g_free(uri_list);
724 }
725 
726 /* Called when some data arrives from the remote app (which we asked for
727  * in drag_drop).
728  */
drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint32 time,gpointer user_data)729 static void drag_data_received(GtkWidget      	*widget,
730 			       GdkDragContext  	*context,
731 			       gint            	x,
732 			       gint            	y,
733 			       GtkSelectionData *selection_data,
734 			       guint            info,
735 			       guint32          time,
736 			       gpointer		user_data)
737 {
738 	if (!selection_data->data)
739 	{
740 		/* Timeout? */
741 		gtk_drag_finish(context, FALSE, FALSE, time);	/* Failure */
742 		return;
743 	}
744 
745 	switch (info)
746 	{
747 		case TARGET_XDS:
748 			got_data_xds_reply(widget, context,
749 					selection_data, time);
750 			break;
751 		case TARGET_RAW:
752 			got_data_raw(widget, context, selection_data, time);
753 			break;
754 		case TARGET_URI_LIST:
755 			got_uri_list(widget, context, selection_data->data,
756 					time);
757 			break;
758 		case TARGET_MOZ_URL:
759 			got_moz_uri(widget, context, selection_data, time);
760 			break;
761 		default:
762 			gtk_drag_finish(context, FALSE, FALSE, time);
763 			delayed_error("drag_data_received: %s",
764 					_("Unknown target"));
765 			break;
766 	}
767 }
768 
got_data_xds_reply(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint32 time)769 static void got_data_xds_reply(GtkWidget 		*widget,
770 		  		GdkDragContext 		*context,
771 				GtkSelectionData 	*selection_data,
772 				guint32             	time)
773 {
774 	gboolean	mark_unsafe = TRUE;
775 	char		response = *selection_data->data;
776 	const char	*error = NULL;
777 	char		*dest_path;
778 
779 	dest_path = g_dataset_get_data(context, "drop_dest_path");
780 
781 	if (selection_data->length != 1)
782 		response = '?';
783 
784 	if (response == 'F')
785 	{
786 		/* Sender couldn't save there - ask for another
787 		 * type if possible.
788 		 */
789 		if (provides(context, xa_application_octet_stream))
790 		{
791 			mark_unsafe = FALSE;	/* Wait and see */
792 
793 			gtk_drag_get_data(widget, context,
794 					xa_application_octet_stream, time);
795 		}
796 		else
797 			error = _("Remote app can't or won't send me "
798 					"the data - sorry");
799 	}
800 	else if (response == 'S')
801 	{
802 		/* Success - data is saved */
803 		mark_unsafe = FALSE;	/* It really is safe */
804 		gtk_drag_finish(context, TRUE, FALSE, time);
805 
806 		refresh_dirs(dest_path);
807 	}
808 	else if (response != 'E')
809 	{
810 		error = _("XDS protocol error: "
811 			"return code should be 'S', 'F' or 'E'\n");
812 	}
813 	/* else: error has been reported by the sender */
814 
815 	if (mark_unsafe)
816 	{
817 		set_xds_prop(context, "");
818 		/* Unsave also implies that the drag failed */
819 		gtk_drag_finish(context, FALSE, FALSE, time);
820 	}
821 
822 	if (error)
823 		delayed_error("%s", error);
824 }
825 
got_data_raw(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint32 time)826 static void got_data_raw(GtkWidget 		*widget,
827 			GdkDragContext 		*context,
828 			GtkSelectionData 	*selection_data,
829 			guint32             	time)
830 {
831 	const char	*leafname;
832 	int		fd;
833 	const char	*error = NULL;
834 	const char	*dest_path;
835 
836 	g_return_if_fail(selection_data->data != NULL);
837 
838 	dest_path = g_dataset_get_data(context, "drop_dest_path");
839 
840 	if (context->action == GDK_ACTION_ASK)
841 	{
842 		gtk_drag_finish(context, FALSE, FALSE, time);	/* Failure */
843 		delayed_error(_("Sorry, can't display a menu of actions "
844 				"for a remote file / raw data."));
845 		return;
846 	}
847 
848 	if (g_dataset_get_data(context, "drop_dest_type") == drop_dest_prog)
849 	{
850 		/* The data needs to be sent to an application */
851 		run_with_data(dest_path,
852 				selection_data->data, selection_data->length);
853 		gtk_drag_finish(context, TRUE, FALSE, time);    /* Success! */
854 		return;
855 	}
856 
857 	leafname = g_dataset_get_data(context, "leafname");
858 	if (!leafname)
859 		leafname = _("UntitledData");
860 
861 	fd = open(make_path(dest_path, leafname),
862 		O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
863 			S_IRUSR | S_IRGRP | S_IROTH |
864 			S_IWUSR | S_IWGRP | S_IWOTH);
865 
866 	if (fd == -1)
867 		error = g_strerror(errno);
868 	else
869 	{
870 		if (write(fd,
871 			selection_data->data,
872 			selection_data->length) == -1)
873 				error = g_strerror(errno);
874 
875 		if (close(fd) == -1 && !error)
876 			error = g_strerror(errno);
877 
878 		refresh_dirs(dest_path);
879 	}
880 
881 	if (error)
882 	{
883 		if (provides(context, XdndDirectSave0))
884 			set_xds_prop(context, "");
885 		gtk_drag_finish(context, FALSE, FALSE, time);	/* Failure */
886 		delayed_error(_("Error saving file: %s"), error);
887 	}
888 	else
889 		gtk_drag_finish(context, TRUE, FALSE, time);    /* Success! */
890 }
891 
uri_is_local(const EscapedPath * uri)892 static gboolean uri_is_local(const EscapedPath *uri)
893 {
894 	char *path;
895 	path = get_local_path(uri);
896 	if (!path)
897 		return FALSE;
898 	g_free(path);
899 	return TRUE;
900 }
901 
902 /* Run the shell command 'command', replacing $1 with 'arg' */
run_with_argument(const char * dir,const char * command,const char * arg)903 static void run_with_argument(const char *dir,
904 				const char *command,
905 				const char *arg)
906 {
907 	GPtrArray	*argv;
908 
909 	argv = g_ptr_array_new();
910 
911 	g_ptr_array_add(argv, "sh");
912 	g_ptr_array_add(argv, "-c");
913 	g_ptr_array_add(argv, (char *) command);
914 	g_ptr_array_add(argv, "sh");
915 	g_ptr_array_add(argv, (char *) arg);
916 	g_ptr_array_add(argv, NULL);
917 
918 	rox_spawn(dir, (const gchar **) argv->pdata);
919 
920 	g_ptr_array_free(argv, TRUE);
921 }
922 
923 /* We've got a list of URIs from somewhere (probably another filer window).
924  * If the files are on the local machine then try to copy them ourselves,
925  * otherwise, if there was only one file and application/octet-stream was
926  * provided, get the data via the X server.
927  * For http:, https: or ftp: schemes, use the download handler.
928  */
got_uri_list(GtkWidget * widget,GdkDragContext * context,const char * selection_data,guint32 time)929 static void got_uri_list(GtkWidget 		*widget,
930 			 GdkDragContext 	*context,
931 			 const char 		*selection_data,
932 			 guint32             	time)
933 {
934 	GList		*uri_list;
935 	const char	*error = NULL;
936 	GList		*next_uri;
937 	gboolean	send_reply = TRUE;
938 	char		*dest_path;
939 	char		*type;
940 
941 	dest_path = g_dataset_get_data(context, "drop_dest_path");
942 	type = g_dataset_get_data(context, "drop_dest_type");
943 
944 	uri_list = uri_list_to_glist(selection_data);
945 
946 	if (type == drop_dest_bookmark)
947 	{
948 		GList *next;
949 		for (next = uri_list; next; next = next->next)
950 			bookmarks_add_uri((EscapedPath *) next->data);
951 		destroy_glist(&uri_list);
952 		gtk_drag_finish(context, TRUE, FALSE, time);    /* Success! */
953 		return;
954 	}
955 
956 	g_return_if_fail(dest_path != NULL);
957 
958 	if (!uri_list)
959 		error = _("No URIs in the text/uri-list (nothing to do!)");
960 	else if (context->action != GDK_ACTION_ASK && type == drop_dest_prog)
961 		run_with_files(dest_path, uri_list);
962 	else if ((!uri_list->next) && !uri_is_local(uri_list->data))
963 	{
964 		/* There is one URI in the list, and it's not on the local
965 		 * machine. Get it via the X server if possible.
966 		 */
967 
968 		if (provides(context, xa_application_octet_stream))
969 		{
970 			char	*leaf;
971 			leaf = strrchr(uri_list->data, '/');
972 			if (leaf)
973 				leaf++;
974 			else
975 				leaf = uri_list->data;
976 			g_dataset_set_data_full(context, "leafname",
977 				unescape_uri((EscapedPath *) leaf), g_free);
978 			gtk_drag_get_data(widget, context,
979 					xa_application_octet_stream, time);
980 			send_reply = FALSE;
981 		}
982 		else if ((strncasecmp(uri_list->data, "http:", 5) == 0) ||
983 			 (strncasecmp(uri_list->data, "https:", 6) == 0) ||
984                          (strncasecmp(uri_list->data, "ftp:", 4) == 0))
985 		{
986 			run_with_argument(dest_path,
987 					o_dnd_uri_handler.value,
988 					(char *) uri_list->data);
989                 }
990                 else
991 			error = _("Can't get data from remote machine "
992 				"(application/octet-stream not provided)");
993 	}
994 	else
995 	{
996 		GList *local_paths = NULL;
997 
998 		/* Either one local URI, or a list. If everything in the list
999 		 * isn't local then we are stuck.
1000 		 */
1001 
1002 		for (next_uri = uri_list; next_uri; next_uri = next_uri->next)
1003 		{
1004 			char *path;
1005 
1006 			path = get_local_path((EscapedPath *) next_uri->data);
1007 			/*printf("%s -> %s\n", (char *) next_uri->data,
1008 			  path? path: "NULL");*/
1009 
1010 			if (path)
1011 				local_paths = g_list_append(local_paths,
1012 								path);
1013 			else
1014 				error = _("Some of these files are on a "
1015 					"different machine - they will be "
1016 					"ignored - sorry");
1017 		}
1018 
1019 		if (!local_paths)
1020 		{
1021 			error = _("None of these files are on the local "
1022 				"machine - I can't operate on multiple "
1023 				"remote files - sorry.");
1024 		}
1025 		else if (context->action == GDK_ACTION_ASK)
1026 			prompt_action(local_paths, dest_path);
1027 		else if (context->action == GDK_ACTION_MOVE)
1028 			action_move(local_paths, dest_path, NULL, -1);
1029 		else if (context->action == GDK_ACTION_COPY)
1030 			action_copy(local_paths, dest_path, NULL, -1);
1031 		else if (context->action == GDK_ACTION_LINK)
1032 			action_link(local_paths, dest_path, NULL, TRUE);
1033 		else
1034 			error = _("Unknown action requested");
1035 
1036 		destroy_glist(&local_paths);
1037 	}
1038 
1039 	if (error)
1040 	{
1041 		gtk_drag_finish(context, FALSE, FALSE, time);	/* Failure */
1042 		delayed_error(_("Error getting file list: %s"), error);
1043 	}
1044 	else if (send_reply)
1045 		gtk_drag_finish(context, TRUE, FALSE, time);    /* Success! */
1046 
1047 	destroy_glist(&uri_list);
1048 }
1049 
1050 /* Called when an item from the ACTION_ASK menu is chosen */
menuitem_response(gpointer data,guint action,GtkWidget * widget)1051 static void menuitem_response(gpointer data, guint action, GtkWidget *widget)
1052 {
1053 	if (action == MENU_MOVE)
1054 		action_move(prompt_local_paths, prompt_dest_path, NULL, -1);
1055 	else if (action == MENU_COPY)
1056 		action_copy(prompt_local_paths, prompt_dest_path, NULL, -1);
1057 	else if (action == MENU_LINK_REL)
1058 		action_link(prompt_local_paths, prompt_dest_path, NULL, TRUE);
1059 	else if (action == MENU_LINK_ABS)
1060 		action_link(prompt_local_paths, prompt_dest_path, NULL, FALSE);
1061 }
1062 
1063 /* When some local files are dropped somewhere with ACTION_ASK, this
1064  * function is called to display the menu.
1065  */
prompt_action(GList * paths,gchar * dest)1066 static void prompt_action(GList *paths, gchar *dest)
1067 {
1068 	GList		*next;
1069 	GdkEvent	*event;
1070 
1071 	if (prompt_local_paths)
1072 	{
1073 		destroy_glist(&prompt_local_paths);
1074 		null_g_free(&prompt_dest_path);
1075 	}
1076 
1077 	/* Make a copy of the arguments */
1078 	for (next = paths; next; next = next->next)
1079 		prompt_local_paths = g_list_append(prompt_local_paths,
1080 						g_strdup((gchar *) next->data));
1081 	prompt_dest_path = g_strdup(dest);
1082 
1083 	if (!dnd_menu)
1084 	{
1085 		GtkItemFactory	*item_factory;
1086 
1087 		item_factory = menu_create(menu_def,
1088 				sizeof(menu_def) / sizeof(*menu_def),
1089 				"<dnd>", NULL);
1090 		dnd_menu = gtk_item_factory_get_widget(item_factory, "<dnd>");
1091 	}
1092 
1093 	/* Shade 'Set Icon' if there are multiple files */
1094 	menu_set_items_shaded(dnd_menu, g_list_length(paths) != 1, 4, 1);
1095 
1096 	event = gtk_get_current_event();
1097 	show_popup_menu(dnd_menu, event, 1);
1098 	if (event)
1099 		gdk_event_free(event);
1100 }
1101 
1102 
1103 /*			SPRING-LOADING 				*/
1104 
1105 /* This is the code that makes directories pop open if you hold a
1106  * file over them...
1107  *
1108  * First, call dnd_spring_load(context) to arm the system.
1109  * After a timeout (1/2 a second) the dest_path directory will be
1110  * opened in a new window, unless dnd_spring_abort is called first.
1111  */
1112 
1113 static gint spring_timeout = -1;
1114 static GdkDragContext *spring_context = NULL;
1115 static FilerWindow *spring_window = NULL;
1116 static FilerWindow *spring_src_window = NULL;
1117 
dnd_spring_load(GdkDragContext * context,FilerWindow * src_win)1118 void dnd_spring_load(GdkDragContext *context, FilerWindow *src_win)
1119 {
1120 	g_return_if_fail(context != NULL);
1121 
1122 	if (!o_dnd_spring_open.int_value)
1123 		return;
1124 
1125 	if (spring_context)
1126 		dnd_spring_abort();
1127 
1128 	spring_context = context;
1129 	g_object_ref(spring_context);
1130 	spring_src_window = src_win;
1131 	spring_timeout = gtk_timeout_add(
1132 			o_dnd_spring_delay.int_value, spring_now, NULL);
1133 }
1134 
dnd_spring_abort(void)1135 void dnd_spring_abort(void)
1136 {
1137 	if (!spring_context)
1138 		return;
1139 
1140 	g_object_unref(spring_context);
1141 	spring_context = NULL;
1142 	gtk_timeout_remove(spring_timeout);
1143 }
1144 
1145 /* If all mod keys are released, no buttons are pressed, and the
1146  * mouse is outside the spring window, then close it.
1147  */
spring_check_idle(gpointer data)1148 static gboolean spring_check_idle(gpointer data)
1149 {
1150 	int	p_x, p_y;
1151 
1152 	if (!spring_window)
1153 		return FALSE;
1154 
1155 	if (!get_pointer_xy(&p_x, &p_y))
1156 	{
1157 		/*
1158 		GdkWindow	*win = spring_window->window->window;
1159 		int		x, y;
1160 		int		w, h;
1161 
1162 		gdk_window_get_position(win, &x, &y);
1163 		gdk_window_get_size(win, &w, &h);
1164 
1165 		if (p_x < x || p_x > x + w || p_y < y || p_y > y + h)
1166 		{
1167 		*/
1168 
1169 		gtk_widget_destroy(spring_window->window);
1170 		return FALSE;		/* Got it! */
1171 	}
1172 
1173 	return TRUE;	/* Try again later */
1174 }
1175 
spring_now(gpointer data)1176 static gboolean spring_now(gpointer data)
1177 {
1178 	const char	*type;
1179 	const guchar	*dest_path;
1180 	gint		x, y;
1181 
1182 	g_return_val_if_fail(spring_context != NULL, FALSE);
1183 	g_return_val_if_fail(!spring_in_progress, FALSE);
1184 
1185 	type = g_dataset_get_data(spring_context, "drop_dest_type");
1186 	if (type == drop_dest_bookmark)
1187 	{
1188 		bookmarks_edit();
1189 		goto out;
1190 	}
1191 
1192 	dest_path = g_dataset_get_data(spring_context, "drop_dest_path");
1193 	g_return_val_if_fail(dest_path != NULL, FALSE);
1194 
1195 	/*
1196 	 * Note: Due to a bug in gtk, if a window disappears during
1197 	 * a drag and the pointer moves over where the window was,
1198 	 * the sender crashes! Therefore, do not close any windows
1199 	 * while dragging! (fixed in later versions)
1200 	 */
1201 	/*
1202 	if (spring_window)
1203 		gtk_widget_destroy(spring_window->window);
1204 		*/
1205 
1206 	get_pointer_xy(&x, &y);
1207 
1208 	spring_in_progress++;
1209 	if (spring_window)
1210 	{
1211 		view_cursor_to_iter(spring_window->view, NULL);
1212 		filer_change_to(spring_window, dest_path, NULL);
1213 		/* DON'T move the window. Gtk+ sometimes doesn't
1214 		 * notice :-(
1215 		 */
1216 	}
1217 	else
1218 	{
1219 		spring_window = filer_opendir(dest_path,
1220 						spring_src_window, NULL);
1221 		if (spring_window)
1222 		{
1223 			gtk_timeout_add(500, spring_check_idle, NULL);
1224 			g_signal_connect(spring_window->window, "destroy",
1225 					G_CALLBACK(spring_win_destroyed), NULL);
1226 			centre_window(spring_window->window->window, x, y);
1227 		}
1228 	}
1229 	spring_in_progress--;
1230 
1231 out:
1232 	dnd_spring_abort();
1233 
1234 	return FALSE;
1235 }
1236 
spring_win_destroyed(GtkWidget * widget,gpointer data)1237 static void spring_win_destroyed(GtkWidget *widget, gpointer data)
1238 {
1239 	spring_window = NULL;
1240 }
1241 
1242 /*			HANDLING MOTION EVENTS				*/
1243 
1244 /* If not-NULL, then this widget has a grab */
1245 static GtkWidget *motion_widget = NULL;
1246 
1247 /* If TRUE, we must gdk_pointer_ungrab() too when finishing */
1248 static gboolean  motion_pointer_grab = FALSE;
1249 
1250 /* Call this on a button press event. It stores the mouse position
1251  * as the start of the new drag and returns TRUE if all is well.
1252  * Further motions events are disabled at this point - you must
1253  * then call dnd_motion_start() to set the type of motion expected.
1254  * Grabs the widget on the first press.
1255  *
1256  * If the system is not ready to handle a motion event (because a
1257  * button is already held down?) it does nothing and returns FALSE.
1258  *
1259  * If the event is not a single click then it simply returns TRUE.
1260  */
dnd_motion_press(GtkWidget * widget,GdkEventButton * event)1261 gboolean dnd_motion_press(GtkWidget *widget, GdkEventButton *event)
1262 {
1263 	if (event->type != GDK_BUTTON_PRESS)
1264 		return TRUE;		/* Not a click event! */
1265 
1266 	motion_buttons_pressed++;
1267 	if (motion_buttons_pressed == 1)
1268 	{
1269 		/* g_print("[ grab! ]\n"); */
1270 		gtk_grab_add(widget);
1271 		motion_widget = widget;
1272 	}
1273 
1274 	if (motion_state != MOTION_NONE)
1275 		return FALSE;		/* Ignore clicks - we're busy! */
1276 
1277 	motion_state = MOTION_DISABLED;
1278 	drag_start_x = event->x_root;
1279 	drag_start_y = event->y_root;
1280 
1281 	return TRUE;
1282 }
1283 
1284 /* After the button press event, decide what kind of motion is expected.
1285  * If you don't call this then the motion system is disabled - call
1286  * dnd_motion_release() to reset it.
1287  *
1288  * Note: If you open a popup menu or start DND call dnd_motion_ungrab()
1289  * instead.
1290  */
dnd_motion_start(MotionType motion)1291 void dnd_motion_start(MotionType motion)
1292 {
1293 	g_return_if_fail(motion_state == MOTION_DISABLED);
1294 
1295 	motion_state = motion;
1296 }
1297 
1298 /* Call this on a button release event. If some buttons are still pressed,
1299  * returns TRUE and does nothing.
1300  *
1301  * Otherwise, it resets the motion system to be ready again and returns TRUE.
1302  *
1303  * If the motion system wasn't being used (MOTION_NONE) then it does nothing
1304  * and returns FALSE - process the release event yourself as it isn't part
1305  * of a motion. This also happens if a motion was primed but never happened.
1306  */
dnd_motion_release(GdkEventButton * event)1307 gboolean dnd_motion_release(GdkEventButton *event)
1308 {
1309 	MotionType	motion = motion_state;
1310 	gint		drag_threshold;
1311 	int		dx, dy;
1312 
1313 	if (motion_buttons_pressed == 0)
1314 		return TRUE;		/* We were disabled */
1315 
1316 	if (motion_buttons_pressed == 1)
1317 		dnd_motion_ungrab();
1318 	else
1319 	{
1320 		motion_buttons_pressed--;
1321 		return TRUE;
1322 	}
1323 
1324 	if (motion == MOTION_REPOSITION || motion == MOTION_DISABLED)
1325 		return TRUE;	/* Already done something - eat the event */
1326 
1327 	/* Eat release events that happen too far from the click
1328 	 * source. Otherwise, allow the caller to treat this as a click
1329 	 * that never became a motion.
1330 	 */
1331 	dx = event->x_root - drag_start_x;
1332 	dy = event->y_root - drag_start_y;
1333 
1334 	g_object_get(gtk_settings_get_default(),
1335 		"gtk-dnd-drag-threshold", &drag_threshold,
1336 		NULL);
1337 
1338 	return ABS(dx) > drag_threshold || ABS(dy) > drag_threshold;
1339 }
1340 
1341 /* Use this to disable the motion system. The system will be reset once
1342  * all mouse buttons are released.
1343  */
dnd_motion_disable(void)1344 void dnd_motion_disable(void)
1345 {
1346 	g_return_if_fail(motion_state != MOTION_NONE &&
1347 			 motion_state != MOTION_DISABLED);
1348 
1349 	motion_state = MOTION_DISABLED;
1350 }
1351 
1352 /* Use this if something else is going to grab the pointer so that
1353  * we won't get any more motion or release events.
1354  */
dnd_motion_ungrab(void)1355 void dnd_motion_ungrab(void)
1356 {
1357 	if (motion_buttons_pressed > 0)
1358 	{
1359 		if (motion_pointer_grab)
1360 		{
1361 			gdk_pointer_ungrab(GDK_CURRENT_TIME);
1362 			motion_pointer_grab = FALSE;
1363 			/* g_print("[ ungrab_pointer ]\n"); */
1364 		}
1365 		gtk_grab_remove(motion_widget);
1366 		motion_widget = NULL;
1367 		motion_buttons_pressed = 0;
1368 		/* g_print("[ ungrab ]\n"); */
1369 	}
1370 
1371 	motion_state = MOTION_NONE;
1372 }
1373 
1374 /* Call this on motion events. If the mouse position is far enough
1375  * from the click position, returns TRUE and does dnd_motion_ungrab().
1376  * You should then start regular drag-and-drop.
1377  *
1378  * Otherwise, returns FALSE.
1379  */
dnd_motion_moved(GdkEventMotion * event)1380 gboolean dnd_motion_moved(GdkEventMotion *event)
1381 {
1382 	gint	drag_threshold;
1383 	int	dx, dy;
1384 
1385 	g_object_get(gtk_settings_get_default(),
1386 		"gtk-dnd-drag-threshold", &drag_threshold,
1387 		NULL);
1388 
1389 	dx = event->x_root - drag_start_x;
1390 	dy = event->y_root - drag_start_y;
1391 
1392 	if (ABS(dx) <= drag_threshold && ABS(dy) <= drag_threshold)
1393 		return FALSE;		/* Not far enough */
1394 
1395 	dnd_motion_ungrab();
1396 
1397 	return TRUE;
1398 }
1399 
1400 /* Normally, the X server will automatically grab the pointer on a
1401  * button press and ungrab on release. However, if the grab widget
1402  * is reparented then call this to re-aquire the grab.
1403  */
dnd_motion_grab_pointer(void)1404 void dnd_motion_grab_pointer(void)
1405 {
1406 	g_return_if_fail(motion_widget != NULL);
1407 
1408 	gdk_pointer_grab(motion_widget->window, FALSE,
1409 			GDK_POINTER_MOTION_MASK |
1410 			GDK_BUTTON_RELEASE_MASK,
1411 			FALSE, NULL, GDK_CURRENT_TIME);
1412 
1413 	motion_pointer_grab = TRUE;
1414 }
1415