2N/A/* theme_loader.c - Theme file loader for gfxmenu. */
2N/A/*
2N/A * GRUB -- GRand Unified Bootloader
2N/A * Copyright (C) 2008 Free Software Foundation, Inc.
2N/A * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
2N/A *
2N/A * GRUB is free software: you can redistribute it and/or modify
2N/A * it under the terms of the GNU General Public License as published by
2N/A * the Free Software Foundation, either version 3 of the License, or
2N/A * (at your option) any later version.
2N/A *
2N/A * GRUB is distributed in the hope that it will be useful,
2N/A * but WITHOUT ANY WARRANTY; without even the implied warranty of
2N/A * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2N/A * GNU General Public License for more details.
2N/A *
2N/A * You should have received a copy of the GNU General Public License
2N/A * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
2N/A */
2N/A
2N/A#include <grub/types.h>
2N/A#include <grub/file.h>
2N/A#include <grub/misc.h>
2N/A#include <grub/mm.h>
2N/A#include <grub/err.h>
2N/A#include <grub/dl.h>
2N/A#include <grub/video.h>
2N/A#include <grub/gui_string_util.h>
2N/A#include <grub/bitmap.h>
2N/A#include <grub/bitmap_scale.h>
2N/A#include <grub/gfxwidgets.h>
2N/A#include <grub/gfxmenu_view.h>
2N/A#include <grub/gui.h>
2N/A
2N/A/* Construct a new box widget using ABSPATTERN to find the pixmap files for
2N/A it, storing the new box instance at *BOXPTR.
2N/A PATTERN should be of the form: "(hd0,0)/somewhere/style*.png".
2N/A The '*' then gets substituted with the various pixmap names that the
2N/A box uses. */
2N/Astatic grub_err_t
2N/Arecreate_box_absolute (grub_gfxmenu_box_t *boxptr, const char *abspattern)
2N/A{
2N/A char *prefix;
2N/A char *suffix;
2N/A char *star;
2N/A grub_gfxmenu_box_t box;
2N/A
2N/A star = grub_strchr (abspattern, '*');
2N/A if (! star)
2N/A return grub_error (GRUB_ERR_BAD_ARGUMENT,
2N/A "missing `*' in box pixmap pattern `%s'", abspattern);
2N/A
2N/A /* Prefix: Get the part before the '*'. */
2N/A prefix = grub_malloc (star - abspattern + 1);
2N/A if (! prefix)
2N/A return grub_errno;
2N/A
2N/A grub_memcpy (prefix, abspattern, star - abspattern);
2N/A prefix[star - abspattern] = '\0';
2N/A
2N/A /* Suffix: Everything after the '*' is the suffix. */
2N/A suffix = star + 1;
2N/A
2N/A box = grub_gfxmenu_create_box (prefix, suffix);
2N/A grub_free (prefix);
2N/A if (! box)
2N/A return grub_errno;
2N/A
2N/A if (*boxptr)
2N/A (*boxptr)->destroy (*boxptr);
2N/A *boxptr = box;
2N/A return grub_errno;
2N/A}
2N/A
2N/A
2N/A/* Construct a new box widget using PATTERN to find the pixmap files for it,
2N/A storing the new widget at *BOXPTR. PATTERN should be of the form:
2N/A "somewhere/style*.png". The '*' then gets substituted with the various
2N/A pixmap names that the widget uses.
2N/A
2N/A Important! The value of *BOXPTR must be initialized! It must either
2N/A (1) Be 0 (a NULL pointer), or
2N/A (2) Be a pointer to a valid 'grub_gfxmenu_box_t' instance.
2N/A In this case, the previous instance is destroyed. */
2N/Agrub_err_t
2N/Agrub_gui_recreate_box (grub_gfxmenu_box_t *boxptr,
2N/A const char *pattern, const char *theme_dir)
2N/A{
2N/A char *abspattern;
2N/A
2N/A /* Check arguments. */
2N/A if (! pattern)
2N/A {
2N/A /* If no pixmap pattern is given, then just create an empty box. */
2N/A if (*boxptr)
2N/A (*boxptr)->destroy (*boxptr);
2N/A *boxptr = grub_gfxmenu_create_box (0, 0);
2N/A return grub_errno;
2N/A }
2N/A
2N/A if (! theme_dir)
2N/A return grub_error (GRUB_ERR_BAD_ARGUMENT,
2N/A "styled box missing theme directory");
2N/A
2N/A /* Resolve to an absolute path. */
2N/A abspattern = grub_resolve_relative_path (theme_dir, pattern);
2N/A if (! abspattern)
2N/A return grub_errno;
2N/A
2N/A /* Create the box. */
2N/A recreate_box_absolute (boxptr, abspattern);
2N/A grub_free (abspattern);
2N/A return grub_errno;
2N/A}
2N/A
2N/A/* Set the specified property NAME on the view to the given string VALUE.
2N/A The caller is responsible for the lifetimes of NAME and VALUE. */
2N/Astatic grub_err_t
2N/Atheme_set_string (grub_gfxmenu_view_t view,
2N/A const char *name,
2N/A const char *value,
2N/A const char *theme_dir,
2N/A const char *filename,
2N/A int line_num,
2N/A int col_num)
2N/A{
2N/A if (! grub_strcmp ("title-font", name))
2N/A view->title_font = grub_font_get (value);
2N/A else if (! grub_strcmp ("message-font", name))
2N/A view->message_font = grub_font_get (value);
2N/A else if (! grub_strcmp ("terminal-font", name))
2N/A {
2N/A grub_free (view->terminal_font_name);
2N/A view->terminal_font_name = grub_strdup (value);
2N/A if (! view->terminal_font_name)
2N/A return grub_errno;
2N/A }
2N/A else if (! grub_strcmp ("title-color", name))
2N/A grub_video_parse_color (value, &view->title_color);
2N/A else if (! grub_strcmp ("message-color", name))
2N/A grub_video_parse_color (value, &view->message_color);
2N/A else if (! grub_strcmp ("message-bg-color", name))
2N/A grub_video_parse_color (value, &view->message_bg_color);
2N/A else if (! grub_strcmp ("desktop-image", name))
2N/A {
2N/A struct grub_video_bitmap *raw_bitmap;
2N/A struct grub_video_bitmap *scaled_bitmap;
2N/A char *path;
2N/A path = grub_resolve_relative_path (theme_dir, value);
2N/A if (! path)
2N/A return grub_errno;
2N/A if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
2N/A {
2N/A grub_free (path);
2N/A return grub_errno;
2N/A }
2N/A grub_free(path);
2N/A grub_video_bitmap_create_scaled (&scaled_bitmap,
2N/A view->screen.width,
2N/A view->screen.height,
2N/A raw_bitmap,
2N/A GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
2N/A grub_video_bitmap_destroy (raw_bitmap);
2N/A if (! scaled_bitmap)
2N/A {
2N/A grub_error_push ();
2N/A return grub_error (grub_errno, "error scaling desktop image");
2N/A }
2N/A
2N/A grub_video_bitmap_destroy (view->desktop_image);
2N/A view->desktop_image = scaled_bitmap;
2N/A }
2N/A else if (! grub_strcmp ("desktop-color", name))
2N/A grub_video_parse_color (value, &view->desktop_color);
2N/A else if (! grub_strcmp ("terminal-box", name))
2N/A {
2N/A grub_err_t err;
2N/A err = grub_gui_recreate_box (&view->terminal_box, value, theme_dir);
2N/A if (err != GRUB_ERR_NONE)
2N/A return err;
2N/A }
2N/A else if (! grub_strcmp ("title-text", name))
2N/A {
2N/A grub_free (view->title_text);
2N/A view->title_text = grub_strdup (value);
2N/A if (! view->title_text)
2N/A return grub_errno;
2N/A }
2N/A else
2N/A {
2N/A return grub_error (GRUB_ERR_BAD_ARGUMENT,
2N/A "%s:%d:%d unknown property `%s'",
2N/A filename, line_num, col_num, name);
2N/A }
2N/A return grub_errno;
2N/A}
2N/A
2N/Astruct parsebuf
2N/A{
2N/A char *buf;
2N/A int pos;
2N/A int len;
2N/A int line_num;
2N/A int col_num;
2N/A const char *filename;
2N/A char *theme_dir;
2N/A grub_gfxmenu_view_t view;
2N/A};
2N/A
2N/Astatic int
2N/Ahas_more (struct parsebuf *p)
2N/A{
2N/A return p->pos < p->len;
2N/A}
2N/A
2N/Astatic int
2N/Aread_char (struct parsebuf *p)
2N/A{
2N/A if (has_more (p))
2N/A {
2N/A char c;
2N/A c = p->buf[p->pos++];
2N/A if (c == '\n')
2N/A {
2N/A p->line_num++;
2N/A p->col_num = 1;
2N/A }
2N/A else
2N/A {
2N/A p->col_num++;
2N/A }
2N/A return c;
2N/A }
2N/A else
2N/A return -1;
2N/A}
2N/A
2N/Astatic int
2N/Apeek_char (struct parsebuf *p)
2N/A{
2N/A if (has_more (p))
2N/A return p->buf[p->pos];
2N/A else
2N/A return -1;
2N/A}
2N/A
2N/Astatic int
2N/Ais_whitespace (char c)
2N/A{
2N/A return (c == ' '
2N/A || c == '\t'
2N/A || c == '\r'
2N/A || c == '\n'
2N/A || c == '\f');
2N/A}
2N/A
2N/Astatic void
2N/Askip_whitespace (struct parsebuf *p)
2N/A{
2N/A while (has_more (p) && is_whitespace(peek_char (p)))
2N/A read_char (p);
2N/A}
2N/A
2N/Astatic void
2N/Aadvance_to_next_line (struct parsebuf *p)
2N/A{
2N/A int c;
2N/A
2N/A /* Eat characters up to the newline. */
2N/A do
2N/A {
2N/A c = read_char (p);
2N/A }
2N/A while (c != -1 && c != '\n');
2N/A}
2N/A
2N/Astatic int
2N/Ais_identifier_char (int c)
2N/A{
2N/A return (c != -1
2N/A && (grub_isalpha(c)
2N/A || grub_isdigit(c)
2N/A || c == '_'
2N/A || c == '-'));
2N/A}
2N/A
2N/Astatic char *
2N/Aread_identifier (struct parsebuf *p)
2N/A{
2N/A /* Index of the first character of the identifier in p->buf. */
2N/A int start;
2N/A /* Next index after the last character of the identifer in p->buf. */
2N/A int end;
2N/A
2N/A skip_whitespace (p);
2N/A
2N/A /* Capture the start of the identifier. */
2N/A start = p->pos;
2N/A
2N/A /* Scan for the end. */
2N/A while (is_identifier_char (peek_char (p)))
2N/A read_char (p);
2N/A end = p->pos;
2N/A
2N/A if (end - start < 1)
2N/A return 0;
2N/A
2N/A return grub_new_substring (p->buf, start, end);
2N/A}
2N/A
2N/Astatic char *
2N/Aread_expression (struct parsebuf *p)
2N/A{
2N/A int start;
2N/A int end;
2N/A
2N/A skip_whitespace (p);
2N/A if (peek_char (p) == '"')
2N/A {
2N/A /* Read as a quoted string.
2N/A The quotation marks are not included in the expression value. */
2N/A /* Skip opening quotation mark. */
2N/A read_char (p);
2N/A start = p->pos;
2N/A while (has_more (p) && peek_char (p) != '"')
2N/A read_char (p);
2N/A end = p->pos;
2N/A /* Skip the terminating quotation mark. */
2N/A read_char (p);
2N/A }
2N/A else if (peek_char (p) == '(')
2N/A {
2N/A /* Read as a parenthesized string -- for tuples/coordinates. */
2N/A /* The parentheses are included in the expression value. */
2N/A int c;
2N/A
2N/A start = p->pos;
2N/A do
2N/A {
2N/A c = read_char (p);
2N/A }
2N/A while (c != -1 && c != ')');
2N/A end = p->pos;
2N/A }
2N/A else if (has_more (p))
2N/A {
2N/A /* Read as a single word -- for numeric values or words without
2N/A whitespace. */
2N/A start = p->pos;
2N/A while (has_more (p) && ! is_whitespace (peek_char (p)))
2N/A read_char (p);
2N/A end = p->pos;
2N/A }
2N/A else
2N/A {
2N/A /* The end of the theme file has been reached. */
2N/A grub_error (GRUB_ERR_IO, "%s:%d:%d expression expected in theme file",
2N/A p->filename, p->line_num, p->col_num);
2N/A return 0;
2N/A }
2N/A
2N/A return grub_new_substring (p->buf, start, end);
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Aparse_proportional_spec (char *value, signed *abs, grub_fixed_signed_t *prop)
2N/A{
2N/A signed num;
2N/A char *ptr;
2N/A int sig = 0;
2N/A *abs = 0;
2N/A *prop = 0;
2N/A ptr = value;
2N/A while (*ptr)
2N/A {
2N/A sig = 0;
2N/A
2N/A while (*ptr == '-' || *ptr == '+')
2N/A {
2N/A if (*ptr == '-')
2N/A sig = !sig;
2N/A ptr++;
2N/A }
2N/A
2N/A num = grub_strtoul (ptr, &ptr, 0);
2N/A if (grub_errno)
2N/A return grub_errno;
2N/A if (sig)
2N/A num = -num;
2N/A if (*ptr == '%')
2N/A {
2N/A *prop += grub_fixed_fsf_divide (grub_signed_to_fixed (num), 100);
2N/A ptr++;
2N/A }
2N/A else
2N/A *abs += num;
2N/A }
2N/A return GRUB_ERR_NONE;
2N/A}
2N/A
2N/A
2N/A/* Read a GUI object specification from the theme file.
2N/A Any components created will be added to the GUI container PARENT. */
2N/Astatic grub_err_t
2N/Aread_object (struct parsebuf *p, grub_gui_container_t parent)
2N/A{
2N/A grub_video_rect_t bounds;
2N/A
2N/A char *name;
2N/A name = read_identifier (p);
2N/A if (! name)
2N/A goto cleanup;
2N/A
2N/A grub_gui_component_t component = 0;
2N/A if (grub_strcmp (name, "label") == 0)
2N/A {
2N/A component = grub_gui_label_new ();
2N/A }
2N/A else if (grub_strcmp (name, "image") == 0)
2N/A {
2N/A component = grub_gui_image_new ();
2N/A }
2N/A else if (grub_strcmp (name, "vbox") == 0)
2N/A {
2N/A component = (grub_gui_component_t) grub_gui_vbox_new ();
2N/A }
2N/A else if (grub_strcmp (name, "hbox") == 0)
2N/A {
2N/A component = (grub_gui_component_t) grub_gui_hbox_new ();
2N/A }
2N/A else if (grub_strcmp (name, "canvas") == 0)
2N/A {
2N/A component = (grub_gui_component_t) grub_gui_canvas_new ();
2N/A }
2N/A else if (grub_strcmp (name, "progress_bar") == 0)
2N/A {
2N/A component = grub_gui_progress_bar_new ();
2N/A }
2N/A else if (grub_strcmp (name, "circular_progress") == 0)
2N/A {
2N/A component = grub_gui_circular_progress_new ();
2N/A }
2N/A else if (grub_strcmp (name, "boot_menu") == 0)
2N/A {
2N/A component = grub_gui_list_new ();
2N/A }
2N/A else
2N/A {
2N/A /* Unknown type. */
2N/A grub_error (GRUB_ERR_IO, "%s:%d:%d unknown object type `%s'",
2N/A p->filename, p->line_num, p->col_num, name);
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (! component)
2N/A goto cleanup;
2N/A
2N/A /* Inform the component about the theme so it can find its resources. */
2N/A component->ops->set_property (component, "theme_dir", p->theme_dir);
2N/A component->ops->set_property (component, "theme_path", p->filename);
2N/A
2N/A /* Add the component as a child of PARENT. */
2N/A bounds.x = 0;
2N/A bounds.y = 0;
2N/A bounds.width = -1;
2N/A bounds.height = -1;
2N/A component->ops->set_bounds (component, &bounds);
2N/A parent->ops->add (parent, component);
2N/A
2N/A skip_whitespace (p);
2N/A if (read_char (p) != '{')
2N/A {
2N/A grub_error (GRUB_ERR_IO,
2N/A "%s:%d:%d expected `{' after object type name `%s'",
2N/A p->filename, p->line_num, p->col_num, name);
2N/A goto cleanup;
2N/A }
2N/A
2N/A while (has_more (p))
2N/A {
2N/A skip_whitespace (p);
2N/A
2N/A /* Check whether the end has been encountered. */
2N/A if (peek_char (p) == '}')
2N/A {
2N/A /* Skip the closing brace. */
2N/A read_char (p);
2N/A break;
2N/A }
2N/A
2N/A if (peek_char (p) == '#')
2N/A {
2N/A /* Skip comments. */
2N/A advance_to_next_line (p);
2N/A continue;
2N/A }
2N/A
2N/A if (peek_char (p) == '+')
2N/A {
2N/A /* Skip the '+'. */
2N/A read_char (p);
2N/A
2N/A /* Check whether this component is a container. */
2N/A if (component->ops->is_instance (component, "container"))
2N/A {
2N/A /* Read the sub-object recursively and add it as a child. */
2N/A if (read_object (p, (grub_gui_container_t) component) != 0)
2N/A goto cleanup;
2N/A /* After reading the sub-object, resume parsing, expecting
2N/A another property assignment or sub-object definition. */
2N/A continue;
2N/A }
2N/A else
2N/A {
2N/A grub_error (GRUB_ERR_IO,
2N/A "%s:%d:%d attempted to add object to non-container",
2N/A p->filename, p->line_num, p->col_num);
2N/A goto cleanup;
2N/A }
2N/A }
2N/A
2N/A char *property;
2N/A property = read_identifier (p);
2N/A if (! property)
2N/A {
2N/A grub_error (GRUB_ERR_IO, "%s:%d:%d identifier expected in theme file",
2N/A p->filename, p->line_num, p->col_num);
2N/A goto cleanup;
2N/A }
2N/A
2N/A skip_whitespace (p);
2N/A if (read_char (p) != '=')
2N/A {
2N/A grub_error (GRUB_ERR_IO,
2N/A "%s:%d:%d expected `=' after property name `%s'",
2N/A p->filename, p->line_num, p->col_num, property);
2N/A grub_free (property);
2N/A goto cleanup;
2N/A }
2N/A skip_whitespace (p);
2N/A
2N/A char *value;
2N/A value = read_expression (p);
2N/A if (! value)
2N/A {
2N/A grub_free (property);
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* Handle the property value. */
2N/A if (grub_strcmp (property, "left") == 0)
2N/A parse_proportional_spec (value, &component->x, &component->xfrac);
2N/A else if (grub_strcmp (property, "top") == 0)
2N/A parse_proportional_spec (value, &component->y, &component->yfrac);
2N/A else if (grub_strcmp (property, "width") == 0)
2N/A parse_proportional_spec (value, &component->w, &component->wfrac);
2N/A else if (grub_strcmp (property, "height") == 0)
2N/A parse_proportional_spec (value, &component->h, &component->hfrac);
2N/A else
2N/A /* General property handling. */
2N/A component->ops->set_property (component, property, value);
2N/A
2N/A grub_free (value);
2N/A grub_free (property);
2N/A if (grub_errno != GRUB_ERR_NONE)
2N/A goto cleanup;
2N/A }
2N/A
2N/Acleanup:
2N/A grub_free (name);
2N/A return grub_errno;
2N/A}
2N/A
2N/Astatic grub_err_t
2N/Aread_property (struct parsebuf *p)
2N/A{
2N/A char *name;
2N/A
2N/A /* Read the property name. */
2N/A name = read_identifier (p);
2N/A if (! name)
2N/A {
2N/A advance_to_next_line (p);
2N/A return grub_errno;
2N/A }
2N/A
2N/A /* Skip whitespace before separator. */
2N/A skip_whitespace (p);
2N/A
2N/A /* Read separator. */
2N/A if (read_char (p) != ':')
2N/A {
2N/A grub_error (GRUB_ERR_IO,
2N/A "%s:%d:%d missing separator after property name `%s'",
2N/A p->filename, p->line_num, p->col_num, name);
2N/A goto done;
2N/A }
2N/A
2N/A /* Skip whitespace after separator. */
2N/A skip_whitespace (p);
2N/A
2N/A /* Get the value based on its type. */
2N/A if (peek_char (p) == '"')
2N/A {
2N/A /* String value (e.g., '"My string"'). */
2N/A char *value = read_expression (p);
2N/A if (! value)
2N/A {
2N/A grub_error (GRUB_ERR_IO, "%s:%d:%d missing property value",
2N/A p->filename, p->line_num, p->col_num);
2N/A goto done;
2N/A }
2N/A /* If theme_set_string results in an error, grub_errno will be returned
2N/A below. */
2N/A theme_set_string (p->view, name, value, p->theme_dir,
2N/A p->filename, p->line_num, p->col_num);
2N/A grub_free (value);
2N/A }
2N/A else
2N/A {
2N/A grub_error (GRUB_ERR_IO,
2N/A "%s:%d:%d property value invalid; "
2N/A "enclose literal values in quotes (\")",
2N/A p->filename, p->line_num, p->col_num);
2N/A goto done;
2N/A }
2N/A
2N/Adone:
2N/A grub_free (name);
2N/A return grub_errno;
2N/A}
2N/A
2N/A/* Set properties on the view based on settings from the specified
2N/A theme file. */
2N/Agrub_err_t
2N/Agrub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
2N/A{
2N/A grub_file_t file;
2N/A struct parsebuf p;
2N/A
2N/A p.view = view;
2N/A p.theme_dir = grub_get_dirname (theme_path);
2N/A
2N/A file = grub_file_open (theme_path);
2N/A if (! file)
2N/A {
2N/A grub_free (p.theme_dir);
2N/A return grub_errno;
2N/A }
2N/A
2N/A p.len = grub_file_size (file);
2N/A p.buf = grub_malloc (p.len);
2N/A p.pos = 0;
2N/A p.line_num = 1;
2N/A p.col_num = 1;
2N/A p.filename = theme_path;
2N/A if (! p.buf)
2N/A {
2N/A grub_file_close (file);
2N/A grub_free (p.theme_dir);
2N/A return grub_errno;
2N/A }
2N/A if (grub_file_read (file, p.buf, p.len) != p.len)
2N/A {
2N/A grub_free (p.buf);
2N/A grub_file_close (file);
2N/A grub_free (p.theme_dir);
2N/A return grub_errno;
2N/A }
2N/A
2N/A if (view->canvas)
2N/A view->canvas->component.ops->destroy (view->canvas);
2N/A
2N/A view->canvas = grub_gui_canvas_new ();
2N/A if (! view->canvas)
2N/A goto fail;
2N/A
2N/A ((grub_gui_component_t) view->canvas)
2N/A ->ops->set_bounds ((grub_gui_component_t) view->canvas,
2N/A &view->screen);
2N/A
2N/A while (has_more (&p))
2N/A {
2N/A /* Skip comments (lines beginning with #). */
2N/A if (peek_char (&p) == '#')
2N/A {
2N/A advance_to_next_line (&p);
2N/A continue;
2N/A }
2N/A
2N/A /* Find the first non-whitespace character. */
2N/A skip_whitespace (&p);
2N/A
2N/A /* Handle the content. */
2N/A if (peek_char (&p) == '+')
2N/A {
2N/A /* Skip the '+'. */
2N/A read_char (&p);
2N/A read_object (&p, view->canvas);
2N/A }
2N/A else
2N/A {
2N/A read_property (&p);
2N/A }
2N/A
2N/A if (grub_errno != GRUB_ERR_NONE)
2N/A goto fail;
2N/A }
2N/A
2N/A /* Set the new theme path. */
2N/A grub_free (view->theme_path);
2N/A view->theme_path = grub_strdup (theme_path);
2N/A goto cleanup;
2N/A
2N/Afail:
2N/A if (view->canvas)
2N/A {
2N/A view->canvas->component.ops->destroy (view->canvas);
2N/A view->canvas = 0;
2N/A }
2N/A
2N/Acleanup:
2N/A grub_free (p.buf);
2N/A grub_file_close (file);
2N/A grub_free (p.theme_dir);
2N/A return grub_errno;
2N/A}