]> git.proxmox.com Git - grub2.git/blame - gfxmenu/theme_loader.c
Merge mainline into bidi
[grub2.git] / gfxmenu / theme_loader.c
CommitLineData
d920a32a
CB
1/* theme_loader.c - Theme file loader for gfxmenu. */
2/*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008 Free Software Foundation, Inc.
5 *
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <grub/types.h>
21#include <grub/file.h>
22#include <grub/misc.h>
23#include <grub/mm.h>
24#include <grub/err.h>
25#include <grub/dl.h>
26#include <grub/video.h>
27#include <grub/gui_string_util.h>
28#include <grub/bitmap.h>
29#include <grub/bitmap_scale.h>
30#include <grub/gfxwidgets.h>
31#include <grub/gfxmenu_view.h>
32#include <grub/gui.h>
33
34/* Construct a new box widget using ABSPATTERN to find the pixmap files for
35 it, storing the new box instance at *BOXPTR.
36 PATTERN should be of the form: "(hd0,0)/somewhere/style*.png".
37 The '*' then gets substituted with the various pixmap names that the
38 box uses. */
39static grub_err_t
40recreate_box_absolute (grub_gfxmenu_box_t *boxptr, const char *abspattern)
41{
42 char *prefix;
43 char *suffix;
44 char *star;
45 grub_gfxmenu_box_t box;
46
47 star = grub_strchr (abspattern, '*');
48 if (! star)
49 return grub_error (GRUB_ERR_BAD_ARGUMENT,
50 "missing `*' in box pixmap pattern `%s'", abspattern);
51
52 /* Prefix: Get the part before the '*'. */
53 prefix = grub_malloc (star - abspattern + 1);
54 if (! prefix)
55 return grub_errno;
56
57 grub_memcpy (prefix, abspattern, star - abspattern);
58 prefix[star - abspattern] = '\0';
59
60 /* Suffix: Everything after the '*' is the suffix. */
61 suffix = star + 1;
62
63 box = grub_gfxmenu_create_box (prefix, suffix);
64 grub_free (prefix);
65 if (! box)
66 return grub_errno;
67
68 if (*boxptr)
69 (*boxptr)->destroy (*boxptr);
70 *boxptr = box;
71 return grub_errno;
72}
73
74
75/* Construct a new box widget using PATTERN to find the pixmap files for it,
76 storing the new widget at *BOXPTR. PATTERN should be of the form:
77 "somewhere/style*.png". The '*' then gets substituted with the various
78 pixmap names that the widget uses.
79
80 Important! The value of *BOXPTR must be initialized! It must either
81 (1) Be 0 (a NULL pointer), or
82 (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance.
83 In this case, the previous instance is destroyed. */
84grub_err_t
85grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr,
86 const char *pattern, const char *theme_dir)
87{
88 char *abspattern;
89
90 /* Check arguments. */
91 if (! pattern)
92 {
93 /* If no pixmap pattern is given, then just create an empty box. */
94 if (*boxptr)
95 (*boxptr)->destroy (*boxptr);
96 *boxptr = grub_gfxmenu_create_box (0, 0);
97 return grub_errno;
98 }
99
100 if (! theme_dir)
101 return grub_error (GRUB_ERR_BAD_ARGUMENT,
102 "styled box missing theme directory");
103
104 /* Resolve to an absolute path. */
105 abspattern = grub_resolve_relative_path (theme_dir, pattern);
106 if (! abspattern)
107 return grub_errno;
108
109 /* Create the box. */
110 recreate_box_absolute (boxptr, abspattern);
111 grub_free (abspattern);
112 return grub_errno;
113}
114
115/* Set the specified property NAME on the view to the given string VALUE.
116 The caller is responsible for the lifetimes of NAME and VALUE. */
117static grub_err_t
118theme_set_string (grub_gfxmenu_view_t view,
119 const char *name,
120 const char *value,
121 const char *theme_dir,
122 const char *filename,
123 int line_num,
124 int col_num)
125{
126 if (! grub_strcmp ("title-font", name))
127 view->title_font = grub_font_get (value);
128 else if (! grub_strcmp ("message-font", name))
129 view->message_font = grub_font_get (value);
130 else if (! grub_strcmp ("terminal-font", name))
131 {
132 grub_free (view->terminal_font_name);
133 view->terminal_font_name = grub_strdup (value);
134 if (! view->terminal_font_name)
135 return grub_errno;
136 }
137 else if (! grub_strcmp ("title-color", name))
138 grub_gui_parse_color (value, &view->title_color);
139 else if (! grub_strcmp ("message-color", name))
140 grub_gui_parse_color (value, &view->message_color);
141 else if (! grub_strcmp ("message-bg-color", name))
142 grub_gui_parse_color (value, &view->message_bg_color);
143 else if (! grub_strcmp ("desktop-image", name))
144 {
145 struct grub_video_bitmap *raw_bitmap;
146 struct grub_video_bitmap *scaled_bitmap;
147 char *path;
148 path = grub_resolve_relative_path (theme_dir, value);
149 if (! path)
150 return grub_errno;
151 if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
152 {
153 grub_free (path);
154 return grub_errno;
155 }
156 grub_free(path);
157 grub_video_bitmap_create_scaled (&scaled_bitmap,
158 view->screen.width,
159 view->screen.height,
160 raw_bitmap,
161 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
162 grub_video_bitmap_destroy (raw_bitmap);
163 if (! scaled_bitmap)
164 {
165 grub_error_push ();
166 return grub_error (grub_errno, "error scaling desktop image");
167 }
168
169 grub_video_bitmap_destroy (view->desktop_image);
170 view->desktop_image = scaled_bitmap;
171 }
172 else if (! grub_strcmp ("desktop-color", name))
173 grub_gui_parse_color (value, &view->desktop_color);
174 else if (! grub_strcmp ("terminal-box", name))
175 {
176 grub_err_t err;
177 err = grub_gui_recreate_box (&view->terminal_box, value, theme_dir);
178 if (err != GRUB_ERR_NONE)
179 return err;
180 }
181 else if (! grub_strcmp ("title-text", name))
182 {
183 grub_free (view->title_text);
184 view->title_text = grub_strdup (value);
185 if (! view->title_text)
186 return grub_errno;
187 }
188 else
189 {
190 return grub_error (GRUB_ERR_BAD_ARGUMENT,
191 "%s:%d:%d unknown property `%s'",
192 filename, line_num, col_num, name);
193 }
194 return grub_errno;
195}
196
197struct parsebuf
198{
199 char *buf;
200 int pos;
201 int len;
202 int line_num;
203 int col_num;
204 const char *filename;
205 char *theme_dir;
206 grub_gfxmenu_view_t view;
207};
208
209static int
210has_more (struct parsebuf *p)
211{
212 return p->pos < p->len;
213}
214
215static int
216read_char (struct parsebuf *p)
217{
218 if (has_more (p))
219 {
220 char c;
221 c = p->buf[p->pos++];
222 if (c == '\n')
223 {
224 p->line_num++;
225 p->col_num = 1;
226 }
227 else
228 {
229 p->col_num++;
230 }
231 return c;
232 }
233 else
234 return -1;
235}
236
237static int
238peek_char (struct parsebuf *p)
239{
240 if (has_more (p))
241 return p->buf[p->pos];
242 else
243 return -1;
244}
245
246static int
247is_whitespace (char c)
248{
249 return (c == ' '
250 || c == '\t'
251 || c == '\r'
252 || c == '\n'
253 || c == '\f');
254}
255
256static void
257skip_whitespace (struct parsebuf *p)
258{
259 while (has_more (p) && is_whitespace(peek_char (p)))
260 read_char (p);
261}
262
263static void
264advance_to_next_line (struct parsebuf *p)
265{
266 int c;
267
268 /* Eat characters up to the newline. */
269 do
270 {
271 c = read_char (p);
272 }
273 while (c != -1 && c != '\n');
274}
275
276static int
277is_identifier_char (int c)
278{
279 return (c != -1
280 && (grub_isalpha(c)
281 || grub_isdigit(c)
282 || c == '_'
283 || c == '-'));
284}
285
286static char *
287read_identifier (struct parsebuf *p)
288{
289 /* Index of the first character of the identifier in p->buf. */
290 int start;
291 /* Next index after the last character of the identifer in p->buf. */
292 int end;
293
294 skip_whitespace (p);
295
296 /* Capture the start of the identifier. */
297 start = p->pos;
298
299 /* Scan for the end. */
300 while (is_identifier_char (peek_char (p)))
301 read_char (p);
302 end = p->pos;
303
304 if (end - start < 1)
305 return 0;
306
307 return grub_new_substring (p->buf, start, end);
308}
309
310static char *
311read_expression (struct parsebuf *p)
312{
313 int start;
314 int end;
315
316 skip_whitespace (p);
317 if (peek_char (p) == '"')
318 {
319 /* Read as a quoted string.
320 The quotation marks are not included in the expression value. */
321 /* Skip opening quotation mark. */
322 read_char (p);
323 start = p->pos;
324 while (has_more (p) && peek_char (p) != '"')
325 read_char (p);
326 end = p->pos;
327 /* Skip the terminating quotation mark. */
328 read_char (p);
329 }
330 else if (peek_char (p) == '(')
331 {
332 /* Read as a parenthesized string -- for tuples/coordinates. */
333 /* The parentheses are included in the expression value. */
334 int c;
335
336 start = p->pos;
337 do
338 {
339 c = read_char (p);
340 }
341 while (c != -1 && c != ')');
342 end = p->pos;
343 }
344 else if (has_more (p))
345 {
346 /* Read as a single word -- for numeric values or words without
347 whitespace. */
348 start = p->pos;
349 while (has_more (p) && ! is_whitespace (peek_char (p)))
350 read_char (p);
351 end = p->pos;
352 }
353 else
354 {
355 /* The end of the theme file has been reached. */
356 grub_error (GRUB_ERR_IO, "%s:%d:%d expression expected in theme file",
357 p->filename, p->line_num, p->col_num);
358 return 0;
359 }
360
361 return grub_new_substring (p->buf, start, end);
362}
363
b9da1700
VS
364static grub_err_t
365parse_proportional_spec (char *value, signed *abs, grub_fixed_signed_t *prop)
366{
367 signed num;
368 char *ptr;
369 int sig = 0;
370 *abs = 0;
371 *prop = 0;
372 ptr = value;
373 while (*ptr)
374 {
375 sig = 0;
376
377 while (*ptr == '-' || *ptr == '+')
378 {
379 if (*ptr == '-')
380 sig = !sig;
381 ptr++;
382 }
383
384 num = grub_strtoul (ptr, &ptr, 0);
385 if (grub_errno)
386 return grub_errno;
387 if (sig)
388 num = -num;
389 if (*ptr == '%')
6812d2e7
VS
390 {
391 *prop += grub_fixed_fsf_divide (grub_signed_to_fixed (num), 100);
392 ptr++;
393 }
b9da1700
VS
394 else
395 *abs += num;
396 }
397 return GRUB_ERR_NONE;
398}
399
400
d920a32a
CB
401/* Read a GUI object specification from the theme file.
402 Any components created will be added to the GUI container PARENT. */
403static grub_err_t
404read_object (struct parsebuf *p, grub_gui_container_t parent)
405{
406 grub_video_rect_t bounds;
407
408 char *name;
409 name = read_identifier (p);
410 if (! name)
411 goto cleanup;
412
413 grub_gui_component_t component = 0;
414 if (grub_strcmp (name, "label") == 0)
415 {
416 component = grub_gui_label_new ();
417 }
418 else if (grub_strcmp (name, "image") == 0)
419 {
420 component = grub_gui_image_new ();
421 }
422 else if (grub_strcmp (name, "vbox") == 0)
423 {
424 component = (grub_gui_component_t) grub_gui_vbox_new ();
425 }
426 else if (grub_strcmp (name, "hbox") == 0)
427 {
428 component = (grub_gui_component_t) grub_gui_hbox_new ();
429 }
430 else if (grub_strcmp (name, "canvas") == 0)
431 {
432 component = (grub_gui_component_t) grub_gui_canvas_new ();
433 }
434 else if (grub_strcmp (name, "progress_bar") == 0)
435 {
436 component = grub_gui_progress_bar_new ();
437 }
438 else if (grub_strcmp (name, "circular_progress") == 0)
439 {
440 component = grub_gui_circular_progress_new ();
441 }
442 else if (grub_strcmp (name, "boot_menu") == 0)
443 {
444 component = grub_gui_list_new ();
445 }
446 else
447 {
448 /* Unknown type. */
449 grub_error (GRUB_ERR_IO, "%s:%d:%d unknown object type `%s'",
450 p->filename, p->line_num, p->col_num, name);
451 goto cleanup;
452 }
453
454 if (! component)
455 goto cleanup;
456
457 /* Inform the component about the theme so it can find its resources. */
458 component->ops->set_property (component, "theme_dir", p->theme_dir);
459 component->ops->set_property (component, "theme_path", p->filename);
460
461 /* Add the component as a child of PARENT. */
462 bounds.x = 0;
463 bounds.y = 0;
464 bounds.width = -1;
465 bounds.height = -1;
466 component->ops->set_bounds (component, &bounds);
467 parent->ops->add (parent, component);
468
469 skip_whitespace (p);
470 if (read_char (p) != '{')
471 {
472 grub_error (GRUB_ERR_IO,
473 "%s:%d:%d expected `{' after object type name `%s'",
474 p->filename, p->line_num, p->col_num, name);
475 goto cleanup;
476 }
477
478 while (has_more (p))
479 {
480 skip_whitespace (p);
481
482 /* Check whether the end has been encountered. */
483 if (peek_char (p) == '}')
484 {
485 /* Skip the closing brace. */
486 read_char (p);
487 break;
488 }
489
490 if (peek_char (p) == '#')
491 {
492 /* Skip comments. */
493 advance_to_next_line (p);
494 continue;
495 }
496
497 if (peek_char (p) == '+')
498 {
499 /* Skip the '+'. */
500 read_char (p);
501
502 /* Check whether this component is a container. */
503 if (component->ops->is_instance (component, "container"))
504 {
505 /* Read the sub-object recursively and add it as a child. */
506 if (read_object (p, (grub_gui_container_t) component) != 0)
507 goto cleanup;
508 /* After reading the sub-object, resume parsing, expecting
509 another property assignment or sub-object definition. */
510 continue;
511 }
512 else
513 {
514 grub_error (GRUB_ERR_IO,
515 "%s:%d:%d attempted to add object to non-container",
516 p->filename, p->line_num, p->col_num);
517 goto cleanup;
518 }
519 }
520
521 char *property;
522 property = read_identifier (p);
523 if (! property)
524 {
525 grub_error (GRUB_ERR_IO, "%s:%d:%d identifier expected in theme file",
526 p->filename, p->line_num, p->col_num);
527 goto cleanup;
528 }
529
530 skip_whitespace (p);
531 if (read_char (p) != '=')
532 {
533 grub_error (GRUB_ERR_IO,
534 "%s:%d:%d expected `=' after property name `%s'",
535 p->filename, p->line_num, p->col_num, property);
536 grub_free (property);
537 goto cleanup;
538 }
539 skip_whitespace (p);
540
541 char *value;
542 value = read_expression (p);
543 if (! value)
544 {
545 grub_free (property);
546 goto cleanup;
547 }
548
549 /* Handle the property value. */
9a175884 550 if (grub_strcmp (property, "left") == 0)
b9da1700 551 parse_proportional_spec (value, &component->x, &component->xfrac);
9a175884 552 else if (grub_strcmp (property, "top") == 0)
b9da1700 553 parse_proportional_spec (value, &component->y, &component->yfrac);
9a175884 554 else if (grub_strcmp (property, "width") == 0)
b9da1700 555 parse_proportional_spec (value, &component->w, &component->wfrac);
9a175884 556 else if (grub_strcmp (property, "height") == 0)
b9da1700 557 parse_proportional_spec (value, &component->h, &component->hfrac);
d920a32a 558 else
b9da1700
VS
559 /* General property handling. */
560 component->ops->set_property (component, property, value);
d920a32a
CB
561
562 grub_free (value);
563 grub_free (property);
564 if (grub_errno != GRUB_ERR_NONE)
565 goto cleanup;
566 }
567
d920a32a
CB
568cleanup:
569 grub_free (name);
570 return grub_errno;
571}
572
573static grub_err_t
574read_property (struct parsebuf *p)
575{
576 char *name;
577
578 /* Read the property name. */
579 name = read_identifier (p);
580 if (! name)
581 {
582 advance_to_next_line (p);
583 return grub_errno;
584 }
585
586 /* Skip whitespace before separator. */
587 skip_whitespace (p);
588
589 /* Read separator. */
590 if (read_char (p) != ':')
591 {
592 grub_error (GRUB_ERR_IO,
593 "%s:%d:%d missing separator after property name `%s'",
594 p->filename, p->line_num, p->col_num, name);
595 goto done;
596 }
597
598 /* Skip whitespace after separator. */
599 skip_whitespace (p);
600
601 /* Get the value based on its type. */
602 if (peek_char (p) == '"')
603 {
604 /* String value (e.g., '"My string"'). */
605 char *value = read_expression (p);
606 if (! value)
607 {
608 grub_error (GRUB_ERR_IO, "%s:%d:%d missing property value",
609 p->filename, p->line_num, p->col_num);
610 goto done;
611 }
612 /* If theme_set_string results in an error, grub_errno will be returned
613 below. */
614 theme_set_string (p->view, name, value, p->theme_dir,
615 p->filename, p->line_num, p->col_num);
616 grub_free (value);
617 }
618 else
619 {
620 grub_error (GRUB_ERR_IO,
621 "%s:%d:%d property value invalid; "
622 "enclose literal values in quotes (\")",
623 p->filename, p->line_num, p->col_num);
624 goto done;
625 }
626
627done:
628 grub_free (name);
629 return grub_errno;
630}
631
632/* Set properties on the view based on settings from the specified
633 theme file. */
634grub_err_t
635grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
636{
637 grub_file_t file;
638 struct parsebuf p;
639
640 p.view = view;
641 p.theme_dir = grub_get_dirname (theme_path);
642
643 file = grub_file_open (theme_path);
644 if (! file)
645 {
646 grub_free (p.theme_dir);
647 return grub_errno;
648 }
649
650 p.len = grub_file_size (file);
651 p.buf = grub_malloc (p.len);
652 p.pos = 0;
653 p.line_num = 1;
654 p.col_num = 1;
655 p.filename = theme_path;
656 if (! p.buf)
657 {
658 grub_file_close (file);
659 grub_free (p.theme_dir);
660 return grub_errno;
661 }
662 if (grub_file_read (file, p.buf, p.len) != p.len)
663 {
664 grub_free (p.buf);
665 grub_file_close (file);
666 grub_free (p.theme_dir);
667 return grub_errno;
668 }
669
670 if (view->canvas)
9a175884 671 view->canvas->component.ops->destroy (view->canvas);
d920a32a
CB
672
673 view->canvas = grub_gui_canvas_new ();
674 ((grub_gui_component_t) view->canvas)
675 ->ops->set_bounds ((grub_gui_component_t) view->canvas,
676 &view->screen);
677
678 while (has_more (&p))
679 {
680 /* Skip comments (lines beginning with #). */
681 if (peek_char (&p) == '#')
682 {
683 advance_to_next_line (&p);
684 continue;
685 }
686
687 /* Find the first non-whitespace character. */
688 skip_whitespace (&p);
689
690 /* Handle the content. */
691 if (peek_char (&p) == '+')
692 {
693 /* Skip the '+'. */
694 read_char (&p);
695 read_object (&p, view->canvas);
696 }
697 else
698 {
699 read_property (&p);
700 }
701
702 if (grub_errno != GRUB_ERR_NONE)
703 goto fail;
704 }
705
706 /* Set the new theme path. */
707 grub_free (view->theme_path);
708 view->theme_path = grub_strdup (theme_path);
709 goto cleanup;
710
711fail:
712 if (view->canvas)
713 {
9a175884 714 view->canvas->component.ops->destroy (view->canvas);
d920a32a
CB
715 view->canvas = 0;
716 }
717
718cleanup:
719 grub_free (p.buf);
720 grub_file_close (file);
721 grub_free (p.theme_dir);
722 return grub_errno;
723}