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)
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 /* type.c - code for dealing with filetypes */
31 #include <sys/param.h>
39 #include "gui_support.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
};
75 extension_hash
= g_hash_table_new(g_str_hash
, g_str_equal
);
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
)
95 while ((item
= readdir(dir
)))
100 if (item
->d_name
[0] == '.')
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
);
113 /* Add one entry to the extension_hash table */
114 static void add_ext(char *type_name
, char *ext
)
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);
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 */
145 return _("Missing MIME-type");
146 while (*line
&& isspace(*line
))
149 if (strncmp(line
, "ext:", 4) == 0)
156 while (*line
&& isspace(*line
))
161 while (*line
&& !isspace(*line
))
165 add_ext(current_type
, ext
);
173 while (*line
&& *line
!= ':' && !isspace(*line
))
177 while (*line
&& isspace(*line
))
180 return _("Trailing chars after MIME-type");
181 current_type
= g_strdup(type
);
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
)
195 switch (item
->base_type
)
201 case TYPE_CHAR_DEVICE
:
202 return _("Char dev");
203 case TYPE_BLOCK_DEVICE
:
204 return _("Block dev");
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
, '/');
229 while ((dot
= strchr(ext
, '.')))
235 type
= g_hash_table_lookup(extension_hash
, ext
);
240 lower
= g_strdup(ext
);
242 type
= g_hash_table_lookup(extension_hash
, lower
);
252 /* Actions for types */
254 gboolean
type_open(char *path
, MIME_type
*type
)
256 char *argv
[] = {NULL
, NULL
, NULL
};
259 gboolean retval
= TRUE
;
264 type_name
= g_strconcat(type
->media_type
, "_", type
->subtype
, NULL
);
265 open
= choices_find_path_load(type_name
, "MIME-types");
269 open
= choices_find_path_load(type
->media_type
,
275 if (stat(open
, &info
))
277 report_error(PROJECT
, g_strerror(errno
));
282 if (S_ISDIR(info
.st_mode
))
283 argv
[0] = g_strconcat(open
, "/AppRun", NULL
);
287 if (!spawn_full(argv
, home_dir
))
289 report_error(PROJECT
,
290 _("Failed to fork() child process"));
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
)
318 pixmap_ref(im_unknown
);
323 /* Already got an image? */
326 /* Yes - don't recheck too often */
327 if (abs(now
- type
->image_time
) < 2)
329 pixmap_ref(type
->image
);
332 pixmap_unref(type
->image
);
336 type_name
= g_strconcat(type
->media_type
, "_",
337 type
->subtype
, ".xpm", NULL
);
338 path
= choices_find_path_load(type_name
, "MIME-icons");
341 strcpy(type_name
+ strlen(type
->media_type
), ".xpm");
342 path
= choices_find_path_load(type_name
, "MIME-icons");
349 type
->image
= g_fscache_lookup(pixmap_cache
, path
);
355 type
->image
= im_unknown
;
356 pixmap_ref(type
->image
);
359 type
->image_time
= now
;
361 pixmap_ref(type
->image
);
365 GdkAtom
type_to_atom(MIME_type
*type
)
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
);
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"
387 /* Called if the user clicks on the OK button */
388 static void set_shell_action(GtkWidget
*dialog
)
391 GtkToggleButton
*for_all
;
392 guchar
*command
, *path
, *tmp
;
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
);
408 path
= get_action_save_path(dialog
);
412 tmp
= g_strdup_printf("#! /bin/sh\nexec %s\n", command
);
415 file
= fopen(path
, "wb");
416 if (fwrite(tmp
, 1, len
, file
) < len
)
418 if (fclose(file
) && error
== 0)
420 if (chmod(path
, 0777))
424 report_error(PROJECT
, g_strerror(errno
));
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
436 void drag_app_dropped(GtkWidget
*frame
,
437 GdkDragContext
*context
,
440 GtkSelectionData
*selection_data
,
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
);
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"));
467 dir_stat(app
, &item
, FALSE
);
468 if (item
.flags
& (ITEM_FLAG_APPDIR
| ITEM_FLAG_EXEC_FILE
))
472 path
= get_action_save_path(dialog
);
476 if (symlink(app
, path
))
477 delayed_error("symlink failed",
480 destroy_on_idle(dialog
);
486 delayed_error(PROJECT
,
487 _("This is not a program! Give me an application "
490 dir_item_clear(&item
);
493 /* Display a dialog box allowing the user to set the default run action
496 void type_set_handler_dialog(MIME_type
*type
)
499 GtkWidget
*dialog
, *vbox
, *frame
, *hbox
, *entry
, *label
, *button
;
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
);
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
)),
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
),
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")),
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
),
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
;
599 guchar
*type_name
= NULL
;
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
);
611 type_name
= g_strconcat(type
->media_type
, "_",
612 type
->subtype
, NULL
);
614 path
= choices_find_path_save("", PROJECT
, FALSE
);
617 report_error(PROJECT
,
618 _("Choices saving is disabled by CHOICESPATH variable"));
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)
643 tmp
= g_strdup_printf( _("Can't remove %s: %s"),
644 path
, g_strerror(errno
));
645 report_error(PROJECT
, tmp
);
658 MIME_type
*mime_type_from_base_type(int base_type
)
665 return &special_directory
;
667 return &special_pipe
;
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
))
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
))
693 else if (S_ISSOCK(st_mode
))