mirror of
https://github.com/MidnightCommander/mc
synced 2024-12-25 05:46:51 +03:00
0a1238fe83
* gdesktop.c, gdnd.c, gaction.c, gicon.c, gnome-file-property-dialog.c, gpopup.c, gpopup2.c, gprefs.cgmain.h, setup.c: Add option of using GNOME "magic" routines to determine file types. This makes MC intelligently look at the data in the file to see what type of file it is, rather than looking at the filename extension. This slows down MC quite a bit, so it has been made an optional preference item. Type detection is expected to improve as mime-magic matures.
544 lines
12 KiB
C
544 lines
12 KiB
C
/* Icon loading support for the Midnight Commander
|
|
*
|
|
* Copyright (C) 1998-1999 The Free Software Foundation
|
|
*
|
|
* Authors: Miguel de Icaza <miguel@nuclecu.unam.mx>
|
|
* Federico Mena <federico@nuclecu.unam.mx>
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include "util.h"
|
|
#include "dialog.h"
|
|
|
|
#include <gnome.h>
|
|
#include "gicon.h"
|
|
#include "gmain.h"
|
|
|
|
|
|
/* What kinds of images can an icon set contain */
|
|
typedef enum {
|
|
ICON_TYPE_PLAIN,
|
|
ICON_TYPE_SYMLINK,
|
|
ICON_TYPE_STALLED
|
|
} IconType;
|
|
|
|
/* Information for an icon set (plain icon plus overlayed versions) */
|
|
typedef struct {
|
|
GdkImlibImage *plain; /* Plain version */
|
|
GdkImlibImage *symlink; /* Symlink version */
|
|
GdkImlibImage *stalled; /* Stalled symlink version */
|
|
char *filename; /* Filename for the plain image */
|
|
} IconSet;
|
|
|
|
static int gicon_inited; /* Has the icon system been inited? */
|
|
|
|
static GHashTable *name_hash; /* Hash from filename -> IconSet */
|
|
static GHashTable *image_hash; /* Hash from imlib image -> IconSet */
|
|
|
|
static GdkImlibImage *symlink_overlay; /* The little symlink overlay */
|
|
static GdkImlibImage *stalled_overlay; /* The little stalled symlink overlay */
|
|
|
|
/* Default icons, guaranteed to exist */
|
|
static IconSet *iset_directory;
|
|
static IconSet *iset_dirclosed;
|
|
static IconSet *iset_executable;
|
|
static IconSet *iset_regular;
|
|
static IconSet *iset_core;
|
|
static IconSet *iset_sock;
|
|
static IconSet *iset_fifo;
|
|
static IconSet *iset_chardev;
|
|
static IconSet *iset_blockdev;
|
|
|
|
/* Our UID and GID, needed to see if the user can access some files */
|
|
static uid_t our_uid;
|
|
static gid_t our_gid;
|
|
|
|
|
|
/* Whether we should always use (expensive) metadata lookups for file panels or not */
|
|
int we_can_afford_the_speed = 0;
|
|
|
|
/* Builds a composite of the plain image and the litle symlink icon */
|
|
static GdkImlibImage *
|
|
build_overlay (GdkImlibImage *plain, GdkImlibImage *overlay)
|
|
{
|
|
int rowstride;
|
|
int overlay_rowstride;
|
|
guchar *src, *dest;
|
|
int y;
|
|
GdkImlibImage *im;
|
|
|
|
im = gdk_imlib_clone_image (plain);
|
|
|
|
rowstride = plain->rgb_width * 3;
|
|
overlay_rowstride = overlay->rgb_width * 3;
|
|
|
|
dest = im->rgb_data + ((plain->rgb_height - overlay->rgb_height) * rowstride
|
|
+ (plain->rgb_width - overlay->rgb_width) * 3);
|
|
|
|
src = overlay->rgb_data;
|
|
|
|
for (y = 0; y < overlay->rgb_height; y++) {
|
|
memcpy (dest, src, overlay_rowstride);
|
|
|
|
dest += rowstride;
|
|
src += overlay_rowstride;
|
|
}
|
|
|
|
gdk_imlib_changed_image (im);
|
|
return im;
|
|
}
|
|
|
|
/* Ensures that the icon set has the requested image */
|
|
static void
|
|
ensure_icon_image (IconSet *iset, IconType type)
|
|
{
|
|
g_assert (iset->plain != NULL);
|
|
|
|
switch (type) {
|
|
case ICON_TYPE_PLAIN:
|
|
/* The plain type always exists, so do nothing */
|
|
break;
|
|
|
|
case ICON_TYPE_SYMLINK:
|
|
iset->symlink = build_overlay (iset->plain, symlink_overlay);
|
|
g_hash_table_insert (image_hash, iset->symlink, iset);
|
|
break;
|
|
|
|
case ICON_TYPE_STALLED:
|
|
iset->stalled = build_overlay (iset->plain, stalled_overlay);
|
|
g_hash_table_insert (image_hash, iset->stalled, iset);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
compute_scaled_size (int width, int height, int *nwidth, int *nheight)
|
|
{
|
|
g_return_if_fail (nwidth != NULL);
|
|
g_return_if_fail (nheight != NULL);
|
|
|
|
if (width <= ICON_IMAGE_WIDTH && height <= ICON_IMAGE_HEIGHT) {
|
|
*nheight = height;
|
|
*nwidth = width;
|
|
} else if (width < height) {
|
|
*nheight = ICON_IMAGE_HEIGHT;
|
|
*nwidth = *nheight * width / height;
|
|
} else {
|
|
*nwidth = ICON_IMAGE_WIDTH;
|
|
*nheight = *nwidth * height / width;
|
|
}
|
|
}
|
|
|
|
/* Returns a newly allocated, correctly scaled image */
|
|
static GdkImlibImage *
|
|
get_scaled_image (GdkImlibImage *orig)
|
|
{
|
|
GdkImlibImage *im;
|
|
int width, height;
|
|
|
|
g_return_val_if_fail (orig != NULL, NULL);
|
|
|
|
compute_scaled_size (orig->rgb_width, orig->rgb_height,
|
|
&width, &height);
|
|
im = gdk_imlib_clone_scaled_image (orig, width, height);
|
|
|
|
return im;
|
|
}
|
|
|
|
/* Returns the icon set corresponding to the specified image.
|
|
* If we create a new IconSet, iset->plain is set to a new scaled
|
|
* image, so _WE FREE THE IM PARAMETER_. */
|
|
static IconSet *
|
|
get_icon_set_from_image (GdkImlibImage *im)
|
|
{
|
|
IconSet *iset;
|
|
|
|
g_return_val_if_fail (im != NULL, NULL);
|
|
|
|
iset = g_hash_table_lookup (image_hash, im);
|
|
if (iset)
|
|
return iset;
|
|
|
|
iset = g_new (IconSet, 1);
|
|
iset->plain = get_scaled_image (im);
|
|
iset->symlink = NULL;
|
|
iset->stalled = NULL;
|
|
iset->filename = NULL;
|
|
|
|
|
|
/* Insert the icon information into the hash tables */
|
|
|
|
g_hash_table_remove (image_hash, im);
|
|
g_hash_table_insert (image_hash, iset->plain, iset);
|
|
|
|
gdk_imlib_destroy_image (im);
|
|
|
|
return iset;
|
|
}
|
|
|
|
/* Returns the icon set corresponding to the specified icon filename, or NULL if
|
|
* the file could not be loaded.
|
|
*/
|
|
static IconSet *
|
|
get_icon_set (const char *filename)
|
|
{
|
|
GdkImlibImage *im;
|
|
IconSet *iset;
|
|
|
|
iset = g_hash_table_lookup (name_hash, filename);
|
|
if (iset)
|
|
return iset;
|
|
|
|
im = gdk_imlib_load_image ((char *) filename);
|
|
if (!im)
|
|
return NULL;
|
|
|
|
iset = get_icon_set_from_image (im);
|
|
im = NULL;
|
|
|
|
iset->filename = g_strdup (filename);
|
|
|
|
/* Insert the icon information into the hash tables */
|
|
|
|
g_hash_table_insert (name_hash, iset->filename, iset);
|
|
|
|
return iset;
|
|
}
|
|
|
|
/* Die because the icon installation is wrong */
|
|
static void
|
|
die_with_no_icons (void)
|
|
{
|
|
message (1, _("Error"), _("Default set of icons not found, please check your installation"));
|
|
exit (1);
|
|
}
|
|
|
|
/* Convenience function to load one of the default icons and die if this fails */
|
|
static IconSet *
|
|
get_stock_icon (char *name)
|
|
{
|
|
char *filename;
|
|
IconSet *iset;
|
|
|
|
filename = g_concat_dir_and_file (ICONDIR, name);
|
|
iset = get_icon_set (filename);
|
|
g_free (filename);
|
|
|
|
if (!iset)
|
|
die_with_no_icons ();
|
|
|
|
return iset;
|
|
}
|
|
|
|
/* Convenience function to load one of the default overlays and die if this fails */
|
|
static GdkImlibImage *
|
|
get_stock_overlay (char *name)
|
|
{
|
|
char *filename;
|
|
GdkImlibImage *im;
|
|
|
|
filename = g_concat_dir_and_file (ICONDIR, name);
|
|
im = gdk_imlib_load_image (filename);
|
|
g_free (filename);
|
|
|
|
if (!im)
|
|
die_with_no_icons ();
|
|
|
|
return im;
|
|
|
|
}
|
|
|
|
/**
|
|
* gicon_init:
|
|
* @void:
|
|
*
|
|
* Initializes the icon module.
|
|
**/
|
|
void
|
|
gicon_init (void)
|
|
{
|
|
if (gicon_inited)
|
|
return;
|
|
|
|
gicon_inited = TRUE;
|
|
|
|
name_hash = g_hash_table_new (g_str_hash, g_str_equal);
|
|
image_hash = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
/* Load the default icons */
|
|
|
|
iset_directory = get_stock_icon ("i-directory.png");
|
|
iset_dirclosed = get_stock_icon ("i-dirclosed.png");
|
|
iset_executable = get_stock_icon ("i-executable.png");
|
|
iset_regular = get_stock_icon ("i-regular.png");
|
|
iset_core = get_stock_icon ("i-core.png");
|
|
iset_sock = get_stock_icon ("i-sock.png");
|
|
iset_fifo = get_stock_icon ("i-fifo.png");
|
|
iset_chardev = get_stock_icon ("i-chardev.png");
|
|
iset_blockdev = get_stock_icon ("i-blockdev.png");
|
|
|
|
/* Load the overlay icons */
|
|
|
|
symlink_overlay = get_stock_overlay ("i-symlink.png");
|
|
stalled_overlay = get_stock_overlay ("i-stalled.png");
|
|
|
|
our_uid = getuid ();
|
|
our_gid = getgid ();
|
|
}
|
|
|
|
/* Tries to get an icon from the file's metadata information */
|
|
static GdkImlibImage *
|
|
get_icon_from_metadata (char *filename)
|
|
{
|
|
int size;
|
|
char *buf;
|
|
IconSet *iset = NULL;
|
|
|
|
/* Try the inlined icon */
|
|
|
|
if (gnome_metadata_get (filename, "icon-inline-png", &size, &buf) == 0) {
|
|
GdkImlibImage *im;
|
|
im = gdk_imlib_inlined_png_to_image (buf, size);
|
|
g_free (buf);
|
|
if (im) {
|
|
iset = get_icon_set_from_image (im);
|
|
im = NULL;
|
|
}
|
|
}
|
|
|
|
/* Try the non-inlined icon */
|
|
|
|
if (!iset && gnome_metadata_get (filename, "icon-filename", &size, &buf) == 0) {
|
|
iset = get_icon_set (buf);
|
|
g_free (buf);
|
|
}
|
|
|
|
if (iset) {
|
|
ensure_icon_image (iset, ICON_TYPE_PLAIN);
|
|
return iset->plain;
|
|
}
|
|
|
|
return NULL; /* nothing is available */
|
|
}
|
|
|
|
/* Returns whether we are in the specified group or not */
|
|
static int
|
|
we_are_in_group (gid_t gid)
|
|
{
|
|
gid_t *groups;
|
|
int ngroups, i;
|
|
int retval;
|
|
|
|
if (our_gid == gid)
|
|
return TRUE;
|
|
|
|
ngroups = getgroups (0, NULL);
|
|
if (ngroups == -1 || ngroups == 0)
|
|
return FALSE;
|
|
|
|
groups = g_new (gid_t, ngroups);
|
|
ngroups = getgroups (ngroups, groups);
|
|
if (ngroups == -1) {
|
|
g_free (groups);
|
|
return FALSE;
|
|
}
|
|
|
|
retval = FALSE;
|
|
|
|
for (i = 0; i < ngroups; i++)
|
|
if (groups[i] == gid)
|
|
retval = TRUE;
|
|
|
|
g_free (groups);
|
|
return retval;
|
|
}
|
|
|
|
/* Returns whether we can access the contents of directory specified by the file entry */
|
|
static int
|
|
can_access_directory (file_entry *fe)
|
|
{
|
|
mode_t test_mode;
|
|
|
|
if (fe->buf.st_uid == our_uid)
|
|
test_mode = S_IRUSR | S_IXUSR;
|
|
else if (we_are_in_group (fe->buf.st_gid))
|
|
test_mode = S_IRGRP | S_IXGRP;
|
|
else
|
|
test_mode = S_IROTH | S_IXOTH;
|
|
|
|
return (fe->buf.st_mode & test_mode) == test_mode;
|
|
}
|
|
|
|
/* This is the last resort for getting a file's icon. It uses the file mode
|
|
* bits or a hardcoded name.
|
|
*/
|
|
static IconSet *
|
|
get_default_icon (file_entry *fe)
|
|
{
|
|
mode_t mode = fe->buf.st_mode;
|
|
|
|
if (S_ISSOCK (mode))
|
|
return iset_sock;
|
|
|
|
if (S_ISCHR (mode))
|
|
return iset_chardev;
|
|
|
|
if (S_ISBLK (mode))
|
|
return iset_blockdev;
|
|
|
|
if (S_ISFIFO (mode))
|
|
return iset_fifo;
|
|
|
|
if (is_exe (mode))
|
|
return iset_executable;
|
|
|
|
if (!strcmp (fe->fname, "core") || !strcmp (extension (fe->fname), "core"))
|
|
return iset_core;
|
|
|
|
return iset_regular; /* boooo */
|
|
}
|
|
|
|
/**
|
|
* gicon_get_icon_for_file:
|
|
* @directory: The directory on which the file resides.
|
|
* @fe: The file entry that represents the file.
|
|
* @do_quick: Whether the function should try to use (expensive) metadata information.
|
|
*
|
|
* Returns the appropriate icon for the specified file.
|
|
*
|
|
* Return value: The icon for the specified file.
|
|
**/
|
|
GdkImlibImage *
|
|
gicon_get_icon_for_file (char *directory, file_entry *fe, gboolean do_quick)
|
|
{
|
|
IconSet *iset;
|
|
mode_t mode;
|
|
const char *mime_type;
|
|
gboolean is_user_set = FALSE;
|
|
|
|
g_return_val_if_fail (directory != NULL, NULL);
|
|
g_return_val_if_fail (fe != NULL, NULL);
|
|
|
|
gicon_init ();
|
|
|
|
mode = fe->buf.st_mode;
|
|
|
|
/* 1. First try the user's settings */
|
|
|
|
if (!do_quick || we_can_afford_the_speed) {
|
|
char *full_name;
|
|
GdkImlibImage *im;
|
|
|
|
full_name = g_concat_dir_and_file (directory, fe->fname);
|
|
im = get_icon_from_metadata (full_name);
|
|
g_free (full_name);
|
|
|
|
if (im) {
|
|
iset = get_icon_set_from_image (im);
|
|
im = NULL;
|
|
is_user_set = TRUE;
|
|
goto add_link;
|
|
}
|
|
}
|
|
|
|
/* 2. Before we do anything else, make sure the
|
|
* pointed-to file exists if a link */
|
|
|
|
if (S_ISLNK (mode) && fe->f.stalled_link) {
|
|
const char *icon_name;
|
|
|
|
icon_name = gnome_unconditional_pixmap_file ("gnome-warning.png");
|
|
if (icon_name) {
|
|
iset = get_icon_set (icon_name);
|
|
if (iset)
|
|
goto add_link;
|
|
}
|
|
}
|
|
|
|
/* 3. See if it is a directory */
|
|
|
|
if (S_ISDIR (mode)) {
|
|
if (can_access_directory (fe))
|
|
iset = iset_directory;
|
|
else
|
|
iset = iset_dirclosed;
|
|
|
|
goto add_link;
|
|
}
|
|
|
|
/* 4. Try MIME-types */
|
|
|
|
if (use_magic) {
|
|
char *full_name;
|
|
full_name = g_concat_dir_and_file (directory, fe->fname);
|
|
mime_type = gnome_mime_type_or_default_of_file (full_name, NULL);
|
|
g_free (full_name);
|
|
}
|
|
else
|
|
mime_type = gnome_mime_type_or_default (fe->fname, NULL);
|
|
|
|
if (mime_type) {
|
|
const char *icon_name;
|
|
|
|
icon_name = gnome_mime_get_value (mime_type, "icon-filename");
|
|
if (icon_name) {
|
|
iset = get_icon_set (icon_name);
|
|
if (iset)
|
|
goto add_link;
|
|
}
|
|
}
|
|
|
|
/* 5. Get an icon from the file mode */
|
|
|
|
iset = get_default_icon (fe);
|
|
|
|
add_link:
|
|
|
|
g_assert (iset != NULL);
|
|
|
|
if (S_ISLNK (mode)) {
|
|
if (fe->f.link_to_dir && !is_user_set)
|
|
iset = iset_directory;
|
|
|
|
if (fe->f.stalled_link) {
|
|
ensure_icon_image (iset, ICON_TYPE_STALLED);
|
|
return iset->stalled;
|
|
} else {
|
|
ensure_icon_image (iset, ICON_TYPE_SYMLINK);
|
|
return iset->symlink;
|
|
}
|
|
} else {
|
|
ensure_icon_image (iset, ICON_TYPE_PLAIN);
|
|
return iset->plain;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gicon_get_filename_for_icon:
|
|
* @image: An icon image loaded by the icon module.
|
|
*
|
|
* Queries the icon database for the icon filename that corresponds to the
|
|
* specified image.
|
|
*
|
|
* Return value: The filename that contains the icon for the specified image.
|
|
**/
|
|
const char *
|
|
gicon_get_filename_for_icon (GdkImlibImage *image)
|
|
{
|
|
IconSet *iset;
|
|
|
|
g_return_val_if_fail (image != NULL, NULL);
|
|
|
|
gicon_init ();
|
|
|
|
iset = g_hash_table_lookup (image_hash, image);
|
|
g_assert (iset != NULL);
|
|
return iset->filename;
|
|
}
|