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 */
30 /* Possible values for drop_dest_type (can also be NULL).
31 * In either case, drop_dest_path is the app/file/dir to use.
33 static char *drop_dest_prog
= "drop_dest_prog"; /* Run a program */
34 static char *drop_dest_dir
= "drop_dest_dir"; /* Save to path */
43 GdkAtom XdndDirectSave0
;
45 GdkAtom text_uri_list
;
46 GdkAtom application_octet_stream
;
48 /* Static prototypes */
49 static char *get_dest_path(FilerWindow
*filer_window
, GdkDragContext
*context
);
50 static void create_uri_list(GString
*string
,
51 Collection
*collection
,
52 FilerWindow
*filer_window
);
53 static gboolean
drag_drop(GtkWidget
*widget
,
54 GdkDragContext
*context
,
58 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
);
59 static void set_xds_prop(GdkDragContext
*context
, char *text
);
60 static gboolean
drag_motion(GtkWidget
*widget
,
61 GdkDragContext
*context
,
65 static void drag_leave(GtkWidget
*widget
,
66 GdkDragContext
*context
);
67 static void drag_data_received(GtkWidget
*widget
,
68 GdkDragContext
*context
,
71 GtkSelectionData
*selection_data
,
74 static void got_data_xds_reply(GtkWidget
*widget
,
75 GdkDragContext
*context
,
76 GtkSelectionData
*selection_data
,
78 static void got_data_raw(GtkWidget
*widget
,
79 GdkDragContext
*context
,
80 GtkSelectionData
*selection_data
,
82 static gboolean
load_file(char *pathname
, char **data_out
, long *length_out
);
83 static GSList
*uri_list_to_gslist(char *uri_list
);
84 static void got_uri_list(GtkWidget
*widget
,
85 GdkDragContext
*context
,
86 GtkSelectionData
*selection_data
,
88 char *get_local_path(char *uri
);
89 static void run_with_files(char *path
, GSList
*uri_list
);
93 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
94 text_plain
= gdk_atom_intern("text/plain", FALSE
);
95 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
96 application_octet_stream
= gdk_atom_intern("application/octet-stream",
100 static char *get_dest_path(FilerWindow
*filer_window
, GdkDragContext
*context
)
104 path
= g_dataset_get_data(context
, "drop_dest_path");
106 return path
? path
: filer_window
->path
;
109 /* Set the XdndDirectSave0 property on the source window for this context */
110 static void set_xds_prop(GdkDragContext
*context
, char *text
)
112 gdk_property_change(context
->source_window
,
115 GDK_PROP_MODE_REPLACE
,
120 static char *get_xds_prop(GdkDragContext
*context
)
125 if (gdk_property_get(context
->source_window
,
131 &length
, &prop_text
) && prop_text
)
133 /* Terminate the string */
134 prop_text
= g_realloc(prop_text
, length
+ 1);
135 prop_text
[length
] = '\0';
142 /* Is the sender willing to supply this target type? */
143 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
145 GList
*targets
= context
->targets
;
147 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
148 targets
= targets
->next
;
150 return targets
!= NULL
;
153 /* Convert a URI to a local pathname (or NULL if it isn't local).
154 * The returned pointer points inside the input string.
161 char *get_local_path(char *uri
)
165 host
= our_host_name();
172 return uri
; /* Just a local path - no host part */
174 path
= strchr(uri
+ 2, '/');
176 return NULL
; /* //something */
179 return path
; /* ///path */
180 if (strlen(host
) == path
- uri
- 2 &&
181 strncmp(uri
+ 2, host
, path
- uri
- 2) == 0)
182 return path
; /* //myhost/path */
184 return NULL
; /* From a different host */
188 if (strncasecmp(uri
, "file:", 5))
189 return NULL
; /* Don't know this format */
194 return get_local_path(uri
);
200 /* Convert a list of URIs into a list of strings.
201 * Lines beginning with # are skipped.
202 * The text block passed in is zero terminated (after the final CRLF)
204 static GSList
*uri_list_to_gslist(char *uri_list
)
214 linebreak
= strchr(uri_list
, 13);
216 if (!linebreak
|| linebreak
[1] != 10)
218 delayed_error("uri_list_to_gslist",
219 "Incorrect or missing line break "
220 "in text/uri-list data");
224 length
= linebreak
- uri_list
;
226 if (length
&& uri_list
[0] != '#')
228 uri
= g_malloc(sizeof(char) * (length
+ 1));
229 strncpy(uri
, uri_list
, length
);
231 list
= g_slist_append(list
, uri
);
234 uri_list
= linebreak
+ 2;
240 /* Append all the URIs in the selection to the string */
241 static void create_uri_list(GString
*string
,
242 Collection
*collection
,
243 FilerWindow
*filer_window
)
248 leader
= g_string_new("file://");
249 g_string_append(leader
, our_host_name());
250 g_string_append(leader
, filer_window
->path
);
251 if (leader
->str
[leader
->len
- 1] != '/')
252 g_string_append_c(leader
, '/');
254 num_selected
= collection
->number_selected
;
256 for (i
= 0; num_selected
> 0; i
++)
258 if (collection
->items
[i
].selected
)
260 FileItem
*item
= (FileItem
*) collection
->items
[i
].data
;
262 g_string_append(string
, leader
->str
);
263 g_string_append(string
, item
->leafname
);
264 g_string_append(string
, "\r\n");
269 g_string_free(leader
, TRUE
);
272 /* DRAGGING FROM US */
274 /* The user has held the mouse button down over an item and moved -
277 * We always provide text/uri-list. If we are dragging a single, regular file
278 * then we also offer application/octet-stream.
280 void drag_selection(Collection
*collection
,
281 GdkEventMotion
*event
,
282 gint number_selected
,
285 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
288 GdkDragContext
*context
;
289 GtkTargetList
*target_list
;
290 GtkTargetEntry target_table
[] =
292 {"text/uri-list", 0, TARGET_URI_LIST
},
293 {"application/octet-stream", 0, TARGET_RAW
},
297 if (number_selected
== 1)
298 item
= selected_item(collection
);
302 widget
= GTK_WIDGET(collection
);
304 target_list
= gtk_target_list_new(target_table
,
305 item
&& item
->base_type
== TYPE_FILE
? 2 : 1);
307 context
= gtk_drag_begin(widget
,
309 GDK_ACTION_COPY
| GDK_ACTION_MOVE
| GDK_ACTION_LINK
,
310 (event
->state
& GDK_BUTTON1_MASK
) ? 1 : 2,
312 g_dataset_set_data(context
, "filer_window", filer_window
);
314 image
= item
? item
->image
: &default_pixmap
[TYPE_MULTIPLE
];
316 gtk_drag_set_icon_pixmap(context
,
317 gtk_widget_get_colormap(widget
),
323 /* Called when a remote app wants us to send it some data.
324 * TODO: Maybe we should handle errors better (ie, let the remote app know
325 * the drag has failed)?
327 void drag_data_get(GtkWidget
*widget
,
328 GdkDragContext
*context
,
329 GtkSelectionData
*selection_data
,
333 char *to_send
= "E"; /* Default to sending an error */
334 long to_send_length
= 1;
335 gboolean delete_once_sent
= FALSE
;
336 GdkAtom type
= XA_STRING
;
338 FilerWindow
*filer_window
;
341 filer_window
= g_dataset_get_data(context
, "filer_window");
342 g_return_if_fail(filer_window
!= NULL
);
347 item
= selected_item(filer_window
->collection
);
348 if (item
&& load_file(make_path(filer_window
->path
,
349 item
->leafname
)->str
,
350 &to_send
, &to_send_length
))
352 delete_once_sent
= TRUE
;
353 type
= application_octet_stream
; /* XXX */
357 case TARGET_URI_LIST
:
358 string
= g_string_new(NULL
);
359 create_uri_list(string
,
362 to_send
= string
->str
;
363 to_send_length
= string
->len
;
364 delete_once_sent
= TRUE
;
365 g_string_free(string
, FALSE
);
368 delayed_error("drag_data_get",
369 "Internal error - bad info type\n");
373 gtk_selection_data_set(selection_data
,
379 if (delete_once_sent
)
383 /* Load the file into memory. Return TRUE on success. */
384 static gboolean
load_file(char *pathname
, char **data_out
, long *length_out
)
389 gboolean retval
= FALSE
;
391 file
= fopen(pathname
, "r");
395 delayed_error("Opening file for DND", g_strerror(errno
));
399 fseek(file
, 0, SEEK_END
);
400 length
= ftell(file
);
402 buffer
= malloc(length
);
405 fseek(file
, 0, SEEK_SET
);
406 fread(buffer
, 1, length
, file
);
410 delayed_error("Loading file for DND",
417 *length_out
= length
;
422 delayed_error("Loading file for DND",
423 "Can't allocate memory for buffer to "
424 "transfer this file");
433 /* Set up this filer window as a drop target. Called once, when the
434 * filer window is first created.
436 void drag_set_dest(GtkWidget
*widget
, FilerWindow
*filer_window
)
438 GtkTargetEntry target_table
[] =
440 {"text/uri-list", 0, TARGET_URI_LIST
},
441 {"XdndDirectSave0", 0, TARGET_XDS
},
442 {"application/octet-stream", 0, TARGET_RAW
},
445 gtk_drag_dest_set(widget
,
446 0, /* GTK_DEST_DEFAULT_MOTION, */
448 sizeof(target_table
) / sizeof(*target_table
),
449 GDK_ACTION_COPY
| GDK_ACTION_MOVE
450 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
452 gtk_signal_connect(GTK_OBJECT(widget
), "drag_motion",
453 GTK_SIGNAL_FUNC(drag_motion
), filer_window
);
454 gtk_signal_connect(GTK_OBJECT(widget
), "drag_leave",
455 GTK_SIGNAL_FUNC(drag_leave
), filer_window
);
456 gtk_signal_connect(GTK_OBJECT(widget
), "drag_drop",
457 GTK_SIGNAL_FUNC(drag_drop
), filer_window
);
458 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
459 GTK_SIGNAL_FUNC(drag_data_received
), filer_window
);
462 /* Decide if panel drag is OK, setting drop_dest_type and drop_dest_path
463 * on the context as needed.
465 static gboolean
panel_drag_ok(FilerWindow
*filer_window
,
466 GdkDragContext
*context
,
469 FileItem
*fileitem
= NULL
;
474 panel_set_timeout(NULL
, 0);
477 fileitem
= (FileItem
*)
478 filer_window
->collection
->items
[item
].data
;
484 else if (fileitem
->flags
& (ITEM_FLAG_APPDIR
| ITEM_FLAG_EXEC_FILE
))
486 if (provides(context
, text_uri_list
))
488 type
= drop_dest_prog
;
489 new_path
= make_path(filer_window
->path
,
490 fileitem
->leafname
)->str
;
495 else if (fileitem
->base_type
== TYPE_DIRECTORY
)
497 type
= drop_dest_dir
;
498 new_path
= make_path(filer_window
->path
,
499 fileitem
->leafname
)->str
;
504 old_path
= g_dataset_get_data(context
, "drop_dest_path");
505 if (old_path
== new_path
||
506 (old_path
&& new_path
&& strcmp(old_path
, new_path
) == 0))
508 return new_path
!= NULL
; /* Same as before */
513 g_dataset_set_data(context
, "drop_dest_type", type
);
514 g_dataset_set_data_full(context
, "drop_dest_path",
515 g_strdup(new_path
), g_free
);
520 g_dataset_set_data(context
, "drop_dest_path", NULL
);
523 collection_set_cursor_item(filer_window
->collection
, item
);
525 return new_path
!= NULL
;
528 /* Called during the drag when the mouse is in a widget registered
529 * as a drop target. Returns TRUE if we can accept the drop.
531 static gboolean
drag_motion(GtkWidget
*widget
,
532 GdkDragContext
*context
,
537 FilerWindow
*filer_window
;
540 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
541 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
543 if (gtk_drag_get_source_widget(context
) == widget
)
544 return FALSE
; /* Not within a single widget! */
546 if (filer_window
->panel
== FALSE
)
548 gdk_drag_status(context
, context
->suggested_action
, time
);
552 /* OK, this is a drag to a panel.
553 * Allow drags to directories, applications and X bit files only.
555 item
= collection_get_item(filer_window
->collection
, x
, y
);
557 if (panel_drag_ok(filer_window
, context
, item
))
559 gdk_drag_status(context
, context
->suggested_action
, time
);
566 /* Remove panel highlights */
567 static void drag_leave(GtkWidget
*widget
,
568 GdkDragContext
*context
)
570 FilerWindow
*filer_window
;
572 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
573 g_return_if_fail(filer_window
!= NULL
);
575 panel_set_timeout(NULL
, 0);
576 collection_set_cursor_item(filer_window
->collection
, -1);
579 /* User has tried to drop some data on us. Decide what format we would
582 static gboolean
drag_drop(GtkWidget
*widget
,
583 GdkDragContext
*context
,
589 char *leafname
= NULL
;
590 FilerWindow
*filer_window
;
595 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
596 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
598 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
599 if (dest_path
== NULL
)
601 if (filer_window
->panel
)
602 error
= "Bad drop on panel";
605 dest_path
= filer_window
->path
;
606 dest_type
= drop_dest_dir
;
611 dest_type
= g_dataset_get_data(context
, "drop_dest_type");
618 else if (dest_type
== drop_dest_dir
619 && provides(context
, XdndDirectSave0
))
621 leafname
= get_xds_prop(context
);
624 if (strchr(leafname
, '/'))
626 error
= "XDS protocol error: "
627 "leafname may not contain '/'\n";
636 uri
= g_string_new(NULL
);
637 g_string_sprintf(uri
, "file://%s%s",
641 set_xds_prop(context
, uri
->str
);
642 g_string_free(uri
, TRUE
);
644 target
= XdndDirectSave0
;
645 g_dataset_set_data_full(context
, "leafname",
650 error
= "XdndDirectSave0 target provided, but the atom "
651 "XdndDirectSave0 (type text/plain) did not "
652 "contain a leafname\n";
654 else if (provides(context
, text_uri_list
))
655 target
= text_uri_list
;
658 if (dest_type
== drop_dest_dir
)
659 error
= "Sorry - I require a target type of "
660 "text/uri-list or XdndDirectSave0.";
662 error
= "Sorry - I require a target type of "
668 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
670 delayed_error("ROX-Filer", error
);
673 gtk_drag_get_data(widget
, context
, target
, time
);
678 /* Called when some data arrives from the remote app (which we asked for
681 static void drag_data_received(GtkWidget
*widget
,
682 GdkDragContext
*context
,
685 GtkSelectionData
*selection_data
,
689 if (!selection_data
->data
)
692 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
699 got_data_xds_reply(widget
, context
,
700 selection_data
, time
);
703 got_data_raw(widget
, context
, selection_data
, time
);
705 case TARGET_URI_LIST
:
706 got_uri_list(widget
, context
, selection_data
, time
);
709 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
710 delayed_error("drag_data_received", "Unknown target");
715 static void got_data_xds_reply(GtkWidget
*widget
,
716 GdkDragContext
*context
,
717 GtkSelectionData
*selection_data
,
720 gboolean mark_unsafe
= TRUE
;
721 char response
= *selection_data
->data
;
724 if (selection_data
->length
!= 1)
729 /* Sender couldn't save there - ask for another
732 if (provides(context
, application_octet_stream
))
734 mark_unsafe
= FALSE
; /* Wait and see */
736 gtk_drag_get_data(widget
, context
,
737 application_octet_stream
, time
);
740 error
= "Remote app can't or won't send me "
743 else if (response
== 'S')
745 FilerWindow
*filer_window
;
747 /* Success - data is saved */
748 mark_unsafe
= FALSE
; /* It really is safe */
749 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
751 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
),
753 g_return_if_fail(filer_window
!= NULL
);
755 scan_dir(filer_window
);
757 else if (response
!= 'E')
759 error
= "XDS protocol error: "
760 "return code should be 'S', 'F' or 'E'\n";
762 /* else: error has been reported by the sender */
766 set_xds_prop(context
, "");
767 /* Unsave also implies that the drag failed */
768 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
772 delayed_error("ROX-Filer", error
);
775 static void got_data_raw(GtkWidget
*widget
,
776 GdkDragContext
*context
,
777 GtkSelectionData
*selection_data
,
780 FilerWindow
*filer_window
;
786 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
787 g_return_if_fail(filer_window
!= NULL
);
789 leafname
= g_dataset_get_data(context
, "leafname");
791 leafname
= "UntitledData";
793 dest_path
= get_dest_path(filer_window
, context
);
795 fd
= open(make_path(dest_path
, leafname
)->str
,
796 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
797 S_IRUSR
| S_IRGRP
| S_IROTH
|
798 S_IWUSR
| S_IWGRP
| S_IWOTH
);
801 error
= g_strerror(errno
);
805 selection_data
->data
,
806 selection_data
->length
) == -1)
807 error
= g_strerror(errno
);
809 if (close(fd
) == -1 && !error
)
810 error
= g_strerror(errno
);
812 scan_dir(filer_window
);
817 if (provides(context
, XdndDirectSave0
))
818 set_xds_prop(context
, "");
819 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
820 delayed_error("Error saving file", error
);
823 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
826 /* Execute this program, passing all the URIs in the list as arguments.
827 * URIs that are files on the local machine will be passed as simple
828 * pathnames. The uri_list should be freed after this function returns.
830 static void run_with_files(char *path
, GSList
*uri_list
)
836 if (stat(path
, &info
))
838 delayed_error("ROX-Filer", "Program not found - deleted?");
842 argv
= g_malloc(sizeof(char *) * (g_slist_length(uri_list
) + 2));
844 if (S_ISDIR(info
.st_mode
))
845 argv
[argc
++] = make_path(path
, "AppRun")->str
;
851 char *uri
= (char *) uri_list
->data
;
854 local
= get_local_path(uri
);
856 argv
[argc
++] = local
;
859 uri_list
= uri_list
->next
;
865 delayed_error("ROX-Filer", "Failed to fork() child process");
868 /* We've got a list of URIs from somewhere (probably another filer).
869 * If the files are on the local machine then try to copy them ourselves,
870 * otherwise, if there was only one file and application/octet-stream was
871 * provided, get the data via the X server.
873 static void got_uri_list(GtkWidget
*widget
,
874 GdkDragContext
*context
,
875 GtkSelectionData
*selection_data
,
878 FilerWindow
*filer_window
;
881 char **argv
= NULL
; /* Command to exec, or NULL */
883 gboolean send_reply
= TRUE
;
887 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
888 g_return_if_fail(filer_window
!= NULL
);
890 dest_path
= get_dest_path(filer_window
, context
);
891 type
= g_dataset_get_data(context
, "drop_dest_type");
893 uri_list
= uri_list_to_gslist(selection_data
->data
);
896 error
= "No URIs in the text/uri-list (nothing to do!)";
897 else if (type
== drop_dest_prog
)
898 run_with_files(dest_path
, uri_list
);
899 else if ((!uri_list
->next
) && (!get_local_path(uri_list
->data
)))
901 /* There is one URI in the list, and it's not on the local
902 * machine. Get it via the X server if possible.
905 if (provides(context
, application_octet_stream
))
908 leaf
= strrchr(uri_list
->data
, '/');
912 leaf
= uri_list
->data
;
913 g_dataset_set_data_full(context
, "leafname",
914 g_strdup(leaf
), g_free
);
915 gtk_drag_get_data(widget
, context
,
916 application_octet_stream
, time
);
920 error
= "Can't get data from remote machine "
921 "(application/octet-stream not provided)";
926 const char *start_args
[] = {"xterm", "-wf", "-e"};
927 int argc
= sizeof(start_args
) / sizeof(char *);
931 argv
= g_malloc(sizeof(start_args
) +
932 sizeof(char *) * (g_slist_length(uri_list
) + 4));
933 memcpy(argv
, start_args
, sizeof(start_args
));
935 if (context
->action
== GDK_ACTION_MOVE
)
938 argv
[argc
++] = "-iv";
940 else if (context
->action
== GDK_ACTION_LINK
)
943 argv
[argc
++] = "-vis";
948 argv
[argc
++] = "-Riva";
951 /* Either one local URI, or a list. If anything in the list
952 * isn't local then we are stuck.
959 path
= get_local_path((char *) next_uri
->data
);
967 error
= "Some of these files are on a "
968 "different machine - they will be "
971 next_uri
= next_uri
->next
;
976 error
= "None of these files are on the local machine "
977 "- I can't operate on multiple remote files - "
984 argv
[argc
++] = dest_path
;
991 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
992 delayed_error("Error getting file list", error
);
996 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
999 int child
; /* Child process ID */
1001 child
= spawn(argv
);
1003 g_hash_table_insert(child_to_filer
,
1004 (gpointer
) child
, filer_window
);
1006 delayed_error("ROX-Filer",
1007 "Failed to fork() child process");
1012 next_uri
= uri_list
;
1015 g_free(next_uri
->data
);
1016 next_uri
= next_uri
->next
;
1018 g_slist_free(uri_list
);