# 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +enum box_pixmaps +{ + BOX_PIXMAP_NW, BOX_PIXMAP_NE, BOX_PIXMAP_SE, BOX_PIXMAP_SW, + BOX_PIXMAP_N, BOX_PIXMAP_E, BOX_PIXMAP_S, BOX_PIXMAP_W, + BOX_PIXMAP_CENTER +}; + +static const char *box_pixmap_names[] = { + /* Corners: */ + "nw", "ne", "se", "sw", + /* Sides: */ + "n", "e", "s", "w", + /* Center: */ + "c" +}; + +#define BOX_NUM_PIXMAPS (sizeof(box_pixmap_names)/sizeof(*box_pixmap_names)) + +static void +draw (grub_gfxmenu_box_t self, int x, int y) +{ + int height_n; + int height_s; + int height_e; + int height_w; + int width_n; + int width_s; + int width_e; + int width_w; + unsigned i; + + /* Don't try to draw if any pixmaps are null, except the center. */ + for (i = 0; i < BOX_NUM_PIXMAPS; i++) + { + if (i != BOX_PIXMAP_CENTER && self->scaled_pixmaps[i] == 0) + return; + } + + height_n = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_N]); + height_s = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_S]); + height_e = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_E]); + height_w = grub_video_bitmap_get_height (self->scaled_pixmaps[BOX_PIXMAP_W]); + width_n = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_N]); + width_s = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_S]); + width_e = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_E]); + width_w = grub_video_bitmap_get_width (self->scaled_pixmaps[BOX_PIXMAP_W]); + + /* Draw sides. */ + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_N], + GRUB_VIDEO_BLIT_BLEND, + x + width_w, y, 0, 0, width_n, height_n); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_S], + GRUB_VIDEO_BLIT_BLEND, + x + width_w, y + height_n + height_w, + 0, 0, width_s, height_s); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_E], + GRUB_VIDEO_BLIT_BLEND, + x + width_w + width_n, y + height_n, + 0, 0, width_e, height_e); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_W], + GRUB_VIDEO_BLIT_BLEND, + x, y + height_n, 0, 0, width_w, height_w); + + /* Draw corners. */ + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NW], + GRUB_VIDEO_BLIT_BLEND, + x, y, 0, 0, width_w, height_n); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_NE], + GRUB_VIDEO_BLIT_BLEND, + x + width_w + width_n, y, + 0, 0, width_e, height_n); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SE], + GRUB_VIDEO_BLIT_BLEND, + x + width_w + width_n, y + height_n + height_e, + 0, 0, width_e, height_s); + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_SW], + GRUB_VIDEO_BLIT_BLEND, + x, y + height_n + height_w, + 0, 0, width_w, height_s); + + /* Draw center. */ + if (self->scaled_pixmaps[BOX_PIXMAP_CENTER]) + grub_video_blit_bitmap (self->scaled_pixmaps[BOX_PIXMAP_CENTER], + GRUB_VIDEO_BLIT_BLEND, + x + width_w, y + height_n, + 0, 0, self->width, self->height); +} + +static void +scale_pixmap (grub_gfxmenu_box_t self, int i, int w, int h) +{ + struct grub_video_bitmap **scaled = &self->scaled_pixmaps[i]; + struct grub_video_bitmap *raw = self->raw_pixmaps[i]; + + if (raw == 0) + return; + + if (w == -1) + w = grub_video_bitmap_get_width (raw); + if (h == -1) + h = grub_video_bitmap_get_height (raw); + + if (*scaled == 0 + || ((int) grub_video_bitmap_get_width (*scaled) != w) + || ((int) grub_video_bitmap_get_height (*scaled) != h)) + { + if (*scaled) + { + grub_video_bitmap_destroy (*scaled); + *scaled = 0; + } + grub_video_bitmap_create_scaled (scaled, w, h, raw, + GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); + } +} + +static void +set_content_size (grub_gfxmenu_box_t self, + int width, int height) +{ + self->width = width; + self->height = height; + /* Resize sides to match the width and height. */ + /* It is assumed that the corners width/height match the adjacent sides. */ + + /* Resize N and S sides to match width. */ + scale_pixmap(self, BOX_PIXMAP_N, width, -1); + scale_pixmap(self, BOX_PIXMAP_S, width, -1); + + /* Resize E and W sides to match height. */ + scale_pixmap(self, BOX_PIXMAP_E, -1, height); + scale_pixmap(self, BOX_PIXMAP_W, -1, height); + + /* Don't scale the corners--they are assumed to match the sides. */ + scale_pixmap(self, BOX_PIXMAP_NW, -1, -1); + scale_pixmap(self, BOX_PIXMAP_SW, -1, -1); + scale_pixmap(self, BOX_PIXMAP_NE, -1, -1); + scale_pixmap(self, BOX_PIXMAP_SE, -1, -1); + + /* Scale the center area. */ + scale_pixmap(self, BOX_PIXMAP_CENTER, width, height); +} + +static int +get_left_pad (grub_gfxmenu_box_t self) +{ + return grub_video_bitmap_get_width (self->raw_pixmaps[BOX_PIXMAP_W]); +} + +static int +get_top_pad (grub_gfxmenu_box_t self) +{ + return grub_video_bitmap_get_height (self->raw_pixmaps[BOX_PIXMAP_N]); +} + +static void +destroy (grub_gfxmenu_box_t self) +{ + unsigned i; + for (i = 0; i < BOX_NUM_PIXMAPS; i++) + { + if (self->raw_pixmaps[i]) + grub_video_bitmap_destroy(self->raw_pixmaps[i]); + self->raw_pixmaps[i] = 0; + + if (self->scaled_pixmaps[i]) + grub_video_bitmap_destroy(self->scaled_pixmaps[i]); + self->scaled_pixmaps[i] = 0; + } + grub_free (self->raw_pixmaps); + self->raw_pixmaps = 0; + grub_free (self->scaled_pixmaps); + self->scaled_pixmaps = 0; + + grub_free (self); /* Free self: must be the last step! */ +} + + +grub_gfxmenu_box_t +grub_gfxmenu_create_box (const char *pixmaps_prefix, + const char *pixmaps_suffix) +{ + char path[200]; + unsigned i; + grub_gfxmenu_box_t box; + + box = (grub_gfxmenu_box_t) grub_malloc (sizeof (*box)); + if (!box) + return 0; + box->width = 0; + box->height = 0; + box->raw_pixmaps = + (struct grub_video_bitmap **) + grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *)); + box->scaled_pixmaps = + (struct grub_video_bitmap **) + grub_malloc (BOX_NUM_PIXMAPS * sizeof (struct grub_video_bitmap *)); + + for (i = 0; i < BOX_NUM_PIXMAPS; i++) + { + /* TODO XXX dynamically allocate PATH, making sure it's large enough */ + grub_sprintf (path, "%s%s%s", + pixmaps_prefix, box_pixmap_names[i], pixmaps_suffix); + grub_video_bitmap_load (&box->raw_pixmaps[i], path); + box->scaled_pixmaps[i] = 0; + } + + box->draw = draw; + box->set_content_size = set_content_size; + box->get_left_pad = get_left_pad; + box->get_top_pad = get_top_pad; + box->destroy = destroy; + + return box; +} + === added file 'include/grub/gfxterm.h' --- include/grub/gfxterm.h 1970-01-01 00:00:00 +0000 +++ include/grub/gfxterm.h 2008-07-19 19:56:06 +0000 @@ -0,0 +1,41 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_GFXTERM_HEADER +#define GRUB_GFXTERM_HEADER 1 + +#include +#include +#include +#include + +grub_err_t +grub_gfxterm_init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width); + +void grub_gfxterm_destroy_window (void); + +grub_term_t grub_gfxterm_get_term (void); + +typedef void (*grub_gfxterm_repaint_callback_t)(int x, int y, + int width, int height); + +void grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func); + +#endif /* ! GRUB_GFXTERM_HEADER */ === added file 'include/grub/gfxwidgets.h' --- include/grub/gfxwidgets.h 1970-01-01 00:00:00 +0000 +++ include/grub/gfxwidgets.h 2008-07-19 18:25:18 +0000 @@ -0,0 +1,47 @@ +/* gfxwidgets.h - Widgets for the graphical menu (gfxmenu). */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_GFXWIDGETS_HEADER +#define GRUB_GFXWIDGETS_HEADER 1 + +#include + +typedef struct grub_gfxmenu_box *grub_gfxmenu_box_t; + +struct grub_gfxmenu_box +{ + /* The size of the content. */ + int width; + int height; + + struct grub_video_bitmap **raw_pixmaps; + struct grub_video_bitmap **scaled_pixmaps; + + void (*draw) (grub_gfxmenu_box_t self, int x, int y); + void (*set_content_size) (grub_gfxmenu_box_t self, + int width, int height); + int (*get_left_pad) (grub_gfxmenu_box_t self); + int (*get_top_pad) (grub_gfxmenu_box_t self); + void (*destroy) (grub_gfxmenu_box_t self); +}; + +grub_gfxmenu_box_t grub_gfxmenu_create_box (const char *pixmaps_prefix, + const char *pixmaps_suffix); + +#endif /* ! GRUB_GFXWIDGETS_HEADER */ === added file 'include/grub/menu.h' --- include/grub/menu.h 1970-01-01 00:00:00 +0000 +++ include/grub/menu.h 2008-07-19 18:40:28 +0000 @@ -0,0 +1,51 @@ +/* menu.h - Menu and menu entry model declarations. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_MENU_HEADER +#define GRUB_MENU_HEADER 1 + +/* The menu entry. */ +struct grub_menu_entry +{ + /* The title name. */ + const char *title; + + /* The commands associated with this menu entry. */ + struct grub_script *commands; + + /* The sourcecode of the menu entry, used by the editor. */ + const char *sourcecode; + + /* The next element. */ + struct grub_menu_entry *next; +}; +typedef struct grub_menu_entry *grub_menu_entry_t; + +/* The menu. */ +struct grub_menu +{ + /* The size of a menu. */ + int size; + + /* The list of menu entries. */ + grub_menu_entry_t entry_list; +}; +typedef struct grub_menu *grub_menu_t; + +#endif /* GRUB_MENU_HEADER */ === added file 'include/grub/menu_viewer.h' --- include/grub/menu_viewer.h 1970-01-01 00:00:00 +0000 +++ include/grub/menu_viewer.h 2008-07-19 19:07:47 +0000 @@ -0,0 +1,48 @@ +/* menu_viewer.h - Interface to menu viewer implementations. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_MENU_VIEWER_HEADER +#define GRUB_MENU_VIEWER_HEADER 1 + +#include +#include +#include +#include + +struct grub_menu_viewer +{ + /* The menu viewer name. */ + const char *name; + + grub_err_t (*show_menu) (grub_menu_t menu, int nested); + + struct grub_menu_viewer *next; +}; +typedef struct grub_menu_viewer *grub_menu_viewer_t; + +void EXPORT_FUNC(grub_menu_viewer_init) (void); + +void EXPORT_FUNC(grub_menu_viewer_register) (grub_menu_viewer_t viewer); + +grub_err_t EXPORT_FUNC(grub_menu_viewer_show_menu) (grub_menu_t menu, int nested); + +/* Return nonzero iff the menu viewer should clean up and return ASAP. */ +int EXPORT_FUNC(grub_menu_viewer_should_return) (void); + +#endif /* GRUB_MENU_VIEWER_HEADER */ === modified file 'include/grub/normal.h' --- include/grub/normal.h 2008-03-26 12:01:02 +0000 +++ include/grub/normal.h 2008-07-19 19:24:29 +0000 @@ -25,6 +25,7 @@ #include #include #include +#include /* The maximum size of a command-line. */ #define GRUB_MAX_CMDLINE 1600 @@ -84,34 +85,6 @@ }; typedef struct grub_command *grub_command_t; -/* The menu entry. */ -struct grub_menu_entry -{ - /* The title name. */ - const char *title; - - /* The commands associated with this menu entry. */ - struct grub_script *commands; - - /* The sourcecode of the menu entry, used by the editor. */ - const char *sourcecode; - - /* The next element. */ - struct grub_menu_entry *next; -}; -typedef struct grub_menu_entry *grub_menu_entry_t; - -/* The menu. */ -struct grub_menu -{ - /* The size of a menu. */ - int size; - - /* The list of menu entries. */ - grub_menu_entry_t entry_list; -}; -typedef struct grub_menu *grub_menu_t; - /* This is used to store the names of filesystem modules for auto-loading. */ struct grub_fs_module_list { @@ -123,10 +96,12 @@ /* To exit from the normal mode. */ extern grub_jmp_buf grub_exit_env; +extern struct grub_menu_viewer grub_normal_terminal_menu_viewer; + void grub_enter_normal_mode (const char *config); void grub_normal_execute (const char *config, int nested); -void grub_menu_run (grub_menu_t menu, int nested); void grub_menu_entry_run (grub_menu_entry_t entry); +void grub_menu_execute_entry(grub_menu_entry_t entry); void grub_cmdline_run (int nested); int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len, int echo_char, int readline); === modified file 'include/grub/video.h' --- include/grub/video.h 2008-07-03 14:12:08 +0000 +++ include/grub/video.h 2008-07-19 19:31:46 +0000 @@ -151,6 +151,16 @@ grub_uint8_t a; /* Reserved bits value (0-255). */ }; +/* A 2D rectangle type. */ +struct grub_video_rect +{ + int x; + int y; + int width; + int height; +}; +typedef struct grub_video_rect grub_video_rect_t; + struct grub_video_adapter { /* The video adapter name. */ @@ -307,4 +317,21 @@ grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target); + +/* Defined in video/setmode.c */ + +/* Set the video mode based on the preferred modes specified in MODE_LIST in + the form: x[x][;...] + + For example: 640x480;800x600x8;400x300x32 + + If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and + DEFAULT_HEIGHT are used to set the mode. The MODE_FLAGS argument determines + the video mode flags such as double buffering that are used. */ + +grub_err_t +grub_video_setup_preferred_mode (const char *mode_list, int mode_flags, + int default_width, int default_height); + + #endif /* ! GRUB_VIDEO_HEADER */ === added file 'kern/menu_viewer.c' --- kern/menu_viewer.c 1970-01-01 00:00:00 +0000 +++ kern/menu_viewer.c 2008-07-19 19:07:47 +0000 @@ -0,0 +1,99 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2002,2003,2005,2007 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include +#include + +/* The list of menu viewers. */ +static grub_menu_viewer_t menu_viewer_list; + +static int should_return; +static int menu_viewer_changed; + +void +grub_menu_viewer_register (grub_menu_viewer_t viewer) +{ + viewer->next = menu_viewer_list; + menu_viewer_list = viewer; +} + +static grub_menu_viewer_t get_current_menu_viewer (void) +{ + const char *selected_name = grub_env_get ("menuviewer"); + + /* If none selected, pick the last registered one. */ + if (selected_name == 0) + return menu_viewer_list; + + grub_menu_viewer_t cur; + for (cur = menu_viewer_list; cur; cur = cur->next) + { + if (grub_strcmp (cur->name, selected_name) == 0) + return cur; + } + + /* Fall back to the first entry (or null). */ + return menu_viewer_list; +} + +grub_err_t +grub_menu_viewer_show_menu (grub_menu_t menu, int nested) +{ + grub_err_t err; + int repeat = 0; + do + { + repeat = 0; + menu_viewer_changed = 0; + grub_menu_viewer_t cur = get_current_menu_viewer (); + if (!cur) + return grub_error (GRUB_ERR_BAD_ARGUMENT, "No menu viewer available."); + + should_return = 0; + err = cur->show_menu (menu, nested); + if (menu_viewer_changed) + repeat = 1; + } + while (repeat); + return err; +} + +int +grub_menu_viewer_should_return (void) +{ + return should_return; +} + +static char * +menuviewer_write_hook (struct grub_env_var *var __attribute__ ((unused)), + const char *val) +{ + menu_viewer_changed = 1; + should_return = 1; + return grub_strdup (val); +} + +void +grub_menu_viewer_init (void) +{ + grub_register_variable_hook ("menuviewer", 0, menuviewer_write_hook); +} + === modified file 'normal/main.c' --- normal/main.c 2008-02-02 16:48:52 +0000 +++ normal/main.c 2008-07-19 19:07:47 +0000 @@ -28,6 +28,7 @@ #include #include #include +#include grub_jmp_buf grub_exit_env; @@ -476,7 +477,7 @@ if (menu && menu->size) { - grub_menu_run (menu, nested); + grub_menu_viewer_show_menu (menu, nested); if (nested) free_menu (menu); } @@ -519,6 +520,8 @@ if (mod) grub_dl_ref (mod); + grub_menu_viewer_register (&grub_normal_terminal_menu_viewer); + grub_set_history (GRUB_DEFAULT_HISTORY_SIZE); /* Register a command "normal" for the rescue mode. */ @@ -535,6 +538,8 @@ /* This registers some built-in commands. */ grub_command_init (); + + grub_menu_viewer_init (); } GRUB_MOD_FINI(normal) === modified file 'normal/menu.c' --- normal/menu.c 2008-02-09 11:00:19 +0000 +++ normal/menu.c 2008-07-19 19:24:29 +0000 @@ -24,6 +24,7 @@ #include #include #include +#include static grub_uint8_t grub_color_menu_normal; static grub_uint8_t grub_color_menu_highlight; @@ -364,7 +365,7 @@ if (timeout > 0) print_timeout (timeout, offset, 0); - while (1) + while (!grub_menu_viewer_should_return ()) { int c; timeout = get_timeout (); @@ -468,6 +469,10 @@ } goto refresh; + case 't': + grub_env_set ("menuviewer", "protomenu"); + goto refresh; + default: break; } @@ -476,13 +481,14 @@ } } - /* Never reach here. */ + /* Exit menu without activating an item. This occurs if the user presses + * 't', switching to the graphical menu viewer. */ return -1; } /* Run a menu entry. */ -static void -run_menu_entry (grub_menu_entry_t entry) +void +grub_menu_execute_entry(grub_menu_entry_t entry) { grub_script_execute (entry->commands); @@ -491,8 +497,8 @@ grub_command_execute ("boot", 0); } -void -grub_menu_run (grub_menu_t menu, int nested) +static grub_err_t +show_menu (grub_menu_t menu, int nested) { while (1) { @@ -513,7 +519,7 @@ grub_printf (" Booting \'%s\'\n\n", e->title); - run_menu_entry (e); + grub_menu_execute_entry (e); /* Deal with a fallback entry. */ /* FIXME: Multiple fallback entries like GRUB Legacy. */ @@ -526,7 +532,7 @@ e = get_entry (menu, fallback_entry); grub_env_unset ("fallback"); grub_printf ("\n Falling back to \'%s\'\n\n", e->title); - run_menu_entry (e); + grub_menu_execute_entry (e); } if (grub_errno != GRUB_ERR_NONE) @@ -537,4 +543,12 @@ grub_wait_after_message (); } } + + return GRUB_ERR_NONE; } + +struct grub_menu_viewer grub_normal_terminal_menu_viewer = +{ + .name = "terminal", + .show_menu = show_menu +}; === modified file 'term/gfxterm.c' --- term/gfxterm.c 2008-07-03 14:28:01 +0000 +++ term/gfxterm.c 2008-07-19 19:56:06 +0000 @@ -28,12 +28,12 @@ #include #include #include +#include #include #include #define DEFAULT_VIDEO_WIDTH 640 #define DEFAULT_VIDEO_HEIGHT 480 -#define DEFAULT_VIDEO_FLAGS 0 #define DEFAULT_BORDER_WIDTH 10 @@ -108,10 +108,20 @@ struct grub_colored_char *text_buffer; }; +static int refcount; +static struct grub_video_render_target *render_target; +static grub_video_rect_t window; static struct grub_virtual_screen virtual_screen; +static grub_gfxterm_repaint_callback_t repaint_callback; + +static grub_err_t init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width); + +static void destroy_window (void); + static grub_dl_t my_mod; -static struct grub_video_mode_info mode_info; static struct grub_video_render_target *text_layer; @@ -235,287 +245,129 @@ } static grub_err_t +init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width) +{ + /* Clean up any prior instance. */ + destroy_window (); + + /* Create virtual screen. */ + if (grub_virtual_screen_setup (border_width, border_width, + width - 2 * border_width, + height - 2 * border_width, + font_name) + != GRUB_ERR_NONE) + { + return grub_errno; + } + + /* Set the render target. */ + render_target = target; + + /* Set window bounds. */ + window.x = x; + window.y = y; + window.width = width; + window.height = height; + + /* Mark whole window as dirty. */ + dirty_region_reset (); + dirty_region_add (0, 0, width, height); + + return (grub_errno = GRUB_ERR_NONE); +} + +grub_err_t +grub_gfxterm_init_window (struct grub_video_render_target *target, + int x, int y, int width, int height, + const char *font_name, int border_width) +{ + grub_errno = GRUB_ERR_NONE; + if (refcount++ == 0) + init_window (target, x, y, width, height, font_name, border_width); + return grub_errno; +} + +static grub_err_t grub_gfxterm_init (void) { - char *font_name; - char *modevar; - int width = DEFAULT_VIDEO_WIDTH; - int height = DEFAULT_VIDEO_HEIGHT; - int depth = -1; - int flags = DEFAULT_VIDEO_FLAGS; - grub_video_color_t color; - - /* Select the font to use. */ - font_name = grub_env_get ("gfxterm_font"); - if (!font_name) - font_name = ""; /* Allow fallback to any font. */ + /* If gfxterm has already been initialized by calling the init_window + function, then leave it alone when it is set as the current terminal. */ + if (refcount++ != 0) + return GRUB_ERR_NONE; /* Parse gfxmode environment variable if set. */ - modevar = grub_env_get ("gfxmode"); - if (modevar) - { - char *tmp; - char *next_mode; - char *current_mode; - char *param; - char *value; - int mode_found = 0; - - /* Take copy of env.var. as we don't want to modify that. */ - tmp = grub_strdup (modevar); - modevar = tmp; - - if (grub_errno != GRUB_ERR_NONE) - return grub_errno; - - /* Initialize next mode. */ - next_mode = modevar; - - /* Loop until all modes has been tested out. */ - while (next_mode != NULL) - { - /* Use last next_mode as current mode. */ - tmp = next_mode; - - /* Reset video mode settings. */ - width = DEFAULT_VIDEO_WIDTH; - height = DEFAULT_VIDEO_HEIGHT; - depth = -1; - flags = DEFAULT_VIDEO_FLAGS; - - /* Save position of next mode and separate modes. */ - next_mode = grub_strchr(next_mode, ';'); - if (next_mode) - { - *next_mode = 0; - next_mode++; - } - - /* Skip whitespace. */ - while (grub_isspace (*tmp)) - tmp++; - - /* Initialize token holders. */ - current_mode = tmp; - param = tmp; - value = NULL; - - /* Parse x[x]*/ - - /* Find width value. */ - value = param; - param = grub_strchr(param, 'x'); - if (param == NULL) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - - *param = 0; - param++; - - width = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - - /* Find height value. */ - value = param; - param = grub_strchr(param, 'x'); - if (param == NULL) - { - height = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - } - else - { - /* We have optional color depth value. */ - *param = 0; - param++; - - height = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - - /* Convert color depth value. */ - value = param; - depth = grub_strtoul (value, 0, 0); - if (grub_errno != GRUB_ERR_NONE) - { - grub_err_t rc; - - /* First setup error message. */ - rc = grub_error (GRUB_ERR_BAD_ARGUMENT, - "Invalid mode: %s\n", - current_mode); - - /* Free memory before returning. */ - grub_free (modevar); - - return rc; - } - } - - /* Try out video mode. */ - - /* If we have 8 or less bits, then assume that it is indexed color mode. */ - if ((depth <= 8) && (depth != -1)) - flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR; - - /* We have more than 8 bits, then assume that it is RGB color mode. */ - if (depth > 8) - flags |= GRUB_VIDEO_MODE_TYPE_RGB; - - /* If user requested specific depth, forward that information to driver. */ - if (depth != -1) - flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS) - & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK; - - /* Try to initialize requested mode. Ignore any errors. */ - grub_error_push (); - if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE) - { - grub_error_pop (); - continue; - } - - /* Figure out what mode we ended up. */ - if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) - { - /* Couldn't get video mode info, restore old mode and continue to next one. */ - grub_error_pop (); - - grub_video_restore (); - continue; - } - - /* Restore state of error stack. */ - grub_error_pop (); - - /* Mode found! Exit loop. */ - mode_found = 1; - break; - } - - /* Free memory. */ - grub_free (modevar); - - if (!mode_found) - return grub_error (GRUB_ERR_BAD_ARGUMENT, - "No suitable mode found."); - } - else - { - /* No gfxmode variable set, use defaults. */ - - /* If we have 8 or less bits, then assume that it is indexed color mode. */ - if ((depth <= 8) && (depth != -1)) - flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR; - - /* We have more than 8 bits, then assume that it is RGB color mode. */ - if (depth > 8) - flags |= GRUB_VIDEO_MODE_TYPE_RGB; - - /* If user requested specific depth, forward that information to driver. */ - if (depth != -1) - flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS) - & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK; - - /* Initialize user requested mode. */ - if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE) - return grub_errno; - - /* Figure out what mode we ended up. */ - if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) - { - grub_video_restore (); - return grub_errno; - } + const char *modevar = grub_env_get ("gfxmode"); + if (grub_video_setup_preferred_mode (modevar, 0, + DEFAULT_VIDEO_WIDTH, + DEFAULT_VIDEO_HEIGHT) + != GRUB_ERR_NONE) + return grub_errno; + + /* Figure out what mode we ended up. */ + struct grub_video_mode_info mode_info; + if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) + { + grub_video_restore (); + return grub_errno; } /* Make sure screen is black. */ - color = grub_video_map_rgb (0, 0, 0); - grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height); + grub_video_fill_rect (grub_video_map_rgb (0, 0, 0), + 0, 0, mode_info.width, mode_info.height); bitmap = 0; + /* Select the font to use. */ + char *font_name = grub_env_get ("gfxterm_font"); + if (!font_name) + font_name = ""; /* Allow fallback to any font. */ + /* Leave borders for virtual screen. */ - width = mode_info.width - (2 * DEFAULT_BORDER_WIDTH); - height = mode_info.height - (2 * DEFAULT_BORDER_WIDTH); - - /* Create virtual screen. */ - if (grub_virtual_screen_setup (DEFAULT_BORDER_WIDTH, DEFAULT_BORDER_WIDTH, - width, height, font_name) != GRUB_ERR_NONE) + if (init_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY, + 0, 0, mode_info.width, mode_info.height, + font_name, + DEFAULT_BORDER_WIDTH) != GRUB_ERR_NONE) { grub_video_restore (); return grub_errno; } - /* Mark whole screen as dirty. */ - dirty_region_reset (); - dirty_region_add (0, 0, mode_info.width, mode_info.height); - return (grub_errno = GRUB_ERR_NONE); } +static void +destroy_window (void) +{ + if (bitmap) + { + grub_video_bitmap_destroy (bitmap); + bitmap = 0; + } + + repaint_callback = 0; + grub_virtual_screen_free (); +} + +void +grub_gfxterm_destroy_window (void) +{ + if (--refcount == 0) + destroy_window (); +} + static grub_err_t grub_gfxterm_fini (void) { - if (bitmap) + /* Don't destroy an explicitly initialized terminal instance when it is + unset as the current terminal. */ + if (--refcount == 0) { - grub_video_bitmap_destroy (bitmap); - bitmap = 0; + destroy_window (); + grub_video_restore (); } - grub_virtual_screen_free (); - - grub_video_restore (); - - return GRUB_ERR_NONE; + return (grub_errno = GRUB_ERR_NONE); } static void @@ -523,9 +375,15 @@ unsigned int width, unsigned int height) { grub_video_color_t color; - - grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); - + grub_video_rect_t saved_view; + + grub_video_set_active_render_target (render_target); + /* Save viewport and set it to our window. */ + grub_video_get_viewport ((unsigned *) &saved_view.x, + (unsigned *) &saved_view.y, + (unsigned *) &saved_view.width, + (unsigned *) &saved_view.height); + grub_video_set_viewport (window.x, window.y, window.width, window.height); if (bitmap) { @@ -592,6 +450,14 @@ y - virtual_screen.offset_y, width, height); } + + /* Restore saved viewport. */ + grub_video_set_viewport (saved_view.x, saved_view.y, + saved_view.width, saved_view.height); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + + if (repaint_callback) + repaint_callback (x, y, width, height); } static void @@ -786,7 +652,16 @@ dirty_region_add_virtualscreen (); } else - { + { + grub_video_rect_t saved_view; + grub_video_set_active_render_target (render_target); + /* Save viewport and set it to our window. */ + grub_video_get_viewport ((unsigned *) &saved_view.x, + (unsigned *) &saved_view.y, + (unsigned *) &saved_view.width, + (unsigned *) &saved_view.height); + grub_video_set_viewport (window.x, window.y, window.width, window.height); + /* Clear new border area. */ grub_video_fill_rect (color, virtual_screen.offset_x, virtual_screen.offset_y, @@ -795,10 +670,18 @@ /* Scroll physical screen. */ grub_video_scroll (color, 0, -virtual_screen.char_height); + /* Restore saved viewport. */ + grub_video_set_viewport (saved_view.x, saved_view.y, + saved_view.width, saved_view.height); + grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); + /* Draw cursor if visible. */ if (virtual_screen.cursor_state) write_cursor (); } + + if (repaint_callback) + repaint_callback (window.x, window.y, window.width, window.height); } static void @@ -898,7 +781,7 @@ } static grub_ssize_t -grub_gfxterm_getcharwidth (grub_uint32_t c) +grub_gfxterm_getcharwidth (grub_uint32_t c __attribute__((unused))) { #if 0 struct grub_font_glyph *glyph; @@ -970,7 +853,8 @@ /* Clear text layer. */ grub_video_set_active_render_target (text_layer); color = virtual_screen.bg_color; - grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height); + grub_video_fill_rect (color, 0, 0, + virtual_screen.width, virtual_screen.height); grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY); /* Mark virtual screen to be redrawn. */ @@ -1039,6 +923,11 @@ dirty_region_redraw (); } +void +grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func) +{ + repaint_callback = func; +} /* Option array indices. */ #define BACKGROUND_CMD_ARGINDEX_MODE 0 @@ -1066,7 +955,7 @@ /* Mark whole screen as dirty. */ dirty_region_reset (); - dirty_region_add (0, 0, mode_info.width, mode_info.height); + dirty_region_add (0, 0, window.width, window.height); } /* If filename was provided, try to load that. */ @@ -1082,13 +971,13 @@ || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg, "stretch") == 0) { - if (mode_info.width != grub_video_bitmap_get_width (bitmap) - || mode_info.height != grub_video_bitmap_get_height (bitmap)) + if (window.width != (int) grub_video_bitmap_get_width (bitmap) + || window.height != (int) grub_video_bitmap_get_height (bitmap)) { struct grub_video_bitmap *scaled_bitmap; grub_video_bitmap_create_scaled (&scaled_bitmap, - mode_info.width, - mode_info.height, + window.width, + window.height, bitmap, GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST); if (grub_errno == GRUB_ERR_NONE) @@ -1110,7 +999,7 @@ /* Mark whole screen as dirty. */ dirty_region_reset (); - dirty_region_add (0, 0, mode_info.width, mode_info.height); + dirty_region_add (0, 0, window.width, window.height); } } @@ -1141,9 +1030,16 @@ .next = 0 }; +grub_term_t +grub_gfxterm_get_term (void) +{ + return &grub_video_term; +} + GRUB_MOD_INIT(term_gfxterm) { my_mod = mod; + refcount = 0; grub_term_register (&grub_video_term); grub_register_command ("background_image", === modified file 'video/i386/pc/vbe.c' --- video/i386/pc/vbe.c 2008-07-09 20:59:34 +0000 +++ video/i386/pc/vbe.c 2008-07-19 19:42:37 +0000 @@ -652,6 +652,8 @@ if (doublebuf_pageflipping_commit () != GRUB_ERR_NONE) return 1; /* Unable to set the display start. */ + framebuffer.render_target.mode_info.mode_type + |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; return 0; } @@ -690,6 +692,8 @@ doublebuf_state.update_screen = doublebuf_blit_update_screen; doublebuf_state.destroy = doublebuf_blit_destroy; + framebuffer.render_target.mode_info.mode_type + |= GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; return 0; } @@ -713,6 +717,9 @@ framebuffer.render_target.data = framebuffer.ptr; doublebuf_state.update_screen = doublebuf_null_update_screen; doublebuf_state.destroy = doublebuf_null_destroy; + + framebuffer.render_target.mode_info.mode_type + &= ~GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED; return 0; } === added file 'video/setmode.c' --- video/setmode.c 1970-01-01 00:00:00 +0000 +++ video/setmode.c 2008-07-19 19:31:46 +0000 @@ -0,0 +1,249 @@ +/* video/setmode.c - Smart video mode selection based on preferences. */ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#include +#include +#include +#include + +/* Set the video mode based on the preferred modes specified in MODE_LIST in + the form: x[x][;...] + + For example: 640x480;800x600x8;400x300x32 + + If MODE_LIST is null, or no modes in it are usable, then DEFAULT_WIDTH and + DEFAULT_HEIGHT are used to set the mode. The MODE_FLAGS argument determines + the video mode flags such as double buffering that are used. */ + +grub_err_t +grub_video_setup_preferred_mode (const char *mode_list, int mode_flags, + int default_width, int default_height) +{ + int mode_found = 0; + + if (mode_list != NULL) + { + /* Take copy of mode_list as we don't want tat. */ + char *const modes_copy = grub_strdup (mode_list); + if (modes_copy == NULL) + return grub_errno; + + /* Initialize next mode. */ + char *next_mode = modes_copy; + + /* Loop until all modes has been tested out. */ + while ((next_mode != NULL) && !mode_found) + { + /* Use last next_mode as current mode. */ + char *tmp = next_mode; + + int width = -1; + int height = -1; + int depth = -1; + + /* Save position of next mode and separate modes. */ + next_mode = grub_strchr(next_mode, ';'); + if (next_mode) + { + *next_mode = 0; + next_mode++; + } + + /* Skip whitespace. */ + while (grub_isspace (*tmp)) + tmp++; + + /* Initialize token holders. */ + char *current_mode = tmp; + char *param = tmp; + char *value = NULL; + + /* Parse x[x]*/ + + /* Find width value. */ + value = param; + param = grub_strchr(param, 'x'); + if (param == NULL) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + + *param = 0; + param++; + + width = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + + /* Find height value. */ + value = param; + param = grub_strchr(param, 'x'); + if (param == NULL) + { + height = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + } + else + { + /* We have optional color depth value. */ + *param = 0; + param++; + + height = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + + /* Convert color depth value. */ + value = param; + depth = grub_strtoul (value, 0, 0); + if (grub_errno != GRUB_ERR_NONE) + { + grub_err_t rc; + + /* First setup error message. */ + rc = grub_error (GRUB_ERR_BAD_ARGUMENT, + "Invalid mode: %s\n", + current_mode); + + /* Free memory before returning. */ + grub_free (modes_copy); + + return rc; + } + } + + /* Try out video mode. */ + + int flags = mode_flags; + /* If we have <= 8 bits, assume it is an indexed color mode. */ + if ((depth <= 8) && (depth != -1)) + flags |= GRUB_VIDEO_MODE_TYPE_INDEX_COLOR; + + /* We have > 8 bits; assume that it is RGB color mode. */ + if (depth > 8) + flags |= GRUB_VIDEO_MODE_TYPE_RGB; + + /* If user requested specific depth, pass the request to driver. */ + if (depth != -1) + flags |= (depth << GRUB_VIDEO_MODE_TYPE_DEPTH_POS) + & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK; + + /* Try to initialize requested mode. Ignore any errors. */ + grub_error_push (); + if (grub_video_setup (width, height, flags) != GRUB_ERR_NONE) + { + grub_error_pop (); + continue; + } + + /* Figure out what mode we ended up. */ + struct grub_video_mode_info mode_info; + if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) + { + /* Couldn't get video mode info, restore old mode + and continue to next one. */ + grub_error_pop (); + + grub_video_restore (); + continue; + } + + /* Restore state of error stack. */ + grub_error_pop (); + + /* Mode found! Exit loop. */ + mode_found = 1; + } + + /* Free memory. */ + grub_free (modes_copy); + } + + if (!mode_found) + { + /* No gfxmode variable set, or no listed mode was supported. + Use the caller-specified defaults. */ + int flags = mode_flags | GRUB_VIDEO_MODE_TYPE_RGB; + + /* Initialize user requested mode. */ + if (grub_video_setup (default_width, default_height, flags) + != GRUB_ERR_NONE) + return grub_errno; + + /* Figure out what mode we ended up. */ + struct grub_video_mode_info mode_info; + if (grub_video_get_info (&mode_info) != GRUB_ERR_NONE) + grub_video_restore (); + else + mode_found = 1; + } + + if (!mode_found) + return grub_error (GRUB_ERR_BAD_ARGUMENT, + "No suitable mode found."); + + return (grub_errno = GRUB_ERR_NONE); +} # Begin bundle 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