| /* |
| * 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; |
| |
| /* 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); |
| } |