diff --git a/src/launcher/launcher.c b/src/launcher/launcher.c index 83038d0..aaafaab 100644 --- a/src/launcher/launcher.c +++ b/src/launcher/launcher.c @@ -34,7 +34,7 @@ int launcher_enabled; int launcher_max_icon_size; - +GSList *icon_themes = 0; void default_launcher() { @@ -56,7 +56,6 @@ void init_launcher_panel(void *p) launcher->area.parent = p; launcher->area.panel = p; launcher->area._draw_foreground = draw_launcher; - launcher->area.size_mode = SIZE_BY_CONTENT; launcher->area._resize = resize_launcher; launcher->area.resize = 1; launcher->area.redraw = 1; @@ -256,3 +255,229 @@ void launcher_action(LauncherIcon *icon) free(cmd); } +/***************** Freedesktop app.desktop and icon theme handling *********************/ +/* http://standards.freedesktop.org/desktop-entry-spec/ */ +/* http://standards.freedesktop.org/icon-theme-spec/ */ + +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) + strlen(entry->name) + strlen(entry->icon) + 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[4096]; + char *buffer; + char *key, *value; + + entry->name = entry->icon = entry->exec = NULL; + + if ((fp = fopen(path, "r")) == NULL) { + fprintf(stderr, "Could not open file %s\n", path); + return 0; + } + + buffer = line; + while (fgets(buffer, sizeof(line) - (buffer - line), fp) != NULL) { + //TODO use UTF8 capable strlen + int len = strlen(buffer); + int total_len = strlen(line); + if (!g_utf8_validate(buffer, total_len, NULL)) { + // Not a real newline, read more + buffer += len; + if (sizeof(line) - (buffer - line) < 2) { + fprintf(stderr, "%s %d: line too long (%s)\n", __FILE__, __LINE__, path); + break; + } else { + continue; + } + } + // We have a valid line + buffer[len-1] = '\0'; + buffer = line; + if (parse_dektop_line(line, &key, &value)) { + if (strcmp(key, "Name") == 0) { + //TODO use UTF-8 capable strdup + entry->name = strdup(value); + } else if (strcmp(key, "Exec") == 0) { + entry->exec = strdup(value); + } else if (strcmp(key, "Icon") == 0) { + //TODO use UTF-8 capable strdup + 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); + + return 1; +} + +void test_launcher_read_desktop_file() +{ + 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); +} + +IconTheme *load_theme(char *name) +{ + //TODO + // 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 struct + return NULL; +} + +// Populates the icon_themes list +void launcher_load_themes() +{ + //TODO load the user theme, all the inherited themes recursively (DFS), and the hicolor theme + // avoid inheritance loops +} + +// Returns the full path to an icon file (or NULL) given the icon name +char *icon_path(const char *name) +{ + //TODO + /* + +// Stage 1: exact size match +LookupIcon (iconname, size): +for each subdir in $(theme subdir list) { // <---- each subdir in each theme + for each directory in $(basename list) { + for extension in ("png", "svg", "xpm") { + if DirectoryMatchesSize(subdir, size) { + filename = directory/$(themename)/subdirectory/iconname.extension + if exist filename + return filename +} +} +} +} + +// Stage 2: best size match +minimal_size = MAXINT +for each subdir in $(theme subdir list) { // <---- each subdir in each theme + for each directory in $(basename list) { + for extension in ("png", "svg", "xpm") { + filename = directory/$(themename)/subdirectory/iconname.extension + if exist filename and DirectorySizeDistance(subdir, size) < minimal_size + closest_filename = filename + minimal_size = DirectorySizeDistance(subdir, size) +} +} +} +if closest_filename set + return closest_filename + +// Stage 3: look in unthemed icons +for each directory in $(basename list) { // <---- $HOME/.icons, /usr/share/icons, /usr/share/pixmaps + for extension in ("png", "svg", "xpm") { + if exists directory/iconname.extension + return directory/iconname.extension + } + } + + return failed icon lookup + +// With the following helper functions: + +DirectoryMatchesSize(subdir, iconsize): +read Type and size data from subdir +if Type is Fixed +return Size == iconsize +if Type is Scaled +return MinSize <= iconsize <= MaxSize +if Type is Threshold +return Size - Threshold <= iconsize <= Size + Threshold + +DirectorySizeDistance(subdir, size): +read Type and size data from subdir +if Type is Fixed +return abs(Size - iconsize) +if Type is Scaled +if iconsize < MinSize +return MinSize - iconsize +if iconsize > MaxSize +return iconsize - MaxSize +return 0 +if Type is Threshold +if iconsize < Size - Threshold +return MinSize - iconsize +if iconsize > Size + Threshold +return iconsize - MaxSize +return 0 + */ +} diff --git a/src/launcher/launcher.h b/src/launcher/launcher.h index de86835..1a3f011 100644 --- a/src/launcher/launcher.h +++ b/src/launcher/launcher.h @@ -25,9 +25,35 @@ typedef struct LauncherIcon { int width, height; } 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; +} 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 GSList *icon_themes; // each item is an IconTheme* + // default global data void default_launcher(); @@ -41,4 +67,6 @@ void draw_launcher (void *obj, cairo_t *c); void launcher_action(LauncherIcon *icon); +void test_launcher_read_desktop_file(); + #endif