From 1a3ba2124560c273410975a28160622266d679a5 Mon Sep 17 00:00:00 2001 From: o9000 Date: Wed, 28 Jan 2015 14:47:39 +0000 Subject: [PATCH] Launcher: SVG icon support and icon loading improvements git-svn-id: http://tint2.googlecode.com/svn/trunk@670 121b4492-b84c-0410-8b4c-0d4edfb3f3cc --- AUTHORS | 2 +- CMakeLists.txt | 31 +- src/launcher/apps-common.c | 187 +++++++ src/launcher/apps-common.h | 29 ++ src/launcher/icon-theme-common.c | 661 +++++++++++++++++++++++++ src/launcher/icon-theme-common.h | 45 ++ src/launcher/launcher.c | 815 ++++--------------------------- src/launcher/launcher.h | 32 +- src/panel.c | 4 +- 9 files changed, 1049 insertions(+), 757 deletions(-) create mode 100644 src/launcher/apps-common.c create mode 100644 src/launcher/apps-common.h create mode 100644 src/launcher/icon-theme-common.c create mode 100644 src/launcher/icon-theme-common.h diff --git a/AUTHORS b/AUTHORS index da54861..ae4cb73 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,5 +16,5 @@ Contributors: James Buren : Frugalware package Pierre-Emmanuel Andre : openbsd port Redroar : arch package - + Rene Garcia : launcher SVG support diff --git a/CMakeLists.txt b/CMakeLists.txt index 8752808..82cb8eb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,9 @@ pkg_check_modules( CAIRO REQUIRED cairo ) pkg_check_modules( GLIB2 REQUIRED glib-2.0 ) pkg_check_modules( GOBJECT2 REQUIRED gobject-2.0 ) pkg_check_modules( IMLIB2 REQUIRED imlib2>=1.4.2 ) +pkg_check_modules( RSVG librsvg-2.0>=2.36.0 ) pkg_check_modules( SN libstartup-notification-1.0>=0.12 ) + find_library( RT_LIBRARY rt ) if( NOT X11_FOUND OR NOT PANGOCAIRO_FOUND OR NOT PANGO_FOUND OR NOT CAIRO_FOUND OR NOT GLIB2_FOUND OR NOT GOBJECT2_FOUND OR NOT IMLIB2_FOUND ) @@ -40,7 +42,8 @@ include_directories( ${PROJECT_BINARY_DIR} ${GLIB2_INCLUDE_DIRS} ${GOBJECT2_INCLUDE_DIRS} ${IMLIB2_INCLUDE_DIRS} - ${SN_INCLUDE_DIRS} ) + ${RSVG_INCLUDE_DIRS} + ${SN_INCLUDE_DIRS} ) set( SOURCES src/config.c src/panel.c @@ -49,6 +52,8 @@ set( SOURCES src/config.c src/clock/clock.c src/systray/systraybar.c src/launcher/launcher.c + src/launcher/apps-common.c + src/launcher/icon-theme-common.c src/launcher/xsettings-client.c src/launcher/xsettings-common.c src/taskbar/task.c @@ -63,6 +68,19 @@ set( SOURCES src/config.c option( ENABLE_BATTERY "Enable battery status plugin" ON ) option( ENABLE_TINT2CONF "Enable tint2conf build, a GTK+2 theme switcher for tint2" ON ) option( ENABLE_EXAMPLES "Install additional tin2rc examples" OFF ) +option( ENABLE_RSVG "Rsvg support (launcher only)" ON ) + +if( ENABLE_BATTERY ) + set( SOURCES ${SOURCES} src/battery/battery.c ) + add_definitions( -DENABLE_BATTERY ) +endif( ENABLE_BATTERY ) + +if( ENABLE_RSVG ) + if( RSVG_FOUND ) + add_definitions( -DHAVE_RSVG ) + endif( RSVG_FOUND ) +endif( ENABLE_RSVG ) + option( ENABLE_SN "Startup notification support" ON ) if( ENABLE_SN ) if( SN_FOUND ) @@ -70,11 +88,6 @@ if( ENABLE_SN ) endif( SN_FOUND ) endif( ENABLE_SN) -if( ENABLE_BATTERY ) - set( SOURCES ${SOURCES} src/battery/battery.c ) - add_definitions( -DENABLE_BATTERY ) -endif( ENABLE_BATTERY ) - set( MANDIR share/man CACHE PATH "Directory for man pages" ) set( DATADIR share CACHE PATH "Directory for shared data" ) set( SYSCONFDIR /etc CACHE PATH "Directory for configuration files" ) @@ -95,7 +108,8 @@ link_directories( ${X11_LIBRARY_DIRS} ${GLIB2_LIBRARY_DIRS} ${GOBJECT2_LIBRARY_DIRS} ${IMLIB2_LIBRARY_DIRS} - ${SN_LIBRARY_DIRS} ) + ${RSVG_LIBRARY_DIRS} + ${SN_LIBRARY_DIRS} ) add_executable(tint2 ${SOURCES}) target_link_libraries( tint2 ${X11_LIBRARIES} ${PANGOCAIRO_LIBRARIES} @@ -104,7 +118,8 @@ target_link_libraries( tint2 ${X11_LIBRARIES} ${GLIB2_LIBRARIES} ${GOBJECT2_LIBRARIES} ${IMLIB2_LIBRARIES} - ${SN_LIBRARIES} ) + ${RSVG_LIBRARIES} + ${SN_LIBRARIES} ) if( RT_LIBRARY ) target_link_libraries( tint2 ${RT_LIBRARY} ) endif( RT_LIBRARY ) diff --git a/src/launcher/apps-common.c b/src/launcher/apps-common.c new file mode 100644 index 0000000..6508e14 --- /dev/null +++ b/src/launcher/apps-common.c @@ -0,0 +1,187 @@ +/************************************************************************** +* Tint2 : .desktop file handling +* +* Copyright (C) 2015 (mrovi9000@gmail.com) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License version 2 +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**************************************************************************/ + +/* http://standards.freedesktop.org/desktop-entry-spec/ */ + +#include "apps-common.h" + +#include +#include +#include +#include + +int parse_dektop_line(char *line, char **key, char **value) +{ + char *p; + int found = 0; + *key = line; + for (p = line; *p; p++) { + if (*p == '=') { + *value = p + 1; + *p = 0; + found = 1; + break; + } + } + if (!found) + return 0; + if (found && (strlen(*key) == 0 || strlen(*value) == 0)) + return 0; + return 1; +} + +void expand_exec(DesktopEntry *entry, const char *path) +{ + // Expand % in exec + // %i -> --icon Icon + // %c -> Name + // %k -> path + if (entry->exec) { + char *exec2 = malloc(strlen(entry->exec) + (entry->name ? strlen(entry->name) : 1) + (entry->icon ? strlen(entry->icon) : 1) + 100); + char *p, *q; + // p will never point to an escaped char + for (p = entry->exec, q = exec2; *p; p++, q++) { + *q = *p; // Copy + if (*p == '\\') { + p++, q++; + // Copy the escaped char + if (*p == '%') // For % we delete the backslash, i.e. write % over it + q--; + *q = *p; + if (!*p) break; + continue; + } + if (*p == '%') { + p++; + if (!*p) break; + if (*p == 'i' && entry->icon != NULL) { + sprintf(q, "--icon '%s'", entry->icon); + q += strlen("--icon ''"); + q += strlen(entry->icon); + q--; // To balance the q++ in the for + } else if (*p == 'c' && entry->name != NULL) { + sprintf(q, "'%s'", entry->name); + q += strlen("''"); + q += strlen(entry->name); + q--; // To balance the q++ in the for + } else if (*p == 'c') { + sprintf(q, "'%s'", path); + q += strlen("''"); + q += strlen(path); + q--; // To balance the q++ in the for + } else { + // We don't care about other expansions + q--; // Delete the last % from q + } + continue; + } + } + *q = '\0'; + free(entry->exec); + entry->exec = exec2; + } +} + +int read_desktop_file(const char *path, DesktopEntry *entry) +{ + FILE *fp; + char *line = NULL; + size_t line_size; + char *key, *value; + int i; + + entry->name = entry->icon = entry->exec = NULL; + + if ((fp = fopen(path, "rt")) == NULL) { + fprintf(stderr, "Could not open file %s\n", path); + return 0; + } + + gchar **languages = (gchar **)g_get_language_names(); + // lang_index is the index of the language for the best Name key in the language vector + // lang_index_default is a constant that encodes the Name key without a language + int lang_index, lang_index_default; +#define LANG_DBG 0 + if (LANG_DBG) printf("Languages:"); + for (i = 0; languages[i]; i++) { + if (LANG_DBG) printf(" %s", languages[i]); + } + if (LANG_DBG) printf("\n"); + lang_index_default = i; + // we currently do not know about any Name key at all, so use an invalid index + lang_index = lang_index_default + 1; + + int inside_desktop_entry = 0; + while (getline(&line, &line_size, fp) >= 0) { + int len = strlen(line); + if (len == 0) + continue; + line[len - 1] = '\0'; + if (line[0] == '[') { + inside_desktop_entry = (strcmp(line, "[Desktop Entry]") == 0); + } + if (inside_desktop_entry && parse_dektop_line(line, &key, &value)) { + if (strstr(key, "Name") == key) { + if (strcmp(key, "Name") == 0 && lang_index > lang_index_default) { + entry->name = strdup(value); + lang_index = lang_index_default; + } else { + for (i = 0; languages[i] && i < lang_index; i++) { + gchar *localized_key = g_strdup_printf("Name[%s]", languages[i]); + if (strcmp(key, localized_key) == 0) { + if (entry->name) + free(entry->name); + entry->name = strdup(value); + lang_index = i; + } + g_free(localized_key); + } + } + } else if (!entry->exec && strcmp(key, "Exec") == 0) { + entry->exec = strdup(value); + } else if (!entry->icon && strcmp(key, "Icon") == 0) { + entry->icon = strdup(value); + } + } + } + fclose (fp); + // From this point: + // entry->name, entry->icon, entry->exec will never be empty strings (can be NULL though) + + expand_exec(entry, path); + + free(line); + return 1; +} + +void free_desktop_entry(DesktopEntry *entry) +{ + free(entry->name); + free(entry->icon); + free(entry->exec); + entry->name = entry->icon = entry->exec = NULL; +} + +void test_read_desktop_file() +{ + fprintf(stdout, "\033[1;33m"); + DesktopEntry entry; + read_desktop_file("/usr/share/applications/firefox.desktop", &entry); + printf("Name:%s Icon:%s Exec:%s\n", entry.name, entry.icon, entry.exec); + fprintf(stdout, "\033[0m"); +} diff --git a/src/launcher/apps-common.h b/src/launcher/apps-common.h new file mode 100644 index 0000000..ae70ee7 --- /dev/null +++ b/src/launcher/apps-common.h @@ -0,0 +1,29 @@ +/************************************************************************** + * Copyright (C) 2015 (mrovi9000@gmail.com) + * + * + **************************************************************************/ + +#ifndef APPS_COMMON_H +#define APPS_COMMON_H + +typedef struct DesktopEntry { + char *name; + char *exec; + char *icon; +} DesktopEntry; + +// Parses a line of the form "key = value". Modifies the line. +// Returns 1 if successful, and parts are not empty. +// Key and value point to the parts. +int parse_dektop_line(char *line, char **key, char **value); + +// Reads the .desktop file from the given path into the DesktopEntry entry. +// The DesktopEntry object must be initially empty. +// Returns 1 if successful. +int read_desktop_file(const char *path, DesktopEntry *entry); + +// Empties DesktopEntry: releases the memory of the *members* of entry. +void free_desktop_entry(DesktopEntry *entry); + +#endif diff --git a/src/launcher/icon-theme-common.c b/src/launcher/icon-theme-common.c new file mode 100644 index 0000000..21e6e4f --- /dev/null +++ b/src/launcher/icon-theme-common.c @@ -0,0 +1,661 @@ +/************************************************************************** +* Tint2 : Icon theme handling +* +* Copyright (C) 2015 (mrovi9000@gmail.com) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License version 2 +* as published by the Free Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +**************************************************************************/ + +/* http://standards.freedesktop.org/icon-theme-spec/ */ + +#include "icon-theme-common.h" + +#include +#include +#include + +#include "apps-common.h" + +#define ICON_DIR_TYPE_SCALABLE 0 +#define ICON_DIR_TYPE_FIXED 1 +#define ICON_DIR_TYPE_THRESHOLD 2 +typedef struct IconThemeDir { + char *name; + int size; + int type; + int max_size; + int min_size; + int threshold; +} IconThemeDir; + + +int parse_theme_line(char *line, char **key, char **value) +{ + return parse_dektop_line(line, key, value); +} + +GSList *icon_locations = NULL; +// Do not free the result. +const GSList *get_icon_locations() +{ + if (icon_locations) + return icon_locations; + + gchar *path; + path = g_build_filename(g_get_home_dir(), ".icons", NULL); + icon_locations = g_slist_append(icon_locations, g_strdup(path)); + g_free(path); + path = g_build_filename(g_get_home_dir(), ".local/share/icons", NULL); + icon_locations = g_slist_append(icon_locations, g_strdup(path)); + g_free(path); + icon_locations = g_slist_append(icon_locations, g_strdup("/usr/local/share/icons")); + icon_locations = g_slist_append(icon_locations, g_strdup("/usr/local/share/pixmaps")); + icon_locations = g_slist_append(icon_locations, g_strdup("/usr/share/icons")); + icon_locations = g_slist_append(icon_locations, g_strdup("/usr/share/pixmaps")); + icon_locations = g_slist_append(icon_locations, g_strdup("/opt/share/icons")); + icon_locations = g_slist_append(icon_locations, g_strdup("/opt/share/pixmaps")); + return icon_locations; +} + +IconTheme *make_theme(char *name) +{ + IconTheme *theme = calloc(1, sizeof(IconTheme)); + theme->name = strdup(name); + theme->list_inherits = NULL; + theme->list_directories = NULL; + return theme; +} + +//TODO Use UTF8 when parsing the file +IconTheme *load_theme_from_index(char *file_name, char *name) +{ + IconTheme *theme; + FILE *f; + char *line = NULL; + size_t line_size; + + if ((f = fopen(file_name, "rt")) == NULL) { + fprintf(stderr, "Could not open theme '%s'\n", file_name); + return NULL; + } + + theme = make_theme(name); + + IconThemeDir *current_dir = NULL; + int inside_header = 1; + while (getline(&line, &line_size, f) >= 0) { + char *key, *value; + + int line_len = strlen(line); + if (line_len >= 1) { + if (line[line_len - 1] == '\n') { + line[line_len - 1] = '\0'; + line_len--; + } + } + + if (line_len == 0) + continue; + + if (inside_header) { + if (parse_theme_line(line, &key, &value)) { + if (strcmp(key, "Inherits") == 0) { + // value is like oxygen,wood,default + char *token; + token = strtok(value, ",\n"); + while (token != NULL) { + theme->list_inherits = g_slist_append(theme->list_inherits, strdup(token)); + token = strtok(NULL, ",\n"); + } + } else if (strcmp(key, "Directories") == 0) { + // value is like 48x48/apps,48x48/mimetypes,32x32/apps,scalable/apps,scalable/mimetypes + char *token; + token = strtok(value, ",\n"); + while (token != NULL) { + IconThemeDir *dir = calloc(1, sizeof(IconThemeDir)); + dir->name = strdup(token); + dir->max_size = dir->min_size = dir->size = -1; + dir->type = ICON_DIR_TYPE_THRESHOLD; + dir->threshold = 2; + theme->list_directories = g_slist_append(theme->list_directories, dir); + token = strtok(NULL, ",\n"); + } + } + } + } else if (current_dir != NULL) { + if (parse_theme_line(line, &key, &value)) { + if (strcmp(key, "Size") == 0) { + // value is like 24 + sscanf(value, "%d", ¤t_dir->size); + if (current_dir->max_size == -1) + current_dir->max_size = current_dir->size; + if (current_dir->min_size == -1) + current_dir->min_size = current_dir->size; + } else if (strcmp(key, "MaxSize") == 0) { + // value is like 24 + sscanf(value, "%d", ¤t_dir->max_size); + } else if (strcmp(key, "MinSize") == 0) { + // value is like 24 + sscanf(value, "%d", ¤t_dir->min_size); + } else if (strcmp(key, "Threshold") == 0) { + // value is like 2 + sscanf(value, "%d", ¤t_dir->threshold); + } else if (strcmp(key, "Type") == 0) { + // value is Fixed, Scalable or Threshold : default to scalable for unknown Type. + if (strcmp(value, "Fixed") == 0) { + current_dir->type = ICON_DIR_TYPE_FIXED; + } else if (strcmp(value, "Threshold") == 0) { + current_dir->type = ICON_DIR_TYPE_THRESHOLD; + } else { + current_dir->type = ICON_DIR_TYPE_SCALABLE; + } + } + } + } + + if (line[0] == '[' && line[line_len - 1] == ']' && strcmp(line, "[Icon Theme]") != 0) { + inside_header = 0; + current_dir = NULL; + line[line_len - 1] = '\0'; + char *dir_name = line + 1; + GSList* dir_item = theme->list_directories; + while (dir_item != NULL) + { + IconThemeDir *dir = dir_item->data; + if (strcmp(dir->name, dir_name) == 0) { + current_dir = dir; + break; + } + dir_item = g_slist_next(dir_item); + } + } + } + fclose(f); + + free(line); + return theme; +} + +void load_theme_from_fs_dir(IconTheme *theme, char *dir_name) +{ + gchar *file_name = g_build_filename(dir_name, "index.theme", NULL); + if (g_file_test(file_name, G_FILE_TEST_EXISTS)) { + g_free(file_name); + return; + } + + GDir *d = g_dir_open(dir_name, 0, NULL); + if (d) { + const gchar *size_name; + while ((size_name = g_dir_read_name(d))) { + gchar *full_size_name = g_build_filename(dir_name, size_name, NULL); + if (g_file_test(file_name, G_FILE_TEST_IS_DIR)) { + int size, size2; + if ((sscanf(size_name, "%dx%d", &size, &size2) == 2 && size == size2) || + (sscanf(size_name, "%d", &size) == 1)) { + GDir *dSize = g_dir_open(full_size_name, 0, NULL); + if (dSize) { + const gchar *subdir_name; + while ((subdir_name = g_dir_read_name(dSize))) { + IconThemeDir *dir = calloc(1, sizeof(IconThemeDir)); + // value is like 48x48/apps + gchar *value = g_build_filename(size_name, subdir_name, NULL); + dir->name = strdup(value); + g_free(value); + dir->max_size = dir->min_size = dir->size = size; + dir->type = ICON_DIR_TYPE_FIXED; + theme->list_directories = g_slist_append(theme->list_directories, dir); + } + g_dir_close(dSize); + } + } + } + g_free(full_size_name); + } + g_dir_close(d); + } +} + +IconTheme *load_theme_from_fs(char *name, IconTheme *theme) +{ + char *dir_name = NULL; + const GSList *location; + for (location = get_icon_locations(); location; location = g_slist_next(location)) { + gchar *path = (gchar*) location->data; + dir_name = g_build_filename(path, name, NULL); + if (g_file_test(dir_name, G_FILE_TEST_IS_DIR)) { + if (!theme) { + theme = make_theme(name); + } + load_theme_from_fs_dir(theme, dir_name); + } + g_free(dir_name); + dir_name = NULL; + } + + return theme; +} + +IconTheme *load_theme(char *name) +{ + // Look for name/index.theme in $HOME/.icons, /usr/share/icons, /usr/share/pixmaps (stop at the first found) + // Parse index.theme -> list of IconThemeDir with attributes + // Return IconTheme* + + if (name == NULL) + return NULL; + + char *file_name = NULL; + const GSList *location; + for (location = get_icon_locations(); location; location = g_slist_next(location)) { + gchar *path = (gchar*) location->data; + file_name = g_build_filename(path, name, "index.theme", NULL); + if (!g_file_test(file_name, G_FILE_TEST_EXISTS)) { + g_free(file_name); + file_name = NULL; + } + if (file_name) + break; + } + + IconTheme *theme = NULL; + if (file_name) { + theme = load_theme_from_index(file_name, name); + g_free(file_name); + } + + return load_theme_from_fs(name, theme); +} + +void free_icon_theme(IconTheme *theme) +{ + if (!theme) + return; + free(theme->name); + theme->name = NULL; + GSList *l_inherits; + for (l_inherits = theme->list_inherits; l_inherits ; l_inherits = l_inherits->next) { + free(l_inherits->data); + } + g_slist_free(theme->list_inherits); + theme->list_inherits = NULL; + GSList *l_dir; + for (l_dir = theme->list_directories; l_dir ; l_dir = l_dir->next) { + IconThemeDir *dir = (IconThemeDir *)l_dir->data; + free(dir->name); + free(l_dir->data); + } + g_slist_free(theme->list_directories); + theme->list_directories = NULL; +} + +void free_themes(IconThemeWrapper *themes) +{ + if (!themes) + return; + GSList *l; + for (l = themes->themes; l ; l = l->next) { + IconTheme *theme = (IconTheme*) l->data; + free_icon_theme(theme); + free(theme); + } + g_slist_free(themes->themes); + for (l = themes->themes_fallback; l ; l = l->next) { + IconTheme *theme = (IconTheme*) l->data; + free_icon_theme(theme); + free(theme); + } + g_slist_free(themes->themes_fallback); + free(themes); +} + +void test_launcher_read_theme_file() +{ + fprintf(stdout, "\033[1;33m"); + IconTheme *theme = load_theme("oxygen"); + if (!theme) { + printf("Could not load theme\n"); + return; + } + printf("Loaded theme: %s\n", theme->name); + GSList* item = theme->list_inherits; + while (item != NULL) + { + printf("Inherits:%s\n", (char*)item->data); + item = g_slist_next(item); + } + item = theme->list_directories; + while (item != NULL) + { + IconThemeDir *dir = item->data; + printf("Dir:%s Size=%d MinSize=%d MaxSize=%d Threshold=%d Type=%s\n", + dir->name, dir->size, dir->min_size, dir->max_size, dir->threshold, + dir->type == ICON_DIR_TYPE_FIXED ? "Fixed" : + dir->type == ICON_DIR_TYPE_SCALABLE ? "Scalable" : + dir->type == ICON_DIR_TYPE_THRESHOLD ? "Threshold" : "?????"); + item = g_slist_next(item); + } + fprintf(stdout, "\033[0m"); +} + +gboolean str_list_contains(const GSList *list, const char *value) +{ + const GSList* item = list; + while (item != NULL) { + if (g_str_equal(item->data, value)) { + return TRUE; + } + item = g_slist_next(item); + } + return FALSE; +} + +void load_themes_helper(const char *name, GSList **themes, GSList **queued) +{ + if (str_list_contains(*queued, name)) + return; + GSList *queue = g_slist_append(NULL, strdup(name)); + *queued = g_slist_append(*queued, strdup(name)); + + // Load wrapper->themes + while (queue) { + char *name = queue->data; + queue = g_slist_remove(queue, name); + + fprintf(stderr, " '%s',", name); + IconTheme *theme = load_theme(name); + if (theme != NULL) { + *themes = g_slist_append(*themes, theme); + + GSList* item = theme->list_inherits; + int pos = 0; + while (item != NULL) + { + char *parent = item->data; + if (!str_list_contains(*queued, parent)) { + queue = g_slist_insert(queue, strdup(parent), pos); + pos++; + *queued = g_slist_append(*queued, strdup(parent)); + } + item = g_slist_next(item); + } + } + + free(name); + } + fprintf(stderr, "\n"); + fflush(stderr); + + // Free the queue + GSList *l; + for (l = queue; l ; l = l->next) + free(l->data); + g_slist_free(queue); +} + +IconThemeWrapper *load_themes(const char *icon_theme_name) +{ + IconThemeWrapper *wrapper = calloc(1, sizeof(IconThemeWrapper)); + + if (!icon_theme_name) { + fprintf(stderr, "Missing icon_theme_name theme, default to 'hicolor'.\n"); + icon_theme_name = "hicolor"; + } else { + fprintf(stderr, "Loading %s. Icon theme :", icon_theme_name); + } + + GSList *queued = NULL; + load_themes_helper(icon_theme_name, &wrapper->themes, &queued); + load_themes_helper("hicolor", &wrapper->themes, &queued); + + // Load wrapper->themes_fallback + const GSList *location; + for (location = get_icon_locations(); location; location = g_slist_next(location)) { + gchar *path = (gchar*) location->data; + GDir *d = g_dir_open(path, 0, NULL); + if (d) { + const gchar *name; + while ((name = g_dir_read_name(d))) { + gchar *file_name = g_build_filename(path, name, "index.theme", NULL); + if (g_file_test(file_name, G_FILE_TEST_EXISTS) && + g_file_test(file_name, G_FILE_TEST_IS_REGULAR)) { + load_themes_helper(name, &wrapper->themes_fallback, &queued); + } + g_free(file_name); + } + g_dir_close(d); + } + } + + // Free the queued list + GSList *l; + for (l = queued; l ; l = l->next) + free(l->data); + g_slist_free(queued); + + return wrapper; +} + +int directory_matches_size(IconThemeDir *dir, int size) +{ + if (dir->type == ICON_DIR_TYPE_FIXED) { + return dir->size == size; + } else if (dir->type == ICON_DIR_TYPE_SCALABLE) { + return dir->min_size <= size && size <= dir->max_size; + } else /*if (dir->type == ICON_DIR_TYPE_THRESHOLD)*/ { + return dir->size - dir->threshold <= size && size <= dir->size + dir->threshold; + } +} + +int directory_size_distance(IconThemeDir *dir, int size) +{ + if (dir->type == ICON_DIR_TYPE_FIXED) { + return abs(dir->size - size); + } else if (dir->type == ICON_DIR_TYPE_SCALABLE) { + if (size < dir->min_size) { + return dir->min_size - size; + } else if (size > dir->max_size) { + return size - dir->max_size; + } else { + return 0; + } + } else /*if (dir->type == ICON_DIR_TYPE_THRESHOLD)*/ { + if (size < dir->size - dir->threshold) { + return dir->min_size - size; + } else if (size > dir->size + dir->threshold) { + return size - dir->max_size; + } else { + return 0; + } + } +} + +gint compare_theme_directories(gconstpointer a, gconstpointer b, gpointer size_query) +{ + int size = GPOINTER_TO_INT(size_query); + const IconThemeDir *da = (const IconThemeDir*)a; + const IconThemeDir *db = (const IconThemeDir*)b; + return abs(da->size - size) - abs(db->size - size); +} + +#define DEBUG_ICON_SEARCH 0 +char *get_icon_path_helper(GSList *themes, const char *icon_name, int size) +{ + if (icon_name == NULL) + return NULL; + + // If the icon_name is already a path and the file exists, return it + if (strstr(icon_name, "/") == icon_name) { + if (g_file_test(icon_name, G_FILE_TEST_EXISTS)) + return strdup(icon_name); + else + return NULL; + } + + const GSList *basenames = get_icon_locations(); + GSList *extensions = NULL; + extensions = g_slist_append(extensions, ".png"); + extensions = g_slist_append(extensions, ".xpm"); +#ifdef HAVE_RSVG + extensions = g_slist_append(extensions, ".svg"); +#endif + // if the icon name already contains one of the extensions (e.g. vlc.png instead of vlc) add a special entry + GSList *ext; + for (ext = extensions; ext; ext = g_slist_next(ext)) { + char *extension = (char*) ext->data; + if (strlen(icon_name) > strlen(extension) && + strcmp(extension, icon_name + strlen(icon_name) - strlen(extension)) == 0) { + extensions = g_slist_append(extensions, ""); + break; + } + } + + GSList *theme; + + // Best size match + // Contrary to the freedesktop spec, we are not choosing the closest icon in size, but the next larger icon + // otherwise the quality is usually crap (for size 22, if you can choose 16 or 32, you're better with 32) + // We do fallback to the closest size if we cannot find a larger or equal icon + + // These 3 variables are used for keeping the closest size match + int minimal_size = INT_MAX; + char *best_file_name = NULL; + GSList *best_file_theme = NULL; + + // These 3 variables are used for keeping the next larger match + int next_larger_size = -1; + char *next_larger = NULL; + GSList *next_larger_theme = NULL; + + for (theme = themes; theme; theme = g_slist_next(theme)) { + ((IconTheme*)theme->data)->list_directories = g_slist_sort_with_data(((IconTheme*)theme->data)->list_directories, + compare_theme_directories, + GINT_TO_POINTER(size)); + GSList *dir; + for (dir = ((IconTheme*)theme->data)->list_directories; dir; dir = g_slist_next(dir)) { + // Closest match + gboolean possible = directory_size_distance((IconThemeDir*)dir->data, size) < minimal_size && + (!best_file_theme ? TRUE : theme == best_file_theme); + // Next larger match + possible = possible || + (((IconThemeDir*)dir->data)->size >= size && + (next_larger_size == -1 || ((IconThemeDir*)dir->data)->size < next_larger_size) && + (!next_larger_theme ? 1 : theme == next_larger_theme)); + if (!possible) + continue; + const GSList *base; + for (base = basenames; base; base = g_slist_next(base)) { + GSList *ext; + for (ext = extensions; ext; ext = g_slist_next(ext)) { + char *base_name = (char*) base->data; + char *theme_name = ((IconTheme*)theme->data)->name; + char *dir_name = ((IconThemeDir*)dir->data)->name; + char *extension = (char*) ext->data; + char *file_name = malloc(strlen(base_name) + strlen(theme_name) + + strlen(dir_name) + strlen(icon_name) + strlen(extension) + 100); + // filename = directory/$(themename)/subdirectory/iconname.extension + sprintf(file_name, "%s/%s/%s/%s%s", base_name, theme_name, dir_name, icon_name, extension); + if (DEBUG_ICON_SEARCH) + printf("checking %s\n", file_name); + if (g_file_test(file_name, G_FILE_TEST_EXISTS)) { + if (DEBUG_ICON_SEARCH) + printf("found: %s\n", file_name); + // Closest match + if (directory_size_distance((IconThemeDir*)dir->data, size) < minimal_size && (!best_file_theme ? 1 : theme == best_file_theme)) { + if (best_file_name) { + free(best_file_name); + best_file_name = NULL; + } + best_file_name = strdup(file_name); + minimal_size = directory_size_distance((IconThemeDir*)dir->data, size); + best_file_theme = theme; + if (DEBUG_ICON_SEARCH) + printf("best_file_name = %s; minimal_size = %d\n", best_file_name, minimal_size); + } + // Next larger match + if (((IconThemeDir*)dir->data)->size >= size && + (next_larger_size == -1 || ((IconThemeDir*)dir->data)->size < next_larger_size) && + (!next_larger_theme ? 1 : theme == next_larger_theme)) { + if (next_larger) { + free(next_larger); + next_larger = NULL; + } + next_larger = strdup(file_name); + next_larger_size = ((IconThemeDir*)dir->data)->size; + next_larger_theme = theme; + if (DEBUG_ICON_SEARCH) + printf("next_larger = %s; next_larger_size = %d\n", next_larger, next_larger_size); + } + } + free(file_name); + } + } + } + } + if (next_larger) { + g_slist_free(extensions); + free(best_file_name); + return next_larger; + } + if (best_file_name) { + g_slist_free(extensions); + return best_file_name; + } + + // Look in unthemed icons + { + const GSList *base; + for (base = basenames; base; base = g_slist_next(base)) { + GSList *ext; + for (ext = extensions; ext; ext = g_slist_next(ext)) { + char *base_name = (char*) base->data; + char *extension = (char*) ext->data; + char *file_name = malloc(strlen(base_name) + strlen(icon_name) + + strlen(extension) + 100); + // filename = directory/iconname.extension + sprintf(file_name, "%s/%s%s", base_name, icon_name, extension); + if (DEBUG_ICON_SEARCH) + printf("checking %s\n", file_name); + if (g_file_test(file_name, G_FILE_TEST_EXISTS)) { + g_slist_free(extensions); + return file_name; + } else { + free(file_name); + file_name = NULL; + } + } + } + } + + g_slist_free(extensions); + return NULL; +} + +char *get_icon_path(IconThemeWrapper *theme, const char *icon_name, int size) +{ + if (!theme) + return NULL; + icon_name = icon_name ? icon_name : DEFAULT_ICON; + char *path = get_icon_path_helper(theme->themes, icon_name, size); + if (!path) { + path = get_icon_path_helper(theme->themes_fallback, icon_name, size); + } + if (!path) { + fprintf(stderr, "Could not find icon %s\n", icon_name); + path = get_icon_path_helper(theme->themes, DEFAULT_ICON, size); + } + if (!path) { + path = get_icon_path_helper(theme->themes_fallback, DEFAULT_ICON, size); + } + return path; +} diff --git a/src/launcher/icon-theme-common.h b/src/launcher/icon-theme-common.h new file mode 100644 index 0000000..e3f853f --- /dev/null +++ b/src/launcher/icon-theme-common.h @@ -0,0 +1,45 @@ +/************************************************************************** + * Copyright (C) 2015 (mrovi9000@gmail.com) + * + **************************************************************************/ + +#ifndef ICON_THEME_COMMON_H +#define ICON_THEME_COMMON_H + +#include + +typedef struct IconThemeWrapper { + // List of IconTheme* + GSList *themes; + // List of IconTheme* + GSList *themes_fallback; +} IconThemeWrapper; + +typedef struct IconTheme { + char *name; + GSList *list_inherits; // each item is a char* (theme name) + GSList *list_directories; // each item is an IconThemeDir* +} IconTheme; + +// Parses a line of the form "key = value". Modifies the line. +// Returns 1 if successful, and parts are not empty. +// Key and value point to the parts. +int parse_theme_line(char *line, char **key, char **value); + +// Returns an IconThemeWrapper* containing the icon theme identified by the name icon_theme_name, all the +// inherited themes, the hicolor theme and possibly fallback themes. +IconThemeWrapper *load_themes(const char *icon_theme_name); + +void free_themes(IconThemeWrapper *themes); + +#define DEFAULT_ICON "application-x-executable" + +// Returns the full path to an icon file (or NULL) given the list of icon themes to search and the icon name +// Note: needs to be released with free(). +char *get_icon_path(IconThemeWrapper *theme, const char *icon_name, int size); + +// Returns a list of the directories used to store icons. +// Do not free the result, it is cached. +const GSList *get_icon_locations(); + +#endif diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c index 69c31fb..0843c8f 100644 --- a/src/launcher/launcher.c +++ b/src/launcher/launcher.c @@ -3,6 +3,9 @@ * * Copyright (C) 2010 (mrovi@interfete-web-club.com) * +* SVG support: https://github.com/ixxra/tint2-svg +* Copyright (C) 2010 Rene Garcia (garciamx@gmail.com) +* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version 2 * as published by the Free Software Foundation. @@ -25,9 +28,13 @@ #include #include #include +#include +#include +#include -#ifdef HAVE_SN -#include +#ifdef HAVE_RSVG +#include +#include #endif #include "window.h" @@ -36,6 +43,8 @@ #include "panel.h" #include "taskbar.h" #include "launcher.h" +#include "apps-common.h" +#include "icon-theme-common.h" int launcher_enabled; int launcher_max_icon_size; @@ -46,16 +55,8 @@ int launcher_brightness; char *icon_theme_name; XSettingsClient *xsettings_client; -#define ICON_FALLBACK "application-x-executable" - -char *icon_path(Launcher *launcher, const char *icon_name, int size); -void launcher_load_themes(Launcher *launcher); -void free_desktop_entry(DesktopEntry *entry); -int launcher_read_desktop_file(const char *path, DesktopEntry *entry); Imlib_Image scale_icon(Imlib_Image original, int icon_size); void free_icon(Imlib_Image icon); -void free_icon_theme(IconTheme *theme); - void default_launcher() { @@ -65,7 +66,7 @@ void default_launcher() launcher_alpha = 100; launcher_saturation = 0; launcher_brightness = 0; - icon_theme_name = 0; + icon_theme_name = NULL; xsettings_client = NULL; } @@ -124,7 +125,7 @@ void cleanup_launcher() g_slist_free(panel_config.launcher.list_apps); panel_config.launcher.list_apps = NULL; free(icon_theme_name); - icon_theme_name = 0; + icon_theme_name = NULL; launcher_enabled = 0; } @@ -147,13 +148,9 @@ void cleanup_launcher_theme(Launcher *launcher) } g_slist_free(launcher->list_icons); - for (l = launcher->list_themes; l ; l = l->next) { - IconTheme *theme = (IconTheme*) l->data; - free_icon_theme(theme); - free(theme); - } - g_slist_free(launcher->list_themes); - launcher->list_icons = launcher->list_themes = NULL; + free_themes(launcher->list_themes); + launcher->list_icons = NULL; + launcher->list_themes = NULL; } @@ -164,10 +161,11 @@ int resize_launcher(void *obj) int count, icon_size; int icons_per_column=1, icons_per_row=1, marging=0; - if (panel_horizontal) + if (panel_horizontal) { icon_size = launcher->area.height; - else + } else { icon_size = launcher->area.width; + } icon_size = icon_size - (2 * launcher->area.bg->border.width) - (2 * launcher->area.paddingy); if (launcher_max_icon_size > 0 && icon_size > launcher_max_icon_size) icon_size = launcher_max_icon_size; @@ -181,20 +179,13 @@ int resize_launcher(void *obj) launcherIcon->area.height = launcherIcon->icon_size; // Get the path for an icon file with the new size - char *new_icon_path = icon_path(launcher, launcherIcon->icon_name, launcherIcon->icon_size); + char *new_icon_path = get_icon_path(launcher->list_themes, launcherIcon->icon_name, launcherIcon->icon_size); if (!new_icon_path) { // Draw a blank icon free_icon(launcherIcon->icon_original); launcherIcon->icon_original = NULL; free_icon(launcherIcon->icon_scaled); launcherIcon->icon_scaled = NULL; - new_icon_path = icon_path(launcher, ICON_FALLBACK, launcherIcon->icon_size); - if (new_icon_path) { - launcherIcon->icon_original = imlib_load_image(new_icon_path); - fprintf(stderr, "launcher.c %d: Using icon %s\n", __LINE__, new_icon_path); - free(new_icon_path); - } - launcherIcon->icon_scaled = scale_icon(launcherIcon->icon_original, icon_size); continue; } if (launcherIcon->icon_path && strcmp(new_icon_path, launcherIcon->icon_path) == 0) { @@ -207,12 +198,52 @@ int resize_launcher(void *obj) // Free the old files free_icon(launcherIcon->icon_original); free_icon(launcherIcon->icon_scaled); + launcherIcon->icon_original = launcherIcon->icon_scaled = NULL; // Load the new file and scale - launcherIcon->icon_original = imlib_load_image(new_icon_path); +#ifdef HAVE_RSVG + if (g_str_has_suffix(new_icon_path, ".svg")) { + GError* err = NULL; + RsvgHandle* svg = rsvg_handle_new_from_file(new_icon_path, &err); + + if (err != NULL) { + fprintf(stderr, "Could not load svg image!: %s", err->message); + g_error_free(err); + launcherIcon->icon_original = NULL; + } else { + char suffix[128]; + sprintf(suffix, "tmpicon-%d.png", getpid()); + gchar *name = g_build_filename(g_get_user_config_dir(), "tint2", suffix, NULL); + GdkPixbuf *pixbuf = rsvg_handle_get_pixbuf(svg); + gdk_pixbuf_save(pixbuf, name, "png", NULL, NULL); + launcherIcon->icon_original = imlib_load_image_immediately_without_cache(name); + g_remove(name); + g_free(name); + g_object_unref(G_OBJECT(pixbuf)); + g_object_unref(G_OBJECT(svg)); + } + } else +#endif + { + launcherIcon->icon_original = imlib_load_image_immediately(new_icon_path); + } + // On loading error, fallback to default + if (!launcherIcon->icon_original) { + free(new_icon_path); + new_icon_path = get_icon_path(launcher->list_themes, DEFAULT_ICON, launcherIcon->icon_size); + if (new_icon_path) + launcherIcon->icon_original = imlib_load_image_immediately(new_icon_path); + } + + if (!launcherIcon->icon_original) { + // Loading default icon failed, draw a blank icon + free(new_icon_path); + } else { + // Loaded icon successfully launcherIcon->icon_scaled = scale_icon(launcherIcon->icon_original, launcherIcon->icon_size); free(launcherIcon->icon_path); launcherIcon->icon_path = new_icon_path; fprintf(stderr, "launcher.c %d: Using icon %s\n", __LINE__, launcherIcon->icon_path); + } } } } @@ -220,25 +251,33 @@ int resize_launcher(void *obj) count = g_slist_length(launcher->list_icons); if (panel_horizontal) { - if (!count) launcher->area.width = 0; - else { + if (!count) { + launcher->area.width = 0; + } else { int height = launcher->area.height - 2*launcher->area.bg->border.width - 2*launcher->area.paddingy; // here icons_per_column always higher than 0 icons_per_column = (height+launcher->area.paddingx) / (icon_size+launcher->area.paddingx); marging = height - (icons_per_column-1)*(icon_size+launcher->area.paddingx) - icon_size; icons_per_row = count / icons_per_column + (count%icons_per_column != 0); - launcher->area.width = (2 * launcher->area.bg->border.width) + (2 * launcher->area.paddingxlr) + (icon_size * icons_per_row) + ((icons_per_row-1) * launcher->area.paddingx); + launcher->area.width = (2 * launcher->area.bg->border.width) + + (2 * launcher->area.paddingxlr) + + (icon_size * icons_per_row) + + ((icons_per_row-1) * launcher->area.paddingx); } } else { - if (!count) launcher->area.height = 0; - else { + if (!count) { + launcher->area.height = 0; + } else { int width = launcher->area.width - 2*launcher->area.bg->border.width - 2*launcher->area.paddingy; // here icons_per_row always higher than 0 icons_per_row = (width+launcher->area.paddingx) / (icon_size+launcher->area.paddingx); marging = width - (icons_per_row-1)*(icon_size+launcher->area.paddingx) - icon_size; icons_per_column = count / icons_per_row+ (count%icons_per_row != 0); - launcher->area.height = (2 * launcher->area.bg->border.width) + (2 * launcher->area.paddingxlr) + (icon_size * icons_per_column) + ((icons_per_column-1) * launcher->area.paddingx); + launcher->area.height = (2 * launcher->area.bg->border.width) + + (2 * launcher->area.paddingxlr) + + (icon_size * icons_per_column) + + ((icons_per_column-1) * launcher->area.paddingx); } } @@ -247,8 +286,7 @@ int resize_launcher(void *obj) if (panel_horizontal) { posy = start; posx = launcher->area.bg->border.width + launcher->area.paddingxlr; - } - else { + } else { posx = start; posy = launcher->area.bg->border.width + launcher->area.paddingxlr; } @@ -258,19 +296,22 @@ int resize_launcher(void *obj) launcherIcon->y = posy; launcherIcon->x = posx; + launcherIcon->area.posy = ((Area*)launcherIcon->area.parent)->posy + launcherIcon->y; + launcherIcon->area.posx = ((Area*)launcherIcon->area.parent)->posx + launcherIcon->x; + launcherIcon->area.width = launcherIcon->icon_size; + launcherIcon->area.height = launcherIcon->icon_size; //printf("launcher %d : %d,%d\n", i, posx, posy); if (panel_horizontal) { - if (i % icons_per_column) + if (i % icons_per_column) { posy += icon_size + launcher->area.paddingx; - else { + } else { posy = start; posx += (icon_size + launcher->area.paddingx); } - } - else { - if (i % icons_per_row) + } else { + if (i % icons_per_row) { posx += icon_size + launcher->area.paddingx; - else { + } else { posx = start; posy += (icon_size + launcher->area.paddingx); } @@ -286,6 +327,8 @@ void launcher_icon_on_change_layout(void *obj) LauncherIcon *launcherIcon = (LauncherIcon*)obj; launcherIcon->area.posy = ((Area*)launcherIcon->area.parent)->posy + launcherIcon->y; launcherIcon->area.posx = ((Area*)launcherIcon->area.parent)->posx + launcherIcon->x; + launcherIcon->area.width = launcherIcon->icon_size; + launcherIcon->area.height = launcherIcon->icon_size; } const char* launcher_icon_get_tooltip_text(void *obj) @@ -297,10 +340,12 @@ const char* launcher_icon_get_tooltip_text(void *obj) void draw_launcher_icon(void *obj, cairo_t *c) { LauncherIcon *launcherIcon = (LauncherIcon*)obj; + Imlib_Image icon_scaled = launcherIcon->icon_scaled; // Render imlib_context_set_image (icon_scaled); if (server.real_transparency) { + // TODO with compton this does not work with Firefox and Chrome but it works with the other icons ?! render_image(launcherIcon->area.pix, 0, 0, imlib_image_get_width(), imlib_image_get_height() ); } else { imlib_context_set_drawable(launcherIcon->area.pix); @@ -387,379 +432,14 @@ void launcher_action(LauncherIcon *icon, XEvent* evt) free(cmd); } -/***************** Freedesktop app.desktop and icon theme handling *********************/ -/* http://standards.freedesktop.org/desktop-entry-spec/ */ -/* http://standards.freedesktop.org/icon-theme-spec/ */ - -// Splits line at first '=' and returns 1 if successful, and parts are not empty -// key and value point to the parts -int parse_dektop_line(char *line, char **key, char **value) -{ - char *p; - int found = 0; - *key = line; - for (p = line; *p; p++) { - if (*p == '=') { - *value = p + 1; - *p = 0; - found = 1; - break; - } - } - if (!found) - return 0; - if (found && (strlen(*key) == 0 || strlen(*value) == 0)) - return 0; - return 1; -} - -int parse_theme_line(char *line, char **key, char **value) -{ - return parse_dektop_line(line, key, value); -} - -void expand_exec(DesktopEntry *entry, const char *path) -{ - // Expand % in exec - // %i -> --icon Icon - // %c -> Name - // %k -> path - if (entry->exec) { - char *exec2 = malloc(strlen(entry->exec) + (entry->name ? strlen(entry->name) : 1) + (entry->icon ? strlen(entry->icon) : 1) + 100); - char *p, *q; - // p will never point to an escaped char - for (p = entry->exec, q = exec2; *p; p++, q++) { - *q = *p; // Copy - if (*p == '\\') { - p++, q++; - // Copy the escaped char - if (*p == '%') // For % we delete the backslash, i.e. write % over it - q--; - *q = *p; - if (!*p) break; - continue; - } - if (*p == '%') { - p++; - if (!*p) break; - if (*p == 'i' && entry->icon != NULL) { - sprintf(q, "--icon '%s'", entry->icon); - q += strlen("--icon ''"); - q += strlen(entry->icon); - q--; // To balance the q++ in the for - } else if (*p == 'c' && entry->name != NULL) { - sprintf(q, "'%s'", entry->name); - q += strlen("''"); - q += strlen(entry->name); - q--; // To balance the q++ in the for - } else if (*p == 'c') { - sprintf(q, "'%s'", path); - q += strlen("''"); - q += strlen(path); - q--; // To balance the q++ in the for - } else { - // We don't care about other expansions - q--; // Delete the last % from q - } - continue; - } - } - *q = '\0'; - free(entry->exec); - entry->exec = exec2; - } -} - -int launcher_read_desktop_file(const char *path, DesktopEntry *entry) -{ - FILE *fp; - char *line = NULL; - size_t line_size; - char *key, *value; - int i; - - entry->name = entry->icon = entry->exec = NULL; - - if ((fp = fopen(path, "rt")) == NULL) { - fprintf(stderr, "Could not open file %s\n", path); - return 0; - } - - gchar **languages = (gchar **)g_get_language_names(); - // lang_index is the index of the language for the best Name key in the language vector - // lang_index_default is a constant that encodes the Name key without a language - int lang_index, lang_index_default; -#define LANG_DBG 0 - if (LANG_DBG) printf("Languages:"); - for (i = 0; languages[i]; i++) { - if (LANG_DBG) printf(" %s", languages[i]); - } - if (LANG_DBG) printf("\n"); - lang_index_default = i; - // we currently do not know about any Name key at all, so use an invalid index - lang_index = lang_index_default + 1; - - int inside_desktop_entry = 0; - while (getline(&line, &line_size, fp) >= 0) { - int len = strlen(line); - if (len == 0) - continue; - line[len - 1] = '\0'; - if (line[0] == '[') { - inside_desktop_entry = (strcmp(line, "[Desktop Entry]") == 0); - } - if (inside_desktop_entry && parse_dektop_line(line, &key, &value)) { - if (strstr(key, "Name") == key) { - if (strcmp(key, "Name") == 0 && lang_index > lang_index_default) { - entry->name = strdup(value); - lang_index = lang_index_default; - } else { - for (i = 0; languages[i] && i < lang_index; i++) { - gchar *localized_key = g_strdup_printf("Name[%s]", languages[i]); - if (strcmp(key, localized_key) == 0) { - if (entry->name) - free(entry->name); - entry->name = strdup(value); - lang_index = i; - } - g_free(localized_key); - } - } - } else if (!entry->exec && strcmp(key, "Exec") == 0) { - entry->exec = strdup(value); - } else if (!entry->icon && strcmp(key, "Icon") == 0) { - entry->icon = strdup(value); - } - } - } - fclose (fp); - // From this point: - // entry->name, entry->icon, entry->exec will never be empty strings (can be NULL though) - - expand_exec(entry, path); - - free(line); - return 1; -} - -void free_desktop_entry(DesktopEntry *entry) -{ - free(entry->name); - free(entry->icon); - free(entry->exec); -} - -void test_launcher_read_desktop_file() -{ - fprintf(stdout, "\033[1;33m"); - DesktopEntry entry; - launcher_read_desktop_file("/usr/share/applications/firefox.desktop", &entry); - printf("Name:%s Icon:%s Exec:%s\n", entry.name, entry.icon, entry.exec); - fprintf(stdout, "\033[0m"); -} - -//TODO Use UTF8 when parsing the file -IconTheme *load_theme(char *name) -{ - // Look for name/index.theme in $HOME/.icons, /usr/share/icons, /usr/share/pixmaps (stop at the first found) - // Parse index.theme -> list of IconThemeDir with attributes - // Return IconTheme* - - IconTheme *theme; - char *file_name; - FILE *f; - char *line = NULL; - size_t line_size; - - if (name == NULL) - return NULL; - - file_name = g_build_filename(g_get_home_dir(), ".icons", name, "index.theme", NULL); - if (!g_file_test(file_name, G_FILE_TEST_EXISTS)) { - g_free (file_name); - file_name = g_build_filename("/usr/share/icons", name, "index.theme", NULL); - if (!g_file_test(file_name, G_FILE_TEST_EXISTS)) { - g_free (file_name); - file_name = g_build_filename("/usr/share/pixmaps", name, "index.theme", NULL); - if (!g_file_test(file_name, G_FILE_TEST_EXISTS)) { - g_free (file_name); - file_name = NULL; - } - } - } - - if (!file_name) { - return NULL; - } - - if ((f = fopen(file_name, "rt")) == NULL) { - fprintf(stderr, "Could not open theme '%s'\n", file_name); - return NULL; - } - - g_free (file_name); - - theme = calloc(1, sizeof(IconTheme)); - theme->name = strdup(name); - theme->list_inherits = NULL; - theme->list_directories = NULL; - - IconThemeDir *current_dir = NULL; - int inside_header = 1; - while (getline(&line, &line_size, f) >= 0) { - char *key, *value; - - int line_len = strlen(line); - if (line_len >= 1) { - if (line[line_len - 1] == '\n') { - line[line_len - 1] = '\0'; - line_len--; - } - } - - if (line_len == 0) - continue; - - if (inside_header) { - if (parse_theme_line(line, &key, &value)) { - if (strcmp(key, "Inherits") == 0) { - // value is like oxygen,wood,default - char *token; - token = strtok(value, ",\n"); - while (token != NULL) - { - theme->list_inherits = g_slist_append(theme->list_inherits, strdup(token)); - token = strtok(NULL, ",\n"); - } - } else if (strcmp(key, "Directories") == 0) { - // value is like 48x48/apps,48x48/mimetypes,32x32/apps,scalable/apps,scalable/mimetypes - char *token; - token = strtok(value, ",\n"); - while (token != NULL) - { - IconThemeDir *dir = calloc(1, sizeof(IconThemeDir)); - dir->name = strdup(token); - dir->max_size = dir->min_size = dir->size = -1; - dir->type = ICON_DIR_TYPE_THRESHOLD; - dir->threshold = 2; - theme->list_directories = g_slist_append(theme->list_directories, dir); - token = strtok(NULL, ",\n"); - } - } - } - } else if (current_dir != NULL) { - if (parse_theme_line(line, &key, &value)) { - if (strcmp(key, "Size") == 0) { - // value is like 24 - sscanf(value, "%d", ¤t_dir->size); - if (current_dir->max_size == -1) - current_dir->max_size = current_dir->size; - if (current_dir->min_size == -1) - current_dir->min_size = current_dir->size; - } else if (strcmp(key, "MaxSize") == 0) { - // value is like 24 - sscanf(value, "%d", ¤t_dir->max_size); - } else if (strcmp(key, "MinSize") == 0) { - // value is like 24 - sscanf(value, "%d", ¤t_dir->min_size); - } else if (strcmp(key, "Threshold") == 0) { - // value is like 2 - sscanf(value, "%d", ¤t_dir->threshold); - } else if (strcmp(key, "Type") == 0) { - // value is Fixed, Scalable or Threshold : default to scalable for unknown Type. - if (strcmp(value, "Fixed") == 0) { - current_dir->type = ICON_DIR_TYPE_FIXED; - } else if (strcmp(value, "Threshold") == 0) { - current_dir->type = ICON_DIR_TYPE_THRESHOLD; - } else { - current_dir->type = ICON_DIR_TYPE_SCALABLE; - } - } else if (strcmp(key, "Context") == 0) { - // usual values: Actions, Applications, Devices, FileSystems, MimeTypes - current_dir->context = strdup(value); - } - } - } - - if (line[0] == '[' && line[line_len - 1] == ']' && strcmp(line, "[Icon Theme]") != 0) { - inside_header = 0; - current_dir = NULL; - line[line_len - 1] = '\0'; - char *dir_name = line + 1; - GSList* dir_item = theme->list_directories; - while (dir_item != NULL) - { - IconThemeDir *dir = dir_item->data; - if (strcmp(dir->name, dir_name) == 0) { - current_dir = dir; - break; - } - dir_item = g_slist_next(dir_item); - } - } - } - fclose(f); - - free(line); - - return theme; -} - -void free_icon_theme(IconTheme *theme) -{ - free(theme->name); - GSList *l_inherits; - for (l_inherits = theme->list_inherits; l_inherits ; l_inherits = l_inherits->next) { - free(l_inherits->data); - } - GSList *l_dir; - for (l_dir = theme->list_directories; l_dir ; l_dir = l_dir->next) { - IconThemeDir *dir = (IconThemeDir *)l_dir->data; - free(dir->name); - free(dir->context); - free(l_dir->data); - } -} - -void test_launcher_read_theme_file() -{ - fprintf(stdout, "\033[1;33m"); - IconTheme *theme = load_theme("oxygen"); - if (!theme) { - printf("Could not load theme\n"); - return; - } - printf("Loaded theme: %s\n", theme->name); - GSList* item = theme->list_inherits; - while (item != NULL) - { - printf("Inherits:%s\n", (char*)item->data); - item = g_slist_next(item); - } - item = theme->list_directories; - while (item != NULL) - { - IconThemeDir *dir = item->data; - printf("Dir:%s Size=%d MinSize=%d MaxSize=%d Threshold=%d Type=%s Context=%s\n", - dir->name, dir->size, dir->min_size, dir->max_size, dir->threshold, - dir->type == ICON_DIR_TYPE_FIXED ? "Fixed" : - dir->type == ICON_DIR_TYPE_SCALABLE ? "Scalable" : - dir->type == ICON_DIR_TYPE_THRESHOLD ? "Threshold" : "?????", - dir->context); - item = g_slist_next(item); - } - fprintf(stdout, "\033[0m"); -} - - -// Populates the list_icons list +// Populates the list_icons list from the list_apps list void launcher_load_icons(Launcher *launcher) { // Load apps (.desktop style launcher items) GSList* app = launcher->list_apps; while (app != NULL) { DesktopEntry entry; - launcher_read_desktop_file(app->data, &entry); + read_desktop_file(app->data, &entry); if (entry.exec) { LauncherIcon *launcherIcon = calloc(1, sizeof(LauncherIcon)); launcherIcon->area.parent = launcher; @@ -772,13 +452,14 @@ void launcher_load_icons(Launcher *launcher) launcherIcon->area.bg = &g_array_index(backgrounds, Background, 0); launcherIcon->area.on_screen = 1; launcherIcon->area._on_change_layout = launcher_icon_on_change_layout; - if (launcher_tooltip_enabled) + if (launcher_tooltip_enabled) { launcherIcon->area._get_tooltip_text = launcher_icon_get_tooltip_text; - else + } else { launcherIcon->area._get_tooltip_text = NULL; + } launcherIcon->is_app_desktop = 1; launcherIcon->cmd = strdup(entry.exec); - launcherIcon->icon_name = entry.icon ? strdup(entry.icon) : strdup(ICON_FALLBACK); + launcherIcon->icon_name = entry.icon ? strdup(entry.icon) : strdup(DEFAULT_ICON); launcherIcon->icon_size = 1; launcherIcon->icon_tooltip = entry.name ? strdup(entry.name) : strdup(entry.exec); free_desktop_entry(&entry); @@ -793,309 +474,5 @@ void launcher_load_icons(Launcher *launcher) // Populates the list_themes list void launcher_load_themes(Launcher *launcher) { - // load the user theme, all the inherited themes recursively (DFS), and the hicolor theme - // avoid inheritance loops - if (!icon_theme_name) { - fprintf(stderr, "Missing launcher theme, default to 'hicolor'.\n"); - icon_theme_name = strdup("hicolor"); - } else { - fprintf(stderr, "Loading %s. Icon theme :", icon_theme_name); - } - - GSList *queue = g_slist_append(NULL, strdup(icon_theme_name)); - GSList *queued = g_slist_append(NULL, strdup(icon_theme_name)); - - int hicolor_loaded = 0; - while (queue || !hicolor_loaded) { - if (!queue) { - GSList* queued_item = queued; - while (queued_item != NULL) { - if (strcmp(queued_item->data, "hicolor") == 0) { - hicolor_loaded = 1; - break; - } - queued_item = g_slist_next(queued_item); - } - if (hicolor_loaded) - break; - queue = g_slist_append(queue, strdup("hicolor")); - queued = g_slist_append(queued, strdup("hicolor")); - } - - char *name = queue->data; - queue = g_slist_remove(queue, name); - - fprintf(stderr, " '%s',", name); - IconTheme *theme = load_theme(name); - if (theme != NULL) { - launcher->list_themes = g_slist_append(launcher->list_themes, theme); - - GSList* item = theme->list_inherits; - int pos = 0; - while (item != NULL) - { - char *parent = item->data; - int duplicate = 0; - GSList* queued_item = queued; - while (queued_item != NULL) { - if (strcmp(queued_item->data, parent) == 0) { - duplicate = 1; - break; - } - queued_item = g_slist_next(queued_item); - } - if (!duplicate) { - queue = g_slist_insert(queue, strdup(parent), pos); - pos++; - queued = g_slist_append(queued, strdup(parent)); - } - item = g_slist_next(item); - } - } - } - fprintf(stderr, "\n"); - - // Free the queue - GSList *l; - for (l = queue; l ; l = l->next) - free(l->data); - g_slist_free(queue); - for (l = queued; l ; l = l->next) - free(l->data); - g_slist_free(queued); + launcher->list_themes = load_themes(icon_theme_name); } - -int directory_matches_size(IconThemeDir *dir, int size) -{ - if (dir->type == ICON_DIR_TYPE_FIXED) { - return dir->size == size; - } else if (dir->type == ICON_DIR_TYPE_SCALABLE) { - return dir->min_size <= size && size <= dir->max_size; - } else /*if (dir->type == ICON_DIR_TYPE_THRESHOLD)*/ { - return dir->size - dir->threshold <= size && size <= dir->size + dir->threshold; - } -} - -int directory_size_distance(IconThemeDir *dir, int size) -{ - if (dir->type == ICON_DIR_TYPE_FIXED) { - return abs(dir->size - size); - } else if (dir->type == ICON_DIR_TYPE_SCALABLE) { - if (size < dir->min_size) { - return dir->min_size - size; - } else if (size > dir->max_size) { - return size - dir->max_size; - } else { - return 0; - } - } else /*if (dir->type == ICON_DIR_TYPE_THRESHOLD)*/ { - if (size < dir->size - dir->threshold) { - return dir->min_size - size; - } else if (size > dir->size + dir->threshold) { - return size - dir->max_size; - } else { - return 0; - } - } -} - -#define DEBUG_ICON_SEARCH 0 -// Returns the full path to an icon file (or NULL) given the icon name -char *icon_path(Launcher *launcher, const char *icon_name, int size) -{ - if (icon_name == NULL) - return NULL; - - // If the icon_name is already a path and the file exists, return it - if (strstr(icon_name, "/") == icon_name) { - if (g_file_test(icon_name, G_FILE_TEST_EXISTS)) - return strdup(icon_name); - else - return NULL; - } - - GSList *basenames = NULL; - char *home_icons = g_build_filename(g_get_home_dir(), ".icons", NULL); - basenames = g_slist_append(basenames, home_icons); - char *home_local_icons = g_build_filename(g_get_home_dir(), ".local/share/icons", NULL); - basenames = g_slist_append(basenames, home_local_icons); - basenames = g_slist_append(basenames, "/usr/local/share/icons"); - basenames = g_slist_append(basenames, "/usr/local/share/pixmaps"); - basenames = g_slist_append(basenames, "/usr/share/icons"); - basenames = g_slist_append(basenames, "/usr/share/pixmaps"); - - GSList *extensions = NULL; - extensions = g_slist_append(extensions, ".png"); - extensions = g_slist_append(extensions, ".xpm"); - // if the icon name already contains one of the extensions (e.g. vlc.png instead of vlc) add a special entry - GSList *ext; - for (ext = extensions; ext; ext = g_slist_next(ext)) { - char *extension = (char*) ext->data; - if (strlen(icon_name) > strlen(extension) && - strcmp(extension, icon_name + strlen(icon_name) - strlen(extension)) == 0) { - extensions = g_slist_append(extensions, ""); - break; - } - } - - GSList *theme; - // Stage 1: exact size match - // the theme must have a higher priority than having an exact size match, so we will just use - // the code that searches for the best size match (it will find the exact size match if one exists) - /* - for (theme = launcher->list_themes; theme; theme = g_slist_next(theme)) { - GSList *dir; - for (dir = ((IconTheme*)theme->data)->list_directories; dir; dir = g_slist_next(dir)) { - if (directory_matches_size((IconThemeDir*)dir->data, size)) { - GSList *base; - for (base = basenames; base; base = g_slist_next(base)) { - GSList *ext; - for (ext = extensions; ext; ext = g_slist_next(ext)) { - char *base_name = (char*) base->data; - char *theme_name = ((IconTheme*)theme->data)->name; - char *dir_name = ((IconThemeDir*)dir->data)->name; - char *extension = (char*) ext->data; - char *file_name = malloc(strlen(base_name) + strlen(theme_name) + - strlen(dir_name) + strlen(icon_name) + strlen(extension) + 100); - // filename = directory/$(themename)/subdirectory/iconname.extension - sprintf(file_name, "%s/%s/%s/%s%s", base_name, theme_name, dir_name, icon_name, extension); - //printf("found exact: %s\n", file_name); - //printf("checking %s\n", file_name); - if (g_file_test(file_name, G_FILE_TEST_EXISTS)) { - g_slist_free(basenames); - g_slist_free(extensions); - g_free(home_icons); - g_free(home_local_icons); - return file_name; - } else { - free(file_name); - file_name = NULL; - } - } - } - } - } - } - g_free (file_name); - */ - - // Stage 2: best size match - // Contrary to the freedesktop spec, we are not choosing the closest icon in size, but the next larger icon - // otherwise the quality is usually crap (for size 22, if you can choose 16 or 32, you're better with 32) - // We do fallback to the closest size if we cannot find a larger or equal icon - - // These 3 variables are used for keeping the closest size match - int minimal_size = INT_MAX; - char *best_file_name = NULL; - GSList *best_file_theme = NULL; - - // These 3 variables are used for keeping the next larger match - int next_larger_size = -1; - char *next_larger = NULL; - GSList *next_larger_theme = NULL; - - for (theme = launcher->list_themes; theme; theme = g_slist_next(theme)) { - GSList *dir; - for (dir = ((IconTheme*)theme->data)->list_directories; dir; dir = g_slist_next(dir)) { - GSList *base; - for (base = basenames; base; base = g_slist_next(base)) { - GSList *ext; - for (ext = extensions; ext; ext = g_slist_next(ext)) { - char *base_name = (char*) base->data; - char *theme_name = ((IconTheme*)theme->data)->name; - char *dir_name = ((IconThemeDir*)dir->data)->name; - char *extension = (char*) ext->data; - char *file_name = malloc(strlen(base_name) + strlen(theme_name) + - strlen(dir_name) + strlen(icon_name) + strlen(extension) + 100); - // filename = directory/$(themename)/subdirectory/iconname.extension - sprintf(file_name, "%s/%s/%s/%s%s", base_name, theme_name, dir_name, icon_name, extension); - if (DEBUG_ICON_SEARCH) - printf("checking %s\n", file_name); - if (g_file_test(file_name, G_FILE_TEST_EXISTS)) { - if (DEBUG_ICON_SEARCH) - printf("found: %s\n", file_name); - // Closest match - if (directory_size_distance((IconThemeDir*)dir->data, size) < minimal_size && (!best_file_theme ? 1 : theme == best_file_theme)) { - if (best_file_name) { - free(best_file_name); - best_file_name = NULL; - } - best_file_name = strdup(file_name); - minimal_size = directory_size_distance((IconThemeDir*)dir->data, size); - best_file_theme = theme; - if (DEBUG_ICON_SEARCH) - printf("best_file_name = %s; minimal_size = %d\n", best_file_name, minimal_size); - } - // Next larger match - if (((IconThemeDir*)dir->data)->size >= size && - (next_larger_size == -1 || ((IconThemeDir*)dir->data)->size < next_larger_size) && - (!next_larger_theme ? 1 : theme == next_larger_theme)) { - if (next_larger) { - free(next_larger); - next_larger = NULL; - } - next_larger = strdup(file_name); - next_larger_size = ((IconThemeDir*)dir->data)->size; - next_larger_theme = theme; - if (DEBUG_ICON_SEARCH) - printf("next_larger = %s; next_larger_size = %d\n", next_larger, next_larger_size); - } - } - free(file_name); - } - } - } - } - if (next_larger) { - g_slist_free(basenames); - g_slist_free(extensions); - free(best_file_name); - g_free(home_icons); - g_free(home_local_icons); - return next_larger; - } - if (best_file_name) { - g_slist_free(basenames); - g_slist_free(extensions); - g_free(home_icons); - g_free(home_local_icons); - return best_file_name; - } - - // Stage 3: look in unthemed icons - { - GSList *base; - for (base = basenames; base; base = g_slist_next(base)) { - GSList *ext; - for (ext = extensions; ext; ext = g_slist_next(ext)) { - char *base_name = (char*) base->data; - char *extension = (char*) ext->data; - char *file_name = malloc(strlen(base_name) + strlen(icon_name) + - strlen(extension) + 100); - // filename = directory/iconname.extension - sprintf(file_name, "%s/%s%s", base_name, icon_name, extension); - if (DEBUG_ICON_SEARCH) - printf("checking %s\n", file_name); - if (g_file_test(file_name, G_FILE_TEST_EXISTS)) { - g_slist_free(basenames); - g_slist_free(extensions); - g_free(home_icons); - g_free(home_local_icons); - return file_name; - } else { - free(file_name); - file_name = NULL; - } - } - } - } - - fprintf(stderr, "Could not find icon %s\n", icon_name); - - g_slist_free(basenames); - g_slist_free(extensions); - g_free(home_icons); - g_free(home_local_icons); - return NULL; -} - diff --git a/src/launcher/launcher.h b/src/launcher/launcher.h index f60acee..eef4cfe 100644 --- a/src/launcher/launcher.h +++ b/src/launcher/launcher.h @@ -10,13 +10,14 @@ #include "common.h" #include "area.h" #include "xsettings-client.h" +#include "icon-theme-common.h" typedef struct Launcher { // always start with area Area area; GSList *list_apps; // List of char*, each is a path to a app.desktop file GSList *list_icons; // List of LauncherIcon* - GSList *list_themes; // List of IconTheme* + IconThemeWrapper *list_themes; } Launcher; typedef struct LauncherIcon { @@ -33,31 +34,6 @@ typedef struct LauncherIcon { int x, y; } LauncherIcon; -typedef struct DesktopEntry { - char *name; - char *exec; - char *icon; -} DesktopEntry; - -#define ICON_DIR_TYPE_SCALABLE 0 -#define ICON_DIR_TYPE_FIXED 1 -#define ICON_DIR_TYPE_THRESHOLD 2 -typedef struct IconThemeDir { - char *name; - int size; - int type; - int max_size; - int min_size; - int threshold; - char *context; -} IconThemeDir; - -typedef struct IconTheme { - char *name; - GSList *list_inherits; // each item is a char* (theme name) - GSList *list_directories; // each item is an IconThemeDir* -} IconTheme; - extern int launcher_enabled; extern int launcher_max_icon_size; extern int launcher_tooltip_enabled; @@ -79,10 +55,10 @@ void cleanup_launcher_theme(Launcher *launcher); int resize_launcher(void *obj); void draw_launcher (void *obj, cairo_t *c); -// Populates the list_themes list -void launcher_load_themes(Launcher *launcher); // Populates the list_icons list void launcher_load_icons(Launcher *launcher); +// Populates the list_themes list +void launcher_load_themes(Launcher *launcher); void launcher_action(LauncherIcon *icon, XEvent* e); void test_launcher_read_desktop_file(); diff --git a/src/panel.c b/src/panel.c index 7af2a08..2186dbd 100644 --- a/src/panel.c +++ b/src/panel.c @@ -391,8 +391,10 @@ void set_panel_items_order(Panel *p) } for (k=0 ; k < strlen(panel_items_order) ; k++) { - if (panel_items_order[k] == 'L') + if (panel_items_order[k] == 'L') { p->area.list = g_slist_append(p->area.list, &p->launcher); + p->launcher.area.resize = 1; + } if (panel_items_order[k] == 'T') { for (j=0 ; j < p->nb_desktop ; j++) p->area.list = g_slist_append(p->area.list, &p->taskbar[j]);