mirror of https://github.com/MidnightCommander/mc
539 lines
12 KiB
C
539 lines
12 KiB
C
/* Icon loading support for the Midnight Commander
|
|
*
|
|
* Copyright (C) 1998-2000 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);
|
|
|
|
g_return_val_if_fail(plain, NULL);
|
|
g_return_val_if_fail(overlay, NULL);
|
|
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;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
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);
|
|
|
|
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");
|
|
|
|
if (!iset_directory || !iset_dirclosed || !iset_executable ||
|
|
!iset_regular || !iset_core || !iset_fifo || !iset_chardev ||
|
|
!iset_blockdev || !symlink_overlay || !stalled_overlay) {
|
|
message (1, _("Error"), _("Default set of icons not found, check your installation"));
|
|
exit(1);
|
|
}
|
|
|
|
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 (!S_ISLNK (mode) && 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_return_val_if_fail (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;
|
|
}
|