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 */
74 static DirItem
*mount_item
;
76 /* Static prototypes */
77 static gboolean
send();
78 static gboolean
send_error();
79 static gboolean
send_dir(char *dir
);
80 static gboolean
read_exact(int source
, char *buffer
, ssize_t len
);
81 static void do_mount(FilerWindow
*filer_window
, DirItem
*item
);
83 static gboolean
display_dir(gpointer data
)
85 GUIside
*gui_side
= (GUIside
*) data
;
87 gtk_label_set_text(GTK_LABEL(gui_side
->dir
), gui_side
->next_dir
+ 1);
88 g_free(gui_side
->next_dir
);
89 gui_side
->next_dir
= NULL
;
94 /* Called when the child sends us a message */
95 static void message_from_child(gpointer data
,
97 GdkInputCondition condition
)
100 GUIside
*gui_side
= (GUIside
*) data
;
101 GtkWidget
*log
= gui_side
->log
;
103 if (read_exact(source
, buf
, 4))
109 message_len
= strtol(buf
, NULL
, 16);
110 buffer
= g_malloc(message_len
+ 1);
111 if (message_len
> 0 && read_exact(source
, buffer
, message_len
))
113 buffer
[message_len
] = '\0';
115 gtk_widget_set_sensitive(gui_side
->actions
,
117 else if (*buffer
== '+')
119 refresh_dirs(buffer
+ 1);
123 else if (*buffer
== 'm')
125 filer_check_mounted(buffer
+ 1);
129 else if (*buffer
== '/')
131 if (gui_side
->next_dir
)
132 g_free(gui_side
->next_dir
);
134 gui_side
->next_timer
=
138 gui_side
->next_dir
= buffer
;
141 else if (*buffer
== '!')
144 gtk_text_insert(GTK_TEXT(log
),
146 *buffer
== '!' ? &red
: NULL
,
148 buffer
+ 1, message_len
- 1);
152 g_print("Child died in the middle of a message.\n");
155 /* The child is dead */
158 fclose(gui_side
->to_child
);
159 close(gui_side
->from_child
);
160 gdk_input_remove(gui_side
->input_tag
);
162 if (gui_side
->errors
)
165 report
= g_string_new(NULL
);
166 g_string_sprintf(report
, "There %s %d error%s.\n",
167 gui_side
->errors
== 1 ? "was" : "were",
169 gui_side
->errors
== 1 ? "" : "s");
170 gtk_text_insert(GTK_TEXT(log
), NULL
, &red
, NULL
,
171 report
->str
, report
->len
);
173 g_string_free(report
, TRUE
);
175 else if (gui_side
->show_info
== FALSE
)
176 gtk_widget_destroy(gui_side
->window
);
179 /* Scans src_dir, updating dest_path whenever cb returns TRUE */
180 static void for_dir_contents(ForDirCB
*cb
, char *src_dir
, char *dest_path
)
184 GSList
*list
= NULL
, *next
;
186 d
= opendir(src_dir
);
195 while ((ent
= readdir(d
)))
197 if (ent
->d_name
[0] == '.' && (ent
->d_name
[1] == '\0'
198 || (ent
->d_name
[1] == '.' && ent
->d_name
[2] == '\0')))
200 list
= g_slist_append(list
, g_strdup(make_path(src_dir
,
212 if (cb((char *) next
->data
, dest_path
))
214 g_string_sprintf(message
, "+%s", dest_path
);
225 /* Read this many bytes into the buffer. TRUE on success. */
226 static gboolean
read_exact(int source
, char *buffer
, ssize_t len
)
231 got
= read(source
, buffer
, len
);
240 /* Send 'message' to our parent process. TRUE on success. */
241 static gboolean
send()
246 g_return_val_if_fail(message
->len
< 0xffff, FALSE
);
248 sprintf(len_buffer
, "%04x", message
->len
);
249 fwrite(len_buffer
, 1, 4, to_parent
);
250 len
= fwrite(message
->str
, 1, message
->len
, to_parent
);
252 return len
== message
->len
;
255 /* Set the directory indicator at the top of the window */
256 static gboolean
send_dir(char *dir
)
258 g_string_sprintf(message
, "/%s", dir
);
262 static gboolean
send_error()
264 g_string_sprintf(message
, "!ERROR: %s\n", g_strerror(errno
));
268 static void button_reply(GtkWidget
*button
, GUIside
*gui_side
)
272 text
= gtk_object_get_data(GTK_OBJECT(button
), "send-code");
273 g_return_if_fail(text
!= NULL
);
274 fputc(*text
, gui_side
->to_child
);
275 fflush(gui_side
->to_child
);
277 gtk_widget_set_sensitive(gui_side
->actions
, FALSE
);
280 /* Get one char from fd. Quit on error. */
281 static char reply(int fd
)
286 len
= read(fd
, &retval
, 1);
290 fprintf(stderr
, "read() error: %s\n", g_strerror(errno
));
291 _exit(1); /* Parent died? */
295 static void destroy_action_window(GtkWidget
*widget
, gpointer data
)
297 GUIside
*gui_side
= (GUIside
*) data
;
301 kill(gui_side
->child
, SIGTERM
);
302 fclose(gui_side
->to_child
);
303 close(gui_side
->from_child
);
304 gdk_input_remove(gui_side
->input_tag
);
307 if (gui_side
->next_dir
)
309 gtk_timeout_remove(gui_side
->next_timer
);
310 g_free(gui_side
->next_dir
);
314 if (--number_of_windows
< 1)
318 /* Create two pipes, fork() a child and return a pointer to a GUIside struct
319 * (NULL on failure). The child calls func().
321 static GUIside
*start_action(gpointer data
, ActionChild
*func
)
323 int filedes
[4]; /* 0 and 2 are for reading */
326 GtkWidget
*vbox
, *button
, *control
, *hbox
, *scrollbar
;
330 report_error("ROX-Filer", g_strerror(errno
));
334 if (pipe(filedes
+ 2))
338 report_error("ROX-Filer", g_strerror(errno
));
346 report_error("ROX-Filer", g_strerror(errno
));
349 /* We are the child */
350 message
= g_string_new(NULL
);
353 to_parent
= fdopen(filedes
[1], "wb");
354 from_parent
= filedes
[2];
359 /* We are the parent */
362 gui_side
= g_malloc(sizeof(GUIside
));
363 gui_side
->from_child
= filedes
[0];
364 gui_side
->to_child
= fdopen(filedes
[3], "wb");
365 gui_side
->log
= NULL
;
366 gui_side
->child
= child
;
367 gui_side
->errors
= 0;
368 gui_side
->show_info
= FALSE
;
370 gui_side
->window
= gtk_window_new(GTK_WINDOW_TOPLEVEL
);
371 gtk_window_set_default_size(GTK_WINDOW(gui_side
->window
), 500, 100);
372 gtk_signal_connect(GTK_OBJECT(gui_side
->window
), "destroy",
373 GTK_SIGNAL_FUNC(destroy_action_window
), gui_side
);
375 vbox
= gtk_vbox_new(FALSE
, 4);
376 gtk_container_add(GTK_CONTAINER(gui_side
->window
), vbox
);
378 gui_side
->dir
= gtk_label_new("<dir>");
379 gui_side
->next_dir
= NULL
;
380 gtk_misc_set_alignment(GTK_MISC(gui_side
->dir
), 0.5, 0.5);
381 gtk_box_pack_start(GTK_BOX(vbox
), gui_side
->dir
, FALSE
, TRUE
, 0);
383 hbox
= gtk_hbox_new(FALSE
, 0);
384 gtk_box_pack_start(GTK_BOX(vbox
), hbox
, TRUE
, TRUE
, 0);
386 gui_side
->log
= gtk_text_new(NULL
, NULL
);
387 gtk_box_pack_start(GTK_BOX(hbox
), gui_side
->log
, TRUE
, TRUE
, 0);
388 scrollbar
= gtk_vscrollbar_new(GTK_TEXT(gui_side
->log
)->vadj
);
389 gtk_box_pack_start(GTK_BOX(hbox
), scrollbar
, FALSE
, TRUE
, 0);
391 gui_side
->actions
= gtk_hbox_new(TRUE
, 4);
392 gtk_box_pack_start(GTK_BOX(vbox
), gui_side
->actions
, FALSE
, TRUE
, 0);
394 button
= gtk_button_new_with_label("Always");
395 gtk_object_set_data(GTK_OBJECT(button
), "send-code", "A");
396 gtk_box_pack_start(GTK_BOX(gui_side
->actions
), button
, TRUE
, TRUE
, 0);
397 gtk_signal_connect(GTK_OBJECT(button
), "clicked",
398 button_reply
, gui_side
);
399 button
= gtk_button_new_with_label("Yes");
400 gtk_object_set_data(GTK_OBJECT(button
), "send-code", "Y");
401 gtk_box_pack_start(GTK_BOX(gui_side
->actions
), button
, TRUE
, TRUE
, 0);
402 gtk_signal_connect(GTK_OBJECT(button
), "clicked",
403 button_reply
, gui_side
);
404 button
= gtk_button_new_with_label("No");
405 gtk_object_set_data(GTK_OBJECT(button
), "send-code", "N");
406 gtk_box_pack_start(GTK_BOX(gui_side
->actions
), button
, TRUE
, TRUE
, 0);
407 gtk_signal_connect(GTK_OBJECT(button
), "clicked",
408 button_reply
, gui_side
);
409 gtk_widget_set_sensitive(gui_side
->actions
, FALSE
);
411 control
= gtk_hbox_new(TRUE
, 4);
412 gtk_box_pack_start(GTK_BOX(vbox
), control
, FALSE
, TRUE
, 0);
414 button
= gtk_button_new_with_label("Suspend");
415 gtk_box_pack_start(GTK_BOX(control
), button
, TRUE
, TRUE
, 0);
417 button
= gtk_button_new_with_label("Resume");
418 gtk_widget_set_sensitive(button
, FALSE
);
419 gtk_box_pack_start(GTK_BOX(control
), button
, TRUE
, TRUE
, 0);
421 button
= gtk_button_new_with_label("Abort");
422 gtk_box_pack_start(GTK_BOX(control
), button
, TRUE
, TRUE
, 0);
423 gtk_signal_connect_object(GTK_OBJECT(button
), "clicked",
424 gtk_widget_destroy
, GTK_OBJECT(gui_side
->window
));
426 gui_side
->input_tag
= gdk_input_add(gui_side
->from_child
,
434 /* ACTIONS ON ONE ITEM */
436 /* These may call themselves recursively, or ask questions, etc.
437 * TRUE iff the directory containing dest_path needs to be rescanned.
440 /* dest_path is the dir containing src_path.
441 * Updates the global size_tally.
443 static gboolean
do_usage(char *src_path
, char *dest_path
)
447 if (lstat(src_path
, &info
))
449 g_string_sprintf(message
, "'%s:\n", src_path
);
455 if (S_ISREG(info
.st_mode
) || S_ISLNK(info
.st_mode
))
456 size_tally
+= info
.st_size
;
457 else if (S_ISDIR(info
.st_mode
))
460 safe_path
= g_strdup(src_path
);
461 for_dir_contents(do_usage
, safe_path
, safe_path
);
468 /* dest_path is the dir containing src_path */
469 static gboolean
do_delete(char *src_path
, char *dest_path
)
475 if (lstat(src_path
, &info
))
481 write_prot
= S_ISLNK(info
.st_mode
) ? FALSE
482 : access(src_path
, W_OK
) != 0;
483 if (quiet
== 0 || write_prot
)
485 g_string_sprintf(message
, "?Delete %s'%s'?\n",
486 write_prot
? "WRITE-PROTECTED " : " ",
489 rep
= reply(from_parent
);
495 if (S_ISDIR(info
.st_mode
))
498 safe_path
= g_strdup(src_path
);
499 for_dir_contents(do_delete
, safe_path
, safe_path
);
500 if (rmdir(safe_path
))
506 g_string_assign(message
, "'Directory deleted\n");
510 else if (unlink(src_path
) == 0)
512 g_string_sprintf(message
, "'Deleted '%s'\n", src_path
);
524 static gboolean
do_copy(char *path
, char *dest
)
529 gboolean retval
= TRUE
;
531 leaf
= strrchr(path
, '/');
533 leaf
= path
; /* Error? */
537 dest_path
= make_path(dest
, leaf
)->str
;
539 g_string_sprintf(message
, "'Copying %s as %s\n", path
, dest_path
);
542 if (access(dest_path
, F_OK
) == 0)
545 g_string_sprintf(message
, "?'%s' already exists - overwrite?\n",
549 rep
= reply(from_parent
);
556 if (lstat(path
, &info
))
562 if (S_ISDIR(info
.st_mode
))
564 char *safe_path
, *safe_dest
;
565 struct stat dest_info
;
568 /* (we will do the update ourselves now, rather than
573 safe_path
= g_strdup(path
);
574 safe_dest
= g_strdup(dest_path
);
576 exists
= !lstat(dest_path
, &dest_info
);
578 if (exists
&& !S_ISDIR(dest_info
.st_mode
))
580 g_string_sprintf(message
,
581 "!ERROR: Destination already exists, "
582 "but is not a directory\n");
584 else if (exists
== FALSE
&& mkdir(dest_path
, info
.st_mode
))
590 /* (just been created then) */
591 g_string_sprintf(message
, "+%s", dest
);
595 for_dir_contents(do_copy
, safe_path
, safe_dest
);
596 /* Note: dest_path now invalid... */
604 g_string_sprintf(message
, "cp -af %s %s", path
, dest_path
);
605 if (system(message
->str
))
607 g_string_sprintf(message
, "!ERROR: %s\n",
617 static gboolean
do_move(char *path
, char *dest
)
621 gboolean retval
= TRUE
;
623 leaf
= strrchr(path
, '/');
625 leaf
= path
; /* Error? */
629 dest_path
= make_path(dest
, leaf
)->str
;
631 g_string_sprintf(message
, "'Moving %s into %s...\n", path
, dest
);
634 if (access(dest_path
, F_OK
) == 0)
639 g_string_sprintf(message
, "?'%s' already exists - overwrite?\n",
643 rep
= reply(from_parent
);
649 if (lstat(dest_path
, &info
))
655 if (S_ISDIR(info
.st_mode
))
657 char *safe_path
, *safe_dest
;
659 safe_path
= g_strdup(path
);
660 safe_dest
= g_strdup(dest_path
);
661 for_dir_contents(do_move
, safe_path
, safe_dest
);
663 if (rmdir(safe_path
))
669 g_string_assign(message
, "'Directory deleted\n");
672 g_string_sprintf(message
, "+%s", path
);
673 g_string_truncate(message
, leaf
- path
);
679 g_string_sprintf(message
, "mv -f %s %s", path
, dest
);
680 if (system(message
->str
) == 0)
682 g_string_sprintf(message
, "+%s", path
);
683 g_string_truncate(message
, leaf
- path
);
688 g_string_sprintf(message
, "!ERROR: Failed to move %s as %s\n",
697 static gboolean
do_link(char *path
, char *dest
)
702 leaf
= strrchr(path
, '/');
704 leaf
= path
; /* Error? */
708 dest_path
= make_path(dest
, leaf
)->str
;
710 if (symlink(path
, dest_path
))
717 g_string_sprintf(message
, "'Symlinked %s as %s\n",
725 /* Mount/umount this item */
726 static void do_mount(FilerWindow
*filer_window
, DirItem
*item
)
731 path
= make_path(filer_window
->path
, item
->leafname
)->str
;
733 command
= g_strdup_printf("%smount %s",
734 item
->flags
& ITEM_FLAG_MOUNTED
? "u" : "", path
);
735 g_string_sprintf(message
, "'> %s\n", command
);
737 if (system(command
) == 0)
739 g_string_sprintf(message
, "+%s", filer_window
->path
);
741 g_string_sprintf(message
, "m%s", path
);
746 g_string_sprintf(message
, "!Operation failed.\n");
752 /* CHILD MAIN LOOPS */
754 /* After forking, the child calls one of these functions */
756 static void usage_cb(gpointer data
)
758 FilerWindow
*filer_window
= (FilerWindow
*) data
;
759 Collection
*collection
= filer_window
->collection
;
761 int left
= collection
->number_selected
;
763 off_t total_size
= 0;
765 send_dir(filer_window
->path
);
770 if (!collection
->items
[i
].selected
)
772 item
= (DirItem
*) collection
->items
[i
].data
;
774 do_usage(make_path(filer_window
->path
,
775 item
->leafname
)->str
,
777 g_string_sprintf(message
, "'%s: %s\n",
779 format_size((unsigned long) size_tally
));
781 total_size
+= size_tally
;
785 g_string_sprintf(message
, "'\nTotal: %s\n",
786 format_size((unsigned long) total_size
));
790 #ifdef DO_MOUNT_POINTS
791 static void mount_cb(gpointer data
)
793 FilerWindow
*filer_window
= (FilerWindow
*) data
;
794 Collection
*collection
= filer_window
->collection
;
797 gboolean mount_points
= FALSE
;
799 send_dir(filer_window
->path
);
802 do_mount(filer_window
, mount_item
);
805 for (i
= 0; i
< collection
->number_of_items
; i
++)
807 if (!collection
->items
[i
].selected
)
809 item
= (DirItem
*) collection
->items
[i
].data
;
810 if (!(item
->flags
& ITEM_FLAG_MOUNT_POINT
))
814 do_mount(filer_window
, item
);
819 g_string_sprintf(message
,
820 "!No mount points selected!\n");
825 g_string_sprintf(message
, "'\nDone\n");
830 static void delete_cb(gpointer data
)
832 FilerWindow
*filer_window
= (FilerWindow
*) data
;
833 Collection
*collection
= filer_window
->collection
;
835 int left
= collection
->number_selected
;
838 send_dir(filer_window
->path
);
843 if (!collection
->items
[i
].selected
)
845 item
= (DirItem
*) collection
->items
[i
].data
;
846 if (do_delete(make_path(filer_window
->path
,
847 item
->leafname
)->str
,
850 g_string_sprintf(message
, "+%s", filer_window
->path
);
856 g_string_sprintf(message
, "'\nDone\n");
860 static void list_cb(gpointer data
)
862 GSList
*paths
= (GSList
*) data
;
866 send_dir((char *) paths
->data
);
868 if (action_do_func((char *) paths
->data
, action_dest
))
870 g_string_sprintf(message
, "+%s", action_dest
);
877 g_string_sprintf(message
, "'\nDone\n");
881 /* EXTERNAL INTERFACE */
883 /* Count disk space used by selected items */
884 void action_usage(FilerWindow
*filer_window
)
887 Collection
*collection
;
889 collection
= filer_window
->collection
;
891 if (collection
->number_selected
< 1)
893 report_error("ROX-Filer", "You need to select some items "
898 gui_side
= start_action(filer_window
, usage_cb
);
902 gui_side
->show_info
= TRUE
;
904 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Disk Usage");
906 gtk_widget_show_all(gui_side
->window
);
909 /* Mount/unmount 'item', or all selected mount points if NULL. */
910 void action_mount(FilerWindow
*filer_window
, DirItem
*item
)
912 #ifdef DO_MOUNT_POINTS
914 Collection
*collection
;
916 collection
= filer_window
->collection
;
918 if (item
== NULL
&& collection
->number_selected
< 1)
920 report_error("ROX-Filer", "You need to select some items "
921 "to mount or unmount");
926 gui_side
= start_action(filer_window
, mount_cb
);
930 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Mount / Unmount");
932 gtk_widget_show_all(gui_side
->window
);
934 report_error("ROX-Filer",
935 "ROX-Filer does not yet support mount points on your "
937 #endif /* DO_MOUNT_POINTS */
940 /* Deletes all selected items in the window */
941 void action_delete(FilerWindow
*filer_window
)
944 Collection
*collection
;
946 collection
= filer_window
->collection
;
948 if (collection
->number_selected
< 1)
950 report_error("ROX-Filer", "You need to select some items "
955 gui_side
= start_action(filer_window
, delete_cb
);
959 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Delete");
961 gtk_widget_show_all(gui_side
->window
);
964 void action_copy(GSList
*paths
, char *dest
)
969 action_do_func
= do_copy
;
970 gui_side
= start_action(paths
, list_cb
);
974 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Copy");
976 gtk_widget_show_all(gui_side
->window
);
979 void action_move(GSList
*paths
, char *dest
)
984 action_do_func
= do_move
;
985 gui_side
= start_action(paths
, list_cb
);
989 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Move");
991 gtk_widget_show_all(gui_side
->window
);
994 void action_link(GSList
*paths
, char *dest
)
999 action_do_func
= do_link
;
1000 gui_side
= start_action(paths
, list_cb
);
1004 gtk_window_set_title(GTK_WINDOW(gui_side
->window
), "Link");
1005 number_of_windows
++;
1006 gtk_widget_show_all(gui_side
->window
);