r1996: Cope slightly better with invalid filenames in various places (reported by
[rox-filer.git] / ROX-Filer / src / infobox.c
blobe7547348be4edd5e88aeb58bb4258689c4e6056b
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, 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 <libxml/parser.h>
32 #include <gtk/gtk.h>
34 #include "global.h"
36 #include "support.h"
37 #include "main.h"
38 #include "gui_support.h"
39 #include "diritem.h"
40 #include "type.h"
41 #include "infobox.h"
42 #include "appinfo.h"
43 #include "dnd.h" /* For xa_string */
44 #include "run.h" /* For show_item_help() */
45 #include "xml.h"
46 #include "mount.h"
48 typedef struct _FileStatus FileStatus;
50 /* This is for the 'file(1) says...' thing */
51 struct _FileStatus
53 int fd; /* FD to read from, -1 if closed */
54 int input; /* Input watcher tag if fd valid */
55 GtkLabel *label; /* Widget to output to */
56 gchar *text; /* String so far */
59 /* Static prototypes */
60 static void refresh_info(GObject *window);
61 static GtkWidget *make_vbox(guchar *path);
62 static GtkWidget *make_details(guchar *path, DirItem *item);
63 static GtkWidget *make_about(guchar *path, xmlNode *about);
64 static GtkWidget *make_file_says(guchar *path);
65 static void add_file_output(FileStatus *fs,
66 gint source, GdkInputCondition condition);
67 static guchar *pretty_type(DirItem *file, guchar *path);
68 static void got_response(GObject *window, gint response, gpointer data);
69 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs);
71 /****************************************************************
72 * EXTERNAL INTERFACE *
73 ****************************************************************/
75 /* Open each item in a new infobox. Confirms if there are a large
76 * number of items to show.
78 void infobox_show_list(GList *paths)
80 int n;
82 n = g_list_length(paths);
84 if (n >= 10)
86 gchar *message;
87 int button;
89 message = g_strdup_printf(
90 _("Are you sure you want to open %d windows?"), n);
91 button = get_choice(_("File Information"),
92 message, 2, _("Cancel"), _("Show Info"));
93 g_free(message);
94 if (button != 1)
95 return;
98 g_list_foreach(paths, (GFunc) infobox_new, NULL);
101 /* Create and display a new info box showing details about this item */
102 void infobox_new(const gchar *pathname)
104 GtkWidget *window, *details;
105 gchar *path;
107 g_return_if_fail(pathname != NULL);
109 path = g_strdup(pathname); /* Gets attached to window & freed later */
111 window = gtk_dialog_new_with_buttons(
112 g_utf8_validate(path, -1, NULL) ? path
113 : _("(bad utf-8)"),
114 NULL, GTK_DIALOG_NO_SEPARATOR,
115 GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
116 GTK_STOCK_REFRESH, GTK_RESPONSE_APPLY,
117 NULL);
119 gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_MOUSE);
121 details = make_vbox(path);
122 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(window)->vbox),
123 details, TRUE, TRUE, 0);
125 g_object_set_data(G_OBJECT(window), "details", details);
126 g_object_set_data_full(G_OBJECT(window), "path", path, g_free);
128 g_signal_connect(window, "response", G_CALLBACK(got_response), NULL);
130 number_of_windows++;
131 gtk_widget_show_all(window);
134 /****************************************************************
135 * INTERNAL FUNCTIONS *
136 ****************************************************************/
138 static void show_help_clicked(const gchar *path)
140 DirItem *item;
142 item = diritem_new("");
143 diritem_restat(path, item, NULL);
144 show_item_help(path, item);
145 diritem_free(item);
148 static void got_response(GObject *window, gint response, gpointer data)
150 if (response == GTK_RESPONSE_APPLY)
151 refresh_info(window);
152 else
154 gtk_widget_destroy(GTK_WIDGET(window));
155 one_less_window();
159 static void refresh_info(GObject *window)
161 GtkWidget *details, *vbox;
162 guchar *path;
164 path = g_object_get_data(window, "path");
165 details = g_object_get_data(window, "details");
166 g_return_if_fail(details != NULL);
167 g_return_if_fail(path != NULL);
169 vbox = details->parent;
170 gtk_widget_destroy(details);
172 details = make_vbox(path);
173 g_object_set_data(window, "details", details);
174 gtk_box_pack_start(GTK_BOX(vbox), details, TRUE, TRUE, 0);
175 gtk_widget_show_all(details);
178 /* Create the VBox widget that contains the details.
179 * Note that 'path' must not be freed until the vbox is destroyed.
181 static GtkWidget *make_vbox(guchar *path)
183 DirItem *item;
184 GtkWidget *vbox, *list, *frame;
185 XMLwrapper *ai;
186 xmlNode *about = NULL;
187 gchar *help_dir;
189 g_return_val_if_fail(path[0] == '/', NULL);
191 item = diritem_new(g_basename(path));
192 diritem_restat(path, item, NULL);
194 vbox = gtk_vbox_new(FALSE, 4);
196 ai = appinfo_get(path, item);
197 if (ai)
198 about = xml_get_section(ai, NULL, "About");
200 frame = gtk_frame_new(NULL);
201 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
202 list = make_details(path, item);
203 gtk_container_add(GTK_CONTAINER(frame), list);
204 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
206 help_dir = g_strconcat(path, "/Help", NULL);
207 if (access(help_dir, F_OK) == 0)
209 GtkWidget *button, *align;
211 align = gtk_alignment_new(0.5, 0.5, 0, 0);
213 button = button_new_mixed(GTK_STOCK_JUMP_TO,
214 _("Show _Help Files"));
215 gtk_box_pack_start(GTK_BOX(vbox), align, FALSE, TRUE, 0);
216 gtk_container_add(GTK_CONTAINER(align), button);
217 g_signal_connect_swapped(button, "clicked",
218 G_CALLBACK(show_help_clicked),
219 path);
221 g_free(help_dir);
223 if (about)
225 frame = gtk_frame_new(NULL);
226 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
227 list = make_about(path, about);
228 gtk_container_add(GTK_CONTAINER(frame), list);
229 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
231 else
232 gtk_box_pack_start(GTK_BOX(vbox), make_file_says(path), TRUE,
233 TRUE, 0);
235 if (ai)
236 g_object_unref(ai);
238 diritem_free(item);
240 return vbox;
243 /* The selection has changed - grab or release the primary selection */
244 static void set_selection(GtkTreeView *view, gpointer data)
246 static GtkClipboard *primary = NULL;
247 GtkTreeModel *model;
248 GtkTreePath *path = NULL;
249 GtkTreeIter iter;
250 gchar *text;
252 gtk_tree_view_get_cursor(view, &path, NULL);
253 if (!path)
254 return;
256 if (!primary)
257 primary = gtk_clipboard_get(gdk_atom_intern("PRIMARY", FALSE));
259 model = gtk_tree_view_get_model(GTK_TREE_VIEW(view));
261 gtk_tree_model_get_iter(model, &iter, path);
262 gtk_tree_path_free(path);
264 gtk_tree_model_get(model, &iter, 1, &text, -1);
266 gtk_clipboard_set_text(primary, text, -1);
268 g_free(text);
271 static void add_row(GtkListStore *store, const gchar *label, const gchar *data)
273 GtkTreeIter iter;
275 gtk_list_store_append(store, &iter);
276 gtk_list_store_set(store, &iter, 0, label, 1, data, -1);
279 /* Create an empty list view, ready to place some data in */
280 static void make_list(GtkListStore **list_store, GtkWidget **list_view)
282 GtkListStore *store;
283 GtkWidget *view;
284 GtkCellRenderer *cell_renderer;
286 store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
287 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
288 g_object_unref(G_OBJECT(store));
289 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
291 cell_renderer = gtk_cell_renderer_text_new();
292 g_object_set(G_OBJECT(cell_renderer), "xalign", 1.0, NULL);
293 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
294 0, NULL, cell_renderer, "text", 0, NULL);
296 cell_renderer = gtk_cell_renderer_text_new();
297 gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
298 1, NULL, cell_renderer, "text", 1, NULL);
300 g_signal_connect(view, "cursor_changed",
301 G_CALLBACK(set_selection), NULL);
303 *list_store = store;
304 *list_view = view;
307 /* Create the TreeView widget with the file's details */
308 static GtkWidget *make_details(guchar *path, DirItem *item)
310 GtkListStore *store;
311 GtkWidget *view;
312 GString *gstring;
313 struct stat info;
314 gchar *tmp, *tmp2;
316 make_list(&store, &view);
318 if (g_utf8_validate(path, -1, NULL))
319 add_row(store, _("Name:"), item->leafname);
320 else
322 gchar *u8;
323 u8 = to_utf8(path);
324 add_row(store, _("Name:"), u8);
325 g_free(u8);
328 if (lstat(path, &info))
330 add_row(store, _("Error:"), g_strerror(errno));
331 return view;
334 tmp = g_dirname(path);
335 tmp2 = pathdup(tmp);
336 if (strcmp(tmp, tmp2) != 0)
337 add_row(store, _("Real directory:"), tmp2);
338 g_free(tmp);
339 g_free(tmp2);
341 gstring = g_string_new(NULL);
343 g_string_sprintf(gstring, "%s, %s", user_name(info.st_uid),
344 group_name(info.st_gid));
345 add_row(store, _("Owner, Group:"), gstring->str);
347 if (info.st_size >= PRETTY_SIZE_LIMIT)
349 g_string_sprintf(gstring, "%s (%" SIZE_FMT " %s)",
350 format_size(info.st_size),
351 info.st_size, _("bytes"));
353 else
355 g_string_assign(gstring,
356 format_size(info.st_size));
358 add_row(store, _("Size:"), gstring->str);
360 tmp = pretty_time(&info.st_ctime);
361 add_row(store, _("Change time:"), tmp);
362 g_free(tmp);
364 tmp = pretty_time(&info.st_mtime);
365 add_row(store, _("Modify time:"), tmp);
366 g_free(tmp);
368 tmp = pretty_time(&info.st_atime);
369 add_row(store, _("Access time:"), tmp);
370 g_free(tmp);
372 g_string_free(gstring, TRUE);
374 add_row(store, _("Permissions:"), pretty_permissions(info.st_mode));
376 tmp = pretty_type(item, path);
377 add_row(store, _("Type:"), tmp);
378 g_free(tmp);
380 if (item->base_type != TYPE_DIRECTORY)
382 tmp = describe_current_command(item->mime_type);
383 add_row(store, _("Run action:"), tmp);
384 g_free(tmp);
387 return view;
390 /* Create the TreeView widget with the application's details, or
391 * the file(1) summary (for non-apps).
393 static GtkWidget *make_about(guchar *path, xmlNode *about)
395 GtkListStore *store;
396 GtkWidget *view;
397 xmlNode *prop;
398 gchar *tmp;
400 g_return_val_if_fail(about != NULL, NULL);
402 make_list(&store, &view);
404 for (prop = about->xmlChildrenNode; prop; prop = prop->next)
406 if (prop->type == XML_ELEMENT_NODE)
408 char *l;
410 l = g_strconcat((char *) prop->name, ":", NULL);
411 tmp = xmlNodeListGetString(prop->doc,
412 prop->xmlChildrenNode, 1);
413 if (!tmp)
414 tmp = g_strdup("-");
415 add_row(store, l, tmp);
416 g_free(l);
417 g_free(tmp);
421 return view;
424 static GtkWidget *make_file_says(guchar *path)
426 GtkWidget *frame;
427 GtkWidget *file_label;
428 int file_data[2];
429 char *argv[] = {"file", "-b", NULL, NULL};
430 FileStatus *fs = NULL;
431 guchar *tmp;
433 frame = gtk_frame_new(_("file(1) says..."));
434 file_label = gtk_label_new(_("<nothing yet>"));
435 gtk_misc_set_padding(GTK_MISC(file_label), 4, 4);
436 gtk_label_set_line_wrap(GTK_LABEL(file_label), TRUE);
437 gtk_container_add(GTK_CONTAINER(frame), file_label);
439 if (pipe(file_data))
441 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
442 gtk_label_set_text(GTK_LABEL(file_label), tmp);
443 g_free(tmp);
444 return frame;
447 switch (fork())
449 case -1:
450 tmp = g_strdup_printf("pipe(): %s", g_strerror(errno));
451 gtk_label_set_text(GTK_LABEL(file_label), tmp);
452 g_free(tmp);
453 close(file_data[0]);
454 close(file_data[1]);
455 break;
456 case 0:
457 /* We are the child */
458 close(file_data[0]);
459 dup2(file_data[1], STDOUT_FILENO);
460 dup2(file_data[1], STDERR_FILENO);
461 #ifdef FILE_B_FLAG
462 argv[2] = path;
463 #else
464 argv[1] = (char *) g_basename(path);
465 chdir(g_dirname(path));
466 #endif
467 if (execvp(argv[0], argv))
468 fprintf(stderr, "execvp() error: %s\n",
469 g_strerror(errno));
470 _exit(0);
471 default:
472 /* We are the parent */
473 close(file_data[1]);
474 fs = g_new(FileStatus, 1);
475 fs->label = GTK_LABEL(file_label);
476 fs->fd = file_data[0];
477 fs->text = g_strdup("");
478 fs->input = gtk_input_add_full(fs->fd, GDK_INPUT_READ,
479 (GdkInputFunction) add_file_output,
480 NULL, fs, NULL);
481 g_signal_connect(frame, "destroy",
482 G_CALLBACK(file_info_destroyed), fs);
483 break;
486 return frame;
489 /* Got some data from file(1) - stick it in the window. */
490 static void add_file_output(FileStatus *fs,
491 gint source, GdkInputCondition condition)
493 char buffer[20];
494 char *str;
495 int got;
497 got = read(source, buffer, sizeof(buffer) - 1);
498 if (got <= 0)
500 int err = errno;
501 gtk_input_remove(fs->input);
502 close(source);
503 fs->fd = -1;
504 if (got < 0)
505 delayed_error(_("file(1) says... %s"),
506 g_strerror(err));
507 return;
509 buffer[got] = '\0';
511 str = g_strconcat(fs->text, buffer, NULL);
512 g_free(fs->text);
513 fs->text = str;
515 str = g_strdup(fs->text);
516 g_strstrip(str);
517 gtk_label_set_text(fs->label, str);
518 g_free(str);
521 static void file_info_destroyed(GtkWidget *widget, FileStatus *fs)
523 if (fs->fd != -1)
525 gtk_input_remove(fs->input);
526 close(fs->fd);
529 g_free(fs->text);
530 g_free(fs);
533 /* g_free() the result */
534 static guchar *pretty_type(DirItem *file, guchar *path)
536 if (file->flags & ITEM_FLAG_SYMLINK)
538 char *target;
540 target = readlink_dup(path);
541 if (target)
543 char *retval;
545 retval = g_strdup_printf(_("Symbolic link to %s"),
546 target);
547 g_free(target);
548 return retval;
551 return g_strdup(_("Symbolic link"));
554 if (file->flags & ITEM_FLAG_APPDIR)
555 return g_strdup(_("ROX application"));
557 if (file->flags & ITEM_FLAG_MOUNT_POINT)
559 MountPoint *mp;
560 const char *mounted;
562 mounted = mount_is_mounted(path, NULL, NULL)
563 ? _("mounted") : _("unmounted");
565 mp = g_hash_table_lookup(fstab_mounts, path);
566 if (mp)
567 return g_strdup_printf(_("Mount point for %s (%s)"),
568 mp->name, mounted);
569 return g_strdup_printf(_("Mount point (%s)"), mounted);
572 if (file->mime_type)
573 return g_strconcat(file->mime_type->media_type, "/",
574 file->mime_type->subtype, NULL);
576 return g_strdup("-");