# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080728020230-a79jyq7pkv9jnco1
# target_branch: http://grub.gibibit.com/bzr/trunk-clean
# testament_sha1: 990f6d3418364109d162680f19eb5aa7adddfadf
# timestamp: 2008-07-27 19:16:45 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080728020030-u9gr2usazh2r2pcw
# 
# 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-28 02:00:30 +0000
+++ conf/common.rmk	2008-07-28 02:02:30 +0000
@@ -274,6 +274,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 crc.mod
 
 # For hello.mod.
@@ -281,6 +282,16 @@
 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/stringutil.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-28 02:00:30 +0000
+++ conf/i386-pc.rmk	2008-07-28 02:02:30 +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-27 00:06:20 +0000
@@ -0,0 +1,221 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_model.h>
+#include <grub/gfxmenu_view.h>
+
+static void switch_to_text_menu (void)
+{
+  grub_env_set ("menuviewer", "terminal");
+}
+
+static void
+process_key_press (int c,
+                   grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  /* When a key is pressed, stop the timeout.  */
+  grub_gfxmenu_model_clear_timeout (model);
+
+  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)
+        *should_exit = 1;
+    }
+  else if (c == 't')
+    {
+      /* The write hook for 'menuviewer' will cause
+       * grub_menu_viewer_should_return to return nonzero. */
+      switch_to_text_menu ();
+      *should_exit = 1;
+    }
+  else if (c == '1')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/proto/theme.txt");
+    }
+  else if (c == '2')
+    {
+      grub_gfxmenu_view_load_theme (view,
+                                    "/boot/grub/themes/winter/theme.txt");
+    }
+  else if (nested && c == GRUB_TERM_ESC)
+    {
+      *should_exit = 1;
+    }
+}
+
+static void
+handle_key_events (grub_gfxmenu_model_t model,
+                   grub_gfxmenu_view_t view,
+                   int nested,
+                   int *should_exit)
+{
+  while (!*should_exit && grub_checkkey () != -1)
+    {
+      int key = grub_getkey ();
+      int c = GRUB_TERM_ASCII_CHAR (key);
+      process_key_press (c, model, view, nested, should_exit);
+    }
+}
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
+{
+  grub_gfxmenu_model_t model;
+
+  model = grub_gfxmenu_model_new (menu);
+  if (! model)
+    {
+      grub_print_error ();
+      grub_printf ("Initializing menu data for graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  grub_gfxmenu_view_t view;
+
+  /* Create the view.  */
+  const char *theme_path = grub_env_get ("theme");
+  if (! theme_path)
+    theme_path = "/boot/grub/themes/proto/theme.txt";
+
+  view = grub_gfxmenu_view_new (theme_path, model);
+  if (! view)
+    {
+      grub_print_error ();
+      grub_printf ("Starting graphical menu failed;\n"
+                   "falling back to terminal based menu.\n");
+      grub_wait_after_message ();
+      grub_gfxmenu_model_destroy (model);
+      switch_to_text_menu ();
+      return grub_errno;
+    }
+
+  /* Initially select the default menu entry.  */
+  int default_index = grub_menu_get_default_entry_index (menu);
+  grub_gfxmenu_model_set_selected_index (model, default_index);
+
+  /* Start the timer to execute the default entry.  */
+  grub_gfxmenu_model_set_timeout (model);
+
+  /* Main event loop.  */
+  int exit_requested = 0;
+  while (!exit_requested && !grub_menu_viewer_should_return ())
+    {
+      if (grub_gfxmenu_model_timeout_expired (model))
+        {
+          grub_gfxmenu_model_clear_timeout (model);
+          int i = grub_gfxmenu_model_get_selected_index (model);
+          grub_menu_entry_t e = grub_gfxmenu_model_get_entry (model, i);
+          grub_gfxmenu_view_execute_with_fallback (view, e);
+          continue;
+        }
+
+      grub_gfxmenu_view_draw (view);
+      grub_video_swap_buffers ();
+      handle_key_events (model, view, nested, &exit_requested);
+    }
+
+  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-26 23:37:04 +0000
@@ -0,0 +1,192 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/normal.h>
+#include <grub/menu.h>
+#include <grub/time.h>
+#include <grub/gfxmenu_model.h>
+
+/* Model type definition.  */
+struct grub_gfxmenu_model
+{
+  grub_menu_t menu;
+  int num_entries;
+  grub_menu_entry_t *entries;
+  int selected_entry_index;
+  int timeout_set;
+  grub_uint64_t timeout_start;
+  grub_uint64_t timeout_at;
+};
+
+
+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->menu = menu;
+  model->num_entries = menu->size;
+  model->entries = 0;
+  model->selected_entry_index = 0;
+  model->timeout_set = 0;
+  model->timeout_at = 0;
+  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);
+}
+
+grub_menu_t
+grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model)
+{
+  return model->menu;
+}
+
+void
+grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model)
+{
+  int timeout_sec = grub_menu_get_timeout ();
+  if (timeout_sec >= 0)
+    {
+      model->timeout_start = grub_get_time_ms ();
+      model->timeout_at = model->timeout_start + timeout_sec * 1000;
+      model->timeout_set = 1;
+    }
+  else
+    {
+      model->timeout_set = 0;
+    }
+}
+
+void
+grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model)
+{
+  model->timeout_set = 0;
+  grub_menu_set_timeout (-1);
+}
+
+int
+grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - model->timeout_start;
+}
+
+int
+grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model)
+{
+  if (!model->timeout_set)
+    return -1;
+
+  return model->timeout_at - grub_get_time_ms ();
+}
+
+int
+grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model)
+{
+  if (model->timeout_set 
+      && grub_get_time_ms () >= model->timeout_at)
+    return 1;
+
+  return 0;
+}
+
+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/stringutil.c'
--- gfxmenu/stringutil.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/stringutil.c	2008-07-26 00:44:48 +0000
@@ -0,0 +1,196 @@
+/* stringutil.c - String utilities. */
+/*
+ *  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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/stringutil.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+
+/* Create a new NUL-terminated string on the heap as a substring of BUF.
+   The range of buf included is the half-open interval [START,END).
+   The index START is inclusive, END is exclusive.  */
+char *
+grub_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;
+}
+
+/* Eliminate "." and ".." path elements from PATH.  A new heap-allocated
+   string is returned.  */
+static char *
+canonicalize_path (const char *path)
+{
+  int i;
+  const char *p;
+  char *newpath = 0;
+
+  /* Count the path components in path.  */
+  int components = 1;
+  for (p = path; *p; p++)
+    if (*p == '/')
+      components++;
+
+  char **path_array = grub_malloc (components * sizeof (*path_array));
+  if (! path_array)
+    return 0;
+
+  /* Initialize array elements to NULL pointers; in case once of the
+     allocations fails, the cleanup code can just call grub_free() for all
+     pointers in the array.  */
+  for (i = 0; i < components; i++)
+    path_array[i] = 0;
+
+  /* Parse the path into path_array.  */
+  p = path;
+  for (i = 0; i < components && p; i++)
+    {
+      /* Find the end of the path element.  */
+      const char *end = grub_strchr (p, '/');
+      if (!end)
+        end = p + grub_strlen (p);
+
+      /* Copy the element.  */
+      path_array[i] = grub_new_substring (p, 0, end - p);
+      if (!path_array[i])
+        goto cleanup;
+
+      /* Advance p to point to the start of the next element, or NULL.  */
+      if (*end)
+        p = end + 1;
+      else
+        p = 0;
+    }
+
+  /* Eliminate '.' and '..' elements from the path array.  */
+  int newpath_length = 0;
+  for (i = components - 1; i >= 0; --i)
+    {
+      if (! grub_strcmp (path_array[i], "."))
+        {
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else if (! grub_strcmp (path_array[i], "..")
+               && i > 0)
+        {
+          /* Delete the '..' and the prior path element.  */
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+          --i;
+          grub_free (path_array[i]);
+          path_array[i] = 0;
+        }
+      else
+        {
+          newpath_length += grub_strlen (path_array[i]) + 1;
+        }
+    }
+
+  /* Construct a new path string.  */
+  newpath = grub_malloc (newpath_length + 1);
+  if (! newpath)
+    goto cleanup;
+
+  newpath[0] = '\0';
+  char *newpath_end = newpath;
+  int first = 1;
+  for (i = 0; i < components; i++)
+    {
+      char *element = path_array[i];
+      if (element)
+        {
+          /* For all components but the first, prefix with a slash.  */
+          if (! first)
+            newpath_end = grub_stpcpy (newpath_end, "/");
+          newpath_end = grub_stpcpy (newpath_end, element);
+          first = 0;
+        }
+    }
+
+cleanup:
+  for (i = 0; i < components; i++)
+    grub_free (path_array[i]);
+  grub_free (path_array);
+
+  return newpath;
+}
+
+/* Return a new heap-allocated string representing to absolute path
+   to the file referred to by PATH.  If PATH is an absolute path, then
+   the returned path is a copy of PATH.  If PATH is a relative path, then
+   BASE is with PATH used to construct the absolute path.  */
+char *
+grub_resolve_relative_path (const char *base, const char *path)
+{
+  char *abspath;
+  char *canonpath;
+  char *p;
+
+  /* If PATH is an absolute path, then just use it as is.  */
+  if (path[0] == '/' || path[0] == '(')
+    return canonicalize_path (path);
+
+  abspath = grub_malloc (grub_strlen (base) + grub_strlen (path) + 1);
+  if (!abspath)
+    return 0;
+
+  /* Concatenate BASE and PATH.
+     Note that BASE is expected to have a trailing slash.  */
+  p = grub_stpcpy (abspath, base);
+  grub_stpcpy (p, path);
+
+  canonpath = canonicalize_path (abspath);
+  if (!canonpath)
+    return abspath;
+
+  grub_free (abspath);
+  return canonpath;
+}
+
+/* Get the path of the directory where the file at FILE_PATH is located.
+   FILE_PATH should refer to a file, not a directory.  The returned path
+   includes a trailing slash.
+   This does not handle GRUB "(hd0,0)" paths properly yet since it only
+   looks at slashes.  */
+char *
+grub_get_dirname (const char *file_path)
+{
+  int i;
+  int last_slash;
+
+  last_slash = -1;
+  for (i = grub_strlen (file_path) - 1; i >= 0; --i)
+    {
+      if (file_path[i] == '/')
+        {
+          last_slash = i;
+          break;
+        }
+    }
+  if (last_slash == -1)
+    return grub_strdup ("/");
+
+  return grub_new_substring (file_path, 0, last_slash + 1);
+}

=== added file 'gfxmenu/view.c'
--- gfxmenu/view.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c	2008-07-27 00:32:41 +0000
@@ -0,0 +1,959 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/file.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/video.h>
+#include <grub/stringutil.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/term.h>
+#include <grub/gfxwidgets.h>
+#include <grub/time.h>
+#include <grub/menu.h>
+#include <grub/menu_viewer.h>
+#include <grub/gfxmenu_view.h>
+
+/* Specifies a time (in ms) to delay so the user can read the message if a
+   default boot attempt fails and an attempt is made to fall back on
+   another entry.  */
+#define FALLBACK_MESSAGE_DELAY 2000
+
+/* Definition of the private representation of the view.  */
+struct grub_gfxmenu_view
+{
+  grub_video_rect_t screen;
+
+  int icon_width;
+  int icon_height;
+  int item_height;
+  int item_padding;
+  int item_icon_space;
+  int item_spacing;
+  grub_font_t title_font;
+  grub_font_t item_font;
+  grub_font_t selected_item_font;
+  grub_font_t status_font;
+  char *terminal_font_name;
+  grub_video_color_t title_color;
+  grub_video_color_t item_color;
+  grub_video_color_t selected_item_color;
+  grub_video_color_t status_color;
+  grub_video_color_t status_bg_color;
+  grub_video_color_t progress_bar_border_color;
+  grub_video_color_t progress_bar_fg_color;
+  grub_video_color_t progress_bar_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;
+  grub_gfxmenu_box_t terminal_box;
+  char *title_text;
+  char *progress_message_text;
+
+  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);
+
+/* Create a new view object, loading the theme specified by THEME_PATH and
+   associating MODEL with the view.  */
+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 (0, 0, 0),
+                        view->screen.x, view->screen.y,
+                        view->screen.width, view->screen.height);
+  grub_video_swap_buffers ();
+
+  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->item_padding = 14;
+  view->item_icon_space = 4;
+  view->item_spacing = 16;
+  view->title_font = default_font;
+  view->item_font = default_font;
+  view->selected_item_font = default_font;
+  view->status_font = default_font;
+  view->terminal_font_name = grub_strdup ("Fixed 10");
+  view->title_color = default_fg_color;
+  view->item_color = default_fg_color;
+  view->selected_item_color = default_fg_color;
+  view->status_color = default_bg_color;
+  view->status_bg_color = default_fg_color;
+  view->progress_bar_border_color = default_fg_color;
+  view->progress_bar_fg_color = grub_video_map_rgb (160, 160, 160);
+  view->progress_bar_bg_color = default_bg_color;
+  view->desktop_image = 0;
+  view->desktop_color = default_bg_color;
+  view->menu_box = 0;
+  view->selected_item_box = 0;
+  view->terminal_box = 0;
+  view->title_text = grub_strdup ("GRUB Boot Menu");
+  view->progress_message_text = 0;
+
+  view->model = model;
+
+  if (! grub_gfxmenu_view_load_theme (view, theme_path))
+    {
+      grub_gfxmenu_view_destroy (view);
+      return 0;
+    }
+
+  init_terminal (view);
+
+  return view;
+}
+
+/* Destroy the view object.  All used memory is freed.  */
+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);
+  if (view->terminal_box)
+    view->terminal_box->destroy (view->terminal_box);
+  grub_free (view->terminal_font_name);
+  grub_free (view->title_text);
+  grub_free (view->progress_message_text);
+  grub_free (view);
+
+  set_text_mode ();
+  destroy_terminal ();
+}
+
+/* Sets MESSAGE as the progress message for the view.  The string MESSAGE
+   must be heap-allocated and will be owned by VIEW.  MESSAGE can be 0, in
+   which case no message is displayed.  */
+static void
+set_progress_message (grub_gfxmenu_view_t view, char *message)
+{
+  grub_free (view->progress_message_text);
+  view->progress_message_text = message;
+}
+
+/* Parse a color string of the form "r, g, b".
+   Whitespace is insignificant.  */
+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;
+}
+
+/* Construct a new box widget using PATTERN to find the pixmap files for it,
+   storing the new widget at *BOXPTR.  PATTERN should be of the form:
+   "somewhere/style*.png".  The '*' then gets substituted with the various
+   pixmap names that the widget uses.
+   Return zero on failure, nonzero on success.  */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr,
+               char *pattern, const char *theme_dir)
+{
+  char *abspattern;
+  char *prefix;
+  char *suffix;
+  char *star;
+  grub_gfxmenu_box_t box;
+
+  abspattern = grub_resolve_relative_path (theme_dir, pattern);
+  if (! abspattern)
+    return 0;
+
+  star = grub_strchr (abspattern, '*');
+  if (! star)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  /* Prefix:  Get the part before the '*'.  */
+  prefix = grub_malloc (star - abspattern + 1);
+  if (! prefix)
+    {
+      grub_free (abspattern);
+      return 0;
+    }
+
+  grub_memcpy (prefix, abspattern, star - abspattern);
+  prefix[star - abspattern] = '\0';
+
+  /* Suffix:  Everything after the '*' is the suffix.  */
+  suffix = star + 1;
+
+  box = grub_gfxmenu_create_box (prefix, suffix);
+  grub_free (abspattern);   /* Note: suffix, star point into abspattern.  */
+  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,
+                  const char *theme_dir)
+{
+  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 ("selected-item-font", name))
+    view->selected_item_font = grub_font_get (value);
+  else if (! grub_strcmp ("status-font", name))
+    view->status_font = grub_font_get (value);
+  else if (! grub_strcmp ("terminal-font", name))
+    {
+      grub_free (view->terminal_font_name);
+      view->terminal_font_name = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+  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 ("selected-item-color", name))
+    view->selected_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 ("progress-bar-border-color", name))
+    view->progress_bar_border_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-fg-color", name))
+    view->progress_bar_fg_color = parse_color (value);
+  else if (! grub_strcmp ("progress-bar-bg-color", name))
+    view->progress_bar_bg_color = parse_color (value);
+  else if (! grub_strcmp ("desktop-image", name))
+    {
+      struct grub_video_bitmap *raw_bitmap;
+      struct grub_video_bitmap *scaled_bitmap;
+      char *path;
+      path = grub_resolve_relative_path (theme_dir, value);
+      if (! path)
+        goto fail;
+      if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
+        {
+          grub_free (path);
+          goto fail;
+        }
+      grub_free(path);
+      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, theme_dir);
+  else if (! grub_strcmp ("selected-item-box", name))
+    theme_set_box (&view->selected_item_box, value, theme_dir);
+  else if (! grub_strcmp ("terminal-box", name))
+    theme_set_box (&view->terminal_box, value, theme_dir);
+  else if (! grub_strcmp ("title-text", name))
+    {
+      grub_free (view->title_text);
+      view->title_text = value;
+      value = 0;  /* Prevent value from being freed below.  */
+    }
+
+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;
+  else if (! grub_strcmp ("item-padding", name))
+    view->item_padding = value;
+  else if (! grub_strcmp ("item-icon-space", name))
+    view->item_icon_space = value;
+  else if (! grub_strcmp ("item-spacing", name))
+    view->item_spacing = value;
+
+  grub_free (name);
+}
+
+/* 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)
+{
+  char *theme_dir;
+  grub_file_t file;
+  char *buf;
+  int pos;
+  int len;
+
+  theme_dir = grub_get_dirname (theme_path);
+
+  file = grub_file_open (theme_path);
+  if (!file)
+    {
+      grub_free (theme_dir);
+      return 0;
+    }
+
+  len = grub_file_size (file);
+  buf = grub_malloc (len);
+  if (! buf)
+    {
+      grub_file_close (file);
+      grub_free (theme_dir);
+      return 0;
+    }
+  if (grub_file_read (file, buf, len) != len)
+    {
+      grub_free (buf);
+      grub_file_close (file);
+      grub_free (theme_dir);
+      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,
+                            grub_new_substring (buf, name_start, name_end),
+                            grub_new_substring (buf, value_start, value_end),
+                            theme_dir);
+        }
+      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 = grub_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,
+                            grub_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);
+  grub_free (theme_dir);
+  return 1;
+}
+
+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 = view->item_padding;
+  int icon_text_space = view->item_icon_space;
+  int item_vspace = view->item_spacing;
+
+  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++)
+    {
+      int is_selected =
+        (i == grub_gfxmenu_model_get_selected_index (view->model));
+
+      if (is_selected)
+        {
+          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);
+
+      const char *item_title =
+        grub_gfxmenu_model_get_entry_title (view->model, i);
+      grub_video_draw_string (item_title,
+                              (is_selected
+                               ? view->selected_item_font
+                               : view->item_font),
+                              (is_selected
+                               ? view->selected_item_color
+                               : 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->title_text)
+    return;
+
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (view->title_font,
+                                                view->title_text);
+  int x = (view->screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (view->title_font);
+  grub_video_draw_string (view->title_text,
+                          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);
+}
+
+static void
+draw_timeout (grub_gfxmenu_view_t view)
+{
+  int timeout = grub_gfxmenu_model_get_timeout_ms (view->model);
+  if (timeout == -1)
+    return;
+
+  int remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model);
+  if (remaining < 0)
+    remaining = 0;
+
+  int t = timeout - remaining;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 3 / 5;
+  f.height = view->screen.height / 25;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90;
+
+  /* First attempt just uses filled rectangles;
+     TODO we should enhance with a pixmap themed progress bar component.  */
+
+  /* Border.  */
+  grub_video_fill_rect (view->progress_bar_border_color,
+                        f.x - 1, f.y - 1,
+                        f.width + 2, f.height + 2);
+
+  /* Bar background.  */
+  int barwidth = f.width * t / timeout;
+  grub_video_fill_rect (view->progress_bar_bg_color,
+                        f.x + barwidth, f.y,
+                        f.width - barwidth, f.height);
+
+  /* Bar foreground.  */
+  grub_video_fill_rect (view->progress_bar_fg_color,
+                        f.x, f.y,
+                        barwidth, f.height);
+
+  char *text = grub_malloc (200);
+  if (!text)
+    return;
+  int seconds_remaining_rounded_up = (remaining + 999) / 1000;
+  grub_sprintf (text,
+                "The highlighted entry will be booted automatically in %d s.",
+                seconds_remaining_rounded_up);
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->progress_bar_border_color;
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+  grub_free (text);
+}
+
+static void
+draw_message (grub_gfxmenu_view_t view)
+{
+  char *text = view->progress_message_text;
+  if (! text)
+    return;
+
+  grub_font_t font = view->status_font;
+  grub_video_color_t color = view->status_color;
+
+  /* Set the timeout bar's frame.  */
+  grub_video_rect_t f;
+  f.width = view->screen.width * 4 / 5;
+  f.height = 50;
+  f.x = view->screen.x + (view->screen.width - f.width) / 2;
+  f.y = view->screen.y + view->screen.height - 90 - 20 - f.height;
+
+  /* Border.  */
+  grub_video_fill_rect (color,
+                        f.x-1, f.y-1, f.width+2, f.height+2);
+  /* Fill.  */
+  grub_video_fill_rect (view->status_bg_color,
+                        f.x, f.y, f.width, f.height);
+
+  /* Center the text. */
+  int text_width = grub_font_get_string_width (font, text);
+  int x = f.x + (f.width - text_width) / 2;
+  int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+           + grub_font_get_ascent (font) / 2);
+  grub_video_draw_string (text, font, color, x, y);
+}
+
+
+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);
+  draw_timeout (view);
+  draw_message (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 int term_initialized;
+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)))
+{
+  if (! term_view)
+    return;
+
+  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 - 7) / 10 / 2;
+  int termy = term_view->screen.y
+    + term_view->screen.height * (10 - 7) / 10 / 2;
+
+  grub_gfxmenu_box_t term_box = term_view->terminal_box;
+  if (term_box)
+    {
+      term_box->set_content_size (term_box,
+                                  term_target_width, term_target_height);
+
+      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 * 7 / 10;
+  term_target_height = view->screen.height * 7 / 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;
+
+  /* Note: currently there is no API for changing the gfxterm font
+     on the fly, so whatever font the initially loaded theme specifies
+     will be permanent.  */
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            view->terminal_font_name, 3);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+  term_initialized = 1;
+
+  /* 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_view = 0;
+  if (term_initialized)
+    grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  if (term_target)
+    grub_video_delete_render_target (term_target);
+  if (term_original)
+    grub_term_set_current (term_original);
+}
+
+
+static void
+notify_booting (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Booting '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+}
+
+static void
+notify_fallback (void *userdata, grub_menu_entry_t entry)
+{
+  grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+  char *s = grub_malloc (100 + grub_strlen (entry->title));
+  if (!s)
+    return;
+
+  grub_sprintf (s, "Falling back to '%s'", entry->title);
+  set_progress_message (view, s);
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  grub_millisleep (FALLBACK_MESSAGE_DELAY);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__ ((unused)))
+{
+}
+
+
+static struct grub_menu_execute_callback execute_callback =
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+int
+grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+                                         grub_menu_entry_t entry)
+{
+  grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view->model),
+                                   entry, &execute_callback, (void *) view);
+
+  if (set_graphics_mode () != GRUB_ERR_NONE)
+    return 0;  /* Failure.  */
+
+  /* If we returned, there was a failure.  */
+  set_progress_message (view, grub_strdup ("Unable to automatically boot."));
+  grub_gfxmenu_view_draw (view);
+  grub_video_swap_buffers ();
+  grub_millisleep (5000);
+  set_progress_message (view, 0);   /* Clear the message.  */
+
+  return 1;   /* Ok.  */
+}
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/video.h>
+#include <grub/bitmap.h>
+#include <grub/bitmap_scale.h>
+#include <grub/gfxwidgets.h>
+
+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-26 23:37:04 +0000
@@ -0,0 +1,59 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_MODEL_HEADER
+#define GRUB_GFXMENU_MODEL_HEADER 1
+
+#include <grub/menu.h>
+
+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);
+
+grub_menu_t grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_timeout_expired (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-26 23:37:04 +0000
@@ -0,0 +1,53 @@
+/* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_VIEW_HEADER
+#define GRUB_GFXMENU_VIEW_HEADER 1
+
+#include <grub/types.h>
+#include <grub/err.h>
+#include <grub/menu.h>
+#include <grub/gfxmenu_model.h>
+
+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_with_fallback (grub_gfxmenu_view_t view,
+                                             grub_menu_entry_t entry);
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXTERM_HEADER
+#define GRUB_GFXTERM_HEADER	1
+
+#include <grub/err.h>
+#include <grub/types.h>
+#include <grub/term.h>
+#include <grub/video.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXWIDGETS_HEADER
+#define GRUB_GFXWIDGETS_HEADER 1
+
+#include <grub/video.h>
+
+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 <http://www.gnu.org/licenses/>.
+ */
+
+#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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_MENU_VIEWER_HEADER
+#define GRUB_MENU_VIEWER_HEADER 1
+
+#include <grub/err.h>
+#include <grub/symbol.h>
+#include <grub/types.h>
+#include <grub/menu.h>
+
+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-26 23:37:04 +0000
@@ -25,6 +25,7 @@
 #include <grub/err.h>
 #include <grub/arg.h>
 #include <grub/env.h>
+#include <grub/menu.h>
 
 /* 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,27 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
+typedef struct grub_menu_execute_callback
+{
+  void (*notify_booting) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_fallback) (void *userdata, grub_menu_entry_t entry);
+  void (*notify_failure) (void *userdata);
+} 
+*grub_menu_execute_callback_t;
+
 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_execute_with_fallback (grub_menu_t menu,
+                                      grub_menu_entry_t entry,
+                                      grub_menu_execute_callback_t callback,
+                                      void *callback_data);
 void grub_menu_entry_run (grub_menu_entry_t entry);
+int grub_menu_get_default_entry_index (grub_menu_t menu);
+int grub_menu_get_timeout (void);
+void grub_menu_set_timeout (int timeout);
+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);

=== added file 'include/grub/stringutil.h'
--- include/grub/stringutil.h	1970-01-01 00:00:00 +0000
+++ include/grub/stringutil.h	2008-07-25 15:52:57 +0000
@@ -0,0 +1,32 @@
+/* gfxmenu_stringutil.h - String utilities for the graphical menu 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GRUB_GFXMENU_STRINGUTIL_HEADER
+#define GRUB_GFXMENU_STRINGUTIL_HEADER 1
+
+#include <grub/types.h>
+
+char *grub_new_substring (const char *buf,
+                          grub_size_t start, grub_size_t end);
+
+char *grub_resolve_relative_path (const char *base, const char *path);
+
+char *grub_get_dirname (const char *file_path);
+
+#endif /* GRUB_GFXMENU_STRINGUTIL_HEADER */

=== 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: <width>x<height>[x<depth>][;...]
+
+   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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/mm.h>
+#include <grub/misc.h>
+#include <grub/env.h>
+#include <grub/menu_viewer.h>
+#include <grub/menu.h>
+
+/* 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 <grub/env.h>
 #include <grub/parser.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 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-26 23:37:04 +0000
@@ -21,9 +21,11 @@
 #include <grub/misc.h>
 #include <grub/loader.h>
 #include <grub/mm.h>
+#include <grub/time.h>
 #include <grub/machine/time.h>
 #include <grub/env.h>
 #include <grub/script.h>
+#include <grub/menu_viewer.h>
 
 static grub_uint8_t grub_color_menu_normal;
 static grub_uint8_t grub_color_menu_highlight;
@@ -241,8 +243,8 @@
 
 /* Return the current timeout. If the variable "timeout" is not set or
    invalid, return -1.  */
-static int
-get_timeout (void)
+int
+grub_menu_get_timeout (void)
 {
   char *val;
   int timeout;
@@ -269,8 +271,8 @@
 }
 
 /* Set current timeout in the variable "timeout".  */
-static void
-set_timeout (int timeout)
+void
+grub_menu_set_timeout (int timeout)
 {
   /* Ignore TIMEOUT if it is zero, because it will be unset really soon.  */
   if (timeout > 0)
@@ -308,6 +310,57 @@
   return entry;
 }
 
+/* Get the default menu entry index.  */
+int
+grub_menu_get_default_entry_index (grub_menu_t menu)
+{
+  int i = get_entry_number ("default");
+
+  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
+     the first entry.  */
+  if (i < 0 || i >= menu->size)
+    i = 0;
+
+  return i;
+}
+
+/* Get the first entry number from the variable NAME, which is a 
+   space-separated list of nonnegative integers.  The entry number which
+   is returned is stripped from the value of NAME.  */
+static int
+get_and_remove_first_entry_number (const char *name)
+{
+  char *val;
+  char *tail;
+  int entry;
+  
+  val = grub_env_get (name);
+  if (! val)
+    return -1;
+  
+  grub_error_push ();
+  
+  entry = (int) grub_strtoul (val, &tail, 0);
+
+  if (grub_errno == GRUB_ERR_NONE)
+    {
+      /* Skip whitespace to find the next digit.  */
+      while (*tail && grub_isspace (*tail))
+	tail++;
+      grub_env_set (name, tail);
+    }
+  else
+    {
+      grub_env_unset (name);
+      grub_errno = GRUB_ERR_NONE;
+      entry = -1;
+    }
+
+  grub_error_pop ();
+  
+  return entry;
+}
+
 static void
 print_timeout (int timeout, int offset, int second_stage)
 {
@@ -332,15 +385,10 @@
   
   first = 0;
   
-  default_entry = get_entry_number ("default");
-
-  /* If DEFAULT_ENTRY is not within the menu entries, fall back to
-     the first entry.  */
-  if (default_entry < 0 || default_entry >= menu->size)
-    default_entry = 0;
+  default_entry = grub_menu_get_default_entry_index (menu);
 
   /* If timeout is 0, drawing is pointless (and ugly).  */
-  if (get_timeout () == 0)
+  if (grub_menu_get_timeout () == 0)
     return default_entry;
 
   offset = default_entry;
@@ -359,15 +407,15 @@
   print_entries (menu, first, offset);
   grub_refresh ();
 
-  timeout = get_timeout ();
+  timeout = grub_menu_get_timeout ();
 
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
-      timeout = get_timeout ();
+      timeout = grub_menu_get_timeout ();
       
       if (timeout > 0)
 	{
@@ -377,7 +425,7 @@
 	  if (current_time - saved_time >= GRUB_TICKS_PER_SECOND)
 	    {
 	      timeout--;
-	      set_timeout (timeout);
+	      grub_menu_set_timeout (timeout);
 	      saved_time = current_time;
 	      print_timeout (timeout, offset, 1);
 	    }
@@ -468,6 +516,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +528,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,15 +544,80 @@
     grub_command_execute ("boot", 0);
 }
 
+/* Execute ENTRY from the menu MENU, falling back to entries specified
+   in the environment variable "fallback" if it fails.  CALLBACK is a 
+   pointer to a struct of function pointers which are used to allow the
+   caller provide feedback to the user.  */
 void
-grub_menu_run (grub_menu_t menu, int nested)
+grub_menu_execute_with_fallback (grub_menu_t menu,
+				 grub_menu_entry_t entry,
+				 grub_menu_execute_callback_t callback,
+				 void *callback_data)
+{
+  int fallback_entry;
+
+  callback->notify_booting (callback_data, entry);
+
+  grub_menu_execute_entry (entry);
+
+  /* Deal with fallback entries.  */
+  while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
+	 >= 0)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      entry = get_entry (menu, fallback_entry);
+      callback->notify_fallback (callback_data, entry);
+      grub_menu_execute_entry (entry);
+    }
+
+  if (grub_errno != GRUB_ERR_NONE)
+    callback->notify_failure (callback_data);
+}
+
+static void
+notify_booting (void *userdata __attribute__((unused)),
+		grub_menu_entry_t entry)
+{
+  grub_printf ("  Booting \'%s\'\n\n", entry->title);
+}
+
+static void
+notify_fallback (void *userdata __attribute__((unused)),
+		 grub_menu_entry_t entry)
+{
+  grub_printf ("\n  Falling back to \'%s\'\n\n", entry->title);
+  grub_millisleep (2000);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__((unused)))
+{
+  if (grub_errno != GRUB_ERR_NONE)
+    {
+      grub_print_error ();
+      grub_errno = GRUB_ERR_NONE;
+
+      grub_wait_after_message ();
+    }
+}
+
+static struct grub_menu_execute_callback execution_callback = 
+{
+  .notify_booting = notify_booting,
+  .notify_fallback = notify_fallback,
+  .notify_failure = notify_execution_failure
+};
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
       int boot_entry;
       grub_menu_entry_t e;
-      int fallback_entry;
-      
+
       boot_entry = run_menu (menu, nested);
       if (boot_entry < 0)
 	break;
@@ -507,34 +625,18 @@
       e = get_entry (menu, boot_entry);
       if (! e)
 	continue; /* Menu is empty.  */
-	
+
       grub_cls ();
       grub_setcursor (1);
 
-      grub_printf ("  Booting \'%s\'\n\n", e->title);
-  
-      run_menu_entry (e);
-
-      /* Deal with a fallback entry.  */
-      /* FIXME: Multiple fallback entries like GRUB Legacy.  */
-      fallback_entry = get_entry_number ("fallback");
-      if (fallback_entry >= 0)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-	  
-	  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);
-	}
-
-      if (grub_errno != GRUB_ERR_NONE)
-	{
-	  grub_print_error ();
-	  grub_errno = GRUB_ERR_NONE;
-
-	  grub_wait_after_message ();
-	}
+      grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
     }
+
+  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 <grub/mm.h>
 #include <grub/env.h>
 #include <grub/video.h>
+#include <grub/gfxterm.h>
 #include <grub/bitmap.h>
 #include <grub/bitmap_scale.h>
 
 #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 <width>x<height>[x<depth>]*/
-
-          /* 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/video.h>
+#include <grub/types.h>
+#include <grub/mm.h>
+#include <grub/misc.h>
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <width>x<height>[x<depth>][;...]
+
+   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 <width>x<height>[x<depth>]*/
+
+          /* 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
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
lw/LVmjKjKmuoff8OxPNc0HKM6pNNeBr7IATOxQ/O4GBoLtiY1blfLERMfWL29rJoeXtKp5yPDr7
Sp5U7J9mygPkahW9vQ6b6BBriOgcYsnJlFHZuqO7s6O6Mj6UYROh3QvA9m6fZe6CqgoqRPToiJnX
Z7RMvsWmXxETsc7sDBLK7AhyUNLJAkIMIVxeMXtuL2GOOf0MecH8rL7/Bzn+SgwIRT9UkQkEkD9k
BJiBCSBKA4jDxz/F7/4H93fz3N/ck99Zn786Ss35xyytWWGm5yZtXiYDZFK1yMmlYrCvK2v66/F7
34v7b+39azb1+LTscfRrr/BRcPl0b8KM0fn0ht1wfN48r8ffh274/r5vUiHu2ohF8O/cgdpynSl2
+IWWrGBDnC7TerEoS1OzPJTEQugOv6fX69B216atajTOopDcqHLdev4qRXBzKbs1Y6jMuU0RqodK
xZrSPp9/4n3NZ7ldPc7PxvqccfW/WeXve95aVOjJsqdFT6j8pY8FTZo6PY+v5efafR8J+KIaX5Z7
dKe3ikfMtza3Ctj4zhg1Go882JPbCHsjTrpOMKTV5uOH8p674muZ+SsZ+fPrz9nrr0S/r8nLO0D1
zrt4vxzdzWPPHsvNXgyse4aeKpYzjV08LtuK6zll2/V7LL6eO665tjq3d2F+nZ3dbQ27Lrl3at3V
Zo9cm5t3HW3dZYShGO849kSTQdpQk03pB4wpH6D4mbez20oPovLOM2MTqfUHROHWrw73iv6IyK+g
5fyoOyzvZ6zBxTQUed/Y7BdCD+8/fBoPnZFSjNhmS4FyNEk3qZimg4Ooqxt8iIeLDnA9UP+tUVEo
DBRWEBVkREEnjdedv0390Gxp5elu/w9MfQ5jwuhB290PVfG2unu92G/pogh40Q6ehEIdP/JqiRD/
6P+r4bv/HAKn/xRUZzIEqQCSxS0Bv+wwpn9GSEC5LIKUQpRAoxkiRCn6ChWUyYCGFpKSwoFAlE/l
h0yBZIMZIEUIPuF5hPGK70VfmbgdgvSB8r6xwQ8JcLGkIefYFk/Shtih/ws/qJCAfGB/wifm5VKZ
OREKQoUIMAkGBAj83mLGy1G/504k3y+THb+WY8/2qTf8HhRiGR9yHz8fvYUY/JoeAofyCxOb/T+X
ev7n6KdtKUn2Z53p+f3H9bD9pOy/9P9A/CHbswOqJ4qgWnzpEeggB54IdZxY7UuD+6IIPpivjkkh
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
4UJDpT4ISA==
