4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 1999, Thomas Leonard, <tal197@ecs.soton.ac.uk>.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* action.c - code for handling the filer action windows.
23 * These routines generally fork() and talk to us via pipes.
32 #include <sys/types.h>
40 #include "gui_support.h"
44 static GdkColor red
= {0, 0xffff, 0, 0};
46 typedef struct _GUIside GUIside
;
47 typedef void ActionChild(gpointer data
);
48 typedef gboolean
ForDirCB(char *path
, char *dest_path
);
52 int from_child
; /* File descriptor */
54 int input_tag
; /* gdk_input_add() */
55 GtkWidget
*log
, *window
, *actions
, *dir
;
56 int child
; /* Process ID */
58 gboolean show_info
; /* For Disk Usage */
60 char *next_dir
; /* NULL => no timer active */
64 /* These don't need to be in a structure because we fork() before
67 static int from_parent
= 0;
68 static FILE *to_parent
= NULL
;
69 static gboolean quiet
= FALSE
;
70 static GString
*message
= NULL
;
71 static char *action_dest
= NULL
;
72 static gboolean (*action_do_func
)(char *source
, char *dest
);
73 static size_t size_tally
; /* For Disk Usage */
75 /* Static prototypes */
76 static gboolean
send();
77 static gboolean
send_error();
78 static gboolean
send_dir(char *dir
);
79 static gboolean
read_exact(int source
, char *buffer
, ssize_t len
);
81 static gboolean
display_dir(gpointer data
)
83 GUIside
*gui_side
= (GUIside
*) data
;
85 gtk_label_set_text(GTK_LABEL(gui_side
->dir
), gui_side
->next_dir
+ 1);
86 g_free(gui_side
->next_dir
);
87 gui_side
->next_dir
= NULL
;
92 /* Called when the child sends us a message */
93 static void message_from_child(gpointer data
,
95 GdkInputCondition condition
)
98 GUIside
*gui_side
= (GUIside
*) data
;
99 GtkWidget
*log
= gui_side
->log
;
101 if (read_exact(source
, buf
, 4))
107 message_len
= strtol(buf
, NULL
, 16);
108 buffer
= g_malloc(message_len
+ 1);
109 if (message_len
> 0 && read_exact(source
, buffer
, message_len
))
111 buffer
[message_len
] = '\0';
113 gtk_widget_set_sensitive(gui_side
->actions
,
115 else if (*buffer
== '+')
117 refresh_dirs(buffer
+ 1);
121 else if (*buffer
== '/')
123 if (gui_side
->next_dir
)
124 g_free(gui_side
->next_dir
);
126 gui_side
->next_timer
=
130 gui_side
->next_dir
= buffer
;
133 else if (*buffer
== '!')
136 gtk_text_insert(GTK_TEXT(log
),
138 *buffer
== '!' ? &red
: NULL
,
140 buffer
+ 1, message_len
- 1);
144 g_print("Child died in the middle of a message.\n");
147 /* The child is dead */
150 fclose(gui_side
->to_child
);
151 close(gui_side
->from_child
);
152 gdk_input_remove(gui_side
->input_tag
);
154 if (gui_side
->errors
)
157 report
= g_string_new(NULL
);
158 g_string_sprintf(report
, "There %s %d error%s.\n",
159 gui_side
->errors
== 1 ? "was" : "were",
161 gui_side
->errors
== 1 ? "" : "s");
162 gtk_text_insert(GTK_TEXT(log
), NULL
, &red
, NULL
,
163 report
->str
, report
->len
);
165 g_string_free(report
, TRUE
);
167 else if (gui_side
->show_info
== FALSE
)
168 gtk_widget_destroy(gui_side
->window
);
171 /* Scans src_dir, updating dest_path whenever cb returns TRUE */
172 static void for_dir_contents(ForDirCB
*cb
, char *src_dir
, char *dest_path
)
176 GSList
*list
= NULL
, *next
;
178 d
= opendir(src_dir
);
187 while ((ent
= readdir(d
)))
189 if (ent
->d_name
[0] == '.' && (ent
->d_name
[1] == '\0'
190 || (ent
->d_name
[1] == '.' && ent
->d_name
[2] == '\0')))
192 list
= g_slist_append(list
, g_strdup(make_path(src_dir
,
204 if (cb((char *) next
->data
, dest_path
))
206 g_string_sprintf(message
, "+%s", dest_path
);
217 /* Read this many bytes into the buffer. TRUE on success. */
218 static gboolean
read_exact(int source
, char *buffer
, ssize_t len
)
223 got
= read(source
, buffer
, len
);
232 /* Send 'message' to our parent process. TRUE on success. */
233 static gboolean
send()
238 g_return_val_if_fail(message
->len
< 0xffff, FALSE
);
240 sprintf(len_buffer
, "%04x", message
->len
);
241 fwrite(len_buffer
, 1, 4, to_parent
);
242 len
= fwrite(message
->str
, 1, message
->len
, to_parent
);
244 return len
== message
->len
;
247 /* Set the directory indicator at the top of the window */
248 static gboolean
send_dir(char *dir
)
250 g_string_sprintf(message
, "/%s", dir
);
254 static gboolean
send_error()
256 g_string_sprintf(message
, "!ERROR: %s\n", g_strerror(errno
));
260 static void button_reply(GtkWidget
*button
, GUIside
*gui_side
)
264 text
= gtk_object_get_data(GTK_OBJECT(button
), "send-code");
265 g_return_if_fail(text
!= NULL
);
266 fputc(*text
, gui_side
->to_child
);
267 fflush(gui_side
->to_child
);
269 gtk_widget_set_sensitive(gui_side
->actions
, FALSE
);
272 /* Get one char from fd. Quit on error. */
273 static char reply(int fd
)
278 len
= read(fd
, &retval
, 1);
282 fprintf(stderr
, "read() error: %s\n", g_strerror(errno
));
283 _exit(1); /* Parent died? */
287 static void destroy_action_window(GtkWidget
*widget
, gpointer data
)
289 GUIside
*gui_side
= (GUIside
*) data
;
293 kill(gui_side
->child
, SIGTERM
);
294 fclose(gui_side
->to_child
);
295 close(gui_side
->from_child
);
296 gdk_input_remove(gui_side
->input_tag
);
299 if (gui_side
->next_dir
)
301 gtk_timeout_remove(gui_side
->next_timer
);
302 g_free(gui_side
->next_dir
);
306 if (--number_of_windows
< 1)
310 /* Create two pipes, fork() a child and return a pointer to a GUIside struct
311 * (NULL on failure). The child calls func().
313 static GUIside
*start_action(gpointer data
, ActionChild
*func
)
315 int filedes
[4]; /* 0 and 2 are for reading */
318 GtkWidget
*vbox
, *button
, *control
, *hbox
, *scrollbar
;
322 report_error("ROX-Filer", g_strerror(errno
));
326 if (pipe(filedes
+ 2))
330 report_error("ROX-Filer", g_strerror(errno
));
338 report_error("ROX-Filer", g_strerror(errno
));
341 /* We are the child */
342 message
= g_string_new(NULL
);
345 to_parent
= fdopen(filedes
[1], "wb");
346 from_parent
= filedes
[2];
351 /* We are the parent */
354 gui_side
= g_malloc(sizeof(GUIside
));
355 gui_side
->from_child
= filedes
[0];
356 gui_side
->to_child
= fdopen(filedes
[3], "wb");
357 gui_side
->log
= NULL
;
358 gui_side
->child
= child
;
359 gui_side
->errors
= 0;
360 gui_side
->show_info
= FALSE
;
362 gui_side
->window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
363 gtk_window_set_default_size(GTK_WINDOW(gui_side
->window
), 500, 100);
364 gtk_signal_connect(GTK_OBJECT(gui_side
->window
), "destroy",
365 GTK_SIGNAL_FUNC(destroy_action_window
), gui_side
);
367 vbox
= gtk_vbox_new(FALSE
, 4);
368 gtk_container_add(GTK_CONTAINER(gui_side
->window
), vbox
);
370 gui_side
->dir
= gtk_label_new("<dir>");
371 gui_side
->next_dir
= NULL
;
372 gtk_misc_set_alignment(GTK_MISC(gui_side
->dir
), 0.5, 0.5);
373 gtk_box_pack_start(GTK_BOX(vbox
), gui_side
->dir
, FALSE
, TRUE
, 0);
375 hbox
= gtk_hbox_new(FALSE
, 0);
376 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, TRUE
, 0);
378 gui_side
->log
= gtk_text_new(NULL
, NULL
);
379 gtk_box_pack_start(GTK_BOX(hbox
), gui_side
->log
, TRUE
, TRUE
, 0);
380 scrollbar
= gtk_vscrollbar_new(GTK_TEXT(gui_side
->log
)->vadj
);
381 gtk_box_pack_start(GTK_BOX(hbox
), scrollbar
, FALSE
, TRUE
, 0);
383 gui_side
->actions
= gtk_hbox_new(TRUE
, 4);
384 gtk_box_pack_start(GTK_BOX(vbox
), gui_side
->actions
, FALSE
, TRUE
, 0);
386 button
= gtk_button_new_with_label("Always");
387 gtk_object_set_data(GTK_OBJECT(button
), "send-code", "A");
388 gtk_box_pack_start(GTK_BOX(gui_side
->actions
), button
, TRUE
, TRUE
, 0);
389 gtk_signal_connect(GTK_OBJECT(button
), "clicked",
390 button_reply
, gui_side
);
391 button
= gtk_button_new_with_label("Yes");
392 gtk_object_set_data(GTK_OBJECT(button
), "send-code", "Y");
393 gtk_box_pack_start(GTK_BOX(gui_side
->actions
), button
, TRUE
, TRUE
, 0);
394 gtk_signal_connect(GTK_OBJECT(button
), "clicked",
395 button_reply
, gui_side
);
396 button
= gtk_button_new_with_label("No");
397 gtk_object_set_data(GTK_OBJECT(button
), "send-code", "N");
398 gtk_box_pack_start(GTK_BOX(gui_side
->actions
), button
, TRUE
, TRUE
, 0);
399 gtk_signal_connect(GTK_OBJECT(button
), "clicked",
400 button_reply
, gui_side
);
401 gtk_widget_set_sensitive(gui_side
->actions
, FALSE
);
403 control
= gtk_hbox_new(TRUE
, 4);
404 gtk_box_pack_start(GTK_BOX(vbox
), control
, FALSE
, TRUE
, 0);
406 button
= gtk_button_new_with_label("Suspend");
407 gtk_box_pack_start(GTK_BOX(control
), button
, TRUE
, TRUE
, 0);
409 button
= gtk_button_new_with_label("Resume");
410 gtk_widget_set_sensitive(button
, FALSE
);
411 gtk_box_pack_start(GTK_BOX(control
), button
, TRUE
, TRUE
, 0);
413 button
= gtk_button_new_with_label("Abort");
414 gtk_box_pack_start(GTK_BOX(control
), button
, TRUE
, TRUE
, 0);
415 gtk_signal_connect_object(GTK_OBJECT(button
), "clicked",
416 gtk_widget_destroy
, GTK_OBJECT(gui_side
->window
));
418 gui_side
->input_tag
= gdk_input_add(gui_side
->from_child
,
426 /* ACTIONS ON ONE ITEM */
428 /* These may call themselves recursively, or ask questions, etc.
429 * TRUE iff the directory containing dest_path needs to be rescanned.
432 /* dest_path is the dir containing src_path.
433 * Updates the global size_tally.
435 static gboolean
do_usage(char *src_path
, char *dest_path
)
439 if (lstat(src_path
, &info
))
441 g_string_sprintf(message
, "'%s:\n", src_path
);
447 if (S_ISREG(info
.st_mode
) || S_ISLNK(info
.st_mode
))
448 size_tally
+= info
.st_size
;
449 else if (S_ISDIR(info
.st_mode
))
452 safe_path
= g_strdup(src_path
);
453 for_dir_contents(do_usage
, safe_path
, safe_path
);
460 /* dest_path is the dir containing src_path */
461 static gboolean
do_delete(char *src_path
, char *dest_path
)
467 if (lstat(src_path
, &info
))
473 write_prot
= S_ISLNK(info
.st_mode
) ? FALSE
474 : access(src_path
, W_OK
) != 0;
475 if (quiet
== 0 || write_prot
)
477 g_string_sprintf(message
, "?Delete %s'%s'?\n",
478 write_prot
? "WRITE-PROTECTED " : " ",
481 rep
= reply(from_parent
);
487 if (S_ISDIR(info
.st_mode
))
490 safe_path
= g_strdup(src_path
);
491 for_dir_contents(do_delete
, safe_path
, safe_path
);
492 if (rmdir(safe_path
))
498 g_string_assign(message
, "'Directory deleted\n");
502 else if (unlink(src_path
) == 0)
504 g_string_sprintf(message
, "'Deleted '%s'\n", src_path
);
516 static gboolean
do_copy(char *path
, char *dest
)
521 gboolean retval
= TRUE
;
523 leaf
= strrchr(path
, '/');
525 leaf
= path
; /* Error? */
529 dest_path
= make_path(dest
, leaf
)->str
;
531 g_string_sprintf(message
, "'Copying %s as %s\n", path
, dest_path
);
534 if (access(dest_path
, F_OK
) == 0)
537 g_string_sprintf(message
, "?'%s' already exists - overwrite?\n",
541 rep
= reply(from_parent
);
548 if (lstat(path
, &info
))
554 if (S_ISDIR(info
.st_mode
))
556 char *safe_path
, *safe_dest
;
557 struct stat dest_info
;
560 /* (we will do the update ourselves now, rather than
565 safe_path
= g_strdup(path
);
566 safe_dest
= g_strdup(dest_path
);
568 exists
= !lstat(dest_path
, &dest_info
);
570 if (exists
&& !S_ISDIR(dest_info
.st_mode
))
572 g_string_sprintf(message
,
573 "!ERROR: Destination already exists, "
574 "but is not a directory\n");
576 else if (exists
== FALSE
&& mkdir(dest_path
, info
.st_mode
))
582 /* (just been created then) */
583 g_string_sprintf(message
, "+%s", dest
);
587 for_dir_contents(do_copy
, safe_path
, safe_dest
);
588 /* Note: dest_path now invalid... */
596 g_string_sprintf(message
, "cp -af %s %s", path
, dest_path
);
597 if (system(message
->str
))
599 g_string_sprintf(message
, "!ERROR: %s\n",
609 static gboolean
do_move(char *path
, char *dest
)
613 gboolean retval
= TRUE
;
615 leaf
= strrchr(path
, '/');
617 leaf
= path
; /* Error? */
621 dest_path
= make_path(dest
, leaf
)->str
;
623 g_string_sprintf(message
, "'Moving %s into %s...\n", path
, dest
);
626 if (access(dest_path
, F_OK
) == 0)
631 g_string_sprintf(message
, "?'%s' already exists - overwrite?\n",
635 rep
= reply(from_parent
);
641 if (lstat(dest_path
, &info
))
647 if (S_ISDIR(info
.st_mode
))
649 char *safe_path
, *safe_dest
;
651 safe_path
= g_strdup(path
);
652 safe_dest
= g_strdup(dest_path
);
653 for_dir_contents(do_move
, safe_path
, safe_dest
);
655 if (rmdir(safe_path
))
661 g_string_assign(message
, "'Directory deleted\n");
664 g_string_sprintf(message
, "+%s", path
);
665 g_string_truncate(message
, leaf
- path
);
671 g_string_sprintf(message
, "mv -f %s %s", path
, dest
);
672 if (system(message
->str
) == 0)
674 g_string_sprintf(message
, "+%s", path
);
675 g_string_truncate(message
, leaf
- path
);
680 g_string_sprintf(message
, "!ERROR: Failed to move %s as %s\n",
689 static gboolean
do_link(char *path
, char *dest
)
694 leaf
= strrchr(path
, '/');
696 leaf
= path
; /* Error? */
700 dest_path
= make_path(dest
, leaf
)->str
;
702 if (symlink(path
, dest_path
))
709 g_string_sprintf(message
, "'Symlinked %s as %s\n",
717 /* CHILD MAIN LOOPS */
719 /* After forking, the child calls one of these functions */
721 static void usage_cb(gpointer data
)
723 FilerWindow
*filer_window
= (FilerWindow
*) data
;
724 Collection
*collection
= filer_window
->collection
;
726 int left
= collection
->number_selected
;
728 off_t total_size
= 0;
730 send_dir(filer_window
->path
);
735 if (!collection
->items
[i
].selected
)
737 item
= (DirItem
*) collection
->items
[i
].data
;
739 do_usage(make_path(filer_window
->path
,
740 item
->leafname
)->str
,
742 g_string_sprintf(message
, "'%s: %s\n",
744 format_size((unsigned long) size_tally
));
746 total_size
+= size_tally
;
750 g_string_sprintf(message
, "'\nTotal: %s\n",
751 format_size((unsigned long) total_size
));
755 #ifdef DO_MOUNT_POINTS
756 static void mount_cb(gpointer data
)
758 FilerWindow
*filer_window
= (FilerWindow
*) data
;
759 Collection
*collection
= filer_window
->collection
;
763 gboolean mount_points
= FALSE
;
765 send_dir(filer_window
->path
);
767 for (i
= 0; i
< collection
->number_of_items
; i
++)
769 if (!collection
->items
[i
].selected
)
771 item
= (DirItem
*) collection
->items
[i
].data
;
772 if (!(item
->flags
& ITEM_FLAG_MOUNT_POINT
))
776 command
= g_strdup_printf("%smount %s",
777 item
->flags
& ITEM_FLAG_MOUNTED
? "u" : "",
778 make_path(filer_window
->path
, item
->leafname
)->str
);
779 g_string_sprintf(message
, "'> %s\n", command
);
781 if (system(command
) == 0)
783 g_string_sprintf(message
, "+%s", filer_window
->path
);
788 g_string_sprintf(message
, "!Operation failed.\n");
796 g_string_sprintf(message
, "!No mount points selected!\n");
800 g_string_sprintf(message
, "'\nDone\n");
805 static void delete_cb(gpointer data
)
807 FilerWindow
*filer_window
= (FilerWindow
*) data
;
808 Collection
*collection
= filer_window
->collection
;
810 int left
= collection
->number_selected
;
813 send_dir(filer_window
->path
);
818 if (!collection
->items
[i
].selected
)
820 item
= (DirItem
*) collection
->items
[i
].data
;
821 if (do_delete(make_path(filer_window
->path
,
822 item
->leafname
)->str
,
825 g_string_sprintf(message
, "+%s", filer_window
->path
);
831 g_string_sprintf(message
, "'\nDone\n");
835 static void list_cb(gpointer data
)
837 GSList
*paths
= (GSList
*) data
;
841 send_dir((char *) paths
->data
);
843 if (action_do_func((char *) paths
->data
, action_dest
))
845 g_string_sprintf(message
, "+%s", action_dest
);
852 g_string_sprintf(message
, "'\nDone\n");
856 /* EXTERNAL INTERFACE */
858 /* Count disk space used by selected items */
859 void action_usage(FilerWindow
*filer_window
)
862 Collection
*collection
;
864 collection
= filer_window
->collection
;
866 if (collection
->number_selected
< 1)
868 report_error("ROX-Filer", "You need to select some items "
873 gui_side
= start_action(filer_window
, usage_cb
);
877 gui_side
->show_info
= TRUE
;
879 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Disk Usage");
881 gtk_widget_show_all(gui_side
->window
);
884 /* Mount/unmount all selected mount points */
885 void action_mount(FilerWindow
*filer_window
)
887 #ifdef DO_MOUNT_POINTS
889 Collection
*collection
;
891 collection
= filer_window
->collection
;
893 if (collection
->number_selected
< 1)
895 report_error("ROX-Filer", "You need to select some items "
896 "to mount or unmount");
900 gui_side
= start_action(filer_window
, mount_cb
);
904 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Mount / Unmount");
906 gtk_widget_show_all(gui_side
->window
);
908 report_error("ROX-Filer",
909 "ROX-Filer does not yet support mount points on your "
911 #endif /* DO_MOUNT_POINTS */
914 /* Deletes all selected items in the window */
915 void action_delete(FilerWindow
*filer_window
)
918 Collection
*collection
;
920 collection
= filer_window
->collection
;
922 if (collection
->number_selected
< 1)
924 report_error("ROX-Filer", "You need to select some items "
929 gui_side
= start_action(filer_window
, delete_cb
);
933 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Delete");
935 gtk_widget_show_all(gui_side
->window
);
938 void action_copy(GSList
*paths
, char *dest
)
943 action_do_func
= do_copy
;
944 gui_side
= start_action(paths
, list_cb
);
948 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Copy");
950 gtk_widget_show_all(gui_side
->window
);
953 void action_move(GSList
*paths
, char *dest
)
958 action_do_func
= do_move
;
959 gui_side
= start_action(paths
, list_cb
);
963 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Move");
965 gtk_widget_show_all(gui_side
->window
);
968 void action_link(GSList
*paths
, char *dest
)
973 action_do_func
= do_link
;
974 gui_side
= start_action(paths
, list_cb
);
978 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Link");
980 gtk_widget_show_all(gui_side
->window
);