r496: Many internal changes to the Options system.
[rox-filer.git] / ROX-Filer / src / type.c
blobbc462cadab21f27e448a2626fd200603fd9e0e3c
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2000, Thomas Leonard, <tal197@users.sourceforge.net>.
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>
33 #include "global.h"
35 #include "string.h"
36 #include "main.h"
37 #include "pixmaps.h"
38 #include "run.h"
39 #include "gui_support.h"
40 #include "choices.h"
41 #include "type.h"
42 #include "support.h"
43 #include "dir.h"
44 #include "dnd.h"
46 /* Static prototypes */
47 static char *import_extensions(guchar *line);
48 static void import_for_dir(guchar *path);
49 char *get_action_save_path(GtkWidget *dialog);
51 /* Maps extensions to MIME_types (eg 'png'-> MIME_type *).
52 * Extensions may contain dots; 'tar.gz' matches '*.tar.gz', etc.
53 * The hash table is consulted from each dot in the string in turn
54 * (First .ps.gz, then try .gz)
56 static GHashTable *extension_hash = NULL;
57 static char *current_type = NULL; /* (used while reading file) */
59 /* Most things on Unix are text files, so this is the default type */
60 MIME_type text_plain = {"text", "plain", NULL};
62 MIME_type special_directory = {"special", "directory", NULL};
63 MIME_type special_pipe = {"special", "pipe", NULL};
64 MIME_type special_socket = {"special", "socket", NULL};
65 MIME_type special_block_dev = {"special", "block-device", NULL};
66 MIME_type special_char_dev = {"special", "char-device", NULL};
67 MIME_type special_exec = {"special", "executable", NULL};
68 MIME_type special_unknown = {"special", "unknown", NULL};
70 void type_init()
72 int i;
73 GPtrArray *list;
75 extension_hash = g_hash_table_new(g_str_hash, g_str_equal);
77 current_type = NULL;
79 list = choices_list_dirs("MIME-info");
80 for (i = 0; i < list->len; i++)
81 import_for_dir((gchar *) g_ptr_array_index(list, i));
82 choices_free_list(list);
85 /* Parse every file in 'dir' */
86 static void import_for_dir(guchar *path)
88 DIR *dir;
89 struct dirent *item;
91 dir = opendir(path);
92 if (!dir)
93 return;
95 while ((item = readdir(dir)))
97 guchar *file;
98 struct stat info;
100 if (item->d_name[0] == '.')
101 continue;
103 current_type = NULL;
104 file = make_path(path, item->d_name)->str;
106 if (stat(file, &info) == 0 && S_ISREG(info.st_mode))
107 parse_file(file, import_extensions);
110 closedir(dir);
113 /* Add one entry to the extension_hash table */
114 static void add_ext(char *type_name, char *ext)
116 MIME_type *new;
117 char *slash;
118 int len;
120 slash = strchr(type_name, '/');
121 g_return_if_fail(slash != NULL); /* XXX: Report nicely */
122 len = slash - type_name;
124 new = g_new(MIME_type, 1);
125 new->media_type = g_malloc(sizeof(char) * (len + 1));
126 memcpy(new->media_type, type_name, len);
127 new->media_type[len] = '\0';
129 new->subtype = g_strdup(slash + 1);
130 new->image = NULL;
132 g_hash_table_insert(extension_hash, g_strdup(ext), new);
135 /* Parse one line from the file and add entries to extension_hash */
136 static char *import_extensions(guchar *line)
139 if (*line == '\0' || *line == '#')
140 return NULL; /* Comment */
142 if (isspace(*line))
144 if (!current_type)
145 return _("Missing MIME-type");
146 while (*line && isspace(*line))
147 line++;
149 if (strncmp(line, "ext:", 4) == 0)
151 char *ext;
152 line += 4;
154 for (;;)
156 while (*line && isspace(*line))
157 line++;
158 if (*line == '\0')
159 break;
160 ext = line;
161 while (*line && !isspace(*line))
162 line++;
163 if (*line)
164 *line++ = '\0';
165 add_ext(current_type, ext);
168 /* else ignore */
170 else
172 char *type = line;
173 while (*line && *line != ':' && !isspace(*line))
174 line++;
175 if (*line)
176 *line++ = '\0';
177 while (*line && isspace(*line))
178 line++;
179 if (*line)
180 return _("Trailing chars after MIME-type");
181 current_type = g_strdup(type);
183 return NULL;
186 char *basetype_name(DirItem *item)
188 if (item->flags & ITEM_FLAG_SYMLINK)
189 return _("Sym link");
190 else if (item->flags & ITEM_FLAG_MOUNT_POINT)
191 return _("Mount point");
192 else if (item->flags & ITEM_FLAG_APPDIR)
193 return _("App dir");
195 switch (item->base_type)
197 case TYPE_FILE:
198 return _("File");
199 case TYPE_DIRECTORY:
200 return _("Dir");
201 case TYPE_CHAR_DEVICE:
202 return _("Char dev");
203 case TYPE_BLOCK_DEVICE:
204 return _("Block dev");
205 case TYPE_PIPE:
206 return _("Pipe");
207 case TYPE_SOCKET:
208 return _("Socket");
211 return _("Unknown");
214 /* MIME-type guessing */
216 /* Returns a pointer to the MIME-type.
217 * NULL if we can't think of anything.
219 MIME_type *type_from_path(char *path)
221 char *ext, *dot, *lower;
223 ext = strrchr(path, '/');
224 if (!ext)
225 ext = path;
226 else
227 ext++;
229 while ((dot = strchr(ext, '.')))
231 MIME_type *type;
233 ext = dot + 1;
235 type = g_hash_table_lookup(extension_hash, ext);
237 if (type)
238 return type;
240 lower = g_strdup(ext);
241 g_strdown(lower);
242 type = g_hash_table_lookup(extension_hash, lower);
243 g_free(lower);
245 if (type)
246 return type;
249 return NULL;
252 /* Actions for types */
254 gboolean type_open(char *path, MIME_type *type)
256 char *argv[] = {NULL, NULL, NULL};
257 char *open;
258 char *type_name;
259 gboolean retval = TRUE;
260 struct stat info;
262 argv[1] = path;
264 type_name = g_strconcat(type->media_type, "_", type->subtype, NULL);
265 open = choices_find_path_load(type_name, "MIME-types");
266 g_free(type_name);
267 if (!open)
269 open = choices_find_path_load(type->media_type,
270 "MIME-types");
271 if (!open)
272 return FALSE;
275 if (stat(open, &info))
277 report_error(PROJECT, g_strerror(errno));
278 g_free(open);
279 return FALSE;
282 if (S_ISDIR(info.st_mode))
283 argv[0] = g_strconcat(open, "/AppRun", NULL);
284 else
285 argv[0] = open;
287 if (!spawn_full(argv, home_dir))
289 report_error(PROJECT,
290 _("Failed to fork() child process"));
291 retval = FALSE;
294 if (argv[0] != open)
295 g_free(argv[0]);
297 g_free(open);
299 return retval;
302 /* Return the image for this type, loading it if needed.
303 * Places to check are: (eg type="text_plain", base="text")
304 * 1. Choices:MIME-icons/<type>
305 * 2. Choices:MIME-icons/<base>
306 * 3. Unknown type icon.
308 * Note: You must pixmap_unref() the image afterwards.
310 MaskedPixmap *type_to_icon(MIME_type *type)
312 char *path;
313 char *type_name;
314 time_t now;
316 if (type == NULL)
318 pixmap_ref(im_unknown);
319 return im_unknown;
322 now = time(NULL);
323 /* Already got an image? */
324 if (type->image)
326 /* Yes - don't recheck too often */
327 if (abs(now - type->image_time) < 2)
329 pixmap_ref(type->image);
330 return type->image;
332 pixmap_unref(type->image);
333 type->image = NULL;
336 type_name = g_strconcat(type->media_type, "_",
337 type->subtype, ".xpm", NULL);
338 path = choices_find_path_load(type_name, "MIME-icons");
339 if (!path)
341 strcpy(type_name + strlen(type->media_type), ".xpm");
342 path = choices_find_path_load(type_name, "MIME-icons");
345 g_free(type_name);
347 if (path)
349 type->image = g_fscache_lookup(pixmap_cache, path);
350 g_free(path);
353 if (!type->image)
355 type->image = im_unknown;
356 pixmap_ref(type->image);
359 type->image_time = now;
361 pixmap_ref(type->image);
362 return type->image;
365 GdkAtom type_to_atom(MIME_type *type)
367 char *str;
368 GdkAtom retval;
370 g_return_val_if_fail(type != NULL, GDK_NONE);
372 str = g_strconcat(type->media_type, "/", type->subtype, NULL);
373 retval = gdk_atom_intern(str, FALSE);
374 g_free(str);
376 return retval;
379 void show_shell_help(gpointer data)
381 report_error(PROJECT,
382 _("Enter a shell command which will load \"$1\" into "
383 "a suitable program. Eg:\n\n"
384 "gimp \"$1\""));
387 /* Called if the user clicks on the OK button */
388 static void set_shell_action(GtkWidget *dialog)
390 GtkEntry *entry;
391 GtkToggleButton *for_all;
392 guchar *command, *path, *tmp;
393 int error = 0, len;
394 FILE *file;
396 entry = gtk_object_get_data(GTK_OBJECT(dialog), "shell_command");
397 for_all = gtk_object_get_data(GTK_OBJECT(dialog), "set_for_all");
398 g_return_if_fail(entry != NULL);
400 command = gtk_entry_get_text(entry);
402 if (!strchr(command, '$'))
404 show_shell_help(NULL);
405 return;
408 path = get_action_save_path(dialog);
409 if (!path)
410 return;
412 tmp = g_strdup_printf("#! /bin/sh\nexec %s\n", command);
413 len = strlen(tmp);
415 file = fopen(path, "wb");
416 if (fwrite(tmp, 1, len, file) < len)
417 error = errno;
418 if (fclose(file) && error == 0)
419 error = errno;
420 if (chmod(path, 0777))
421 error = errno;
423 if (error)
424 report_error(PROJECT, g_strerror(errno));
426 g_free(tmp);
427 g_free(path);
429 gtk_widget_destroy(dialog);
432 /* Called when a URI list is dropped onto the box in the Set Run Action
433 * dialog. Make sure it's an application, and make that the default
434 * handler.
436 void drag_app_dropped(GtkWidget *frame,
437 GdkDragContext *context,
438 gint x,
439 gint y,
440 GtkSelectionData *selection_data,
441 guint info,
442 guint32 time,
443 GtkWidget *dialog)
445 GSList *uris;
446 guchar *app = NULL;
447 DirItem item;
449 if (!selection_data->data)
450 return; /* Timeout? */
452 uris = uri_list_to_gslist(selection_data->data);
454 if (g_slist_length(uris) == 1)
455 app = get_local_path((guchar *) uris->data);
456 g_slist_free(uris);
458 if (!app)
460 delayed_error(PROJECT,
461 _("You should drop a single (local) application "
462 "onto the drop box - that application will be "
463 "used to load files of this type in future"));
464 return;
467 dir_stat(app, &item, FALSE);
468 if (item.flags & (ITEM_FLAG_APPDIR | ITEM_FLAG_EXEC_FILE))
470 guchar *path;
472 path = get_action_save_path(dialog);
474 if (path)
476 if (symlink(app, path))
477 delayed_error("symlink failed",
478 g_strerror(errno));
479 else
480 destroy_on_idle(dialog);
483 g_free(path);
485 else
486 delayed_error(PROJECT,
487 _("This is not a program! Give me an application "
488 "instead!"));
490 dir_item_clear(&item);
493 /* Display a dialog box allowing the user to set the default run action
494 * for this type.
496 void type_set_handler_dialog(MIME_type *type)
498 guchar *tmp;
499 GtkWidget *dialog, *vbox, *frame, *hbox, *entry, *label, *button;
500 GtkWidget *radio;
501 GtkTargetEntry targets[] = {
502 {"text/uri-list", 0, TARGET_URI_LIST},
505 g_return_if_fail(type != NULL);
507 dialog = gtk_window_new(GTK_WINDOW_DIALOG);
508 gtk_object_set_data(GTK_OBJECT(dialog), "mime_type", type);
510 gtk_window_set_title(GTK_WINDOW(dialog), _("Set run action"));
511 gtk_container_set_border_width(GTK_CONTAINER(dialog), 10);
513 vbox = gtk_vbox_new(FALSE, 4);
514 gtk_container_add(GTK_CONTAINER(dialog), vbox);
516 tmp = g_strconcat("Set default for all `", type->media_type,
517 "/<anything>'", NULL);
518 radio = gtk_radio_button_new_with_label(NULL, tmp);
519 g_free(tmp);
520 gtk_object_set_data(GTK_OBJECT(dialog), "set_for_all", radio);
522 tmp = g_strconcat("Only for the type `", type->media_type, "/",
523 type->subtype, "'", NULL);
524 gtk_box_pack_start(GTK_BOX(vbox), radio, FALSE, TRUE, 0);
525 radio = gtk_radio_button_new_with_label(
526 gtk_radio_button_group(GTK_RADIO_BUTTON(radio)),
527 tmp);
528 g_free(tmp);
529 gtk_box_pack_start(GTK_BOX(vbox), radio, FALSE, TRUE, 0);
530 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio), TRUE);
532 frame = gtk_frame_new(NULL);
533 gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 4);
534 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
535 gtk_container_set_border_width(GTK_CONTAINER(frame), 4);
537 gtk_drag_dest_set(frame, GTK_DEST_DEFAULT_ALL,
538 targets, sizeof(targets) / sizeof(*targets),
539 GDK_ACTION_COPY);
540 gtk_signal_connect(GTK_OBJECT(frame), "drag_data_received",
541 GTK_SIGNAL_FUNC(drag_app_dropped), dialog);
543 label = gtk_label_new(_("Drop a suitable\napplication here"));
544 gtk_misc_set_padding(GTK_MISC(label), 10, 20);
545 gtk_container_add(GTK_CONTAINER(frame), label);
547 hbox = gtk_hbox_new(FALSE, 4);
548 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 4);
549 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
550 gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new(_("OR")),
551 FALSE, TRUE, 0);
552 gtk_box_pack_start(GTK_BOX(hbox), gtk_hseparator_new(), TRUE, TRUE, 0);
554 hbox = gtk_hbox_new(FALSE, 4);
555 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
557 label = gtk_label_new(_("Enter a shell command:")),
558 gtk_misc_set_alignment(GTK_MISC(label), 0, .5);
559 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 4);
561 gtk_box_pack_start(GTK_BOX(hbox),
562 new_help_button(show_shell_help, NULL), FALSE, TRUE, 0);
564 entry = gtk_entry_new();
565 gtk_box_pack_start(GTK_BOX(vbox), entry, FALSE, TRUE, 0);
566 gtk_widget_grab_focus(entry);
567 gtk_object_set_data(GTK_OBJECT(dialog), "shell_command", entry);
568 gtk_signal_connect_object(GTK_OBJECT(entry), "activate",
569 GTK_SIGNAL_FUNC(set_shell_action), GTK_OBJECT(dialog));
571 hbox = gtk_hbox_new(TRUE, 4);
572 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
574 button = gtk_button_new_with_label(_("OK"));
575 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
576 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
577 gtk_window_set_default(GTK_WINDOW(dialog), button);
578 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
579 GTK_SIGNAL_FUNC(set_shell_action), GTK_OBJECT(dialog));
581 button = gtk_button_new_with_label(_("Cancel"));
582 GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
583 gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0);
584 gtk_signal_connect_object(GTK_OBJECT(button), "clicked",
585 GTK_SIGNAL_FUNC(gtk_widget_destroy),
586 GTK_OBJECT(dialog));
588 gtk_widget_show_all(dialog);
591 /* The user wants to set a new default action for files of this type.
592 * Removes the current binding if possible and returns the path to
593 * save the new one to. NULL means cancel. g_free() the result.
595 char *get_action_save_path(GtkWidget *dialog)
597 guchar *tmp, *path = NULL;
598 struct stat info;
599 guchar *type_name = NULL;
600 MIME_type *type;
601 GtkToggleButton *for_all;
603 g_return_val_if_fail(dialog != NULL, NULL);
604 type = gtk_object_get_data(GTK_OBJECT(dialog), "mime_type");
605 for_all = gtk_object_get_data(GTK_OBJECT(dialog), "set_for_all");
606 g_return_val_if_fail(for_all != NULL && type != NULL, NULL);
608 if (gtk_toggle_button_get_active(for_all))
609 type_name = g_strdup(type->media_type);
610 else
611 type_name = g_strconcat(type->media_type, "_",
612 type->subtype, NULL);
614 path = choices_find_path_save("", PROJECT, FALSE);
615 if (!path)
617 report_error(PROJECT,
618 _("Choices saving is disabled by CHOICESPATH variable"));
619 goto out;
621 g_free(path);
623 path = choices_find_path_save(type_name, "MIME-types", TRUE);
625 if (lstat(path, &info) == 0)
627 /* A binding already exists... */
628 if (S_ISREG(info.st_mode) && info.st_size > 256)
630 if (get_choice(PROJECT,
631 _("A run action already exists and is quite "
632 "a big program - are you sure you want to "
633 "delete it?"), 2, "Delete", "Cancel") != 0)
635 g_free(path);
636 path = NULL;
637 goto out;
641 if (unlink(path))
643 tmp = g_strdup_printf( _("Can't remove %s: %s"),
644 path, g_strerror(errno));
645 report_error(PROJECT, tmp);
646 g_free(tmp);
647 g_free(path);
648 path = NULL;
649 goto out;
653 out:
654 g_free(type_name);
655 return path;
658 MIME_type *mime_type_from_base_type(int base_type)
660 switch (base_type)
662 case TYPE_FILE:
663 return &text_plain;
664 case TYPE_DIRECTORY:
665 return &special_directory;
666 case TYPE_PIPE:
667 return &special_pipe;
668 case TYPE_SOCKET:
669 return &special_socket;
670 case TYPE_BLOCK_DEVICE:
671 return &special_block_dev;
672 case TYPE_CHAR_DEVICE:
673 return &special_char_dev;
675 return &special_unknown;
678 /* Takes the st_mode field from stat() and returns the base type.
679 * Should not be a symlink.
681 int mode_to_base_type(int st_mode)
683 if (S_ISREG(st_mode))
684 return TYPE_FILE;
685 else if (S_ISDIR(st_mode))
686 return TYPE_DIRECTORY;
687 else if (S_ISBLK(st_mode))
688 return TYPE_BLOCK_DEVICE;
689 else if (S_ISCHR(st_mode))
690 return TYPE_CHAR_DEVICE;
691 else if (S_ISFIFO(st_mode))
692 return TYPE_PIPE;
693 else if (S_ISSOCK(st_mode))
694 return TYPE_SOCKET;
696 return TYPE_UNKNOWN;