4 * ROX-Filer, filer for the ROX desktop project
5 * By Thomas Leonard, <tal197@ecs.soton.ac.uk>.
8 /* dnd.c - code for handling drag and drop */
19 #include <X11/Xatom.h>
21 #include <collection.h>
25 #include "gui_support.h"
28 #define MAXURILEN 4096 /* Longest URI to allow */
37 GdkAtom XdndDirectSave0
;
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 FileItem
*selected_item(Collection
*collection
);
48 static gboolean
drag_drop(GtkWidget
*widget
,
49 GdkDragContext
*context
,
53 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
);
54 static void set_xds_prop(GdkDragContext
*context
, char *text
);
55 static gboolean
drag_motion(GtkWidget
*widget
,
56 GdkDragContext
*context
,
60 static void drag_leave(GtkWidget
*widget
,
61 GdkDragContext
*context
);
62 static void drag_data_received(GtkWidget
*widget
,
63 GdkDragContext
*context
,
66 GtkSelectionData
*selection_data
,
69 static void got_data_xds_reply(GtkWidget
*widget
,
70 GdkDragContext
*context
,
71 GtkSelectionData
*selection_data
,
73 static void got_data_raw(GtkWidget
*widget
,
74 GdkDragContext
*context
,
75 GtkSelectionData
*selection_data
,
77 static gboolean
load_file(char *pathname
, char **data_out
, long *length_out
);
78 static GSList
*uri_list_to_gslist(char *uri_list
);
79 static void got_uri_list(GtkWidget
*widget
,
80 GdkDragContext
*context
,
81 GtkSelectionData
*selection_data
,
83 char *get_local_path(char *uri
);
87 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
88 text_plain
= gdk_atom_intern("text/plain", FALSE
);
89 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
90 application_octet_stream
= gdk_atom_intern("application/octet-stream",
94 static char *get_dest_path(FilerWindow
*filer_window
, GdkDragContext
*context
)
98 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
100 return dest_path
? dest_path
: filer_window
->path
;
103 /* Set the XdndDirectSave0 property on the source window for this context */
104 static void set_xds_prop(GdkDragContext
*context
, char *text
)
106 gdk_property_change(context
->source_window
,
109 GDK_PROP_MODE_REPLACE
,
114 static char *get_xds_prop(GdkDragContext
*context
)
119 if (gdk_property_get(context
->source_window
,
125 &length
, &prop_text
) && prop_text
)
127 /* Terminate the string */
128 prop_text
= g_realloc(prop_text
, length
+ 1);
129 prop_text
[length
] = '\0';
136 /* Is the sender willing to supply this target type? */
137 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
139 GList
*targets
= context
->targets
;
141 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
142 targets
= targets
->next
;
144 return targets
!= NULL
;
147 /* Convert a URI to a local pathname (or NULL if it isn't local).
148 * The returned pointer points inside the input string.
155 char *get_local_path(char *uri
)
159 host
= our_host_name();
166 return uri
; /* Just a local path - no host part */
168 path
= strchr(uri
+ 2, '/');
170 return NULL
; /* //something */
173 return path
; /* ///path */
174 if (strlen(host
) == path
- uri
- 2 &&
175 strncmp(uri
+ 2, host
, path
- uri
- 2) == 0)
176 return path
; /* //myhost/path */
178 return NULL
; /* From a different host */
182 if (strncasecmp(uri
, "file:", 5))
183 return NULL
; /* Don't know this format */
188 return get_local_path(uri
);
194 /* Convert a list of URIs into a list of strings.
195 * Lines beginning with # are skipped.
196 * The text block passed in is zero terminated (after the final CRLF)
198 static GSList
*uri_list_to_gslist(char *uri_list
)
208 linebreak
= strchr(uri_list
, 13);
210 if (!linebreak
|| linebreak
[1] != 10)
212 delayed_error("uri_list_to_gslist",
213 "Incorrect or missing line break "
214 "in text/uri-list data");
218 length
= linebreak
- uri_list
;
220 if (length
&& uri_list
[0] != '#')
222 uri
= g_malloc(sizeof(char) * (length
+ 1));
223 strncpy(uri
, uri_list
, length
);
225 list
= g_slist_append(list
, uri
);
228 uri_list
= linebreak
+ 2;
234 /* Append all the URIs in the selection to the string */
235 static void create_uri_list(GString
*string
,
236 Collection
*collection
,
237 FilerWindow
*filer_window
)
242 leader
= g_string_new("file://");
243 g_string_append(leader
, our_host_name());
244 g_string_append(leader
, filer_window
->path
);
245 if (leader
->str
[leader
->len
- 1] != '/')
246 g_string_append_c(leader
, '/');
248 num_selected
= collection
->number_selected
;
250 for (i
= 0; num_selected
> 0; i
++)
252 if (collection
->items
[i
].selected
)
254 FileItem
*item
= (FileItem
*) collection
->items
[i
].data
;
256 g_string_append(string
, leader
->str
);
257 g_string_append(string
, item
->leafname
);
258 g_string_append(string
, "\r\n");
263 g_string_free(leader
, TRUE
);
266 static FileItem
*selected_item(Collection
*collection
)
270 g_return_val_if_fail(collection
!= NULL
, NULL
);
271 g_return_val_if_fail(IS_COLLECTION(collection
), NULL
);
272 g_return_val_if_fail(collection
->number_selected
== 1, NULL
);
274 for (i
= 0; i
< collection
->number_of_items
; i
++)
275 if (collection
->items
[i
].selected
)
276 return (FileItem
*) collection
->items
[i
].data
;
278 g_warning("selected_item: number_selected is wrong\n");
283 /* DRAGGING FROM US */
285 /* The user has held the mouse button down over an item and moved -
288 * We always provide text/uri-list. If we are dragging a single, regular file
289 * then we also offer application/octet-stream.
291 void drag_selection(Collection
*collection
,
292 GdkEventMotion
*event
,
293 gint number_selected
,
296 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
299 GdkDragContext
*context
;
300 GtkTargetList
*target_list
;
301 GtkTargetEntry target_table
[] =
303 {"text/uri-list", 0, TARGET_URI_LIST
},
304 {"application/octet-stream", 0, TARGET_RAW
},
308 if (number_selected
== 1)
309 item
= selected_item(collection
);
313 widget
= GTK_WIDGET(collection
);
315 target_list
= gtk_target_list_new(target_table
,
316 item
&& item
->base_type
== TYPE_FILE
? 2 : 1);
318 context
= gtk_drag_begin(widget
,
320 GDK_ACTION_COPY
| GDK_ACTION_MOVE
| GDK_ACTION_LINK
,
321 (event
->state
& GDK_BUTTON1_MASK
) ? 1 : 2,
323 g_dataset_set_data(context
, "filer_window", filer_window
);
325 image
= item
? item
->image
: &default_pixmap
[TYPE_MULTIPLE
];
327 gtk_drag_set_icon_pixmap(context
,
328 gtk_widget_get_colormap(widget
),
334 /* Called when a remote app wants us to send it some data.
335 * TODO: Maybe we should handle errors better (ie, let the remote app know
336 * the drag has failed)?
338 void drag_data_get(GtkWidget
*widget
,
339 GdkDragContext
*context
,
340 GtkSelectionData
*selection_data
,
344 char *to_send
= "E"; /* Default to sending an error */
345 long to_send_length
= 1;
346 gboolean delete_once_sent
= FALSE
;
347 GdkAtom type
= XA_STRING
;
349 FilerWindow
*filer_window
;
352 filer_window
= g_dataset_get_data(context
, "filer_window");
353 g_return_if_fail(filer_window
!= NULL
);
358 item
= selected_item(filer_window
->collection
);
359 if (item
&& load_file(make_path(filer_window
->path
,
360 item
->leafname
)->str
,
361 &to_send
, &to_send_length
))
363 delete_once_sent
= TRUE
;
364 type
= application_octet_stream
; /* XXX */
368 case TARGET_URI_LIST
:
369 string
= g_string_new(NULL
);
370 create_uri_list(string
,
373 to_send
= string
->str
;
374 to_send_length
= string
->len
;
375 delete_once_sent
= TRUE
;
376 g_string_free(string
, FALSE
);
379 delayed_error("drag_data_get",
380 "Internal error - bad info type\n");
384 gtk_selection_data_set(selection_data
,
390 if (delete_once_sent
)
394 /* Load the file into memory. Return TRUE on success. */
395 static gboolean
load_file(char *pathname
, char **data_out
, long *length_out
)
400 gboolean retval
= FALSE
;
402 file
= fopen(pathname
, "r");
406 delayed_error("Opening file for DND", g_strerror(errno
));
410 fseek(file
, 0, SEEK_END
);
411 length
= ftell(file
);
413 buffer
= malloc(length
);
416 fseek(file
, 0, SEEK_SET
);
417 fread(buffer
, 1, length
, file
);
421 delayed_error("Loading file for DND",
428 *length_out
= length
;
433 delayed_error("Loading file for DND",
434 "Can't allocate memory for buffer to "
435 "transfer this file");
444 /* Set up this filer window as a drop target. Called once, when the
445 * filer window is first created.
447 void drag_set_dest(GtkWidget
*widget
, FilerWindow
*filer_window
)
449 GtkTargetEntry target_table
[] =
451 {"text/uri-list", 0, TARGET_URI_LIST
},
452 {"XdndDirectSave0", 0, TARGET_XDS
},
453 {"application/octet-stream", 0, TARGET_RAW
},
456 gtk_drag_dest_set(widget
,
457 0, /* GTK_DEST_DEFAULT_MOTION, */
459 sizeof(target_table
) / sizeof(*target_table
),
460 GDK_ACTION_COPY
| GDK_ACTION_MOVE
461 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
463 gtk_signal_connect(GTK_OBJECT(widget
), "drag_motion",
464 GTK_SIGNAL_FUNC(drag_motion
), filer_window
);
465 gtk_signal_connect(GTK_OBJECT(widget
), "drag_leave",
466 GTK_SIGNAL_FUNC(drag_leave
), filer_window
);
467 gtk_signal_connect(GTK_OBJECT(widget
), "drag_drop",
468 GTK_SIGNAL_FUNC(drag_drop
), filer_window
);
469 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
470 GTK_SIGNAL_FUNC(drag_data_received
), filer_window
);
473 /* Called during the drag when the mouse is in a widget registered
474 * as a drop target. Returns TRUE if we can accept the drop.
476 static gboolean
drag_motion(GtkWidget
*widget
,
477 GdkDragContext
*context
,
482 FilerWindow
*filer_window
;
485 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
486 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
488 if (gtk_drag_get_source_widget(context
) == widget
)
490 /* Ignore drags within a single window */
494 if (filer_window
->panel
== FALSE
)
496 gdk_drag_status(context
, context
->suggested_action
, time
);
500 item
= collection_get_item(filer_window
->collection
, x
, y
);
502 if (item
!= filer_window
->collection
->cursor_item
&& item
!= -1)
504 FileItem
*fileitem
= (FileItem
*)
505 filer_window
->collection
->items
[item
].data
;
507 panel_set_timeout(NULL
, 0);
509 g_dataset_set_data_full(context
, "drop_dest_path",
510 g_strdup(make_path(filer_window
->path
,
511 fileitem
->leafname
)->str
),
514 collection_set_cursor_item(filer_window
->collection
, item
);
516 gdk_drag_status(context
, context
->suggested_action
, time
);
520 /* Remove panel highlights */
521 static void drag_leave(GtkWidget
*widget
,
522 GdkDragContext
*context
)
524 FilerWindow
*filer_window
;
526 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
527 g_return_if_fail(filer_window
!= NULL
);
529 panel_set_timeout(NULL
, 0);
530 collection_set_cursor_item(filer_window
->collection
, -1);
533 /* User has tried to drop some data on us. Decide what format we would
536 static gboolean
drag_drop(GtkWidget
*widget
,
537 GdkDragContext
*context
,
543 char *leafname
= NULL
;
544 FilerWindow
*filer_window
;
548 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
549 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
551 if (gtk_drag_get_source_widget(context
) == widget
)
553 /* Ignore drags within a single window */
554 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
558 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
560 if (((!dest_path
) && filer_window
->panel
)
561 || !((dest_path
= filer_window
->path
)))
563 error
= "Bad drop on panel";
565 else if (provides(context
, XdndDirectSave0
))
567 leafname
= get_xds_prop(context
);
570 if (strchr(leafname
, '/'))
572 error
= "XDS protocol error: "
573 "leafname may not contain '/'\n";
582 uri
= g_string_new(NULL
);
583 g_string_sprintf(uri
, "file://%s%s",
587 set_xds_prop(context
, uri
->str
);
588 g_string_free(uri
, TRUE
);
590 target
= XdndDirectSave0
;
591 g_dataset_set_data_full(context
, "leafname",
596 error
= "XdndDirectSave0 target provided, but the atom "
597 "XdndDirectSave0 (type text/plain) did not "
598 "contain a leafname\n";
600 else if (provides(context
, text_uri_list
))
601 target
= text_uri_list
;
603 error
= "Sorry - I require a target type of text/uri-list or "
608 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
610 delayed_error("ROX-Filer", error
);
613 gtk_drag_get_data(widget
, context
, target
, time
);
618 /* Called when some data arrives from the remote app (which we asked for
621 static void drag_data_received(GtkWidget
*widget
,
622 GdkDragContext
*context
,
625 GtkSelectionData
*selection_data
,
629 if (!selection_data
->data
)
632 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
639 got_data_xds_reply(widget
, context
,
640 selection_data
, time
);
643 got_data_raw(widget
, context
, selection_data
, time
);
645 case TARGET_URI_LIST
:
646 got_uri_list(widget
, context
, selection_data
, time
);
649 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
650 delayed_error("drag_data_received", "Unknown target");
655 static void got_data_xds_reply(GtkWidget
*widget
,
656 GdkDragContext
*context
,
657 GtkSelectionData
*selection_data
,
660 gboolean mark_unsafe
= TRUE
;
661 char response
= *selection_data
->data
;
664 if (selection_data
->length
!= 1)
669 /* Sender couldn't save there - ask for another
672 if (provides(context
, application_octet_stream
))
674 mark_unsafe
= FALSE
; /* Wait and see */
676 gtk_drag_get_data(widget
, context
,
677 application_octet_stream
, time
);
680 error
= "Remote app can't or won't send me "
683 else if (response
== 'S')
685 FilerWindow
*filer_window
;
687 /* Success - data is saved */
688 mark_unsafe
= FALSE
; /* It really is safe */
689 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
691 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
),
693 g_return_if_fail(filer_window
!= NULL
);
695 scan_dir(filer_window
);
697 else if (response
!= 'E')
699 error
= "XDS protocol error: "
700 "return code should be 'S', 'F' or 'E'\n";
702 /* else: error has been reported by the sender */
706 set_xds_prop(context
, "");
707 /* Unsave also implies that the drag failed */
708 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
712 delayed_error("ROX-Filer", error
);
715 static void got_data_raw(GtkWidget
*widget
,
716 GdkDragContext
*context
,
717 GtkSelectionData
*selection_data
,
720 FilerWindow
*filer_window
;
726 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
727 g_return_if_fail(filer_window
!= NULL
);
729 leafname
= g_dataset_get_data(context
, "leafname");
731 leafname
= "UntitledData";
733 dest_path
= get_dest_path(filer_window
, context
);
735 fd
= open(make_path(dest_path
, leafname
)->str
,
736 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
737 S_IRUSR
| S_IRGRP
| S_IROTH
|
738 S_IWUSR
| S_IWGRP
| S_IWOTH
);
741 error
= g_strerror(errno
);
745 selection_data
->data
,
746 selection_data
->length
) == -1)
747 error
= g_strerror(errno
);
749 if (close(fd
) == -1 && !error
)
750 error
= g_strerror(errno
);
752 scan_dir(filer_window
);
757 if (provides(context
, XdndDirectSave0
))
758 set_xds_prop(context
, "");
759 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
760 delayed_error("Error saving file", error
);
763 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
766 /* We've got a list of URIs from somewhere (probably another filer).
767 * If the files are on the local machine then try to copy them ourselves,
768 * otherwise, if there was only one file and application/octet-stream was
769 * provided, get the data via the X server.
771 static void got_uri_list(GtkWidget
*widget
,
772 GdkDragContext
*context
,
773 GtkSelectionData
*selection_data
,
776 FilerWindow
*filer_window
;
779 char **argv
= NULL
; /* Command to exec, or NULL */
781 gboolean send_reply
= TRUE
;
784 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
785 g_return_if_fail(filer_window
!= NULL
);
787 dest_path
= get_dest_path(filer_window
, context
);
789 uri_list
= uri_list_to_gslist(selection_data
->data
);
792 error
= "No URIs in the text/uri-list (nothing to do!)";
793 else if ((!uri_list
->next
) && (!get_local_path(uri_list
->data
)))
795 /* There is one URI in the list, and it's not on the local
796 * machine. Get it via the X server if possible.
799 if (provides(context
, application_octet_stream
))
802 leaf
= strrchr(uri_list
->data
, '/');
806 leaf
= uri_list
->data
;
807 g_dataset_set_data_full(context
, "leafname",
808 g_strdup(leaf
), g_free
);
809 gtk_drag_get_data(widget
, context
,
810 application_octet_stream
, time
);
814 error
= "Can't get data from remote machine "
815 "(application/octet-stream not provided)";
820 const char *start_args
[] = {"xterm", "-wf", "-e"};
821 int argc
= sizeof(start_args
) / sizeof(char *);
825 argv
= g_malloc(sizeof(start_args
) +
826 sizeof(char *) * (g_slist_length(uri_list
) + 4));
827 memcpy(argv
, start_args
, sizeof(start_args
));
829 if (context
->action
== GDK_ACTION_MOVE
)
832 argv
[argc
++] = "-iv";
834 else if (context
->action
== GDK_ACTION_LINK
)
837 argv
[argc
++] = "-vis";
842 argv
[argc
++] = "-Riva";
845 /* Either one local URI, or a list. If anything in the list
846 * isn't local then we are stuck.
853 path
= get_local_path((char *) next_uri
->data
);
861 error
= "Some of these files are on a "
862 "different machine - they will be "
865 next_uri
= next_uri
->next
;
870 error
= "None of these files are on the local machine "
871 "- I can't operate on multiple remote files - "
878 argv
[argc
++] = dest_path
;
885 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
886 delayed_error("Error getting file list", error
);
890 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
893 int child
; /* Child process ID */
897 g_hash_table_insert(child_to_filer
,
898 (gpointer
) child
, filer_window
);
900 delayed_error("ROX-Filer",
901 "Failed to fork() child process");
909 g_free(next_uri
->data
);
910 next_uri
= next_uri
->next
;
912 g_slist_free(uri_list
);