Applied Bernard Jungen's patch and suggestion:
[rox-filer/th.git] / ROX-Filer / src / infobox.c
blobc89ab6a8fb0504bf31733c629fa6dcdeacdd6da2
1 /*
2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* infobox.c - code for showing a file's attributes */
22 #include "config.h"
24 #include <errno.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <sys/param.h>
28 #include <signal.h>
29 #include <libxml/parser.h>
31 #include <gtk/gtk.h>
33 #include "global.h"
35 #include "support.h"
36 #include "main.h"
37 #include "gui_support.h"
38 #include "diritem.h"
39 #include "type.h"
40 #include "infobox.h"
41 #include "appinfo.h"
42 #include "dnd.h" /* For xa_string */
43 #include "run.h" /* For show_help_files() */
44 #include "xml.h"
45 #include "mount.h"
46 #include "pixmaps.h"
47 #include "xtypes.h"
48 #include "filer.h"
50 typedef struct _FileStatus FileStatus;
52 /* This is for the 'file(1) says...' thing */
53 struct _FileStatus
55 int fd; /* FD to read from, -1 if closed */
56 int input; /* Input watcher tag if fd valid */
57 GtkLabel *label; /* Widget to output to */
58 gchar *text; /* String so far */
61 typedef struct du {
62 gchar *path;
63 GtkListStore *store;
64 guint watch;
65 GIOChannel *chan;
66 gint child;
67 } DU;
69 typedef struct _Permissions Permissions;
71 struct _Permissions
73 gchar *path;
74 DirItem *item;
75 GtkWidget *bits[12];
78 /* Static prototypes */
79 static void refresh_info(GObject *window);
80 static GtkWidget *make_vbox(const guchar *path, GObject *window);
81 static GtkWidget *make_details(const guchar *path, DirItem *item,
82 GObject *window);
83 static GtkWidget *make_about(const guchar *path, XMLwrapper *ai);
84 static GtkWidget *make_about_desktop(const gchar *path);
85 static GtkWidget *make_file_says(const guchar *path);
86 static GtkWidget *make_permissions(const gchar *path, DirItem *item);
87 static GtkWidget *make_unmount_options(const gchar *path);
88 static void add_file_output(FileStatus *fs,
89 gint source, GdkInputCondition condition);
90 static const gchar *pretty_type(DirItem *file, const guchar *path);
91 static void got_response(GObject *window, gint response, gpointer data);
92 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs);
94 /****************************************************************
95 * EXTERNAL INTERFACE *
96 ****************************************************************/
98 /* Open each item in a new infobox. Confirms if there are a large
99 * number of items to show.
101 void infobox_show_list(GList *paths)
103 int n;
105 n = g_list_length(paths);
107 if (n >= 10)
109 gchar *message;
110 gboolean ok;
112 message = g_strdup_printf(
113 _("Are you sure you want to open %d windows?"), n);
114 ok = confirm(message, GTK_STOCK_YES, _("Show Info"));
115 g_free(message);
116 if (!ok)
117 return;
120 g_list_foreach(paths, (GFunc) infobox_new, NULL);
123 /* Create and display a new info box showing details about this item */
124 void infobox_new(const gchar *pathname)
126 GtkWidget *window, *details;
127 gchar *path;
128 GObject *owindow;
130 g_return_if_fail(pathname != NULL);
132 path = g_strdup(pathname); /* Gets attached to window & freed later */
134 window = gtk_dialog_new_with_buttons(
135 g_utf8_validate(path, -1, NULL) ? path
136 : _("(bad utf-8)"),
137 NULL, GTK_DIALOG_NO_SEPARATOR,
138 GTK_STOCK_CLOSE, GTK_RESPONSE_CANCEL,
139 GTK_STOCK_REFRESH, GTK_RESPONSE_APPLY,
140 NULL);
142 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
144 owindow = G_OBJECT(window);
145 details = make_vbox(path, owindow);
146 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(window)->vbox),
147 details);
149 g_object_set_data(owindow, "details", details);
150 g_object_set_data_full(owindow, "path", path, g_free);
152 g_signal_connect(window, "response", G_CALLBACK(got_response), NULL);
154 number_of_windows++;
155 gtk_widget_show_all(window);
158 /****************************************************************
159 * INTERNAL FUNCTIONS *
160 ****************************************************************/
162 static void got_response(GObject *window, gint response, gpointer data)
164 if (response == GTK_RESPONSE_APPLY)
165 refresh_info(window);
166 else
168 gtk_widget_destroy(GTK_WIDGET(window));
169 one_less_window();
173 static void refresh_info(GObject *window)
175 GtkWidget *details, *vbox;
176 guchar *path;
178 path = g_object_get_data(window, "path");
179 details = g_object_get_data(window, "details");
180 g_return_if_fail(details != NULL);
181 g_return_if_fail(path != NULL);
183 vbox = details->parent;
184 gtk_widget_destroy(details);
186 details = make_vbox(path, window);
187 g_object_set_data(window, "details", details);
188 gtk_box_pack_start_defaults(GTK_BOX(vbox), details);
189 gtk_widget_show_all(details);
192 static void add_frame(GtkBox *vbox, GtkWidget *list)
194 GtkWidget *frame;
196 frame = gtk_frame_new(NULL);
197 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
198 gtk_container_add(GTK_CONTAINER(frame), list);
199 gtk_box_pack_start_defaults(vbox, frame);
202 /* Create the VBox widget that contains the details.
203 * Note that 'path' must not be freed until the vbox is destroyed.
205 static GtkWidget *make_vbox(const guchar *path, GObject *window)
207 DirItem *item;
208 GtkBox *vbox;
209 XMLwrapper *ai;
210 xmlNode *about = NULL;
211 gchar *help_dir;
212 GtkWidget *hbox, *name, *label;
214 g_return_val_if_fail(path[0] == '/', NULL);
216 item = diritem_new(g_basename(path));
217 diritem_restat(path, item, NULL);
219 ai = appinfo_get(path, item);
220 if (ai)
221 about = xml_get_section(ai, NULL, "About");
223 vbox = GTK_BOX(gtk_vbox_new(FALSE, 4));
224 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
226 /* Heading, with icon and name */
227 hbox = gtk_hbox_new(FALSE, 4);
228 gtk_box_pack_start(vbox, hbox, FALSE, TRUE, 0);
229 gtk_box_pack_start(GTK_BOX(hbox),
230 gtk_image_new_from_pixbuf(di_image(item)->pixbuf),
231 FALSE, FALSE, 4);
233 if (g_utf8_validate(item->leafname, -1, NULL))
234 name = gtk_label_new(item->leafname);
235 else
237 guchar *u8;
239 u8 = to_utf8(item->leafname);
240 name = gtk_label_new(u8);
241 g_free(u8);
243 gtk_label_set_selectable(GTK_LABEL(name), TRUE);
244 gtk_label_set_line_wrap(GTK_LABEL(name), TRUE);
245 gtk_box_pack_start(GTK_BOX(hbox), name, FALSE, TRUE, 4);
247 make_heading(name, PANGO_SCALE_X_LARGE);
249 /* List of file attributes */
250 add_frame(vbox, make_details(path, item, window));
252 help_dir = g_strconcat(path, "/Help", NULL);
254 if (access(help_dir, F_OK) == 0)
256 GtkWidget *button, *align;
258 align = gtk_alignment_new(0.5, 0.5, 0, 0);
260 button = button_new_mixed(GTK_STOCK_JUMP_TO,
261 _("Show _Help Files"));
262 gtk_box_pack_start(vbox, align, FALSE, TRUE, 0);
263 gtk_container_add(GTK_CONTAINER(align), button);
264 g_signal_connect_swapped(button, "clicked",
265 G_CALLBACK(show_help_files),
266 (gpointer) path);
268 g_free(help_dir);
270 if (!(item->flags & ITEM_FLAG_SYMLINK))
272 label = gtk_label_new(NULL);
273 gtk_label_set_markup(GTK_LABEL(label),
274 _("<b>Permissions</b>"));
275 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
276 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
278 gtk_box_pack_start(vbox, make_permissions(path, item),
279 FALSE, TRUE, 0);
282 if (about)
283 add_frame(vbox, make_about(path, ai));
284 else if (item->mime_type == application_x_desktop)
286 add_frame(vbox, make_about_desktop(path));
288 else if (item->base_type == TYPE_FILE)
290 label = gtk_label_new(NULL);
291 gtk_label_set_markup(GTK_LABEL(label),
292 _("<b>Contents indicate...</b>"));
293 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
294 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
296 gtk_box_pack_start_defaults(vbox, make_file_says(path));
298 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
300 label = gtk_label_new(NULL);
301 gtk_label_set_markup(GTK_LABEL(label),
302 _("<b>When all directories are closed</b>"));
303 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
304 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
305 gtk_box_pack_start(vbox, make_unmount_options(path), FALSE, TRUE, 0);
308 if (ai)
309 g_object_unref(ai);
311 diritem_free(item);
313 return (GtkWidget *) vbox;
316 /* The selection has changed - grab or release the primary selection */
317 static void set_selection(GtkTreeView *view, gpointer data)
319 static GtkClipboard *primary = NULL;
320 GtkTreeModel *model;
321 GtkTreePath *path = NULL;
322 GtkTreeIter iter;
323 gchar *text;
325 gtk_tree_view_get_cursor(view, &path, NULL);
326 if (!path)
327 return;
329 if (!primary)
330 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
332 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
334 gtk_tree_model_get_iter(model, &iter, path);
335 gtk_tree_path_free(path);
337 gtk_tree_model_get(model, &iter, 1, &text, -1);
339 gtk_clipboard_set_text(primary, text, -1);
341 g_free(text);
344 /* Returns a GtkTreePath for the item */
345 static const gchar *add_row(GtkListStore *store, const gchar *label,
346 const gchar *data)
348 GtkTreeIter iter;
349 gchar *u8 = NULL;
350 GtkTreePath *tpath;
351 static gchar *last = NULL;
353 if (!g_utf8_validate(data, -1, NULL))
354 u8 = to_utf8(data);
356 gtk_list_store_append(store, &iter);
357 gtk_list_store_set(store, &iter, 0, label, 1, u8 ? u8 : data, -1);
359 g_free(u8);
361 tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
362 if (last)
363 g_free(last);
364 last = gtk_tree_path_to_string(tpath);
365 gtk_tree_path_free(tpath);
367 return last;
370 static void add_row_and_free(GtkListStore *store,
371 const gchar *label, gchar *data)
373 add_row(store, label, data);
374 g_free(data);
377 /* Create an empty list view, ready to place some data in */
378 static void make_list(GtkListStore **list_store, GtkWidget **list_view,
379 GCallback cell_edited)
381 GtkListStore *store;
382 GtkTreeView *view;
383 GtkCellRenderer *cell_renderer;
385 /* Field name, value, editable */
386 store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING,
387 G_TYPE_BOOLEAN);
388 view = GTK_TREE_VIEW(
389 gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
390 g_object_unref(G_OBJECT(store));
391 gtk_tree_view_set_headers_visible(view, FALSE);
393 cell_renderer = gtk_cell_renderer_text_new();
394 g_object_set(G_OBJECT(cell_renderer), "xalign", 1.0, NULL);
395 gtk_tree_view_insert_column_with_attributes(view,
396 0, NULL, cell_renderer, "text", 0, NULL);
398 cell_renderer = gtk_cell_renderer_text_new();
399 gtk_tree_view_insert_column_with_attributes(view,
400 1, NULL, cell_renderer, "text", 1, "editable", 2, NULL);
402 if (cell_edited) {
403 g_signal_connect(G_OBJECT(cell_renderer), "edited",
404 G_CALLBACK(cell_edited), store);
407 g_signal_connect(view, "cursor_changed",
408 G_CALLBACK(set_selection), NULL);
410 *list_store = store;
411 *list_view = (GtkWidget *) view;
414 static void set_cell(GtkListStore *store, const gchar *path,
415 const gchar *ctext)
417 GtkTreeIter iter;
419 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store),
420 &iter, path);
421 gtk_list_store_set(store, &iter, 1, ctext, -1);
424 static void insert_size(DU *du, const char *line)
426 off_t size;
427 gchar *cell;
429 #ifdef LARGE_FILE_SUPPORT
430 size = strtoll(line, NULL, 10);
431 #else
432 size = strtol(line, NULL, 10);
433 #endif
434 size <<= 10; /* Because du reports in K */
435 cell = (size >= PRETTY_SIZE_LIMIT)
436 ? g_strdup_printf("%s (%" SIZE_FMT " %s)",
437 format_size(size),
438 size, _("bytes"))
439 : g_strdup(format_size(size));
441 set_cell(du->store, du->path, cell);
443 g_free(cell);
446 static gboolean read_du_output(GIOChannel *source, GIOCondition cond, DU *du)
448 GString *line;
449 GIOStatus stat;
450 GError *err = NULL;
452 line = g_string_new("");
453 stat = g_io_channel_read_line_string(source, line, NULL, &err);
454 switch (stat)
456 case G_IO_STATUS_NORMAL:
457 insert_size(du, line->str);
458 break;
459 case G_IO_STATUS_EOF:
460 set_cell(du->store, du->path,
461 _("Failed to read size"));
462 break;
463 case G_IO_STATUS_AGAIN:
464 g_string_free(line, TRUE);
465 return TRUE;
466 case G_IO_STATUS_ERROR:
467 set_cell(du->store, du->path, err->message);
468 break;
470 g_string_free(line, TRUE);
472 return FALSE;
475 static void kill_du_output(GtkWidget *widget, DU *du)
477 g_source_remove(du->watch);
478 g_io_channel_shutdown(du->chan, FALSE, NULL);
479 g_io_channel_unref(du->chan);
480 kill((pid_t) du->child, SIGTERM);
481 g_object_unref(G_OBJECT(du->store));
482 g_free(du->path);
483 g_free(du);
486 static gboolean refresh_info_idle(gpointer data)
488 GObject *window = G_OBJECT(data);
490 refresh_info(window);
491 g_object_unref(window);
492 return FALSE;
495 static void cell_edited(GtkCellRendererText *cell,
496 const gchar *path_string,
497 const gchar *new_text,
498 gpointer data)
500 GtkTreeModel *model = (GtkTreeModel *) data;
501 GtkTreePath *path;
502 GtkTreeIter iter;
503 GObject *window;
504 const char *fullpath;
505 char *oldlink;
507 window = g_object_get_data(G_OBJECT(model), "rox_window");
508 g_return_if_fail(window != NULL);
510 fullpath = g_object_get_data(window, "path");
511 g_return_if_fail(fullpath != NULL);
513 path = gtk_tree_path_new_from_string(path_string);
514 gtk_tree_model_get_iter(model, &iter, path);
515 gtk_tree_path_free(path);
517 oldlink = readlink_dup(fullpath);
518 if (!oldlink) {
519 /* Must use delayed_error(), as this can be called
520 * from a focus-out event (causes a crash).
522 delayed_error(_("'%s' is no longer a symlink"), fullpath);
523 return;
525 if (strcmp(oldlink, new_text) == 0)
526 return; /* No change */
527 g_free(oldlink);
528 if (unlink(fullpath)) {
529 delayed_error(_("Failed to unlink '%s':\n%s"),
530 fullpath, g_strerror(errno));
531 return;
533 if (symlink(new_text, fullpath)) {
534 delayed_error(_("Failed to create symlink from '%s':\n%s\n"
535 "(note: old link has been deleted)"),
536 fullpath, g_strerror(errno));
537 return;
540 g_object_ref(window);
541 g_idle_add(refresh_info_idle, window);
544 /* Create the TreeView widget with the file's details */
545 static GtkWidget *make_details(const guchar *path, DirItem *item,
546 GObject *window)
548 GtkListStore *store;
549 GtkWidget *view;
550 gchar *tmp, *tmp2;
552 make_list(&store, &view, G_CALLBACK(cell_edited));
553 g_object_set_data(G_OBJECT(store), "rox_window", window);
555 /* For a symlink to an error, don't show the error */
556 if (item->base_type == TYPE_ERROR && item->lstat_errno)
558 add_row(store, _("Error:"), g_strerror(item->lstat_errno));
559 return view;
562 tmp = g_path_get_dirname(path);
563 tmp2 = pathdup(tmp);
564 if (strcmp(tmp, tmp2) != 0)
565 add_row_and_free(store, _("Real directory:"), tmp2);
566 g_free(tmp);
568 add_row_and_free(store, _("Owner, Group:"),
569 g_strdup_printf("%s, %s",
570 user_name(item->uid),
571 group_name(item->gid)));
573 if (item->base_type != TYPE_DIRECTORY)
575 add_row_and_free(store, _("Size:"),
576 item->size >= PRETTY_SIZE_LIMIT
577 ? g_strdup_printf("%s (%" SIZE_FMT " %s)",
578 format_size(item->size),
579 item->size, _("bytes"))
580 : g_strdup(format_size(item->size)));
582 else
584 gchar *stt=NULL;
586 if(item->flags & ITEM_FLAG_MOUNTED)
587 stt=mount_get_fs_size(path);
589 if(stt) {
590 add_row_and_free(store, _("Size:"), stt);
591 } else {
592 DU *du;
593 int out;
595 gchar *args[] = {"du", "-sk", "", NULL};
597 du = g_new(DU, 1);
598 du->store = store;
599 du->path = g_strdup(add_row(store, _("Size:"),
600 _("Scanning")));
602 args[2] = (gchar *) path;
603 if (g_spawn_async_with_pipes(NULL, args, NULL,
604 G_SPAWN_SEARCH_PATH,
605 NULL, NULL, &du->child,
606 NULL, &out, NULL,
607 NULL))
609 du->chan = g_io_channel_unix_new(out);
610 /* Select binary encoding so we don't get an
611 * error with non-UTF-8 filenames.
613 g_io_channel_set_encoding(du->chan, NULL, NULL);
614 du->watch = g_io_add_watch(du->chan,
615 G_IO_IN|G_IO_ERR|G_IO_HUP,
616 (GIOFunc) read_du_output, du);
617 g_object_ref(G_OBJECT(du->store));
618 g_signal_connect(G_OBJECT(view),
619 "destroy",
620 G_CALLBACK(kill_du_output),
621 du);
623 else
625 set_cell(store, du->path, _("Failed to scan"));
626 g_free(du->path);
627 g_free(du);
632 add_row_and_free(store, _("Change time:"), pretty_time(&item->ctime));
634 add_row_and_free(store, _("Modify time:"), pretty_time(&item->mtime));
636 add_row_and_free(store, _("Access time:"), pretty_time(&item->atime));
638 add_row(store, _("Type:"), pretty_type(item, path));
640 if (item->mime_type)
641 add_row(store, "", mime_type_comment(item->mime_type));
643 if (xattr_supported(NULL)) {
644 add_row(store, _("Extended attributes:"),
645 (item->flags & ITEM_FLAG_HAS_XATTR)
646 ? _("Present")
647 : xattr_supported(path) ? _("None")
648 : _("Not supported"));
651 if (item->flags & ITEM_FLAG_SYMLINK)
653 GtkTreeIter iter;
654 GtkTreeModel *model = GTK_TREE_MODEL(store);
655 char *target;
657 target = readlink_dup(path);
658 if (!target)
659 target = g_strdup(g_strerror(errno));
660 add_row_and_free(store, _("Link target:"), target);
662 /* Make cell editable */
663 gtk_tree_model_iter_nth_child(model, &iter,
664 NULL, gtk_tree_model_iter_n_children(model, NULL) - 1);
666 gtk_list_store_set(store, &iter, 2, TRUE, -1);
669 if (item->base_type != TYPE_DIRECTORY)
671 if (EXECUTABLE_FILE(item))
672 add_row(store, _("Run action:"), _("Execute file"));
673 else
675 add_row_and_free(store, _("Run action:"),
676 describe_current_command(item->mime_type));
680 return view;
683 /* Create the TreeView widget with the application's details */
684 static GtkWidget *make_about(const guchar *path, XMLwrapper *ai)
686 GtkListStore *store;
687 GtkWidget *view;
688 xmlNode *prop;
689 xmlNode *about, *about_trans;
690 GHashTable *translate;
692 g_return_val_if_fail(ai != NULL, NULL);
694 about_trans = xml_get_section(ai, NULL, "About");
696 about = xmlDocGetRootElement(ai->doc)->xmlChildrenNode;
697 for (; about; about = about->next)
699 if (about->type != XML_ELEMENT_NODE)
700 continue;
701 if (about->ns == NULL && strcmp(about->name, "About") == 0)
702 break;
705 g_return_val_if_fail(about != NULL, NULL);
707 make_list(&store, &view, NULL);
709 /* Add each field in about to the list, but overriding each element
710 * with about_trans if a translation is supplied.
712 translate = g_hash_table_new(g_str_hash, g_str_equal);
713 if (about_trans != about)
715 xmlNode *p;
716 for (p = about_trans->xmlChildrenNode; p; p = p->next)
718 if (p->type != XML_ELEMENT_NODE)
719 continue;
720 g_hash_table_insert(translate, (char *) p->name, p);
723 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
725 if (prop->type == XML_ELEMENT_NODE)
727 char *label = NULL;
728 char *value = NULL;
729 char *tmp = NULL;
730 xmlNode *trans;
732 trans = g_hash_table_lookup(translate, prop->name);
733 if (!trans)
734 trans = prop;
736 tmp = xmlGetProp(trans, "label");
737 label = g_strconcat(tmp ? tmp
738 : (char *) trans->name,
739 ":", NULL);
740 g_free(tmp);
741 value = xmlNodeListGetString(trans->doc,
742 trans->xmlChildrenNode, 1);
743 if (!value)
744 value = xmlNodeListGetString(prop->doc,
745 prop->xmlChildrenNode, 1);
746 if (!value)
747 value = g_strdup("-");
748 add_row_and_free(store, label, value);
749 g_free(label);
753 g_hash_table_destroy(translate);
755 return view;
758 /* Create the TreeView widget with the desktop entry's details */
759 static GtkWidget *make_about_desktop(const gchar *path)
761 GtkListStore *store;
762 GtkWidget *view;
763 GError *error=NULL;
764 gchar *name=NULL, *comment=NULL, *exec=NULL;
766 make_list(&store, &view, NULL);
768 if(!get_values_from_desktop_file(path, &error,
769 "Desktop Entry", "Name", &name,
770 "Desktop Entry", "Comment", &comment,
771 "Desktop Entry", "Exec", &exec,
772 NULL))
774 /* Report it? */
775 delayed_error("%s", error->message);
776 if(error)
777 g_error_free(error);
778 return view;
781 if(name)
782 add_row_and_free(store, _("Name"), name);
783 if(comment)
784 add_row_and_free(store, _("Comment"), comment);
785 if(exec)
786 add_row_and_free(store, _("Execute"), exec);
788 return view;
791 static GtkWidget *make_file_says(const guchar *path)
793 GtkWidget *w_file_label;
794 GtkLabel *l_file_label;
795 int file_data[2];
796 char *argv[] = {"file", "-b", NULL, NULL};
797 FileStatus *fs = NULL;
798 guchar *tmp;
800 w_file_label = gtk_label_new(_("<nothing yet>"));
801 l_file_label = GTK_LABEL(w_file_label);
802 gtk_label_set_line_wrap(l_file_label, TRUE);
803 gtk_label_set_selectable(l_file_label, TRUE);
805 if (pipe(file_data))
807 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
808 gtk_label_set_text(l_file_label, tmp);
809 g_free(tmp);
810 return w_file_label;
813 switch (fork())
815 case -1:
816 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
817 gtk_label_set_text(l_file_label, tmp);
818 g_free(tmp);
819 close(file_data[0]);
820 close(file_data[1]);
821 break;
822 case 0:
823 /* We are the child */
824 close(file_data[0]);
825 dup2(file_data[1], STDOUT_FILENO);
826 dup2(file_data[1], STDERR_FILENO);
827 #ifdef FILE_B_FLAG
828 argv[2] = (char *) path;
829 #else
830 argv[1] = (char *) g_basename(path);
831 chdir(g_path_get_dirname(path));
832 #endif
833 if (execvp(argv[0], argv))
834 fprintf(stderr, "execvp() error: %s\n",
835 g_strerror(errno));
836 _exit(0);
837 default:
838 /* We are the parent */
839 close(file_data[1]);
840 fs = g_new(FileStatus, 1);
841 fs->label = l_file_label;
842 fs->fd = file_data[0];
843 fs->text = g_strdup("");
844 fs->input = gdk_input_add_full(fs->fd, GDK_INPUT_READ,
845 (GdkInputFunction) add_file_output,
846 fs, NULL);
847 g_signal_connect(w_file_label, "destroy",
848 G_CALLBACK(file_info_destroyed), fs);
849 break;
852 return w_file_label;
855 /* Got some data from file(1) - stick it in the window. */
856 static void add_file_output(FileStatus *fs,
857 gint source, GdkInputCondition condition)
859 char buffer[20];
860 char *str;
861 int got;
863 got = read(source, buffer, sizeof(buffer) - 1);
864 if (got <= 0)
866 int err = errno;
867 g_source_remove(fs->input);
868 close(source);
869 fs->fd = -1;
870 if (got < 0)
871 delayed_error(_("file(1) says... %s"),
872 g_strerror(err));
873 return;
875 buffer[got] = '\0';
877 str = g_strconcat(fs->text, buffer, NULL);
878 g_free(fs->text);
879 fs->text = str;
881 str = to_utf8(fs->text);
882 g_strstrip(str);
883 gtk_label_set_text(fs->label, str);
884 g_free(str);
887 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
889 if (fs->fd != -1)
891 g_source_remove(fs->input);
892 close(fs->fd);
895 g_free(fs->text);
896 g_free(fs);
899 static void permissions_destroyed(GtkWidget *widget, Permissions *perm)
901 g_free(perm->path);
902 diritem_free(perm->item);
904 g_free(perm);
907 static void permissions_apply(GtkWidget *widget, Permissions *perm)
909 mode_t nmode;
910 int i;
912 nmode=0;
914 for (i = 0; i < 9; i++)
916 GtkToggleButton *bit = GTK_TOGGLE_BUTTON(perm->bits[i]);
917 if (gtk_toggle_button_get_active(bit))
918 nmode |= 1 << i;
920 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[9])))
921 nmode |= S_ISUID;
922 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[10])))
923 nmode |= S_ISGID;
924 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[11])))
925 nmode |= S_ISVTX;
927 if (chmod(perm->path, nmode))
928 report_error(_("Could not change permissions: %s"),
929 g_strerror(errno));
932 static GtkWidget *make_permissions(const gchar *path, DirItem *item)
934 Permissions *perm;
935 GtkWidget *table;
936 GtkWidget *tick, *label;
937 int i, x, y;
939 perm = g_new(Permissions, 1);
941 perm->path = g_strdup(path);
942 perm->item = diritem_new(path);
944 table = gtk_table_new(4, 5, TRUE);
946 label = gtk_label_new(_("Owner"));
947 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
948 label = gtk_label_new(_("Group"));
949 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
950 label = gtk_label_new(_("World"));
951 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
953 label = gtk_label_new(_("Read"));
954 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
955 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
956 label = gtk_label_new(_("Write"));
957 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
958 gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 0, 1);
959 label = gtk_label_new(_("Exec"));
960 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
961 gtk_table_attach_defaults(GTK_TABLE(table), label, 3, 4, 0, 1);
963 for (i = 0; i < 9; i++)
965 x = 1 + 2 - i % 3;
966 y = 1 + 2 - i / 3;
967 perm->bits[i] = tick = gtk_check_button_new();
968 gtk_table_attach_defaults(GTK_TABLE(table), tick,
969 x, x + 1, y, y + 1);
970 if (item->mode & (1 << i))
971 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick),
972 TRUE);
973 g_signal_connect(tick, "toggled",
974 G_CALLBACK(permissions_apply), perm);
977 tick = gtk_check_button_new_with_label(_("SUID"));
978 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 1, 2);
979 if (item->mode & S_ISUID)
980 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
981 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
982 perm->bits[9] = tick;
984 tick = gtk_check_button_new_with_label(_("SGID"));
985 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 2, 3);
986 if (item->mode & S_ISGID)
987 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
988 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
989 perm->bits[10] = tick;
991 tick = gtk_check_button_new_with_label(_("Sticky"));
992 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 3, 4);
993 if (item->mode & S_ISVTX)
994 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
995 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
996 perm->bits[11] = tick;
998 g_signal_connect(table, "destroy",
999 G_CALLBACK(permissions_destroyed), perm);
1001 gtk_widget_show_all(table);
1002 return table;
1005 static void unmount_option_toggled(GtkToggleButton *toggle, const char *path)
1007 if (gtk_toggle_button_get_active(toggle))
1009 filer_set_unmount_action(path,
1010 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(toggle),
1011 "unmount_action")));
1015 static GtkWidget *pack_unmount_radio(const char *path,
1016 UnmountPrompt path_value, const char *label,
1017 UnmountPrompt btn_value, GtkWidget *group_owner, GtkWidget *hbox)
1019 GtkWidget *radio;
1021 if (group_owner)
1023 radio = gtk_radio_button_new_with_label_from_widget(
1024 GTK_RADIO_BUTTON(group_owner), label);
1026 else
1028 radio = gtk_radio_button_new_with_label(NULL, label);
1030 if (path_value == btn_value)
1031 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
1032 g_object_set_data(G_OBJECT(radio), "unmount_action",
1033 GINT_TO_POINTER(btn_value));
1034 g_signal_connect(radio, "toggled", G_CALLBACK(unmount_option_toggled),
1035 (gpointer) path);
1036 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, FALSE, 0);
1037 return radio;
1040 static GtkWidget *make_unmount_options(const char *path)
1042 GtkWidget *hbox, *radio;
1043 UnmountPrompt upval = filer_get_unmount_action(path);
1045 hbox = gtk_hbox_new(TRUE, 4);
1046 radio = pack_unmount_radio(path, upval,
1047 _("Do nothing"), UNMOUNT_PROMPT_NO_CHANGE, NULL, hbox);
1048 radio = pack_unmount_radio(path, upval,
1049 _("Unmount"), UNMOUNT_PROMPT_UNMOUNT, radio, hbox);
1050 radio = pack_unmount_radio(path, upval,
1051 _("Eject"), UNMOUNT_PROMPT_EJECT, radio, hbox);
1052 pack_unmount_radio(path, upval,
1053 _("Ask"), UNMOUNT_PROMPT_ASK, radio, hbox);
1054 return hbox;
1057 /* Don't g_free() the result */
1058 static const gchar *pretty_type(DirItem *file, const guchar *path)
1060 static gchar *text = NULL;
1062 null_g_free(&text);
1064 if (file->flags & ITEM_FLAG_SYMLINK)
1065 return _("Symbolic link");
1067 if (file->flags & ITEM_FLAG_APPDIR)
1068 return _("ROX application");
1070 if (file->flags & ITEM_FLAG_MOUNT_POINT)
1072 MountPoint *mp;
1073 const gchar *mounted;
1075 mounted = mount_is_mounted(path, NULL, NULL)
1076 ? _("mounted") : _("unmounted");
1078 mp = g_hash_table_lookup(fstab_mounts, path);
1079 if (mp)
1080 text = g_strdup_printf(_("Mount point for %s (%s)"),
1081 mp->name, mounted);
1082 else
1083 text = g_strdup_printf(_("Mount point (%s)"), mounted);
1084 return text;
1087 if (file->mime_type)
1089 text = g_strconcat(file->mime_type->media_type, "/",
1090 file->mime_type->subtype, NULL);
1091 return text;
1094 return "-";