r38: Rescanning a directory only shrinks the item width at the end rather than the
[rox-filer.git] / ROX-Filer / src / dnd.c
blob56de13ccbe067868d728043b84bebe79f55dece5
1 /* vi: set cindent:
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
6 */
8 /* dnd.c - code for handling drag and drop */
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <unistd.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <errno.h>
18 #include <X11/Xlib.h>
19 #include <X11/Xatom.h>
20 #include <gtk/gtk.h>
21 #include <collection.h>
23 #include "filer.h"
24 #include "pixmaps.h"
25 #include "gui_support.h"
26 #include "support.h"
28 #define MAXURILEN 4096 /* Longest URI to allow */
30 enum
32 TARGET_RAW,
33 TARGET_URI_LIST,
34 TARGET_XDS,
37 GdkAtom XdndDirectSave0;
38 GdkAtom text_plain;
39 GdkAtom text_uri_list;
40 GdkAtom application_octet_stream;
42 /* Static prototypes */
43 static char *get_dest_path(FilerWindow *filer_window, GdkDragContext *context);
44 static void create_uri_list(GString *string,
45 Collection *collection,
46 FilerWindow *filer_window);
47 static gboolean drag_drop(GtkWidget *widget,
48 GdkDragContext *context,
49 gint x,
50 gint y,
51 guint time);
52 static gboolean provides(GdkDragContext *context, GdkAtom target);
53 static void set_xds_prop(GdkDragContext *context, char *text);
54 static gboolean drag_motion(GtkWidget *widget,
55 GdkDragContext *context,
56 gint x,
57 gint y,
58 guint time);
59 static void drag_leave(GtkWidget *widget,
60 GdkDragContext *context);
61 static void drag_data_received(GtkWidget *widget,
62 GdkDragContext *context,
63 gint x,
64 gint y,
65 GtkSelectionData *selection_data,
66 guint info,
67 guint32 time);
68 static void got_data_xds_reply(GtkWidget *widget,
69 GdkDragContext *context,
70 GtkSelectionData *selection_data,
71 guint32 time);
72 static void got_data_raw(GtkWidget *widget,
73 GdkDragContext *context,
74 GtkSelectionData *selection_data,
75 guint32 time);
76 static gboolean load_file(char *pathname, char **data_out, long *length_out);
77 static GSList *uri_list_to_gslist(char *uri_list);
78 static void got_uri_list(GtkWidget *widget,
79 GdkDragContext *context,
80 GtkSelectionData *selection_data,
81 guint32 time);
82 char *get_local_path(char *uri);
84 void dnd_init()
86 XdndDirectSave0 = gdk_atom_intern("XdndDirectSave0", FALSE);
87 text_plain = gdk_atom_intern("text/plain", FALSE);
88 text_uri_list = gdk_atom_intern("text/uri-list", FALSE);
89 application_octet_stream = gdk_atom_intern("application/octet-stream",
90 FALSE);
93 static char *get_dest_path(FilerWindow *filer_window, GdkDragContext *context)
95 char *dest_path;
97 dest_path = g_dataset_get_data(context, "drop_dest_path");
99 return dest_path ? dest_path : filer_window->path;
102 /* Set the XdndDirectSave0 property on the source window for this context */
103 static void set_xds_prop(GdkDragContext *context, char *text)
105 gdk_property_change(context->source_window,
106 XdndDirectSave0,
107 text_plain, 8,
108 GDK_PROP_MODE_REPLACE,
109 text,
110 strlen(text));
113 static char *get_xds_prop(GdkDragContext *context)
115 guchar *prop_text;
116 gint length;
118 if (gdk_property_get(context->source_window,
119 XdndDirectSave0,
120 text_plain,
121 0, MAXURILEN,
122 FALSE,
123 NULL, NULL,
124 &length, &prop_text) && prop_text)
126 /* Terminate the string */
127 prop_text = g_realloc(prop_text, length + 1);
128 prop_text[length] = '\0';
129 return prop_text;
132 return NULL;
135 /* Is the sender willing to supply this target type? */
136 static gboolean provides(GdkDragContext *context, GdkAtom target)
138 GList *targets = context->targets;
140 while (targets && ((GdkAtom) targets->data != target))
141 targets = targets->next;
143 return targets != NULL;
146 /* Convert a URI to a local pathname (or NULL if it isn't local).
147 * The returned pointer points inside the input string.
148 * Possible formats:
149 * /path
150 * ///path
151 * //host/path
152 * file://host/path
154 char *get_local_path(char *uri)
156 char *host;
158 host = our_host_name();
160 if (*uri == '/')
162 char *path;
164 if (uri[1] != '/')
165 return uri; /* Just a local path - no host part */
167 path = strchr(uri + 2, '/');
168 if (!path)
169 return NULL; /* //something */
171 if (path - uri == 2)
172 return path; /* ///path */
173 if (strlen(host) == path - uri - 2 &&
174 strncmp(uri + 2, host, path - uri - 2) == 0)
175 return path; /* //myhost/path */
177 return NULL; /* From a different host */
179 else
181 if (strncasecmp(uri, "file:", 5))
182 return NULL; /* Don't know this format */
184 uri += 5;
186 if (*uri == '/')
187 return get_local_path(uri);
189 return NULL;
193 /* Convert a list of URIs into a list of strings.
194 * Lines beginning with # are skipped.
195 * The text block passed in is zero terminated (after the final CRLF)
197 static GSList *uri_list_to_gslist(char *uri_list)
199 GSList *list = NULL;
201 while (*uri_list)
203 char *linebreak;
204 char *uri;
205 int length;
207 linebreak = strchr(uri_list, 13);
209 if (!linebreak || linebreak[1] != 10)
211 delayed_error("uri_list_to_gslist",
212 "Incorrect or missing line break "
213 "in text/uri-list data");
214 return list;
217 length = linebreak - uri_list;
219 if (length && uri_list[0] != '#')
221 uri = g_malloc(sizeof(char) * (length + 1));
222 strncpy(uri, uri_list, length);
223 uri[length] = 0;
224 list = g_slist_append(list, uri);
227 uri_list = linebreak + 2;
230 return list;
233 /* Append all the URIs in the selection to the string */
234 static void create_uri_list(GString *string,
235 Collection *collection,
236 FilerWindow *filer_window)
238 GString *leader;
239 int i, num_selected;
241 leader = g_string_new("file://");
242 g_string_append(leader, our_host_name());
243 g_string_append(leader, filer_window->path);
244 if (leader->str[leader->len - 1] != '/')
245 g_string_append_c(leader, '/');
247 num_selected = collection->number_selected;
249 for (i = 0; num_selected > 0; i++)
251 if (collection->items[i].selected)
253 FileItem *item = (FileItem *) collection->items[i].data;
255 g_string_append(string, leader->str);
256 g_string_append(string, item->leafname);
257 g_string_append(string, "\r\n");
258 num_selected--;
262 g_string_free(leader, TRUE);
265 /* DRAGGING FROM US */
267 /* The user has held the mouse button down over an item and moved -
268 * start a drag.
270 * We always provide text/uri-list. If we are dragging a single, regular file
271 * then we also offer application/octet-stream.
273 void drag_selection(Collection *collection,
274 GdkEventMotion *event,
275 gint number_selected,
276 gpointer user_data)
278 FilerWindow *filer_window = (FilerWindow *) user_data;
279 GtkWidget *widget;
280 MaskedPixmap *image;
281 GdkDragContext *context;
282 GtkTargetList *target_list;
283 GtkTargetEntry target_table[] =
285 {"text/uri-list", 0, TARGET_URI_LIST},
286 {"application/octet-stream", 0, TARGET_RAW},
288 FileItem *item;
290 if (number_selected == 1)
291 item = selected_item(collection);
292 else
293 item = NULL;
295 widget = GTK_WIDGET(collection);
297 target_list = gtk_target_list_new(target_table,
298 item && item->base_type == TYPE_FILE ? 2 : 1);
300 context = gtk_drag_begin(widget,
301 target_list,
302 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK,
303 (event->state & GDK_BUTTON1_MASK) ? 1 : 2,
304 (GdkEvent *) event);
305 g_dataset_set_data(context, "filer_window", filer_window);
307 image = item ? item->image : &default_pixmap[TYPE_MULTIPLE];
309 gtk_drag_set_icon_pixmap(context,
310 gtk_widget_get_colormap(widget),
311 image->pixmap,
312 image->mask,
313 0, 0);
316 /* Called when a remote app wants us to send it some data.
317 * TODO: Maybe we should handle errors better (ie, let the remote app know
318 * the drag has failed)?
320 void drag_data_get(GtkWidget *widget,
321 GdkDragContext *context,
322 GtkSelectionData *selection_data,
323 guint info,
324 guint32 time)
326 char *to_send = "E"; /* Default to sending an error */
327 long to_send_length = 1;
328 gboolean delete_once_sent = FALSE;
329 GdkAtom type = XA_STRING;
330 GString *string;
331 FilerWindow *filer_window;
332 FileItem *item;
334 filer_window = g_dataset_get_data(context, "filer_window");
335 g_return_if_fail(filer_window != NULL);
337 switch (info)
339 case TARGET_RAW:
340 item = selected_item(filer_window->collection);
341 if (item && load_file(make_path(filer_window->path,
342 item->leafname)->str,
343 &to_send, &to_send_length))
345 delete_once_sent = TRUE;
346 type = application_octet_stream; /* XXX */
347 break;
349 return;
350 case TARGET_URI_LIST:
351 string = g_string_new(NULL);
352 create_uri_list(string,
353 COLLECTION(widget),
354 filer_window);
355 to_send = string->str;
356 to_send_length = string->len;
357 delete_once_sent = TRUE;
358 g_string_free(string, FALSE);
359 break;
360 default:
361 delayed_error("drag_data_get",
362 "Internal error - bad info type\n");
363 break;
366 gtk_selection_data_set(selection_data,
367 type,
369 to_send,
370 to_send_length);
372 if (delete_once_sent)
373 g_free(to_send);
376 /* Load the file into memory. Return TRUE on success. */
377 static gboolean load_file(char *pathname, char **data_out, long *length_out)
379 FILE *file;
380 long length;
381 char *buffer;
382 gboolean retval = FALSE;
384 file = fopen(pathname, "r");
386 if (!file)
388 delayed_error("Opening file for DND", g_strerror(errno));
389 return FALSE;
392 fseek(file, 0, SEEK_END);
393 length = ftell(file);
395 buffer = malloc(length);
396 if (buffer)
398 fseek(file, 0, SEEK_SET);
399 fread(buffer, 1, length, file);
401 if (ferror(file))
403 delayed_error("Loading file for DND",
404 g_strerror(errno));
405 g_free(buffer);
407 else
409 *data_out = buffer;
410 *length_out = length;
411 retval = TRUE;
414 else
415 delayed_error("Loading file for DND",
416 "Can't allocate memory for buffer to "
417 "transfer this file");
419 fclose(file);
421 return retval;
424 /* DRAGGING TO US */
426 /* Set up this filer window as a drop target. Called once, when the
427 * filer window is first created.
429 void drag_set_dest(GtkWidget *widget, FilerWindow *filer_window)
431 GtkTargetEntry target_table[] =
433 {"text/uri-list", 0, TARGET_URI_LIST},
434 {"XdndDirectSave0", 0, TARGET_XDS},
435 {"application/octet-stream", 0, TARGET_RAW},
438 gtk_drag_dest_set(widget,
439 0, /* GTK_DEST_DEFAULT_MOTION, */
440 target_table,
441 sizeof(target_table) / sizeof(*target_table),
442 GDK_ACTION_COPY | GDK_ACTION_MOVE
443 | GDK_ACTION_LINK | GDK_ACTION_PRIVATE);
445 gtk_signal_connect(GTK_OBJECT(widget), "drag_motion",
446 GTK_SIGNAL_FUNC(drag_motion), filer_window);
447 gtk_signal_connect(GTK_OBJECT(widget), "drag_leave",
448 GTK_SIGNAL_FUNC(drag_leave), filer_window);
449 gtk_signal_connect(GTK_OBJECT(widget), "drag_drop",
450 GTK_SIGNAL_FUNC(drag_drop), filer_window);
451 gtk_signal_connect(GTK_OBJECT(widget), "drag_data_received",
452 GTK_SIGNAL_FUNC(drag_data_received), filer_window);
455 /* Called during the drag when the mouse is in a widget registered
456 * as a drop target. Returns TRUE if we can accept the drop.
458 static gboolean drag_motion(GtkWidget *widget,
459 GdkDragContext *context,
460 gint x,
461 gint y,
462 guint time)
464 FilerWindow *filer_window;
465 int item;
467 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
468 g_return_val_if_fail(filer_window != NULL, TRUE);
470 if (gtk_drag_get_source_widget(context) == widget)
472 /* Ignore drags within a single window */
473 return FALSE;
476 if (filer_window->panel == FALSE)
478 gdk_drag_status(context, context->suggested_action, time);
479 return TRUE;
482 item = collection_get_item(filer_window->collection, x, y);
484 if (item != filer_window->collection->cursor_item && item != -1)
486 FileItem *fileitem = (FileItem *)
487 filer_window->collection->items[item].data;
489 panel_set_timeout(NULL, 0);
491 g_dataset_set_data_full(context, "drop_dest_path",
492 g_strdup(make_path(filer_window->path,
493 fileitem->leafname)->str),
494 g_free);
496 collection_set_cursor_item(filer_window->collection, item);
498 gdk_drag_status(context, context->suggested_action, time);
499 return TRUE;
502 /* Remove panel highlights */
503 static void drag_leave(GtkWidget *widget,
504 GdkDragContext *context)
506 FilerWindow *filer_window;
508 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
509 g_return_if_fail(filer_window != NULL);
511 panel_set_timeout(NULL, 0);
512 collection_set_cursor_item(filer_window->collection, -1);
515 /* User has tried to drop some data on us. Decide what format we would
516 * like the data in.
518 static gboolean drag_drop(GtkWidget *widget,
519 GdkDragContext *context,
520 gint x,
521 gint y,
522 guint time)
524 char *error = NULL;
525 char *leafname = NULL;
526 FilerWindow *filer_window;
527 GdkAtom target;
528 char *dest_path;
530 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
531 g_return_val_if_fail(filer_window != NULL, TRUE);
533 if (gtk_drag_get_source_widget(context) == widget)
535 /* Ignore drags within a single window */
536 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
537 return TRUE;
540 dest_path = g_dataset_get_data(context, "drop_dest_path");
542 if (((!dest_path) && filer_window->panel)
543 || !((dest_path = filer_window->path)))
545 error = "Bad drop on panel";
547 else if (provides(context, XdndDirectSave0))
549 leafname = get_xds_prop(context);
550 if (leafname)
552 if (strchr(leafname, '/'))
554 error = "XDS protocol error: "
555 "leafname may not contain '/'\n";
556 g_free(leafname);
558 leafname = NULL;
560 else
562 GString *uri;
564 uri = g_string_new(NULL);
565 g_string_sprintf(uri, "file://%s%s",
566 our_host_name(),
567 make_path(dest_path,
568 leafname)->str);
569 set_xds_prop(context, uri->str);
570 g_string_free(uri, TRUE);
572 target = XdndDirectSave0;
573 g_dataset_set_data_full(context, "leafname",
574 leafname, g_free);
577 else
578 error = "XdndDirectSave0 target provided, but the atom "
579 "XdndDirectSave0 (type text/plain) did not "
580 "contain a leafname\n";
582 else if (provides(context, text_uri_list))
583 target = text_uri_list;
584 else
585 error = "Sorry - I require a target type of text/uri-list or "
586 "XdndDirectSave0.";
588 if (error)
590 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
592 delayed_error("ROX-Filer", error);
594 else
595 gtk_drag_get_data(widget, context, target, time);
597 return TRUE;
600 /* Called when some data arrives from the remote app (which we asked for
601 * in drag_drop).
603 static void drag_data_received(GtkWidget *widget,
604 GdkDragContext *context,
605 gint x,
606 gint y,
607 GtkSelectionData *selection_data,
608 guint info,
609 guint32 time)
611 if (!selection_data->data)
613 /* Timeout? */
614 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
615 return;
618 switch (info)
620 case TARGET_XDS:
621 got_data_xds_reply(widget, context,
622 selection_data, time);
623 break;
624 case TARGET_RAW:
625 got_data_raw(widget, context, selection_data, time);
626 break;
627 case TARGET_URI_LIST:
628 got_uri_list(widget, context, selection_data, time);
629 break;
630 default:
631 gtk_drag_finish(context, FALSE, FALSE, time);
632 delayed_error("drag_data_received", "Unknown target");
633 break;
637 static void got_data_xds_reply(GtkWidget *widget,
638 GdkDragContext *context,
639 GtkSelectionData *selection_data,
640 guint32 time)
642 gboolean mark_unsafe = TRUE;
643 char response = *selection_data->data;
644 char *error = NULL;
646 if (selection_data->length != 1)
647 response = '?';
649 if (response == 'F')
651 /* Sender couldn't save there - ask for another
652 * type if possible.
654 if (provides(context, application_octet_stream))
656 mark_unsafe = FALSE; /* Wait and see */
658 gtk_drag_get_data(widget, context,
659 application_octet_stream, time);
661 else
662 error = "Remote app can't or won't send me "
663 "the data - sorry";
665 else if (response == 'S')
667 FilerWindow *filer_window;
669 /* Success - data is saved */
670 mark_unsafe = FALSE; /* It really is safe */
671 gtk_drag_finish(context, TRUE, FALSE, time);
673 filer_window = gtk_object_get_data(GTK_OBJECT(widget),
674 "filer_window");
675 g_return_if_fail(filer_window != NULL);
677 scan_dir(filer_window);
679 else if (response != 'E')
681 error = "XDS protocol error: "
682 "return code should be 'S', 'F' or 'E'\n";
684 /* else: error has been reported by the sender */
686 if (mark_unsafe)
688 set_xds_prop(context, "");
689 /* Unsave also implies that the drag failed */
690 gtk_drag_finish(context, FALSE, FALSE, time);
693 if (error)
694 delayed_error("ROX-Filer", error);
697 static void got_data_raw(GtkWidget *widget,
698 GdkDragContext *context,
699 GtkSelectionData *selection_data,
700 guint32 time)
702 FilerWindow *filer_window;
703 char *leafname;
704 int fd;
705 char *error = NULL;
706 char *dest_path;
708 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
709 g_return_if_fail(filer_window != NULL);
711 leafname = g_dataset_get_data(context, "leafname");
712 if (!leafname)
713 leafname = "UntitledData";
715 dest_path = get_dest_path(filer_window, context);
717 fd = open(make_path(dest_path, leafname)->str,
718 O_WRONLY | O_CREAT | O_EXCL | O_NOCTTY,
719 S_IRUSR | S_IRGRP | S_IROTH |
720 S_IWUSR | S_IWGRP | S_IWOTH);
722 if (fd == -1)
723 error = g_strerror(errno);
724 else
726 if (write(fd,
727 selection_data->data,
728 selection_data->length) == -1)
729 error = g_strerror(errno);
731 if (close(fd) == -1 && !error)
732 error = g_strerror(errno);
734 scan_dir(filer_window);
737 if (error)
739 if (provides(context, XdndDirectSave0))
740 set_xds_prop(context, "");
741 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
742 delayed_error("Error saving file", error);
744 else
745 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
748 /* We've got a list of URIs from somewhere (probably another filer).
749 * If the files are on the local machine then try to copy them ourselves,
750 * otherwise, if there was only one file and application/octet-stream was
751 * provided, get the data via the X server.
753 static void got_uri_list(GtkWidget *widget,
754 GdkDragContext *context,
755 GtkSelectionData *selection_data,
756 guint32 time)
758 FilerWindow *filer_window;
759 GSList *uri_list;
760 char *error = NULL;
761 char **argv = NULL; /* Command to exec, or NULL */
762 GSList *next_uri;
763 gboolean send_reply = TRUE;
764 char *dest_path;
766 filer_window = gtk_object_get_data(GTK_OBJECT(widget), "filer_window");
767 g_return_if_fail(filer_window != NULL);
769 dest_path = get_dest_path(filer_window, context);
771 uri_list = uri_list_to_gslist(selection_data->data);
773 if (!uri_list)
774 error = "No URIs in the text/uri-list (nothing to do!)";
775 else if ((!uri_list->next) && (!get_local_path(uri_list->data)))
777 /* There is one URI in the list, and it's not on the local
778 * machine. Get it via the X server if possible.
781 if (provides(context, application_octet_stream))
783 char *leaf;
784 leaf = strrchr(uri_list->data, '/');
785 if (leaf)
786 leaf++;
787 else
788 leaf = uri_list->data;
789 g_dataset_set_data_full(context, "leafname",
790 g_strdup(leaf), g_free);
791 gtk_drag_get_data(widget, context,
792 application_octet_stream, time);
793 send_reply = FALSE;
795 else
796 error = "Can't get data from remote machine "
797 "(application/octet-stream not provided)";
799 else
801 int local_files = 0;
802 const char *start_args[] = {"xterm", "-wf", "-e"};
803 int argc = sizeof(start_args) / sizeof(char *);
805 next_uri = uri_list;
807 argv = g_malloc(sizeof(start_args) +
808 sizeof(char *) * (g_slist_length(uri_list) + 4));
809 memcpy(argv, start_args, sizeof(start_args));
811 if (context->action == GDK_ACTION_MOVE)
813 argv[argc++] = "mv";
814 argv[argc++] = "-iv";
816 else if (context->action == GDK_ACTION_LINK)
818 argv[argc++] = "ln";
819 argv[argc++] = "-vis";
821 else
823 argv[argc++] = "cp";
824 argv[argc++] = "-Riva";
827 /* Either one local URI, or a list. If anything in the list
828 * isn't local then we are stuck.
831 while (next_uri)
833 char *path;
835 path = get_local_path((char *) next_uri->data);
837 if (path)
839 argv[argc++] = path;
840 local_files++;
842 else
843 error = "Some of these files are on a "
844 "different machine - they will be "
845 "ignored - sorry";
847 next_uri = next_uri->next;
850 if (local_files < 1)
852 error = "None of these files are on the local machine "
853 "- I can't operate on multiple remote files - "
854 "sorry.";
855 g_free(argv);
856 argv = NULL;
858 else
860 argv[argc++] = dest_path;
861 argv[argc++] = NULL;
865 if (error)
867 gtk_drag_finish(context, FALSE, FALSE, time); /* Failure */
868 delayed_error("Error getting file list", error);
870 else if (send_reply)
872 gtk_drag_finish(context, TRUE, FALSE, time); /* Success! */
873 if (argv)
875 int child; /* Child process ID */
877 child = spawn(argv);
878 if (child)
879 g_hash_table_insert(child_to_filer,
880 (gpointer) child, filer_window);
881 else
882 delayed_error("ROX-Filer",
883 "Failed to fork() child process");
884 g_free(argv);
888 next_uri = uri_list;
889 while (next_uri)
891 g_free(next_uri->data);
892 next_uri = next_uri->next;
894 g_slist_free(uri_list);