]> Dogcows Code - chaz/tint2/blob - src/launcher/launcher.c
Adding startup-notification support.
[chaz/tint2] / src / launcher / launcher.c
1 /**************************************************************************
2 * Tint2 : launcher
3 *
4 * Copyright (C) 2010 (mrovi@interfete-web-club.com)
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License version 2
8 * as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 **************************************************************************/
18
19 #include <string.h>
20 #include <stdio.h>
21 #include <cairo.h>
22 #include <cairo-xlib.h>
23 #include <pango/pangocairo.h>
24 #include <unistd.h>
25 #include <signal.h>
26 #include <stdlib.h>
27 #include <glib/gi18n.h>
28
29 #ifdef HAVE_SN
30 #include <libsn/sn.h>
31 #endif
32
33 #include "window.h"
34 #include "server.h"
35 #include "area.h"
36 #include "panel.h"
37 #include "taskbar.h"
38 #include "launcher.h"
39
40 int launcher_enabled;
41 int launcher_max_icon_size;
42 int launcher_tooltip_enabled;
43 int launcher_alpha;
44 int launcher_saturation;
45 int launcher_brightness;
46 char *icon_theme_name;
47 XSettingsClient *xsettings_client;
48
49 #define ICON_FALLBACK "application-x-executable"
50
51 char *icon_path(Launcher *launcher, const char *icon_name, int size);
52 void launcher_load_themes(Launcher *launcher);
53 void free_desktop_entry(DesktopEntry *entry);
54 int launcher_read_desktop_file(const char *path, DesktopEntry *entry);
55 Imlib_Image scale_icon(Imlib_Image original, int icon_size);
56 void free_icon(Imlib_Image icon);
57 void free_icon_theme(IconTheme *theme);
58
59
60 void default_launcher()
61 {
62 launcher_enabled = 0;
63 launcher_max_icon_size = 0;
64 launcher_tooltip_enabled = 0;
65 launcher_alpha = 100;
66 launcher_saturation = 0;
67 launcher_brightness = 0;
68 icon_theme_name = 0;
69 xsettings_client = NULL;
70 }
71
72
73 void init_launcher()
74 {
75 if (launcher_enabled) {
76 // if XSETTINGS manager running, tint2 read the icon_theme_name.
77 xsettings_client = xsettings_client_new(server.dsp, server.screen, xsettings_notify_cb, NULL, NULL);
78 }
79 }
80
81
82 void init_launcher_panel(void *p)
83 {
84 Panel *panel =(Panel*)p;
85 Launcher *launcher = &panel->launcher;
86
87 launcher->area.parent = p;
88 launcher->area.panel = p;
89 launcher->area._draw_foreground = NULL;
90 launcher->area.size_mode = SIZE_BY_CONTENT;
91 launcher->area._resize = resize_launcher;
92 launcher->area.resize = 1;
93 launcher->area.redraw = 1;
94 if (launcher->area.bg == 0)
95 launcher->area.bg = &g_array_index(backgrounds, Background, 0);
96
97 // check consistency
98 if (launcher->list_apps == NULL)
99 return;
100
101 launcher->area.on_screen = 1;
102 panel_refresh = 1;
103
104 launcher_load_themes(launcher);
105 launcher_load_icons(launcher);
106 }
107
108
109 void cleanup_launcher()
110 {
111 int i;
112 GSList *l;
113
114 if (xsettings_client)
115 xsettings_client_destroy(xsettings_client);
116 for (i = 0 ; i < nb_panel ; i++) {
117 Panel *panel = &panel1[i];
118 Launcher *launcher = &panel->launcher;
119 cleanup_launcher_theme(launcher);
120 }
121 for (l = panel_config.launcher.list_apps; l ; l = l->next) {
122 free(l->data);
123 }
124 g_slist_free(panel_config.launcher.list_apps);
125 panel_config.launcher.list_apps = NULL;
126 free(icon_theme_name);
127 icon_theme_name = 0;
128 launcher_enabled = 0;
129 }
130
131
132 void cleanup_launcher_theme(Launcher *launcher)
133 {
134 free_area(&launcher->area);
135 GSList *l;
136 for (l = launcher->list_icons; l ; l = l->next) {
137 LauncherIcon *launcherIcon = (LauncherIcon*)l->data;
138 if (launcherIcon) {
139 free_icon(launcherIcon->icon_scaled);
140 free_icon(launcherIcon->icon_original);
141 free(launcherIcon->icon_name);
142 free(launcherIcon->icon_path);
143 free(launcherIcon->cmd);
144 free(launcherIcon->icon_tooltip);
145 }
146 free(launcherIcon);
147 }
148 g_slist_free(launcher->list_icons);
149
150 for (l = launcher->list_themes; l ; l = l->next) {
151 IconTheme *theme = (IconTheme*) l->data;
152 free_icon_theme(theme);
153 free(theme);
154 }
155 g_slist_free(launcher->list_themes);
156 launcher->list_icons = launcher->list_themes = NULL;
157 }
158
159
160 int resize_launcher(void *obj)
161 {
162 Launcher *launcher = obj;
163 GSList *l;
164 int count, icon_size;
165 int icons_per_column=1, icons_per_row=1, marging=0;
166
167 if (panel_horizontal)
168 icon_size = launcher->area.height;
169 else
170 icon_size = launcher->area.width;
171 icon_size = icon_size - (2 * launcher->area.bg->border.width) - (2 * launcher->area.paddingy);
172 if (launcher_max_icon_size > 0 && icon_size > launcher_max_icon_size)
173 icon_size = launcher_max_icon_size;
174
175 // Resize icons if necessary
176 for (l = launcher->list_icons; l ; l = l->next) {
177 LauncherIcon *launcherIcon = (LauncherIcon *)l->data;
178 if (launcherIcon->icon_size != icon_size || !launcherIcon->icon_original) {
179 launcherIcon->icon_size = icon_size;
180 launcherIcon->area.width = launcherIcon->icon_size;
181 launcherIcon->area.height = launcherIcon->icon_size;
182
183 // Get the path for an icon file with the new size
184 char *new_icon_path = icon_path(launcher, launcherIcon->icon_name, launcherIcon->icon_size);
185 if (!new_icon_path) {
186 // Draw a blank icon
187 free_icon(launcherIcon->icon_original);
188 launcherIcon->icon_original = NULL;
189 free_icon(launcherIcon->icon_scaled);
190 launcherIcon->icon_scaled = NULL;
191 new_icon_path = icon_path(launcher, ICON_FALLBACK, launcherIcon->icon_size);
192 if (new_icon_path) {
193 launcherIcon->icon_original = imlib_load_image(new_icon_path);
194 fprintf(stderr, "launcher.c %d: Using icon %s\n", __LINE__, new_icon_path);
195 free(new_icon_path);
196 }
197 launcherIcon->icon_scaled = scale_icon(launcherIcon->icon_original, icon_size);
198 continue;
199 }
200 if (launcherIcon->icon_path && strcmp(new_icon_path, launcherIcon->icon_path) == 0) {
201 // If it's the same file just rescale
202 free_icon(launcherIcon->icon_scaled);
203 launcherIcon->icon_scaled = scale_icon(launcherIcon->icon_original, icon_size);
204 free(new_icon_path);
205 fprintf(stderr, "launcher.c %d: Using icon %s\n", __LINE__, launcherIcon->icon_path);
206 } else {
207 // Free the old files
208 free_icon(launcherIcon->icon_original);
209 free_icon(launcherIcon->icon_scaled);
210 // Load the new file and scale
211 launcherIcon->icon_original = imlib_load_image(new_icon_path);
212 launcherIcon->icon_scaled = scale_icon(launcherIcon->icon_original, launcherIcon->icon_size);
213 free(launcherIcon->icon_path);
214 launcherIcon->icon_path = new_icon_path;
215 fprintf(stderr, "launcher.c %d: Using icon %s\n", __LINE__, launcherIcon->icon_path);
216 }
217 }
218 }
219
220 count = g_slist_length(launcher->list_icons);
221
222 if (panel_horizontal) {
223 if (!count) launcher->area.width = 0;
224 else {
225 int height = launcher->area.height - 2*launcher->area.bg->border.width - 2*launcher->area.paddingy;
226 // here icons_per_column always higher than 0
227 icons_per_column = (height+launcher->area.paddingx) / (icon_size+launcher->area.paddingx);
228 marging = height - (icons_per_column-1)*(icon_size+launcher->area.paddingx) - icon_size;
229 icons_per_row = count / icons_per_column + (count%icons_per_column != 0);
230 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);
231 }
232 }
233 else {
234 if (!count) launcher->area.height = 0;
235 else {
236 int width = launcher->area.width - 2*launcher->area.bg->border.width - 2*launcher->area.paddingy;
237 // here icons_per_row always higher than 0
238 icons_per_row = (width+launcher->area.paddingx) / (icon_size+launcher->area.paddingx);
239 marging = width - (icons_per_row-1)*(icon_size+launcher->area.paddingx) - icon_size;
240 icons_per_column = count / icons_per_row+ (count%icons_per_row != 0);
241 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);
242 }
243 }
244
245 int i, posx, posy;
246 int start = launcher->area.bg->border.width + launcher->area.paddingy + marging/2;
247 if (panel_horizontal) {
248 posy = start;
249 posx = launcher->area.bg->border.width + launcher->area.paddingxlr;
250 }
251 else {
252 posx = start;
253 posy = launcher->area.bg->border.width + launcher->area.paddingxlr;
254 }
255
256 for (i=1, l = launcher->list_icons; l ; i++, l = l->next) {
257 LauncherIcon *launcherIcon = (LauncherIcon*)l->data;
258
259 launcherIcon->y = posy;
260 launcherIcon->x = posx;
261 //printf("launcher %d : %d,%d\n", i, posx, posy);
262 if (panel_horizontal) {
263 if (i % icons_per_column)
264 posy += icon_size + launcher->area.paddingx;
265 else {
266 posy = start;
267 posx += (icon_size + launcher->area.paddingx);
268 }
269 }
270 else {
271 if (i % icons_per_row)
272 posx += icon_size + launcher->area.paddingx;
273 else {
274 posx = start;
275 posy += (icon_size + launcher->area.paddingx);
276 }
277 }
278 }
279 return 1;
280 }
281
282 // Here we override the default layout of the icons; normally Area layouts its children
283 // in a stack; we need to layout them in a kind of table
284 void launcher_icon_on_change_layout(void *obj)
285 {
286 LauncherIcon *launcherIcon = (LauncherIcon*)obj;
287 launcherIcon->area.posy = ((Area*)launcherIcon->area.parent)->posy + launcherIcon->y;
288 launcherIcon->area.posx = ((Area*)launcherIcon->area.parent)->posx + launcherIcon->x;
289 }
290
291 const char* launcher_icon_get_tooltip_text(void *obj)
292 {
293 LauncherIcon *launcherIcon = (LauncherIcon*)obj;
294 return launcherIcon->icon_tooltip;
295 }
296
297 void draw_launcher_icon(void *obj, cairo_t *c)
298 {
299 LauncherIcon *launcherIcon = (LauncherIcon*)obj;
300 Imlib_Image icon_scaled = launcherIcon->icon_scaled;
301 // Render
302 imlib_context_set_image (icon_scaled);
303 if (server.real_transparency) {
304 render_image(launcherIcon->area.pix, 0, 0, imlib_image_get_width(), imlib_image_get_height() );
305 } else {
306 imlib_context_set_drawable(launcherIcon->area.pix);
307 imlib_render_image_on_drawable (0, 0);
308 }
309 }
310
311 Imlib_Image scale_icon(Imlib_Image original, int icon_size)
312 {
313 Imlib_Image icon_scaled;
314 if (original) {
315 imlib_context_set_image (original);
316 icon_scaled = imlib_create_cropped_scaled_image(0, 0, imlib_image_get_width(), imlib_image_get_height(), icon_size, icon_size);
317 imlib_context_set_image (icon_scaled);
318 imlib_image_set_has_alpha(1);
319 DATA32* data = imlib_image_get_data();
320 adjust_asb(data, icon_size, icon_size, launcher_alpha, (float)launcher_saturation/100, (float)launcher_brightness/100);
321 imlib_image_put_back_data(data);
322 } else {
323 icon_scaled = imlib_create_image(icon_size, icon_size);
324 imlib_context_set_image (icon_scaled);
325 imlib_context_set_color(255, 255, 255, 255);
326 imlib_image_fill_rectangle(0, 0, icon_size, icon_size);
327 }
328 return icon_scaled;
329 }
330
331 void free_icon(Imlib_Image icon)
332 {
333 if (icon) {
334 imlib_context_set_image(icon);
335 imlib_free_image();
336 }
337 }
338
339 void launcher_action(LauncherIcon *icon, XEvent* evt)
340 {
341 char *cmd = malloc(strlen(icon->cmd) + 10);
342 sprintf(cmd, "(%s&)", icon->cmd);
343 #if HAVE_SN
344 SnLauncherContext* ctx;
345 Time time;
346
347 ctx = sn_launcher_context_new(server.sn_dsp, server.screen);
348 sn_launcher_context_set_name(ctx, icon->icon_tooltip);
349 sn_launcher_context_set_description(ctx, "Application launched from tint2");
350 sn_launcher_context_set_binary_name (ctx, icon->cmd);
351 // Get a timestamp from the X event
352 if (evt->type == ButtonPress || evt->type == ButtonRelease) {
353 time = evt->xbutton.time;
354 }
355 else {
356 fprintf(stderr, "Unknown X event: %d\n", evt->type);
357 free(cmd);
358 return;
359 }
360 sn_launcher_context_initiate(ctx, "tint2", icon->cmd, time);
361 #endif /* HAVE_SN */
362 pid_t pid;
363 pid = fork();
364 if (pid < 0) {
365 fprintf(stderr, "Could not fork\n");
366 }
367 else if (pid == 0) {
368 #if HAVE_SN
369 sn_launcher_context_setup_child_process (ctx);
370 #endif // HAVE_SN
371 // Allow children to exist after parent destruction
372 setsid ();
373 // Run the command
374 execl("/bin/sh", "/bin/sh", "-c", icon->cmd, NULL);
375
376 fprintf(stderr, "Failed to execlp %s\n", icon->cmd);
377 #if HAVE_SN
378 sn_launcher_context_unref (ctx);
379 #endif // HAVE_SN
380 _exit(1);
381 }
382 #if HAVE_SN
383 else {
384 g_tree_insert (server.pids, GINT_TO_POINTER (pid), ctx);
385 }
386 #endif // HAVE_SN
387 free(cmd);
388 }
389
390 /***************** Freedesktop app.desktop and icon theme handling *********************/
391 /* http://standards.freedesktop.org/desktop-entry-spec/ */
392 /* http://standards.freedesktop.org/icon-theme-spec/ */
393
394 // Splits line at first '=' and returns 1 if successful, and parts are not empty
395 // key and value point to the parts
396 int parse_dektop_line(char *line, char **key, char **value)
397 {
398 char *p;
399 int found = 0;
400 *key = line;
401 for (p = line; *p; p++) {
402 if (*p == '=') {
403 *value = p + 1;
404 *p = 0;
405 found = 1;
406 break;
407 }
408 }
409 if (!found)
410 return 0;
411 if (found && (strlen(*key) == 0 || strlen(*value) == 0))
412 return 0;
413 return 1;
414 }
415
416 int parse_theme_line(char *line, char **key, char **value)
417 {
418 return parse_dektop_line(line, key, value);
419 }
420
421 void expand_exec(DesktopEntry *entry, const char *path)
422 {
423 // Expand % in exec
424 // %i -> --icon Icon
425 // %c -> Name
426 // %k -> path
427 if (entry->exec) {
428 char *exec2 = malloc(strlen(entry->exec) + (entry->name ? strlen(entry->name) : 1) + (entry->icon ? strlen(entry->icon) : 1) + 100);
429 char *p, *q;
430 // p will never point to an escaped char
431 for (p = entry->exec, q = exec2; *p; p++, q++) {
432 *q = *p; // Copy
433 if (*p == '\\') {
434 p++, q++;
435 // Copy the escaped char
436 if (*p == '%') // For % we delete the backslash, i.e. write % over it
437 q--;
438 *q = *p;
439 if (!*p) break;
440 continue;
441 }
442 if (*p == '%') {
443 p++;
444 if (!*p) break;
445 if (*p == 'i' && entry->icon != NULL) {
446 sprintf(q, "--icon '%s'", entry->icon);
447 q += strlen("--icon ''");
448 q += strlen(entry->icon);
449 q--; // To balance the q++ in the for
450 } else if (*p == 'c' && entry->name != NULL) {
451 sprintf(q, "'%s'", entry->name);
452 q += strlen("''");
453 q += strlen(entry->name);
454 q--; // To balance the q++ in the for
455 } else if (*p == 'c') {
456 sprintf(q, "'%s'", path);
457 q += strlen("''");
458 q += strlen(path);
459 q--; // To balance the q++ in the for
460 } else {
461 // We don't care about other expansions
462 q--; // Delete the last % from q
463 }
464 continue;
465 }
466 }
467 *q = '\0';
468 free(entry->exec);
469 entry->exec = exec2;
470 }
471 }
472
473 int launcher_read_desktop_file(const char *path, DesktopEntry *entry)
474 {
475 FILE *fp;
476 char *line = NULL;
477 size_t line_size;
478 char *key, *value;
479 int i;
480
481 entry->name = entry->icon = entry->exec = NULL;
482
483 if ((fp = fopen(path, "rt")) == NULL) {
484 fprintf(stderr, "Could not open file %s\n", path);
485 return 0;
486 }
487
488 gchar **languages = (gchar **)g_get_language_names();
489 // lang_index is the index of the language for the best Name key in the language vector
490 // lang_index_default is a constant that encodes the Name key without a language
491 int lang_index, lang_index_default;
492 #define LANG_DBG 0
493 if (LANG_DBG) printf("Languages:");
494 for (i = 0; languages[i]; i++) {
495 if (LANG_DBG) printf(" %s", languages[i]);
496 }
497 if (LANG_DBG) printf("\n");
498 lang_index_default = i;
499 // we currently do not know about any Name key at all, so use an invalid index
500 lang_index = lang_index_default + 1;
501
502 int inside_desktop_entry = 0;
503 while (getline(&line, &line_size, fp) >= 0) {
504 int len = strlen(line);
505 if (len == 0)
506 continue;
507 line[len - 1] = '\0';
508 if (line[0] == '[') {
509 inside_desktop_entry = (strcmp(line, "[Desktop Entry]") == 0);
510 }
511 if (inside_desktop_entry && parse_dektop_line(line, &key, &value)) {
512 if (strstr(key, "Name") == key) {
513 if (strcmp(key, "Name") == 0 && lang_index > lang_index_default) {
514 entry->name = strdup(value);
515 lang_index = lang_index_default;
516 } else {
517 for (i = 0; languages[i] && i < lang_index; i++) {
518 gchar *localized_key = g_strdup_printf("Name[%s]", languages[i]);
519 if (strcmp(key, localized_key) == 0) {
520 if (entry->name)
521 free(entry->name);
522 entry->name = strdup(value);
523 lang_index = i;
524 }
525 g_free(localized_key);
526 }
527 }
528 } else if (!entry->exec && strcmp(key, "Exec") == 0) {
529 entry->exec = strdup(value);
530 } else if (!entry->icon && strcmp(key, "Icon") == 0) {
531 entry->icon = strdup(value);
532 }
533 }
534 }
535 fclose (fp);
536 // From this point:
537 // entry->name, entry->icon, entry->exec will never be empty strings (can be NULL though)
538
539 expand_exec(entry, path);
540
541 free(line);
542 return 1;
543 }
544
545 void free_desktop_entry(DesktopEntry *entry)
546 {
547 free(entry->name);
548 free(entry->icon);
549 free(entry->exec);
550 }
551
552 void test_launcher_read_desktop_file()
553 {
554 fprintf(stdout, "\033[1;33m");
555 DesktopEntry entry;
556 launcher_read_desktop_file("/usr/share/applications/firefox.desktop", &entry);
557 printf("Name:%s Icon:%s Exec:%s\n", entry.name, entry.icon, entry.exec);
558 fprintf(stdout, "\033[0m");
559 }
560
561 //TODO Use UTF8 when parsing the file
562 IconTheme *load_theme(char *name)
563 {
564 // Look for name/index.theme in $HOME/.icons, /usr/share/icons, /usr/share/pixmaps (stop at the first found)
565 // Parse index.theme -> list of IconThemeDir with attributes
566 // Return IconTheme*
567
568 IconTheme *theme;
569 char *file_name;
570 FILE *f;
571 char *line = NULL;
572 size_t line_size;
573
574 if (name == NULL)
575 return NULL;
576
577 file_name = g_build_filename(g_get_home_dir(), ".icons", name, "index.theme", NULL);
578 if (!g_file_test(file_name, G_FILE_TEST_EXISTS)) {
579 g_free (file_name);
580 file_name = g_build_filename("/usr/share/icons", name, "index.theme", NULL);
581 if (!g_file_test(file_name, G_FILE_TEST_EXISTS)) {
582 g_free (file_name);
583 file_name = g_build_filename("/usr/share/pixmaps", name, "index.theme", NULL);
584 if (!g_file_test(file_name, G_FILE_TEST_EXISTS)) {
585 g_free (file_name);
586 file_name = NULL;
587 }
588 }
589 }
590
591 if (!file_name) {
592 return NULL;
593 }
594
595 if ((f = fopen(file_name, "rt")) == NULL) {
596 fprintf(stderr, "Could not open theme '%s'\n", file_name);
597 return NULL;
598 }
599
600 g_free (file_name);
601
602 theme = calloc(1, sizeof(IconTheme));
603 theme->name = strdup(name);
604 theme->list_inherits = NULL;
605 theme->list_directories = NULL;
606
607 IconThemeDir *current_dir = NULL;
608 int inside_header = 1;
609 while (getline(&line, &line_size, f) >= 0) {
610 char *key, *value;
611
612 int line_len = strlen(line);
613 if (line_len >= 1) {
614 if (line[line_len - 1] == '\n') {
615 line[line_len - 1] = '\0';
616 line_len--;
617 }
618 }
619
620 if (line_len == 0)
621 continue;
622
623 if (inside_header) {
624 if (parse_theme_line(line, &key, &value)) {
625 if (strcmp(key, "Inherits") == 0) {
626 // value is like oxygen,wood,default
627 char *token;
628 token = strtok(value, ",\n");
629 while (token != NULL)
630 {
631 theme->list_inherits = g_slist_append(theme->list_inherits, strdup(token));
632 token = strtok(NULL, ",\n");
633 }
634 } else if (strcmp(key, "Directories") == 0) {
635 // value is like 48x48/apps,48x48/mimetypes,32x32/apps,scalable/apps,scalable/mimetypes
636 char *token;
637 token = strtok(value, ",\n");
638 while (token != NULL)
639 {
640 IconThemeDir *dir = calloc(1, sizeof(IconThemeDir));
641 dir->name = strdup(token);
642 dir->max_size = dir->min_size = dir->size = -1;
643 dir->type = ICON_DIR_TYPE_THRESHOLD;
644 dir->threshold = 2;
645 theme->list_directories = g_slist_append(theme->list_directories, dir);
646 token = strtok(NULL, ",\n");
647 }
648 }
649 }
650 } else if (current_dir != NULL) {
651 if (parse_theme_line(line, &key, &value)) {
652 if (strcmp(key, "Size") == 0) {
653 // value is like 24
654 sscanf(value, "%d", &current_dir->size);
655 if (current_dir->max_size == -1)
656 current_dir->max_size = current_dir->size;
657 if (current_dir->min_size == -1)
658 current_dir->min_size = current_dir->size;
659 } else if (strcmp(key, "MaxSize") == 0) {
660 // value is like 24
661 sscanf(value, "%d", &current_dir->max_size);
662 } else if (strcmp(key, "MinSize") == 0) {
663 // value is like 24
664 sscanf(value, "%d", &current_dir->min_size);
665 } else if (strcmp(key, "Threshold") == 0) {
666 // value is like 2
667 sscanf(value, "%d", &current_dir->threshold);
668 } else if (strcmp(key, "Type") == 0) {
669 // value is Fixed, Scalable or Threshold : default to scalable for unknown Type.
670 if (strcmp(value, "Fixed") == 0) {
671 current_dir->type = ICON_DIR_TYPE_FIXED;
672 } else if (strcmp(value, "Threshold") == 0) {
673 current_dir->type = ICON_DIR_TYPE_THRESHOLD;
674 } else {
675 current_dir->type = ICON_DIR_TYPE_SCALABLE;
676 }
677 } else if (strcmp(key, "Context") == 0) {
678 // usual values: Actions, Applications, Devices, FileSystems, MimeTypes
679 current_dir->context = strdup(value);
680 }
681 }
682 }
683
684 if (line[0] == '[' && line[line_len - 1] == ']' && strcmp(line, "[Icon Theme]") != 0) {
685 inside_header = 0;
686 current_dir = NULL;
687 line[line_len - 1] = '\0';
688 char *dir_name = line + 1;
689 GSList* dir_item = theme->list_directories;
690 while (dir_item != NULL)
691 {
692 IconThemeDir *dir = dir_item->data;
693 if (strcmp(dir->name, dir_name) == 0) {
694 current_dir = dir;
695 break;
696 }
697 dir_item = g_slist_next(dir_item);
698 }
699 }
700 }
701 fclose(f);
702
703 free(line);
704
705 return theme;
706 }
707
708 void free_icon_theme(IconTheme *theme)
709 {
710 free(theme->name);
711 GSList *l_inherits;
712 for (l_inherits = theme->list_inherits; l_inherits ; l_inherits = l_inherits->next) {
713 free(l_inherits->data);
714 }
715 GSList *l_dir;
716 for (l_dir = theme->list_directories; l_dir ; l_dir = l_dir->next) {
717 IconThemeDir *dir = (IconThemeDir *)l_dir->data;
718 free(dir->name);
719 free(dir->context);
720 free(l_dir->data);
721 }
722 }
723
724 void test_launcher_read_theme_file()
725 {
726 fprintf(stdout, "\033[1;33m");
727 IconTheme *theme = load_theme("oxygen");
728 if (!theme) {
729 printf("Could not load theme\n");
730 return;
731 }
732 printf("Loaded theme: %s\n", theme->name);
733 GSList* item = theme->list_inherits;
734 while (item != NULL)
735 {
736 printf("Inherits:%s\n", (char*)item->data);
737 item = g_slist_next(item);
738 }
739 item = theme->list_directories;
740 while (item != NULL)
741 {
742 IconThemeDir *dir = item->data;
743 printf("Dir:%s Size=%d MinSize=%d MaxSize=%d Threshold=%d Type=%s Context=%s\n",
744 dir->name, dir->size, dir->min_size, dir->max_size, dir->threshold,
745 dir->type == ICON_DIR_TYPE_FIXED ? "Fixed" :
746 dir->type == ICON_DIR_TYPE_SCALABLE ? "Scalable" :
747 dir->type == ICON_DIR_TYPE_THRESHOLD ? "Threshold" : "?????",
748 dir->context);
749 item = g_slist_next(item);
750 }
751 fprintf(stdout, "\033[0m");
752 }
753
754
755 // Populates the list_icons list
756 void launcher_load_icons(Launcher *launcher)
757 {
758 // Load apps (.desktop style launcher items)
759 GSList* app = launcher->list_apps;
760 while (app != NULL) {
761 DesktopEntry entry;
762 launcher_read_desktop_file(app->data, &entry);
763 if (entry.exec) {
764 LauncherIcon *launcherIcon = calloc(1, sizeof(LauncherIcon));
765 launcherIcon->area.parent = launcher;
766 launcherIcon->area.panel = launcher->area.panel;
767 launcherIcon->area._draw_foreground = draw_launcher_icon;
768 launcherIcon->area.size_mode = SIZE_BY_CONTENT;
769 launcherIcon->area._resize = NULL;
770 launcherIcon->area.resize = 0;
771 launcherIcon->area.redraw = 1;
772 launcherIcon->area.bg = &g_array_index(backgrounds, Background, 0);
773 launcherIcon->area.on_screen = 1;
774 launcherIcon->area._on_change_layout = launcher_icon_on_change_layout;
775 if (launcher_tooltip_enabled)
776 launcherIcon->area._get_tooltip_text = launcher_icon_get_tooltip_text;
777 else
778 launcherIcon->area._get_tooltip_text = NULL;
779 launcherIcon->is_app_desktop = 1;
780 launcherIcon->cmd = strdup(entry.exec);
781 launcherIcon->icon_name = entry.icon ? strdup(entry.icon) : strdup(ICON_FALLBACK);
782 launcherIcon->icon_size = 1;
783 launcherIcon->icon_tooltip = entry.name ? strdup(entry.name) : strdup(entry.exec);
784 free_desktop_entry(&entry);
785 launcher->list_icons = g_slist_append(launcher->list_icons, launcherIcon);
786 add_area(&launcherIcon->area);
787 }
788 app = g_slist_next(app);
789 }
790 }
791
792
793 // Populates the list_themes list
794 void launcher_load_themes(Launcher *launcher)
795 {
796 // load the user theme, all the inherited themes recursively (DFS), and the hicolor theme
797 // avoid inheritance loops
798 if (!icon_theme_name) {
799 fprintf(stderr, "Missing launcher theme, default to 'hicolor'.\n");
800 icon_theme_name = strdup("hicolor");
801 } else {
802 fprintf(stderr, "Loading %s. Icon theme :", icon_theme_name);
803 }
804
805 GSList *queue = g_slist_append(NULL, strdup(icon_theme_name));
806 GSList *queued = g_slist_append(NULL, strdup(icon_theme_name));
807
808 int hicolor_loaded = 0;
809 while (queue || !hicolor_loaded) {
810 if (!queue) {
811 GSList* queued_item = queued;
812 while (queued_item != NULL) {
813 if (strcmp(queued_item->data, "hicolor") == 0) {
814 hicolor_loaded = 1;
815 break;
816 }
817 queued_item = g_slist_next(queued_item);
818 }
819 if (hicolor_loaded)
820 break;
821 queue = g_slist_append(queue, strdup("hicolor"));
822 queued = g_slist_append(queued, strdup("hicolor"));
823 }
824
825 char *name = queue->data;
826 queue = g_slist_remove(queue, name);
827
828 fprintf(stderr, " '%s',", name);
829 IconTheme *theme = load_theme(name);
830 if (theme != NULL) {
831 launcher->list_themes = g_slist_append(launcher->list_themes, theme);
832
833 GSList* item = theme->list_inherits;
834 int pos = 0;
835 while (item != NULL)
836 {
837 char *parent = item->data;
838 int duplicate = 0;
839 GSList* queued_item = queued;
840 while (queued_item != NULL) {
841 if (strcmp(queued_item->data, parent) == 0) {
842 duplicate = 1;
843 break;
844 }
845 queued_item = g_slist_next(queued_item);
846 }
847 if (!duplicate) {
848 queue = g_slist_insert(queue, strdup(parent), pos);
849 pos++;
850 queued = g_slist_append(queued, strdup(parent));
851 }
852 item = g_slist_next(item);
853 }
854 }
855 }
856 fprintf(stderr, "\n");
857
858 // Free the queue
859 GSList *l;
860 for (l = queue; l ; l = l->next)
861 free(l->data);
862 g_slist_free(queue);
863 for (l = queued; l ; l = l->next)
864 free(l->data);
865 g_slist_free(queued);
866 }
867
868 int directory_matches_size(IconThemeDir *dir, int size)
869 {
870 if (dir->type == ICON_DIR_TYPE_FIXED) {
871 return dir->size == size;
872 } else if (dir->type == ICON_DIR_TYPE_SCALABLE) {
873 return dir->min_size <= size && size <= dir->max_size;
874 } else /*if (dir->type == ICON_DIR_TYPE_THRESHOLD)*/ {
875 return dir->size - dir->threshold <= size && size <= dir->size + dir->threshold;
876 }
877 }
878
879 int directory_size_distance(IconThemeDir *dir, int size)
880 {
881 if (dir->type == ICON_DIR_TYPE_FIXED) {
882 return abs(dir->size - size);
883 } else if (dir->type == ICON_DIR_TYPE_SCALABLE) {
884 if (size < dir->min_size) {
885 return dir->min_size - size;
886 } else if (size > dir->max_size) {
887 return size - dir->max_size;
888 } else {
889 return 0;
890 }
891 } else /*if (dir->type == ICON_DIR_TYPE_THRESHOLD)*/ {
892 if (size < dir->size - dir->threshold) {
893 return dir->min_size - size;
894 } else if (size > dir->size + dir->threshold) {
895 return size - dir->max_size;
896 } else {
897 return 0;
898 }
899 }
900 }
901
902 #define DEBUG_ICON_SEARCH 0
903 // Returns the full path to an icon file (or NULL) given the icon name
904 char *icon_path(Launcher *launcher, const char *icon_name, int size)
905 {
906 if (icon_name == NULL)
907 return NULL;
908
909 // If the icon_name is already a path and the file exists, return it
910 if (strstr(icon_name, "/") == icon_name) {
911 if (g_file_test(icon_name, G_FILE_TEST_EXISTS))
912 return strdup(icon_name);
913 else
914 return NULL;
915 }
916
917 GSList *basenames = NULL;
918 char *home_icons = g_build_filename(g_get_home_dir(), ".icons", NULL);
919 basenames = g_slist_append(basenames, home_icons);
920 char *home_local_icons = g_build_filename(g_get_home_dir(), ".local/share/icons", NULL);
921 basenames = g_slist_append(basenames, home_local_icons);
922 basenames = g_slist_append(basenames, "/usr/local/share/icons");
923 basenames = g_slist_append(basenames, "/usr/local/share/pixmaps");
924 basenames = g_slist_append(basenames, "/usr/share/icons");
925 basenames = g_slist_append(basenames, "/usr/share/pixmaps");
926
927 GSList *extensions = NULL;
928 extensions = g_slist_append(extensions, ".png");
929 extensions = g_slist_append(extensions, ".xpm");
930 // if the icon name already contains one of the extensions (e.g. vlc.png instead of vlc) add a special entry
931 GSList *ext;
932 for (ext = extensions; ext; ext = g_slist_next(ext)) {
933 char *extension = (char*) ext->data;
934 if (strlen(icon_name) > strlen(extension) &&
935 strcmp(extension, icon_name + strlen(icon_name) - strlen(extension)) == 0) {
936 extensions = g_slist_append(extensions, "");
937 break;
938 }
939 }
940
941 GSList *theme;
942 // Stage 1: exact size match
943 // the theme must have a higher priority than having an exact size match, so we will just use
944 // the code that searches for the best size match (it will find the exact size match if one exists)
945 /*
946 for (theme = launcher->list_themes; theme; theme = g_slist_next(theme)) {
947 GSList *dir;
948 for (dir = ((IconTheme*)theme->data)->list_directories; dir; dir = g_slist_next(dir)) {
949 if (directory_matches_size((IconThemeDir*)dir->data, size)) {
950 GSList *base;
951 for (base = basenames; base; base = g_slist_next(base)) {
952 GSList *ext;
953 for (ext = extensions; ext; ext = g_slist_next(ext)) {
954 char *base_name = (char*) base->data;
955 char *theme_name = ((IconTheme*)theme->data)->name;
956 char *dir_name = ((IconThemeDir*)dir->data)->name;
957 char *extension = (char*) ext->data;
958 char *file_name = malloc(strlen(base_name) + strlen(theme_name) +
959 strlen(dir_name) + strlen(icon_name) + strlen(extension) + 100);
960 // filename = directory/$(themename)/subdirectory/iconname.extension
961 sprintf(file_name, "%s/%s/%s/%s%s", base_name, theme_name, dir_name, icon_name, extension);
962 //printf("found exact: %s\n", file_name);
963 //printf("checking %s\n", file_name);
964 if (g_file_test(file_name, G_FILE_TEST_EXISTS)) {
965 g_slist_free(basenames);
966 g_slist_free(extensions);
967 g_free(home_icons);
968 g_free(home_local_icons);
969 return file_name;
970 } else {
971 free(file_name);
972 file_name = NULL;
973 }
974 }
975 }
976 }
977 }
978 }
979 g_free (file_name);
980 */
981
982 // Stage 2: best size match
983 // Contrary to the freedesktop spec, we are not choosing the closest icon in size, but the next larger icon
984 // otherwise the quality is usually crap (for size 22, if you can choose 16 or 32, you're better with 32)
985 // We do fallback to the closest size if we cannot find a larger or equal icon
986
987 // These 3 variables are used for keeping the closest size match
988 int minimal_size = INT_MAX;
989 char *best_file_name = NULL;
990 GSList *best_file_theme = NULL;
991
992 // These 3 variables are used for keeping the next larger match
993 int next_larger_size = -1;
994 char *next_larger = NULL;
995 GSList *next_larger_theme = NULL;
996
997 for (theme = launcher->list_themes; theme; theme = g_slist_next(theme)) {
998 GSList *dir;
999 for (dir = ((IconTheme*)theme->data)->list_directories; dir; dir = g_slist_next(dir)) {
1000 GSList *base;
1001 for (base = basenames; base; base = g_slist_next(base)) {
1002 GSList *ext;
1003 for (ext = extensions; ext; ext = g_slist_next(ext)) {
1004 char *base_name = (char*) base->data;
1005 char *theme_name = ((IconTheme*)theme->data)->name;
1006 char *dir_name = ((IconThemeDir*)dir->data)->name;
1007 char *extension = (char*) ext->data;
1008 char *file_name = malloc(strlen(base_name) + strlen(theme_name) +
1009 strlen(dir_name) + strlen(icon_name) + strlen(extension) + 100);
1010 // filename = directory/$(themename)/subdirectory/iconname.extension
1011 sprintf(file_name, "%s/%s/%s/%s%s", base_name, theme_name, dir_name, icon_name, extension);
1012 if (DEBUG_ICON_SEARCH)
1013 printf("checking %s\n", file_name);
1014 if (g_file_test(file_name, G_FILE_TEST_EXISTS)) {
1015 if (DEBUG_ICON_SEARCH)
1016 printf("found: %s\n", file_name);
1017 // Closest match
1018 if (directory_size_distance((IconThemeDir*)dir->data, size) < minimal_size && (!best_file_theme ? 1 : theme == best_file_theme)) {
1019 if (best_file_name) {
1020 free(best_file_name);
1021 best_file_name = NULL;
1022 }
1023 best_file_name = strdup(file_name);
1024 minimal_size = directory_size_distance((IconThemeDir*)dir->data, size);
1025 best_file_theme = theme;
1026 if (DEBUG_ICON_SEARCH)
1027 printf("best_file_name = %s; minimal_size = %d\n", best_file_name, minimal_size);
1028 }
1029 // Next larger match
1030 if (((IconThemeDir*)dir->data)->size >= size &&
1031 (next_larger_size == -1 || ((IconThemeDir*)dir->data)->size < next_larger_size) &&
1032 (!next_larger_theme ? 1 : theme == next_larger_theme)) {
1033 if (next_larger) {
1034 free(next_larger);
1035 next_larger = NULL;
1036 }
1037 next_larger = strdup(file_name);
1038 next_larger_size = ((IconThemeDir*)dir->data)->size;
1039 next_larger_theme = theme;
1040 if (DEBUG_ICON_SEARCH)
1041 printf("next_larger = %s; next_larger_size = %d\n", next_larger, next_larger_size);
1042 }
1043 }
1044 free(file_name);
1045 }
1046 }
1047 }
1048 }
1049 if (next_larger) {
1050 g_slist_free(basenames);
1051 g_slist_free(extensions);
1052 free(best_file_name);
1053 g_free(home_icons);
1054 g_free(home_local_icons);
1055 return next_larger;
1056 }
1057 if (best_file_name) {
1058 g_slist_free(basenames);
1059 g_slist_free(extensions);
1060 g_free(home_icons);
1061 g_free(home_local_icons);
1062 return best_file_name;
1063 }
1064
1065 // Stage 3: look in unthemed icons
1066 {
1067 GSList *base;
1068 for (base = basenames; base; base = g_slist_next(base)) {
1069 GSList *ext;
1070 for (ext = extensions; ext; ext = g_slist_next(ext)) {
1071 char *base_name = (char*) base->data;
1072 char *extension = (char*) ext->data;
1073 char *file_name = malloc(strlen(base_name) + strlen(icon_name) +
1074 strlen(extension) + 100);
1075 // filename = directory/iconname.extension
1076 sprintf(file_name, "%s/%s%s", base_name, icon_name, extension);
1077 if (DEBUG_ICON_SEARCH)
1078 printf("checking %s\n", file_name);
1079 if (g_file_test(file_name, G_FILE_TEST_EXISTS)) {
1080 g_slist_free(basenames);
1081 g_slist_free(extensions);
1082 g_free(home_icons);
1083 g_free(home_local_icons);
1084 return file_name;
1085 } else {
1086 free(file_name);
1087 file_name = NULL;
1088 }
1089 }
1090 }
1091 }
1092
1093 fprintf(stderr, "Could not find icon %s\n", icon_name);
1094
1095 g_slist_free(basenames);
1096 g_slist_free(extensions);
1097 g_free(home_icons);
1098 g_free(home_local_icons);
1099 return NULL;
1100 }
1101
This page took 0.086495 seconds and 4 git commands to generate.