summaryrefslogtreecommitdiffstats
path: root/grub-core/term/gfxterm.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/term/gfxterm.c')
-rw-r--r--grub-core/term/gfxterm.c1160
1 files changed, 1160 insertions, 0 deletions
diff --git a/grub-core/term/gfxterm.c b/grub-core/term/gfxterm.c
new file mode 100644
index 0000000..b40fcce
--- /dev/null
+++ b/grub-core/term/gfxterm.c
@@ -0,0 +1,1160 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/term.h>
+#include <grub/types.h>
+#include <grub/dl.h>
+#include <grub/misc.h>
+#include <grub/font.h>
+#include <grub/mm.h>
+#include <grub/env.h>
+#include <grub/video.h>
+#include <grub/gfxterm.h>
+#include <grub/bitmap.h>
+#include <grub/command.h>
+#include <grub/extcmd.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+#define DEFAULT_VIDEO_MODE "auto"
+#define DEFAULT_BORDER_WIDTH 10
+
+#define DEFAULT_STANDARD_COLOR 0x07
+
+struct grub_dirty_region
+{
+ int top_left_x;
+ int top_left_y;
+ int bottom_right_x;
+ int bottom_right_y;
+};
+
+struct grub_colored_char
+{
+ /* An Unicode codepoint. */
+ struct grub_unicode_glyph code;
+
+ /* Color values. */
+ grub_video_color_t fg_color;
+ grub_video_color_t bg_color;
+};
+
+struct grub_virtual_screen
+{
+ /* Dimensions of the virtual screen in pixels. */
+ unsigned int width;
+ unsigned int height;
+
+ /* Offset in the display in pixels. */
+ unsigned int offset_x;
+ unsigned int offset_y;
+
+ /* TTY Character sizes in pixes. */
+ unsigned int normal_char_width;
+ unsigned int normal_char_height;
+
+ /* Virtual screen TTY size in characters. */
+ unsigned int columns;
+ unsigned int rows;
+
+ /* Current cursor location in characters. */
+ unsigned int cursor_x;
+ unsigned int cursor_y;
+
+ /* Current cursor state. */
+ int cursor_state;
+
+ /* Font settings. */
+ grub_font_t font;
+
+ /* Terminal color settings. */
+ grub_uint8_t standard_color_setting;
+ grub_uint8_t term_color;
+
+ /* Color settings. */
+ grub_video_color_t fg_color;
+ grub_video_color_t bg_color;
+ grub_video_color_t bg_color_display;
+
+ /* Text buffer for virtual screen. Contains (columns * rows) number
+ of entries. */
+ struct grub_colored_char *text_buffer;
+
+ int total_scroll;
+
+ int functional;
+};
+
+struct grub_gfxterm_window
+{
+ unsigned x;
+ unsigned y;
+ unsigned width;
+ unsigned height;
+ int double_repaint;
+};
+
+static struct grub_video_render_target *render_target;
+void (*grub_gfxterm_decorator_hook) (void) = NULL;
+static struct grub_gfxterm_window window;
+static struct grub_virtual_screen virtual_screen;
+static int repaint_scheduled = 0;
+static int repaint_was_scheduled = 0;
+
+static void destroy_window (void);
+
+static struct grub_video_render_target *text_layer;
+
+struct grub_gfxterm_background grub_gfxterm_background;
+
+static struct grub_dirty_region dirty_region;
+
+static void dirty_region_reset (void);
+
+static int dirty_region_is_empty (void);
+
+static void dirty_region_add (int x, int y,
+ unsigned int width, unsigned int height);
+
+static unsigned int calculate_normal_character_width (grub_font_t font);
+
+static unsigned char calculate_character_width (struct grub_font_glyph *glyph);
+
+static void grub_gfxterm_refresh (struct grub_term_output *term __attribute__ ((unused)));
+
+static grub_size_t
+grub_gfxterm_getcharwidth (struct grub_term_output *term __attribute__ ((unused)),
+ const struct grub_unicode_glyph *c);
+
+static void
+set_term_color (grub_uint8_t term_color)
+{
+ struct grub_video_render_target *old_target;
+
+ /* Save previous target and switch to text layer. */
+ grub_video_get_active_render_target (&old_target);
+ grub_video_set_active_render_target (text_layer);
+
+ /* Map terminal color to text layer compatible video colors. */
+ virtual_screen.fg_color = grub_video_map_color(term_color & 0x0f);
+
+ /* Special case: use black as transparent color. */
+ if (((term_color >> 4) & 0x0f) == 0)
+ {
+ virtual_screen.bg_color = grub_video_map_rgba(0, 0, 0, 0);
+ }
+ else
+ {
+ virtual_screen.bg_color = grub_video_map_color((term_color >> 4) & 0x0f);
+ }
+
+ /* Restore previous target. */
+ grub_video_set_active_render_target (old_target);
+}
+
+static void
+clear_char (struct grub_colored_char *c)
+{
+ grub_unicode_destroy_glyph (&c->code);
+ grub_unicode_set_glyph_from_code (&c->code, ' ');
+ c->fg_color = virtual_screen.fg_color;
+ c->bg_color = virtual_screen.bg_color;
+}
+
+static void
+grub_virtual_screen_free (void)
+{
+ virtual_screen.functional = 0;
+
+ /* If virtual screen has been allocated, free it. */
+ if (virtual_screen.text_buffer != 0)
+ {
+ unsigned i;
+ for (i = 0;
+ i < virtual_screen.columns * virtual_screen.rows;
+ i++)
+ grub_unicode_destroy_glyph (&virtual_screen.text_buffer[i].code);
+ grub_free (virtual_screen.text_buffer);
+ }
+
+ /* Reset virtual screen data. */
+ grub_memset (&virtual_screen, 0, sizeof (virtual_screen));
+
+ /* Free render targets. */
+ grub_video_delete_render_target (text_layer);
+ text_layer = 0;
+}
+
+static grub_err_t
+grub_virtual_screen_setup (unsigned int x, unsigned int y,
+ unsigned int width, unsigned int height,
+ grub_font_t font)
+{
+ unsigned int i;
+
+ /* Free old virtual screen. */
+ grub_virtual_screen_free ();
+
+ /* Initialize with default data. */
+ virtual_screen.font = font;
+ virtual_screen.width = width;
+ virtual_screen.height = height;
+ virtual_screen.offset_x = x;
+ virtual_screen.offset_y = y;
+ virtual_screen.normal_char_width =
+ calculate_normal_character_width (virtual_screen.font);
+ virtual_screen.normal_char_height =
+ grub_font_get_max_char_height (virtual_screen.font);
+ if (virtual_screen.normal_char_height == 0)
+ virtual_screen.normal_char_height = 16;
+ virtual_screen.cursor_x = 0;
+ virtual_screen.cursor_y = 0;
+ virtual_screen.cursor_state = 1;
+ virtual_screen.total_scroll = 0;
+
+ /* Calculate size of text buffer. */
+ virtual_screen.columns = virtual_screen.width / virtual_screen.normal_char_width;
+ virtual_screen.rows = virtual_screen.height / virtual_screen.normal_char_height;
+
+ /*
+ * There must be a minimum number of rows and columns for the screen to
+ * make sense. Arbitrarily pick half of 80x24. If either dimensions is 0
+ * we would allocate 0 bytes for the text_buffer.
+ */
+ if (virtual_screen.columns < 40 || virtual_screen.rows < 12)
+ return grub_error (GRUB_ERR_BAD_FONT,
+ "font: glyphs too large to fit on screen");
+
+ /* Allocate memory for text buffer. */
+ virtual_screen.text_buffer =
+ (struct grub_colored_char *) grub_malloc (virtual_screen.columns
+ * virtual_screen.rows
+ * sizeof (*virtual_screen.text_buffer));
+ if (grub_errno != GRUB_ERR_NONE)
+ return grub_errno;
+
+ /* Create new render target for text layer. */
+ grub_video_create_render_target (&text_layer,
+ virtual_screen.width,
+ virtual_screen.height,
+ GRUB_VIDEO_MODE_TYPE_INDEX_COLOR
+ | GRUB_VIDEO_MODE_TYPE_ALPHA);
+ if (grub_errno != GRUB_ERR_NONE)
+ return grub_errno;
+
+ /* As we want to have colors compatible with rendering target,
+ we can only have those after mode is initialized. */
+ grub_video_set_active_render_target (text_layer);
+
+ virtual_screen.standard_color_setting = DEFAULT_STANDARD_COLOR;
+
+ virtual_screen.term_color = virtual_screen.standard_color_setting;
+
+ set_term_color (virtual_screen.term_color);
+
+ grub_video_set_active_render_target (render_target);
+
+ virtual_screen.bg_color_display =
+ grub_video_map_rgba_color (grub_gfxterm_background.default_bg_color);
+
+ /* Clear out text buffer. */
+ for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++)
+ {
+ virtual_screen.text_buffer[i].code.ncomb = 0;
+ clear_char (&(virtual_screen.text_buffer[i]));
+ }
+ if (grub_errno)
+ return grub_errno;
+
+ virtual_screen.functional = 1;
+
+ return GRUB_ERR_NONE;
+}
+
+void
+grub_gfxterm_schedule_repaint (void)
+{
+ repaint_scheduled = 1;
+}
+
+grub_err_t
+grub_gfxterm_set_window (struct grub_video_render_target *target,
+ int x, int y, int width, int height,
+ int double_repaint,
+ grub_font_t font, int border_width)
+{
+ /* Clean up any prior instance. */
+ destroy_window ();
+
+ /* Set the render target. */
+ render_target = target;
+
+ /* Create virtual screen. */
+ if (grub_virtual_screen_setup (border_width, border_width,
+ width - 2 * border_width,
+ height - 2 * border_width,
+ font)
+ != GRUB_ERR_NONE)
+ {
+ return grub_errno;
+ }
+
+ /* Set window bounds. */
+ window.x = x;
+ window.y = y;
+ window.width = width;
+ window.height = height;
+ window.double_repaint = double_repaint;
+
+ dirty_region_reset ();
+ grub_gfxterm_schedule_repaint ();
+
+ return grub_errno;
+}
+
+static grub_err_t
+grub_gfxterm_fullscreen (void)
+{
+ const char *font_name;
+ struct grub_video_mode_info mode_info;
+ grub_video_color_t color;
+ grub_err_t err;
+ int double_redraw;
+ grub_font_t font;
+
+ err = grub_video_get_info (&mode_info);
+ /* Figure out what mode we ended up. */
+ if (err)
+ return err;
+
+ grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
+
+ double_redraw = mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED
+ && !(mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_UPDATING_SWAP);
+
+ /* Make sure screen is set to the default background color. */
+ color = grub_video_map_rgba_color (grub_gfxterm_background.default_bg_color);
+ grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+ if (double_redraw)
+ {
+ grub_video_swap_buffers ();
+ grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
+ }
+
+ /* Select the font to use. */
+ font_name = grub_env_get ("gfxterm_font");
+ if (! font_name)
+ font_name = ""; /* Allow fallback to any font. */
+
+ font = grub_font_get (font_name);
+ if (!font)
+ return grub_error (GRUB_ERR_BAD_FONT, "no font loaded");
+
+ grub_gfxterm_decorator_hook = NULL;
+
+ return grub_gfxterm_set_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
+ 0, 0, mode_info.width, mode_info.height,
+ double_redraw,
+ font, DEFAULT_BORDER_WIDTH);
+}
+
+static grub_err_t
+grub_gfxterm_term_init (struct grub_term_output *term __attribute__ ((unused)))
+{
+ char *tmp;
+ grub_err_t err;
+ const char *modevar;
+
+ /* Parse gfxmode environment variable if set. */
+ modevar = grub_env_get ("gfxmode");
+ if (! modevar || *modevar == 0)
+ err = grub_video_set_mode (DEFAULT_VIDEO_MODE,
+ GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0);
+ else
+ {
+ tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar);
+ if (!tmp)
+ return grub_errno;
+ err = grub_video_set_mode (tmp, GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0);
+ grub_free (tmp);
+ }
+
+ if (err)
+ return err;
+
+ err = grub_gfxterm_fullscreen ();
+ if (err)
+ grub_video_restore ();
+
+ return err;
+}
+
+static void
+destroy_window (void)
+{
+ grub_virtual_screen_free ();
+}
+
+static grub_err_t
+grub_gfxterm_term_fini (struct grub_term_output *term __attribute__ ((unused)))
+{
+ unsigned i;
+ destroy_window ();
+ grub_video_restore ();
+
+ for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++)
+ {
+ grub_unicode_destroy_glyph (&virtual_screen.text_buffer[i].code);
+ virtual_screen.text_buffer[i].code.ncomb = 0;
+ virtual_screen.text_buffer[i].code.base = 0;
+ }
+
+ /* Clear error state. */
+ grub_errno = GRUB_ERR_NONE;
+ return GRUB_ERR_NONE;
+}
+
+static void
+redraw_screen_rect (unsigned int x, unsigned int y,
+ unsigned int width, unsigned int height)
+{
+ grub_video_color_t color;
+ 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 (grub_gfxterm_background.bitmap)
+ {
+ /* Render bitmap as background. */
+ grub_video_blit_bitmap (grub_gfxterm_background.bitmap,
+ GRUB_VIDEO_BLIT_REPLACE, x, y,
+ x, y,
+ width, height);
+
+ /* If bitmap is smaller than requested blit area, use background
+ color. */
+ color = virtual_screen.bg_color_display;
+
+ /* Fill right side of the bitmap if needed. */
+ if ((x + width >= grub_gfxterm_background.bitmap->mode_info.width)
+ && (y < grub_gfxterm_background.bitmap->mode_info.height))
+ {
+ int w = (x + width) - grub_gfxterm_background.bitmap->mode_info.width;
+ int h = height;
+ unsigned int tx = x;
+
+ if (y + height >= grub_gfxterm_background.bitmap->mode_info.height)
+ {
+ h = grub_gfxterm_background.bitmap->mode_info.height - y;
+ }
+
+ if (grub_gfxterm_background.bitmap->mode_info.width > tx)
+ {
+ tx = grub_gfxterm_background.bitmap->mode_info.width;
+ }
+
+ /* Render background layer. */
+ grub_video_fill_rect (color, tx, y, w, h);
+ }
+
+ /* Fill bottom side of the bitmap if needed. */
+ if (y + height >= grub_gfxterm_background.bitmap->mode_info.height)
+ {
+ int h = (y + height) - grub_gfxterm_background.bitmap->mode_info.height;
+ unsigned int ty = y;
+
+ if (grub_gfxterm_background.bitmap->mode_info.height > ty)
+ {
+ ty = grub_gfxterm_background.bitmap->mode_info.height;
+ }
+
+ /* Render background layer. */
+ grub_video_fill_rect (color, x, ty, width, h);
+ }
+ }
+ else
+ {
+ /* Render background layer. */
+ color = virtual_screen.bg_color_display;
+ grub_video_fill_rect (color, x, y, width, height);
+ }
+
+ if (grub_gfxterm_background.blend_text_bg)
+ /* Render text layer as blended. */
+ grub_video_blit_render_target (text_layer, GRUB_VIDEO_BLIT_BLEND, x, y,
+ x - virtual_screen.offset_x,
+ y - virtual_screen.offset_y,
+ width, height);
+ else
+ /* Render text layer as replaced (to get texts background color). */
+ grub_video_blit_render_target (text_layer, GRUB_VIDEO_BLIT_REPLACE, x, y,
+ x - virtual_screen.offset_x,
+ 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 (render_target);
+}
+
+static void
+dirty_region_reset (void)
+{
+ dirty_region.top_left_x = -1;
+ dirty_region.top_left_y = -1;
+ dirty_region.bottom_right_x = -1;
+ dirty_region.bottom_right_y = -1;
+ repaint_was_scheduled = 0;
+}
+
+static int
+dirty_region_is_empty (void)
+{
+ if ((dirty_region.top_left_x == -1)
+ || (dirty_region.top_left_y == -1)
+ || (dirty_region.bottom_right_x == -1)
+ || (dirty_region.bottom_right_y == -1))
+ return 1;
+ return 0;
+}
+
+static void
+dirty_region_add_real (int x, int y, unsigned int width, unsigned int height)
+{
+ if (dirty_region_is_empty ())
+ {
+ dirty_region.top_left_x = x;
+ dirty_region.top_left_y = y;
+ dirty_region.bottom_right_x = x + width - 1;
+ dirty_region.bottom_right_y = y + height - 1;
+ }
+ else
+ {
+ if (x < dirty_region.top_left_x)
+ dirty_region.top_left_x = x;
+ if (y < dirty_region.top_left_y)
+ dirty_region.top_left_y = y;
+ if ((x + (int)width - 1) > dirty_region.bottom_right_x)
+ dirty_region.bottom_right_x = x + width - 1;
+ if ((y + (int)height - 1) > dirty_region.bottom_right_y)
+ dirty_region.bottom_right_y = y + height - 1;
+ }
+}
+
+static void
+dirty_region_add (int x, int y, unsigned int width, unsigned int height)
+{
+ if ((width == 0) || (height == 0))
+ return;
+
+ if (repaint_scheduled)
+ {
+ dirty_region_add_real (0, 0,
+ window.width, window.height);
+ repaint_scheduled = 0;
+ repaint_was_scheduled = 1;
+ }
+ dirty_region_add_real (x, y, width, height);
+}
+
+static void
+dirty_region_add_virtualscreen (void)
+{
+ /* Mark virtual screen as dirty. */
+ dirty_region_add (virtual_screen.offset_x, virtual_screen.offset_y,
+ virtual_screen.width, virtual_screen.height);
+}
+
+
+static void
+dirty_region_redraw (void)
+{
+ int x;
+ int y;
+ int width;
+ int height;
+
+ if (dirty_region_is_empty ())
+ return;
+
+ x = dirty_region.top_left_x;
+ y = dirty_region.top_left_y;
+
+ width = dirty_region.bottom_right_x - x + 1;
+ height = dirty_region.bottom_right_y - y + 1;
+
+ if (repaint_was_scheduled && grub_gfxterm_decorator_hook)
+ grub_gfxterm_decorator_hook ();
+
+ redraw_screen_rect (x, y, width, height);
+}
+
+static inline void
+paint_char (unsigned cx, unsigned cy)
+{
+ struct grub_colored_char *p;
+ struct grub_font_glyph *glyph;
+ grub_video_color_t color;
+ grub_video_color_t bgcolor;
+ unsigned int x;
+ unsigned int y;
+ int ascent;
+ unsigned int height;
+ unsigned int width;
+
+ if (cy + virtual_screen.total_scroll >= virtual_screen.rows)
+ return;
+
+ /* Find out active character. */
+ p = (virtual_screen.text_buffer
+ + cx + (cy * virtual_screen.columns));
+
+ if (!p->code.base)
+ return;
+
+ /* Get glyph for character. */
+ glyph = grub_font_construct_glyph (virtual_screen.font, &p->code);
+ if (!glyph)
+ {
+ grub_errno = GRUB_ERR_NONE;
+ return;
+ }
+ ascent = grub_font_get_ascent (virtual_screen.font);
+
+ width = virtual_screen.normal_char_width * calculate_character_width(glyph);
+ height = virtual_screen.normal_char_height;
+
+ color = p->fg_color;
+ bgcolor = p->bg_color;
+
+ x = cx * virtual_screen.normal_char_width;
+ y = (cy + virtual_screen.total_scroll) * virtual_screen.normal_char_height;
+
+ /* Render glyph to text layer. */
+ grub_video_set_active_render_target (text_layer);
+ grub_video_fill_rect (bgcolor, x, y, width, height);
+ grub_font_draw_glyph (glyph, color, x, y + ascent);
+ grub_video_set_active_render_target (render_target);
+
+ /* Mark character to be drawn. */
+ dirty_region_add (virtual_screen.offset_x + x, virtual_screen.offset_y + y,
+ width, height);
+}
+
+static inline void
+write_char (void)
+{
+ paint_char (virtual_screen.cursor_x, virtual_screen.cursor_y);
+}
+
+static inline void
+draw_cursor (int show)
+{
+ unsigned int x;
+ unsigned int y;
+ unsigned int width;
+ unsigned int height;
+ unsigned int ascent;
+ grub_video_color_t color;
+
+ write_char ();
+
+ if (!show)
+ return;
+
+ if (virtual_screen.cursor_y + virtual_screen.total_scroll
+ >= virtual_screen.rows)
+ return;
+
+ /* Ensure that cursor doesn't go outside of character box. */
+ ascent = grub_font_get_ascent(virtual_screen.font);
+ if (ascent > virtual_screen.normal_char_height - 2)
+ ascent = virtual_screen.normal_char_height - 2;
+
+ /* Determine cursor properties and position on text layer. */
+ x = virtual_screen.cursor_x * virtual_screen.normal_char_width;
+ width = virtual_screen.normal_char_width;
+ color = virtual_screen.fg_color;
+ y = ((virtual_screen.cursor_y + virtual_screen.total_scroll)
+ * virtual_screen.normal_char_height
+ + ascent);
+ height = 2;
+
+ /* Render cursor to text layer. */
+ grub_video_set_active_render_target (text_layer);
+ grub_video_fill_rect (color, x, y, width, height);
+ grub_video_set_active_render_target (render_target);
+
+ /* Mark cursor to be redrawn. */
+ dirty_region_add (virtual_screen.offset_x + x,
+ virtual_screen.offset_y + y,
+ width, height);
+}
+
+static void
+real_scroll (void)
+{
+ unsigned int i, j, was_scroll;
+ grub_video_color_t color;
+
+ if (!virtual_screen.total_scroll)
+ return;
+
+ /* If we have bitmap, re-draw screen, otherwise scroll physical screen too. */
+ if (grub_gfxterm_background.bitmap)
+ {
+ /* Scroll physical screen. */
+ grub_video_set_active_render_target (text_layer);
+ color = virtual_screen.bg_color;
+ grub_video_scroll (color, 0, -virtual_screen.normal_char_height
+ * virtual_screen.total_scroll);
+
+ /* Mark virtual screen to be redrawn. */
+ dirty_region_add_virtualscreen ();
+ }
+ else
+ {
+ grub_video_rect_t saved_view;
+
+ /* Remove cursor. */
+ draw_cursor (0);
+
+ grub_video_set_active_render_target (render_target);
+
+ i = window.double_repaint ? 2 : 1;
+
+ color = virtual_screen.bg_color_display;
+
+ while (i--)
+ {
+ /* 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,
+ virtual_screen.width,
+ virtual_screen.normal_char_height
+ * virtual_screen.total_scroll);
+
+ grub_video_set_active_render_target (render_target);
+ dirty_region_redraw ();
+
+ /* Scroll physical screen. */
+ grub_video_scroll (color, 0, -virtual_screen.normal_char_height
+ * virtual_screen.total_scroll);
+
+ /* Restore saved viewport. */
+ grub_video_set_viewport (saved_view.x, saved_view.y,
+ saved_view.width, saved_view.height);
+
+ if (i)
+ grub_video_swap_buffers ();
+ }
+ dirty_region_reset ();
+
+ /* Scroll physical screen. */
+ grub_video_set_active_render_target (text_layer);
+ color = virtual_screen.bg_color;
+ grub_video_scroll (color, 0, -virtual_screen.normal_char_height
+ * virtual_screen.total_scroll);
+
+ grub_video_set_active_render_target (render_target);
+
+ }
+
+ was_scroll = virtual_screen.total_scroll;
+ virtual_screen.total_scroll = 0;
+
+ if (was_scroll > virtual_screen.rows)
+ was_scroll = virtual_screen.rows;
+
+ /* Draw shadow part. */
+ for (i = virtual_screen.rows - was_scroll;
+ i < virtual_screen.rows; i++)
+ for (j = 0; j < virtual_screen.columns; j++)
+ paint_char (j, i);
+
+ /* Draw cursor if visible. */
+ if (virtual_screen.cursor_state)
+ draw_cursor (1);
+}
+
+static void
+scroll_up (void)
+{
+ unsigned int i;
+
+ /* Clear first line in text buffer. */
+ for (i = 0; i < virtual_screen.columns; i++)
+ grub_unicode_destroy_glyph (&virtual_screen.text_buffer[i].code);
+
+ /* Scroll text buffer with one line to up. */
+ grub_memmove (virtual_screen.text_buffer,
+ virtual_screen.text_buffer + virtual_screen.columns,
+ sizeof (*virtual_screen.text_buffer)
+ * virtual_screen.columns
+ * (virtual_screen.rows - 1));
+
+ /* Clear last line in text buffer. */
+ for (i = virtual_screen.columns * (virtual_screen.rows - 1);
+ i < virtual_screen.columns * virtual_screen.rows;
+ i++)
+ clear_char (&(virtual_screen.text_buffer[i]));
+
+ virtual_screen.total_scroll++;
+}
+
+static void
+grub_gfxterm_putchar (struct grub_term_output *term,
+ const struct grub_unicode_glyph *c)
+{
+ if (!virtual_screen.functional)
+ return;
+
+ if (c->base == '\a')
+ /* FIXME */
+ return;
+
+ /* Erase current cursor, if any. */
+ if (virtual_screen.cursor_state)
+ draw_cursor (0);
+
+ if (c->base == '\b' || c->base == '\n' || c->base == '\r')
+ {
+ switch (c->base)
+ {
+ case '\b':
+ if (virtual_screen.cursor_x > 0)
+ virtual_screen.cursor_x--;
+ break;
+
+ case '\n':
+ if (virtual_screen.cursor_y >= virtual_screen.rows - 1)
+ scroll_up ();
+ else
+ virtual_screen.cursor_y++;
+ break;
+
+ case '\r':
+ virtual_screen.cursor_x = 0;
+ break;
+ }
+ }
+ else
+ {
+ struct grub_colored_char *p;
+ unsigned char char_width;
+
+ /* Calculate actual character width for glyph. This is number of
+ times of normal_font_width. */
+ char_width = grub_gfxterm_getcharwidth (term, c);
+
+ /* If we are about to exceed line length, wrap to next line. */
+ if (virtual_screen.cursor_x + char_width > virtual_screen.columns)
+ {
+ if (virtual_screen.cursor_y >= virtual_screen.rows - 1)
+ scroll_up ();
+ else
+ virtual_screen.cursor_y++;
+ }
+
+ /* Find position on virtual screen, and fill information. */
+ p = (virtual_screen.text_buffer +
+ virtual_screen.cursor_x +
+ virtual_screen.cursor_y * virtual_screen.columns);
+ grub_unicode_destroy_glyph (&p->code);
+ grub_unicode_set_glyph (&p->code, c);
+ grub_errno = GRUB_ERR_NONE;
+ p->fg_color = virtual_screen.fg_color;
+ p->bg_color = virtual_screen.bg_color;
+
+ /* If we have large glyph, add fixup info. */
+ if (char_width > 1)
+ {
+ unsigned i;
+
+ for (i = 1; i < char_width && p + i <
+ virtual_screen.text_buffer + virtual_screen.columns
+ * virtual_screen.rows; i++)
+ {
+ grub_unicode_destroy_glyph (&p[i].code);
+ p[i].code.base = 0;
+ }
+ }
+
+ /* Draw glyph. */
+ write_char ();
+
+ /* Make sure we scroll screen when needed and wrap line correctly. */
+ virtual_screen.cursor_x += char_width;
+ if (virtual_screen.cursor_x >= virtual_screen.columns)
+ {
+ virtual_screen.cursor_x = 0;
+
+ if (virtual_screen.cursor_y >= virtual_screen.rows - 1)
+ scroll_up ();
+ else
+ virtual_screen.cursor_y++;
+ }
+ }
+
+ /* Redraw cursor if it should be visible. */
+ /* Note: This will redraw the character as well, which means that the
+ above call to write_char is redundant when the cursor is showing. */
+ if (virtual_screen.cursor_state)
+ draw_cursor (1);
+}
+
+/* Use ASCII characters to determine normal character width. */
+static unsigned int
+calculate_normal_character_width (grub_font_t font)
+{
+ struct grub_font_glyph *glyph;
+ unsigned int width = 0;
+ unsigned int i;
+
+ /* Get properties of every printable ASCII character. */
+ for (i = 32; i < 127; i++)
+ {
+ glyph = grub_font_get_glyph (font, i);
+
+ /* Skip unknown characters. Should never happen on normal conditions. */
+ if (! glyph)
+ continue;
+
+ if (glyph->device_width > width)
+ width = glyph->device_width;
+ }
+ if (!width)
+ return 8;
+
+ return width;
+}
+
+static unsigned char
+calculate_character_width (struct grub_font_glyph *glyph)
+{
+ if (! glyph || glyph->device_width == 0)
+ return 1;
+
+ return (glyph->device_width
+ + (virtual_screen.normal_char_width - 1))
+ / virtual_screen.normal_char_width;
+}
+
+static grub_size_t
+grub_gfxterm_getcharwidth (struct grub_term_output *term __attribute__ ((unused)),
+ const struct grub_unicode_glyph *c)
+{
+ int dev_width;
+ dev_width = grub_font_get_constructed_device_width (virtual_screen.font, c);
+
+ if (dev_width == 0)
+ return 1;
+
+ return (dev_width + (virtual_screen.normal_char_width - 1))
+ / virtual_screen.normal_char_width;
+}
+
+static struct grub_term_coordinate
+grub_virtual_screen_getwh (struct grub_term_output *term __attribute__ ((unused)))
+{
+ return (struct grub_term_coordinate) { virtual_screen.columns, virtual_screen.rows };
+}
+
+static struct grub_term_coordinate
+grub_virtual_screen_getxy (struct grub_term_output *term __attribute__ ((unused)))
+{
+ return (struct grub_term_coordinate) { virtual_screen.cursor_x, virtual_screen.cursor_y };
+}
+
+static void
+grub_gfxterm_gotoxy (struct grub_term_output *term __attribute__ ((unused)),
+ struct grub_term_coordinate pos)
+{
+ if (pos.x >= virtual_screen.columns)
+ pos.x = virtual_screen.columns - 1;
+
+ if (pos.y >= virtual_screen.rows)
+ pos.y = virtual_screen.rows - 1;
+
+ /* Erase current cursor, if any. */
+ if (virtual_screen.cursor_state)
+ draw_cursor (0);
+
+ virtual_screen.cursor_x = pos.x;
+ virtual_screen.cursor_y = pos.y;
+
+ /* Draw cursor if visible. */
+ if (virtual_screen.cursor_state)
+ draw_cursor (1);
+}
+
+static void
+grub_virtual_screen_cls (struct grub_term_output *term __attribute__ ((unused)))
+{
+ grub_uint32_t i;
+
+ for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++)
+ clear_char (&(virtual_screen.text_buffer[i]));
+
+ virtual_screen.cursor_x = virtual_screen.cursor_y = 0;
+}
+
+static void
+grub_gfxterm_cls (struct grub_term_output *term)
+{
+ grub_video_color_t color;
+
+ /* Clear virtual screen. */
+ grub_virtual_screen_cls (term);
+
+ /* Clear text layer. */
+ grub_video_set_active_render_target (text_layer);
+ color = virtual_screen.bg_color;
+ grub_video_fill_rect (color, 0, 0,
+ virtual_screen.width, virtual_screen.height);
+ grub_video_set_active_render_target (render_target);
+
+ /* Mark virtual screen to be redrawn. */
+ dirty_region_add_virtualscreen ();
+
+ grub_gfxterm_refresh (term);
+}
+
+static void
+grub_virtual_screen_setcolorstate (struct grub_term_output *term __attribute__ ((unused)),
+ grub_term_color_state state)
+{
+ switch (state)
+ {
+ case GRUB_TERM_COLOR_STANDARD:
+ virtual_screen.term_color = virtual_screen.standard_color_setting;
+ break;
+
+ case GRUB_TERM_COLOR_NORMAL:
+ virtual_screen.term_color = grub_term_normal_color;
+ break;
+
+ case GRUB_TERM_COLOR_HIGHLIGHT:
+ virtual_screen.term_color = grub_term_highlight_color;
+ break;
+
+ default:
+ break;
+ }
+
+ /* Change color to virtual terminal. */
+ set_term_color (virtual_screen.term_color);
+}
+
+static void
+grub_gfxterm_setcursor (struct grub_term_output *term __attribute__ ((unused)),
+ int on)
+{
+ if (virtual_screen.cursor_state != on)
+ {
+ if (virtual_screen.cursor_state)
+ draw_cursor (0);
+ else
+ draw_cursor (1);
+
+ virtual_screen.cursor_state = on;
+ }
+}
+
+static void
+grub_gfxterm_refresh (struct grub_term_output *term __attribute__ ((unused)))
+{
+ real_scroll ();
+
+ /* Redraw only changed regions. */
+ dirty_region_redraw ();
+
+ grub_video_swap_buffers ();
+
+ if (window.double_repaint)
+ dirty_region_redraw ();
+ dirty_region_reset ();
+}
+
+static struct grub_term_output grub_video_term =
+ {
+ .name = "gfxterm",
+ .init = grub_gfxterm_term_init,
+ .fini = grub_gfxterm_term_fini,
+ .putchar = grub_gfxterm_putchar,
+ .getcharwidth = grub_gfxterm_getcharwidth,
+ .getwh = grub_virtual_screen_getwh,
+ .getxy = grub_virtual_screen_getxy,
+ .gotoxy = grub_gfxterm_gotoxy,
+ .cls = grub_gfxterm_cls,
+ .setcolorstate = grub_virtual_screen_setcolorstate,
+ .setcursor = grub_gfxterm_setcursor,
+ .refresh = grub_gfxterm_refresh,
+ .fullscreen = grub_gfxterm_fullscreen,
+ .flags = GRUB_TERM_CODE_TYPE_VISUAL_GLYPHS,
+ .progress_update_divisor = GRUB_PROGRESS_SLOW,
+ .next = 0
+ };
+
+void
+grub_gfxterm_video_update_color (void)
+{
+ struct grub_video_render_target *old_target;
+
+ grub_video_get_active_render_target (&old_target);
+ grub_video_set_active_render_target (text_layer);
+ virtual_screen.bg_color = grub_video_map_rgba_color (grub_gfxterm_background.default_bg_color);
+ grub_video_set_active_render_target (old_target);
+ virtual_screen.bg_color_display =
+ grub_video_map_rgba_color (grub_gfxterm_background.default_bg_color);
+}
+
+void
+grub_gfxterm_get_dimensions (unsigned *width, unsigned *height)
+{
+ *width = window.width;
+ *height = window.height;
+}
+
+GRUB_MOD_INIT(gfxterm)
+{
+ grub_term_register_output ("gfxterm", &grub_video_term);
+}
+
+GRUB_MOD_FINI(gfxterm)
+{
+ grub_term_unregister_output (&grub_video_term);
+}