# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080719214640-ixudxurtvb5v5zpy
# target_branch: http://grub.gibibit.com/bzr/trunk-clean
# testament_sha1: 7597022c488e88b1cb28dfd2082bb898b6411899
# timestamp: 2008-07-19 14:52:00 -0700
# source_branch: http://grub.gibibit.com/bzr/cdbmain
# base_revision_id: colin@gibibit.com-20080719214404-ye3x3zjigausaru3
# 
# Begin patch
=== modified file 'commands/videotest.c'
--- commands/videotest.c	2008-07-09 16:14:04 +0000
+++ commands/videotest.c	2008-07-19 18:25:18 +0000
@@ -49,19 +49,6 @@
   int double_buffering;
 };
 
-/* A 2D rectangle type.
- * This could be worth integrating into the video API if it proves useful.*/
-struct grub_video_rect
-{
-  /* These are signed because if there are unsigned it causes Bad Things 
-   * to happen when arithmetic and comparisions involving signed types is 
-   * done.  Important signed types include offsets from absolute locations. */
-  int x;
-  int y;
-  int width;
-  int height;
-};
-typedef struct grub_video_rect grub_video_rect_t;
 
 static void
 basic_video_test (struct videotest_options *vt_opts)

=== modified file 'conf/common.rmk'
--- conf/common.rmk	2008-07-19 21:39:24 +0000
+++ conf/common.rmk	2008-07-19 21:46:40 +0000
@@ -269,6 +269,7 @@
 	cmp.mod cat.mod help.mod font.mod search.mod		\
 	loopback.mod fs_uuid.mod configfile.mod echo.mod	\
 	terminfo.mod test.mod blocklist.mod hexdump.mod		\
+	gfxmenu.mod                                             \
 	read.mod sleep.mod loadenv.mod
 
 # For hello.mod.
@@ -276,6 +277,11 @@
 hello_mod_CFLAGS = $(COMMON_CFLAGS)
 hello_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
+# For gfxmenu.mod.
+gfxmenu_mod_SOURCES = gfxmenu/gfxmenu.c gfxmenu/widget-box.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
 # For boot.mod.
 boot_mod_SOURCES = commands/boot.c
 boot_mod_CFLAGS = $(COMMON_CFLAGS)

=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk	2008-07-19 21:39:45 +0000
+++ conf/i386-pc.rmk	2008-07-19 21:46:40 +0000
@@ -46,11 +46,13 @@
 	kern/i386/tsc.c \
 	kern/generic/millisleep.c \
 	kern/env.c \
+	kern/menu_viewer.c \
 	term/i386/pc/console.c \
 	symlist.c
 kernel_img_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
 	env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
 	partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \
+	menu_viewer.h \
 	machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \
 	machine/memory.h machine/loader.h machine/vga.h machine/vbe.h machine/kernel.h
 kernel_img_CFLAGS = $(COMMON_CFLAGS)
@@ -248,7 +250,7 @@
 play_mod_LDFLAGS = $(COMMON_LDFLAGS)
 
 # For video.mod.
-video_mod_SOURCES = video/video.c
+video_mod_SOURCES = video/video.c video/setmode.c
 video_mod_CFLAGS = $(COMMON_CFLAGS)
 video_mod_LDFLAGS = $(COMMON_LDFLAGS)
 

=== modified file 'config.h.in'
--- config.h.in	2008-07-13 00:55:15 +0000
+++ config.h.in	2008-07-19 21:46:40 +0000
@@ -113,10 +113,37 @@
 /* Number of bits in a file offset, on hosts where this is settable. */
 #undef _FILE_OFFSET_BITS
 
+/* Define for large files, on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Define to 1 if on MINIX. */
+#undef _MINIX
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+   this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Enable extensions on AIX 3, Interix.  */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
 /* Enable GNU extensions on systems that have them.  */
 #ifndef _GNU_SOURCE
 # undef _GNU_SOURCE
 #endif
+/* Enable threading extensions on Solaris.  */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# undef _POSIX_PTHREAD_SEMANTICS
+#endif
+/* Enable extensions on HP NonStop.  */
+#ifndef _TANDEM_SOURCE
+# undef _TANDEM_SOURCE
+#endif
+/* Enable general extensions on Solaris.  */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
 
-/* Define for large files, on AIX-style hosts. */
-#undef _LARGE_FILES

=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c	1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c	2008-07-19 19:56:06 +0000
@@ -0,0 +1,506 @@
+/* gfxmenu.c - 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/>.
+ */
+
+#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>
+
+typedef struct menu_item
+{
+  const char *name;
+  struct grub_video_bitmap *icon;
+  grub_menu_entry_t entry;
+}
+menu_item_t;
+
+static const int icon_width = 32;
+static const int icon_height = 32;
+static grub_video_rect_t screen;
+static grub_font_t title_font;
+static grub_font_t item_font;
+static grub_font_t status_font;
+static grub_video_color_t title_color;
+static grub_video_color_t item_color;
+static grub_video_color_t status_color;
+static grub_video_color_t status_bg_color;
+static struct grub_video_bitmap *background_image;
+static const char title_text[] = "GRUB 2 Boot Menu";
+static int num_items;
+static menu_item_t *items;
+static int selected_item_index;
+static grub_gfxmenu_box_t menu_box;
+static grub_gfxmenu_box_t selected_item_box;
+
+static grub_err_t
+menu_init (grub_menu_t menu)
+{
+  num_items = menu->size;
+  if (num_items < 1)
+    return grub_error (GRUB_ERR_MENU, "Empty menu");
+
+  items = grub_malloc (num_items * sizeof (*items));
+  if (!items)
+    return grub_errno;
+
+  int i;
+  grub_menu_entry_t cur;
+  for (i = 0, cur = menu->entry_list; i < num_items; i++, cur = cur->next)
+    {
+      items[i].name = cur->title;
+      items[i].icon = 0;  /* TODO match w/ patterns to choose an icon */
+      items[i].entry = cur;
+    }
+
+  /*
+     items[0].name = "Ubuntu 8.10";
+     grub_video_bitmap_load (&img, "/boot/images/icon-ubuntu.tga");
+     if (grub_errno == GRUB_ERR_NONE)
+     {
+     grub_video_bitmap_create_scaled (&items[0].icon, icon_width,
+     icon_height, img,
+     GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+     if (grub_errno != GRUB_ERR_NONE)
+     return;
+     grub_video_bitmap_destroy (img);
+     }
+   */
+
+  return GRUB_ERR_NONE;
+}
+
+static void
+menu_destroy (void)
+{
+  if (items)
+    {
+      int i;
+      for (i = 0; i < num_items; i++)
+        {
+          if (items[i].icon)
+            {
+              grub_video_bitmap_destroy (items[i].icon);
+              items[i].icon = 0;
+            }
+        }
+      grub_free (items);
+      items = 0;
+    }
+}
+
+static grub_err_t
+style_init (void)
+{
+  if (!(title_font = grub_font_get ("Helvetica Bold 24"))
+      || !(item_font = grub_font_get ("Helvetica Bold 12"))
+      || !(status_font = grub_font_get ("Helvetica 10")))
+    return grub_errno;
+
+  title_color = grub_video_map_rgb (0, 0, 0);
+  item_color = grub_video_map_rgb (0, 0, 0);
+  status_color = grub_video_map_rgb (255, 255, 255);
+  status_bg_color = grub_video_map_rgba (0, 0, 0, 112);
+
+  struct grub_video_bitmap *img;
+  grub_video_bitmap_load (&img, "/boot/images/bg-protomenu1.tga");
+  if (grub_errno != GRUB_ERR_NONE)
+    return grub_errno;
+
+  grub_video_bitmap_create_scaled (&background_image, screen.width,
+                                   screen.height, img,
+                                   GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+  if (grub_errno != GRUB_ERR_NONE)
+    return grub_errno;
+  grub_video_bitmap_destroy (img);
+
+  menu_box = grub_gfxmenu_create_box ("/boot/images/menubox_", ".tga");
+  if (menu_box == 0)
+    return grub_error (GRUB_ERR_MENU, "Unable to create menu box");
+
+  selected_item_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga");
+  if (selected_item_box == 0)
+    return grub_error (GRUB_ERR_MENU, "Unable to create selection box");
+
+  return GRUB_ERR_NONE;
+}
+
+static void
+style_destroy (void)
+{
+  if (menu_box)
+    {
+      menu_box->destroy(menu_box);
+      menu_box = 0;
+    }
+  if (selected_item_box)
+    {
+      selected_item_box->destroy(selected_item_box);
+      selected_item_box = 0;
+    }
+}
+
+static void
+draw_background (void)
+{
+  grub_video_blit_bitmap (background_image, GRUB_VIDEO_BLIT_REPLACE,
+                          0, 0, 0, 0,
+                          grub_video_bitmap_get_width (background_image),
+                          grub_video_bitmap_get_height (background_image));
+}
+
+static void
+draw_menu (void)
+{
+  int boxpad = 14;
+  int icon_text_space = 4;
+  int item_vspace = 16;
+
+  int ascent = grub_font_get_ascent (item_font);
+  int descent = grub_font_get_descent (item_font);
+  int item_height = 34;
+
+  grub_video_rect_t r;
+  r.width = 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 = (screen.width - r.width) / 2;
+  r.y = (screen.height - r.height) / 2;
+  menu_box->set_content_size (menu_box, r.width, r.height);
+
+  int menu_box_left_pad = menu_box->get_left_pad (menu_box);
+  int menu_box_top_pad = menu_box->get_top_pad (menu_box);
+  menu_box->draw (menu_box, r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+  int item_top = r.y + boxpad;
+  int item_left = r.x + boxpad;
+  int i;
+
+  for (i = 0; i < num_items; i++)
+    {
+      if (i == selected_item_index)
+        {
+          selected_item_box->set_content_size (selected_item_box,
+                                               r.width - 2 * boxpad,
+                                               item_height);
+          int leftpad = selected_item_box->get_left_pad (selected_item_box);
+          int toppad = selected_item_box->get_top_pad (selected_item_box);
+          selected_item_box->draw (selected_item_box,
+                                   item_left - leftpad,
+                                   item_top - toppad);
+        }
+
+      if (items[i].icon)
+        grub_video_blit_bitmap (items[i].icon, GRUB_VIDEO_BLIT_BLEND,
+                                item_left,
+                                item_top + (item_height - icon_height) / 2,
+                                0, 0, icon_width, icon_height);
+
+      grub_video_draw_string (items[i].name, item_font, item_color,
+                              item_left + icon_width + icon_text_space,
+                              (item_top + (item_height - (ascent + descent))
+                               / 2 + ascent));
+
+      item_top += item_height + item_vspace;
+    }
+}
+
+static void
+draw_title (void)
+{
+  /* Center the title. */
+  int title_width = grub_font_get_string_width (title_font, title_text);
+  int x = (screen.width - title_width) / 2;
+  int y = 40 + grub_font_get_ascent (title_font);
+  grub_video_draw_string (title_text, title_font, title_color, x, y);
+}
+
+static void
+draw_status (void)
+{
+  int descent = grub_font_get_descent (status_font);
+  int ascent = grub_font_get_ascent (status_font);
+  int vpad = 5;
+  int textheight = descent + ascent + 1;
+  int h = 2 * vpad + 2 * textheight;
+
+  grub_video_fill_rect (status_bg_color, 0, screen.height - h,
+                        screen.width, screen.height - 1);
+
+  int texty = screen.height - h + vpad + ascent;
+  grub_video_draw_string ("Select an item with the arrow keys and "
+                          "press Enter to boot.",
+                          status_font, status_color, 30, texty);
+  texty += textheight;
+  grub_video_draw_string ("Press:  'c' for command line; 't' to switch to "
+                          "non-graphical menu.",
+                          status_font, status_color, 30, texty);
+}
+
+static void
+draw_menu_screen (void)
+{
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+  draw_background ();
+  draw_menu ();
+  draw_title ();
+  draw_status ();
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+  const char *doublebuf_str = grub_env_get ("doublebuffering");
+  int doublebuf_flags =
+    (doublebuf_str && doublebuf_str[0] == 'n')
+    ? 0
+    : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+  const char *modestr = grub_env_get ("gfxmode");
+  if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480)
+      != GRUB_ERR_NONE)
+    return grub_errno;
+
+  return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+  return grub_video_restore ();
+}
+
+static int term_target_width;
+static int term_target_height;
+static struct grub_video_render_target *term_target;
+static grub_gfxmenu_box_t term_box;
+static grub_term_t term_original;
+
+static void
+repaint_terminal (int x __attribute ((unused)),
+                  int y __attribute ((unused)),
+                  int width __attribute ((unused)),
+                  int height __attribute ((unused)))
+{
+  draw_menu_screen ();
+  grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+  int termx = screen.x + screen.width / 10;
+  int termy = screen.y + screen.height / 10;
+
+  if (term_box)
+    term_box->draw (term_box, 
+                    termx - term_box->get_left_pad (term_box),
+                    termy - term_box->get_top_pad (term_box));
+
+  grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE,
+                                 termx, termy,
+                                 0, 0, term_target_width, term_target_height);
+  grub_video_swap_buffers ();
+}
+
+static void init_terminal (void)
+{
+  term_original = grub_term_get_current ();
+
+  term_target_width = screen.width * 8 / 10;
+  term_target_height = screen.height * 8 / 10;
+
+  grub_video_create_render_target (&term_target,
+                                   term_target_width, 
+                                   term_target_height,
+                                   GRUB_VIDEO_MODE_TYPE_RGB
+                                   | GRUB_VIDEO_MODE_TYPE_ALPHA);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  grub_gfxterm_init_window (term_target, 0, 0,
+                            term_target_width, term_target_height,
+                            "Fixed 10", 16);
+  if (grub_errno != GRUB_ERR_NONE)
+    return;
+
+  term_box = grub_gfxmenu_create_box ("/boot/images/select_blue_", ".tga");
+  term_box->set_content_size (term_box, term_target_width, term_target_height);
+
+  grub_gfxterm_set_repaint_callback (repaint_terminal);
+  grub_term_set_current (grub_gfxterm_get_term ());
+}
+
+static void destroy_terminal (void)
+{
+  term_box->destroy (term_box);
+  term_box = 0;
+  grub_gfxterm_destroy_window ();
+  grub_gfxterm_set_repaint_callback (0);
+  grub_video_delete_render_target (term_target);
+  grub_term_set_current (term_original);
+}
+
+static void
+run_terminal (void)
+{
+  grub_cmdline_run (1);
+}
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested __attribute__ ((unused)))
+{
+  set_graphics_mode ();
+  grub_error_push ();
+  grub_video_get_viewport ((unsigned *) &screen.x,
+                           (unsigned *) &screen.y,
+                           (unsigned *) &screen.width,
+                           (unsigned *) &screen.height);
+
+  /* Clear the screen; there may be garbage left over in video memory, and
+     loading the menu style (particularly the background) can take a while. */
+  grub_video_fill_rect (grub_video_map_rgb (100, 100, 100),
+                        screen.x, screen.y, screen.width, screen.height);
+  grub_video_swap_buffers ();
+
+  init_terminal ();
+
+  if (menu_init (menu) != GRUB_ERR_NONE)
+    goto menufail;
+
+  if (style_init () != GRUB_ERR_NONE)
+    goto stylefail;
+
+  selected_item_index = 0;
+
+  int done = 0;
+  while (!done && !grub_menu_viewer_should_return ())
+    {
+      draw_menu_screen ();
+      grub_video_swap_buffers ();
+
+      int c = GRUB_TERM_ASCII_CHAR (grub_getkey ());
+      if (c == 'j' || c == 14)
+        {
+          selected_item_index++;
+          if (selected_item_index >= num_items)
+            selected_item_index = 0;
+        }
+      else if (c == 'k' || c == 16)
+        {
+          selected_item_index--;
+          if (selected_item_index < 0)
+            selected_item_index = num_items - 1;
+        }
+      else if (c == '\r' || c == '\n' || c == 6)
+        {
+          if (selected_item_index >=0 && selected_item_index < num_items)
+            {
+              menu_item_t *item = &items[selected_item_index];
+              if (item->entry != 0)
+                {
+                  /* 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 (item->entry);
+                  if (grub_errno != GRUB_ERR_NONE)
+                    grub_wait_after_message ();
+
+                  if (set_graphics_mode () != GRUB_ERR_NONE)
+                    done = 1;
+                  else
+                    init_terminal ();
+                }
+            }
+        }
+      else if (c == 'c')
+        {
+          run_terminal ();
+          if (grub_errno != GRUB_ERR_NONE)
+            goto fail;
+        }
+      else if (c == 't')
+        {
+          /* The write hook for 'menuviewer' will cause 
+           * grub_menu_viewer_should_return to return nonzero. */
+          grub_env_set ("menuviewer", "terminal");
+        }
+      else if (nested && c == GRUB_TERM_ESC)
+        {
+          done = 1;
+        }
+    }
+
+fail:
+  style_destroy ();
+stylefail:
+  menu_destroy ();
+menufail:
+  set_text_mode ();
+  destroy_terminal ();
+
+  grub_print_error ();
+
+  return GRUB_ERR_NONE;
+}
+
+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/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/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-19 19:24:29 +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,12 @@
 /* To exit from the normal mode.  */
 extern grub_jmp_buf grub_exit_env;
 
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
 void grub_enter_normal_mode (const char *config);
 void grub_normal_execute (const char *config, int nested);
-void grub_menu_run (grub_menu_t menu, int nested);
 void grub_menu_entry_run (grub_menu_entry_t entry);
+void grub_menu_execute_entry(grub_menu_entry_t entry);
 void grub_cmdline_run (int nested);
 int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
 		      int echo_char, int readline);

=== modified file 'include/grub/video.h'
--- include/grub/video.h	2008-07-03 14:12:08 +0000
+++ include/grub/video.h	2008-07-19 19:31:46 +0000
@@ -151,6 +151,16 @@
   grub_uint8_t a; /* Reserved bits value (0-255).  */
 };
 
+/* A 2D rectangle type.  */
+struct grub_video_rect
+{
+  int x;
+  int y;
+  int width;
+  int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
 struct grub_video_adapter
 {
   /* The video adapter name.  */
@@ -307,4 +317,21 @@
 
 grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target);
 
+
+/* Defined in video/setmode.c */
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+   the form: <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-19 19:24:29 +0000
@@ -24,6 +24,7 @@
 #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;
@@ -364,7 +365,7 @@
   if (timeout > 0)
     print_timeout (timeout, offset, 0);
 
-  while (1)
+  while (!grub_menu_viewer_should_return ())
     {
       int c;
       timeout = get_timeout ();
@@ -468,6 +469,10 @@
 		}
 	      goto refresh;
 	      
+	    case 't':
+	      grub_env_set ("menuviewer", "protomenu");
+	      goto refresh;
+
 	    default:
 	      break;
 	    }
@@ -476,13 +481,14 @@
 	}
     }
 
-  /* Never reach here.  */
+  /* Exit menu without activating an item.  This occurs if the user presses
+   * 't', switching to the graphical menu viewer.  */
   return -1;
 }
 
 /* Run a menu entry.  */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
 {
   grub_script_execute (entry->commands);
   
@@ -491,8 +497,8 @@
     grub_command_execute ("boot", 0);
 }
 
-void
-grub_menu_run (grub_menu_t menu, int nested)
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
 {
   while (1)
     {
@@ -513,7 +519,7 @@
 
       grub_printf ("  Booting \'%s\'\n\n", e->title);
   
-      run_menu_entry (e);
+      grub_menu_execute_entry (e);
 
       /* Deal with a fallback entry.  */
       /* FIXME: Multiple fallback entries like GRUB Legacy.  */
@@ -526,7 +532,7 @@
 	  e = get_entry (menu, fallback_entry);
 	  grub_env_unset ("fallback");
 	  grub_printf ("\n  Falling back to \'%s\'\n\n", e->title);
-	  run_menu_entry (e);
+	  grub_menu_execute_entry (e);
 	}
 
       if (grub_errno != GRUB_ERR_NONE)
@@ -537,4 +543,12 @@
 	  grub_wait_after_message ();
 	}
     }
+
+  return GRUB_ERR_NONE;
 }
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+  .name = "terminal",
+  .show_menu = show_menu
+};

=== modified file 'term/gfxterm.c'
--- term/gfxterm.c	2008-07-03 14:28:01 +0000
+++ term/gfxterm.c	2008-07-19 19:56:06 +0000
@@ -28,12 +28,12 @@
 #include <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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
