# Bazaar merge directive format 2 (Bazaar 0.90)
# revision_id: colin@gibibit.com-20080728020230-a79jyq7pkv9jnco1
# target_branch: http://grub.gibibit.com/bzr/trunk-clean
# testament_sha1: 990f6d3418364109d162680f19eb5aa7adddfadf
# timestamp: 2008-07-27 19:16:45 -0700
# source_branch: http://grub.gibibit.com/bzr/trunk-clean
# base_revision_id: colin@gibibit.com-20080728020030-u9gr2usazh2r2pcw
#
# Begin patch
=== modified file 'commands/videotest.c'
--- commands/videotest.c 2008-07-09 16:14:04 +0000
+++ commands/videotest.c 2008-07-19 18:25:18 +0000
@@ -49,19 +49,6 @@
int double_buffering;
};
-/* A 2D rectangle type.
- * This could be worth integrating into the video API if it proves useful.*/
-struct grub_video_rect
-{
- /* These are signed because if there are unsigned it causes Bad Things
- * to happen when arithmetic and comparisions involving signed types is
- * done. Important signed types include offsets from absolute locations. */
- int x;
- int y;
- int width;
- int height;
-};
-typedef struct grub_video_rect grub_video_rect_t;
static void
basic_video_test (struct videotest_options *vt_opts)
=== modified file 'conf/common.rmk'
--- conf/common.rmk 2008-07-28 02:00:30 +0000
+++ conf/common.rmk 2008-07-28 02:02:30 +0000
@@ -274,6 +274,7 @@
cmp.mod cat.mod help.mod font.mod search.mod \
loopback.mod fs_uuid.mod configfile.mod echo.mod \
terminfo.mod test.mod blocklist.mod hexdump.mod \
+ gfxmenu.mod \
read.mod sleep.mod loadenv.mod crc.mod
# For hello.mod.
@@ -281,6 +282,16 @@
hello_mod_CFLAGS = $(COMMON_CFLAGS)
hello_mod_LDFLAGS = $(COMMON_LDFLAGS)
+# For gfxmenu.mod.
+gfxmenu_mod_SOURCES = \
+ gfxmenu/gfxmenu.c \
+ gfxmenu/model.c \
+ gfxmenu/view.c \
+ gfxmenu/widget-box.c \
+ gfxmenu/stringutil.c
+gfxmenu_mod_CFLAGS = $(COMMON_CFLAGS)
+gfxmenu_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
# For boot.mod.
boot_mod_SOURCES = commands/boot.c
boot_mod_CFLAGS = $(COMMON_CFLAGS)
=== modified file 'conf/i386-pc.rmk'
--- conf/i386-pc.rmk 2008-07-28 02:00:30 +0000
+++ conf/i386-pc.rmk 2008-07-28 02:02:30 +0000
@@ -46,11 +46,13 @@
kern/i386/tsc.c \
kern/generic/millisleep.c \
kern/env.c \
+ kern/menu_viewer.c \
term/i386/pc/console.c \
symlist.c
kernel_img_HEADERS = arg.h boot.h cache.h device.h disk.h dl.h elf.h elfload.h \
env.h err.h file.h fs.h kernel.h loader.h misc.h mm.h net.h parser.h \
partition.h pc_partition.h rescue.h symbol.h term.h time.h types.h \
+ menu_viewer.h \
machine/biosdisk.h machine/boot.h machine/console.h machine/init.h \
machine/memory.h machine/loader.h machine/vga.h machine/vbe.h machine/kernel.h
kernel_img_CFLAGS = $(COMMON_CFLAGS)
@@ -248,7 +250,7 @@
play_mod_LDFLAGS = $(COMMON_LDFLAGS)
# For video.mod.
-video_mod_SOURCES = video/video.c
+video_mod_SOURCES = video/video.c video/setmode.c
video_mod_CFLAGS = $(COMMON_CFLAGS)
video_mod_LDFLAGS = $(COMMON_LDFLAGS)
=== modified file 'config.h.in'
--- config.h.in 2008-07-13 00:55:15 +0000
+++ config.h.in 2008-07-19 21:46:40 +0000
@@ -113,10 +113,37 @@
/* Number of bits in a file offset, on hosts where this is settable. */
#undef _FILE_OFFSET_BITS
+/* Define for large files, on AIX-style hosts. */
+#undef _LARGE_FILES
+
+/* Define to 1 if on MINIX. */
+#undef _MINIX
+
+/* Define to 2 if the system does not provide POSIX.1 features except with
+ this defined. */
+#undef _POSIX_1_SOURCE
+
+/* Define to 1 if you need to in order for `stat' and other things to work. */
+#undef _POSIX_SOURCE
+
+/* Enable extensions on AIX 3, Interix. */
+#ifndef _ALL_SOURCE
+# undef _ALL_SOURCE
+#endif
/* Enable GNU extensions on systems that have them. */
#ifndef _GNU_SOURCE
# undef _GNU_SOURCE
#endif
+/* Enable threading extensions on Solaris. */
+#ifndef _POSIX_PTHREAD_SEMANTICS
+# undef _POSIX_PTHREAD_SEMANTICS
+#endif
+/* Enable extensions on HP NonStop. */
+#ifndef _TANDEM_SOURCE
+# undef _TANDEM_SOURCE
+#endif
+/* Enable general extensions on Solaris. */
+#ifndef __EXTENSIONS__
+# undef __EXTENSIONS__
+#endif
-/* Define for large files, on AIX-style hosts. */
-#undef _LARGE_FILES
=== added directory 'gfxmenu'
=== added file 'gfxmenu/gfxmenu.c'
--- gfxmenu/gfxmenu.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/gfxmenu.c 2008-07-27 00:06:20 +0000
@@ -0,0 +1,221 @@
+/* gfxmenu.c - Graphical menu interface controller. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static void switch_to_text_menu (void)
+{
+ grub_env_set ("menuviewer", "terminal");
+}
+
+static void
+process_key_press (int c,
+ grub_gfxmenu_model_t model,
+ grub_gfxmenu_view_t view,
+ int nested,
+ int *should_exit)
+{
+ /* When a key is pressed, stop the timeout. */
+ grub_gfxmenu_model_clear_timeout (model);
+
+ if (c == 'j' || c == GRUB_TERM_DOWN)
+ {
+ int i = grub_gfxmenu_model_get_selected_index (model);
+ int num_items = grub_gfxmenu_model_get_num_entries (model);
+ if (i < num_items - 1)
+ {
+ i++;
+ grub_gfxmenu_model_set_selected_index (model, i);
+ }
+ }
+ else if (c == 'k' || c == GRUB_TERM_UP)
+ {
+ int i = grub_gfxmenu_model_get_selected_index (model);
+ if (i > 0)
+ {
+ i--;
+ grub_gfxmenu_model_set_selected_index (model, i);
+ }
+ }
+ else if (c == '\r' || c == '\n' || c == GRUB_TERM_RIGHT)
+ {
+ int selected = grub_gfxmenu_model_get_selected_index (model);
+ int num_entries = grub_gfxmenu_model_get_num_entries (model);
+ if (selected >= 0 && selected < num_entries)
+ {
+ grub_menu_entry_t entry =
+ grub_gfxmenu_model_get_entry (model, selected);
+ grub_gfxmenu_view_execute_entry (view, entry);
+ }
+ }
+ else if (c == 'c')
+ {
+ grub_gfxmenu_view_run_terminal (view);
+ if (grub_errno != GRUB_ERR_NONE)
+ *should_exit = 1;
+ }
+ else if (c == 't')
+ {
+ /* The write hook for 'menuviewer' will cause
+ * grub_menu_viewer_should_return to return nonzero. */
+ switch_to_text_menu ();
+ *should_exit = 1;
+ }
+ else if (c == '1')
+ {
+ grub_gfxmenu_view_load_theme (view,
+ "/boot/grub/themes/proto/theme.txt");
+ }
+ else if (c == '2')
+ {
+ grub_gfxmenu_view_load_theme (view,
+ "/boot/grub/themes/winter/theme.txt");
+ }
+ else if (nested && c == GRUB_TERM_ESC)
+ {
+ *should_exit = 1;
+ }
+}
+
+static void
+handle_key_events (grub_gfxmenu_model_t model,
+ grub_gfxmenu_view_t view,
+ int nested,
+ int *should_exit)
+{
+ while (!*should_exit && grub_checkkey () != -1)
+ {
+ int key = grub_getkey ();
+ int c = GRUB_TERM_ASCII_CHAR (key);
+ process_key_press (c, model, view, nested, should_exit);
+ }
+}
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
+{
+ grub_gfxmenu_model_t model;
+
+ model = grub_gfxmenu_model_new (menu);
+ if (! model)
+ {
+ grub_print_error ();
+ grub_printf ("Initializing menu data for graphical menu failed;\n"
+ "falling back to terminal based menu.\n");
+ grub_wait_after_message ();
+ switch_to_text_menu ();
+ return grub_errno;
+ }
+
+ grub_gfxmenu_view_t view;
+
+ /* Create the view. */
+ const char *theme_path = grub_env_get ("theme");
+ if (! theme_path)
+ theme_path = "/boot/grub/themes/proto/theme.txt";
+
+ view = grub_gfxmenu_view_new (theme_path, model);
+ if (! view)
+ {
+ grub_print_error ();
+ grub_printf ("Starting graphical menu failed;\n"
+ "falling back to terminal based menu.\n");
+ grub_wait_after_message ();
+ grub_gfxmenu_model_destroy (model);
+ switch_to_text_menu ();
+ return grub_errno;
+ }
+
+ /* Initially select the default menu entry. */
+ int default_index = grub_menu_get_default_entry_index (menu);
+ grub_gfxmenu_model_set_selected_index (model, default_index);
+
+ /* Start the timer to execute the default entry. */
+ grub_gfxmenu_model_set_timeout (model);
+
+ /* Main event loop. */
+ int exit_requested = 0;
+ while (!exit_requested && !grub_menu_viewer_should_return ())
+ {
+ if (grub_gfxmenu_model_timeout_expired (model))
+ {
+ grub_gfxmenu_model_clear_timeout (model);
+ int i = grub_gfxmenu_model_get_selected_index (model);
+ grub_menu_entry_t e = grub_gfxmenu_model_get_entry (model, i);
+ grub_gfxmenu_view_execute_with_fallback (view, e);
+ continue;
+ }
+
+ grub_gfxmenu_view_draw (view);
+ grub_video_swap_buffers ();
+ handle_key_events (model, view, nested, &exit_requested);
+ }
+
+ grub_gfxmenu_view_destroy (view);
+ grub_gfxmenu_model_destroy (model);
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_cmd_gfxmenu (struct grub_arg_list *state __attribute__ ((unused)),
+ int argc __attribute__ ((unused)),
+ char **args __attribute__ ((unused)))
+{
+ grub_menu_t menu = grub_env_get_data_slot ("menu");
+ if (!menu)
+ return grub_error (GRUB_ERR_MENU, "No menu context");
+
+ return show_menu (menu, 1);
+}
+
+static struct grub_menu_viewer menu_viewer =
+{
+ .name = "gfxmenu",
+ .show_menu = show_menu
+};
+
+GRUB_MOD_INIT (gfxmenu)
+{
+ (void) mod; /* To stop warning. */
+ grub_menu_viewer_register (&menu_viewer);
+ grub_register_command ("gfxmenu",
+ grub_cmd_gfxmenu, GRUB_COMMAND_FLAG_BOTH,
+ "gfxmenu", "Show graphical menu interface", 0);
+}
+
+GRUB_MOD_FINI (gfxmenu)
+{
+ grub_unregister_command ("gfxmenu");
+}
=== added file 'gfxmenu/model.c'
--- gfxmenu/model.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/model.c 2008-07-26 23:37:04 +0000
@@ -0,0 +1,192 @@
+/* model.c - Graphical menu interface MVC model. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Model type definition. */
+struct grub_gfxmenu_model
+{
+ grub_menu_t menu;
+ int num_entries;
+ grub_menu_entry_t *entries;
+ int selected_entry_index;
+ int timeout_set;
+ grub_uint64_t timeout_start;
+ grub_uint64_t timeout_at;
+};
+
+
+grub_gfxmenu_model_t
+grub_gfxmenu_model_new (grub_menu_t menu)
+{
+ grub_gfxmenu_model_t model;
+
+ model = grub_malloc (sizeof (*model));
+ if (! model)
+ return 0;
+
+ model->menu = menu;
+ model->num_entries = menu->size;
+ model->entries = 0;
+ model->selected_entry_index = 0;
+ model->timeout_set = 0;
+ model->timeout_at = 0;
+ if (model->num_entries > 0)
+ {
+ model->entries = grub_malloc (model->num_entries
+ * sizeof (*model->entries));
+ if (! model->entries)
+ goto fail_and_free;
+
+ int i;
+ grub_menu_entry_t cur;
+ for (i = 0, cur = menu->entry_list;
+ i < model->num_entries;
+ i++, cur = cur->next)
+ {
+ model->entries[i] = cur;
+ }
+ }
+
+ return model;
+
+fail_and_free:
+ grub_free (model->entries);
+ grub_free (model);
+ return 0;
+}
+
+void
+grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model)
+{
+ if (! model)
+ return;
+
+ grub_free (model->entries);
+ model->entries = 0;
+
+ grub_free (model);
+}
+
+grub_menu_t
+grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model)
+{
+ return model->menu;
+}
+
+void
+grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model)
+{
+ int timeout_sec = grub_menu_get_timeout ();
+ if (timeout_sec >= 0)
+ {
+ model->timeout_start = grub_get_time_ms ();
+ model->timeout_at = model->timeout_start + timeout_sec * 1000;
+ model->timeout_set = 1;
+ }
+ else
+ {
+ model->timeout_set = 0;
+ }
+}
+
+void
+grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model)
+{
+ model->timeout_set = 0;
+ grub_menu_set_timeout (-1);
+}
+
+int
+grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model)
+{
+ if (!model->timeout_set)
+ return -1;
+
+ return model->timeout_at - model->timeout_start;
+}
+
+int
+grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model)
+{
+ if (!model->timeout_set)
+ return -1;
+
+ return model->timeout_at - grub_get_time_ms ();
+}
+
+int
+grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model)
+{
+ if (model->timeout_set
+ && grub_get_time_ms () >= model->timeout_at)
+ return 1;
+
+ return 0;
+}
+
+int
+grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model)
+{
+ return model->num_entries;
+}
+
+int
+grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model)
+{
+ return model->selected_entry_index;
+}
+
+void
+grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model, int index)
+{
+ model->selected_entry_index = index;
+}
+
+const char *
+grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model, int index)
+{
+ if (index < 0 || index >= model->num_entries)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+ return 0;
+ }
+
+ return model->entries[index]->title;
+}
+
+grub_menu_entry_t
+grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model, int index)
+{
+ if (index < 0 || index >= model->num_entries)
+ {
+ grub_error (GRUB_ERR_OUT_OF_RANGE, "invalid menu index");
+ return 0;
+ }
+
+ return model->entries[index];
+}
+
=== added file 'gfxmenu/stringutil.c'
--- gfxmenu/stringutil.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/stringutil.c 2008-07-26 00:44:48 +0000
@@ -0,0 +1,196 @@
+/* stringutil.c - String utilities. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+
+/* Create a new NUL-terminated string on the heap as a substring of BUF.
+ The range of buf included is the half-open interval [START,END).
+ The index START is inclusive, END is exclusive. */
+char *
+grub_new_substring (const char *buf,
+ grub_size_t start, grub_size_t end)
+{
+ char *s = grub_malloc (end - start + 1);
+ if (! s)
+ return 0;
+ grub_memcpy (s, buf + start, end - start);
+ s[end - start] = '\0';
+ return s;
+}
+
+/* Eliminate "." and ".." path elements from PATH. A new heap-allocated
+ string is returned. */
+static char *
+canonicalize_path (const char *path)
+{
+ int i;
+ const char *p;
+ char *newpath = 0;
+
+ /* Count the path components in path. */
+ int components = 1;
+ for (p = path; *p; p++)
+ if (*p == '/')
+ components++;
+
+ char **path_array = grub_malloc (components * sizeof (*path_array));
+ if (! path_array)
+ return 0;
+
+ /* Initialize array elements to NULL pointers; in case once of the
+ allocations fails, the cleanup code can just call grub_free() for all
+ pointers in the array. */
+ for (i = 0; i < components; i++)
+ path_array[i] = 0;
+
+ /* Parse the path into path_array. */
+ p = path;
+ for (i = 0; i < components && p; i++)
+ {
+ /* Find the end of the path element. */
+ const char *end = grub_strchr (p, '/');
+ if (!end)
+ end = p + grub_strlen (p);
+
+ /* Copy the element. */
+ path_array[i] = grub_new_substring (p, 0, end - p);
+ if (!path_array[i])
+ goto cleanup;
+
+ /* Advance p to point to the start of the next element, or NULL. */
+ if (*end)
+ p = end + 1;
+ else
+ p = 0;
+ }
+
+ /* Eliminate '.' and '..' elements from the path array. */
+ int newpath_length = 0;
+ for (i = components - 1; i >= 0; --i)
+ {
+ if (! grub_strcmp (path_array[i], "."))
+ {
+ grub_free (path_array[i]);
+ path_array[i] = 0;
+ }
+ else if (! grub_strcmp (path_array[i], "..")
+ && i > 0)
+ {
+ /* Delete the '..' and the prior path element. */
+ grub_free (path_array[i]);
+ path_array[i] = 0;
+ --i;
+ grub_free (path_array[i]);
+ path_array[i] = 0;
+ }
+ else
+ {
+ newpath_length += grub_strlen (path_array[i]) + 1;
+ }
+ }
+
+ /* Construct a new path string. */
+ newpath = grub_malloc (newpath_length + 1);
+ if (! newpath)
+ goto cleanup;
+
+ newpath[0] = '\0';
+ char *newpath_end = newpath;
+ int first = 1;
+ for (i = 0; i < components; i++)
+ {
+ char *element = path_array[i];
+ if (element)
+ {
+ /* For all components but the first, prefix with a slash. */
+ if (! first)
+ newpath_end = grub_stpcpy (newpath_end, "/");
+ newpath_end = grub_stpcpy (newpath_end, element);
+ first = 0;
+ }
+ }
+
+cleanup:
+ for (i = 0; i < components; i++)
+ grub_free (path_array[i]);
+ grub_free (path_array);
+
+ return newpath;
+}
+
+/* Return a new heap-allocated string representing to absolute path
+ to the file referred to by PATH. If PATH is an absolute path, then
+ the returned path is a copy of PATH. If PATH is a relative path, then
+ BASE is with PATH used to construct the absolute path. */
+char *
+grub_resolve_relative_path (const char *base, const char *path)
+{
+ char *abspath;
+ char *canonpath;
+ char *p;
+
+ /* If PATH is an absolute path, then just use it as is. */
+ if (path[0] == '/' || path[0] == '(')
+ return canonicalize_path (path);
+
+ abspath = grub_malloc (grub_strlen (base) + grub_strlen (path) + 1);
+ if (!abspath)
+ return 0;
+
+ /* Concatenate BASE and PATH.
+ Note that BASE is expected to have a trailing slash. */
+ p = grub_stpcpy (abspath, base);
+ grub_stpcpy (p, path);
+
+ canonpath = canonicalize_path (abspath);
+ if (!canonpath)
+ return abspath;
+
+ grub_free (abspath);
+ return canonpath;
+}
+
+/* Get the path of the directory where the file at FILE_PATH is located.
+ FILE_PATH should refer to a file, not a directory. The returned path
+ includes a trailing slash.
+ This does not handle GRUB "(hd0,0)" paths properly yet since it only
+ looks at slashes. */
+char *
+grub_get_dirname (const char *file_path)
+{
+ int i;
+ int last_slash;
+
+ last_slash = -1;
+ for (i = grub_strlen (file_path) - 1; i >= 0; --i)
+ {
+ if (file_path[i] == '/')
+ {
+ last_slash = i;
+ break;
+ }
+ }
+ if (last_slash == -1)
+ return grub_strdup ("/");
+
+ return grub_new_substring (file_path, 0, last_slash + 1);
+}
=== added file 'gfxmenu/view.c'
--- gfxmenu/view.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/view.c 2008-07-27 00:32:41 +0000
@@ -0,0 +1,959 @@
+/* view.c - Graphical menu interface MVC view. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+/* Specifies a time (in ms) to delay so the user can read the message if a
+ default boot attempt fails and an attempt is made to fall back on
+ another entry. */
+#define FALLBACK_MESSAGE_DELAY 2000
+
+/* Definition of the private representation of the view. */
+struct grub_gfxmenu_view
+{
+ grub_video_rect_t screen;
+
+ int icon_width;
+ int icon_height;
+ int item_height;
+ int item_padding;
+ int item_icon_space;
+ int item_spacing;
+ grub_font_t title_font;
+ grub_font_t item_font;
+ grub_font_t selected_item_font;
+ grub_font_t status_font;
+ char *terminal_font_name;
+ grub_video_color_t title_color;
+ grub_video_color_t item_color;
+ grub_video_color_t selected_item_color;
+ grub_video_color_t status_color;
+ grub_video_color_t status_bg_color;
+ grub_video_color_t progress_bar_border_color;
+ grub_video_color_t progress_bar_fg_color;
+ grub_video_color_t progress_bar_bg_color;
+ struct grub_video_bitmap *desktop_image;
+ grub_video_color_t desktop_color;
+ grub_gfxmenu_box_t menu_box;
+ grub_gfxmenu_box_t selected_item_box;
+ grub_gfxmenu_box_t terminal_box;
+ char *title_text;
+ char *progress_message_text;
+
+ grub_gfxmenu_model_t model;
+};
+
+static void init_terminal (grub_gfxmenu_view_t view);
+static void destroy_terminal (void);
+static grub_err_t set_graphics_mode (void);
+static grub_err_t set_text_mode (void);
+
+/* Create a new view object, loading the theme specified by THEME_PATH and
+ associating MODEL with the view. */
+grub_gfxmenu_view_t
+grub_gfxmenu_view_new (const char *theme_path, grub_gfxmenu_model_t model)
+{
+ grub_gfxmenu_view_t view;
+
+ view = grub_malloc (sizeof (*view));
+ if (! view)
+ return 0;
+
+ set_graphics_mode ();
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ grub_video_get_viewport ((unsigned *) &view->screen.x,
+ (unsigned *) &view->screen.y,
+ (unsigned *) &view->screen.width,
+ (unsigned *) &view->screen.height);
+
+ /* Clear the screen; there may be garbage left over in video memory, and
+ loading the menu style (particularly the background) can take a while. */
+ grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
+ view->screen.x, view->screen.y,
+ view->screen.width, view->screen.height);
+ grub_video_swap_buffers ();
+
+ grub_font_t default_font;
+ grub_video_color_t default_fg_color;
+ grub_video_color_t default_bg_color;
+
+ default_font = grub_font_get ("Helvetica 12");
+ default_fg_color = grub_video_map_rgb (0, 0, 0);
+ default_bg_color = grub_video_map_rgb (255, 255, 255);
+
+ view->icon_width = 32;
+ view->icon_height = 32;
+ view->item_height = 42;
+ view->item_padding = 14;
+ view->item_icon_space = 4;
+ view->item_spacing = 16;
+ view->title_font = default_font;
+ view->item_font = default_font;
+ view->selected_item_font = default_font;
+ view->status_font = default_font;
+ view->terminal_font_name = grub_strdup ("Fixed 10");
+ view->title_color = default_fg_color;
+ view->item_color = default_fg_color;
+ view->selected_item_color = default_fg_color;
+ view->status_color = default_bg_color;
+ view->status_bg_color = default_fg_color;
+ view->progress_bar_border_color = default_fg_color;
+ view->progress_bar_fg_color = grub_video_map_rgb (160, 160, 160);
+ view->progress_bar_bg_color = default_bg_color;
+ view->desktop_image = 0;
+ view->desktop_color = default_bg_color;
+ view->menu_box = 0;
+ view->selected_item_box = 0;
+ view->terminal_box = 0;
+ view->title_text = grub_strdup ("GRUB Boot Menu");
+ view->progress_message_text = 0;
+
+ view->model = model;
+
+ if (! grub_gfxmenu_view_load_theme (view, theme_path))
+ {
+ grub_gfxmenu_view_destroy (view);
+ return 0;
+ }
+
+ init_terminal (view);
+
+ return view;
+}
+
+/* Destroy the view object. All used memory is freed. */
+void
+grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
+{
+ grub_video_bitmap_destroy (view->desktop_image);
+ if (view->menu_box)
+ view->menu_box->destroy (view->menu_box);
+ if (view->selected_item_box)
+ view->selected_item_box->destroy (view->selected_item_box);
+ if (view->terminal_box)
+ view->terminal_box->destroy (view->terminal_box);
+ grub_free (view->terminal_font_name);
+ grub_free (view->title_text);
+ grub_free (view->progress_message_text);
+ grub_free (view);
+
+ set_text_mode ();
+ destroy_terminal ();
+}
+
+/* Sets MESSAGE as the progress message for the view. The string MESSAGE
+ must be heap-allocated and will be owned by VIEW. MESSAGE can be 0, in
+ which case no message is displayed. */
+static void
+set_progress_message (grub_gfxmenu_view_t view, char *message)
+{
+ grub_free (view->progress_message_text);
+ view->progress_message_text = message;
+}
+
+/* Parse a color string of the form "r, g, b".
+ Whitespace is insignificant. */
+static grub_video_color_t
+parse_color (const char *s)
+{
+ int red = grub_strtoul (s, 0, 0);
+ if ((s = grub_strchr (s, ',')) == 0)
+ goto fail;
+ s++;
+ int green = grub_strtoul (s, 0, 0);
+ if ((s = grub_strchr (s, ',')) == 0)
+ goto fail;
+ s++;
+ int blue = grub_strtoul (s, 0, 0);
+ int alpha;
+ if ((s = grub_strchr (s, ',')) == 0)
+ alpha = 255;
+ else
+ {
+ s++;
+ alpha = grub_strtoul (s, 0, 0);
+ }
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ return grub_video_map_rgba (red, green, blue, alpha);
+
+fail:
+ return 0;
+}
+
+/* Construct a new box widget using PATTERN to find the pixmap files for it,
+ storing the new widget at *BOXPTR. PATTERN should be of the form:
+ "somewhere/style*.png". The '*' then gets substituted with the various
+ pixmap names that the widget uses.
+ Return zero on failure, nonzero on success. */
+static int
+theme_set_box (grub_gfxmenu_box_t *boxptr,
+ char *pattern, const char *theme_dir)
+{
+ char *abspattern;
+ char *prefix;
+ char *suffix;
+ char *star;
+ grub_gfxmenu_box_t box;
+
+ abspattern = grub_resolve_relative_path (theme_dir, pattern);
+ if (! abspattern)
+ return 0;
+
+ star = grub_strchr (abspattern, '*');
+ if (! star)
+ {
+ grub_free (abspattern);
+ return 0;
+ }
+
+ /* Prefix: Get the part before the '*'. */
+ prefix = grub_malloc (star - abspattern + 1);
+ if (! prefix)
+ {
+ grub_free (abspattern);
+ return 0;
+ }
+
+ grub_memcpy (prefix, abspattern, star - abspattern);
+ prefix[star - abspattern] = '\0';
+
+ /* Suffix: Everything after the '*' is the suffix. */
+ suffix = star + 1;
+
+ box = grub_gfxmenu_create_box (prefix, suffix);
+ grub_free (abspattern); /* Note: suffix, star point into abspattern. */
+ grub_free (prefix);
+ if (! box)
+ return 0;
+
+ if (*boxptr)
+ (*boxptr)->destroy (*boxptr);
+ *boxptr = box;
+ return 1;
+
+}
+
+/* Set the specified property NAME on the view to the given string VALUE.
+ This function takes ownership of both NAME and VALUE, so the caller
+ should pass pointers to new heap-allocated strings. */
+static void
+theme_set_string (grub_gfxmenu_view_t view, char *name, char *value,
+ const char *theme_dir)
+{
+ if (! grub_strcmp ("title-font", name))
+ view->title_font = grub_font_get (value);
+ else if (! grub_strcmp ("item-font", name))
+ view->item_font = grub_font_get (value);
+ else if (! grub_strcmp ("selected-item-font", name))
+ view->selected_item_font = grub_font_get (value);
+ else if (! grub_strcmp ("status-font", name))
+ view->status_font = grub_font_get (value);
+ else if (! grub_strcmp ("terminal-font", name))
+ {
+ grub_free (view->terminal_font_name);
+ view->terminal_font_name = value;
+ value = 0; /* Prevent value from being freed below. */
+ }
+ else if (! grub_strcmp ("title-color", name))
+ view->title_color = parse_color (value);
+ else if (! grub_strcmp ("item-color", name))
+ view->item_color = parse_color (value);
+ else if (! grub_strcmp ("selected-item-color", name))
+ view->selected_item_color = parse_color (value);
+ else if (! grub_strcmp ("status-color", name))
+ view->status_color = parse_color (value);
+ else if (! grub_strcmp ("status-bg-color", name))
+ view->status_bg_color = parse_color (value);
+ else if (! grub_strcmp ("progress-bar-border-color", name))
+ view->progress_bar_border_color = parse_color (value);
+ else if (! grub_strcmp ("progress-bar-fg-color", name))
+ view->progress_bar_fg_color = parse_color (value);
+ else if (! grub_strcmp ("progress-bar-bg-color", name))
+ view->progress_bar_bg_color = parse_color (value);
+ else if (! grub_strcmp ("desktop-image", name))
+ {
+ struct grub_video_bitmap *raw_bitmap;
+ struct grub_video_bitmap *scaled_bitmap;
+ char *path;
+ path = grub_resolve_relative_path (theme_dir, value);
+ if (! path)
+ goto fail;
+ if (grub_video_bitmap_load (&raw_bitmap, path) != GRUB_ERR_NONE)
+ {
+ grub_free (path);
+ goto fail;
+ }
+ grub_free(path);
+ grub_video_bitmap_create_scaled (&scaled_bitmap,
+ view->screen.width,
+ view->screen.height,
+ raw_bitmap,
+ GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
+ grub_video_bitmap_destroy (raw_bitmap);
+ if (!scaled_bitmap)
+ goto fail;
+
+ grub_video_bitmap_destroy (view->desktop_image);
+ view->desktop_image = scaled_bitmap;
+ }
+ else if (! grub_strcmp ("desktop-color", name))
+ view->desktop_color = parse_color (value);
+ else if (! grub_strcmp ("menu-box", name))
+ theme_set_box (&view->menu_box, value, theme_dir);
+ else if (! grub_strcmp ("selected-item-box", name))
+ theme_set_box (&view->selected_item_box, value, theme_dir);
+ else if (! grub_strcmp ("terminal-box", name))
+ theme_set_box (&view->terminal_box, value, theme_dir);
+ else if (! grub_strcmp ("title-text", name))
+ {
+ grub_free (view->title_text);
+ view->title_text = value;
+ value = 0; /* Prevent value from being freed below. */
+ }
+
+fail:
+ grub_free (value);
+ grub_free (name);
+}
+
+/* Set the specified property NAME on the view to the given numeric VALUE.
+ This function takes ownership NAME, so the caller should pass a pointer
+ to a new heap-allocated string. */
+static void
+theme_set_number (grub_gfxmenu_view_t view, char *name, int value)
+{
+ if (! grub_strcmp ("icon-width", name))
+ view->icon_width = value;
+ else if (! grub_strcmp ("icon-height", name))
+ view->icon_height = value;
+ else if (! grub_strcmp ("item-height", name))
+ view->item_height = value;
+ else if (! grub_strcmp ("item-padding", name))
+ view->item_padding = value;
+ else if (! grub_strcmp ("item-icon-space", name))
+ view->item_icon_space = value;
+ else if (! grub_strcmp ("item-spacing", name))
+ view->item_spacing = value;
+
+ grub_free (name);
+}
+
+/* Set properties on the view based on settings from the specified
+ theme file. Returns nonzero on success, zero on failure. */
+int
+grub_gfxmenu_view_load_theme (grub_gfxmenu_view_t view, const char *theme_path)
+{
+ char *theme_dir;
+ grub_file_t file;
+ char *buf;
+ int pos;
+ int len;
+
+ theme_dir = grub_get_dirname (theme_path);
+
+ file = grub_file_open (theme_path);
+ if (!file)
+ {
+ grub_free (theme_dir);
+ return 0;
+ }
+
+ len = grub_file_size (file);
+ buf = grub_malloc (len);
+ if (! buf)
+ {
+ grub_file_close (file);
+ grub_free (theme_dir);
+ return 0;
+ }
+ if (grub_file_read (file, buf, len) != len)
+ {
+ grub_free (buf);
+ grub_file_close (file);
+ grub_free (theme_dir);
+ return 0;
+ }
+
+ pos = 0;
+ while (pos < len)
+ {
+ /* Skip comments (lines beginning with #). */
+ if (pos < len && buf[pos] == '#')
+ goto nextline;
+
+ /* Get name. */
+ /* Find a word character. */
+ while (pos < len && grub_isspace(buf[pos]))
+ pos++;
+ int name_start = pos;
+ /* Find the end of the name. */
+ while (pos < len
+ && (grub_isalpha(buf[pos])
+ || grub_isdigit(buf[pos])
+ || buf[pos] == '_'
+ || buf[pos] == '-'))
+ pos++;
+ int name_end = pos;
+
+ if (name_end - name_start < 1)
+ goto nextline;
+
+ /* Skip whitespace before separator. */
+ while (pos < len
+ && (buf[pos] == ' '
+ || buf[pos] == '\t'
+ || buf[pos] == '\f'))
+ pos++;
+
+ /* Read separator. */
+ if (buf[pos] != ':')
+ goto nextline;
+
+ pos++; /* Skip separator. */
+
+ /* Skip whitespace after separator. */
+ while (pos < len
+ && (buf[pos] == ' '
+ || buf[pos] == '\t'
+ || buf[pos] == '\f'))
+ pos++;
+
+ /* Get the value based on its type. */
+ if (pos < len && buf[pos] == '"')
+ {
+ /* String value. (e.g., '"My string"') */
+
+ int value_start;
+ int value_end;
+
+ /* Skip the opening quotation mark. */
+ pos++;
+ /* Get string value. */
+ value_start = pos;
+ /* Find the ending quotation mark. */
+ while (pos < len
+ && !(buf[pos] == '"'
+ || buf[pos] == '\n'))
+ pos++;
+ value_end = pos;
+ theme_set_string (view,
+ grub_new_substring (buf, name_start, name_end),
+ grub_new_substring (buf, value_start, value_end),
+ theme_dir);
+ }
+ else if (pos < len && grub_isdigit(buf[pos]))
+ {
+ /* Numeric value. (e.g., '123') */
+
+ int value_start;
+ int value_end;
+ char *value_str;
+ int value;
+
+ /* Get numeric value. */
+ value_start = pos;
+ /* Find the end of the digit sequence. */
+ while (pos < len && grub_isdigit(buf[pos]))
+ pos++;
+ value_end = pos;
+ value_str = grub_new_substring (buf, value_start, value_end);
+ if (!value_str)
+ continue;
+ value = grub_strtoul (value_str, 0, 0);
+ grub_free (value_str);
+ theme_set_number (view,
+ grub_new_substring (buf, name_start, name_end),
+ value);
+ }
+
+nextline:
+ /* Eat characters up to the newline. */
+ while (pos < len && buf[pos] != '\n')
+ pos++;
+ pos++; /* Eat the newline. */
+ }
+
+ grub_free (buf);
+ grub_file_close (file);
+ grub_free (theme_dir);
+ return 1;
+}
+
+static void
+draw_background (grub_gfxmenu_view_t view)
+{
+ if (view->desktop_image)
+ {
+ struct grub_video_bitmap *img = view->desktop_image;
+ grub_video_blit_bitmap (img, GRUB_VIDEO_BLIT_REPLACE,
+ view->screen.x, view->screen.y, 0, 0,
+ grub_video_bitmap_get_width (img),
+ grub_video_bitmap_get_height (img));
+ }
+ else
+ {
+ grub_video_fill_rect (view->desktop_color,
+ view->screen.x, view->screen.y,
+ view->screen.width, view->screen.height);
+ }
+}
+
+static struct grub_video_bitmap *
+get_item_icon (grub_gfxmenu_view_t view __attribute__((unused)),
+ int item_index __attribute__((unused)))
+{
+ /* TODO Implement icons. */
+ return 0;
+}
+
+static void
+draw_menu (grub_gfxmenu_view_t view)
+{
+ int boxpad = view->item_padding;
+ int icon_text_space = view->item_icon_space;
+ int item_vspace = view->item_spacing;
+
+ int ascent = grub_font_get_ascent (view->item_font);
+ int descent = grub_font_get_descent (view->item_font);
+ int item_height = view->item_height;
+
+ int num_items = grub_gfxmenu_model_get_num_entries (view->model);
+ grub_video_rect_t r;
+ r.width = view->screen.width * 4 / 5;
+ /* Set the menu box height to fit the items. */
+ r.height = (item_height * num_items
+ + item_vspace * (num_items - 1)
+ + 2 * boxpad);
+ r.x = (view->screen.width - r.width) / 2;
+ r.y = (view->screen.height - r.height) / 2;
+ view->menu_box->set_content_size (view->menu_box, r.width, r.height);
+
+ int menu_box_left_pad = view->menu_box->get_left_pad (view->menu_box);
+ int menu_box_top_pad = view->menu_box->get_top_pad (view->menu_box);
+ view->menu_box->draw (view->menu_box,
+ r.x - menu_box_left_pad, r.y - menu_box_top_pad);
+
+ int item_top = r.y + boxpad;
+ int item_left = r.x + boxpad;
+ int i;
+
+ for (i = 0; i < num_items; i++)
+ {
+ int is_selected =
+ (i == grub_gfxmenu_model_get_selected_index (view->model));
+
+ if (is_selected)
+ {
+ view->selected_item_box->set_content_size (view->selected_item_box,
+ r.width - 2 * boxpad,
+ item_height);
+ int leftpad = view->selected_item_box->get_left_pad (view->selected_item_box);
+ int toppad = view->selected_item_box->get_top_pad (view->selected_item_box);
+ view->selected_item_box->draw (view->selected_item_box,
+ item_left - leftpad,
+ item_top - toppad);
+ }
+
+ struct grub_video_bitmap *icon;
+ if ((icon = get_item_icon (view, i)) != 0)
+ grub_video_blit_bitmap (icon, GRUB_VIDEO_BLIT_BLEND,
+ item_left,
+ item_top + (item_height - view->icon_height) / 2,
+ 0, 0, view->icon_width, view->icon_height);
+
+ const char *item_title =
+ grub_gfxmenu_model_get_entry_title (view->model, i);
+ grub_video_draw_string (item_title,
+ (is_selected
+ ? view->selected_item_font
+ : view->item_font),
+ (is_selected
+ ? view->selected_item_color
+ : view->item_color),
+ item_left + view->icon_width + icon_text_space,
+ (item_top + (item_height - (ascent + descent))
+ / 2 + ascent));
+
+ item_top += item_height + item_vspace;
+ }
+}
+
+static void
+draw_title (grub_gfxmenu_view_t view)
+{
+ if (! view->title_text)
+ return;
+
+ /* Center the title. */
+ int title_width = grub_font_get_string_width (view->title_font,
+ view->title_text);
+ int x = (view->screen.width - title_width) / 2;
+ int y = 40 + grub_font_get_ascent (view->title_font);
+ grub_video_draw_string (view->title_text,
+ view->title_font, view->title_color,
+ x, y);
+}
+
+static void
+draw_status (grub_gfxmenu_view_t view)
+{
+ int descent = grub_font_get_descent (view->status_font);
+ int ascent = grub_font_get_ascent (view->status_font);
+ int vpad = 5;
+ int textheight = descent + ascent + 1;
+ int h = 2 * vpad + 2 * textheight;
+
+ grub_video_fill_rect (view->status_bg_color,
+ 0, view->screen.height - h,
+ view->screen.width, view->screen.height - 1);
+
+ int texty = view->screen.height - h + vpad + ascent;
+ grub_video_draw_string ("Select an item with the arrow keys and "
+ "press Enter to boot.",
+ view->status_font, view->status_color, 30, texty);
+ texty += textheight;
+ grub_video_draw_string ("Press: 'c' for command line; 't' to switch to "
+ "non-graphical menu.",
+ view->status_font, view->status_color, 30, texty);
+}
+
+static void
+draw_timeout (grub_gfxmenu_view_t view)
+{
+ int timeout = grub_gfxmenu_model_get_timeout_ms (view->model);
+ if (timeout == -1)
+ return;
+
+ int remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model);
+ if (remaining < 0)
+ remaining = 0;
+
+ int t = timeout - remaining;
+
+ /* Set the timeout bar's frame. */
+ grub_video_rect_t f;
+ f.width = view->screen.width * 3 / 5;
+ f.height = view->screen.height / 25;
+ f.x = view->screen.x + (view->screen.width - f.width) / 2;
+ f.y = view->screen.y + view->screen.height - 90;
+
+ /* First attempt just uses filled rectangles;
+ TODO we should enhance with a pixmap themed progress bar component. */
+
+ /* Border. */
+ grub_video_fill_rect (view->progress_bar_border_color,
+ f.x - 1, f.y - 1,
+ f.width + 2, f.height + 2);
+
+ /* Bar background. */
+ int barwidth = f.width * t / timeout;
+ grub_video_fill_rect (view->progress_bar_bg_color,
+ f.x + barwidth, f.y,
+ f.width - barwidth, f.height);
+
+ /* Bar foreground. */
+ grub_video_fill_rect (view->progress_bar_fg_color,
+ f.x, f.y,
+ barwidth, f.height);
+
+ char *text = grub_malloc (200);
+ if (!text)
+ return;
+ int seconds_remaining_rounded_up = (remaining + 999) / 1000;
+ grub_sprintf (text,
+ "The highlighted entry will be booted automatically in %d s.",
+ seconds_remaining_rounded_up);
+ grub_font_t font = view->status_font;
+ grub_video_color_t color = view->progress_bar_border_color;
+
+ /* Center the text. */
+ int text_width = grub_font_get_string_width (font, text);
+ int x = f.x + (f.width - text_width) / 2;
+ int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+ + grub_font_get_ascent (font) / 2);
+ grub_video_draw_string (text, font, color, x, y);
+ grub_free (text);
+}
+
+static void
+draw_message (grub_gfxmenu_view_t view)
+{
+ char *text = view->progress_message_text;
+ if (! text)
+ return;
+
+ grub_font_t font = view->status_font;
+ grub_video_color_t color = view->status_color;
+
+ /* Set the timeout bar's frame. */
+ grub_video_rect_t f;
+ f.width = view->screen.width * 4 / 5;
+ f.height = 50;
+ f.x = view->screen.x + (view->screen.width - f.width) / 2;
+ f.y = view->screen.y + view->screen.height - 90 - 20 - f.height;
+
+ /* Border. */
+ grub_video_fill_rect (color,
+ f.x-1, f.y-1, f.width+2, f.height+2);
+ /* Fill. */
+ grub_video_fill_rect (view->status_bg_color,
+ f.x, f.y, f.width, f.height);
+
+ /* Center the text. */
+ int text_width = grub_font_get_string_width (font, text);
+ int x = f.x + (f.width - text_width) / 2;
+ int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
+ + grub_font_get_ascent (font) / 2);
+ grub_video_draw_string (text, font, color, x, y);
+}
+
+
+void
+grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
+{
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ draw_background (view);
+ draw_menu (view);
+ draw_title (view);
+ draw_status (view);
+ draw_timeout (view);
+ draw_message (view);
+}
+
+static grub_err_t
+set_graphics_mode (void)
+{
+ const char *doublebuf_str = grub_env_get ("doublebuffering");
+ int doublebuf_flags =
+ (doublebuf_str && doublebuf_str[0] == 'n')
+ ? 0
+ : GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED;
+
+ const char *modestr = grub_env_get ("gfxmode");
+ if (grub_video_setup_preferred_mode (modestr, doublebuf_flags, 640, 480)
+ != GRUB_ERR_NONE)
+ return grub_errno;
+
+ return GRUB_ERR_NONE;
+}
+
+static grub_err_t
+set_text_mode (void)
+{
+ return grub_video_restore ();
+}
+
+static int term_target_width;
+static int term_target_height;
+static struct grub_video_render_target *term_target;
+static int term_initialized;
+static grub_term_t term_original;
+static grub_gfxmenu_view_t term_view;
+
+static void
+repaint_terminal (int x __attribute ((unused)),
+ int y __attribute ((unused)),
+ int width __attribute ((unused)),
+ int height __attribute ((unused)))
+{
+ if (! term_view)
+ return;
+
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+ grub_gfxmenu_view_draw (term_view);
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+ int termx = term_view->screen.x
+ + term_view->screen.width * (10 - 7) / 10 / 2;
+ int termy = term_view->screen.y
+ + term_view->screen.height * (10 - 7) / 10 / 2;
+
+ grub_gfxmenu_box_t term_box = term_view->terminal_box;
+ if (term_box)
+ {
+ term_box->set_content_size (term_box,
+ term_target_width, term_target_height);
+
+ term_box->draw (term_box,
+ termx - term_box->get_left_pad (term_box),
+ termy - term_box->get_top_pad (term_box));
+ }
+
+ grub_video_blit_render_target (term_target, GRUB_VIDEO_BLIT_REPLACE,
+ termx, termy,
+ 0, 0, term_target_width, term_target_height);
+ grub_video_swap_buffers ();
+}
+
+static void
+init_terminal (grub_gfxmenu_view_t view)
+{
+ term_original = grub_term_get_current ();
+
+ term_target_width = view->screen.width * 7 / 10;
+ term_target_height = view->screen.height * 7 / 10;
+
+ grub_video_create_render_target (&term_target,
+ term_target_width,
+ term_target_height,
+ GRUB_VIDEO_MODE_TYPE_RGB
+ | GRUB_VIDEO_MODE_TYPE_ALPHA);
+ if (grub_errno != GRUB_ERR_NONE)
+ return;
+
+ /* Note: currently there is no API for changing the gfxterm font
+ on the fly, so whatever font the initially loaded theme specifies
+ will be permanent. */
+ grub_gfxterm_init_window (term_target, 0, 0,
+ term_target_width, term_target_height,
+ view->terminal_font_name, 3);
+ if (grub_errno != GRUB_ERR_NONE)
+ return;
+ term_initialized = 1;
+
+ /* XXX: store static pointer to the 'view' object so the repaint callback can access it. */
+ term_view = view;
+ grub_gfxterm_set_repaint_callback (repaint_terminal);
+ grub_term_set_current (grub_gfxterm_get_term ());
+}
+
+static void destroy_terminal (void)
+{
+ term_view = 0;
+ if (term_initialized)
+ grub_gfxterm_destroy_window ();
+ grub_gfxterm_set_repaint_callback (0);
+ if (term_target)
+ grub_video_delete_render_target (term_target);
+ if (term_original)
+ grub_term_set_current (term_original);
+}
+
+
+static void
+notify_booting (void *userdata, grub_menu_entry_t entry)
+{
+ grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+ char *s = grub_malloc (100 + grub_strlen (entry->title));
+ if (!s)
+ return;
+
+ grub_sprintf (s, "Booting '%s'", entry->title);
+ set_progress_message (view, s);
+ grub_gfxmenu_view_draw (view);
+ grub_video_swap_buffers ();
+}
+
+static void
+notify_fallback (void *userdata, grub_menu_entry_t entry)
+{
+ grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
+
+ char *s = grub_malloc (100 + grub_strlen (entry->title));
+ if (!s)
+ return;
+
+ grub_sprintf (s, "Falling back to '%s'", entry->title);
+ set_progress_message (view, s);
+ grub_gfxmenu_view_draw (view);
+ grub_video_swap_buffers ();
+ grub_millisleep (FALLBACK_MESSAGE_DELAY);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__ ((unused)))
+{
+}
+
+
+static struct grub_menu_execute_callback execute_callback =
+{
+ .notify_booting = notify_booting,
+ .notify_fallback = notify_fallback,
+ .notify_failure = notify_execution_failure
+};
+
+int
+grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
+ grub_menu_entry_t entry)
+{
+ grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view->model),
+ entry, &execute_callback, (void *) view);
+
+ if (set_graphics_mode () != GRUB_ERR_NONE)
+ return 0; /* Failure. */
+
+ /* If we returned, there was a failure. */
+ set_progress_message (view, grub_strdup ("Unable to automatically boot."));
+ grub_gfxmenu_view_draw (view);
+ grub_video_swap_buffers ();
+ grub_millisleep (5000);
+ set_progress_message (view, 0); /* Clear the message. */
+
+ return 1; /* Ok. */
+}
+
+int
+grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+ grub_menu_entry_t entry)
+{
+ /* Currently we switch back to text mode by restoring
+ the original terminal before executing the menu entry.
+ It is hard to make it work when executing a menu entry
+ that switches video modes -- it using gfxterm in a
+ window, the repaint callback seems to crash GRUB. */
+ /* TODO: Determine if this works when 'gfxterm' was set as
+ the current terminal before invoking the gfxmenu. */
+ destroy_terminal ();
+
+ grub_menu_execute_entry (entry);
+ if (grub_errno != GRUB_ERR_NONE)
+ grub_wait_after_message ();
+
+ if (set_graphics_mode () != GRUB_ERR_NONE)
+ return 0; /* Failure. */
+
+ init_terminal (view);
+ return 1; /* Ok. */
+}
+
+void
+grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view __attribute__((unused)))
+{
+ grub_cmdline_run (1);
+}
+
=== added file 'gfxmenu/widget-box.c'
--- gfxmenu/widget-box.c 1970-01-01 00:00:00 +0000
+++ gfxmenu/widget-box.c 2008-07-19 18:25:18 +0000
@@ -0,0 +1,244 @@
+/* widget_box.c - Pixmap-stylized box widget. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#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-26 23:37:04 +0000
@@ -0,0 +1,59 @@
+/* gfxmenu_model.h - gfxmenu model interface. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_GFXMENU_MODEL_HEADER
+#define GRUB_GFXMENU_MODEL_HEADER 1
+
+#include
+
+struct grub_gfxmenu_model; /* Forward declaration of opaque type. */
+typedef struct grub_gfxmenu_model *grub_gfxmenu_model_t;
+
+
+grub_gfxmenu_model_t grub_gfxmenu_model_new (grub_menu_t menu);
+
+void grub_gfxmenu_model_destroy (grub_gfxmenu_model_t model);
+
+grub_menu_t grub_gfxmenu_model_get_menu (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_timeout (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_clear_timeout (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_timeout_remaining_ms (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_timeout_expired (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_num_entries (grub_gfxmenu_model_t model);
+
+int grub_gfxmenu_model_get_selected_index (grub_gfxmenu_model_t model);
+
+void grub_gfxmenu_model_set_selected_index (grub_gfxmenu_model_t model,
+ int index);
+
+const char *grub_gfxmenu_model_get_entry_title (grub_gfxmenu_model_t model,
+ int index);
+
+grub_menu_entry_t grub_gfxmenu_model_get_entry (grub_gfxmenu_model_t model,
+ int index);
+
+#endif /* GRUB_GFXMENU_MODEL_HEADER */
+
=== added file 'include/grub/gfxmenu_view.h'
--- include/grub/gfxmenu_view.h 1970-01-01 00:00:00 +0000
+++ include/grub/gfxmenu_view.h 2008-07-26 23:37:04 +0000
@@ -0,0 +1,53 @@
+/* gfxmenu_view.h - gfxmenu view interface. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#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_with_fallback (grub_gfxmenu_view_t view,
+ grub_menu_entry_t entry);
+
+int grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
+ grub_menu_entry_t entry);
+
+void grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view);
+
+#endif /* GRUB_GFXMENU_VIEW_HEADER */
+
=== added file 'include/grub/gfxterm.h'
--- include/grub/gfxterm.h 1970-01-01 00:00:00 +0000
+++ include/grub/gfxterm.h 2008-07-19 19:56:06 +0000
@@ -0,0 +1,41 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#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-26 23:37:04 +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,27 @@
/* To exit from the normal mode. */
extern grub_jmp_buf grub_exit_env;
+extern struct grub_menu_viewer grub_normal_terminal_menu_viewer;
+
+typedef struct grub_menu_execute_callback
+{
+ void (*notify_booting) (void *userdata, grub_menu_entry_t entry);
+ void (*notify_fallback) (void *userdata, grub_menu_entry_t entry);
+ void (*notify_failure) (void *userdata);
+}
+*grub_menu_execute_callback_t;
+
void grub_enter_normal_mode (const char *config);
void grub_normal_execute (const char *config, int nested);
-void grub_menu_run (grub_menu_t menu, int nested);
+void grub_menu_execute_with_fallback (grub_menu_t menu,
+ grub_menu_entry_t entry,
+ grub_menu_execute_callback_t callback,
+ void *callback_data);
void grub_menu_entry_run (grub_menu_entry_t entry);
+int grub_menu_get_default_entry_index (grub_menu_t menu);
+int grub_menu_get_timeout (void);
+void grub_menu_set_timeout (int timeout);
+void grub_menu_execute_entry(grub_menu_entry_t entry);
void grub_cmdline_run (int nested);
int grub_cmdline_get (const char *prompt, char cmdline[], unsigned max_len,
int echo_char, int readline);
=== added file 'include/grub/stringutil.h'
--- include/grub/stringutil.h 1970-01-01 00:00:00 +0000
+++ include/grub/stringutil.h 2008-07-25 15:52:57 +0000
@@ -0,0 +1,32 @@
+/* gfxmenu_stringutil.h - String utilities for the graphical menu interface. */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2008 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see .
+ */
+
+#ifndef GRUB_GFXMENU_STRINGUTIL_HEADER
+#define GRUB_GFXMENU_STRINGUTIL_HEADER 1
+
+#include
+
+char *grub_new_substring (const char *buf,
+ grub_size_t start, grub_size_t end);
+
+char *grub_resolve_relative_path (const char *base, const char *path);
+
+char *grub_get_dirname (const char *file_path);
+
+#endif /* GRUB_GFXMENU_STRINGUTIL_HEADER */
=== modified file 'include/grub/video.h'
--- include/grub/video.h 2008-07-03 14:12:08 +0000
+++ include/grub/video.h 2008-07-19 19:31:46 +0000
@@ -151,6 +151,16 @@
grub_uint8_t a; /* Reserved bits value (0-255). */
};
+/* A 2D rectangle type. */
+struct grub_video_rect
+{
+ int x;
+ int y;
+ int width;
+ int height;
+};
+typedef struct grub_video_rect grub_video_rect_t;
+
struct grub_video_adapter
{
/* The video adapter name. */
@@ -307,4 +317,21 @@
grub_err_t grub_video_get_active_render_target (struct grub_video_render_target **target);
+
+/* Defined in video/setmode.c */
+
+/* Set the video mode based on the preferred modes specified in MODE_LIST in
+ the form: 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-26 23:37:04 +0000
@@ -21,9 +21,11 @@
#include
#include
#include
+#include
#include
#include
#include
+#include
static grub_uint8_t grub_color_menu_normal;
static grub_uint8_t grub_color_menu_highlight;
@@ -241,8 +243,8 @@
/* Return the current timeout. If the variable "timeout" is not set or
invalid, return -1. */
-static int
-get_timeout (void)
+int
+grub_menu_get_timeout (void)
{
char *val;
int timeout;
@@ -269,8 +271,8 @@
}
/* Set current timeout in the variable "timeout". */
-static void
-set_timeout (int timeout)
+void
+grub_menu_set_timeout (int timeout)
{
/* Ignore TIMEOUT if it is zero, because it will be unset really soon. */
if (timeout > 0)
@@ -308,6 +310,57 @@
return entry;
}
+/* Get the default menu entry index. */
+int
+grub_menu_get_default_entry_index (grub_menu_t menu)
+{
+ int i = get_entry_number ("default");
+
+ /* If DEFAULT_ENTRY is not within the menu entries, fall back to
+ the first entry. */
+ if (i < 0 || i >= menu->size)
+ i = 0;
+
+ return i;
+}
+
+/* Get the first entry number from the variable NAME, which is a
+ space-separated list of nonnegative integers. The entry number which
+ is returned is stripped from the value of NAME. */
+static int
+get_and_remove_first_entry_number (const char *name)
+{
+ char *val;
+ char *tail;
+ int entry;
+
+ val = grub_env_get (name);
+ if (! val)
+ return -1;
+
+ grub_error_push ();
+
+ entry = (int) grub_strtoul (val, &tail, 0);
+
+ if (grub_errno == GRUB_ERR_NONE)
+ {
+ /* Skip whitespace to find the next digit. */
+ while (*tail && grub_isspace (*tail))
+ tail++;
+ grub_env_set (name, tail);
+ }
+ else
+ {
+ grub_env_unset (name);
+ grub_errno = GRUB_ERR_NONE;
+ entry = -1;
+ }
+
+ grub_error_pop ();
+
+ return entry;
+}
+
static void
print_timeout (int timeout, int offset, int second_stage)
{
@@ -332,15 +385,10 @@
first = 0;
- default_entry = get_entry_number ("default");
-
- /* If DEFAULT_ENTRY is not within the menu entries, fall back to
- the first entry. */
- if (default_entry < 0 || default_entry >= menu->size)
- default_entry = 0;
+ default_entry = grub_menu_get_default_entry_index (menu);
/* If timeout is 0, drawing is pointless (and ugly). */
- if (get_timeout () == 0)
+ if (grub_menu_get_timeout () == 0)
return default_entry;
offset = default_entry;
@@ -359,15 +407,15 @@
print_entries (menu, first, offset);
grub_refresh ();
- timeout = get_timeout ();
+ timeout = grub_menu_get_timeout ();
if (timeout > 0)
print_timeout (timeout, offset, 0);
- while (1)
+ while (!grub_menu_viewer_should_return ())
{
int c;
- timeout = get_timeout ();
+ timeout = grub_menu_get_timeout ();
if (timeout > 0)
{
@@ -377,7 +425,7 @@
if (current_time - saved_time >= GRUB_TICKS_PER_SECOND)
{
timeout--;
- set_timeout (timeout);
+ grub_menu_set_timeout (timeout);
saved_time = current_time;
print_timeout (timeout, offset, 1);
}
@@ -468,6 +516,10 @@
}
goto refresh;
+ case 't':
+ grub_env_set ("menuviewer", "protomenu");
+ goto refresh;
+
default:
break;
}
@@ -476,13 +528,14 @@
}
}
- /* Never reach here. */
+ /* Exit menu without activating an item. This occurs if the user presses
+ * 't', switching to the graphical menu viewer. */
return -1;
}
/* Run a menu entry. */
-static void
-run_menu_entry (grub_menu_entry_t entry)
+void
+grub_menu_execute_entry(grub_menu_entry_t entry)
{
grub_script_execute (entry->commands);
@@ -491,15 +544,80 @@
grub_command_execute ("boot", 0);
}
+/* Execute ENTRY from the menu MENU, falling back to entries specified
+ in the environment variable "fallback" if it fails. CALLBACK is a
+ pointer to a struct of function pointers which are used to allow the
+ caller provide feedback to the user. */
void
-grub_menu_run (grub_menu_t menu, int nested)
+grub_menu_execute_with_fallback (grub_menu_t menu,
+ grub_menu_entry_t entry,
+ grub_menu_execute_callback_t callback,
+ void *callback_data)
+{
+ int fallback_entry;
+
+ callback->notify_booting (callback_data, entry);
+
+ grub_menu_execute_entry (entry);
+
+ /* Deal with fallback entries. */
+ while ((fallback_entry = get_and_remove_first_entry_number ("fallback"))
+ >= 0)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+
+ entry = get_entry (menu, fallback_entry);
+ callback->notify_fallback (callback_data, entry);
+ grub_menu_execute_entry (entry);
+ }
+
+ if (grub_errno != GRUB_ERR_NONE)
+ callback->notify_failure (callback_data);
+}
+
+static void
+notify_booting (void *userdata __attribute__((unused)),
+ grub_menu_entry_t entry)
+{
+ grub_printf (" Booting \'%s\'\n\n", entry->title);
+}
+
+static void
+notify_fallback (void *userdata __attribute__((unused)),
+ grub_menu_entry_t entry)
+{
+ grub_printf ("\n Falling back to \'%s\'\n\n", entry->title);
+ grub_millisleep (2000);
+}
+
+static void
+notify_execution_failure (void *userdata __attribute__((unused)))
+{
+ if (grub_errno != GRUB_ERR_NONE)
+ {
+ grub_print_error ();
+ grub_errno = GRUB_ERR_NONE;
+
+ grub_wait_after_message ();
+ }
+}
+
+static struct grub_menu_execute_callback execution_callback =
+{
+ .notify_booting = notify_booting,
+ .notify_fallback = notify_fallback,
+ .notify_failure = notify_execution_failure
+};
+
+static grub_err_t
+show_menu (grub_menu_t menu, int nested)
{
while (1)
{
int boot_entry;
grub_menu_entry_t e;
- int fallback_entry;
-
+
boot_entry = run_menu (menu, nested);
if (boot_entry < 0)
break;
@@ -507,34 +625,18 @@
e = get_entry (menu, boot_entry);
if (! e)
continue; /* Menu is empty. */
-
+
grub_cls ();
grub_setcursor (1);
- grub_printf (" Booting \'%s\'\n\n", e->title);
-
- run_menu_entry (e);
-
- /* Deal with a fallback entry. */
- /* FIXME: Multiple fallback entries like GRUB Legacy. */
- fallback_entry = get_entry_number ("fallback");
- if (fallback_entry >= 0)
- {
- grub_print_error ();
- grub_errno = GRUB_ERR_NONE;
-
- e = get_entry (menu, fallback_entry);
- grub_env_unset ("fallback");
- grub_printf ("\n Falling back to \'%s\'\n\n", e->title);
- run_menu_entry (e);
- }
-
- if (grub_errno != GRUB_ERR_NONE)
- {
- grub_print_error ();
- grub_errno = GRUB_ERR_NONE;
-
- grub_wait_after_message ();
- }
+ grub_menu_execute_with_fallback (menu, e, &execution_callback, 0);
}
+
+ return GRUB_ERR_NONE;
}
+
+struct grub_menu_viewer grub_normal_terminal_menu_viewer =
+{
+ .name = "terminal",
+ .show_menu = show_menu
+};
=== modified file 'term/gfxterm.c'
--- term/gfxterm.c 2008-07-03 14:28:01 +0000
+++ term/gfxterm.c 2008-07-19 19:56:06 +0000
@@ -28,12 +28,12 @@
#include
#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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
lw/LVmjKjKmuoff8OxPNc0HKM6pNNeBr7IATOxQ/O4GBoLtiY1blfLERMfWL29rJoeXtKp5yPDr7
Sp5U7J9mygPkahW9vQ6b6BBriOgcYsnJlFHZuqO7s6O6Mj6UYROh3QvA9m6fZe6CqgoqRPToiJnX
Z7RMvsWmXxETsc7sDBLK7AhyUNLJAkIMIVxeMXtuL2GOOf0MecH8rL7/Bzn+SgwIRT9UkQkEkD9k
BJiBCSBKA4jDxz/F7/4H93fz3N/ck99Zn786Ss35xyytWWGm5yZtXiYDZFK1yMmlYrCvK2v66/F7
34v7b+39azb1+LTscfRrr/BRcPl0b8KM0fn0ht1wfN48r8ffh274/r5vUiHu2ohF8O/cgdpynSl2
+IWWrGBDnC7TerEoS1OzPJTEQugOv6fX69B216atajTOopDcqHLdev4qRXBzKbs1Y6jMuU0RqodK
xZrSPp9/4n3NZ7ldPc7PxvqccfW/WeXve95aVOjJsqdFT6j8pY8FTZo6PY+v5efafR8J+KIaX5Z7
dKe3ikfMtza3Ctj4zhg1Go882JPbCHsjTrpOMKTV5uOH8p674muZ+SsZ+fPrz9nrr0S/r8nLO0D1
zrt4vxzdzWPPHsvNXgyse4aeKpYzjV08LtuK6zll2/V7LL6eO665tjq3d2F+nZ3dbQ27Lrl3at3V
Zo9cm5t3HW3dZYShGO849kSTQdpQk03pB4wpH6D4mbez20oPovLOM2MTqfUHROHWrw73iv6IyK+g
5fyoOyzvZ6zBxTQUed/Y7BdCD+8/fBoPnZFSjNhmS4FyNEk3qZimg4Ooqxt8iIeLDnA9UP+tUVEo
DBRWEBVkREEnjdedv0390Gxp5elu/w9MfQ5jwuhB290PVfG2unu92G/pogh40Q6ehEIdP/JqiRD/
6P+r4bv/HAKn/xRUZzIEqQCSxS0Bv+wwpn9GSEC5LIKUQpRAoxkiRCn6ChWUyYCGFpKSwoFAlE/l
h0yBZIMZIEUIPuF5hPGK70VfmbgdgvSB8r6xwQ8JcLGkIefYFk/Shtih/ws/qJCAfGB/wifm5VKZ
OREKQoUIMAkGBAj83mLGy1G/504k3y+THb+WY8/2qTf8HhRiGR9yHz8fvYUY/JoeAofyCxOb/T+X
ev7n6KdtKUn2Z53p+f3H9bD9pOy/9P9A/CHbswOqJ4qgWnzpEeggB54IdZxY7UuD+6IIPpivjkkh
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
4UJDpT4ISA==