r3305: Misc changes for next release.
[rox-filer.git] / ROX-Filer / src / infobox.c
blob46b64e21b14698fde567d9f025d05b97a2ce1c5a
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2003, the ROX-Filer team.
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)
10 * any later version.
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
15 * more details.
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 /* infobox.c - code for showing a file's attributes */
24 #include "config.h"
26 #include <errno.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/param.h>
30 #include <signal.h>
31 #include <libxml/parser.h>
33 #include <gtk/gtk.h>
35 #include "global.h"
37 #include "support.h"
38 #include "main.h"
39 #include "gui_support.h"
40 #include "diritem.h"
41 #include "type.h"
42 #include "infobox.h"
43 #include "appinfo.h"
44 #include "dnd.h" /* For xa_string */
45 #include "run.h" /* For show_help_files() */
46 #include "xml.h"
47 #include "mount.h"
48 #include "pixmaps.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_file_says(const guchar *path);
85 static GtkWidget *make_permissions(const gchar *path, DirItem *item);
86 static void add_file_output(FileStatus *fs,
87 gint source, GdkInputCondition condition);
88 static const gchar *pretty_type(DirItem *file, const guchar *path);
89 static void got_response(GObject *window, gint response, gpointer data);
90 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs);
92 /****************************************************************
93 * EXTERNAL INTERFACE *
94 ****************************************************************/
96 /* Open each item in a new infobox. Confirms if there are a large
97 * number of items to show.
99 void infobox_show_list(GList *paths)
101 int n;
103 n = g_list_length(paths);
105 if (n >= 10)
107 gchar *message;
108 gboolean ok;
110 message = g_strdup_printf(
111 _("Are you sure you want to open %d windows?"), n);
112 ok = confirm(message, GTK_STOCK_YES, _("Show Info"));
113 g_free(message);
114 if (!ok)
115 return;
118 g_list_foreach(paths, (GFunc) infobox_new, NULL);
121 /* Create and display a new info box showing details about this item */
122 void infobox_new(const gchar *pathname)
124 GtkWidget *window, *details;
125 gchar *path;
126 GObject *owindow;
128 g_return_if_fail(pathname != NULL);
130 path = g_strdup(pathname); /* Gets attached to window & freed later */
132 window = gtk_dialog_new_with_buttons(
133 g_utf8_validate(path, -1, NULL) ? path
134 : _("(bad utf-8)"),
135 NULL, GTK_DIALOG_NO_SEPARATOR,
136 GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
137 GTK_STOCK_REFRESH, GTK_RESPONSE_APPLY,
138 NULL);
140 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
142 owindow = G_OBJECT(window);
143 details = make_vbox(path, owindow);
144 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(window)->vbox),
145 details);
147 g_object_set_data(owindow, "details", details);
148 g_object_set_data_full(owindow, "path", path, g_free);
150 g_signal_connect(window, "response", G_CALLBACK(got_response), NULL);
152 number_of_windows++;
153 gtk_widget_show_all(window);
156 /****************************************************************
157 * INTERNAL FUNCTIONS *
158 ****************************************************************/
160 static void got_response(GObject *window, gint response, gpointer data)
162 if (response == GTK_RESPONSE_APPLY)
163 refresh_info(window);
164 else
166 gtk_widget_destroy(GTK_WIDGET(window));
167 one_less_window();
171 static void refresh_info(GObject *window)
173 GtkWidget *details, *vbox;
174 guchar *path;
176 path = g_object_get_data(window, "path");
177 details = g_object_get_data(window, "details");
178 g_return_if_fail(details != NULL);
179 g_return_if_fail(path != NULL);
181 vbox = details->parent;
182 gtk_widget_destroy(details);
184 details = make_vbox(path, window);
185 g_object_set_data(window, "details", details);
186 gtk_box_pack_start_defaults(GTK_BOX(vbox), details);
187 gtk_widget_show_all(details);
190 static void add_frame(GtkBox *vbox, GtkWidget *list)
192 GtkWidget *frame;
194 frame = gtk_frame_new(NULL);
195 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
196 gtk_container_add(GTK_CONTAINER(frame), list);
197 gtk_box_pack_start_defaults(vbox, frame);
200 /* Create the VBox widget that contains the details.
201 * Note that 'path' must not be freed until the vbox is destroyed.
203 static GtkWidget *make_vbox(const guchar *path, GObject *window)
205 DirItem *item;
206 GtkBox *vbox;
207 XMLwrapper *ai;
208 xmlNode *about = NULL;
209 gchar *help_dir;
210 GtkWidget *hbox, *name, *label;
212 g_return_val_if_fail(path[0] == '/', NULL);
214 item = diritem_new(g_basename(path));
215 diritem_restat(path, item, NULL);
217 ai = appinfo_get(path, item);
218 if (ai)
219 about = xml_get_section(ai, NULL, "About");
221 vbox = GTK_BOX(gtk_vbox_new(FALSE, 4));
222 gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);
224 /* Heading, with icon and name */
225 hbox = gtk_hbox_new(FALSE, 4);
226 gtk_box_pack_start(vbox, hbox, FALSE, TRUE, 0);
227 gtk_box_pack_start(GTK_BOX(hbox),
228 gtk_image_new_from_pixbuf(item->image->pixbuf),
229 FALSE, FALSE, 4);
231 if (g_utf8_validate(item->leafname, -1, NULL))
232 name = gtk_label_new(item->leafname);
233 else
235 guchar *u8;
237 u8 = to_utf8(item->leafname);
238 name = gtk_label_new(u8);
239 g_free(u8);
241 gtk_label_set_selectable(GTK_LABEL(name), TRUE);
242 gtk_box_pack_start(GTK_BOX(hbox), name, FALSE, TRUE, 4);
244 /* Make the name bolder and larger */
246 PangoAttribute *attr;
247 PangoAttrList *list;
249 list = pango_attr_list_new();
251 attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
252 attr->start_index = 0;
253 attr->end_index = -1;
254 pango_attr_list_insert(list, attr);
256 attr = pango_attr_scale_new(PANGO_SCALE_X_LARGE);
257 attr->start_index = 0;
258 attr->end_index = -1;
259 pango_attr_list_insert(list, attr);
261 gtk_label_set_attributes(GTK_LABEL(name), list);
264 /* List of file attributes */
265 add_frame(vbox, make_details(path, item, window));
267 help_dir = g_strconcat(path, "/Help", NULL);
269 if (access(help_dir, F_OK) == 0)
271 GtkWidget *button, *align;
273 align = gtk_alignment_new(0.5, 0.5, 0, 0);
275 button = button_new_mixed(GTK_STOCK_JUMP_TO,
276 _("Show _Help Files"));
277 gtk_box_pack_start(vbox, align, FALSE, TRUE, 0);
278 gtk_container_add(GTK_CONTAINER(align), button);
279 g_signal_connect_swapped(button, "clicked",
280 G_CALLBACK(show_help_files),
281 (gpointer) path);
283 g_free(help_dir);
285 label = gtk_label_new(NULL);
286 gtk_label_set_markup(GTK_LABEL(label), _("<b>Permissions</b>"));
287 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
288 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
290 gtk_box_pack_start(vbox, make_permissions(path, item),
291 FALSE, TRUE, 0);
293 if (about)
294 add_frame(vbox, make_about(path, ai));
295 else if (item->base_type == TYPE_FILE)
297 label = gtk_label_new(NULL);
298 gtk_label_set_markup(GTK_LABEL(label),
299 _("<b>Contents indicate...</b>"));
300 gtk_misc_set_alignment(GTK_MISC(label), 0, 1);
301 gtk_box_pack_start(vbox, label, FALSE, TRUE, 2);
303 gtk_box_pack_start_defaults(vbox, make_file_says(path));
306 if (ai)
307 g_object_unref(ai);
309 diritem_free(item);
311 return (GtkWidget *) vbox;
314 /* The selection has changed - grab or release the primary selection */
315 static void set_selection(GtkTreeView *view, gpointer data)
317 static GtkClipboard *primary = NULL;
318 GtkTreeModel *model;
319 GtkTreePath *path = NULL;
320 GtkTreeIter iter;
321 gchar *text;
323 gtk_tree_view_get_cursor(view, &path, NULL);
324 if (!path)
325 return;
327 if (!primary)
328 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
330 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
332 gtk_tree_model_get_iter(model, &iter, path);
333 gtk_tree_path_free(path);
335 gtk_tree_model_get(model, &iter, 1, &text, -1);
337 gtk_clipboard_set_text(primary, text, -1);
339 g_free(text);
342 /* Returns a GtkTreePath for the item */
343 static const gchar *add_row(GtkListStore *store, const gchar *label,
344 const gchar *data)
346 GtkTreeIter iter;
347 gchar *u8 = NULL;
348 GtkTreePath *tpath;
349 static gchar *last = NULL;
351 if (!g_utf8_validate(data, -1, NULL))
352 u8 = to_utf8(data);
354 gtk_list_store_append(store, &iter);
355 gtk_list_store_set(store, &iter, 0, label, 1, u8 ? u8 : data, -1);
357 g_free(u8);
359 tpath = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
360 if (last)
361 g_free(last);
362 last = gtk_tree_path_to_string(tpath);
363 gtk_tree_path_free(tpath);
365 return last;
368 static void add_row_and_free(GtkListStore *store,
369 const gchar *label, gchar *data)
371 add_row(store, label, data);
372 g_free(data);
375 /* Create an empty list view, ready to place some data in */
376 static void make_list(GtkListStore **list_store, GtkWidget **list_view,
377 GCallback cell_edited)
379 GtkListStore *store;
380 GtkTreeView *view;
381 GtkCellRenderer *cell_renderer;
383 /* Field name, value, editable */
384 store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING,
385 G_TYPE_BOOLEAN);
386 view = GTK_TREE_VIEW(
387 gtk_tree_view_new_with_model(GTK_TREE_MODEL(store)));
388 g_object_unref(G_OBJECT(store));
389 gtk_tree_view_set_headers_visible(view, FALSE);
391 cell_renderer = gtk_cell_renderer_text_new();
392 g_object_set(G_OBJECT(cell_renderer), "xalign", 1.0, NULL);
393 gtk_tree_view_insert_column_with_attributes(view,
394 0, NULL, cell_renderer, "text", 0, NULL);
396 cell_renderer = gtk_cell_renderer_text_new();
397 gtk_tree_view_insert_column_with_attributes(view,
398 1, NULL, cell_renderer, "text", 1, "editable", 2, NULL);
400 if (cell_edited) {
401 g_signal_connect(G_OBJECT(cell_renderer), "edited",
402 G_CALLBACK(cell_edited), store);
405 g_signal_connect(view, "cursor_changed",
406 G_CALLBACK(set_selection), NULL);
408 *list_store = store;
409 *list_view = (GtkWidget *) view;
412 static void set_cell(GtkListStore *store, const gchar *path,
413 const gchar *ctext)
415 GtkTreeIter iter;
417 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store),
418 &iter, path);
419 gtk_list_store_set(store, &iter, 1, ctext, -1);
422 static void insert_size(DU *du, const char *line)
424 off_t size;
425 gchar *cell;
427 #ifdef LARGE_FILE_SUPPORT
428 size = strtoll(line, NULL, 10);
429 #else
430 size = strtol(line, NULL, 10);
431 #endif
432 size <<= 10; /* Because du reports in K */
433 cell = (size >= PRETTY_SIZE_LIMIT)
434 ? g_strdup_printf("%s (%" SIZE_FMT " %s)",
435 format_size(size),
436 size, _("bytes"))
437 : g_strdup(format_size(size));
439 set_cell(du->store, du->path, cell);
441 g_free(cell);
444 static gboolean read_du_output(GIOChannel *source, GIOCondition cond, DU *du)
446 GString *line;
447 GIOStatus stat;
448 GError *err = NULL;
450 line = g_string_new("");
451 stat = g_io_channel_read_line_string(source, line, NULL, &err);
452 switch (stat)
454 case G_IO_STATUS_NORMAL:
455 insert_size(du, line->str);
456 break;
457 case G_IO_STATUS_EOF:
458 break;
459 case G_IO_STATUS_AGAIN:
460 g_string_free(line, TRUE);
461 return TRUE;
462 case G_IO_STATUS_ERROR:
463 set_cell(du->store, du->path, err->message);
464 break;
466 g_string_free(line, TRUE);
468 return FALSE;
471 /* XXX: possible race?: might still get output from process after freeing
472 * everything. Should wait for EOF / close channel?
474 static void kill_du_output(GtkWidget *widget, DU *du)
476 g_io_channel_unref(du->chan);
477 kill((pid_t) du->child, SIGTERM);
478 g_object_unref(G_OBJECT(du->store));
479 g_free(du->path);
480 g_free(du);
483 static gboolean refresh_info_idle(gpointer data)
485 GObject *window = G_OBJECT(data);
487 refresh_info(window);
488 g_object_unref(window);
489 return FALSE;
492 static void cell_edited(GtkCellRendererText *cell,
493 const gchar *path_string,
494 const gchar *new_text,
495 gpointer data)
497 GtkTreeModel *model = (GtkTreeModel *) data;
498 GtkTreePath *path;
499 GtkTreeIter iter;
500 GObject *window;
501 const char *fullpath;
502 char *oldlink;
504 window = g_object_get_data(G_OBJECT(model), "rox_window");
505 g_return_if_fail(window != NULL);
507 fullpath = g_object_get_data(window, "path");
508 g_return_if_fail(fullpath != NULL);
510 path = gtk_tree_path_new_from_string(path_string);
511 gtk_tree_model_get_iter(model, &iter, path);
512 gtk_tree_path_free(path);
514 oldlink = readlink_dup(fullpath);
515 if (!oldlink) {
516 report_error(_("'%s' is no longer a symlink"), fullpath);
517 return;
519 if (strcmp(oldlink, new_text) == 0)
520 return; /* No change */
521 g_free(oldlink);
522 if (unlink(fullpath)) {
523 report_error(_("Failed to unlink '%s':\n%s"),
524 fullpath, g_strerror(errno));
525 return;
527 if (symlink(new_text, fullpath)) {
528 report_error(_("Failed to create symlink from '%s':\n%s\n"
529 "(note: old link has been deleted)"));
530 return;
533 g_object_ref(window);
534 gtk_idle_add(refresh_info_idle, window);
537 /* Create the TreeView widget with the file's details */
538 static GtkWidget *make_details(const guchar *path, DirItem *item,
539 GObject *window)
541 GtkListStore *store;
542 GtkWidget *view;
543 gchar *tmp, *tmp2;
545 make_list(&store, &view, G_CALLBACK(cell_edited));
546 g_object_set_data(G_OBJECT(store), "rox_window", window);
548 /* For a symlink to an error, don't show the error */
549 if (item->base_type == TYPE_ERROR && item->lstat_errno)
551 add_row(store, _("Error:"), g_strerror(item->lstat_errno));
552 return view;
555 tmp = g_path_get_dirname(path);
556 tmp2 = pathdup(tmp);
557 if (strcmp(tmp, tmp2) != 0)
558 add_row_and_free(store, _("Real directory:"), tmp2);
559 g_free(tmp);
561 add_row_and_free(store, _("Owner, Group:"),
562 g_strdup_printf("%s, %s",
563 user_name(item->uid),
564 group_name(item->gid)));
566 if (item->base_type != TYPE_DIRECTORY)
568 add_row_and_free(store, _("Size:"),
569 item->size >= PRETTY_SIZE_LIMIT
570 ? g_strdup_printf("%s (%" SIZE_FMT " %s)",
571 format_size(item->size),
572 item->size, _("bytes"))
573 : g_strdup(format_size(item->size)));
575 else
577 DU *du;
578 int out;
579 gchar *args[] = {"du", "-sk", "", NULL};
581 du = g_new(DU, 1);
582 du->store = store;
583 du->path = g_strdup(add_row(store, _("Size:"), _("Scanning")));
585 args[2] = (gchar *) path;
586 if (g_spawn_async_with_pipes(NULL, args, NULL,
587 G_SPAWN_SEARCH_PATH,
588 NULL, NULL, &du->child,
589 NULL, &out, NULL,
590 NULL))
592 du->chan = g_io_channel_unix_new(out);
593 du->watch = g_io_add_watch(du->chan, G_IO_IN,
594 (GIOFunc) read_du_output, du);
595 g_object_ref(G_OBJECT(du->store));
596 g_signal_connect(G_OBJECT(view), "destroy",
597 G_CALLBACK(kill_du_output), du);
599 else
601 set_cell(store, du->path, _("Failed to scan"));
602 g_free(du->path);
603 g_free(du);
607 add_row_and_free(store, _("Change time:"), pretty_time(&item->ctime));
609 add_row_and_free(store, _("Modify time:"), pretty_time(&item->mtime));
611 add_row_and_free(store, _("Access time:"), pretty_time(&item->atime));
613 add_row(store, _("Type:"), pretty_type(item, path));
615 if (item->mime_type)
616 add_row(store, "", mime_type_comment(item->mime_type));
618 if (item->flags & ITEM_FLAG_SYMLINK)
620 GtkTreeIter iter;
621 GtkTreeModel *model = GTK_TREE_MODEL(store);
622 char *target;
624 target = readlink_dup(path);
625 if (!target)
626 target = g_strdup(g_strerror(errno));
627 add_row_and_free(store, _("Link target:"), target);
629 /* Make cell editable */
630 gtk_tree_model_iter_nth_child(model, &iter,
631 NULL, gtk_tree_model_iter_n_children(model, NULL) - 1);
633 gtk_list_store_set(store, &iter, 2, TRUE, -1);
636 if (item->base_type != TYPE_DIRECTORY)
637 add_row_and_free(store, _("Run action:"),
638 describe_current_command(item->mime_type));
640 return view;
643 /* Create the TreeView widget with the application's details */
644 static GtkWidget *make_about(const guchar *path, XMLwrapper *ai)
646 GtkListStore *store;
647 GtkWidget *view;
648 xmlNode *prop;
649 xmlNode *about, *about_trans;
650 GHashTable *translate;
652 g_return_val_if_fail(ai != NULL, NULL);
654 about_trans = xml_get_section(ai, NULL, "About");
656 about = xmlDocGetRootElement(ai->doc)->xmlChildrenNode;
657 for (; about; about = about->next)
659 if (about->type != XML_ELEMENT_NODE)
660 continue;
661 if (about->ns == NULL && strcmp(about->name, "About") == 0)
662 break;
665 g_return_val_if_fail(about != NULL, NULL);
667 make_list(&store, &view, NULL);
669 /* Add each field in about to the list, but overriding each element
670 * with about_trans if a translation is supplied.
672 translate = g_hash_table_new(g_str_hash, g_str_equal);
673 if (about_trans != about)
675 xmlNode *p;
676 for (p = about_trans->xmlChildrenNode; p; p = p->next)
678 if (p->type != XML_ELEMENT_NODE)
679 continue;
680 g_hash_table_insert(translate, (char *) p->name, p);
683 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
685 if (prop->type == XML_ELEMENT_NODE)
687 char *label = NULL;
688 char *value = NULL;
689 char *tmp = NULL;
690 xmlNode *trans;
692 trans = g_hash_table_lookup(translate, prop->name);
693 if (!trans)
694 trans = prop;
696 tmp = xmlGetProp(trans, "label");
697 label = g_strconcat(tmp ? tmp
698 : (char *) trans->name,
699 ":", NULL);
700 g_free(tmp);
701 value = xmlNodeListGetString(trans->doc,
702 trans->xmlChildrenNode, 1);
703 if (!value)
704 value = xmlNodeListGetString(prop->doc,
705 prop->xmlChildrenNode, 1);
706 if (!value)
707 value = g_strdup("-");
708 add_row_and_free(store, label, value);
709 g_free(label);
713 g_hash_table_destroy(translate);
715 return view;
718 static GtkWidget *make_file_says(const guchar *path)
720 GtkWidget *w_file_label;
721 GtkLabel *l_file_label;
722 int file_data[2];
723 char *argv[] = {"file", "-b", NULL, NULL};
724 FileStatus *fs = NULL;
725 guchar *tmp;
727 w_file_label = gtk_label_new(_("<nothing yet>"));
728 l_file_label = GTK_LABEL(w_file_label);
729 gtk_label_set_line_wrap(l_file_label, TRUE);
731 if (pipe(file_data))
733 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
734 gtk_label_set_text(l_file_label, tmp);
735 g_free(tmp);
736 return w_file_label;
739 switch (fork())
741 case -1:
742 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
743 gtk_label_set_text(l_file_label, tmp);
744 g_free(tmp);
745 close(file_data[0]);
746 close(file_data[1]);
747 break;
748 case 0:
749 /* We are the child */
750 close(file_data[0]);
751 dup2(file_data[1], STDOUT_FILENO);
752 dup2(file_data[1], STDERR_FILENO);
753 #ifdef FILE_B_FLAG
754 argv[2] = (char *) path;
755 #else
756 argv[1] = (char *) g_basename(path);
757 chdir(g_path_get_dirname(path));
758 #endif
759 if (execvp(argv[0], argv))
760 fprintf(stderr, "execvp() error: %s\n",
761 g_strerror(errno));
762 _exit(0);
763 default:
764 /* We are the parent */
765 close(file_data[1]);
766 fs = g_new(FileStatus, 1);
767 fs->label = l_file_label;
768 fs->fd = file_data[0];
769 fs->text = g_strdup("");
770 fs->input = gtk_input_add_full(fs->fd, GDK_INPUT_READ,
771 (GdkInputFunction) add_file_output,
772 NULL, fs, NULL);
773 g_signal_connect(w_file_label, "destroy",
774 G_CALLBACK(file_info_destroyed), fs);
775 break;
778 return w_file_label;
781 /* Got some data from file(1) - stick it in the window. */
782 static void add_file_output(FileStatus *fs,
783 gint source, GdkInputCondition condition)
785 char buffer[20];
786 char *str;
787 int got;
789 got = read(source, buffer, sizeof(buffer) - 1);
790 if (got <= 0)
792 int err = errno;
793 gtk_input_remove(fs->input);
794 close(source);
795 fs->fd = -1;
796 if (got < 0)
797 delayed_error(_("file(1) says... %s"),
798 g_strerror(err));
799 return;
801 buffer[got] = '\0';
803 str = g_strconcat(fs->text, buffer, NULL);
804 g_free(fs->text);
805 fs->text = str;
807 str = to_utf8(fs->text);
808 g_strstrip(str);
809 gtk_label_set_text(fs->label, str);
810 g_free(str);
813 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
815 if (fs->fd != -1)
817 gtk_input_remove(fs->input);
818 close(fs->fd);
821 g_free(fs->text);
822 g_free(fs);
825 static void permissions_destroyed(GtkWidget *widget, Permissions *perm)
827 g_free(perm->path);
828 diritem_free(perm->item);
830 g_free(perm);
833 static void permissions_apply(GtkWidget *widget, Permissions *perm)
835 mode_t nmode;
836 int i;
838 nmode=0;
840 for (i = 0; i < 9; i++)
842 GtkToggleButton *bit = GTK_TOGGLE_BUTTON(perm->bits[i]);
843 if (gtk_toggle_button_get_active(bit))
844 nmode |= 1 << i;
846 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[9])))
847 nmode |= S_ISUID;
848 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[10])))
849 nmode |= S_ISGID;
850 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm->bits[11])))
851 nmode |= S_ISVTX;
853 if (chmod(perm->path, nmode))
854 report_error(_("Could not change permissions: %s"),
855 g_strerror(errno));
858 static GtkWidget *make_permissions(const gchar *path, DirItem *item)
860 Permissions *perm;
861 GtkWidget *table;
862 GtkWidget *tick, *label;
863 int i, x, y;
865 perm = g_new(Permissions, 1);
867 perm->path = g_strdup(path);
868 perm->item = diritem_new(path);
870 table = gtk_table_new(4, 5, TRUE);
872 label = gtk_label_new(_("Owner"));
873 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
874 label = gtk_label_new(_("Group"));
875 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
876 label = gtk_label_new(_("World"));
877 gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
879 label = gtk_label_new(_("Read"));
880 gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
881 label = gtk_label_new(_("Write"));
882 gtk_table_attach_defaults(GTK_TABLE(table), label, 2, 3, 0, 1);
883 label = gtk_label_new(_("Exec"));
884 gtk_table_attach_defaults(GTK_TABLE(table), label, 3, 4, 0, 1);
886 for (i = 0; i < 9; i++)
888 x = 1 + 2 - i % 3;
889 y = 1 + 2 - i / 3;
890 perm->bits[i] = tick = gtk_check_button_new();
891 gtk_table_attach_defaults(GTK_TABLE(table), tick,
892 x, x + 1, y, y + 1);
893 if (item->mode & (1 << i))
894 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick),
895 TRUE);
896 g_signal_connect(tick, "toggled",
897 G_CALLBACK(permissions_apply), perm);
900 tick = gtk_check_button_new_with_label(_("SUID"));
901 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 1, 2);
902 if (item->mode & S_ISUID)
903 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
904 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
905 perm->bits[9] = tick;
907 tick = gtk_check_button_new_with_label(_("SGID"));
908 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 2, 3);
909 if (item->mode & S_ISGID)
910 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
911 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
912 perm->bits[10] = tick;
914 tick = gtk_check_button_new_with_label(_("Sticky"));
915 gtk_table_attach_defaults(GTK_TABLE(table), tick, 4, 5, 3, 4);
916 if (item->mode & S_ISVTX)
917 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick), TRUE);
918 g_signal_connect(tick, "toggled", G_CALLBACK(permissions_apply), perm);
919 perm->bits[11] = tick;
921 g_signal_connect(table, "destroy",
922 G_CALLBACK(permissions_destroyed), perm);
924 gtk_widget_show_all(table);
925 return table;
928 /* Don't g_free() the result */
929 static const gchar *pretty_type(DirItem *file, const guchar *path)
931 static gchar *text = NULL;
933 null_g_free(&text);
935 if (file->flags & ITEM_FLAG_SYMLINK)
936 return _("Symbolic link");
938 if (file->flags & ITEM_FLAG_APPDIR)
939 return _("ROX application");
941 if (file->flags & ITEM_FLAG_MOUNT_POINT)
943 MountPoint *mp;
944 const gchar *mounted;
946 mounted = mount_is_mounted(path, NULL, NULL)
947 ? _("mounted") : _("unmounted");
949 mp = g_hash_table_lookup(fstab_mounts, path);
950 if (mp)
951 text = g_strdup_printf(_("Mount point for %s (%s)"),
952 mp->name, mounted);
953 else
954 text = g_strdup_printf(_("Mount point (%s)"), mounted);
955 return text;
958 if (file->mime_type)
960 text = g_strconcat(file->mime_type->media_type, "/",
961 file->mime_type->subtype, NULL);
962 return text;
965 return "-";