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>
26 #include "gui_support.h"
30 #define MAXURILEN 4096 /* Longest URI to allow */
32 /* Static prototypes */
33 static char *get_dest_path(FilerWindow
*filer_window
, GdkDragContext
*context
);
34 static void create_uri_list(GString
*string
,
35 Collection
*collection
,
36 FilerWindow
*filer_window
);
37 static gboolean
drag_drop(GtkWidget
*widget
,
38 GdkDragContext
*context
,
42 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
);
43 static void set_xds_prop(GdkDragContext
*context
, char *text
);
44 static gboolean
drag_motion(GtkWidget
*widget
,
45 GdkDragContext
*context
,
49 static void drag_leave(GtkWidget
*widget
,
50 GdkDragContext
*context
);
51 static void drag_data_received(GtkWidget
*widget
,
52 GdkDragContext
*context
,
55 GtkSelectionData
*selection_data
,
58 static void got_data_xds_reply(GtkWidget
*widget
,
59 GdkDragContext
*context
,
60 GtkSelectionData
*selection_data
,
62 static void got_data_raw(GtkWidget
*widget
,
63 GdkDragContext
*context
,
64 GtkSelectionData
*selection_data
,
66 static GSList
*uri_list_to_gslist(char *uri_list
);
67 static void got_uri_list(GtkWidget
*widget
,
68 GdkDragContext
*context
,
69 GtkSelectionData
*selection_data
,
71 static char *get_local_path(char *uri
);
72 static void run_with_files(char *path
, GSList
*uri_list
);
73 static GtkWidget
*create_options();
74 static void update_options();
75 static void set_options();
76 static void save_options();
77 static char *load_no_hostnames(char *data
);
79 /* Possible values for drop_dest_type (can also be NULL).
80 * In either case, drop_dest_path is the app/file/dir to use.
82 static char *drop_dest_prog
= "drop_dest_prog"; /* Run a program */
83 static char *drop_dest_dir
= "drop_dest_dir"; /* Save to path */
85 static OptionsSection options
=
87 "Drag and Drop options",
101 GdkAtom XdndDirectSave0
;
103 GdkAtom text_uri_list
;
104 GdkAtom application_octet_stream
;
108 XdndDirectSave0
= gdk_atom_intern("XdndDirectSave0", FALSE
);
109 text_plain
= gdk_atom_intern("text/plain", FALSE
);
110 text_uri_list
= gdk_atom_intern("text/uri-list", FALSE
);
111 application_octet_stream
= gdk_atom_intern("application/octet-stream",
114 options_sections
= g_slist_prepend(options_sections
, &options
);
115 option_register("dnd_no_hostnames", load_no_hostnames
);
120 static gboolean o_no_hostnames
= FALSE
;
121 static GtkWidget
*toggle_no_hostnames
;
123 /* Build up some option widgets to go in the options dialog, but don't
126 static GtkWidget
*create_options()
128 GtkWidget
*vbox
, *label
;
130 vbox
= gtk_vbox_new(FALSE
, 0);
131 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 4);
133 label
= gtk_label_new("Some older applications don't support XDND "
134 "fully and may need to have this option turned on. "
135 "Use this if dragging files to an application shows "
136 "a + sign on the pointer but the drop doesn't work.");
137 gtk_label_set_line_wrap(GTK_LABEL(label
), TRUE
);
138 gtk_box_pack_start(GTK_BOX(vbox
), label
, FALSE
, TRUE
, 0);
140 toggle_no_hostnames
=
141 gtk_check_button_new_with_label("Don't use hostnames");
142 gtk_box_pack_start(GTK_BOX(vbox
), toggle_no_hostnames
, FALSE
, TRUE
, 0);
147 static void update_options()
149 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle_no_hostnames
),
153 static void set_options()
155 o_no_hostnames
= gtk_toggle_button_get_active(
156 GTK_TOGGLE_BUTTON(toggle_no_hostnames
));
159 static void save_options()
161 option_write("dnd_no_hostnames", o_no_hostnames
? "1" : "0");
164 static char *load_no_hostnames(char *data
)
166 o_no_hostnames
= atoi(data
) != 0;
170 /* SUPPORT FUNCTIONS */
172 static char *get_dest_path(FilerWindow
*filer_window
, GdkDragContext
*context
)
176 path
= g_dataset_get_data(context
, "drop_dest_path");
178 return path
? path
: filer_window
->path
;
181 /* Set the XdndDirectSave0 property on the source window for this context */
182 static void set_xds_prop(GdkDragContext
*context
, char *text
)
184 gdk_property_change(context
->source_window
,
187 GDK_PROP_MODE_REPLACE
,
192 static char *get_xds_prop(GdkDragContext
*context
)
197 if (gdk_property_get(context
->source_window
,
203 &length
, &prop_text
) && prop_text
)
205 /* Terminate the string */
206 prop_text
= g_realloc(prop_text
, length
+ 1);
207 prop_text
[length
] = '\0';
214 /* Is the sender willing to supply this target type? */
215 static gboolean
provides(GdkDragContext
*context
, GdkAtom target
)
217 GList
*targets
= context
->targets
;
219 while (targets
&& ((GdkAtom
) targets
->data
!= target
))
220 targets
= targets
->next
;
222 return targets
!= NULL
;
225 /* Convert a URI to a local pathname (or NULL if it isn't local).
226 * The returned pointer points inside the input string.
233 static char *get_local_path(char *uri
)
237 host
= our_host_name();
244 return uri
; /* Just a local path - no host part */
246 path
= strchr(uri
+ 2, '/');
248 return NULL
; /* //something */
251 return path
; /* ///path */
252 if (strlen(host
) == path
- uri
- 2 &&
253 strncmp(uri
+ 2, host
, path
- uri
- 2) == 0)
254 return path
; /* //myhost/path */
256 return NULL
; /* From a different host */
260 if (strncasecmp(uri
, "file:", 5))
261 return NULL
; /* Don't know this format */
266 return get_local_path(uri
);
272 /* Convert a list of URIs into a list of strings.
273 * Lines beginning with # are skipped.
274 * The text block passed in is zero terminated (after the final CRLF)
276 static GSList
*uri_list_to_gslist(char *uri_list
)
286 linebreak
= strchr(uri_list
, 13);
288 if (!linebreak
|| linebreak
[1] != 10)
290 delayed_error("uri_list_to_gslist",
291 "Incorrect or missing line break "
292 "in text/uri-list data");
296 length
= linebreak
- uri_list
;
298 if (length
&& uri_list
[0] != '#')
300 uri
= g_malloc(sizeof(char) * (length
+ 1));
301 strncpy(uri
, uri_list
, length
);
303 list
= g_slist_append(list
, uri
);
306 uri_list
= linebreak
+ 2;
312 /* Append all the URIs in the selection to the string */
313 static void create_uri_list(GString
*string
,
314 Collection
*collection
,
315 FilerWindow
*filer_window
)
320 leader
= g_string_new("file://");
322 g_string_append(leader
, our_host_name());
323 g_string_append(leader
, filer_window
->path
);
324 if (leader
->str
[leader
->len
- 1] != '/')
325 g_string_append_c(leader
, '/');
327 num_selected
= collection
->number_selected
;
329 for (i
= 0; num_selected
> 0; i
++)
331 if (collection
->items
[i
].selected
)
333 FileItem
*item
= (FileItem
*) collection
->items
[i
].data
;
335 g_string_append(string
, leader
->str
);
336 g_string_append(string
, item
->leafname
);
337 g_string_append(string
, "\r\n");
342 g_string_free(leader
, TRUE
);
345 /* DRAGGING FROM US */
347 /* The user has held the mouse button down over an item and moved -
350 * We always provide text/uri-list. If we are dragging a single, regular file
351 * then we also offer application/octet-stream.
353 void drag_selection(Collection
*collection
,
354 GdkEventMotion
*event
,
355 gint number_selected
,
358 FilerWindow
*filer_window
= (FilerWindow
*) user_data
;
361 GdkDragContext
*context
;
362 GtkTargetList
*target_list
;
363 GtkTargetEntry target_table
[] =
365 {"text/uri-list", 0, TARGET_URI_LIST
},
366 {"application/octet-stream", 0, TARGET_RAW
},
370 if (number_selected
== 1)
371 item
= selected_item(collection
);
375 widget
= GTK_WIDGET(collection
);
377 target_list
= gtk_target_list_new(target_table
,
378 item
&& item
->base_type
== TYPE_FILE
? 2 : 1);
380 context
= gtk_drag_begin(widget
,
382 GDK_ACTION_COPY
| GDK_ACTION_MOVE
| GDK_ACTION_LINK
,
383 (event
->state
& GDK_BUTTON1_MASK
) ? 1 : 2,
385 g_dataset_set_data(context
, "filer_window", filer_window
);
387 image
= item
? item
->image
: &default_pixmap
[TYPE_MULTIPLE
];
389 gtk_drag_set_icon_pixmap(context
,
390 gtk_widget_get_colormap(widget
),
396 /* Called when a remote app wants us to send it some data.
397 * TODO: Maybe we should handle errors better (ie, let the remote app know
398 * the drag has failed)?
400 void drag_data_get(GtkWidget
*widget
,
401 GdkDragContext
*context
,
402 GtkSelectionData
*selection_data
,
406 char *to_send
= "E"; /* Default to sending an error */
407 long to_send_length
= 1;
408 gboolean delete_once_sent
= FALSE
;
409 GdkAtom type
= XA_STRING
;
411 FilerWindow
*filer_window
;
414 filer_window
= g_dataset_get_data(context
, "filer_window");
415 g_return_if_fail(filer_window
!= NULL
);
420 item
= selected_item(filer_window
->collection
);
421 if (item
&& load_file(make_path(filer_window
->path
,
422 item
->leafname
)->str
,
423 &to_send
, &to_send_length
))
425 delete_once_sent
= TRUE
;
426 type
= application_octet_stream
; /* XXX */
430 case TARGET_URI_LIST
:
431 string
= g_string_new(NULL
);
432 create_uri_list(string
,
435 to_send
= string
->str
;
436 to_send_length
= string
->len
;
437 delete_once_sent
= TRUE
;
438 g_string_free(string
, FALSE
);
441 delayed_error("drag_data_get",
442 "Internal error - bad info type\n");
446 gtk_selection_data_set(selection_data
,
452 if (delete_once_sent
)
458 /* Set up this filer window as a drop target. Called once, when the
459 * filer window is first created.
461 void drag_set_dest(GtkWidget
*widget
, FilerWindow
*filer_window
)
463 GtkTargetEntry target_table
[] =
465 {"text/uri-list", 0, TARGET_URI_LIST
},
466 {"XdndDirectSave0", 0, TARGET_XDS
},
467 {"application/octet-stream", 0, TARGET_RAW
},
470 gtk_drag_dest_set(widget
,
471 0, /* GTK_DEST_DEFAULT_MOTION, */
473 sizeof(target_table
) / sizeof(*target_table
),
474 GDK_ACTION_COPY
| GDK_ACTION_MOVE
475 | GDK_ACTION_LINK
| GDK_ACTION_PRIVATE
);
477 gtk_signal_connect(GTK_OBJECT(widget
), "drag_motion",
478 GTK_SIGNAL_FUNC(drag_motion
), filer_window
);
479 gtk_signal_connect(GTK_OBJECT(widget
), "drag_leave",
480 GTK_SIGNAL_FUNC(drag_leave
), filer_window
);
481 gtk_signal_connect(GTK_OBJECT(widget
), "drag_drop",
482 GTK_SIGNAL_FUNC(drag_drop
), filer_window
);
483 gtk_signal_connect(GTK_OBJECT(widget
), "drag_data_received",
484 GTK_SIGNAL_FUNC(drag_data_received
), filer_window
);
487 /* Decide if panel drag is OK, setting drop_dest_type and drop_dest_path
488 * on the context as needed.
490 static gboolean
panel_drag_ok(FilerWindow
*filer_window
,
491 GdkDragContext
*context
,
494 FileItem
*fileitem
= NULL
;
499 panel_set_timeout(NULL
, 0);
502 fileitem
= (FileItem
*)
503 filer_window
->collection
->items
[item
].data
;
509 else if (fileitem
->flags
& (ITEM_FLAG_APPDIR
| ITEM_FLAG_EXEC_FILE
))
511 if (provides(context
, text_uri_list
))
513 type
= drop_dest_prog
;
514 new_path
= make_path(filer_window
->path
,
515 fileitem
->leafname
)->str
;
520 else if (fileitem
->base_type
== TYPE_DIRECTORY
)
522 type
= drop_dest_dir
;
523 new_path
= make_path(filer_window
->path
,
524 fileitem
->leafname
)->str
;
529 if (new_path
&& access(new_path
, W_OK
))
532 old_path
= g_dataset_get_data(context
, "drop_dest_path");
533 if (old_path
== new_path
||
534 (old_path
&& new_path
&& strcmp(old_path
, new_path
) == 0))
536 return new_path
!= NULL
; /* Same as before */
541 g_dataset_set_data(context
, "drop_dest_type", type
);
542 g_dataset_set_data_full(context
, "drop_dest_path",
543 g_strdup(new_path
), g_free
);
548 g_dataset_set_data(context
, "drop_dest_path", NULL
);
551 collection_set_cursor_item(filer_window
->collection
, item
);
553 return new_path
!= NULL
;
556 /* Called during the drag when the mouse is in a widget registered
557 * as a drop target. Returns TRUE if we can accept the drop.
559 static gboolean
drag_motion(GtkWidget
*widget
,
560 GdkDragContext
*context
,
565 FilerWindow
*filer_window
;
568 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
569 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
571 if (gtk_drag_get_source_widget(context
) == widget
)
572 return FALSE
; /* Not within a single widget! */
574 if (filer_window
->panel
== FALSE
)
576 if (access(filer_window
->path
, W_OK
))
577 return FALSE
; /* We can't write here */
578 gdk_drag_status(context
, context
->suggested_action
, time
);
582 /* OK, this is a drag to a panel.
583 * Allow drags to directories, applications and X bit files only.
585 item
= collection_get_item(filer_window
->collection
, x
, y
);
587 if (panel_drag_ok(filer_window
, context
, item
))
589 gdk_drag_status(context
, context
->suggested_action
, time
);
596 /* Remove panel highlights */
597 static void drag_leave(GtkWidget
*widget
,
598 GdkDragContext
*context
)
600 FilerWindow
*filer_window
;
602 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
603 g_return_if_fail(filer_window
!= NULL
);
605 panel_set_timeout(NULL
, 0);
606 collection_set_cursor_item(filer_window
->collection
, -1);
609 /* User has tried to drop some data on us. Decide what format we would
612 static gboolean
drag_drop(GtkWidget
*widget
,
613 GdkDragContext
*context
,
619 char *leafname
= NULL
;
620 FilerWindow
*filer_window
;
625 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
626 g_return_val_if_fail(filer_window
!= NULL
, TRUE
);
628 dest_path
= g_dataset_get_data(context
, "drop_dest_path");
629 if (dest_path
== NULL
)
631 if (filer_window
->panel
)
632 error
= "Bad drop on panel";
635 dest_path
= filer_window
->path
;
636 dest_type
= drop_dest_dir
;
641 dest_type
= g_dataset_get_data(context
, "drop_dest_type");
648 else if (dest_type
== drop_dest_dir
649 && provides(context
, XdndDirectSave0
))
651 leafname
= get_xds_prop(context
);
654 if (strchr(leafname
, '/'))
656 error
= "XDS protocol error: "
657 "leafname may not contain '/'\n";
666 uri
= g_string_new(NULL
);
667 g_string_sprintf(uri
, "file://%s%s",
671 set_xds_prop(context
, uri
->str
);
672 g_string_free(uri
, TRUE
);
674 target
= XdndDirectSave0
;
675 g_dataset_set_data_full(context
, "leafname",
680 error
= "XdndDirectSave0 target provided, but the atom "
681 "XdndDirectSave0 (type text/plain) did not "
682 "contain a leafname\n";
684 else if (provides(context
, text_uri_list
))
685 target
= text_uri_list
;
688 if (dest_type
== drop_dest_dir
)
689 error
= "Sorry - I require a target type of "
690 "text/uri-list or XdndDirectSave0.";
692 error
= "Sorry - I require a target type of "
698 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
700 delayed_error("ROX-Filer", error
);
703 gtk_drag_get_data(widget
, context
, target
, time
);
708 /* Called when some data arrives from the remote app (which we asked for
711 static void drag_data_received(GtkWidget
*widget
,
712 GdkDragContext
*context
,
715 GtkSelectionData
*selection_data
,
719 if (!selection_data
->data
)
722 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
729 got_data_xds_reply(widget
, context
,
730 selection_data
, time
);
733 got_data_raw(widget
, context
, selection_data
, time
);
735 case TARGET_URI_LIST
:
736 got_uri_list(widget
, context
, selection_data
, time
);
739 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
740 delayed_error("drag_data_received", "Unknown target");
745 static void got_data_xds_reply(GtkWidget
*widget
,
746 GdkDragContext
*context
,
747 GtkSelectionData
*selection_data
,
750 gboolean mark_unsafe
= TRUE
;
751 char response
= *selection_data
->data
;
754 if (selection_data
->length
!= 1)
759 /* Sender couldn't save there - ask for another
762 if (provides(context
, application_octet_stream
))
764 mark_unsafe
= FALSE
; /* Wait and see */
766 gtk_drag_get_data(widget
, context
,
767 application_octet_stream
, time
);
770 error
= "Remote app can't or won't send me "
773 else if (response
== 'S')
775 FilerWindow
*filer_window
;
777 /* Success - data is saved */
778 mark_unsafe
= FALSE
; /* It really is safe */
779 gtk_drag_finish(context
, TRUE
, FALSE
, time
);
781 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
),
783 g_return_if_fail(filer_window
!= NULL
);
785 scan_dir(filer_window
);
787 else if (response
!= 'E')
789 error
= "XDS protocol error: "
790 "return code should be 'S', 'F' or 'E'\n";
792 /* else: error has been reported by the sender */
796 set_xds_prop(context
, "");
797 /* Unsave also implies that the drag failed */
798 gtk_drag_finish(context
, FALSE
, FALSE
, time
);
802 delayed_error("ROX-Filer", error
);
805 static void got_data_raw(GtkWidget
*widget
,
806 GdkDragContext
*context
,
807 GtkSelectionData
*selection_data
,
810 FilerWindow
*filer_window
;
816 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
817 g_return_if_fail(filer_window
!= NULL
);
819 leafname
= g_dataset_get_data(context
, "leafname");
821 leafname
= "UntitledData";
823 dest_path
= get_dest_path(filer_window
, context
);
825 fd
= open(make_path(dest_path
, leafname
)->str
,
826 O_WRONLY
| O_CREAT
| O_EXCL
| O_NOCTTY
,
827 S_IRUSR
| S_IRGRP
| S_IROTH
|
828 S_IWUSR
| S_IWGRP
| S_IWOTH
);
831 error
= g_strerror(errno
);
835 selection_data
->data
,
836 selection_data
->length
) == -1)
837 error
= g_strerror(errno
);
839 if (close(fd
) == -1 && !error
)
840 error
= g_strerror(errno
);
842 scan_dir(filer_window
);
847 if (provides(context
, XdndDirectSave0
))
848 set_xds_prop(context
, "");
849 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
850 delayed_error("Error saving file", error
);
853 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
856 /* Execute this program, passing all the URIs in the list as arguments.
857 * URIs that are files on the local machine will be passed as simple
858 * pathnames. The uri_list should be freed after this function returns.
860 static void run_with_files(char *path
, GSList
*uri_list
)
866 if (stat(path
, &info
))
868 delayed_error("ROX-Filer", "Program not found - deleted?");
872 argv
= g_malloc(sizeof(char *) * (g_slist_length(uri_list
) + 2));
874 if (S_ISDIR(info
.st_mode
))
875 argv
[argc
++] = make_path(path
, "AppRun")->str
;
881 char *uri
= (char *) uri_list
->data
;
884 local
= get_local_path(uri
);
886 argv
[argc
++] = local
;
889 uri_list
= uri_list
->next
;
894 if (!spawn_full(argv
, getenv("HOME"), 0))
895 delayed_error("ROX-Filer", "Failed to fork() child process");
898 /* We've got a list of URIs from somewhere (probably another filer window).
899 * If the files are on the local machine then try to copy them ourselves,
900 * otherwise, if there was only one file and application/octet-stream was
901 * provided, get the data via the X server.
903 static void got_uri_list(GtkWidget
*widget
,
904 GdkDragContext
*context
,
905 GtkSelectionData
*selection_data
,
908 FilerWindow
*filer_window
;
911 char **argv
= NULL
; /* Command to exec, or NULL */
913 gboolean send_reply
= TRUE
;
917 filer_window
= gtk_object_get_data(GTK_OBJECT(widget
), "filer_window");
918 g_return_if_fail(filer_window
!= NULL
);
920 dest_path
= get_dest_path(filer_window
, context
);
921 type
= g_dataset_get_data(context
, "drop_dest_type");
923 uri_list
= uri_list_to_gslist(selection_data
->data
);
926 error
= "No URIs in the text/uri-list (nothing to do!)";
927 else if (type
== drop_dest_prog
)
928 run_with_files(dest_path
, uri_list
);
929 else if ((!uri_list
->next
) && (!get_local_path(uri_list
->data
)))
931 /* There is one URI in the list, and it's not on the local
932 * machine. Get it via the X server if possible.
935 if (provides(context
, application_octet_stream
))
938 leaf
= strrchr(uri_list
->data
, '/');
942 leaf
= uri_list
->data
;
943 g_dataset_set_data_full(context
, "leafname",
944 g_strdup(leaf
), g_free
);
945 gtk_drag_get_data(widget
, context
,
946 application_octet_stream
, time
);
950 error
= "Can't get data from remote machine "
951 "(application/octet-stream not provided)";
955 GSList
*local_paths
= NULL
;
958 /* Either one local URI, or a list. If everything in the list
959 * isn't local then we are stuck.
962 for (next_uri
= uri_list
; next_uri
; next_uri
= next_uri
->next
)
966 path
= get_local_path((char *) next_uri
->data
);
969 local_paths
= g_slist_append(local_paths
,
972 error
= "Some of these files are on a "
973 "different machine - they will be "
979 error
= "None of these files are on the local machine "
980 "- I can't operate on multiple remote files - "
983 else if (context
->action
== GDK_ACTION_MOVE
)
984 action_move(local_paths
, filer_window
->path
);
985 else if (context
->action
== GDK_ACTION_COPY
)
986 action_copy(local_paths
, filer_window
->path
);
987 else if (context
->action
== GDK_ACTION_LINK
)
988 action_link(local_paths
, filer_window
->path
);
990 error
= "Unknown action requested";
992 for (next
= local_paths
; next
; next
= next
->next
)
994 g_slist_free(local_paths
);
999 gtk_drag_finish(context
, FALSE
, FALSE
, time
); /* Failure */
1000 delayed_error("Error getting file list", error
);
1002 else if (send_reply
)
1004 gtk_drag_finish(context
, TRUE
, FALSE
, time
); /* Success! */
1007 int child
; /* Child process ID */
1009 child
= spawn(argv
);
1011 g_hash_table_insert(child_to_filer
,
1012 (gpointer
) child
, filer_window
);
1014 delayed_error("ROX-Filer",
1015 "Failed to fork() child process");
1020 next_uri
= uri_list
;
1023 g_free(next_uri
->data
);
1024 next_uri
= next_uri
->next
;
1026 g_slist_free(uri_list
);