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