1
/* theme_loader.c - Theme file loader for gfxmenu. */
3
* GRUB -- GRand Unified Bootloader
4
* Copyright (C) 2008 Free Software Foundation, Inc.
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.
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.
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/>.
20
#include <grub/types.h>
21
#include <grub/file.h>
22
#include <grub/misc.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>
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
40
recreate_box_absolute (grub_gfxmenu_box_t *boxptr, const char *abspattern)
45
grub_gfxmenu_box_t box;
47
star = grub_strchr (abspattern, '*');
49
return grub_error (GRUB_ERR_BAD_ARGUMENT,
50
"missing `*' in box pixmap pattern `%s'", abspattern);
52
/* Prefix: Get the part before the '*'. */
53
prefix = grub_malloc (star - abspattern + 1);
57
grub_memcpy (prefix, abspattern, star - abspattern);
58
prefix[star - abspattern] = '\0';
60
/* Suffix: Everything after the '*' is the suffix. */
63
box = grub_gfxmenu_create_box (prefix, suffix);
69
(*boxptr)->destroy (*boxptr);
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.
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. */
85
grub_gui_recreate_box (grub_gfxmenu_box_t *boxptr,
86
const char *pattern, const char *theme_dir)
90
/* Check arguments. */
93
/* If no pixmap pattern is given, then just create an empty box. */
95
(*boxptr)->destroy (*boxptr);
96
*boxptr = grub_gfxmenu_create_box (0, 0);
101
return grub_error (GRUB_ERR_BAD_ARGUMENT,
102
"styled box missing theme directory");
104
/* Resolve to an absolute path. */
105
abspattern = grub_resolve_relative_path (theme_dir, pattern);
109
/* Create the box. */
110
recreate_box_absolute (boxptr, abspattern);
111
grub_free (abspattern);
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. */
118
theme_set_string (grub_gfxmenu_view_t view,
121
const char *theme_dir,
122
const char *filename,
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))
132
grub_free (view->terminal_font_name);
133
view->terminal_font_name = grub_strdup (value);
134
if (! view->terminal_font_name)
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))
145
struct grub_video_bitmap *raw_bitmap;
146
struct grub_video_bitmap *scaled_bitmap;
148
path = grub_resolve_relative_path (theme_dir, value);
151
if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
157
grub_video_bitmap_create_scaled (&scaled_bitmap,
161
GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
162
grub_video_bitmap_destroy (raw_bitmap);
166
return grub_error (grub_errno, "error scaling desktop image");
169
grub_video_bitmap_destroy (view->desktop_image);
170
view->desktop_image = scaled_bitmap;
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))
177
err = grub_gui_recreate_box (&view->terminal_box, value, theme_dir);
178
if (err != GRUB_ERR_NONE)
181
else if (! grub_strcmp ("title-text", name))
183
grub_free (view->title_text);
184
view->title_text = grub_strdup (value);
185
if (! view->title_text)
190
return grub_error (GRUB_ERR_BAD_ARGUMENT,
191
"%s:%d:%d unknown property `%s'",
192
filename, line_num, col_num, name);
204
const char *filename;
206
grub_gfxmenu_view_t view;
210
has_more (struct parsebuf *p)
212
return p->pos < p->len;
216
read_char (struct parsebuf *p)
221
c = p->buf[p->pos++];
238
peek_char (struct parsebuf *p)
241
return p->buf[p->pos];
247
is_whitespace (char c)
257
skip_whitespace (struct parsebuf *p)
259
while (has_more (p) && is_whitespace(peek_char (p)))
264
advance_to_next_line (struct parsebuf *p)
268
/* Eat characters up to the newline. */
273
while (c != -1 && c != '\n');
277
is_identifier_char (int c)
287
read_identifier (struct parsebuf *p)
289
/* Index of the first character of the identifier in p->buf. */
291
/* Next index after the last character of the identifer in p->buf. */
296
/* Capture the start of the identifier. */
299
/* Scan for the end. */
300
while (is_identifier_char (peek_char (p)))
307
return grub_new_substring (p->buf, start, end);
311
read_expression (struct parsebuf *p)
317
if (peek_char (p) == '"')
319
/* Read as a quoted string.
320
The quotation marks are not included in the expression value. */
321
/* Skip opening quotation mark. */
324
while (has_more (p) && peek_char (p) != '"')
327
/* Skip the terminating quotation mark. */
330
else if (peek_char (p) == '(')
332
/* Read as a parenthesized string -- for tuples/coordinates. */
333
/* The parentheses are included in the expression value. */
341
while (c != -1 && c != ')');
344
else if (has_more (p))
346
/* Read as a single word -- for numeric values or words without
349
while (has_more (p) && ! is_whitespace (peek_char (p)))
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);
361
return grub_new_substring (p->buf, start, end);
365
parse_proportional_spec (char *value, signed *abs, grub_fixed_signed_t *prop)
377
while (*ptr == '-' || *ptr == '+')
384
num = grub_strtoul (ptr, &ptr, 0);
391
*prop += grub_fixed_fsf_divide (grub_signed_to_fixed (num), 100);
397
return GRUB_ERR_NONE;
401
/* Read a GUI object specification from the theme file.
402
Any components created will be added to the GUI container PARENT. */
404
read_object (struct parsebuf *p, grub_gui_container_t parent)
406
grub_video_rect_t bounds;
409
name = read_identifier (p);
413
grub_gui_component_t component = 0;
414
if (grub_strcmp (name, "label") == 0)
416
component = grub_gui_label_new ();
418
else if (grub_strcmp (name, "image") == 0)
420
component = grub_gui_image_new ();
422
else if (grub_strcmp (name, "vbox") == 0)
424
component = (grub_gui_component_t) grub_gui_vbox_new ();
426
else if (grub_strcmp (name, "hbox") == 0)
428
component = (grub_gui_component_t) grub_gui_hbox_new ();
430
else if (grub_strcmp (name, "canvas") == 0)
432
component = (grub_gui_component_t) grub_gui_canvas_new ();
434
else if (grub_strcmp (name, "progress_bar") == 0)
436
component = grub_gui_progress_bar_new ();
438
else if (grub_strcmp (name, "circular_progress") == 0)
440
component = grub_gui_circular_progress_new ();
442
else if (grub_strcmp (name, "boot_menu") == 0)
444
component = grub_gui_list_new ();
449
grub_error (GRUB_ERR_IO, "%s:%d:%d unknown object type `%s'",
450
p->filename, p->line_num, p->col_num, name);
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);
461
/* Add the component as a child of PARENT. */
466
component->ops->set_bounds (component, &bounds);
467
parent->ops->add (parent, component);
470
if (read_char (p) != '{')
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);
482
/* Check whether the end has been encountered. */
483
if (peek_char (p) == '}')
485
/* Skip the closing brace. */
490
if (peek_char (p) == '#')
493
advance_to_next_line (p);
497
if (peek_char (p) == '+')
502
/* Check whether this component is a container. */
503
if (component->ops->is_instance (component, "container"))
505
/* Read the sub-object recursively and add it as a child. */
506
if (read_object (p, (grub_gui_container_t) component) != 0)
508
/* After reading the sub-object, resume parsing, expecting
509
another property assignment or sub-object definition. */
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);
522
property = read_identifier (p);
525
grub_error (GRUB_ERR_IO, "%s:%d:%d identifier expected in theme file",
526
p->filename, p->line_num, p->col_num);
531
if (read_char (p) != '=')
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);
542
value = read_expression (p);
545
grub_free (property);
549
/* Handle the property value. */
550
if (grub_strcmp (property, "left") == 0)
551
parse_proportional_spec (value, &component->x, &component->xfrac);
552
else if (grub_strcmp (property, "top") == 0)
553
parse_proportional_spec (value, &component->y, &component->yfrac);
554
else if (grub_strcmp (property, "width") == 0)
555
parse_proportional_spec (value, &component->w, &component->wfrac);
556
else if (grub_strcmp (property, "height") == 0)
557
parse_proportional_spec (value, &component->h, &component->hfrac);
559
/* General property handling. */
560
component->ops->set_property (component, property, value);
563
grub_free (property);
564
if (grub_errno != GRUB_ERR_NONE)
574
read_property (struct parsebuf *p)
578
/* Read the property name. */
579
name = read_identifier (p);
582
advance_to_next_line (p);
586
/* Skip whitespace before separator. */
589
/* Read separator. */
590
if (read_char (p) != ':')
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);
598
/* Skip whitespace after separator. */
601
/* Get the value based on its type. */
602
if (peek_char (p) == '"')
604
/* String value (e.g., '"My string"'). */
605
char *value = read_expression (p);
608
grub_error (GRUB_ERR_IO, "%s:%d:%d missing property value",
609
p->filename, p->line_num, p->col_num);
612
/* If theme_set_string results in an error, grub_errno will be returned
614
theme_set_string (p->view, name, value, p->theme_dir,
615
p->filename, p->line_num, p->col_num);
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);
632
/* Set properties on the view based on settings from the specified
635
grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
641
p.theme_dir = grub_get_dirname (theme_path);
643
file = grub_file_open (theme_path);
646
grub_free (p.theme_dir);
650
p.len = grub_file_size (file);
651
p.buf = grub_malloc (p.len);
655
p.filename = theme_path;
658
grub_file_close (file);
659
grub_free (p.theme_dir);
662
if (grub_file_read (file, p.buf, p.len) != p.len)
665
grub_file_close (file);
666
grub_free (p.theme_dir);
671
view->canvas->component.ops->destroy (view->canvas);
673
view->canvas = grub_gui_canvas_new ();
674
((grub_gui_component_t) view->canvas)
675
->ops->set_bounds ((grub_gui_component_t) view->canvas,
678
while (has_more (&p))
680
/* Skip comments (lines beginning with #). */
681
if (peek_char (&p) == '#')
683
advance_to_next_line (&p);
687
/* Find the first non-whitespace character. */
688
skip_whitespace (&p);
690
/* Handle the content. */
691
if (peek_char (&p) == '+')
695
read_object (&p, view->canvas);
702
if (grub_errno != GRUB_ERR_NONE)
706
/* Set the new theme path. */
707
grub_free (view->theme_path);
708
view->theme_path = grub_strdup (theme_path);
714
view->canvas->component.ops->destroy (view->canvas);
720
grub_file_close (file);
721
grub_free (p.theme_dir);