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>
33 #include <grub/color.h>
37 parse_proportional_spec (const char *value
, signed *abs
, grub_fixed_signed_t
*prop
);
39 /* Construct a new box widget using ABSPATTERN to find the pixmap files for
40 it, storing the new box instance at *BOXPTR.
41 PATTERN should be of the form: "(hd0,0)/somewhere/style*.png".
42 The '*' then gets substituted with the various pixmap names that the
45 recreate_box_absolute (grub_gfxmenu_box_t
*boxptr
, const char *abspattern
)
50 grub_gfxmenu_box_t box
;
52 star
= grub_strchr (abspattern
, '*');
54 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
55 "missing `*' in box pixmap pattern `%s'", abspattern
);
57 /* Prefix: Get the part before the '*'. */
58 prefix
= grub_malloc (star
- abspattern
+ 1);
62 grub_memcpy (prefix
, abspattern
, star
- abspattern
);
63 prefix
[star
- abspattern
] = '\0';
65 /* Suffix: Everything after the '*' is the suffix. */
68 box
= grub_gfxmenu_create_box (prefix
, suffix
);
74 (*boxptr
)->destroy (*boxptr
);
80 /* Construct a new box widget using PATTERN to find the pixmap files for it,
81 storing the new widget at *BOXPTR. PATTERN should be of the form:
82 "somewhere/style*.png". The '*' then gets substituted with the various
83 pixmap names that the widget uses.
85 Important! The value of *BOXPTR must be initialized! It must either
86 (1) Be 0 (a NULL pointer), or
87 (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance.
88 In this case, the previous instance is destroyed. */
90 grub_gui_recreate_box (grub_gfxmenu_box_t
*boxptr
,
91 const char *pattern
, const char *theme_dir
)
95 /* Check arguments. */
98 /* If no pixmap pattern is given, then just create an empty box. */
100 (*boxptr
)->destroy (*boxptr
);
101 *boxptr
= grub_gfxmenu_create_box (0, 0);
106 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
107 "styled box missing theme directory");
109 /* Resolve to an absolute path. */
110 abspattern
= grub_resolve_relative_path (theme_dir
, pattern
);
114 /* Create the box. */
115 recreate_box_absolute (boxptr
, abspattern
);
116 grub_free (abspattern
);
121 theme_get_unsigned_int_from_proportional (const char *value
,
122 unsigned absolute_value
,
123 unsigned int *parsed_value
)
126 grub_fixed_signed_t frac
;
128 err
= parse_proportional_spec (value
, &pixels
, &frac
);
129 if (err
!= GRUB_ERR_NONE
)
131 int result
= grub_fixed_sfs_multiply (absolute_value
, frac
) + pixels
;
134 *parsed_value
= result
;
135 return GRUB_ERR_NONE
;
138 /* Set the specified property NAME on the view to the given string VALUE.
139 The caller is responsible for the lifetimes of NAME and VALUE. */
141 theme_set_string (grub_gfxmenu_view_t view
,
144 const char *theme_dir
,
145 const char *filename
,
149 if (! grub_strcmp ("title-font", name
))
150 view
->title_font
= grub_font_get (value
);
151 else if (! grub_strcmp ("message-font", name
))
152 view
->message_font
= grub_font_get (value
);
153 else if (! grub_strcmp ("terminal-font", name
))
155 grub_free (view
->terminal_font_name
);
156 view
->terminal_font_name
= grub_strdup (value
);
157 if (! view
->terminal_font_name
)
160 else if (! grub_strcmp ("title-color", name
))
161 grub_video_parse_color (value
, &view
->title_color
);
162 else if (! grub_strcmp ("message-color", name
))
163 grub_video_parse_color (value
, &view
->message_color
);
164 else if (! grub_strcmp ("message-bg-color", name
))
165 grub_video_parse_color (value
, &view
->message_bg_color
);
166 else if (! grub_strcmp("menu-tip-left", name
))
167 grub_env_set("VTOY_TIP_LEFT", value
);
168 else if (! grub_strcmp("menu-tip-top", name
))
169 grub_env_set("VTOY_TIP_TOP", value
);
170 else if (! grub_strcmp("menu-tip-color", name
))
171 grub_env_set("VTOY_TIP_COLOR", value
);
172 else if (! grub_strcmp ("desktop-image", name
))
174 struct grub_video_bitmap
*raw_bitmap
;
176 path
= grub_resolve_relative_path (theme_dir
, value
);
179 if (grub_video_bitmap_load (&raw_bitmap
, path
) != GRUB_ERR_NONE
)
185 grub_video_bitmap_destroy (view
->raw_desktop_image
);
186 view
->raw_desktop_image
= raw_bitmap
;
188 else if (! grub_strcmp ("desktop-image-scale-method", name
))
190 if (! value
|| ! grub_strcmp ("stretch", value
))
191 view
->desktop_image_scale_method
=
192 GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH
;
193 else if (! grub_strcmp ("crop", value
))
194 view
->desktop_image_scale_method
=
195 GRUB_VIDEO_BITMAP_SELECTION_METHOD_CROP
;
196 else if (! grub_strcmp ("padding", value
))
197 view
->desktop_image_scale_method
=
198 GRUB_VIDEO_BITMAP_SELECTION_METHOD_PADDING
;
199 else if (! grub_strcmp ("fitwidth", value
))
200 view
->desktop_image_scale_method
=
201 GRUB_VIDEO_BITMAP_SELECTION_METHOD_FITWIDTH
;
202 else if (! grub_strcmp ("fitheight", value
))
203 view
->desktop_image_scale_method
=
204 GRUB_VIDEO_BITMAP_SELECTION_METHOD_FITHEIGHT
;
206 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
207 "Unsupported scale method: %s",
210 else if (! grub_strcmp ("desktop-image-h-align", name
))
212 if (! grub_strcmp ("left", value
))
213 view
->desktop_image_h_align
= GRUB_VIDEO_BITMAP_H_ALIGN_LEFT
;
214 else if (! grub_strcmp ("center", value
))
215 view
->desktop_image_h_align
= GRUB_VIDEO_BITMAP_H_ALIGN_CENTER
;
216 else if (! grub_strcmp ("right", value
))
217 view
->desktop_image_h_align
= GRUB_VIDEO_BITMAP_H_ALIGN_RIGHT
;
219 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
220 "Unsupported horizontal align method: %s",
223 else if (! grub_strcmp ("desktop-image-v-align", name
))
225 if (! grub_strcmp ("top", value
))
226 view
->desktop_image_v_align
= GRUB_VIDEO_BITMAP_V_ALIGN_TOP
;
227 else if (! grub_strcmp ("center", value
))
228 view
->desktop_image_v_align
= GRUB_VIDEO_BITMAP_V_ALIGN_CENTER
;
229 else if (! grub_strcmp ("bottom", value
))
230 view
->desktop_image_v_align
= GRUB_VIDEO_BITMAP_V_ALIGN_BOTTOM
;
232 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
233 "Unsupported vertical align method: %s",
236 else if (! grub_strcmp ("desktop-color", name
))
237 grub_video_parse_color (value
, &view
->desktop_color
);
238 else if (! grub_strcmp ("terminal-box", name
))
241 err
= grub_gui_recreate_box (&view
->terminal_box
, value
, theme_dir
);
242 if (err
!= GRUB_ERR_NONE
)
245 else if (! grub_strcmp ("terminal-border", name
))
247 view
->terminal_border
= grub_strtoul (value
, 0, 10);
251 else if (! grub_strcmp ("terminal-left", name
))
254 int err
= theme_get_unsigned_int_from_proportional (value
,
257 if (err
!= GRUB_ERR_NONE
)
259 view
->terminal_rect
.x
= tmp
;
261 else if (! grub_strcmp ("terminal-top", name
))
264 int err
= theme_get_unsigned_int_from_proportional (value
,
267 if (err
!= GRUB_ERR_NONE
)
269 view
->terminal_rect
.y
= tmp
;
271 else if (! grub_strcmp ("terminal-width", name
))
274 int err
= theme_get_unsigned_int_from_proportional (value
,
277 if (err
!= GRUB_ERR_NONE
)
279 view
->terminal_rect
.width
= tmp
;
281 else if (! grub_strcmp ("terminal-height", name
))
284 int err
= theme_get_unsigned_int_from_proportional (value
,
287 if (err
!= GRUB_ERR_NONE
)
289 view
->terminal_rect
.height
= tmp
;
291 else if (! grub_strcmp ("title-text", name
))
293 grub_free (view
->title_text
);
294 view
->title_text
= grub_strdup (value
);
295 if (! view
->title_text
)
300 return grub_error (GRUB_ERR_BAD_ARGUMENT
,
301 "%s:%d:%d unknown property `%s'",
302 filename
, line_num
, col_num
, name
);
314 const char *filename
;
316 grub_gfxmenu_view_t view
;
320 has_more (struct parsebuf
*p
)
322 return p
->pos
< p
->len
;
326 read_char (struct parsebuf
*p
)
331 c
= p
->buf
[p
->pos
++];
348 peek_char (struct parsebuf
*p
)
351 return p
->buf
[p
->pos
];
357 is_whitespace (char c
)
367 skip_whitespace (struct parsebuf
*p
)
369 while (has_more (p
) && is_whitespace(peek_char (p
)))
374 advance_to_next_line (struct parsebuf
*p
)
378 /* Eat characters up to the newline. */
383 while (c
!= -1 && c
!= '\n');
387 is_identifier_char (int c
)
397 read_identifier (struct parsebuf
*p
)
399 /* Index of the first character of the identifier in p->buf. */
401 /* Next index after the last character of the identifer in p->buf. */
406 /* Capture the start of the identifier. */
409 /* Scan for the end. */
410 while (is_identifier_char (peek_char (p
)))
417 return grub_new_substring (p
->buf
, start
, end
);
421 read_expression (struct parsebuf
*p
)
427 if (peek_char (p
) == '"')
429 /* Read as a quoted string.
430 The quotation marks are not included in the expression value. */
431 /* Skip opening quotation mark. */
434 while (has_more (p
) && peek_char (p
) != '"')
437 /* Skip the terminating quotation mark. */
440 else if (peek_char (p
) == '(')
442 /* Read as a parenthesized string -- for tuples/coordinates. */
443 /* The parentheses are included in the expression value. */
451 while (c
!= -1 && c
!= ')');
454 else if (has_more (p
))
456 /* Read as a single word -- for numeric values or words without
459 while (has_more (p
) && ! is_whitespace (peek_char (p
)))
465 /* The end of the theme file has been reached. */
466 grub_error (GRUB_ERR_IO
, "%s:%d:%d expression expected in theme file",
467 p
->filename
, p
->line_num
, p
->col_num
);
471 return grub_new_substring (p
->buf
, start
, end
);
475 parse_proportional_spec (const char *value
, signed *abs
, grub_fixed_signed_t
*prop
)
487 while (*ptr
== '-' || *ptr
== '+')
494 num
= grub_strtoul (ptr
, (char **) &ptr
, 0);
501 *prop
+= grub_fixed_fsf_divide (grub_signed_to_fixed (num
), 100);
507 return GRUB_ERR_NONE
;
511 /* Read a GUI object specification from the theme file.
512 Any components created will be added to the GUI container PARENT. */
514 read_object (struct parsebuf
*p
, grub_gui_container_t parent
)
516 grub_video_rect_t bounds
;
519 name
= read_identifier (p
);
523 grub_gui_component_t component
= 0;
524 if (grub_strcmp (name
, "label") == 0)
526 component
= grub_gui_label_new ();
528 else if (grub_strcmp (name
, "image") == 0)
530 component
= grub_gui_image_new ();
532 else if (grub_strcmp (name
, "vbox") == 0)
534 component
= (grub_gui_component_t
) grub_gui_vbox_new ();
536 else if (grub_strcmp (name
, "hbox") == 0)
538 component
= (grub_gui_component_t
) grub_gui_hbox_new ();
540 else if (grub_strcmp (name
, "canvas") == 0)
542 component
= (grub_gui_component_t
) grub_gui_canvas_new ();
544 else if (grub_strcmp (name
, "progress_bar") == 0)
546 component
= grub_gui_progress_bar_new ();
548 else if (grub_strcmp (name
, "circular_progress") == 0)
550 component
= grub_gui_circular_progress_new ();
552 else if (grub_strcmp (name
, "boot_menu") == 0)
554 component
= grub_gui_list_new ();
559 grub_error (GRUB_ERR_IO
, "%s:%d:%d unknown object type `%s'",
560 p
->filename
, p
->line_num
, p
->col_num
, name
);
567 /* Inform the component about the theme so it can find its resources. */
568 component
->ops
->set_property (component
, "theme_dir", p
->theme_dir
);
569 component
->ops
->set_property (component
, "theme_path", p
->filename
);
571 /* Add the component as a child of PARENT. */
576 component
->ops
->set_bounds (component
, &bounds
);
577 parent
->ops
->add (parent
, component
);
580 if (read_char (p
) != '{')
582 grub_error (GRUB_ERR_IO
,
583 "%s:%d:%d expected `{' after object type name `%s'",
584 p
->filename
, p
->line_num
, p
->col_num
, name
);
592 /* Check whether the end has been encountered. */
593 if (peek_char (p
) == '}')
595 /* Skip the closing brace. */
600 if (peek_char (p
) == '#')
603 advance_to_next_line (p
);
607 if (peek_char (p
) == '+')
612 /* Check whether this component is a container. */
613 if (component
->ops
->is_instance (component
, "container"))
615 /* Read the sub-object recursively and add it as a child. */
616 if (read_object (p
, (grub_gui_container_t
) component
) != 0)
618 /* After reading the sub-object, resume parsing, expecting
619 another property assignment or sub-object definition. */
624 grub_error (GRUB_ERR_IO
,
625 "%s:%d:%d attempted to add object to non-container",
626 p
->filename
, p
->line_num
, p
->col_num
);
632 property
= read_identifier (p
);
635 grub_error (GRUB_ERR_IO
, "%s:%d:%d identifier expected in theme file",
636 p
->filename
, p
->line_num
, p
->col_num
);
641 if (read_char (p
) != '=')
643 grub_error (GRUB_ERR_IO
,
644 "%s:%d:%d expected `=' after property name `%s'",
645 p
->filename
, p
->line_num
, p
->col_num
, property
);
646 grub_free (property
);
652 value
= read_expression (p
);
655 grub_free (property
);
659 /* Handle the property value. */
660 if (grub_strcmp (property
, "left") == 0)
661 parse_proportional_spec (value
, &component
->x
, &component
->xfrac
);
662 else if (grub_strcmp (property
, "top") == 0)
663 parse_proportional_spec (value
, &component
->y
, &component
->yfrac
);
664 else if (grub_strcmp (property
, "width") == 0)
665 parse_proportional_spec (value
, &component
->w
, &component
->wfrac
);
666 else if (grub_strcmp (property
, "height") == 0)
667 parse_proportional_spec (value
, &component
->h
, &component
->hfrac
);
669 /* General property handling. */
670 component
->ops
->set_property (component
, property
, value
);
673 grub_free (property
);
674 if (grub_errno
!= GRUB_ERR_NONE
)
684 read_property (struct parsebuf
*p
)
688 /* Read the property name. */
689 name
= read_identifier (p
);
692 advance_to_next_line (p
);
696 /* Skip whitespace before separator. */
699 /* Read separator. */
700 if (read_char (p
) != ':')
702 grub_error (GRUB_ERR_IO
,
703 "%s:%d:%d missing separator after property name `%s'",
704 p
->filename
, p
->line_num
, p
->col_num
, name
);
708 /* Skip whitespace after separator. */
711 /* Get the value based on its type. */
712 if (peek_char (p
) == '"')
714 /* String value (e.g., '"My string"'). */
715 char *value
= read_expression (p
);
718 grub_error (GRUB_ERR_IO
, "%s:%d:%d missing property value",
719 p
->filename
, p
->line_num
, p
->col_num
);
722 /* If theme_set_string results in an error, grub_errno will be returned
724 theme_set_string (p
->view
, name
, value
, p
->theme_dir
,
725 p
->filename
, p
->line_num
, p
->col_num
);
730 grub_error (GRUB_ERR_IO
,
731 "%s:%d:%d property value invalid; "
732 "enclose literal values in quotes (\")",
733 p
->filename
, p
->line_num
, p
->col_num
);
742 extern int g_menu_update_mode
;
744 /* Set properties on the view based on settings from the specified
747 grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view
, const char *theme_path
)
754 p
.theme_dir
= grub_get_dirname (theme_path
);
756 file
= grub_file_open (theme_path
, GRUB_FILE_TYPE_THEME
);
759 grub_free (p
.theme_dir
);
763 p
.len
= grub_file_size (file
);
764 p
.buf
= grub_malloc (p
.len
+ 8192);
768 p
.filename
= theme_path
;
771 grub_file_close (file
);
772 grub_free (p
.theme_dir
);
775 if (grub_file_read (file
, p
.buf
, p
.len
) != p
.len
)
778 grub_file_close (file
);
779 grub_free (p
.theme_dir
);
784 const char *checkret
= grub_env_get("VTOY_CHKDEV_RESULT_STRING");
785 if (checkret
== NULL
|| checkret
[0] != '0')
787 p
.len
+= grub_snprintf(p
.buf
+ p
.len
, 4096, "\n+ hbox{\n left = 1%%\n top = 90%%\n"
788 " + label {text = \"[Unofficial Ventoy]\" color = \"red\" align = \"left\"}\n"
796 view
->canvas
->component
.ops
->destroy (view
->canvas
);
798 view
->canvas
= grub_gui_canvas_new ();
801 ((grub_gui_component_t
) view
->canvas
)
802 ->ops
->set_bounds ((grub_gui_component_t
) view
->canvas
,
806 while (has_more (&p
))
808 /* Skip comments (lines beginning with #). */
809 if (peek_char (&p
) == '#')
811 advance_to_next_line (&p
);
815 /* Find the first non-whitespace character. */
816 skip_whitespace (&p
);
818 /* Handle the content. */
819 if (peek_char (&p
) == '+')
823 read_object (&p
, view
->canvas
);
830 if (grub_errno
!= GRUB_ERR_NONE
)
837 const char *tip
= grub_env_get("VTOY_MENU_TIP_ENABLE");
838 if (tip
&& tip
[0] == '1')
842 grub_memset(tmpmsg
, 'w', 500);
845 g_menu_update_mode
= 1;
846 p
.len
+= grub_snprintf(p
.buf
+ p
.len
, 4096,
847 "\n+ vbox{\n left = %s\n top = %s\n"
848 "+ label { id=\"VTOY_MENU_TIP_1\" text = \"%s\" color = \"%s\" align = \"%s\"}\n"
849 "+ label { id=\"VTOY_MENU_TIP_2\" text = \"%s\" color = \"%s\" align = \"%s\"}\n"
851 grub_env_get("VTOY_TIP_LEFT"),
852 grub_env_get("VTOY_TIP_TOP"),
854 grub_env_get("VTOY_TIP_COLOR"),
855 grub_env_get("VTOY_TIP_ALIGN"),
857 grub_env_get("VTOY_TIP_COLOR"),
858 grub_env_get("VTOY_TIP_ALIGN")
868 /* Set the new theme path. */
869 grub_free (view
->theme_path
);
870 view
->theme_path
= grub_strdup (theme_path
);
876 view
->canvas
->component
.ops
->destroy (view
->canvas
);
882 grub_file_close (file
);
883 grub_free (p
.theme_dir
);