r3247: Experimental extended attributes support for setting MIME types
[rox-filer.git] / ROX-Filer / src / type.c
blobba6136d89accca298c736ae2cfe8ff56d969107e
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 /* type.c - code for dealing with filetypes */
24 #include "config.h"
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <errno.h>
29 #include <ctype.h>
30 #include <time.h>
31 #include <sys/param.h>
32 #include <fnmatch.h>
33 #include <sys/types.h>
34 #include <fcntl.h>
36 #ifdef WITH_GNOMEVFS
37 # include <libgnomevfs/gnome-vfs.h>
38 # include <libgnomevfs/gnome-vfs-mime.h>
39 # include <libgnomevfs/gnome-vfs-mime-handlers.h>
40 # include <libgnomevfs/gnome-vfs-application-registry.h>
41 #endif
43 #include "global.h"
45 #include "string.h"
46 #include "fscache.h"
47 #include "main.h"
48 #include "pixmaps.h"
49 #include "run.h"
50 #include "gui_support.h"
51 #include "choices.h"
52 #include "type.h"
53 #include "support.h"
54 #include "diritem.h"
55 #include "dnd.h"
56 #include "options.h"
57 #include "filer.h"
58 #include "action.h" /* (for action_chmod) */
59 #include "xml.h"
60 #include "dropbox.h"
61 #include "gtkicontheme.h"
63 #define TYPE_NS "http://www.freedesktop.org/standards/shared-mime-info"
64 enum {SET_MEDIA, SET_TYPE};
66 /* Colours for file types (same order as base types) */
67 static gchar *opt_type_colours[][2] = {
68 {"display_err_colour", "#ff0000"},
69 {"display_unkn_colour", "#000000"},
70 {"display_dir_colour", "#000080"},
71 {"display_pipe_colour", "#444444"},
72 {"display_sock_colour", "#ff00ff"},
73 {"display_file_colour", "#000000"},
74 {"display_cdev_colour", "#000000"},
75 {"display_bdev_colour", "#000000"},
76 {"display_door_colour", "#ff00ff"},
77 {"display_exec_colour", "#006000"},
78 {"display_adir_colour", "#006000"}
80 #define NUM_TYPE_COLOURS\
81 (sizeof(opt_type_colours) / sizeof(opt_type_colours[0]))
83 /* Parsed colours for file types */
84 static Option o_type_colours[NUM_TYPE_COLOURS];
85 static GdkColor type_colours[NUM_TYPE_COLOURS];
87 /* Static prototypes */
88 static void load_mime_types(void);
89 static void alloc_type_colours(void);
90 static void options_changed(void);
91 static char *get_action_save_path(GtkWidget *dialog);
92 static MIME_type *get_mime_type(const gchar *type_name, gboolean can_create);
93 static gboolean remove_handler_with_confirm(const guchar *path);
94 static void set_icon_theme(void);
95 static GList *build_icon_theme(Option *option, xmlNode *node, guchar *label);
97 /* When working out the type for a file, this hash table is checked
98 * first...
100 static GHashTable *literal_hash = NULL; /* name -> MIME-type */
102 /* Maps extensions to MIME_types (eg 'png'-> MIME_type *).
103 * Extensions may contain dots; 'tar.gz' matches '*.tar.gz', etc.
104 * The hash table is consulted from each dot in the string in turn
105 * (First .ps.gz, then try .gz)
107 static GHashTable *extension_hash = NULL;
109 /* The first pattern in the list which matches is used */
110 typedef struct pattern {
111 gint len; /* Used for sorting */
112 gchar *glob;
113 MIME_type *type;
114 } Pattern;
115 static GPtrArray *glob_patterns = NULL; /* [Pattern] */
117 /* Hash of all allocated MIME types, indexed by "media/subtype".
118 * MIME_type structs are never freed; this table prevents memory leaks
119 * when rereading the config files.
121 static GHashTable *type_hash = NULL;
123 /* Most things on Unix are text files, so this is the default type */
124 MIME_type *text_plain;
125 MIME_type *inode_directory;
126 MIME_type *inode_mountpoint;
127 MIME_type *inode_pipe;
128 MIME_type *inode_socket;
129 MIME_type *inode_block_dev;
130 MIME_type *inode_char_dev;
131 MIME_type *application_executable;
132 MIME_type *inode_unknown;
133 MIME_type *inode_door;
135 static Option o_display_colour_types;
136 static Option o_icon_theme;
138 static GtkIconTheme *icon_theme = NULL;
140 void type_init(void)
142 int i;
144 icon_theme = rox_icon_theme_new();
146 extension_hash = g_hash_table_new(g_str_hash, g_str_equal);
147 type_hash = g_hash_table_new(g_str_hash, g_str_equal);
149 text_plain = get_mime_type("text/plain", TRUE);
150 inode_directory = get_mime_type("inode/directory", TRUE);
151 inode_mountpoint = get_mime_type("inode/mount-point", TRUE);
152 inode_pipe = get_mime_type("inode/fifo", TRUE);
153 inode_socket = get_mime_type("inode/socket", TRUE);
154 inode_block_dev = get_mime_type("inode/blockdevice", TRUE);
155 inode_char_dev = get_mime_type("inode/chardevice", TRUE);
156 application_executable = get_mime_type("application/x-executable", TRUE);
157 inode_unknown = get_mime_type("inode/unknown", TRUE);
158 inode_door = get_mime_type("inode/door", TRUE);
160 load_mime_types();
162 option_add_string(&o_icon_theme, "icon_theme", "ROX");
163 option_add_int(&o_display_colour_types, "display_colour_types", TRUE);
164 option_register_widget("icon-theme-chooser", build_icon_theme);
166 for (i = 0; i < NUM_TYPE_COLOURS; i++)
167 option_add_string(&o_type_colours[i],
168 opt_type_colours[i][0],
169 opt_type_colours[i][1]);
170 alloc_type_colours();
172 set_icon_theme();
174 option_add_notify(options_changed);
177 /* Read-load all the glob patterns.
178 * Note: calls filer_update_all.
180 void reread_mime_files(void)
182 rox_icon_theme_rescan_if_needed(icon_theme);
183 load_mime_types();
186 /* Returns the MIME_type structure for the given type name. It is looked
187 * up in type_hash and returned if found. If not found (and can_create is
188 * TRUE) then a new MIME_type is made, added to type_hash and returned.
189 * NULL is returned if type_name is not in type_hash and can_create is
190 * FALSE, or if type_name does not contain a '/' character.
192 static MIME_type *get_mime_type(const gchar *type_name, gboolean can_create)
194 MIME_type *mtype;
195 gchar *slash;
197 mtype = g_hash_table_lookup(type_hash, type_name);
198 if (mtype || !can_create)
199 return mtype;
201 slash = strchr(type_name, '/');
202 g_return_val_if_fail(slash != NULL, NULL); /* XXX: Report nicely */
204 mtype = g_new(MIME_type, 1);
205 mtype->media_type = g_strndup(type_name, slash - type_name);
206 mtype->subtype = g_strdup(slash + 1);
207 mtype->image = NULL;
208 mtype->comment = NULL;
210 g_hash_table_insert(type_hash, g_strdup(type_name), mtype);
212 return mtype;
215 const char *basetype_name(DirItem *item)
217 if (item->flags & ITEM_FLAG_SYMLINK)
218 return _("Sym link");
219 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
220 return _("Mount point");
221 else if (item->flags & ITEM_FLAG_APPDIR)
222 return _("App dir");
224 switch (item->base_type)
226 case TYPE_FILE:
227 return _("File");
228 case TYPE_DIRECTORY:
229 return _("Dir");
230 case TYPE_CHAR_DEVICE:
231 return _("Char dev");
232 case TYPE_BLOCK_DEVICE:
233 return _("Block dev");
234 case TYPE_PIPE:
235 return _("Pipe");
236 case TYPE_SOCKET:
237 return _("Socket");
238 case TYPE_DOOR:
239 return _("Door");
242 return _("Unknown");
245 static void append_names(gpointer key, gpointer value, gpointer udata)
247 GList **list=(GList **) udata;
249 *list=g_list_prepend(*list, key);
252 /* Return list of all mime type names */
253 GList *mime_type_name_list(void)
255 GList *list=NULL;
257 g_hash_table_foreach(type_hash, append_names, &list);
258 list=g_list_sort(list, (GCompareFunc) strcmp);
260 return list;
263 /* MIME-type guessing */
265 /* Get the type of this file - stats the file and uses that if
266 * possible. For regular or missing files, uses the pathname.
268 MIME_type *type_get_type(const guchar *path)
270 DirItem *item;
271 MIME_type *type = NULL;
273 item = diritem_new("");
274 diritem_restat(path, item, NULL);
275 if (item->base_type != TYPE_ERROR)
276 type = item->mime_type;
277 diritem_free(item);
279 if (type)
280 return type;
282 type = type_from_path(path);
284 if (!type)
285 return text_plain;
287 return type;
290 /* Returns a pointer to the MIME-type.
291 * NULL if we can't think of anything.
293 MIME_type *type_from_path(const char *path)
295 const char *ext, *dot, *leafname;
296 char *lower;
297 MIME_type *type = NULL;
298 int i;
300 #if 0
301 # ifdef WITH_GNOMEVFS
302 if (o_use_gnomevfs.int_value)
303 return get_mime_type(gnome_vfs_mime_type_from_name(path), TRUE);
304 # endif
305 #endif
307 leafname = g_basename(path);
309 type = g_hash_table_lookup(literal_hash, leafname);
310 if (type)
311 return type;
312 lower = g_utf8_strdown(leafname, -1);
313 type = g_hash_table_lookup(literal_hash, lower);
314 if (type)
315 goto out;
317 ext = leafname;
319 while ((dot = strchr(ext, '.')))
321 ext = dot + 1;
323 type = g_hash_table_lookup(extension_hash, ext);
325 if (type)
326 goto out;
328 type = g_hash_table_lookup(extension_hash,
329 lower + (ext - leafname));
330 if (type)
331 goto out;
334 for (i = 0; i < glob_patterns->len; i++)
336 Pattern *p = glob_patterns->pdata[i];
338 if (fnmatch(p->glob, leafname, 0) == 0 ||
339 fnmatch(p->glob, lower, 0) == 0)
341 type = p->type;
342 goto out;
346 out:
347 g_free(lower);
349 return type;
352 /* Returns the file/dir in Choices for handling this type.
353 * NULL if there isn't one. g_free() the result.
355 static char *handler_for(MIME_type *type)
357 char *type_name;
358 char *open;
360 type_name = g_strconcat(type->media_type, "_", type->subtype, NULL);
361 open = choices_find_path_load(type_name, "MIME-types");
362 g_free(type_name);
364 if (!open)
365 open = choices_find_path_load(type->media_type, "MIME-types");
367 return open;
370 MIME_type *mime_type_lookup(const char *type)
372 return get_mime_type(type, FALSE);
375 /* Actions for types */
377 gboolean type_open(const char *path, MIME_type *type)
379 gchar *argv[] = {NULL, NULL, NULL};
380 char *open;
381 gboolean retval;
382 struct stat info;
384 argv[1] = (char *) path;
386 open = handler_for(type);
387 if (!open)
388 return FALSE;
390 if (stat(open, &info))
392 report_error("stat(%s): %s", open, g_strerror(errno));
393 g_free(open);
394 return FALSE;
397 if (info.st_mode & S_IWOTH)
399 gchar *choices_dir;
400 GList *paths;
402 report_error(_("Executable '%s' is world-writeable! Refusing "
403 "to run. Please change the permissions now (this "
404 "problem may have been caused by a bug in earlier "
405 "versions of the filer).\n\n"
406 "Having (non-symlink) run actions world-writeable "
407 "means that other people who use your computer can "
408 "replace your run actions with malicious versions.\n\n"
409 "If you trust everyone who could write to these files "
410 "then you needn't worry. Otherwise, you should check, "
411 "or even just delete, all the existing run actions."),
412 open);
413 choices_dir = g_path_get_dirname(open);
414 paths = g_list_append(NULL, choices_dir);
415 action_chmod(paths, TRUE, _("go-w (Fix security problem)"));
416 g_free(choices_dir);
417 g_list_free(paths);
418 g_free(open);
419 return TRUE;
422 if (S_ISDIR(info.st_mode))
423 argv[0] = g_strconcat(open, "/AppRun", NULL);
424 else
425 argv[0] = open;
427 retval = rox_spawn(home_dir, (const gchar **) argv) != 0;
429 if (argv[0] != open)
430 g_free(argv[0]);
432 g_free(open);
434 return retval;
437 /* Return the image for this type, loading it if needed.
438 * Places to check are: (eg type="text_plain", base="text")
439 * 1. <Choices>/MIME-icons/base_subtype
440 * 2. Icon theme 'mime-base:subtype'
441 * 3. Icon theme 'mime-base'
442 * 4. Unknown type icon.
444 * Note: You must g_object_unref() the image afterwards.
446 MaskedPixmap *type_to_icon(MIME_type *type)
448 GdkPixbuf *full;
449 char *type_name, *path;
450 time_t now;
452 if (type == NULL)
454 g_object_ref(im_unknown);
455 return im_unknown;
458 now = time(NULL);
459 /* Already got an image? */
460 if (type->image)
462 /* Yes - don't recheck too often */
463 if (abs(now - type->image_time) < 2)
465 g_object_ref(type->image);
466 return type->image;
468 g_object_unref(type->image);
469 type->image = NULL;
472 type_name = g_strconcat(type->media_type, "_", type->subtype,
473 ".png", NULL);
474 path = choices_find_path_load(type_name, "MIME-icons");
475 g_free(type_name);
476 if (path)
478 type->image = g_fscache_lookup(pixmap_cache, path);
479 g_free(path);
482 if (type->image)
483 goto out;
485 type_name = g_strconcat("mime-", type->media_type, ":",
486 type->subtype, NULL);
487 full = rox_icon_theme_load_icon(icon_theme, type_name, HUGE_HEIGHT,
488 0, NULL);
489 g_free(type_name);
490 if (!full)
492 /* Ugly hack... try for a GNOME icon */
493 type_name = g_strconcat("gnome-mime-", type->media_type,
494 "-", type->subtype, NULL);
495 full = rox_icon_theme_load_icon(icon_theme,
496 type_name,
497 HUGE_HEIGHT, 0, NULL);
498 g_free(type_name);
500 if (!full)
502 /* Try for a media type */
503 type_name = g_strconcat("mime-", type->media_type, NULL);
504 full = rox_icon_theme_load_icon(icon_theme,
505 type_name,
506 HUGE_HEIGHT, 0, NULL);
507 g_free(type_name);
509 if (full)
511 type->image = masked_pixmap_new(full);
512 g_object_unref(full);
515 out:
516 if (!type->image)
518 /* One ref from the type structure, one returned */
519 type->image = im_unknown;
520 g_object_ref(im_unknown);
523 type->image_time = now;
525 g_object_ref(type->image);
526 return type->image;
529 GdkAtom type_to_atom(MIME_type *type)
531 char *str;
532 GdkAtom retval;
534 g_return_val_if_fail(type != NULL, GDK_NONE);
536 str = g_strconcat(type->media_type, "/", type->subtype, NULL);
537 retval = gdk_atom_intern(str, FALSE);
538 g_free(str);
540 return retval;
543 static void show_shell_help(gpointer data)
545 info_message(_("Enter a shell command which will load \"$@\" into "
546 "a suitable program. Eg:\n\n"
547 "gimp \"$@\""));
550 /* Called if the user clicks on the OK button. Returns FALSE if an error
551 * was displayed instead of performing the action.
553 static gboolean set_shell_action(GtkWidget *dialog)
555 GtkEntry *entry;
556 const guchar *command;
557 gchar *tmp, *path;
558 int error = 0, len;
559 int fd;
561 entry = g_object_get_data(G_OBJECT(dialog), "shell_command");
563 g_return_val_if_fail(entry != NULL, FALSE);
565 command = gtk_entry_get_text(entry);
567 if (!strchr(command, '$'))
569 show_shell_help(NULL);
570 return FALSE;
573 path = get_action_save_path(dialog);
574 if (!path)
575 return FALSE;
577 tmp = g_strdup_printf("#! /bin/sh\nexec %s\n", command);
578 len = strlen(tmp);
580 fd = open(path, O_CREAT | O_WRONLY, 0755);
581 if (fd == -1)
582 error = errno;
583 else
585 FILE *file;
587 file = fdopen(fd, "w");
588 if (file)
590 if (fwrite(tmp, 1, len, file) < len)
591 error = errno;
592 if (fclose(file) && error == 0)
593 error = errno;
595 else
596 error = errno;
599 if (error)
600 report_error(g_strerror(error));
602 g_free(tmp);
603 g_free(path);
605 gtk_widget_destroy(dialog);
607 return TRUE;
610 static void set_action_response(GtkWidget *dialog, gint response, gpointer data)
612 if (response == GTK_RESPONSE_OK)
613 if (!set_shell_action(dialog))
614 return;
615 gtk_widget_destroy(dialog);
618 /* Return the path of the file in choices that handles this type and
619 * radio setting.
620 * NULL if nothing is defined for it.
622 static guchar *handler_for_radios(GObject *dialog)
624 Radios *radios;
625 MIME_type *type;
627 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
628 type = g_object_get_data(G_OBJECT(dialog), "mime_type");
630 g_return_val_if_fail(radios != NULL, NULL);
631 g_return_val_if_fail(type != NULL, NULL);
633 switch (radios_get_value(radios))
635 case SET_MEDIA:
636 return choices_find_path_load(type->media_type,
637 "MIME-types");
638 case SET_TYPE:
640 gchar *tmp, *handler;
641 tmp = g_strconcat(type->media_type, "_",
642 type->subtype, NULL);
643 handler = choices_find_path_load(tmp, "MIME-types");
644 g_free(tmp);
645 return handler;
647 default:
648 g_warning("Bad type");
649 return NULL;
653 static void run_action_update(gpointer data)
655 guchar *handler;
656 DropBox *drop_box;
657 GObject *dialog = G_OBJECT(data);
659 drop_box = g_object_get_data(dialog, "rox-dropbox");
661 g_return_if_fail(drop_box != NULL);
663 handler = handler_for_radios(dialog);
665 if (handler)
667 char *old = handler;
669 handler = readlink_dup(old);
670 if (handler)
671 g_free(old);
672 else
673 handler = old;
676 drop_box_set_path(DROP_BOX(drop_box), handler);
677 g_free(handler);
680 static void clear_run_action(GtkWidget *drop_box, GtkWidget *dialog)
682 guchar *handler;
684 handler = handler_for_radios(G_OBJECT(dialog));
686 if (handler)
687 remove_handler_with_confirm(handler);
689 run_action_update(dialog);
692 /* Called when a URI list is dropped onto the box in the Set Run Action
693 * dialog. Make sure it's an application, and make that the default
694 * handler.
696 static void drag_app_dropped(GtkWidget *drop_box,
697 const guchar *app,
698 GtkWidget *dialog)
700 DirItem *item;
702 item = diritem_new("");
703 diritem_restat(app, item, NULL);
704 if (item->flags & (ITEM_FLAG_APPDIR | ITEM_FLAG_EXEC_FILE))
706 guchar *path;
708 path = get_action_save_path(dialog);
710 if (path)
712 if (symlink(app, path))
713 delayed_error("symlink: %s",
714 g_strerror(errno));
715 else
716 destroy_on_idle(dialog);
718 g_free(path);
721 else
722 delayed_error(
723 _("This is not a program! Give me an application "
724 "instead!"));
726 diritem_free(item);
729 /* Find the current command which is used to run files of this type.
730 * Returns NULL on failure. g_free() the result.
732 static guchar *get_current_command(MIME_type *type)
734 struct stat info;
735 char *handler, *nl, *data = NULL;
736 long len;
737 guchar *command = NULL;
739 handler = handler_for(type);
741 if (!handler)
742 return NULL; /* No current handler */
744 if (stat(handler, &info))
745 goto out; /* Can't stat */
747 if ((!S_ISREG(info.st_mode)) || info.st_size > 256)
748 goto out; /* Only use small regular files */
750 if (!load_file(handler, &data, &len))
751 goto out; /* Didn't load OK */
753 if (strncmp(data, "#! /bin/sh\nexec ", 16) != 0)
754 goto out; /* Not one of ours */
756 nl = strchr(data + 16, '\n');
757 if (!nl)
758 goto out; /* No newline! */
760 command = g_strndup(data + 16, nl - data - 16);
761 out:
762 g_free(handler);
763 g_free(data);
764 return command;
767 /* Find the current command which is used to run files of this type,
768 * and return a textual description of it.
769 * g_free() the result.
771 gchar *describe_current_command(MIME_type *type)
773 char *handler;
774 char *desc = NULL;
775 struct stat info;
776 char *target;
778 g_return_val_if_fail(type != NULL, NULL);
780 if (type == application_executable)
781 return g_strdup(_("Execute file"));
783 handler = handler_for(type);
785 if (!handler)
786 return g_strdup(_("No run action defined"));
788 target = readlink_dup(handler);
789 if (target)
791 /* Cope with relative paths (shouldn't normally be needed) */
793 if (target[0] == '/')
795 g_free(handler);
796 handler = target;
798 else
800 gchar *dir;
802 dir = g_path_get_dirname(handler);
803 g_free(handler);
804 handler = g_strconcat(dir, "/", target, NULL);
805 g_free(target);
806 g_free(dir);
810 if (mc_stat(handler, &info) !=0 )
812 desc = g_strdup_printf(_("Error in handler %s: %s"), handler,
813 g_strerror(errno));
814 goto out;
817 if (S_ISDIR(info.st_mode))
819 const guchar *tmp;
820 uid_t dir_uid = info.st_uid;
822 tmp = make_path(handler, "AppRun");
824 if (mc_lstat(tmp, &info) != 0 || info.st_uid != dir_uid
825 || !(info.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
826 desc = g_strdup_printf(
827 _("Invalid application %s (bad AppRun)"),
828 handler);
829 /* Else, just report handler... */
831 goto out;
834 /* It's not an application directory, and it's not a symlink... */
836 if (access(handler, X_OK) != 0)
838 desc = g_strdup_printf(_("Non-executable %s"), handler);
839 goto out;
842 desc = get_current_command(type);
843 out:
844 if (!desc)
845 desc = handler;
846 else
847 g_free(handler);
849 return desc;
852 /* Display a dialog box allowing the user to set the default run action
853 * for this type.
855 void type_set_handler_dialog(MIME_type *type)
857 guchar *tmp;
858 GtkDialog *dialog;
859 GtkWidget *frame, *entry, *label;
860 GtkWidget *hbox;
861 Radios *radios;
863 g_return_if_fail(type != NULL);
865 dialog = GTK_DIALOG(gtk_dialog_new());
866 gtk_dialog_set_has_separator(dialog, FALSE);
867 gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
869 g_object_set_data(G_OBJECT(dialog), "mime_type", type);
871 gtk_window_set_title(GTK_WINDOW(dialog), _("Set run action"));
873 radios = radios_new(run_action_update, dialog);
874 g_object_set_data(G_OBJECT(dialog), "rox-radios", radios);
876 radios_add(radios,
877 _("If a handler for the specific type isn't set up, "
878 "use this as the default."), SET_MEDIA,
879 _("Set default for all `%s/<anything>'"),
880 type->media_type);
882 radios_add(radios,
883 _("Use this application for all files with this MIME "
884 "type."), SET_TYPE,
885 _("Only for the type `%s' (%s/%s)"),
886 mime_type_comment(type),
887 type->media_type, type->subtype);
889 radios_set_value(radios, SET_TYPE);
891 frame = drop_box_new(_("Drop a suitable application here"));
893 g_object_set_data(G_OBJECT(dialog), "rox-dropbox", frame);
895 radios_pack(radios, GTK_BOX(dialog->vbox));
896 gtk_box_pack_start(GTK_BOX(dialog->vbox), frame, TRUE, TRUE, 0);
898 g_signal_connect(frame, "path_dropped",
899 G_CALLBACK(drag_app_dropped), dialog);
900 g_signal_connect(frame, "clear",
901 G_CALLBACK(clear_run_action), dialog);
903 hbox = gtk_hbox_new(FALSE, 4);
904 gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, FALSE, TRUE, 4);
905 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
906 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("OR")),
907 FALSE, TRUE, 0);
908 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
910 hbox = gtk_hbox_new(FALSE, 4);
911 gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, FALSE, TRUE, 0);
913 label = gtk_label_new(_("Enter a shell command:")),
914 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
915 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
917 gtk_box_pack_start(GTK_BOX(hbox),
918 new_help_button(show_shell_help, NULL), FALSE, TRUE, 0);
920 entry = gtk_entry_new();
921 gtk_box_pack_start(GTK_BOX(dialog->vbox), entry, FALSE, TRUE, 0);
922 gtk_widget_grab_focus(entry);
923 g_object_set_data(G_OBJECT(dialog), "shell_command", entry);
924 gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
926 /* If possible, fill in the entry box with the current command */
927 tmp = get_current_command(type);
928 if (tmp)
930 gtk_entry_set_text(GTK_ENTRY(entry), tmp);
931 gtk_editable_set_position(GTK_EDITABLE(entry), -1);
932 g_free(tmp);
934 else
936 gtk_entry_set_text(GTK_ENTRY(entry), " \"$@\"");
937 gtk_editable_set_position(GTK_EDITABLE(entry), 0);
940 gtk_dialog_add_buttons(dialog,
941 GTK_STOCK_CANCEL, GTK_RESPONSE_DELETE_EVENT,
942 GTK_STOCK_OK, GTK_RESPONSE_OK,
943 NULL);
945 hbox = gtk_hbox_new(TRUE, 4);
946 gtk_box_pack_start(GTK_BOX(dialog->vbox), hbox, FALSE, TRUE, 0);
948 gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
950 g_signal_connect(dialog, "response",
951 G_CALLBACK(set_action_response), NULL);
953 gtk_widget_show_all(GTK_WIDGET(dialog));
956 /* path is an entry in Choices. If it's a symlink or a very small executable
957 * then just get rid of it, otherwise confirm first. It it doesn't exist,
958 * do nothing.
960 * FALSE on error (abort operation).
962 static gboolean remove_handler_with_confirm(const guchar *path)
964 struct stat info;
966 if (lstat(path, &info) == 0)
968 /* A binding already exists... */
969 if (S_ISREG(info.st_mode) && info.st_size > 256)
971 if (!confirm(_("A run action already exists and is "
972 "quite a big program - are you sure "
973 "you want to delete it?"),
974 GTK_STOCK_DELETE, NULL))
976 return FALSE;
980 if (unlink(path))
982 report_error(_("Can't remove %s: %s"),
983 path, g_strerror(errno));
984 return FALSE;
988 return TRUE;
991 /* The user wants to set a new default action for files of this type (or just
992 * clear the action). Removes the current binding if possible and returns the
993 * path to save the new one to. NULL means cancel. g_free() the result.
995 static char *get_action_save_path(GtkWidget *dialog)
997 guchar *path = NULL;
998 guchar *type_name = NULL;
999 MIME_type *type;
1000 Radios *radios;
1002 g_return_val_if_fail(dialog != NULL, NULL);
1004 type = g_object_get_data(G_OBJECT(dialog), "mime_type");
1005 radios = g_object_get_data(G_OBJECT(dialog), "rox-radios");
1007 g_return_val_if_fail(radios != NULL && type != NULL, NULL);
1009 if (radios_get_value(radios) == SET_MEDIA)
1010 type_name = g_strdup(type->media_type);
1011 else
1012 type_name = g_strconcat(type->media_type, "_",
1013 type->subtype, NULL);
1015 path = choices_find_path_save("", PROJECT, FALSE);
1016 if (!path)
1018 report_error(
1019 _("Choices saving is disabled by CHOICESPATH variable"));
1020 goto out;
1022 g_free(path);
1024 path = choices_find_path_save(type_name, "MIME-types", TRUE);
1026 if (!remove_handler_with_confirm(path))
1027 null_g_free(&path);
1028 out:
1029 g_free(type_name);
1030 return path;
1033 MIME_type *mime_type_from_base_type(int base_type)
1035 switch (base_type)
1037 case TYPE_FILE:
1038 return text_plain;
1039 case TYPE_DIRECTORY:
1040 return inode_directory;
1041 case TYPE_PIPE:
1042 return inode_pipe;
1043 case TYPE_SOCKET:
1044 return inode_socket;
1045 case TYPE_BLOCK_DEVICE:
1046 return inode_block_dev;
1047 case TYPE_CHAR_DEVICE:
1048 return inode_char_dev;
1049 case TYPE_DOOR:
1050 return inode_door;
1052 return inode_unknown;
1055 /* Takes the st_mode field from stat() and returns the base type.
1056 * Should not be a symlink.
1058 int mode_to_base_type(int st_mode)
1060 if (S_ISREG(st_mode))
1061 return TYPE_FILE;
1062 else if (S_ISDIR(st_mode))
1063 return TYPE_DIRECTORY;
1064 else if (S_ISBLK(st_mode))
1065 return TYPE_BLOCK_DEVICE;
1066 else if (S_ISCHR(st_mode))
1067 return TYPE_CHAR_DEVICE;
1068 else if (S_ISFIFO(st_mode))
1069 return TYPE_PIPE;
1070 else if (S_ISSOCK(st_mode))
1071 return TYPE_SOCKET;
1072 else if (S_ISDOOR(st_mode))
1073 return TYPE_DOOR;
1075 return TYPE_ERROR;
1078 /* Returns TRUE is this is something that is run by looking up its type
1079 * in MIME-types and, hence, can have its run action set.
1081 gboolean can_set_run_action(DirItem *item)
1083 g_return_val_if_fail(item != NULL, FALSE);
1085 return item->base_type == TYPE_FILE &&
1086 !(item->mime_type == application_executable);
1089 /* Parse file type colours and allocate/free them as necessary */
1090 static void alloc_type_colours(void)
1092 gboolean success[NUM_TYPE_COLOURS];
1093 int change_count = 0; /* No. needing realloc */
1094 int i;
1095 static gboolean allocated = FALSE;
1097 /* Parse colours */
1098 for (i = 0; i < NUM_TYPE_COLOURS; i++)
1100 GdkColor *c = &type_colours[i];
1101 gushort r = c->red;
1102 gushort g = c->green;
1103 gushort b = c->blue;
1105 gdk_color_parse(o_type_colours[i].value, &type_colours[i]);
1107 if (allocated && (c->red != r || c->green != g || c->blue != b))
1108 change_count++;
1111 /* Free colours if they were previously allocated and
1112 * have changed or become unneeded.
1114 if (allocated && (change_count || !o_display_colour_types.int_value))
1116 gdk_colormap_free_colors(gdk_rgb_get_colormap(),
1117 type_colours, NUM_TYPE_COLOURS);
1118 allocated = FALSE;
1121 /* Allocate colours, unless they are still allocated (=> they didn't
1122 * change) or we don't want them anymore.
1123 * XXX: what should be done if allocation fails?
1125 if (!allocated && o_display_colour_types.int_value)
1127 gdk_colormap_alloc_colors(gdk_rgb_get_colormap(),
1128 type_colours, NUM_TYPE_COLOURS,
1129 FALSE, TRUE, success);
1130 allocated = TRUE;
1134 static void expire_timer(gpointer key, gpointer value, gpointer data)
1136 MIME_type *type = value;
1138 type->image_time = 0;
1141 static void options_changed(void)
1143 alloc_type_colours();
1144 if (o_icon_theme.has_changed)
1146 set_icon_theme();
1147 g_hash_table_foreach(type_hash, expire_timer, NULL);
1148 full_refresh();
1152 /* Return a pointer to a (static) colour for this item. If colouring is
1153 * off, returns normal.
1155 GdkColor *type_get_colour(DirItem *item, GdkColor *normal)
1157 int type = item->base_type;
1159 if (!o_display_colour_types.int_value)
1160 return normal;
1162 if (item->flags & ITEM_FLAG_EXEC_FILE)
1163 type = TYPE_EXEC;
1164 else if (item->flags & ITEM_FLAG_APPDIR)
1165 type = TYPE_APPDIR;
1167 g_return_val_if_fail(type >= 0 && type < NUM_TYPE_COLOURS, normal);
1169 return &type_colours[type];
1172 /* Process the 'Patterns' value */
1173 static void add_pattern(MIME_type *type, const char *pattern, GHashTable *globs)
1175 if (pattern[0] == '*' && pattern[1] == '.' &&
1176 strpbrk(pattern + 2, "*?[") == NULL)
1178 g_hash_table_insert(extension_hash,
1179 g_strdup(pattern + 2),
1180 type);
1182 else if (strpbrk(pattern, "*?[") == NULL)
1183 g_hash_table_insert(literal_hash, g_strdup(pattern), type);
1184 else
1185 g_hash_table_insert(globs, g_strdup(pattern), type);
1188 /* Load and parse this file. literal_hash and extension_hash are updated
1189 * directly. Other patterns are added to 'globs'.
1191 static void import_file(const gchar *file, GHashTable *globs)
1193 MIME_type *type = NULL;
1194 GError *error = NULL;
1195 gchar *data, *line;
1197 if (access(file, F_OK) != 0)
1198 return; /* Doesn't exist. No problem. */
1200 if (!g_file_get_contents(file, &data, NULL, &error))
1202 delayed_error(_("Error loading MIME database:\n%s"),
1203 error->message);
1204 g_error_free(error);
1205 return;
1208 line = data;
1210 while (line && *line)
1212 char *nl;
1214 nl = strchr(line, '\n');
1215 if (!nl)
1216 break;
1217 *nl = '\0';
1219 if (*line != '#')
1221 const gchar *colon;
1222 gchar *name;
1224 colon = strchr(line, ':');
1225 if (!colon)
1227 delayed_error(_("File '%s' corrupted!"), file);
1228 break;
1231 name = g_strndup(line, colon - line);
1232 type = get_mime_type(name, TRUE);
1233 g_free(name);
1234 if (!type)
1235 g_warning("Invalid type in '%s'", file);
1237 add_pattern(type, colon + 1, globs);
1240 line = nl + 1;
1243 g_free(data);
1246 static void add_to_glob_patterns(gpointer key, gpointer value, gpointer unused)
1248 Pattern *pattern;
1250 pattern = g_new(Pattern, 1);
1251 pattern->glob = g_strdup((gchar *) key);
1252 pattern->type = (MIME_type *) value;
1253 pattern->len = strlen(pattern->glob);
1255 g_ptr_array_add(glob_patterns, pattern);
1258 static gint sort_by_strlen(gconstpointer a, gconstpointer b)
1260 const Pattern *pa = *(const Pattern **) a;
1261 const Pattern *pb = *(const Pattern **) b;
1263 if (pa->len > pb->len)
1264 return -1;
1265 else if (pa->len == pb->len)
1266 return 0;
1267 return 1;
1270 static char **get_xdg_data_dirs(int *n_dirs)
1272 const char *env;
1273 char **dirs;
1274 int i, n;
1276 env = getenv("XDG_DATA_DIRS");
1277 if (!env)
1278 env = "/usr/local/share/:/usr/share/";
1279 dirs = g_strsplit(env, ":", 0);
1280 g_return_val_if_fail(dirs != NULL, NULL);
1281 for (n = 0; dirs[n]; n++)
1283 for (i = n; i > 0; i--)
1284 dirs[i] = dirs[i - 1];
1285 env = getenv("XDG_DATA_HOME");
1286 if (env)
1287 dirs[0] = g_strdup(env);
1288 else
1289 dirs[0] = g_build_filename(g_get_home_dir(), ".local",
1290 "share", NULL);
1291 *n_dirs = n + 1;
1292 return dirs;
1295 /* Clear all currently stored information and re-read everything.
1296 * Note: calls filer_update_all.
1298 static void load_mime_types(void)
1300 GHashTable *globs;
1301 char **dirs;
1302 int n_dirs;
1303 int i;
1305 dirs = get_xdg_data_dirs(&n_dirs);
1306 g_return_if_fail(dirs != NULL);
1309 struct stat info;
1310 if (lstat(make_path(home_dir, ".mime"), &info) == 0 &&
1311 S_ISDIR(info.st_mode))
1313 delayed_error(_("The ~/.mime directory has moved. "
1314 "It should now be ~/.local/share/mime. You "
1315 "should move it there (and make a symlink "
1316 "from ~/.mime to it for older applications)."));
1320 if (!glob_patterns)
1321 glob_patterns = g_ptr_array_new();
1322 else
1324 int i;
1326 for (i = glob_patterns->len - 1; i >= 0; i--)
1328 Pattern *p = glob_patterns->pdata[i];
1329 g_free(p->glob);
1330 g_free(p);
1332 g_ptr_array_set_size(glob_patterns, 0);
1335 if (literal_hash)
1336 g_hash_table_destroy(literal_hash);
1337 if (extension_hash)
1338 g_hash_table_destroy(extension_hash);
1339 literal_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
1340 g_free, NULL);
1341 extension_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
1342 g_free, NULL);
1343 globs = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1345 for (i = n_dirs - 1; i >= 0; i--)
1347 char *path;
1348 path = g_build_filename(dirs[i], "mime", "globs", NULL);
1349 import_file(path, globs);
1350 g_free(path);
1351 g_free(dirs[i]);
1353 g_free(dirs);
1355 /* Turn the globs hash into a pointer array */
1356 g_hash_table_foreach(globs, add_to_glob_patterns, NULL);
1357 g_hash_table_destroy(globs);
1358 globs = NULL;
1360 if (glob_patterns->len)
1361 g_ptr_array_sort(glob_patterns, sort_by_strlen);
1363 if (g_hash_table_size(extension_hash) == 0)
1365 delayed_error(_("The standard MIME type database "
1366 "(version 0.9 or later) was not found. "
1367 "The filer will probably not show the correct "
1368 "types for different files. You should download and "
1369 "install the 'shared-mime-info-0.9' package from "
1370 "here:\n"
1371 "http://www.freedesktop.org/software/shared-mime-info\n\n"
1372 "If you have already installed this package, check that the "
1373 "permissions allow the files to be read (check "
1374 "/usr/local/share/mime/globs or /usr/share/mime/globs)."));
1377 filer_update_all();
1380 /* Try to fill in 'type->comment' from this document */
1381 static void get_comment(MIME_type *type, const guchar *path)
1383 xmlNode *node;
1384 XMLwrapper *doc;
1386 doc = xml_cache_load(path);
1387 if (!doc)
1388 return;
1390 node = xml_get_section(doc, TYPE_NS, "comment");
1392 if (node)
1394 char *val;
1395 g_return_if_fail(type->comment == NULL);
1396 val= xmlNodeListGetString(node->doc, node->xmlChildrenNode, 1);
1397 type->comment = g_strdup(val);
1398 xmlFree(val);
1401 g_object_unref(doc);
1404 /* Fill in the comment field for this MIME type */
1405 static void find_comment(MIME_type *type)
1407 char **dirs;
1408 int i, n_dirs;
1410 if (type->comment)
1412 g_free(type->comment);
1413 type->comment = NULL;
1416 dirs = get_xdg_data_dirs(&n_dirs);
1417 g_return_if_fail(dirs != NULL);
1419 for (i = 0; i < n_dirs; i++)
1421 guchar *path;
1423 path = g_strdup_printf("%s/mime/%s/%s.xml", dirs[i],
1424 type->media_type, type->subtype);
1425 get_comment(type, path);
1426 g_free(path);
1427 if (type->comment)
1428 break;
1431 if (!type->comment)
1432 type->comment = g_strdup_printf("%s/%s", type->media_type,
1433 type->subtype);
1435 for (i = 0; i < n_dirs; i++)
1436 g_free(dirs[i]);
1437 g_free(dirs);
1440 const char *mime_type_comment(MIME_type *type)
1442 if (!type->comment)
1443 find_comment(type);
1445 return type->comment;
1448 static void set_icon_theme(void)
1450 GtkIconInfo *info;
1451 char *icon_home;
1452 const char *theme_name = o_icon_theme.value;
1454 if (!theme_name || !*theme_name)
1455 theme_name = "ROX";
1457 while (1)
1459 rox_icon_theme_set_custom_theme(icon_theme, theme_name);
1460 info = rox_icon_theme_lookup_icon(icon_theme,
1461 "mime-application:postscript",
1462 ICON_HEIGHT, 0);
1463 if (!info)
1465 info = rox_icon_theme_lookup_icon(icon_theme,
1466 "gnome-mime-application-postscript",
1467 ICON_HEIGHT, 0);
1469 if (info)
1471 gtk_icon_info_free(info);
1472 return;
1475 if (strcmp(theme_name, "ROX") == 0)
1476 break;
1478 delayed_error(_("Icon theme '%s' does not contain MIME icons. "
1479 "Using ROX default theme instead."),
1480 theme_name);
1482 theme_name = "ROX";
1485 delayed_error(_("ROX icon theme not found... installing..."));
1487 icon_home = g_build_filename(home_dir, ".icons", NULL);
1488 if (!file_exists(icon_home))
1489 mkdir(icon_home, 0755);
1490 g_free(icon_home);
1492 icon_home = g_build_filename(home_dir, ".icons", "ROX", NULL);
1493 if (symlink(make_path(app_dir, "ROX"), icon_home))
1494 delayed_error(_("Failed to create symlink '%s':\n%s"),
1495 icon_home, g_strerror(errno));
1496 g_free(icon_home);
1498 rox_icon_theme_rescan_if_needed(icon_theme);
1501 static guchar *read_theme(Option *option)
1503 GtkOptionMenu *om = GTK_OPTION_MENU(option->widget);
1504 GtkLabel *item;
1506 item = GTK_LABEL(GTK_BIN(om)->child);
1508 g_return_val_if_fail(item != NULL, g_strdup("ROX"));
1510 return g_strdup(gtk_label_get_text(item));
1513 static void update_theme(Option *option)
1515 GtkOptionMenu *om = GTK_OPTION_MENU(option->widget);
1516 GtkWidget *menu;
1517 GList *kids, *next;
1518 int i = 0;
1520 menu = gtk_option_menu_get_menu(om);
1522 kids = gtk_container_get_children(GTK_CONTAINER(menu));
1523 for (next = kids; next; next = next->next, i++)
1525 GtkLabel *item = GTK_LABEL(GTK_BIN(next->data)->child);
1526 const gchar *label;
1528 /* The label actually moves from the menu!! */
1529 if (!item)
1530 item = GTK_LABEL(GTK_BIN(om)->child);
1532 label = gtk_label_get_text(item);
1534 g_return_if_fail(label != NULL);
1536 if (strcmp(label, option->value) == 0)
1537 break;
1539 g_list_free(kids);
1541 if (next)
1542 gtk_option_menu_set_history(om, i);
1543 else
1544 g_warning("Theme '%s' not found", option->value);
1547 static void add_themes_from_dir(GPtrArray *names, const char *dir)
1549 GPtrArray *list;
1550 int i;
1552 if (access(dir, F_OK) != 0)
1553 return;
1555 list = list_dir(dir);
1556 g_return_if_fail(list != NULL);
1558 for (i = 0; i < list->len; i++)
1560 char *index_path;
1562 index_path = g_build_filename(dir, list->pdata[i],
1563 "index.theme", NULL);
1565 if (access(index_path, F_OK) == 0)
1566 g_ptr_array_add(names, list->pdata[i]);
1567 else
1568 g_free(list->pdata[i]);
1570 g_free(index_path);
1573 g_ptr_array_free(list, TRUE);
1576 static GList *build_icon_theme(Option *option, xmlNode *node, guchar *label)
1578 GtkWidget *button, *menu, *hbox;
1579 GPtrArray *names;
1580 gchar **theme_dirs = NULL;
1581 gint n_dirs = 0;
1582 int i;
1584 g_return_val_if_fail(option != NULL, NULL);
1585 g_return_val_if_fail(label != NULL, NULL);
1587 hbox = gtk_hbox_new(FALSE, 4);
1589 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_(label)),
1590 FALSE, TRUE, 0);
1592 button = gtk_option_menu_new();
1593 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
1595 menu = gtk_menu_new();
1596 gtk_option_menu_set_menu(GTK_OPTION_MENU(button), menu);
1598 rox_icon_theme_get_search_path(icon_theme, &theme_dirs, &n_dirs);
1599 names = g_ptr_array_new();
1600 for (i = 0; i < n_dirs; i++)
1601 add_themes_from_dir(names, theme_dirs[i]);
1602 g_strfreev(theme_dirs);
1604 g_ptr_array_sort(names, strcmp2);
1606 for (i = 0; i < names->len; i++)
1608 GtkWidget *item;
1609 char *name = names->pdata[i];
1611 item = gtk_menu_item_new_with_label(name);
1612 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
1613 gtk_widget_show_all(item);
1615 g_free(name);
1618 g_ptr_array_free(names, TRUE);
1620 option->update_widget = update_theme;
1621 option->read_widget = read_theme;
1622 option->widget = button;
1624 gtk_signal_connect_object(GTK_OBJECT(button), "changed",
1625 GTK_SIGNAL_FUNC(option_check_widget),
1626 (GtkObject *) option);
1628 return g_list_append(NULL, hbox);