# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080724055504-ril13j8f5vdkel89
# target_branch: http://grub.gibibit.com/bzr/trunk-clean
# testament_sha1: 9035bd89bd8d56da840e7e32479d8d691fc3b4d3
# timestamp: 2008-07-23 22:57:23 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080724055433-leolv8zxt7jj6a11
#
# Begin patch
=== modified file 'commands/videotest.c'
--- commands/videotest.c 2008-07-09 16:14:04 +0000
+++ commands/videotest.c 2008-07-19 18:25:18 +0000
@@ -49,19 +49,6 @@
int double_buffering;
};
-/* A 2D rectangle type.
- * This could be worth integrating into the video API if it proves useful.*/
-struct grub_video_rect
-{
- /* These are signed because if there are unsigned it causes Bad Things
- * to happen when arithmetic and comparisions involving signed types is
- * done. Important signed types include offsets from absolute locations. */
- int x;
- int y;
- int width;
- int height;
-};
-typedef struct grub_video_rect grub_video_rect_t;
static void
basic_video_test (struct videotest_options *vt_opts)
=== modified file 'conf/common.rmk'
--- conf/common.rmk 2008-07-19 21:39:24 +0000
+++ conf/common.rmk 2008-07-24 05:36:41 +0000
@@ -269,6 +269,7 @@
cmp.mod cat.mod help.mod font.mod search.mod \
loopback.mod fs_uuid.mod configfile.mod echo.mod \
terminfo.mod test.mod blocklist.mod hexdump.mod \
+ gfxmenu.mod \
read.mod sleep.mod loadenv.mod
# For hello.mod.
@@ -276,6 +277,15 @@
hello_mod_CFLAGS = $(COMMON_CFLAGS)
hello_mod_LDFLAGS = $(COMMON_LDFLAGS)
+# For gfxmenu.mod.
+gfxmenu_mod_SOURCES = \
+ gfxmenu/gfxmenu.c \
+ gfxmenu/model.c \
+ gfxmenu/view.c \
+ gfxmenu/widget-box.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
# For boot.mod.
boot_mod_SOURCES = commands/boot.c
boot_mod_CFLAGS = $(COMMON_CFLAGS)
=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk 2008-07-19 21:39:45 +0000
+++ conf/i386-pc.rmk 2008-07-19 21:46:40 +0000
@@ -46,11 +46,13 @@
kern/i386/tsc.c \
kern/generic/millisleep.c \
kern/env.c \
+ kern/menu_viewer.c \
term/i386/pc/console.c \
symlist.c
kernel_img_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \
+ menu_viewer.h \
machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \
machine/memory.h machine/loader.h machine/vga.h machine/vbe.h machine/kernel.h
kernel_img_CFLAGS = $(COMMON_CFLAGS)
@@ -248,7 +250,7 @@
play_mod_LDFLAGS = $(COMMON_LDFLAGS)
# For video.mod.
-video_mod_SOURCES = video/video.c
+video_mod_SOURCES = video/video.c video/setmode.c
video_mod_CFLAGS = $(COMMON_CFLAGS)
video_mod_LDFLAGS = $(COMMON_LDFLAGS)
=== modified file 'config.h.in'
--- config.h.in 2008-07-13 00:55:15 +0000
+++ config.h.in 2008-07-19 21:46:40 +0000
@@ -113,10 +113,37 @@
/* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS
+/* Define for large files, on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Define to 1 if on MINIX. */
+#undef _MINIX
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
/* Enable GNU extensions on systems that have them. */
#ifndef _GNU_SOURCE
# undef _GNU_SOURCE
#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# undef _POSIX_PTHREAD_SEMANTICS
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# undef _TANDEM_SOURCE
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
-/* Define for large files, on AIX-style hosts. */
-#undef _LARGE_FILES
=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c 2008-07-24 05:36:41 +0000
@@ -0,0 +1,155 @@
+/* gfxmenu.c - Graphical menu interface controller. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested __attribute__ ((unused)))
+{
+ grub_gfxmenu_model_t model;
+
+ model = grub_gfxmenu_model_new (menu);
+ if (! model)
+ {
+ grub_print_error ();
+ grub_wait_after_message ();
+ return grub_errno;
+ }
+
+ grub_gfxmenu_view_t view;
+
+ view = grub_gfxmenu_view_new ("/boot/theme.txt", model);
+ if (! view)
+ {
+ grub_print_error ();
+ grub_wait_after_message ();
+ grub_gfxmenu_model_destroy (model);
+ return grub_errno;
+ }
+
+ grub_gfxmenu_model_set_selected_index (model, 0);
+
+ int done = 0;
+ while (!done && !grub_menu_viewer_should_return ())
+ {
+ grub_gfxmenu_view_draw (view);
+ grub_video_swap_buffers ();
+
+ int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
+ if (c == 'j' || c == GRUB_TERM_DOWN)
+ {
+ int i = grub_gfxmenu_model_get_selected_index (model);
+ int num_items = grub_gfxmenu_model_get_num_entries (model);
+ if (i < num_items - 1)
+ {
+ i++;
+ grub_gfxmenu_model_set_selected_index (model, i);
+ }
+ }
+ else if (c == 'k' || c == GRUB_TERM_UP)
+ {
+ int i = grub_gfxmenu_model_get_selected_index (model);
+ if (i > 0)
+ {
+ i--;
+ grub_gfxmenu_model_set_selected_index (model, i);
+ }
+ }
+ else if (c == '\r' || c == '\n' || c == GRUB_TERM_RIGHT)
+ {
+ int selected = grub_gfxmenu_model_get_selected_index (model);
+ int num_entries = grub_gfxmenu_model_get_num_entries (model);
+ if (selected >= 0 && selected < num_entries)
+ {
+ grub_menu_entry_t entry =
+ grub_gfxmenu_model_get_entry (model, selected);
+ grub_gfxmenu_view_execute_entry (view, entry);
+ }
+ }
+ else if (c == 'c')
+ {
+ grub_gfxmenu_view_run_terminal (view);
+ if (grub_errno != GRUB_ERR_NONE)
+ goto fail;
+ }
+ else if (c == 't')
+ {
+ /* The write hook for 'menuviewer' will cause
+ * grub_menu_viewer_should_return to return nonzero. */
+ grub_env_set ("menuviewer", "terminal");
+ }
+ else if (nested && c == GRUB_TERM_ESC)
+ {
+ done = 1;
+ }
+ }
+
+fail:
+ grub_gfxmenu_view_destroy (view);
+ grub_gfxmenu_model_destroy (model);
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_cmd_gfxmenu (struct grub_arg_list *state __attribute__ ((unused)),
+ int argc __attribute__ ((unused)),
+ char **args __attribute__ ((unused)))
+{
+ grub_menu_t menu = grub_env_get_data_slot ("menu");
+ if (!menu)
+ return grub_error (GRUB_ERR_MENU, "No menu context");
+
+ return show_menu (menu, 1);
+}
+
+static struct grub_menu_viewer menu_viewer =
+{
+ .name = "gfxmenu",
+ .show_menu = show_menu
+};
+
+GRUB_MOD_INIT (gfxmenu)
+{
+ (void) mod; /* To stop warning. */
+ grub_menu_viewer_register (&menu_viewer);
+ grub_register_command ("gfxmenu",
+ grub_cmd_gfxmenu, GRUB_COMMAND_FLAG_BOTH,
+ "gfxmenu", "Show graphical menu interface", 0);
+}
+
+GRUB_MOD_FINI (gfxmenu)
+{
+ grub_unregister_command ("gfxmenu");
+}
=== added file 'gfxmenu/model.c'
--- gfxmenu/model.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/model.c 2008-07-24 05:36:41 +0000
@@ -0,0 +1,135 @@
+/* model.c - Graphical menu interface MVC model. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Model type definition. */
+struct grub_gfxmenu_model
+{
+ int num_entries;
+ grub_menu_entry_t *entries;
+ int selected_entry_index;
+};
+
+
+grub_gfxmenu_model_t
+grub_gfxmenu_model_new (grub_menu_t menu)
+{
+ grub_gfxmenu_model_t model;
+
+ model = grub_malloc (sizeof (*model));
+ if (! model)
+ return 0;
+
+ model->entries = 0;
+ model->selected_entry_index = 0;
+ model->num_entries = menu->size;
+ if (model->num_entries > 0)
+ {
+ model->entries = grub_malloc (model->num_entries
+ * sizeof (*model->entries));
+ if (! model->entries)
+ goto fail_and_free;
+
+ int i;
+ grub_menu_entry_t cur;
+ for (i = 0, cur = menu->entry_list;
+ i < model->num_entries;
+ i++, cur = cur->next)
+ {
+ model->entries[i] = cur;
+ }
+ }
+
+ return model;
+
+fail_and_free:
+ grub_free (model->entries);
+ grub_free (model);
+ return 0;
+}
+
+void
+grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model)
+{
+ if (! model)
+ return;
+
+ grub_free (model->entries);
+ model->entries = 0;
+
+ grub_free (model);
+}
+
+int
+grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model)
+{
+ return model->num_entries;
+}
+
+int
+grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model)
+{
+ return model->selected_entry_index;
+}
+
+void
+grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model, int index)
+{
+ model->selected_entry_index = index;
+}
+
+const char *
+grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model, int index)
+{
+ if (index < 0 || index >= model->num_entries)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+ return 0;
+ }
+
+ return model->entries[index]->title;
+}
+
+grub_menu_entry_t
+grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model, int index)
+{
+ if (index < 0 || index >= model->num_entries)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+ return 0;
+ }
+
+ return model->entries[index];
+}
+
=== added file 'gfxmenu/view.c'
--- gfxmenu/view.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c 2008-07-24 05:36:41 +0000
@@ -0,0 +1,694 @@
+/* view.c - Graphical menu interface MVC view. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct grub_gfxmenu_view
+{
+ grub_video_rect_t screen;
+
+ int icon_width;
+ int icon_height;
+ int item_height;
+ grub_font_t title_font;
+ grub_font_t item_font;
+ grub_font_t status_font;
+ grub_video_color_t title_color;
+ grub_video_color_t item_color;
+ grub_video_color_t status_color;
+ grub_video_color_t status_bg_color;
+ struct grub_video_bitmap *desktop_image;
+ grub_video_color_t desktop_color;
+ grub_gfxmenu_box_t menu_box;
+ grub_gfxmenu_box_t selected_item_box;
+ char *screen_title;
+
+ grub_gfxmenu_model_t model;
+};
+
+static void init_terminal (grub_gfxmenu_view_t view);
+static void destroy_terminal (void);
+static grub_err_t set_graphics_mode (void);
+static grub_err_t set_text_mode (void);
+
+/* TODO Make image files referenced by a theme relative to theme path. */
+grub_gfxmenu_view_t
+grub_gfxmenu_view_new (const char *theme_path, grub_gfxmenu_model_t model)
+{
+ grub_gfxmenu_view_t view;
+
+ view = grub_malloc (sizeof (*view));
+ if (! view)
+ return 0;
+
+ set_graphics_mode ();
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ grub_video_get_viewport ((unsigned *) &view->screen.x,
+ (unsigned *) &view->screen.y,
+ (unsigned *) &view->screen.width,
+ (unsigned *) &view->screen.height);
+
+ /* Clear the screen; there may be garbage left over in video memory, and
+ loading the menu style (particularly the background) can take a while. */
+ grub_video_fill_rect (grub_video_map_rgb (100, 100, 100),
+ view->screen.x, view->screen.y,
+ view->screen.width, view->screen.height);
+ grub_video_swap_buffers ();
+
+ init_terminal (view);
+
+ grub_font_t default_font;
+ grub_video_color_t default_fg_color;
+ grub_video_color_t default_bg_color;
+
+ default_font = grub_font_get ("Helvetica 12");
+ default_fg_color = grub_video_map_rgb (0, 0, 0);
+ default_bg_color = grub_video_map_rgb (255, 255, 255);
+
+ view->icon_width = 32;
+ view->icon_height = 32;
+ view->item_height = 42;
+ view->title_font = default_font;
+ view->item_font = default_font;
+ view->status_font = default_font;
+ view->title_color = default_fg_color;
+ view->item_color = default_fg_color;
+ view->status_color = default_bg_color;
+ view->status_bg_color = default_fg_color;
+ view->desktop_image = 0;
+ view->desktop_color = default_bg_color;
+ view->menu_box = 0;
+ view->selected_item_box = 0;
+ view->screen_title = grub_strdup ("GRUB Boot Menu");
+
+ view->model = model;
+
+ grub_gfxmenu_view_load_theme (view, theme_path);
+
+ return view;
+}
+
+void
+grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
+{
+ grub_video_bitmap_destroy (view->desktop_image);
+ if (view->menu_box)
+ view->menu_box->destroy (view->menu_box);
+ if (view->selected_item_box)
+ view->selected_item_box->destroy (view->selected_item_box);
+ grub_free (view->screen_title);
+ grub_free (view);
+
+ set_text_mode ();
+ destroy_terminal ();
+}
+
+static grub_video_color_t
+parse_color (const char *s)
+{
+ int red = grub_strtoul (s, 0, 0);
+ if ((s = grub_strchr (s, ',')) == 0)
+ goto fail;
+ s++;
+ int green = grub_strtoul (s, 0, 0);
+ if ((s = grub_strchr (s, ',')) == 0)
+ goto fail;
+ s++;
+ int blue = grub_strtoul (s, 0, 0);
+ int alpha;
+ if ((s = grub_strchr (s, ',')) == 0)
+ alpha = 255;
+ else
+ {
+ s++;
+ alpha = grub_strtoul (s, 0, 0);
+ }
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ return grub_video_map_rgba (red, green, blue, alpha);
+
+fail:
+ return 0;
+}
+
+/* Return zero on failure, nonzero on success. */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr, char *pattern)
+{
+ char *prefix;
+ char *suffix;
+ char *star;
+ grub_gfxmenu_box_t box;
+
+ star = grub_strchr (pattern, '*');
+ if (! star)
+ return 0;
+
+ prefix = grub_malloc (star - pattern + 1);
+ if (! prefix)
+ return 0;
+ grub_memcpy (prefix, pattern, star - pattern);
+ prefix[star - pattern] = '\0';
+ suffix = star + 1;
+ box = grub_gfxmenu_create_box (prefix, suffix);
+ grub_free (prefix);
+ if (! box)
+ return 0;
+
+ if (*boxptr)
+ (*boxptr)->destroy (*boxptr);
+ *boxptr = box;
+ return 1;
+}
+
+/* Set the specified property NAME on the view to the given string VALUE.
+ This function takes ownership of both NAME and VALUE, so the caller
+ should pass pointers to new heap-allocated strings. */
+static void
+theme_set_string (grub_gfxmenu_view_t view, char *name, char *value)
+{
+ if (! grub_strcmp ("title-font", name))
+ view->title_font = grub_font_get (value);
+ else if (! grub_strcmp ("item-font", name))
+ view->item_font = grub_font_get (value);
+ else if (! grub_strcmp ("status-font", name))
+ view->status_font = grub_font_get (value);
+ else if (! grub_strcmp ("title-color", name))
+ view->title_color = parse_color (value);
+ else if (! grub_strcmp ("item-color", name))
+ view->item_color = parse_color (value);
+ else if (! grub_strcmp ("status-color", name))
+ view->status_color = parse_color (value);
+ else if (! grub_strcmp ("status-bg-color", name))
+ view->status_bg_color = parse_color (value);
+ else if (! grub_strcmp ("desktop-image", name))
+ {
+ struct grub_video_bitmap *raw_bitmap;
+ struct grub_video_bitmap *scaled_bitmap;
+ if (grub_video_bitmap_load (&raw_bitmap, value) != GRUB_ERR_NONE)
+ goto fail;
+ grub_video_bitmap_create_scaled (&scaled_bitmap,
+ view->screen.width,
+ view->screen.height,
+ raw_bitmap,
+ GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+ grub_video_bitmap_destroy (raw_bitmap);
+ if (!scaled_bitmap)
+ goto fail;
+
+ grub_video_bitmap_destroy (view->desktop_image);
+ view->desktop_image = scaled_bitmap;
+ }
+ else if (! grub_strcmp ("desktop-color", name))
+ view->desktop_color = parse_color (value);
+ else if (! grub_strcmp ("menu-box", name))
+ theme_set_box (&view->menu_box, value);
+ else if (! grub_strcmp ("selected-item-box", name))
+ theme_set_box (&view->selected_item_box, value);
+
+fail:
+ grub_free (value);
+ grub_free (name);
+}
+
+/* Set the specified property NAME on the view to the given numeric VALUE.
+ This function takes ownership NAME, so the caller should pass a pointer
+ to a new heap-allocated string. */
+static void
+theme_set_number (grub_gfxmenu_view_t view, char *name, int value)
+{
+ if (! grub_strcmp ("icon-width", name))
+ view->icon_width = value;
+ else if (! grub_strcmp ("icon-height", name))
+ view->icon_height = value;
+ else if (! grub_strcmp ("item-height", name))
+ view->item_height = value;
+
+ grub_free (name);
+}
+
+static char *new_substring (const char *buf,
+ grub_size_t start, grub_size_t end)
+{
+ char *s = grub_malloc (end - start + 1);
+ if (! s)
+ return 0;
+ grub_memcpy (s, buf + start, end - start);
+ s[end - start] = '\0';
+ return s;
+}
+
+/* Set properties on the view based on settings from the specified
+ theme file. Returns nonzero on success, zero on failure. */
+int
+grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
+{
+#if 1
+ grub_file_t file;
+ char *buf;
+ int pos;
+ int len;
+
+ file = grub_file_open (theme_path);
+ if (!file)
+ return 0;
+
+ len = grub_file_size (file);
+ buf = grub_malloc (len);
+ if (! buf)
+ {
+ grub_file_close (file);
+ return 0;
+ }
+ if (grub_file_read (file, buf, len) != len)
+ {
+ grub_free (buf);
+ grub_file_close (file);
+ return 0;
+ }
+
+ pos = 0;
+ while (pos < len)
+ {
+ /* Skip comments (lines beginning with #). */
+ if (pos < len && buf[pos] == '#')
+ goto nextline;
+
+ /* Get name. */
+ /* Find a word character. */
+ while (pos < len && grub_isspace(buf[pos]))
+ pos++;
+ int name_start = pos;
+ /* Find the end of the name. */
+ while (pos < len
+ && (grub_isalpha(buf[pos])
+ || grub_isdigit(buf[pos])
+ || buf[pos] == '_'
+ || buf[pos] == '-'))
+ pos++;
+ int name_end = pos;
+
+ if (name_end - name_start < 1)
+ goto nextline;
+
+ /* Skip whitespace before separator. */
+ while (pos < len
+ && (buf[pos] == ' '
+ || buf[pos] == '\t'
+ || buf[pos] == '\f'))
+ pos++;
+
+ /* Read separator. */
+ if (buf[pos] != ':')
+ goto nextline;
+
+ pos++; /* Skip separator. */
+
+ /* Skip whitespace after separator. */
+ while (pos < len
+ && (buf[pos] == ' '
+ || buf[pos] == '\t'
+ || buf[pos] == '\f'))
+ pos++;
+
+ /* Get the value based on its type. */
+ if (pos < len && buf[pos] == '"')
+ {
+ /* String value. (e.g., '"My string"') */
+
+ int value_start;
+ int value_end;
+
+ /* Skip the opening quotation mark. */
+ pos++;
+ /* Get string value. */
+ value_start = pos;
+ /* Find the ending quotation mark. */
+ while (pos < len
+ && !(buf[pos] == '"'
+ || buf[pos] == '\n'))
+ pos++;
+ value_end = pos;
+ theme_set_string (view,
+ new_substring (buf, name_start, name_end),
+ new_substring (buf, value_start, value_end));
+ }
+ else if (pos < len && grub_isdigit(buf[pos]))
+ {
+ /* Numeric value. (e.g., '123') */
+
+ int value_start;
+ int value_end;
+ char *value_str;
+ int value;
+
+ /* Get numeric value. */
+ value_start = pos;
+ /* Find the end of the digit sequence. */
+ while (pos < len && grub_isdigit(buf[pos]))
+ pos++;
+ value_end = pos;
+ value_str = new_substring (buf, value_start, value_end);
+ if (!value_str)
+ continue;
+ value = grub_strtoul (value_str, 0, 0);
+ grub_free (value_str);
+ theme_set_number (view,
+ new_substring (buf, name_start, name_end),
+ value);
+ }
+
+nextline:
+ /* Eat characters up to the newline. */
+ while (pos < len && buf[pos] != '\n')
+ pos++;
+ pos++; /* Eat the newline. */
+ }
+
+ grub_free (buf);
+ grub_file_close (file);
+ return 1;
+#else
+ theme_set_string (view,
+ grub_strdup ("title-font"),
+ grub_strdup ("Helvetica Bold 24"));
+ theme_set_string (view,
+ grub_strdup ("item-font"),
+ grub_strdup ("Helvetica Bold 12"));
+ theme_set_string (view,
+ grub_strdup ("status-font"),
+ grub_strdup ("Helvetica 12"));
+ theme_set_string (view,
+ grub_strdup ("title-color"),
+ grub_strdup ("255,255,0"));
+ theme_set_string (view,
+ grub_strdup ("item-color"),
+ grub_strdup ("0,0,255"));
+ theme_set_string (view,
+ grub_strdup ("status-color"),
+ grub_strdup ("255,255,0"));
+ theme_set_string (view,
+ grub_strdup ("status-bg-color"),
+ grub_strdup ("64,64,64"));
+ theme_set_string (view,
+ grub_strdup ("menu-box"),
+ grub_strdup ("/boot/images/menubox_*.tga"));
+ theme_set_string (view,
+ grub_strdup ("selected-item-box"),
+ grub_strdup ("/boot/images/select_blue_*.tga"));
+ return 1;
+#endif
+}
+
+static void
+draw_background (grub_gfxmenu_view_t view)
+{
+ if (view->desktop_image)
+ {
+ struct grub_video_bitmap *img = view->desktop_image;
+ grub_video_blit_bitmap (img, GRUB_VIDEO_BLIT_REPLACE,
+ view->screen.x, view->screen.y, 0, 0,
+ grub_video_bitmap_get_width (img),
+ grub_video_bitmap_get_height (img));
+ }
+ else
+ {
+ grub_video_fill_rect (view->desktop_color,
+ view->screen.x, view->screen.y,
+ view->screen.width, view->screen.height);
+ }
+}
+
+static struct grub_video_bitmap *
+get_item_icon (grub_gfxmenu_view_t view __attribute__((unused)),
+ int item_index __attribute__((unused)))
+{
+ /* TODO Implement icons. */
+ return 0;
+}
+
+static void
+draw_menu (grub_gfxmenu_view_t view)
+{
+ int boxpad = 14;
+ int icon_text_space = 4;
+ int item_vspace = 16;
+
+ int ascent = grub_font_get_ascent (view->item_font);
+ int descent = grub_font_get_descent (view->item_font);
+ int item_height = view->item_height;
+
+ int num_items = grub_gfxmenu_model_get_num_entries (view->model);
+ grub_video_rect_t r;
+ r.width = view->screen.width * 4 / 5;
+ /* Set the menu box height to fit the items. */
+ r.height = (item_height * num_items
+ + item_vspace * (num_items - 1)
+ + 2 * boxpad);
+ r.x = (view->screen.width - r.width) / 2;
+ r.y = (view->screen.height - r.height) / 2;
+ view->menu_box->set_content_size (view->menu_box, r.width, r.height);
+
+ int menu_box_left_pad = view->menu_box->get_left_pad (view->menu_box);
+ int menu_box_top_pad = view->menu_box->get_top_pad (view->menu_box);
+ view->menu_box->draw (view->menu_box,
+ r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+ int item_top = r.y + boxpad;
+ int item_left = r.x + boxpad;
+ int i;
+
+ for (i = 0; i < num_items; i++)
+ {
+ if (i == grub_gfxmenu_model_get_selected_index (view->model))
+ {
+ view->selected_item_box->set_content_size (view->selected_item_box,
+ r.width - 2 * boxpad,
+ item_height);
+ int leftpad = view->selected_item_box->get_left_pad (view->selected_item_box);
+ int toppad = view->selected_item_box->get_top_pad (view->selected_item_box);
+ view->selected_item_box->draw (view->selected_item_box,
+ item_left - leftpad,
+ item_top - toppad);
+ }
+
+ struct grub_video_bitmap *icon;
+ if ((icon = get_item_icon (view, i)) != 0)
+ grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+ item_left,
+ item_top + (item_height - view->icon_height) / 2,
+ 0, 0, view->icon_width, view->icon_height);
+
+ grub_video_draw_string (grub_gfxmenu_model_get_entry_title (view->model, i),
+ view->item_font, view->item_color,
+ item_left + view->icon_width + icon_text_space,
+ (item_top + (item_height - (ascent + descent))
+ / 2 + ascent));
+
+ item_top += item_height + item_vspace;
+ }
+}
+
+static void
+draw_title (grub_gfxmenu_view_t view)
+{
+ if (! view->screen_title)
+ return;
+
+ /* Center the title. */
+ int title_width = grub_font_get_string_width (view->title_font,
+ view->screen_title);
+ int x = (view->screen.width - title_width) / 2;
+ int y = 40 + grub_font_get_ascent (view->title_font);
+ grub_video_draw_string (view->screen_title,
+ view->title_font, view->title_color,
+ x, y);
+}
+
+static void
+draw_status (grub_gfxmenu_view_t view)
+{
+ int descent = grub_font_get_descent (view->status_font);
+ int ascent = grub_font_get_ascent (view->status_font);
+ int vpad = 5;
+ int textheight = descent + ascent + 1;
+ int h = 2 * vpad + 2 * textheight;
+
+ grub_video_fill_rect (view->status_bg_color,
+ 0, view->screen.height - h,
+ view->screen.width, view->screen.height - 1);
+
+ int texty = view->screen.height - h + vpad + ascent;
+ grub_video_draw_string ("Select an item with the arrow keys and "
+ "press Enter to boot.",
+ view->status_font, view->status_color, 30, texty);
+ texty += textheight;
+ grub_video_draw_string ("Press: 'c' for command line; 't' to switch to "
+ "non-graphical menu.",
+ view->status_font, view->status_color, 30, texty);
+}
+
+void
+grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
+{
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ draw_background (view);
+ draw_menu (view);
+ draw_title (view);
+ draw_status (view);
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+ const char *doublebuf_str = grub_env_get ("doublebuffering");
+ int doublebuf_flags =
+ (doublebuf_str && doublebuf_str[0] == 'n')
+ ? 0
+ : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+ const char *modestr = grub_env_get ("gfxmode");
+ if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480)
+ != GRUB_ERR_NONE)
+ return grub_errno;
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+ return grub_video_restore ();
+}
+
+static int term_target_width;
+static int term_target_height;
+static struct grub_video_render_target *term_target;
+static grub_gfxmenu_box_t term_box;
+static grub_term_t term_original;
+static grub_gfxmenu_view_t term_view;
+
+static void
+repaint_terminal (int x __attribute ((unused)),
+ int y __attribute ((unused)),
+ int width __attribute ((unused)),
+ int height __attribute ((unused)))
+{
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ grub_gfxmenu_view_draw (term_view);
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+ int termx = term_view->screen.x + term_view->screen.width / 10;
+ int termy = term_view->screen.y + term_view->screen.height / 10;
+
+ if (term_box)
+ term_box->draw (term_box,
+ termx - term_box->get_left_pad (term_box),
+ termy - term_box->get_top_pad (term_box));
+
+ grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE,
+ termx, termy,
+ 0, 0, term_target_width, term_target_height);
+ grub_video_swap_buffers ();
+}
+
+static void init_terminal (grub_gfxmenu_view_t view)
+{
+ term_original = grub_term_get_current ();
+
+ term_target_width = view->screen.width * 8 / 10;
+ term_target_height = view->screen.height * 8 / 10;
+
+ grub_video_create_render_target (&term_target,
+ term_target_width,
+ term_target_height,
+ GRUB_VIDEO_MODE_TYPE_RGB
+ | GRUB_VIDEO_MODE_TYPE_ALPHA);
+ if (grub_errno != GRUB_ERR_NONE)
+ return;
+
+ grub_gfxterm_init_window (term_target, 0, 0,
+ term_target_width, term_target_height,
+ "Fixed 10", 16);
+ if (grub_errno != GRUB_ERR_NONE)
+ return;
+
+ term_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga");
+ term_box->set_content_size (term_box, term_target_width, term_target_height);
+
+ /* XXX: store static pointer to the 'view' object so the repaint callback can access it. */
+ term_view = view;
+ grub_gfxterm_set_repaint_callback (repaint_terminal);
+ grub_term_set_current (grub_gfxterm_get_term ());
+}
+
+static void destroy_terminal (void)
+{
+ term_box->destroy (term_box);
+ term_box = 0;
+ grub_gfxterm_destroy_window ();
+ grub_gfxterm_set_repaint_callback (0);
+ grub_video_delete_render_target (term_target);
+ grub_term_set_current (term_original);
+}
+
+int
+grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+ grub_menu_entry_t entry)
+{
+ /* Currently we switch back to text mode by restoring
+ the original terminal before executing the menu entry.
+ It is hard to make it work when executing a menu entry
+ that switches video modes -- it using gfxterm in a
+ window, the repaint callback seems to crash GRUB. */
+ /* TODO: Determine if this works when 'gfxterm' was set as
+ the current terminal before invoking the gfxmenu. */
+ destroy_terminal ();
+
+ grub_menu_execute_entry (entry);
+ if (grub_errno != GRUB_ERR_NONE)
+ grub_wait_after_message ();
+
+ if (set_graphics_mode () != GRUB_ERR_NONE)
+ return 0; /* Failure. */
+
+ init_terminal (view);
+ return 1; /* Ok. */
+}
+
+void
+grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view __attribute__((unused)))
+{
+ grub_cmdline_run (1);
+}
+
=== added file 'gfxmenu/widget-box.c'
--- gfxmenu/widget-box.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/widget-box.c 2008-07-19 18:25:18 +0000
@@ -0,0 +1,244 @@
+/* widget_box.c - Pixmap-stylized box widget. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+enum box_pixmaps
+{
+ BOX_PIXMAP_NW, BOX_PIXMAP_NE, BOX_PIXMAP_SE, BOX_PIXMAP_SW,
+ BOX_PIXMAP_N, BOX_PIXMAP_E, BOX_PIXMAP_S, BOX_PIXMAP_W,
+ BOX_PIXMAP_CENTER
+};
+
+static const char *box_pixmap_names[] = {
+ /* Corners: */
+ "nw", "ne", "se", "sw",
+ /* Sides: */
+ "n", "e", "s", "w",
+ /* Center: */
+ "c"
+};
+
+#define BOX_NUM_PIXMAPS (sizeof(box_pixmap_names)/sizeof(*box_pixmap_names))
+
+static void
+draw (grub_gfxmenu_box_t self, int x, int y)
+{
+ int height_n;
+ int height_s;
+ int height_e;
+ int height_w;
+ int width_n;
+ int width_s;
+ int width_e;
+ int width_w;
+ unsigned i;
+
+ /* Don't try to draw if any pixmaps are null, except the center. */
+ for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+ {
+ if (i != BOX_PIXMAP_CENTER && self->scaled_pixmaps[i] == 0)
+ return;
+ }
+
+ height_n = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_N]);
+ height_s = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_S]);
+ height_e = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_E]);
+ height_w = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_W]);
+ width_n = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_N]);
+ width_s = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_S]);
+ width_e = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_E]);
+ width_w = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_W]);
+
+ /* Draw sides. */
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_N],
+ GRUB_VIDEO_BLIT_BLEND,
+ x + width_w, y, 0, 0, width_n, height_n);
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_S],
+ GRUB_VIDEO_BLIT_BLEND,
+ x + width_w, y + height_n + height_w,
+ 0, 0, width_s, height_s);
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_E],
+ GRUB_VIDEO_BLIT_BLEND,
+ x + width_w + width_n, y + height_n,
+ 0, 0, width_e, height_e);
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_W],
+ GRUB_VIDEO_BLIT_BLEND,
+ x, y + height_n, 0, 0, width_w, height_w);
+
+ /* Draw corners. */
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NW],
+ GRUB_VIDEO_BLIT_BLEND,
+ x, y, 0, 0, width_w, height_n);
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NE],
+ GRUB_VIDEO_BLIT_BLEND,
+ x + width_w + width_n, y,
+ 0, 0, width_e, height_n);
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SE],
+ GRUB_VIDEO_BLIT_BLEND,
+ x + width_w + width_n, y + height_n + height_e,
+ 0, 0, width_e, height_s);
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SW],
+ GRUB_VIDEO_BLIT_BLEND,
+ x, y + height_n + height_w,
+ 0, 0, width_w, height_s);
+
+ /* Draw center. */
+ if (self->scaled_pixmaps[BOX_PIXMAP_CENTER])
+ grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_CENTER],
+ GRUB_VIDEO_BLIT_BLEND,
+ x + width_w, y + height_n,
+ 0, 0, self->width, self->height);
+}
+
+static void
+scale_pixmap (grub_gfxmenu_box_t self, int i, int w, int h)
+{
+ struct grub_video_bitmap **scaled = &self->scaled_pixmaps[i];
+ struct grub_video_bitmap *raw = self->raw_pixmaps[i];
+
+ if (raw == 0)
+ return;
+
+ if (w == -1)
+ w = grub_video_bitmap_get_width (raw);
+ if (h == -1)
+ h = grub_video_bitmap_get_height (raw);
+
+ if (*scaled == 0
+ || ((int) grub_video_bitmap_get_width (*scaled) != w)
+ || ((int) grub_video_bitmap_get_height (*scaled) != h))
+ {
+ if (*scaled)
+ {
+ grub_video_bitmap_destroy (*scaled);
+ *scaled = 0;
+ }
+ grub_video_bitmap_create_scaled (scaled, w, h, raw,
+ GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+ }
+}
+
+static void
+set_content_size (grub_gfxmenu_box_t self,
+ int width, int height)
+{
+ self->width = width;
+ self->height = height;
+ /* Resize sides to match the width and height. */
+ /* It is assumed that the corners width/height match the adjacent sides. */
+
+ /* Resize N and S sides to match width. */
+ scale_pixmap(self, BOX_PIXMAP_N, width, -1);
+ scale_pixmap(self, BOX_PIXMAP_S, width, -1);
+
+ /* Resize E and W sides to match height. */
+ scale_pixmap(self, BOX_PIXMAP_E, -1, height);
+ scale_pixmap(self, BOX_PIXMAP_W, -1, height);
+
+ /* Don't scale the corners--they are assumed to match the sides. */
+ scale_pixmap(self, BOX_PIXMAP_NW, -1, -1);
+ scale_pixmap(self, BOX_PIXMAP_SW, -1, -1);
+ scale_pixmap(self, BOX_PIXMAP_NE, -1, -1);
+ scale_pixmap(self, BOX_PIXMAP_SE, -1, -1);
+
+ /* Scale the center area. */
+ scale_pixmap(self, BOX_PIXMAP_CENTER, width, height);
+}
+
+static int
+get_left_pad (grub_gfxmenu_box_t self)
+{
+ return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]);
+}
+
+static int
+get_top_pad (grub_gfxmenu_box_t self)
+{
+ return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]);
+}
+
+static void
+destroy (grub_gfxmenu_box_t self)
+{
+ unsigned i;
+ for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+ {
+ if (self->raw_pixmaps[i])
+ grub_video_bitmap_destroy(self->raw_pixmaps[i]);
+ self->raw_pixmaps[i] = 0;
+
+ if (self->scaled_pixmaps[i])
+ grub_video_bitmap_destroy(self->scaled_pixmaps[i]);
+ self->scaled_pixmaps[i] = 0;
+ }
+ grub_free (self->raw_pixmaps);
+ self->raw_pixmaps = 0;
+ grub_free (self->scaled_pixmaps);
+ self->scaled_pixmaps = 0;
+
+ grub_free (self); /* Free self: must be the last step! */
+}
+
+
+grub_gfxmenu_box_t
+grub_gfxmenu_create_box (const char *pixmaps_prefix,
+ const char *pixmaps_suffix)
+{
+ char path[200];
+ unsigned i;
+ grub_gfxmenu_box_t box;
+
+ box = (grub_gfxmenu_box_t) grub_malloc (sizeof (*box));
+ if (!box)
+ return 0;
+ box->width = 0;
+ box->height = 0;
+ box->raw_pixmaps =
+ (struct grub_video_bitmap **)
+ grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+ box->scaled_pixmaps =
+ (struct grub_video_bitmap **)
+ grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *));
+
+ for (i = 0; i < BOX_NUM_PIXMAPS; i++)
+ {
+ /* TODO XXX dynamically allocate PATH, making sure it's large enough */
+ grub_sprintf (path, "%s%s%s",
+ pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix);
+ grub_video_bitmap_load (&box->raw_pixmaps[i], path);
+ box->scaled_pixmaps[i] = 0;
+ }
+
+ box->draw = draw;
+ box->set_content_size = set_content_size;
+ box->get_left_pad = get_left_pad;
+ box->get_top_pad = get_top_pad;
+ box->destroy = destroy;
+
+ return box;
+}
+
=== added file 'include/grub/gfxmenu_model.h'
--- include/grub/gfxmenu_model.h 1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_model.h 2008-07-24 05:36:41 +0000
@@ -0,0 +1,45 @@
+/* gfxmenu_model.h - gfxmenu model interface. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_GFXMENU_MODEL_HEADER
+#define GRUB_GFXMENU_MODEL_HEADER 1
+
+struct grub_gfxmenu_model; /* Forward declaration of opaque type. */
+typedef struct grub_gfxmenu_model *grub_gfxmenu_model_t;
+
+
+grub_gfxmenu_model_t grub_gfxmenu_model_new (grub_menu_t menu);
+
+void grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model,
+ int index);
+
+const char *grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model,
+ int index);
+
+grub_menu_entry_t grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model,
+ int index);
+
+#endif /* GRUB_GFXMENU_MODEL_HEADER */
+
=== added file 'include/grub/gfxmenu_view.h'
--- include/grub/gfxmenu_view.h 1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_view.h 2008-07-24 05:36:41 +0000
@@ -0,0 +1,50 @@
+/* gfxmenu_view.h - gfxmenu view interface. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_GFXMENU_VIEW_HEADER
+#define GRUB_GFXMENU_VIEW_HEADER 1
+
+#include
+#include
+#include
+#include
+
+struct grub_gfxmenu_view; /* Forward declaration of opaque type. */
+typedef struct grub_gfxmenu_view *grub_gfxmenu_view_t;
+
+
+grub_gfxmenu_view_t grub_gfxmenu_view_new (const char *theme_path,
+ grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view);
+
+/* Set properties on the view based on settings from the specified
+ theme file. */
+int grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view,
+ const char *theme_path);
+
+void grub_gfxmenu_view_draw (grub_gfxmenu_view_t view);
+
+int grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+ grub_menu_entry_t entry);
+
+void grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view);
+
+#endif /* GRUB_GFXMENU_VIEW_HEADER */
+
=== added file 'include/grub/gfxterm.h'
--- include/grub/gfxterm.h 1970-01-01 00:00:00 +0000
+++ include/grub/gfxterm.h 2008-07-19 19:56:06 +0000
@@ -0,0 +1,41 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_GFXTERM_HEADER
+#define GRUB_GFXTERM_HEADER 1
+
+#include
+#include
+#include
+#include
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+ int x, int y, int width, int height,
+ const char *font_name, int border_width);
+
+void grub_gfxterm_destroy_window (void);
+
+grub_term_t grub_gfxterm_get_term (void);
+
+typedef void (*grub_gfxterm_repaint_callback_t)(int x, int y,
+ int width, int height);
+
+void grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func);
+
+#endif /* ! GRUB_GFXTERM_HEADER */
=== added file 'include/grub/gfxwidgets.h'
--- include/grub/gfxwidgets.h 1970-01-01 00:00:00 +0000
+++ include/grub/gfxwidgets.h 2008-07-19 18:25:18 +0000
@@ -0,0 +1,47 @@
+/* gfxwidgets.h - Widgets for the graphical menu (gfxmenu). */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_GFXWIDGETS_HEADER
+#define GRUB_GFXWIDGETS_HEADER 1
+
+#include
+
+typedef struct grub_gfxmenu_box *grub_gfxmenu_box_t;
+
+struct grub_gfxmenu_box
+{
+ /* The size of the content. */
+ int width;
+ int height;
+
+ struct grub_video_bitmap **raw_pixmaps;
+ struct grub_video_bitmap **scaled_pixmaps;
+
+ void (*draw) (grub_gfxmenu_box_t self, int x, int y);
+ void (*set_content_size) (grub_gfxmenu_box_t self,
+ int width, int height);
+ int (*get_left_pad) (grub_gfxmenu_box_t self);
+ int (*get_top_pad) (grub_gfxmenu_box_t self);
+ void (*destroy) (grub_gfxmenu_box_t self);
+};
+
+grub_gfxmenu_box_t grub_gfxmenu_create_box (const char *pixmaps_prefix,
+ const char *pixmaps_suffix);
+
+#endif /* ! GRUB_GFXWIDGETS_HEADER */
=== added file 'include/grub/menu.h'
--- include/grub/menu.h 1970-01-01 00:00:00 +0000
+++ include/grub/menu.h 2008-07-19 18:40:28 +0000
@@ -0,0 +1,51 @@
+/* menu.h - Menu and menu entry model declarations. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2002,2003,2005,2006,2007,2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_MENU_HEADER
+#define GRUB_MENU_HEADER 1
+
+/* The menu entry. */
+struct grub_menu_entry
+{
+ /* The title name. */
+ const char *title;
+
+ /* The commands associated with this menu entry. */
+ struct grub_script *commands;
+
+ /* The sourcecode of the menu entry, used by the editor. */
+ const char *sourcecode;
+
+ /* The next element. */
+ struct grub_menu_entry *next;
+};
+typedef struct grub_menu_entry *grub_menu_entry_t;
+
+/* The menu. */
+struct grub_menu
+{
+ /* The size of a menu. */
+ int size;
+
+ /* The list of menu entries. */
+ grub_menu_entry_t entry_list;
+};
+typedef struct grub_menu *grub_menu_t;
+
+#endif /* GRUB_MENU_HEADER */
=== added file 'include/grub/menu_viewer.h'
--- include/grub/menu_viewer.h 1970-01-01 00:00:00 +0000
+++ include/grub/menu_viewer.h 2008-07-19 19:07:47 +0000
@@ -0,0 +1,48 @@
+/* menu_viewer.h - Interface to menu viewer implementations. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2002,2003,2005,2007,2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_MENU_VIEWER_HEADER
+#define GRUB_MENU_VIEWER_HEADER 1
+
+#include
+#include
+#include
+#include
+
+struct grub_menu_viewer
+{
+ /* The menu viewer name. */
+ const char *name;
+
+ grub_err_t (*show_menu) (grub_menu_t menu, int nested);
+
+ struct grub_menu_viewer *next;
+};
+typedef struct grub_menu_viewer *grub_menu_viewer_t;
+
+void EXPORT_FUNC(grub_menu_viewer_init) (void);
+
+void EXPORT_FUNC(grub_menu_viewer_register) (grub_menu_viewer_t viewer);
+
+grub_err_t EXPORT_FUNC(grub_menu_viewer_show_menu) (grub_menu_t menu, int nested);
+
+/* Return nonzero iff the menu viewer should clean up and return ASAP. */
+int EXPORT_FUNC(grub_menu_viewer_should_return) (void);
+
+#endif /* GRUB_MENU_VIEWER_HEADER */
=== modified file 'include/grub/normal.h'
--- include/grub/normal.h 2008-03-26 12:01:02 +0000
+++ include/grub/normal.h 2008-07-19 19:24:29 +0000
@@ -25,6 +25,7 @@
#include
#include
#include
+#include
/* The maximum size of a command-line. */
#define GRUB_MAX_CMDLINE 1600
@@ -84,34 +85,6 @@
};
typedef struct grub_command *grub_command_t;
-/* The menu entry. */
-struct grub_menu_entry
-{
- /* The title name. */
- const char *title;
-
- /* The commands associated with this menu entry. */
- struct grub_script *commands;
-
- /* The sourcecode of the menu entry, used by the editor. */
- const char *sourcecode;
-
- /* The next element. */
- struct grub_menu_entry *next;
-};
-typedef struct grub_menu_entry *grub_menu_entry_t;
-
-/* The menu. */
-struct grub_menu
-{
- /* The size of a menu. */
- int size;
-
- /* The list of menu entries. */
- grub_menu_entry_t entry_list;
-};
-typedef struct grub_menu *grub_menu_t;
-
/* This is used to store the names of filesystem modules for auto-loading. */
struct grub_fs_module_list
{
@@ -123,10 +96,12 @@
/* To exit from the normal mode. */
extern grub_jmp_buf grub_exit_env;
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
void grub_enter_normal_mode (const char *config);
void grub_normal_execute (const char *config, int nested);
-void grub_menu_run (grub_menu_t menu, int nested);
void grub_menu_entry_run (grub_menu_entry_t entry);
+void grub_menu_execute_entry(grub_menu_entry_t entry);
void grub_cmdline_run (int nested);
int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
int echo_char, int readline);
=== modified file 'include/grub/video.h'
--- include/grub/video.h 2008-07-03 14:12:08 +0000
+++ include/grub/video.h 2008-07-19 19:31:46 +0000
@@ -151,6 +151,16 @@
grub_uint8_t a; /* Reserved bits value (0-255). */
};
+/* A 2D rectangle type. */
+struct grub_video_rect
+{
+ int x;
+ int y;
+ int width;
+ int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
struct grub_video_adapter
{
/* The video adapter name. */
@@ -307,4 +317,21 @@
grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target);
+
+/* Defined in video/setmode.c */
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+ the form: x[x][;...]
+
+ For example: 640x480;800x600x8;400x300x32
+
+ If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+ DEFAULT_HEIGHT are used to set the mode. The MODE_FLAGS argument determines
+ the video mode flags such as double buffering that are used. */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+ int default_width, int default_height);
+
+
#endif /* ! GRUB_VIDEO_HEADER */
=== added file 'kern/menu_viewer.c'
--- kern/menu_viewer.c 1970-01-01 00:00:00 +0000
+++ kern/menu_viewer.c 2008-07-19 19:07:47 +0000
@@ -0,0 +1,99 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2002,2003,2005,2007 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+/* The list of menu viewers. */
+static grub_menu_viewer_t menu_viewer_list;
+
+static int should_return;
+static int menu_viewer_changed;
+
+void
+grub_menu_viewer_register (grub_menu_viewer_t viewer)
+{
+ viewer->next = menu_viewer_list;
+ menu_viewer_list = viewer;
+}
+
+static grub_menu_viewer_t get_current_menu_viewer (void)
+{
+ const char *selected_name = grub_env_get ("menuviewer");
+
+ /* If none selected, pick the last registered one. */
+ if (selected_name == 0)
+ return menu_viewer_list;
+
+ grub_menu_viewer_t cur;
+ for (cur = menu_viewer_list; cur; cur = cur->next)
+ {
+ if (grub_strcmp (cur->name, selected_name) == 0)
+ return cur;
+ }
+
+ /* Fall back to the first entry (or null). */
+ return menu_viewer_list;
+}
+
+grub_err_t
+grub_menu_viewer_show_menu (grub_menu_t menu, int nested)
+{
+ grub_err_t err;
+ int repeat = 0;
+ do
+ {
+ repeat = 0;
+ menu_viewer_changed = 0;
+ grub_menu_viewer_t cur = get_current_menu_viewer ();
+ if (!cur)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "No menu viewer available.");
+
+ should_return = 0;
+ err = cur->show_menu (menu, nested);
+ if (menu_viewer_changed)
+ repeat = 1;
+ }
+ while (repeat);
+ return err;
+}
+
+int
+grub_menu_viewer_should_return (void)
+{
+ return should_return;
+}
+
+static char *
+menuviewer_write_hook (struct grub_env_var *var __attribute__ ((unused)),
+ const char *val)
+{
+ menu_viewer_changed = 1;
+ should_return = 1;
+ return grub_strdup (val);
+}
+
+void
+grub_menu_viewer_init (void)
+{
+ grub_register_variable_hook ("menuviewer", 0, menuviewer_write_hook);
+}
+
=== modified file 'normal/main.c'
--- normal/main.c 2008-02-02 16:48:52 +0000
+++ normal/main.c 2008-07-19 19:07:47 +0000
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
grub_jmp_buf grub_exit_env;
@@ -476,7 +477,7 @@
if (menu && menu->size)
{
- grub_menu_run (menu, nested);
+ grub_menu_viewer_show_menu (menu, nested);
if (nested)
free_menu (menu);
}
@@ -519,6 +520,8 @@
if (mod)
grub_dl_ref (mod);
+ grub_menu_viewer_register (&grub_normal_terminal_menu_viewer);
+
grub_set_history (GRUB_DEFAULT_HISTORY_SIZE);
/* Register a command "normal" for the rescue mode. */
@@ -535,6 +538,8 @@
/* This registers some built-in commands. */
grub_command_init ();
+
+ grub_menu_viewer_init ();
}
GRUB_MOD_FINI(normal)
=== modified file 'normal/menu.c'
--- normal/menu.c 2008-02-09 11:00:19 +0000
+++ normal/menu.c 2008-07-19 19:24:29 +0000
@@ -24,6 +24,7 @@
#include
#include
#include
+#include
static grub_uint8_t grub_color_menu_normal;
static grub_uint8_t grub_color_menu_highlight;
@@ -364,7 +365,7 @@
if (timeout > 0)
print_timeout (timeout, offset, 0);
- while (1)
+ while (!grub_menu_viewer_should_return ())
{
int c;
timeout = get_timeout ();
@@ -468,6 +469,10 @@
}
goto refresh;
+ case 't':
+ grub_env_set ("menuviewer", "protomenu");
+ goto refresh;
+
default:
break;
}
@@ -476,13 +481,14 @@
}
}
- /* Never reach here. */
+ /* Exit menu without activating an item. This occurs if the user presses
+ * 't', switching to the graphical menu viewer. */
return -1;
}
/* Run a menu entry. */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
{
grub_script_execute (entry->commands);
@@ -491,8 +497,8 @@
grub_command_execute ("boot", 0);
}
-void
-grub_menu_run (grub_menu_t menu, int nested)
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
{
while (1)
{
@@ -513,7 +519,7 @@
grub_printf (" Booting \'%s\'\n\n", e->title);
- run_menu_entry (e);
+ grub_menu_execute_entry (e);
/* Deal with a fallback entry. */
/* FIXME: Multiple fallback entries like GRUB Legacy. */
@@ -526,7 +532,7 @@
e = get_entry (menu, fallback_entry);
grub_env_unset ("fallback");
grub_printf ("\n Falling back to \'%s\'\n\n", e->title);
- run_menu_entry (e);
+ grub_menu_execute_entry (e);
}
if (grub_errno != GRUB_ERR_NONE)
@@ -537,4 +543,12 @@
grub_wait_after_message ();
}
}
+
+ return GRUB_ERR_NONE;
}
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+ .name = "terminal",
+ .show_menu = show_menu
+};
=== modified file 'term/gfxterm.c'
--- term/gfxterm.c 2008-07-03 14:28:01 +0000
+++ term/gfxterm.c 2008-07-19 19:56:06 +0000
@@ -28,12 +28,12 @@
#include
#include
#include
+#include
#include
#include
#define DEFAULT_VIDEO_WIDTH 640
#define DEFAULT_VIDEO_HEIGHT 480
-#define DEFAULT_VIDEO_FLAGS 0
#define DEFAULT_BORDER_WIDTH 10
@@ -108,10 +108,20 @@
struct grub_colored_char *text_buffer;
};
+static int refcount;
+static struct grub_video_render_target *render_target;
+static grub_video_rect_t window;
static struct grub_virtual_screen virtual_screen;
+static grub_gfxterm_repaint_callback_t repaint_callback;
+
+static grub_err_t init_window (struct grub_video_render_target *target,
+ int x, int y, int width, int height,
+ const char *font_name, int border_width);
+
+static void destroy_window (void);
+
static grub_dl_t my_mod;
-static struct grub_video_mode_info mode_info;
static struct grub_video_render_target *text_layer;
@@ -235,287 +245,129 @@
}
static grub_err_t
+init_window (struct grub_video_render_target *target,
+ int x, int y, int width, int height,
+ const char *font_name, int border_width)
+{
+ /* Clean up any prior instance. */
+ destroy_window ();
+
+ /* Create virtual screen. */
+ if (grub_virtual_screen_setup (border_width, border_width,
+ width - 2 * border_width,
+ height - 2 * border_width,
+ font_name)
+ != GRUB_ERR_NONE)
+ {
+ return grub_errno;
+ }
+
+ /* Set the render target. */
+ render_target = target;
+
+ /* Set window bounds. */
+ window.x = x;
+ window.y = y;
+ window.width = width;
+ window.height = height;
+
+ /* Mark whole window as dirty. */
+ dirty_region_reset ();
+ dirty_region_add (0, 0, width, height);
+
+ return (grub_errno = GRUB_ERR_NONE);
+}
+
+grub_err_t
+grub_gfxterm_init_window (struct grub_video_render_target *target,
+ int x, int y, int width, int height,
+ const char *font_name, int border_width)
+{
+ grub_errno = GRUB_ERR_NONE;
+ if (refcount++ == 0)
+ init_window (target, x, y, width, height, font_name, border_width);
+ return grub_errno;
+}
+
+static grub_err_t
grub_gfxterm_init (void)
{
- char *font_name;
- char *modevar;
- int width = DEFAULT_VIDEO_WIDTH;
- int height = DEFAULT_VIDEO_HEIGHT;
- int depth = -1;
- int flags = DEFAULT_VIDEO_FLAGS;
- grub_video_color_t color;
-
- /* Select the font to use. */
- font_name = grub_env_get ("gfxterm_font");
- if (!font_name)
- font_name = ""; /* Allow fallback to any font. */
+ /* If gfxterm has already been initialized by calling the init_window
+ function, then leave it alone when it is set as the current terminal. */
+ if (refcount++ != 0)
+ return GRUB_ERR_NONE;
/* Parse gfxmode environment variable if set. */
- modevar = grub_env_get ("gfxmode");
- if (modevar)
- {
- char *tmp;
- char *next_mode;
- char *current_mode;
- char *param;
- char *value;
- int mode_found = 0;
-
- /* Take copy of env.var. as we don't want to modify that. */
- tmp = grub_strdup (modevar);
- modevar = tmp;
-
- if (grub_errno != GRUB_ERR_NONE)
- return grub_errno;
-
- /* Initialize next mode. */
- next_mode = modevar;
-
- /* Loop until all modes has been tested out. */
- while (next_mode != NULL)
- {
- /* Use last next_mode as current mode. */
- tmp = next_mode;
-
- /* Reset video mode settings. */
- width = DEFAULT_VIDEO_WIDTH;
- height = DEFAULT_VIDEO_HEIGHT;
- depth = -1;
- flags = DEFAULT_VIDEO_FLAGS;
-
- /* Save position of next mode and separate modes. */
- next_mode = grub_strchr(next_mode, ';');
- if (next_mode)
- {
- *next_mode = 0;
- next_mode++;
- }
-
- /* Skip whitespace. */
- while (grub_isspace (*tmp))
- tmp++;
-
- /* Initialize token holders. */
- current_mode = tmp;
- param = tmp;
- value = NULL;
-
- /* Parse x[x]*/
-
- /* Find width value. */
- value = param;
- param = grub_strchr(param, 'x');
- if (param == NULL)
- {
- grub_err_t rc;
-
- /* First setup error message. */
- rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
- "Invalid mode: %s\n",
- current_mode);
-
- /* Free memory before returning. */
- grub_free (modevar);
-
- return rc;
- }
-
- *param = 0;
- param++;
-
- width = grub_strtoul (value, 0, 0);
- if (grub_errno != GRUB_ERR_NONE)
- {
- grub_err_t rc;
-
- /* First setup error message. */
- rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
- "Invalid mode: %s\n",
- current_mode);
-
- /* Free memory before returning. */
- grub_free (modevar);
-
- return rc;
- }
-
- /* Find height value. */
- value = param;
- param = grub_strchr(param, 'x');
- if (param == NULL)
- {
- height = grub_strtoul (value, 0, 0);
- if (grub_errno != GRUB_ERR_NONE)
- {
- grub_err_t rc;
-
- /* First setup error message. */
- rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
- "Invalid mode: %s\n",
- current_mode);
-
- /* Free memory before returning. */
- grub_free (modevar);
-
- return rc;
- }
- }
- else
- {
- /* We have optional color depth value. */
- *param = 0;
- param++;
-
- height = grub_strtoul (value, 0, 0);
- if (grub_errno != GRUB_ERR_NONE)
- {
- grub_err_t rc;
-
- /* First setup error message. */
- rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
- "Invalid mode: %s\n",
- current_mode);
-
- /* Free memory before returning. */
- grub_free (modevar);
-
- return rc;
- }
-
- /* Convert color depth value. */
- value = param;
- depth = grub_strtoul (value, 0, 0);
- if (grub_errno != GRUB_ERR_NONE)
- {
- grub_err_t rc;
-
- /* First setup error message. */
- rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
- "Invalid mode: %s\n",
- current_mode);
-
- /* Free memory before returning. */
- grub_free (modevar);
-
- return rc;
- }
- }
-
- /* Try out video mode. */
-
- /* If we have 8 or less bits, then assume that it is indexed color mode. */
- if ((depth <= 8) && (depth != -1))
- flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
- /* We have more than 8 bits, then assume that it is RGB color mode. */
- if (depth > 8)
- flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
- /* If user requested specific depth, forward that information to driver. */
- if (depth != -1)
- flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
- & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
- /* Try to initialize requested mode. Ignore any errors. */
- grub_error_push ();
- if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
- {
- grub_error_pop ();
- continue;
- }
-
- /* Figure out what mode we ended up. */
- if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
- {
- /* Couldn't get video mode info, restore old mode and continue to next one. */
- grub_error_pop ();
-
- grub_video_restore ();
- continue;
- }
-
- /* Restore state of error stack. */
- grub_error_pop ();
-
- /* Mode found! Exit loop. */
- mode_found = 1;
- break;
- }
-
- /* Free memory. */
- grub_free (modevar);
-
- if (!mode_found)
- return grub_error (GRUB_ERR_BAD_ARGUMENT,
- "No suitable mode found.");
- }
- else
- {
- /* No gfxmode variable set, use defaults. */
-
- /* If we have 8 or less bits, then assume that it is indexed color mode. */
- if ((depth <= 8) && (depth != -1))
- flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
-
- /* We have more than 8 bits, then assume that it is RGB color mode. */
- if (depth > 8)
- flags |= GRUB_VIDEO_MODE_TYPE_RGB;
-
- /* If user requested specific depth, forward that information to driver. */
- if (depth != -1)
- flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
- & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
-
- /* Initialize user requested mode. */
- if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
- return grub_errno;
-
- /* Figure out what mode we ended up. */
- if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
- {
- grub_video_restore ();
- return grub_errno;
- }
+ const char *modevar = grub_env_get ("gfxmode");
+ if (grub_video_setup_preferred_mode (modevar, 0,
+ DEFAULT_VIDEO_WIDTH,
+ DEFAULT_VIDEO_HEIGHT)
+ != GRUB_ERR_NONE)
+ return grub_errno;
+
+ /* Figure out what mode we ended up. */
+ struct grub_video_mode_info mode_info;
+ if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+ {
+ grub_video_restore ();
+ return grub_errno;
}
/* Make sure screen is black. */
- color = grub_video_map_rgb (0, 0, 0);
- grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+ grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
+ 0, 0, mode_info.width, mode_info.height);
bitmap = 0;
+ /* Select the font to use. */
+ char *font_name = grub_env_get ("gfxterm_font");
+ if (!font_name)
+ font_name = ""; /* Allow fallback to any font. */
+
/* Leave borders for virtual screen. */
- width = mode_info.width - (2 * DEFAULT_BORDER_WIDTH);
- height = mode_info.height - (2 * DEFAULT_BORDER_WIDTH);
-
- /* Create virtual screen. */
- if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH,
- width, height, font_name) != GRUB_ERR_NONE)
+ if (init_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
+ 0, 0, mode_info.width, mode_info.height,
+ font_name,
+ DEFAULT_BORDER_WIDTH) != GRUB_ERR_NONE)
{
grub_video_restore ();
return grub_errno;
}
- /* Mark whole screen as dirty. */
- dirty_region_reset ();
- dirty_region_add (0, 0, mode_info.width, mode_info.height);
-
return (grub_errno = GRUB_ERR_NONE);
}
+static void
+destroy_window (void)
+{
+ if (bitmap)
+ {
+ grub_video_bitmap_destroy (bitmap);
+ bitmap = 0;
+ }
+
+ repaint_callback = 0;
+ grub_virtual_screen_free ();
+}
+
+void
+grub_gfxterm_destroy_window (void)
+{
+ if (--refcount == 0)
+ destroy_window ();
+}
+
static grub_err_t
grub_gfxterm_fini (void)
{
- if (bitmap)
+ /* Don't destroy an explicitly initialized terminal instance when it is
+ unset as the current terminal. */
+ if (--refcount == 0)
{
- grub_video_bitmap_destroy (bitmap);
- bitmap = 0;
+ destroy_window ();
+ grub_video_restore ();
}
- grub_virtual_screen_free ();
-
- grub_video_restore ();
-
- return GRUB_ERR_NONE;
+ return (grub_errno = GRUB_ERR_NONE);
}
static void
@@ -523,9 +375,15 @@
unsigned int width, unsigned int height)
{
grub_video_color_t color;
-
- grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
-
+ grub_video_rect_t saved_view;
+
+ grub_video_set_active_render_target (render_target);
+ /* Save viewport and set it to our window. */
+ grub_video_get_viewport ((unsigned *) &saved_view.x,
+ (unsigned *) &saved_view.y,
+ (unsigned *) &saved_view.width,
+ (unsigned *) &saved_view.height);
+ grub_video_set_viewport (window.x, window.y, window.width, window.height);
if (bitmap)
{
@@ -592,6 +450,14 @@
y - virtual_screen.offset_y,
width, height);
}
+
+ /* Restore saved viewport. */
+ grub_video_set_viewport (saved_view.x, saved_view.y,
+ saved_view.width, saved_view.height);
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+ if (repaint_callback)
+ repaint_callback (x, y, width, height);
}
static void
@@ -786,7 +652,16 @@
dirty_region_add_virtualscreen ();
}
else
- {
+ {
+ grub_video_rect_t saved_view;
+ grub_video_set_active_render_target (render_target);
+ /* Save viewport and set it to our window. */
+ grub_video_get_viewport ((unsigned *) &saved_view.x,
+ (unsigned *) &saved_view.y,
+ (unsigned *) &saved_view.width,
+ (unsigned *) &saved_view.height);
+ grub_video_set_viewport (window.x, window.y, window.width, window.height);
+
/* Clear new border area. */
grub_video_fill_rect (color,
virtual_screen.offset_x, virtual_screen.offset_y,
@@ -795,10 +670,18 @@
/* Scroll physical screen. */
grub_video_scroll (color, 0, -virtual_screen.char_height);
+ /* Restore saved viewport. */
+ grub_video_set_viewport (saved_view.x, saved_view.y,
+ saved_view.width, saved_view.height);
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
/* Draw cursor if visible. */
if (virtual_screen.cursor_state)
write_cursor ();
}
+
+ if (repaint_callback)
+ repaint_callback (window.x, window.y, window.width, window.height);
}
static void
@@ -898,7 +781,7 @@
}
static grub_ssize_t
-grub_gfxterm_getcharwidth (grub_uint32_t c)
+grub_gfxterm_getcharwidth (grub_uint32_t c __attribute__((unused)))
{
#if 0
struct grub_font_glyph *glyph;
@@ -970,7 +853,8 @@
/* Clear text layer. */
grub_video_set_active_render_target (text_layer);
color = virtual_screen.bg_color;
- grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+ grub_video_fill_rect (color, 0, 0,
+ virtual_screen.width, virtual_screen.height);
grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
/* Mark virtual screen to be redrawn. */
@@ -1039,6 +923,11 @@
dirty_region_redraw ();
}
+void
+grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func)
+{
+ repaint_callback = func;
+}
/* Option array indices. */
#define BACKGROUND_CMD_ARGINDEX_MODE 0
@@ -1066,7 +955,7 @@
/* Mark whole screen as dirty. */
dirty_region_reset ();
- dirty_region_add (0, 0, mode_info.width, mode_info.height);
+ dirty_region_add (0, 0, window.width, window.height);
}
/* If filename was provided, try to load that. */
@@ -1082,13 +971,13 @@
|| grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg,
"stretch") == 0)
{
- if (mode_info.width != grub_video_bitmap_get_width (bitmap)
- || mode_info.height != grub_video_bitmap_get_height (bitmap))
+ if (window.width != (int) grub_video_bitmap_get_width (bitmap)
+ || window.height != (int) grub_video_bitmap_get_height (bitmap))
{
struct grub_video_bitmap *scaled_bitmap;
grub_video_bitmap_create_scaled (&scaled_bitmap,
- mode_info.width,
- mode_info.height,
+ window.width,
+ window.height,
bitmap,
GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
if (grub_errno == GRUB_ERR_NONE)
@@ -1110,7 +999,7 @@
/* Mark whole screen as dirty. */
dirty_region_reset ();
- dirty_region_add (0, 0, mode_info.width, mode_info.height);
+ dirty_region_add (0, 0, window.width, window.height);
}
}
@@ -1141,9 +1030,16 @@
.next = 0
};
+grub_term_t
+grub_gfxterm_get_term (void)
+{
+ return &grub_video_term;
+}
+
GRUB_MOD_INIT(term_gfxterm)
{
my_mod = mod;
+ refcount = 0;
grub_term_register (&grub_video_term);
grub_register_command ("background_image",
=== modified file 'video/i386/pc/vbe.c'
--- video/i386/pc/vbe.c 2008-07-09 20:59:34 +0000
+++ video/i386/pc/vbe.c 2008-07-19 19:42:37 +0000
@@ -652,6 +652,8 @@
if (doublebuf_pageflipping_commit () != GRUB_ERR_NONE)
return 1; /* Unable to set the display start. */
+ framebuffer.render_target.mode_info.mode_type
+ |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
return 0;
}
@@ -690,6 +692,8 @@
doublebuf_state.update_screen = doublebuf_blit_update_screen;
doublebuf_state.destroy = doublebuf_blit_destroy;
+ framebuffer.render_target.mode_info.mode_type
+ |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
return 0;
}
@@ -713,6 +717,9 @@
framebuffer.render_target.data = framebuffer.ptr;
doublebuf_state.update_screen = doublebuf_null_update_screen;
doublebuf_state.destroy = doublebuf_null_destroy;
+
+ framebuffer.render_target.mode_info.mode_type
+ &= ~GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
return 0;
}
=== added file 'video/setmode.c'
--- video/setmode.c 1970-01-01 00:00:00 +0000
+++ video/setmode.c 2008-07-19 19:31:46 +0000
@@ -0,0 +1,249 @@
+/* video/setmode.c - Smart video mode selection based on preferences. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+ the form: x[x][;...]
+
+ For example: 640x480;800x600x8;400x300x32
+
+ If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and
+ DEFAULT_HEIGHT are used to set the mode. The MODE_FLAGS argument determines
+ the video mode flags such as double buffering that are used. */
+
+grub_err_t
+grub_video_setup_preferred_mode (const char *mode_list, int mode_flags,
+ int default_width, int default_height)
+{
+ int mode_found = 0;
+
+ if (mode_list != NULL)
+ {
+ /* Take copy of mode_list as we don't want tat. */
+ char *const modes_copy = grub_strdup (mode_list);
+ if (modes_copy == NULL)
+ return grub_errno;
+
+ /* Initialize next mode. */
+ char *next_mode = modes_copy;
+
+ /* Loop until all modes has been tested out. */
+ while ((next_mode != NULL) && !mode_found)
+ {
+ /* Use last next_mode as current mode. */
+ char *tmp = next_mode;
+
+ int width = -1;
+ int height = -1;
+ int depth = -1;
+
+ /* Save position of next mode and separate modes. */
+ next_mode = grub_strchr(next_mode, ';');
+ if (next_mode)
+ {
+ *next_mode = 0;
+ next_mode++;
+ }
+
+ /* Skip whitespace. */
+ while (grub_isspace (*tmp))
+ tmp++;
+
+ /* Initialize token holders. */
+ char *current_mode = tmp;
+ char *param = tmp;
+ char *value = NULL;
+
+ /* Parse x[x]*/
+
+ /* Find width value. */
+ value = param;
+ param = grub_strchr(param, 'x');
+ if (param == NULL)
+ {
+ grub_err_t rc;
+
+ /* First setup error message. */
+ rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Invalid mode: %s\n",
+ current_mode);
+
+ /* Free memory before returning. */
+ grub_free (modes_copy);
+
+ return rc;
+ }
+
+ *param = 0;
+ param++;
+
+ width = grub_strtoul (value, 0, 0);
+ if (grub_errno != GRUB_ERR_NONE)
+ {
+ grub_err_t rc;
+
+ /* First setup error message. */
+ rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Invalid mode: %s\n",
+ current_mode);
+
+ /* Free memory before returning. */
+ grub_free (modes_copy);
+
+ return rc;
+ }
+
+ /* Find height value. */
+ value = param;
+ param = grub_strchr(param, 'x');
+ if (param == NULL)
+ {
+ height = grub_strtoul (value, 0, 0);
+ if (grub_errno != GRUB_ERR_NONE)
+ {
+ grub_err_t rc;
+
+ /* First setup error message. */
+ rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Invalid mode: %s\n",
+ current_mode);
+
+ /* Free memory before returning. */
+ grub_free (modes_copy);
+
+ return rc;
+ }
+ }
+ else
+ {
+ /* We have optional color depth value. */
+ *param = 0;
+ param++;
+
+ height = grub_strtoul (value, 0, 0);
+ if (grub_errno != GRUB_ERR_NONE)
+ {
+ grub_err_t rc;
+
+ /* First setup error message. */
+ rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Invalid mode: %s\n",
+ current_mode);
+
+ /* Free memory before returning. */
+ grub_free (modes_copy);
+
+ return rc;
+ }
+
+ /* Convert color depth value. */
+ value = param;
+ depth = grub_strtoul (value, 0, 0);
+ if (grub_errno != GRUB_ERR_NONE)
+ {
+ grub_err_t rc;
+
+ /* First setup error message. */
+ rc = grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "Invalid mode: %s\n",
+ current_mode);
+
+ /* Free memory before returning. */
+ grub_free (modes_copy);
+
+ return rc;
+ }
+ }
+
+ /* Try out video mode. */
+
+ int flags = mode_flags;
+ /* If we have <= 8 bits, assume it is an indexed color mode. */
+ if ((depth <= 8) && (depth != -1))
+ flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR;
+
+ /* We have > 8 bits; assume that it is RGB color mode. */
+ if (depth > 8)
+ flags |= GRUB_VIDEO_MODE_TYPE_RGB;
+
+ /* If user requested specific depth, pass the request to driver. */
+ if (depth != -1)
+ flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS)
+ & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK;
+
+ /* Try to initialize requested mode. Ignore any errors. */
+ grub_error_push ();
+ if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE)
+ {
+ grub_error_pop ();
+ continue;
+ }
+
+ /* Figure out what mode we ended up. */
+ struct grub_video_mode_info mode_info;
+ if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+ {
+ /* Couldn't get video mode info, restore old mode
+ and continue to next one. */
+ grub_error_pop ();
+
+ grub_video_restore ();
+ continue;
+ }
+
+ /* Restore state of error stack. */
+ grub_error_pop ();
+
+ /* Mode found! Exit loop. */
+ mode_found = 1;
+ }
+
+ /* Free memory. */
+ grub_free (modes_copy);
+ }
+
+ if (!mode_found)
+ {
+ /* No gfxmode variable set, or no listed mode was supported.
+ Use the caller-specified defaults. */
+ int flags = mode_flags | GRUB_VIDEO_MODE_TYPE_RGB;
+
+ /* Initialize user requested mode. */
+ if (grub_video_setup (default_width, default_height, flags)
+ != GRUB_ERR_NONE)
+ return grub_errno;
+
+ /* Figure out what mode we ended up. */
+ struct grub_video_mode_info mode_info;
+ if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE)
+ grub_video_restore ();
+ else
+ mode_found = 1;
+ }
+
+ if (!mode_found)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT,
+ "No suitable mode found.");
+
+ return (grub_errno = GRUB_ERR_NONE);
+}
# Begin bundle
IyBCYXphYXIgcmV2aXNpb24gYnVuZGxlIHY0CiMKQlpoOTFBWSZTWQg8+38AZnh/gHf//9//////
///f/v////9gfb7x71q828tCbddJU5dd7NWDteHl9al32w6Hu5ZYTvCvrXoN60pvUmBSVeNaW03X
ubtm8vcfTQen2u7fd1bn3zPRlgHyoZ1gpSAo5j5veR2tmxN98qoFvfN7750cqZsnefe+n1tqVPNo
+tSS6yttPYved4BDPcA6XPd0oo5sBC0xCl4POffbyHs8oL6kVKkJSAiqOnuN55Gm7nNsubu3cPJ4
CqLu0iI94ZyAzhejNHeQPUV1s7ykvvuNfXfXK5GCun0OuhRgejdAJN7ijqLWHRZjRiHW2aEqUdKx
DYYUujCjoxq7cdABpWUrCsNd2O2C9773I8pH0NS+O6x3ZboOsnIUM2NUu23VyEk2zp73uD0JQQBG
gIQEaT0U9TYpppPKT9KPSZGjEAGmIAAGjJoNGnqMNNAEhTQCNJ6SPKaD1AABo0GTQAAAAAAAAAkI
SEjJCU/J5JqntKeptGSe1Gk8jUfqj9UANimj9Uep5JpoGg0HqGgyACTSSEE1TaNI0NU/U9NT00ST
9T2pT9Jk3pKPapp5IepkxNMj1MNQGmmg0MgaMIkiCAEGiaZDRqYI0eoaAKp+0Cp7AQ9Kfqaeo0no
1M1NI0Hqek8p6gFSRACAgIBEPUaak9TZJ7SR5E0G0g9TQNHqPUZAaGgAAHiH+B30GuQgAoECB/TK
SQfb9X1Yn9oQ/2xJ/+3jP1loVERn1Ws3ZpxiIKSoVAQthURLErAP0H1mZIoa60xh7msn4kqCihu4
kN9EoLMSYknTRfxMjDWRFJiFBchtg3ufsfP8n3/15fBwyb1VcV05vUI4ZVPqem3OW9R2Cvv06IUk
8KqjlI4O2tNtsFpnnOud8ZtxMhDeO6fQRzYHQP7Tw967kSO9ZdpqCVxX8t0LNV0V/3NeTdjz76Rd
at043q8vx0etZdOZpXVych1JUumwopy7hUiO7LDLWVnHO47nxbmad2Zh/VZeKamM4US+TXi3SYkH
/k/h/iNU/jOXXxb/s0m9ZqvfCXjIn/CLOg3/VV6jOq09kl/lio6qlv5D+YjCHxH/kK5lD3lDX/xY
r8OHockO7tT7vKV5G1U8m2/tccmW/WequFjt69rLjs3uPMOcYiSepAoxRVc7Tq5qCoGnry+4NBQn
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
j//i7kinChIBB59v4A==