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)
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 /* infobox.c - code for showing a file's attributes */
29 #include <sys/param.h>
30 #include <libxml/parser.h>
38 #include "gui_support.h"
43 #include "dnd.h" /* For xa_string */
44 #include "run.h" /* For show_item_help() */
49 typedef struct _FileStatus FileStatus
;
51 /* This is for the 'file(1) says...' thing */
54 int fd
; /* FD to read from, -1 if closed */
55 int input
; /* Input watcher tag if fd valid */
56 GtkLabel
*label
; /* Widget to output to */
57 gchar
*text
; /* String so far */
67 typedef struct _Permissions Permissions
;
76 /* Static prototypes */
77 static void refresh_info(GObject
*window
);
78 static GtkWidget
*make_vbox(const guchar
*path
);
79 static GtkWidget
*make_details(const guchar
*path
, DirItem
*item
);
80 static GtkWidget
*make_about(const guchar
*path
, XMLwrapper
*ai
);
81 static GtkWidget
*make_file_says(const guchar
*path
);
82 static GtkWidget
*make_permissions(const gchar
*path
, DirItem
*item
);
83 static void add_file_output(FileStatus
*fs
,
84 gint source
, GdkInputCondition condition
);
85 static const gchar
*pretty_type(DirItem
*file
, const guchar
*path
);
86 static void got_response(GObject
*window
, gint response
, gpointer data
);
87 static void file_info_destroyed(GtkWidget
*widget
, FileStatus
*fs
);
89 /****************************************************************
90 * EXTERNAL INTERFACE *
91 ****************************************************************/
93 /* Open each item in a new infobox. Confirms if there are a large
94 * number of items to show.
96 void infobox_show_list(GList
*paths
)
100 n
= g_list_length(paths
);
107 message
= g_strdup_printf(
108 _("Are you sure you want to open %d windows?"), n
);
109 ok
= confirm(message
, GTK_STOCK_YES
, _("Show Info"));
115 g_list_foreach(paths
, (GFunc
) infobox_new
, NULL
);
118 /* Create and display a new info box showing details about this item */
119 void infobox_new(const gchar
*pathname
)
121 GtkWidget
*window
, *details
;
125 g_return_if_fail(pathname
!= NULL
);
127 path
= g_strdup(pathname
); /* Gets attached to window & freed later */
129 window
= gtk_dialog_new_with_buttons(
130 g_utf8_validate(path
, -1, NULL
) ? path
132 NULL
, GTK_DIALOG_NO_SEPARATOR
,
133 GTK_STOCK_CANCEL
, GTK_RESPONSE_DELETE_EVENT
,
134 GTK_STOCK_REFRESH
, GTK_RESPONSE_APPLY
,
137 gtk_window_set_position(GTK_WINDOW(window
), GTK_WIN_POS_MOUSE
);
139 details
= make_vbox(path
);
140 gtk_box_pack_start_defaults(GTK_BOX(GTK_DIALOG(window
)->vbox
),
143 owindow
= G_OBJECT(window
);
144 g_object_set_data(owindow
, "details", details
);
145 g_object_set_data_full(owindow
, "path", path
, g_free
);
147 g_signal_connect(window
, "response", G_CALLBACK(got_response
), NULL
);
150 gtk_widget_show_all(window
);
153 /****************************************************************
154 * INTERNAL FUNCTIONS *
155 ****************************************************************/
157 static void show_help_clicked(const gchar
*path
)
161 item
= diritem_new("");
162 diritem_restat(path
, item
, NULL
);
163 show_item_help(path
, item
);
167 static void got_response(GObject
*window
, gint response
, gpointer data
)
169 if (response
== GTK_RESPONSE_APPLY
)
170 refresh_info(window
);
173 gtk_widget_destroy(GTK_WIDGET(window
));
178 static void refresh_info(GObject
*window
)
180 GtkWidget
*details
, *vbox
;
183 path
= g_object_get_data(window
, "path");
184 details
= g_object_get_data(window
, "details");
185 g_return_if_fail(details
!= NULL
);
186 g_return_if_fail(path
!= NULL
);
188 vbox
= details
->parent
;
189 gtk_widget_destroy(details
);
191 details
= make_vbox(path
);
192 g_object_set_data(window
, "details", details
);
193 gtk_box_pack_start_defaults(GTK_BOX(vbox
), details
);
194 gtk_widget_show_all(details
);
197 static void add_frame(GtkBox
*vbox
, GtkWidget
*list
)
201 frame
= gtk_frame_new(NULL
);
202 gtk_frame_set_shadow_type(GTK_FRAME(frame
), GTK_SHADOW_IN
);
203 gtk_container_add(GTK_CONTAINER(frame
), list
);
204 gtk_box_pack_start_defaults(vbox
, frame
);
207 /* Create the VBox widget that contains the details.
208 * Note that 'path' must not be freed until the vbox is destroyed.
210 static GtkWidget
*make_vbox(const guchar
*path
)
215 xmlNode
*about
= NULL
;
217 GtkWidget
*hbox
, *name
, *label
;
219 g_return_val_if_fail(path
[0] == '/', NULL
);
221 item
= diritem_new(g_basename(path
));
222 diritem_restat(path
, item
, NULL
);
224 ai
= appinfo_get(path
, item
);
226 about
= xml_get_section(ai
, NULL
, "About");
228 vbox
= GTK_BOX(gtk_vbox_new(FALSE
, 4));
229 gtk_container_set_border_width(GTK_CONTAINER(vbox
), 4);
231 /* Heading, with icon and name */
232 hbox
= gtk_hbox_new(FALSE
, 4);
233 gtk_box_pack_start(vbox
, hbox
, FALSE
, TRUE
, 0);
234 gtk_box_pack_start(GTK_BOX(hbox
),
235 gtk_image_new_from_pixbuf(item
->image
->pixbuf
),
238 if (g_utf8_validate(item
->leafname
, -1, NULL
))
239 name
= gtk_label_new(item
->leafname
);
244 u8
= to_utf8(item
->leafname
);
245 name
= gtk_label_new(u8
);
248 gtk_label_set_selectable(GTK_LABEL(name
), TRUE
);
249 gtk_box_pack_start(GTK_BOX(hbox
), name
, FALSE
, TRUE
, 4);
251 /* Make the name bolder and larger */
253 PangoAttribute
*attr
;
256 list
= pango_attr_list_new();
258 attr
= pango_attr_weight_new(PANGO_WEIGHT_BOLD
);
259 attr
->start_index
= 0;
260 attr
->end_index
= -1;
261 pango_attr_list_insert(list
, attr
);
263 attr
= pango_attr_scale_new(PANGO_SCALE_X_LARGE
);
264 attr
->start_index
= 0;
265 attr
->end_index
= -1;
266 pango_attr_list_insert(list
, attr
);
268 gtk_label_set_attributes(GTK_LABEL(name
), list
);
271 /* List of file attributes */
272 add_frame(vbox
, make_details(path
, item
));
274 help_dir
= g_strconcat(path
, "/Help", NULL
);
276 if (access(help_dir
, F_OK
) == 0)
278 GtkWidget
*button
, *align
;
280 align
= gtk_alignment_new(0.5, 0.5, 0, 0);
282 button
= button_new_mixed(GTK_STOCK_JUMP_TO
,
283 _("Show _Help Files"));
284 gtk_box_pack_start(vbox
, align
, FALSE
, TRUE
, 0);
285 gtk_container_add(GTK_CONTAINER(align
), button
);
286 g_signal_connect_swapped(button
, "clicked",
287 G_CALLBACK(show_help_clicked
),
292 label
= gtk_label_new(NULL
);
293 gtk_label_set_markup(GTK_LABEL(label
), _("<b>Permissions</b>"));
294 gtk_misc_set_alignment(GTK_MISC(label
), 0, 1);
295 gtk_box_pack_start(vbox
, label
, FALSE
, TRUE
, 2);
297 gtk_box_pack_start(vbox
, make_permissions(path
, item
),
301 add_frame(vbox
, make_about(path
, ai
));
302 else if (item
->base_type
== TYPE_FILE
)
304 label
= gtk_label_new(NULL
);
305 gtk_label_set_markup(GTK_LABEL(label
),
306 _("<b>Contents indicate...</b>"));
307 gtk_misc_set_alignment(GTK_MISC(label
), 0, 1);
308 gtk_box_pack_start(vbox
, label
, FALSE
, TRUE
, 2);
310 gtk_box_pack_start_defaults(vbox
, make_file_says(path
));
318 return (GtkWidget
*) vbox
;
321 /* The selection has changed - grab or release the primary selection */
322 static void set_selection(GtkTreeView
*view
, gpointer data
)
324 static GtkClipboard
*primary
= NULL
;
326 GtkTreePath
*path
= NULL
;
330 gtk_tree_view_get_cursor(view
, &path
, NULL
);
335 primary
= gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE
));
337 model
= gtk_tree_view_get_model(GTK_TREE_VIEW(view
));
339 gtk_tree_model_get_iter(model
, &iter
, path
);
340 gtk_tree_path_free(path
);
342 gtk_tree_model_get(model
, &iter
, 1, &text
, -1);
344 gtk_clipboard_set_text(primary
, text
, -1);
349 static gchar
*add_row(GtkListStore
*store
, const gchar
*label
,
357 if (!g_utf8_validate(data
, -1, NULL
))
360 gtk_list_store_append(store
, &iter
);
361 gtk_list_store_set(store
, &iter
, 0, label
, 1, u8
? u8
: data
, -1);
365 tpath
=gtk_tree_model_get_path(GTK_TREE_MODEL(store
), &iter
);
368 last
=gtk_tree_path_to_string(tpath
);
369 gtk_tree_path_free(tpath
);
374 static void add_row_and_free(GtkListStore
*store
,
375 const gchar
*label
, gchar
*data
)
377 add_row(store
, label
, data
);
381 /* Create an empty list view, ready to place some data in */
382 static void make_list(GtkListStore
**list_store
, GtkWidget
**list_view
)
386 GtkCellRenderer
*cell_renderer
;
388 store
= gtk_list_store_new(2, G_TYPE_STRING
, G_TYPE_STRING
);
389 view
= GTK_TREE_VIEW(
390 gtk_tree_view_new_with_model(GTK_TREE_MODEL(store
)));
391 g_object_unref(G_OBJECT(store
));
392 gtk_tree_view_set_headers_visible(view
, FALSE
);
394 cell_renderer
= gtk_cell_renderer_text_new();
395 g_object_set(G_OBJECT(cell_renderer
), "xalign", 1.0, NULL
);
396 gtk_tree_view_insert_column_with_attributes(view
,
397 0, NULL
, cell_renderer
, "text", 0, NULL
);
399 cell_renderer
= gtk_cell_renderer_text_new();
400 gtk_tree_view_insert_column_with_attributes(view
,
401 1, NULL
, cell_renderer
, "text", 1, NULL
);
403 g_signal_connect(view
, "cursor_changed",
404 G_CALLBACK(set_selection
), NULL
);
407 *list_view
= (GtkWidget
*) view
;
410 static void set_cell(GtkListStore
*store
, const gchar
*path
,
415 gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store
),
417 gtk_list_store_set(store
, &iter
, 1, ctext
, -1);
420 static void insert_size(DU
*du
, const char *line
)
425 #ifdef LARGE_FILE_SUPPORT
426 size
=strtoll(line
, NULL
, 10);
428 size
=strtol(line
, NULL
, 10);
430 size
<<=10; /* Because du reports in K */
431 cell
=(size
>= PRETTY_SIZE_LIMIT
)?
432 g_strdup_printf("%s (%" SIZE_FMT
" %s)",
435 : g_strdup(format_size(size
));
437 set_cell(du
->store
, du
->path
, cell
);
442 static gboolean
read_du_output(GIOChannel
*source
, GIOCondition cond
,
449 line
=g_string_new("");
450 stat
=g_io_channel_read_line_string(source
, line
, NULL
, &err
);
452 case G_IO_STATUS_NORMAL
:
453 insert_size(du
, line
->str
);
455 case G_IO_STATUS_EOF
:
457 case G_IO_STATUS_AGAIN
:
458 g_string_free(line
, TRUE
);
460 case G_IO_STATUS_ERROR
:
461 set_cell(du
->store
, du
->path
, err
->message
);
464 g_string_free(line
, TRUE
);
469 static void kill_du_output(GtkWidget
*widget
, DU
*du
)
471 g_io_channel_unref(du
->chan
);
476 /* Create the TreeView widget with the file's details */
477 static GtkWidget
*make_details(const guchar
*path
, DirItem
*item
)
483 make_list(&store
, &view
);
485 if (item
->base_type
== TYPE_ERROR
)
487 add_row(store
, _("Error:"), g_strerror(item
->lstat_errno
));
491 tmp
= g_path_get_dirname(path
);
493 if (strcmp(tmp
, tmp2
) != 0)
494 add_row_and_free(store
, _("Real directory:"), tmp2
);
497 add_row_and_free(store
, _("Owner, Group:"),
498 g_strdup_printf("%s, %s",
499 user_name(item
->uid
),
500 group_name(item
->gid
)));
502 if (item
->base_type
!= TYPE_DIRECTORY
) {
503 add_row_and_free(store
, _("Size:"),
504 item
->size
>= PRETTY_SIZE_LIMIT
505 ? g_strdup_printf("%s (%" SIZE_FMT
" %s)",
506 format_size(item
->size
),
507 item
->size
, _("bytes"))
508 : g_strdup(format_size(item
->size
)));
512 gchar
*args
[]={"du", "-sk", "", NULL
};
515 du
->path
=g_strdup(add_row(store
, _("Size:"), _("Scanning")));
517 args
[2]=(gchar
*) path
;
518 if(g_spawn_async_with_pipes(NULL
, args
, NULL
,
523 du
->chan
=g_io_channel_unix_new(out
);
524 du
->watch
=g_io_add_watch(du
->chan
, G_IO_IN
,
525 (GIOFunc
) read_du_output
, du
);
526 g_signal_connect(G_OBJECT(view
), "destroy",
527 G_CALLBACK(kill_du_output
), du
);
529 set_cell(store
, du
->path
, _("Failed to scan"));
535 add_row_and_free(store
, _("Change time:"), pretty_time(&item
->ctime
));
537 add_row_and_free(store
, _("Modify time:"), pretty_time(&item
->mtime
));
539 add_row_and_free(store
, _("Access time:"), pretty_time(&item
->atime
));
541 add_row(store
, _("Type:"), pretty_type(item
, path
));
544 add_row(store
, "", mime_type_comment(item
->mime_type
));
546 if (item
->base_type
!= TYPE_DIRECTORY
)
547 add_row_and_free(store
, _("Run action:"),
548 describe_current_command(item
->mime_type
));
553 /* Create the TreeView widget with the application's details */
554 static GtkWidget
*make_about(const guchar
*path
, XMLwrapper
*ai
)
559 xmlNode
*about
, *about_trans
;
560 GHashTable
*translate
;
562 g_return_val_if_fail(ai
!= NULL
, NULL
);
564 about_trans
= xml_get_section(ai
, NULL
, "About");
566 about
= xmlDocGetRootElement(ai
->doc
)->xmlChildrenNode
;
567 for (; about
; about
= about
->next
)
569 if (about
->type
!= XML_ELEMENT_NODE
)
571 if (about
->ns
== NULL
&& strcmp(about
->name
, "About") == 0)
575 g_return_val_if_fail(about
!= NULL
, NULL
);
577 make_list(&store
, &view
);
579 /* Add each field in about to the list, but overriding each element
580 * with about_trans if a translation is supplied.
582 translate
= g_hash_table_new(g_str_hash
, g_str_equal
);
583 if (about_trans
!= about
)
586 for (p
= about_trans
->xmlChildrenNode
; p
; p
= p
->next
)
588 if (p
->type
!= XML_ELEMENT_NODE
)
590 g_hash_table_insert(translate
, (char *) p
->name
, p
);
593 for (prop
= about
->xmlChildrenNode
; prop
; prop
= prop
->next
)
595 if (prop
->type
== XML_ELEMENT_NODE
)
602 trans
= g_hash_table_lookup(translate
, prop
->name
);
606 tmp
= xmlGetProp(trans
, "label");
607 label
= g_strconcat(tmp
? tmp
608 : (char *) trans
->name
,
611 value
= xmlNodeListGetString(trans
->doc
,
612 trans
->xmlChildrenNode
, 1);
614 value
= xmlNodeListGetString(prop
->doc
,
615 prop
->xmlChildrenNode
, 1);
617 value
= g_strdup("-");
618 add_row_and_free(store
, label
, value
);
623 g_hash_table_destroy(translate
);
628 static GtkWidget
*make_file_says(const guchar
*path
)
630 GtkWidget
*w_file_label
;
631 GtkLabel
*l_file_label
;
633 char *argv
[] = {"file", "-b", NULL
, NULL
};
634 FileStatus
*fs
= NULL
;
637 w_file_label
= gtk_label_new(_("<nothing yet>"));
638 l_file_label
= GTK_LABEL(w_file_label
);
639 gtk_label_set_line_wrap(l_file_label
, TRUE
);
643 tmp
= g_strdup_printf("pipe(): %s", g_strerror(errno
));
644 gtk_label_set_text(l_file_label
, tmp
);
652 tmp
= g_strdup_printf("pipe(): %s", g_strerror(errno
));
653 gtk_label_set_text(l_file_label
, tmp
);
659 /* We are the child */
661 dup2(file_data
[1], STDOUT_FILENO
);
662 dup2(file_data
[1], STDERR_FILENO
);
664 argv
[2] = (char *) path
;
666 argv
[1] = (char *) g_basename(path
);
667 chdir(g_path_get_dirname(path
));
669 if (execvp(argv
[0], argv
))
670 fprintf(stderr
, "execvp() error: %s\n",
674 /* We are the parent */
676 fs
= g_new(FileStatus
, 1);
677 fs
->label
= l_file_label
;
678 fs
->fd
= file_data
[0];
679 fs
->text
= g_strdup("");
680 fs
->input
= gtk_input_add_full(fs
->fd
, GDK_INPUT_READ
,
681 (GdkInputFunction
) add_file_output
,
683 g_signal_connect(w_file_label
, "destroy",
684 G_CALLBACK(file_info_destroyed
), fs
);
691 /* Got some data from file(1) - stick it in the window. */
692 static void add_file_output(FileStatus
*fs
,
693 gint source
, GdkInputCondition condition
)
699 got
= read(source
, buffer
, sizeof(buffer
) - 1);
703 gtk_input_remove(fs
->input
);
707 delayed_error(_("file(1) says... %s"),
713 str
= g_strconcat(fs
->text
, buffer
, NULL
);
717 str
= to_utf8(fs
->text
);
719 gtk_label_set_text(fs
->label
, str
);
723 static void file_info_destroyed(GtkWidget
*widget
, FileStatus
*fs
)
727 gtk_input_remove(fs
->input
);
735 static void permissions_destroyed(GtkWidget
*widget
, Permissions
*perm
)
743 static void permissions_apply(GtkWidget
*widget
, Permissions
*perm
)
751 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm
->bits
[i
])))
754 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm
->bits
[9])))
756 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm
->bits
[10])))
758 if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(perm
->bits
[11])))
761 if(chmod(perm
->path
, nmode
)) {
762 report_error(_("Could not change permissions: %s"),
767 static GtkWidget
*make_permissions(const gchar
*path
, DirItem
*item
)
771 GtkWidget
*tick
, *label
;
774 perm
=g_new(Permissions
, 1);
776 perm
->path
=g_strdup(path
);
777 perm
->item
=diritem_new(path
);
779 table
=gtk_table_new(4, 5, TRUE
);
781 label
=gtk_label_new(_("Owner"));
782 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 0, 1, 1, 2);
783 label
=gtk_label_new(_("Group"));
784 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 0, 1, 2, 3);
785 label
=gtk_label_new(_("World"));
786 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 0, 1, 3, 4);
788 label
=gtk_label_new(_("Read"));
789 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 1, 2, 0, 1);
790 label
=gtk_label_new(_("Write"));
791 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 2, 3, 0, 1);
792 label
=gtk_label_new(_("Exec"));
793 gtk_table_attach_defaults(GTK_TABLE(table
), label
, 3, 4, 0, 1);
798 perm
->bits
[i
]=tick
=gtk_check_button_new();
799 gtk_table_attach_defaults(GTK_TABLE(table
), tick
,
801 if(item
->mode
& (1<<i
))
802 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick
),
804 g_signal_connect(tick
, "toggled",
805 G_CALLBACK(permissions_apply
), perm
);
808 tick
=gtk_check_button_new_with_label(_("SUID"));
809 gtk_table_attach_defaults(GTK_TABLE(table
), tick
, 4, 5, 1, 2);
810 if(item
->mode
& S_ISUID
)
811 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick
), TRUE
);
812 g_signal_connect(tick
, "toggled",
813 G_CALLBACK(permissions_apply
), perm
);
816 tick
=gtk_check_button_new_with_label(_("SGID"));
817 gtk_table_attach_defaults(GTK_TABLE(table
), tick
, 4, 5, 2, 3);
818 if(item
->mode
& S_ISGID
)
819 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick
), TRUE
);
820 g_signal_connect(tick
, "toggled",
821 G_CALLBACK(permissions_apply
), perm
);
824 tick
=gtk_check_button_new_with_label(_("Sticky"));
825 gtk_table_attach_defaults(GTK_TABLE(table
), tick
, 4, 5, 3, 4);
826 if(item
->mode
& S_ISVTX
)
827 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(tick
), TRUE
);
828 g_signal_connect(tick
, "toggled",
829 G_CALLBACK(permissions_apply
), perm
);
832 g_signal_connect(table
, "destroy",
833 G_CALLBACK(permissions_destroyed
), perm
);
835 gtk_widget_show_all(table
);
839 /* Don't g_free() the result */
840 static const gchar
*pretty_type(DirItem
*file
, const guchar
*path
)
842 static gchar
*text
= NULL
;
846 if (file
->flags
& ITEM_FLAG_SYMLINK
)
850 target
= readlink_dup(path
);
853 ensure_utf8(&target
);
855 text
= g_strdup_printf(_("Symbolic link to %s"),
861 return _("Symbolic link");
864 if (file
->flags
& ITEM_FLAG_APPDIR
)
865 return _("ROX application");
867 if (file
->flags
& ITEM_FLAG_MOUNT_POINT
)
870 const gchar
*mounted
;
872 mounted
= mount_is_mounted(path
, NULL
, NULL
)
873 ? _("mounted") : _("unmounted");
875 mp
= g_hash_table_lookup(fstab_mounts
, path
);
877 text
= g_strdup_printf(_("Mount point for %s (%s)"),
880 text
= g_strdup_printf(_("Mount point (%s)"), mounted
);
886 text
= g_strconcat(file
->mime_type
->media_type
, "/",
887 file
->mime_type
->subtype
, NULL
);