Converted README to markdown
[rox-filer.git] / ROX-Filer / src / infobox.c
blob78117b0bf72a080e15615e65069153867994c0d4
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;
213 MaskedPixmap *thumb;
215 g_return_val_if_fail(path[0] == '/', NULL);
217 item = diritem_new(g_basename(path));
218 diritem_restat(path, item, NULL);
220 ai = appinfo_get(path, item);
221 if (ai)
222 about = xml_get_section(ai, NULL, "About");
224 vbox = GTK_BOX(gtk_vbox_new(FALSE, 4));
225 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
227 /* Heading, with icon and name */
228 hbox = gtk_hbox_new(FALSE, 4);
229 gtk_box_pack_start(vbox, hbox, FALSE, TRUE, 0);
230 gtk_box_pack_start(GTK_BOX(hbox),
231 gtk_image_new_from_pixbuf(di_image(item)->pixbuf),
232 FALSE, FALSE, 4);
234 if (g_utf8_validate(item->leafname, -1, NULL))
235 name = gtk_label_new(item->leafname);
236 else
238 guchar *u8;
240 u8 = to_utf8(item->leafname);
241 name = gtk_label_new(u8);
242 g_free(u8);
244 gtk_label_set_selectable(GTK_LABEL(name), TRUE);
245 gtk_label_set_line_wrap(GTK_LABEL(name), TRUE);
246 gtk_box_pack_start(GTK_BOX(hbox), name, FALSE, TRUE, 4);
248 make_heading(name, PANGO_SCALE_X_LARGE);
250 thumb=pixmap_try_thumb(path, FALSE);
251 if(thumb)
253 gtk_box_pack_start(GTK_BOX(hbox),
254 gtk_image_new_from_pixbuf(thumb->src_pixbuf),
255 FALSE, FALSE, 4);
256 g_object_unref(thumb);
259 /* List of file attributes */
260 add_frame(vbox, make_details(path, item, window));
262 help_dir = g_strconcat(path, "/Help", NULL);
264 if (access(help_dir, F_OK) == 0)
266 GtkWidget *button, *align;
268 align = gtk_alignment_new(0.5, 0.5, 0, 0);
270 button = button_new_mixed(GTK_STOCK_JUMP_TO,
271 _("Show _Help Files"));
272 gtk_box_pack_start(vbox, align, FALSE, TRUE, 0);
273 gtk_container_add(GTK_CONTAINER(align), button);
274 g_signal_connect_swapped(button, "clicked",
275 G_CALLBACK(show_help_files),
276 (gpointer) path);
278 g_free(help_dir);
280 if (!(item->flags & ITEM_FLAG_SYMLINK))
282 label = gtk_label_new(NULL);
283 gtk_label_set_markup(GTK_LABEL(label),
284 _("<b>Permissions</b>"));
285 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
286 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
288 gtk_box_pack_start(vbox, make_permissions(path, item),
289 FALSE, TRUE, 0);
292 if (about)
293 add_frame(vbox, make_about(path, ai));
294 else if (item->mime_type == application_x_desktop)
296 add_frame(vbox, make_about_desktop(path));
298 else if (item->base_type == TYPE_FILE)
300 label = gtk_label_new(NULL);
301 gtk_label_set_markup(GTK_LABEL(label),
302 _("<b>Contents indicate...</b>"));
303 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
304 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
306 gtk_box_pack_start_defaults(vbox, make_file_says(path));
308 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
310 label = gtk_label_new(NULL);
311 gtk_label_set_markup(GTK_LABEL(label),
312 _("<b>When all directories are closed</b>"));
313 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
314 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
315 gtk_box_pack_start(vbox, make_unmount_options(path), FALSE, TRUE, 0);
318 if (ai)
319 g_object_unref(ai);
321 diritem_free(item);
323 return (GtkWidget *) vbox;
326 /* The selection has changed - grab or release the primary selection */
327 static void set_selection(GtkTreeView *view, gpointer data)
329 static GtkClipboard *primary = NULL;
330 GtkTreeModel *model;
331 GtkTreePath *path = NULL;
332 GtkTreeIter iter;
333 gchar *text;
335 gtk_tree_view_get_cursor(view, &path, NULL);
336 if (!path)
337 return;
339 if (!primary)
340 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
342 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
344 gtk_tree_model_get_iter(model, &iter, path);
345 gtk_tree_path_free(path);
347 gtk_tree_model_get(model, &iter, 1, &text, -1);
349 gtk_clipboard_set_text(primary, text, -1);
351 g_free(text);
354 /* Returns a GtkTreePath for the item */
355 static const gchar *add_row(GtkListStore *store, const gchar *label,
356 const gchar *data)
358 GtkTreeIter iter;
359 gchar *u8 = NULL;
360 GtkTreePath *tpath;
361 static gchar *last = NULL;
363 if (!g_utf8_validate(data, -1, NULL))
364 u8 = to_utf8(data);
366 gtk_list_store_append(store, &iter);
367 gtk_list_store_set(store, &iter, 0, label, 1, u8 ? u8 : data, -1);
369 g_free(u8);
371 tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
372 if (last)
373 g_free(last);
374 last = gtk_tree_path_to_string(tpath);
375 gtk_tree_path_free(tpath);
377 return last;
380 static void add_row_and_free(GtkListStore *store,
381 const gchar *label, gchar *data)
383 add_row(store, label, data);
384 g_free(data);
387 /* Create an empty list view, ready to place some data in */
388 static void make_list(GtkListStore **list_store, GtkWidget **list_view,
389 GCallback cell_edited)
391 GtkListStore *store;
392 GtkTreeView *view;
393 GtkCellRenderer *cell_renderer;
395 /* Field name, value, editable */
396 store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING,
397 G_TYPE_BOOLEAN);
398 view = GTK_TREE_VIEW(
399 gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
400 g_object_unref(G_OBJECT(store));
401 gtk_tree_view_set_headers_visible(view, FALSE);
403 cell_renderer = gtk_cell_renderer_text_new();
404 g_object_set(G_OBJECT(cell_renderer), "xalign", 1.0, NULL);
405 gtk_tree_view_insert_column_with_attributes(view,
406 0, NULL, cell_renderer, "text", 0, NULL);
408 cell_renderer = gtk_cell_renderer_text_new();
409 gtk_tree_view_insert_column_with_attributes(view,
410 1, NULL, cell_renderer, "text", 1, "editable", 2, NULL);
412 if (cell_edited) {
413 g_signal_connect(G_OBJECT(cell_renderer), "edited",
414 G_CALLBACK(cell_edited), store);
417 g_signal_connect(view, "cursor_changed",
418 G_CALLBACK(set_selection), NULL);
420 *list_store = store;
421 *list_view = (GtkWidget *) view;
424 static void set_cell(GtkListStore *store, const gchar *path,
425 const gchar *ctext)
427 GtkTreeIter iter;
429 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store),
430 &iter, path);
431 gtk_list_store_set(store, &iter, 1, ctext, -1);
434 static void insert_size(DU *du, const char *line)
436 off_t size;
437 gchar *cell;
439 #ifdef LARGE_FILE_SUPPORT
440 size = strtoll(line, NULL, 10);
441 #else
442 size = strtol(line, NULL, 10);
443 #endif
444 size <<= 10; /* Because du reports in K */
445 cell = (size >= PRETTY_SIZE_LIMIT)
446 ? g_strdup_printf("%s (%" SIZE_FMT " %s)",
447 format_size(size),
448 size, _("bytes"))
449 : g_strdup(format_size(size));
451 set_cell(du->store, du->path, cell);
453 g_free(cell);
456 static gboolean read_du_output(GIOChannel *source, GIOCondition cond, DU *du)
458 GString *line;
459 GIOStatus stat;
460 GError *err = NULL;
462 line = g_string_new("");
463 stat = g_io_channel_read_line_string(source, line, NULL, &err);
464 switch (stat)
466 case G_IO_STATUS_NORMAL:
467 insert_size(du, line->str);
468 break;
469 case G_IO_STATUS_EOF:
470 set_cell(du->store, du->path,
471 _("Failed to read size"));
472 break;
473 case G_IO_STATUS_AGAIN:
474 g_string_free(line, TRUE);
475 return TRUE;
476 case G_IO_STATUS_ERROR:
477 set_cell(du->store, du->path, err->message);
478 break;
480 g_string_free(line, TRUE);
482 return FALSE;
485 static void kill_du_output(GtkWidget *widget, DU *du)
487 g_source_remove(du->watch);
488 g_io_channel_shutdown(du->chan, FALSE, NULL);
489 g_io_channel_unref(du->chan);
490 kill((pid_t) du->child, SIGTERM);
491 g_object_unref(G_OBJECT(du->store));
492 g_free(du->path);
493 g_free(du);
496 static gboolean refresh_info_idle(gpointer data)
498 GObject *window = G_OBJECT(data);
500 refresh_info(window);
501 g_object_unref(window);
502 return FALSE;
505 static void cell_edited(GtkCellRendererText *cell,
506 const gchar *path_string,
507 const gchar *new_text,
508 gpointer data)
510 GtkTreeModel *model = (GtkTreeModel *) data;
511 GtkTreePath *path;
512 GtkTreeIter iter;
513 GObject *window;
514 const char *fullpath;
515 char *oldlink;
517 window = g_object_get_data(G_OBJECT(model), "rox_window");
518 g_return_if_fail(window != NULL);
520 fullpath = g_object_get_data(window, "path");
521 g_return_if_fail(fullpath != NULL);
523 path = gtk_tree_path_new_from_string(path_string);
524 gtk_tree_model_get_iter(model, &iter, path);
525 gtk_tree_path_free(path);
527 oldlink = readlink_dup(fullpath);
528 if (!oldlink) {
529 /* Must use delayed_error(), as this can be called
530 * from a focus-out event (causes a crash).
532 delayed_error(_("'%s' is no longer a symlink"), fullpath);
533 return;
535 if (strcmp(oldlink, new_text) == 0)
536 return; /* No change */
537 g_free(oldlink);
538 if (unlink(fullpath)) {
539 delayed_error(_("Failed to unlink '%s':\n%s"),
540 fullpath, g_strerror(errno));
541 return;
543 if (symlink(new_text, fullpath)) {
544 delayed_error(_("Failed to create symlink from '%s':\n%s\n"
545 "(note: old link has been deleted)"),
546 fullpath, g_strerror(errno));
547 return;
550 g_object_ref(window);
551 g_idle_add(refresh_info_idle, window);
554 /* Create the TreeView widget with the file's details */
555 static GtkWidget *make_details(const guchar *path, DirItem *item,
556 GObject *window)
558 GtkListStore *store;
559 GtkWidget *view;
560 gchar *tmp, *tmp2;
562 make_list(&store, &view, G_CALLBACK(cell_edited));
563 g_object_set_data(G_OBJECT(store), "rox_window", window);
565 /* For a symlink to an error, don't show the error */
566 if (item->base_type == TYPE_ERROR && item->lstat_errno)
568 add_row(store, _("Error:"), g_strerror(item->lstat_errno));
569 return view;
572 tmp = g_path_get_dirname(path);
573 tmp2 = pathdup(tmp);
574 if (strcmp(tmp, tmp2) != 0)
575 add_row_and_free(store, _("Real directory:"), tmp2);
576 g_free(tmp);
578 add_row_and_free(store, _("Owner, Group:"),
579 g_strdup_printf("%s, %s",
580 user_name(item->uid),
581 group_name(item->gid)));
583 if (item->base_type != TYPE_DIRECTORY)
585 add_row_and_free(store, _("Size:"),
586 item->size >= PRETTY_SIZE_LIMIT
587 ? g_strdup_printf("%s (%" SIZE_FMT " %s)",
588 format_size(item->size),
589 item->size, _("bytes"))
590 : g_strdup(format_size(item->size)));
592 else
594 gchar *stt=NULL;
596 if(item->flags & ITEM_FLAG_MOUNTED)
597 stt=mount_get_fs_size(path);
599 if(stt) {
600 add_row_and_free(store, _("Size:"), stt);
601 } else {
602 DU *du;
603 int out;
605 gchar *args[] = {"du", "-sk", "", NULL};
607 du = g_new(DU, 1);
608 du->store = store;
609 du->path = g_strdup(add_row(store, _("Size:"),
610 _("Scanning")));
612 args[2] = (gchar *) path;
613 if (g_spawn_async_with_pipes(NULL, args, NULL,
614 G_SPAWN_SEARCH_PATH,
615 NULL, NULL, &du->child,
616 NULL, &out, NULL,
617 NULL))
619 du->chan = g_io_channel_unix_new(out);
620 /* Select binary encoding so we don't get an
621 * error with non-UTF-8 filenames.
623 g_io_channel_set_encoding(du->chan, NULL, NULL);
624 du->watch = g_io_add_watch(du->chan,
625 G_IO_IN|G_IO_ERR|G_IO_HUP,
626 (GIOFunc) read_du_output, du);
627 g_object_ref(G_OBJECT(du->store));
628 g_signal_connect(G_OBJECT(view),
629 "destroy",
630 G_CALLBACK(kill_du_output),
631 du);
633 else
635 set_cell(store, du->path, _("Failed to scan"));
636 g_free(du->path);
637 g_free(du);
642 add_row_and_free(store, _("Change time:"), pretty_time(&item->ctime));
644 add_row_and_free(store, _("Modify time:"), pretty_time(&item->mtime));
646 add_row_and_free(store, _("Access time:"), pretty_time(&item->atime));
648 add_row(store, _("Type:"), pretty_type(item, path));
650 if (item->mime_type)
651 add_row(store, "", mime_type_comment(item->mime_type));
653 if (xattr_supported(NULL)) {
654 add_row(store, _("Extended attributes:"),
655 (item->flags & ITEM_FLAG_HAS_XATTR)
656 ? _("Present")
657 : xattr_supported(path) ? _("None")
658 : _("Not supported"));
661 if (item->flags & ITEM_FLAG_SYMLINK)
663 GtkTreeIter iter;
664 GtkTreeModel *model = GTK_TREE_MODEL(store);
665 char *target;
667 target = readlink_dup(path);
668 if (!target)
669 target = g_strdup(g_strerror(errno));
670 add_row_and_free(store, _("Link target:"), target);
672 /* Make cell editable */
673 gtk_tree_model_iter_nth_child(model, &iter,
674 NULL, gtk_tree_model_iter_n_children(model, NULL) - 1);
676 gtk_list_store_set(store, &iter, 2, TRUE, -1);
679 if (item->base_type != TYPE_DIRECTORY)
681 if (EXECUTABLE_FILE(item))
682 add_row(store, _("Run action:"), _("Execute file"));
683 else
685 add_row_and_free(store, _("Run action:"),
686 describe_current_command(item->mime_type));
690 return view;
693 /* Create the TreeView widget with the application's details */
694 static GtkWidget *make_about(const guchar *path, XMLwrapper *ai)
696 GtkListStore *store;
697 GtkWidget *view;
698 xmlNode *prop;
699 xmlNode *about, *about_trans;
700 GHashTable *translate;
702 g_return_val_if_fail(ai != NULL, NULL);
704 about_trans = xml_get_section(ai, NULL, "About");
706 about = xmlDocGetRootElement(ai->doc)->xmlChildrenNode;
707 for (; about; about = about->next)
709 if (about->type != XML_ELEMENT_NODE)
710 continue;
711 if (about->ns == NULL && strcmp(about->name, "About") == 0)
712 break;
715 g_return_val_if_fail(about != NULL, NULL);
717 make_list(&store, &view, NULL);
719 /* Add each field in about to the list, but overriding each element
720 * with about_trans if a translation is supplied.
722 translate = g_hash_table_new(g_str_hash, g_str_equal);
723 if (about_trans != about)
725 xmlNode *p;
726 for (p = about_trans->xmlChildrenNode; p; p = p->next)
728 if (p->type != XML_ELEMENT_NODE)
729 continue;
730 g_hash_table_insert(translate, (char *) p->name, p);
733 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
735 if (prop->type == XML_ELEMENT_NODE)
737 char *label = NULL;
738 char *value = NULL;
739 char *tmp = NULL;
740 xmlNode *trans;
742 trans = g_hash_table_lookup(translate, prop->name);
743 if (!trans)
744 trans = prop;
746 tmp = xmlGetProp(trans, "label");
747 label = g_strconcat(tmp ? tmp
748 : (char *) trans->name,
749 ":", NULL);
750 g_free(tmp);
751 value = xmlNodeListGetString(trans->doc,
752 trans->xmlChildrenNode, 1);
753 if (!value)
754 value = xmlNodeListGetString(prop->doc,
755 prop->xmlChildrenNode, 1);
756 if (!value)
757 value = g_strdup("-");
758 add_row_and_free(store, label, value);
759 g_free(label);
763 g_hash_table_destroy(translate);
765 return view;
768 /* Create the TreeView widget with the desktop entry's details */
769 static GtkWidget *make_about_desktop(const gchar *path)
771 GtkListStore *store;
772 GtkWidget *view;
773 GError *error=NULL;
774 gchar *name=NULL, *comment=NULL, *exec=NULL;
776 make_list(&store, &view, NULL);
778 if(!get_values_from_desktop_file(path, &error,
779 "Desktop Entry", "Name", &name,
780 "Desktop Entry", "Comment", &comment,
781 "Desktop Entry", "Exec", &exec,
782 NULL))
784 /* Report it? */
785 delayed_error("%s", error->message);
786 if(error)
787 g_error_free(error);
788 return view;
791 if(name)
792 add_row_and_free(store, _("Name"), name);
793 if(comment)
794 add_row_and_free(store, _("Comment"), comment);
795 if(exec)
796 add_row_and_free(store, _("Execute"), exec);
798 return view;
801 static GtkWidget *make_file_says(const guchar *path)
803 GtkWidget *w_file_label;
804 GtkLabel *l_file_label;
805 int file_data[2];
806 char *argv[] = {"file", "-b", NULL, NULL};
807 FileStatus *fs = NULL;
808 guchar *tmp;
810 w_file_label = gtk_label_new(_("<nothing yet>"));
811 l_file_label = GTK_LABEL(w_file_label);
812 gtk_label_set_line_wrap(l_file_label, TRUE);
813 gtk_label_set_selectable(l_file_label, TRUE);
815 if (pipe(file_data))
817 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
818 gtk_label_set_text(l_file_label, tmp);
819 g_free(tmp);
820 return w_file_label;
823 switch (fork())
825 case -1:
826 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
827 gtk_label_set_text(l_file_label, tmp);
828 g_free(tmp);
829 close(file_data[0]);
830 close(file_data[1]);
831 break;
832 case 0:
833 /* We are the child */
834 close(file_data[0]);
835 dup2(file_data[1], STDOUT_FILENO);
836 dup2(file_data[1], STDERR_FILENO);
837 #ifdef FILE_B_FLAG
838 argv[2] = (char *) path;
839 #else
840 argv[1] = (char *) g_basename(path);
841 chdir(g_path_get_dirname(path));
842 #endif
843 if (execvp(argv[0], argv))
844 fprintf(stderr, "execvp() error: %s\n",
845 g_strerror(errno));
846 _exit(0);
847 default:
848 /* We are the parent */
849 close(file_data[1]);
850 fs = g_new(FileStatus, 1);
851 fs->label = l_file_label;
852 fs->fd = file_data[0];
853 fs->text = g_strdup("");
854 fs->input = gdk_input_add_full(fs->fd, GDK_INPUT_READ,
855 (GdkInputFunction) add_file_output,
856 fs, NULL);
857 g_signal_connect(w_file_label, "destroy",
858 G_CALLBACK(file_info_destroyed), fs);
859 break;
862 return w_file_label;
865 /* Got some data from file(1) - stick it in the window. */
866 static void add_file_output(FileStatus *fs,
867 gint source, GdkInputCondition condition)
869 char buffer[20];
870 char *str;
871 int got;
873 got = read(source, buffer, sizeof(buffer) - 1);
874 if (got <= 0)
876 int err = errno;
877 g_source_remove(fs->input);
878 close(source);
879 fs->fd = -1;
880 if (got < 0)
881 delayed_error(_("file(1) says... %s"),
882 g_strerror(err));
883 return;
885 buffer[got] = '\0';
887 str = g_strconcat(fs->text, buffer, NULL);
888 g_free(fs->text);
889 fs->text = str;
891 str = to_utf8(fs->text);
892 g_strstrip(str);
893 gtk_label_set_text(fs->label, str);
894 g_free(str);
897 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
899 if (fs->fd != -1)
901 g_source_remove(fs->input);
902 close(fs->fd);
905 g_free(fs->text);
906 g_free(fs);
909 static void permissions_destroyed(GtkWidget *widget, Permissions *perm)
911 g_free(perm->path);
912 diritem_free(perm->item);
914 g_free(perm);
917 static void permissions_apply(GtkWidget *widget, Permissions *perm)
919 mode_t nmode;
920 int i;
922 nmode=0;
924 for (i = 0; i < 9; i++)
926 GtkToggleButton *bit = GTK_TOGGLE_BUTTON(perm->bits[i]);
927 if (gtk_toggle_button_get_active(bit))
928 nmode |= 1 << i;
930 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[9])))
931 nmode |= S_ISUID;
932 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[10])))
933 nmode |= S_ISGID;
934 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[11])))
935 nmode |= S_ISVTX;
937 if (chmod(perm->path, nmode))
938 report_error(_("Could not change permissions: %s"),
939 g_strerror(errno));
942 static GtkWidget *make_permissions(const gchar *path, DirItem *item)
944 Permissions *perm;
945 GtkWidget *table;
946 GtkWidget *tick, *label;
947 int i, x, y;
949 perm = g_new(Permissions, 1);
951 perm->path = g_strdup(path);
952 perm->item = diritem_new(path);
954 table = gtk_table_new(4, 5, TRUE);
956 label = gtk_label_new(_("Owner"));
957 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
958 label = gtk_label_new(_("Group"));
959 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
960 label = gtk_label_new(_("World"));
961 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
963 label = gtk_label_new(_("Read"));
964 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
965 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
966 label = gtk_label_new(_("Write"));
967 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
968 gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 0, 1);
969 label = gtk_label_new(_("Exec"));
970 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
971 gtk_table_attach_defaults(GTK_TABLE(table), label, 3, 4, 0, 1);
973 for (i = 0; i < 9; i++)
975 x = 1 + 2 - i % 3;
976 y = 1 + 2 - i / 3;
977 perm->bits[i] = tick = gtk_check_button_new();
978 gtk_table_attach_defaults(GTK_TABLE(table), tick,
979 x, x + 1, y, y + 1);
980 if (item->mode & (1 << i))
981 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick),
982 TRUE);
983 g_signal_connect(tick, "toggled",
984 G_CALLBACK(permissions_apply), perm);
987 tick = gtk_check_button_new_with_label(_("SUID"));
988 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 1, 2);
989 if (item->mode & S_ISUID)
990 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
991 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
992 perm->bits[9] = tick;
994 tick = gtk_check_button_new_with_label(_("SGID"));
995 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 2, 3);
996 if (item->mode & S_ISGID)
997 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
998 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
999 perm->bits[10] = tick;
1001 tick = gtk_check_button_new_with_label(_("Sticky"));
1002 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 3, 4);
1003 if (item->mode & S_ISVTX)
1004 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
1005 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
1006 perm->bits[11] = tick;
1008 g_signal_connect(table, "destroy",
1009 G_CALLBACK(permissions_destroyed), perm);
1011 gtk_widget_show_all(table);
1012 return table;
1015 static void unmount_option_toggled(GtkToggleButton *toggle, const char *path)
1017 if (gtk_toggle_button_get_active(toggle))
1019 filer_set_unmount_action(path,
1020 GPOINTER_TO_INT(g_object_get_data(G_OBJECT(toggle),
1021 "unmount_action")));
1025 static GtkWidget *pack_unmount_radio(const char *path,
1026 UnmountPrompt path_value, const char *label,
1027 UnmountPrompt btn_value, GtkWidget *group_owner, GtkWidget *hbox)
1029 GtkWidget *radio;
1031 if (group_owner)
1033 radio = gtk_radio_button_new_with_label_from_widget(
1034 GTK_RADIO_BUTTON(group_owner), label);
1036 else
1038 radio = gtk_radio_button_new_with_label(NULL, label);
1040 if (path_value == btn_value)
1041 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
1042 g_object_set_data(G_OBJECT(radio), "unmount_action",
1043 GINT_TO_POINTER(btn_value));
1044 g_signal_connect(radio, "toggled", G_CALLBACK(unmount_option_toggled),
1045 (gpointer) path);
1046 gtk_box_pack_start(GTK_BOX(hbox), radio, FALSE, FALSE, 0);
1047 return radio;
1050 static GtkWidget *make_unmount_options(const char *path)
1052 GtkWidget *hbox, *radio;
1053 UnmountPrompt upval = filer_get_unmount_action(path);
1055 hbox = gtk_hbox_new(TRUE, 4);
1056 radio = pack_unmount_radio(path, upval,
1057 _("Do nothing"), UNMOUNT_PROMPT_NO_CHANGE, NULL, hbox);
1058 radio = pack_unmount_radio(path, upval,
1059 _("Unmount"), UNMOUNT_PROMPT_UNMOUNT, radio, hbox);
1060 radio = pack_unmount_radio(path, upval,
1061 _("Eject"), UNMOUNT_PROMPT_EJECT, radio, hbox);
1062 pack_unmount_radio(path, upval,
1063 _("Ask"), UNMOUNT_PROMPT_ASK, radio, hbox);
1064 return hbox;
1067 /* Don't g_free() the result */
1068 static const gchar *pretty_type(DirItem *file, const guchar *path)
1070 static gchar *text = NULL;
1072 null_g_free(&text);
1074 if (file->flags & ITEM_FLAG_SYMLINK)
1075 return _("Symbolic link");
1077 if (file->flags & ITEM_FLAG_APPDIR)
1078 return _("ROX application");
1080 if (file->flags & ITEM_FLAG_MOUNT_POINT)
1082 MountPoint *mp;
1083 const gchar *mounted;
1085 mounted = mount_is_mounted(path, NULL, NULL)
1086 ? _("mounted") : _("unmounted");
1088 mp = g_hash_table_lookup(fstab_mounts, path);
1089 if (mp)
1090 text = g_strdup_printf(_("Mount point for %s (%s)"),
1091 mp->name, mounted);
1092 else
1093 text = g_strdup_printf(_("Mount point (%s)"), mounted);
1094 return text;
1097 if (file->mime_type)
1099 text = g_strconcat(file->mime_type->media_type, "/",
1100 file->mime_type->subtype, NULL);
1101 return text;
1104 return "-";