]> git.proxmox.com Git - grub2.git/blob - grub-core/term/gfxterm.c
bf9705abd880aa1f0ea218c83ed851711e50f4a9
[grub2.git] / grub-core / term / gfxterm.c
1 /*
2 * GRUB -- GRand Unified Bootloader
3 * Copyright (C) 2006,2007,2008,2009 Free Software Foundation, Inc.
4 *
5 * GRUB is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * GRUB is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include <grub/term.h>
20 #include <grub/types.h>
21 #include <grub/dl.h>
22 #include <grub/misc.h>
23 #include <grub/font.h>
24 #include <grub/mm.h>
25 #include <grub/env.h>
26 #include <grub/video.h>
27 #include <grub/gfxterm.h>
28 #include <grub/bitmap.h>
29 #include <grub/command.h>
30 #include <grub/extcmd.h>
31 #include <grub/bitmap_scale.h>
32 #include <grub/i18n.h>
33
34 #define DEFAULT_VIDEO_MODE "auto"
35 #define DEFAULT_BORDER_WIDTH 10
36
37 #define DEFAULT_STANDARD_COLOR 0x07
38
39 struct grub_dirty_region
40 {
41 int top_left_x;
42 int top_left_y;
43 int bottom_right_x;
44 int bottom_right_y;
45 };
46
47 struct grub_colored_char
48 {
49 /* An Unicode codepoint. */
50 struct grub_unicode_glyph *code;
51
52 /* Color values. */
53 grub_video_color_t fg_color;
54 grub_video_color_t bg_color;
55
56 /* The width of this character minus one. */
57 unsigned char width;
58
59 /* The column index of this character. */
60 unsigned char index;
61 };
62
63 struct grub_virtual_screen
64 {
65 /* Dimensions of the virtual screen in pixels. */
66 unsigned int width;
67 unsigned int height;
68
69 /* Offset in the display in pixels. */
70 unsigned int offset_x;
71 unsigned int offset_y;
72
73 /* TTY Character sizes in pixes. */
74 unsigned int normal_char_width;
75 unsigned int normal_char_height;
76
77 /* Virtual screen TTY size in characters. */
78 unsigned int columns;
79 unsigned int rows;
80
81 /* Current cursor location in characters. */
82 unsigned int cursor_x;
83 unsigned int cursor_y;
84
85 /* Current cursor state. */
86 int cursor_state;
87
88 /* Font settings. */
89 grub_font_t font;
90
91 /* Terminal color settings. */
92 grub_uint8_t standard_color_setting;
93 grub_uint8_t term_color;
94
95 /* Color settings. */
96 grub_video_color_t fg_color;
97 grub_video_color_t bg_color;
98 grub_video_color_t bg_color_display;
99
100 /* Text buffer for virtual screen. Contains (columns * rows) number
101 of entries. */
102 struct grub_colored_char *text_buffer;
103
104 int total_scroll;
105 };
106
107 struct grub_gfxterm_window
108 {
109 unsigned x;
110 unsigned y;
111 unsigned width;
112 unsigned height;
113 int double_repaint;
114 };
115
116 static struct grub_video_render_target *render_target;
117 void (*grub_gfxterm_decorator_hook) (void) = NULL;
118 static struct grub_gfxterm_window window;
119 static struct grub_virtual_screen virtual_screen;
120 static grub_gfxterm_repaint_callback_t repaint_callback;
121 static int repaint_scheduled = 0;
122 static int repaint_was_scheduled = 0;
123
124 static void destroy_window (void);
125
126 static struct grub_video_render_target *text_layer;
127
128 static unsigned int bitmap_width;
129 static unsigned int bitmap_height;
130 static struct grub_video_bitmap *bitmap;
131
132 static struct grub_dirty_region dirty_region;
133
134 static void dirty_region_reset (void);
135
136 static int dirty_region_is_empty (void);
137
138 static void dirty_region_add (int x, int y,
139 unsigned int width, unsigned int height);
140
141 static unsigned int calculate_normal_character_width (grub_font_t font);
142
143 static unsigned char calculate_character_width (struct grub_font_glyph *glyph);
144
145 static void grub_gfxterm_refresh (struct grub_term_output *term __attribute__ ((unused)));
146
147 static grub_ssize_t
148 grub_gfxterm_getcharwidth (struct grub_term_output *term __attribute__ ((unused)),
149 const struct grub_unicode_glyph *c);
150
151 static void
152 set_term_color (grub_uint8_t term_color)
153 {
154 struct grub_video_render_target *old_target;
155
156 /* Save previous target and switch to text layer. */
157 grub_video_get_active_render_target (&old_target);
158 grub_video_set_active_render_target (text_layer);
159
160 /* Map terminal color to text layer compatible video colors. */
161 virtual_screen.fg_color = grub_video_map_color(term_color & 0x0f);
162
163 /* Special case: use black as transparent color. */
164 if (((term_color >> 4) & 0x0f) == 0)
165 {
166 virtual_screen.bg_color = grub_video_map_rgba(0, 0, 0, 0);
167 }
168 else
169 {
170 virtual_screen.bg_color = grub_video_map_color((term_color >> 4) & 0x0f);
171 }
172
173 /* Restore previous target. */
174 grub_video_set_active_render_target (old_target);
175 }
176
177 static void
178 clear_char (struct grub_colored_char *c)
179 {
180 grub_free (c->code);
181 c->code = grub_unicode_glyph_from_code (' ');
182 if (!c->code)
183 grub_errno = GRUB_ERR_NONE;
184 c->fg_color = virtual_screen.fg_color;
185 c->bg_color = virtual_screen.bg_color;
186 c->width = 0;
187 c->index = 0;
188 }
189
190 static void
191 grub_virtual_screen_free (void)
192 {
193 /* If virtual screen has been allocated, free it. */
194 if (virtual_screen.text_buffer != 0)
195 grub_free (virtual_screen.text_buffer);
196
197 /* Reset virtual screen data. */
198 grub_memset (&virtual_screen, 0, sizeof (virtual_screen));
199
200 /* Free render targets. */
201 grub_video_delete_render_target (text_layer);
202 text_layer = 0;
203 }
204
205 static grub_err_t
206 grub_virtual_screen_setup (unsigned int x, unsigned int y,
207 unsigned int width, unsigned int height,
208 const char *font_name)
209 {
210 unsigned int i;
211
212 /* Free old virtual screen. */
213 grub_virtual_screen_free ();
214
215 /* Initialize with default data. */
216 virtual_screen.font = grub_font_get (font_name);
217 if (!virtual_screen.font)
218 return grub_error (GRUB_ERR_BAD_FONT,
219 "no font loaded");
220 virtual_screen.width = width;
221 virtual_screen.height = height;
222 virtual_screen.offset_x = x;
223 virtual_screen.offset_y = y;
224 virtual_screen.normal_char_width =
225 calculate_normal_character_width (virtual_screen.font);
226 virtual_screen.normal_char_height =
227 grub_font_get_max_char_height (virtual_screen.font);
228 virtual_screen.cursor_x = 0;
229 virtual_screen.cursor_y = 0;
230 virtual_screen.cursor_state = 1;
231 virtual_screen.total_scroll = 0;
232
233 /* Calculate size of text buffer. */
234 virtual_screen.columns = virtual_screen.width / virtual_screen.normal_char_width;
235 virtual_screen.rows = virtual_screen.height / virtual_screen.normal_char_height;
236
237 /* Allocate memory for text buffer. */
238 virtual_screen.text_buffer =
239 (struct grub_colored_char *) grub_malloc (virtual_screen.columns
240 * virtual_screen.rows
241 * sizeof (*virtual_screen.text_buffer));
242 if (grub_errno != GRUB_ERR_NONE)
243 return grub_errno;
244
245 /* Create new render target for text layer. */
246 grub_video_create_render_target (&text_layer,
247 virtual_screen.width,
248 virtual_screen.height,
249 GRUB_VIDEO_MODE_TYPE_RGB
250 | GRUB_VIDEO_MODE_TYPE_ALPHA);
251 if (grub_errno != GRUB_ERR_NONE)
252 return grub_errno;
253
254 /* As we want to have colors compatible with rendering target,
255 we can only have those after mode is initialized. */
256 grub_video_set_active_render_target (text_layer);
257
258 virtual_screen.standard_color_setting = DEFAULT_STANDARD_COLOR;
259
260 virtual_screen.term_color = GRUB_TERM_DEFAULT_NORMAL_COLOR;
261
262 set_term_color (virtual_screen.term_color);
263
264 grub_video_set_active_render_target (render_target);
265
266 virtual_screen.bg_color_display = grub_video_map_rgba(0, 0, 0, 0);
267
268 /* Clear out text buffer. */
269 for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++)
270 {
271 virtual_screen.text_buffer[i].code = 0;
272 clear_char (&(virtual_screen.text_buffer[i]));
273 }
274
275 return grub_errno;
276 }
277
278 void
279 grub_gfxterm_schedule_repaint (void)
280 {
281 repaint_scheduled = 1;
282 }
283
284 grub_err_t
285 grub_gfxterm_set_window (struct grub_video_render_target *target,
286 int x, int y, int width, int height,
287 int double_repaint,
288 const char *font_name, int border_width)
289 {
290 /* Clean up any prior instance. */
291 destroy_window ();
292
293 /* Set the render target. */
294 render_target = target;
295
296 /* Create virtual screen. */
297 if (grub_virtual_screen_setup (border_width, border_width,
298 width - 2 * border_width,
299 height - 2 * border_width,
300 font_name)
301 != GRUB_ERR_NONE)
302 {
303 return grub_errno;
304 }
305
306 /* Set window bounds. */
307 window.x = x;
308 window.y = y;
309 window.width = width;
310 window.height = height;
311 window.double_repaint = double_repaint;
312
313 dirty_region_reset ();
314 grub_gfxterm_schedule_repaint ();
315
316 return grub_errno;
317 }
318
319 grub_err_t
320 grub_gfxterm_fullscreen (void)
321 {
322 const char *font_name;
323 struct grub_video_mode_info mode_info;
324 grub_video_color_t color;
325 grub_err_t err;
326 int double_redraw;
327
328 err = grub_video_get_info (&mode_info);
329 /* Figure out what mode we ended up. */
330 if (err)
331 return err;
332
333 grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
334
335 double_redraw = mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED
336 && !(mode_info.mode_type & GRUB_VIDEO_MODE_TYPE_UPDATING_SWAP);
337
338 /* Make sure screen is black. */
339 color = grub_video_map_rgb (0, 0, 0);
340 grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
341 if (double_redraw)
342 {
343 grub_video_swap_buffers ();
344 grub_video_fill_rect (color, 0, 0, mode_info.width, mode_info.height);
345 }
346 bitmap = 0;
347
348 /* Select the font to use. */
349 font_name = grub_env_get ("gfxterm_font");
350 if (! font_name)
351 font_name = ""; /* Allow fallback to any font. */
352
353 grub_gfxterm_decorator_hook = NULL;
354
355 return grub_gfxterm_set_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
356 0, 0, mode_info.width, mode_info.height,
357 double_redraw,
358 font_name, DEFAULT_BORDER_WIDTH);
359 }
360
361 static grub_err_t
362 grub_gfxterm_term_init (struct grub_term_output *term __attribute__ ((unused)))
363 {
364 char *tmp;
365 grub_err_t err;
366 const char *modevar;
367
368 /* Parse gfxmode environment variable if set. */
369 modevar = grub_env_get ("gfxmode");
370 if (! modevar || *modevar == 0)
371 err = grub_video_set_mode (DEFAULT_VIDEO_MODE,
372 GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0);
373 else
374 {
375 tmp = grub_xasprintf ("%s;" DEFAULT_VIDEO_MODE, modevar);
376 if (!tmp)
377 return grub_errno;
378 err = grub_video_set_mode (tmp, GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0);
379 grub_free (tmp);
380 }
381
382 if (err)
383 return err;
384
385 err = grub_gfxterm_fullscreen ();
386 if (err)
387 grub_video_restore ();
388
389 return err;
390 }
391
392 static void
393 destroy_window (void)
394 {
395 if (bitmap)
396 {
397 grub_video_bitmap_destroy (bitmap);
398 bitmap = 0;
399 }
400
401 repaint_callback = 0;
402 grub_virtual_screen_free ();
403 }
404
405 static grub_err_t
406 grub_gfxterm_term_fini (struct grub_term_output *term __attribute__ ((unused)))
407 {
408 destroy_window ();
409 grub_video_restore ();
410
411 /* Clear error state. */
412 grub_errno = GRUB_ERR_NONE;
413 return GRUB_ERR_NONE;
414 }
415
416 static void
417 redraw_screen_rect (unsigned int x, unsigned int y,
418 unsigned int width, unsigned int height)
419 {
420 grub_video_color_t color;
421 grub_video_rect_t saved_view;
422
423 grub_video_set_active_render_target (render_target);
424 /* Save viewport and set it to our window. */
425 grub_video_get_viewport ((unsigned *) &saved_view.x,
426 (unsigned *) &saved_view.y,
427 (unsigned *) &saved_view.width,
428 (unsigned *) &saved_view.height);
429 grub_video_set_viewport (window.x, window.y, window.width, window.height);
430
431 if (bitmap)
432 {
433 /* Render bitmap as background. */
434 grub_video_blit_bitmap (bitmap, GRUB_VIDEO_BLIT_REPLACE, x, y,
435 x, y,
436 width, height);
437
438 /* If bitmap is smaller than requested blit area, use background
439 color. */
440 color = virtual_screen.bg_color_display;
441
442 /* Fill right side of the bitmap if needed. */
443 if ((x + width >= bitmap_width) && (y < bitmap_height))
444 {
445 int w = (x + width) - bitmap_width;
446 int h = height;
447 unsigned int tx = x;
448
449 if (y + height >= bitmap_height)
450 {
451 h = bitmap_height - y;
452 }
453
454 if (bitmap_width > tx)
455 {
456 tx = bitmap_width;
457 }
458
459 /* Render background layer. */
460 grub_video_fill_rect (color, tx, y, w, h);
461 }
462
463 /* Fill bottom side of the bitmap if needed. */
464 if (y + height >= bitmap_height)
465 {
466 int h = (y + height) - bitmap_height;
467 unsigned int ty = y;
468
469 if (bitmap_height > ty)
470 {
471 ty = bitmap_height;
472 }
473
474 /* Render background layer. */
475 grub_video_fill_rect (color, x, ty, width, h);
476 }
477
478 /* Render text layer as blended. */
479 grub_video_blit_render_target (text_layer, GRUB_VIDEO_BLIT_BLEND, x, y,
480 x - virtual_screen.offset_x,
481 y - virtual_screen.offset_y,
482 width, height);
483 }
484 else
485 {
486 /* Render background layer. */
487 color = virtual_screen.bg_color_display;
488 grub_video_fill_rect (color, x, y, width, height);
489
490 /* Render text layer as replaced (to get texts background color). */
491 grub_video_blit_render_target (text_layer, GRUB_VIDEO_BLIT_REPLACE, x, y,
492 x - virtual_screen.offset_x,
493 y - virtual_screen.offset_y,
494 width, height);
495 }
496
497 /* Restore saved viewport. */
498 grub_video_set_viewport (saved_view.x, saved_view.y,
499 saved_view.width, saved_view.height);
500 grub_video_set_active_render_target (render_target);
501
502 if (repaint_callback)
503 repaint_callback (x, y, width, height);
504 }
505
506 static void
507 dirty_region_reset (void)
508 {
509 dirty_region.top_left_x = -1;
510 dirty_region.top_left_y = -1;
511 dirty_region.bottom_right_x = -1;
512 dirty_region.bottom_right_y = -1;
513 repaint_was_scheduled = 0;
514 }
515
516 static int
517 dirty_region_is_empty (void)
518 {
519 if ((dirty_region.top_left_x == -1)
520 || (dirty_region.top_left_y == -1)
521 || (dirty_region.bottom_right_x == -1)
522 || (dirty_region.bottom_right_y == -1))
523 return 1;
524 return 0;
525 }
526
527 static void
528 dirty_region_add (int x, int y, unsigned int width, unsigned int height)
529 {
530 if ((width == 0) || (height == 0))
531 return;
532
533 if (repaint_scheduled)
534 {
535 x = virtual_screen.offset_x;
536 y = virtual_screen.offset_y;
537 width = virtual_screen.width;
538 height = virtual_screen.height;
539 repaint_scheduled = 0;
540 repaint_was_scheduled = 1;
541 }
542
543 if (dirty_region_is_empty ())
544 {
545 dirty_region.top_left_x = x;
546 dirty_region.top_left_y = y;
547 dirty_region.bottom_right_x = x + width - 1;
548 dirty_region.bottom_right_y = y + height - 1;
549 }
550 else
551 {
552 if (x < dirty_region.top_left_x)
553 dirty_region.top_left_x = x;
554 if (y < dirty_region.top_left_y)
555 dirty_region.top_left_y = y;
556 if ((x + (int)width - 1) > dirty_region.bottom_right_x)
557 dirty_region.bottom_right_x = x + width - 1;
558 if ((y + (int)height - 1) > dirty_region.bottom_right_y)
559 dirty_region.bottom_right_y = y + height - 1;
560 }
561 }
562
563 static void
564 dirty_region_add_virtualscreen (void)
565 {
566 /* Mark virtual screen as dirty. */
567 dirty_region_add (virtual_screen.offset_x, virtual_screen.offset_y,
568 virtual_screen.width, virtual_screen.height);
569 }
570
571
572 static void
573 dirty_region_redraw (void)
574 {
575 int x;
576 int y;
577 int width;
578 int height;
579
580 if (dirty_region_is_empty ())
581 return;
582
583 x = dirty_region.top_left_x;
584 y = dirty_region.top_left_y;
585
586 width = dirty_region.bottom_right_x - x + 1;
587 height = dirty_region.bottom_right_y - y + 1;
588
589 if (repaint_was_scheduled && grub_gfxterm_decorator_hook)
590 grub_gfxterm_decorator_hook ();
591
592 redraw_screen_rect (x, y, width, height);
593 }
594
595 static inline void
596 paint_char (unsigned cx, unsigned cy)
597 {
598 struct grub_colored_char *p;
599 struct grub_font_glyph *glyph;
600 grub_video_color_t color;
601 grub_video_color_t bgcolor;
602 unsigned int x;
603 unsigned int y;
604 int ascent;
605 unsigned int height;
606 unsigned int width;
607
608 if (cy + virtual_screen.total_scroll >= virtual_screen.rows)
609 return;
610
611 /* Find out active character. */
612 p = (virtual_screen.text_buffer
613 + cx + (cy * virtual_screen.columns));
614
615 p -= p->index;
616
617 /* Get glyph for character. */
618 glyph = grub_font_construct_glyph (virtual_screen.font, p->code);
619 if (!glyph)
620 {
621 grub_errno = GRUB_ERR_NONE;
622 return;
623 }
624 ascent = grub_font_get_ascent (virtual_screen.font);
625
626 width = virtual_screen.normal_char_width * calculate_character_width(glyph);
627 height = virtual_screen.normal_char_height;
628
629 color = p->fg_color;
630 bgcolor = p->bg_color;
631
632 x = cx * virtual_screen.normal_char_width;
633 y = (cy + virtual_screen.total_scroll) * virtual_screen.normal_char_height;
634
635 /* Render glyph to text layer. */
636 grub_video_set_active_render_target (text_layer);
637 grub_video_fill_rect (bgcolor, x, y, width, height);
638 grub_font_draw_glyph (glyph, color, x, y + ascent);
639 grub_video_set_active_render_target (render_target);
640
641 /* Mark character to be drawn. */
642 dirty_region_add (virtual_screen.offset_x + x, virtual_screen.offset_y + y,
643 width, height);
644 grub_free (glyph);
645 }
646
647 static inline void
648 write_char (void)
649 {
650 paint_char (virtual_screen.cursor_x, virtual_screen.cursor_y);
651 }
652
653 static inline void
654 draw_cursor (int show)
655 {
656 unsigned int x;
657 unsigned int y;
658 unsigned int width;
659 unsigned int height;
660 grub_video_color_t color;
661
662 write_char ();
663
664 if (!show)
665 return;
666
667 if (virtual_screen.cursor_y + virtual_screen.total_scroll
668 >= virtual_screen.rows)
669 return;
670
671 /* Determine cursor properties and position on text layer. */
672 x = virtual_screen.cursor_x * virtual_screen.normal_char_width;
673 width = virtual_screen.normal_char_width;
674 color = virtual_screen.fg_color;
675 y = ((virtual_screen.cursor_y + virtual_screen.total_scroll)
676 * virtual_screen.normal_char_height
677 + grub_font_get_ascent (virtual_screen.font));
678 height = 2;
679
680 /* Render cursor to text layer. */
681 grub_video_set_active_render_target (text_layer);
682 grub_video_fill_rect (color, x, y, width, height);
683 grub_video_set_active_render_target (render_target);
684
685 /* Mark cursor to be redrawn. */
686 dirty_region_add (virtual_screen.offset_x + x,
687 virtual_screen.offset_y + y,
688 width, height);
689 }
690
691 static void
692 real_scroll (void)
693 {
694 unsigned int i, j, was_scroll;
695 grub_video_color_t color;
696
697 if (!virtual_screen.total_scroll)
698 return;
699
700 /* If we have bitmap, re-draw screen, otherwise scroll physical screen too. */
701 if (bitmap)
702 {
703 /* Scroll physical screen. */
704 grub_video_set_active_render_target (text_layer);
705 color = virtual_screen.bg_color;
706 grub_video_scroll (color, 0, -virtual_screen.normal_char_height
707 * virtual_screen.total_scroll);
708
709 /* Mark virtual screen to be redrawn. */
710 dirty_region_add_virtualscreen ();
711 }
712 else
713 {
714 grub_video_rect_t saved_view;
715
716 /* Remove cursor. */
717 draw_cursor (0);
718
719 grub_video_set_active_render_target (render_target);
720
721 i = window.double_repaint ? 2 : 1;
722
723 color = virtual_screen.bg_color;
724
725 while (i--)
726 {
727 /* Save viewport and set it to our window. */
728 grub_video_get_viewport ((unsigned *) &saved_view.x,
729 (unsigned *) &saved_view.y,
730 (unsigned *) &saved_view.width,
731 (unsigned *) &saved_view.height);
732
733 grub_video_set_viewport (window.x, window.y, window.width,
734 window.height);
735
736 /* Clear new border area. */
737 grub_video_fill_rect (color,
738 virtual_screen.offset_x,
739 virtual_screen.offset_y,
740 virtual_screen.width,
741 virtual_screen.normal_char_height
742 * virtual_screen.total_scroll);
743
744 grub_video_set_active_render_target (render_target);
745 dirty_region_redraw ();
746
747 /* Scroll physical screen. */
748 grub_video_scroll (color, 0, -virtual_screen.normal_char_height
749 * virtual_screen.total_scroll);
750
751 /* Restore saved viewport. */
752 grub_video_set_viewport (saved_view.x, saved_view.y,
753 saved_view.width, saved_view.height);
754
755 if (i)
756 grub_video_swap_buffers ();
757 }
758 dirty_region_reset ();
759
760 /* Scroll physical screen. */
761 grub_video_set_active_render_target (text_layer);
762 color = virtual_screen.bg_color;
763 grub_video_scroll (color, 0, -virtual_screen.normal_char_height
764 * virtual_screen.total_scroll);
765
766 grub_video_set_active_render_target (render_target);
767
768 }
769
770 was_scroll = virtual_screen.total_scroll;
771 virtual_screen.total_scroll = 0;
772
773 if (was_scroll > virtual_screen.rows)
774 was_scroll = virtual_screen.rows;
775
776 /* Draw shadow part. */
777 for (i = virtual_screen.rows - was_scroll;
778 i < virtual_screen.rows; i++)
779 for (j = 0; j < virtual_screen.columns; j++)
780 paint_char (j, i);
781
782 /* Draw cursor if visible. */
783 if (virtual_screen.cursor_state)
784 draw_cursor (1);
785
786 if (repaint_callback)
787 repaint_callback (window.x, window.y, window.width, window.height);
788 }
789
790 static void
791 scroll_up (void)
792 {
793 unsigned int i;
794
795 /* Clear first line in text buffer. */
796 for (i = 0;
797 i < virtual_screen.columns;
798 i++)
799 {
800 virtual_screen.text_buffer[i].code = 0;
801 clear_char (&(virtual_screen.text_buffer[i]));
802 }
803
804 /* Scroll text buffer with one line to up. */
805 grub_memmove (virtual_screen.text_buffer,
806 virtual_screen.text_buffer + virtual_screen.columns,
807 sizeof (*virtual_screen.text_buffer)
808 * virtual_screen.columns
809 * (virtual_screen.rows - 1));
810
811 /* Clear last line in text buffer. */
812 for (i = virtual_screen.columns * (virtual_screen.rows - 1);
813 i < virtual_screen.columns * virtual_screen.rows;
814 i++)
815 {
816 virtual_screen.text_buffer[i].code = 0;
817 clear_char (&(virtual_screen.text_buffer[i]));
818 }
819
820 virtual_screen.total_scroll++;
821 }
822
823 static void
824 grub_gfxterm_putchar (struct grub_term_output *term,
825 const struct grub_unicode_glyph *c)
826 {
827 if (c->base == '\a')
828 /* FIXME */
829 return;
830
831 /* Erase current cursor, if any. */
832 if (virtual_screen.cursor_state)
833 draw_cursor (0);
834
835 if (c->base == '\b' || c->base == '\n' || c->base == '\r')
836 {
837 switch (c->base)
838 {
839 case '\b':
840 if (virtual_screen.cursor_x > 0)
841 virtual_screen.cursor_x--;
842 break;
843
844 case '\n':
845 if (virtual_screen.cursor_y >= virtual_screen.rows - 1)
846 scroll_up ();
847 else
848 virtual_screen.cursor_y++;
849 break;
850
851 case '\r':
852 virtual_screen.cursor_x = 0;
853 break;
854 }
855 }
856 else
857 {
858 struct grub_colored_char *p;
859 unsigned char char_width;
860
861 /* Calculate actual character width for glyph. This is number of
862 times of normal_font_width. */
863 char_width = grub_gfxterm_getcharwidth (term, c);
864
865 /* If we are about to exceed line length, wrap to next line. */
866 if (virtual_screen.cursor_x + char_width > virtual_screen.columns)
867 {
868 if (virtual_screen.cursor_y >= virtual_screen.rows - 1)
869 scroll_up ();
870 else
871 virtual_screen.cursor_y++;
872 }
873
874 /* Find position on virtual screen, and fill information. */
875 p = (virtual_screen.text_buffer +
876 virtual_screen.cursor_x +
877 virtual_screen.cursor_y * virtual_screen.columns);
878 grub_free (p->code);
879 p->code = grub_unicode_glyph_dup (c);
880 if (!p->code)
881 grub_errno = GRUB_ERR_NONE;
882 p->fg_color = virtual_screen.fg_color;
883 p->bg_color = virtual_screen.bg_color;
884 p->width = char_width - 1;
885 p->index = 0;
886
887 /* If we have large glyph, add fixup info. */
888 if (char_width > 1)
889 {
890 unsigned i;
891
892 for (i = 1; i < char_width; i++)
893 {
894 grub_free (p[i].code);
895 p[i].code = grub_unicode_glyph_from_code (' ');
896 if (!p[i].code)
897 grub_errno = GRUB_ERR_NONE;
898 p[i].width = char_width - 1;
899 p[i].index = i;
900 }
901 }
902
903 /* Draw glyph. */
904 write_char ();
905
906 /* Make sure we scroll screen when needed and wrap line correctly. */
907 virtual_screen.cursor_x += char_width;
908 if (virtual_screen.cursor_x >= virtual_screen.columns)
909 {
910 virtual_screen.cursor_x = 0;
911
912 if (virtual_screen.cursor_y >= virtual_screen.rows - 1)
913 scroll_up ();
914 else
915 virtual_screen.cursor_y++;
916 }
917 }
918
919 /* Redraw cursor if it should be visible. */
920 /* Note: This will redraw the character as well, which means that the
921 above call to write_char is redundant when the cursor is showing. */
922 if (virtual_screen.cursor_state)
923 draw_cursor (1);
924 }
925
926 /* Use ASCII characters to determine normal character width. */
927 static unsigned int
928 calculate_normal_character_width (grub_font_t font)
929 {
930 struct grub_font_glyph *glyph;
931 unsigned int width = 0;
932 unsigned int i;
933
934 /* Get properties of every printable ASCII character. */
935 for (i = 32; i < 127; i++)
936 {
937 glyph = grub_font_get_glyph (font, i);
938
939 /* Skip unknown characters. Should never happen on normal conditions. */
940 if (! glyph)
941 continue;
942
943 if (glyph->device_width > width)
944 width = glyph->device_width;
945 }
946
947 return width;
948 }
949
950 static unsigned char
951 calculate_character_width (struct grub_font_glyph *glyph)
952 {
953 if (! glyph || glyph->device_width == 0)
954 return 1;
955
956 return (glyph->device_width
957 + (virtual_screen.normal_char_width - 1))
958 / virtual_screen.normal_char_width;
959 }
960
961 static grub_ssize_t
962 grub_gfxterm_getcharwidth (struct grub_term_output *term __attribute__ ((unused)),
963 const struct grub_unicode_glyph *c)
964 {
965 int dev_width;
966 dev_width = grub_font_get_constructed_device_width (virtual_screen.font, c);
967
968 if (dev_width == 0)
969 return 1;
970
971 return (dev_width + (virtual_screen.normal_char_width - 1))
972 / virtual_screen.normal_char_width;
973 }
974
975 static grub_uint16_t
976 grub_virtual_screen_getwh (struct grub_term_output *term __attribute__ ((unused)))
977 {
978 return (virtual_screen.columns << 8) | virtual_screen.rows;
979 }
980
981 static grub_uint16_t
982 grub_virtual_screen_getxy (struct grub_term_output *term __attribute__ ((unused)))
983 {
984 return ((virtual_screen.cursor_x << 8) | virtual_screen.cursor_y);
985 }
986
987 static void
988 grub_gfxterm_gotoxy (struct grub_term_output *term __attribute__ ((unused)),
989 grub_uint8_t x, grub_uint8_t y)
990 {
991 if (x >= virtual_screen.columns)
992 x = virtual_screen.columns - 1;
993
994 if (y >= virtual_screen.rows)
995 y = virtual_screen.rows - 1;
996
997 /* Erase current cursor, if any. */
998 if (virtual_screen.cursor_state)
999 draw_cursor (0);
1000
1001 virtual_screen.cursor_x = x;
1002 virtual_screen.cursor_y = y;
1003
1004 /* Draw cursor if visible. */
1005 if (virtual_screen.cursor_state)
1006 draw_cursor (1);
1007 }
1008
1009 static void
1010 grub_virtual_screen_cls (struct grub_term_output *term __attribute__ ((unused)))
1011 {
1012 grub_uint32_t i;
1013
1014 for (i = 0; i < virtual_screen.columns * virtual_screen.rows; i++)
1015 clear_char (&(virtual_screen.text_buffer[i]));
1016
1017 virtual_screen.cursor_x = virtual_screen.cursor_y = 0;
1018 }
1019
1020 static void
1021 grub_gfxterm_cls (struct grub_term_output *term)
1022 {
1023 grub_video_color_t color;
1024
1025 /* Clear virtual screen. */
1026 grub_virtual_screen_cls (term);
1027
1028 /* Clear text layer. */
1029 grub_video_set_active_render_target (text_layer);
1030 color = virtual_screen.bg_color;
1031 grub_video_fill_rect (color, 0, 0,
1032 virtual_screen.width, virtual_screen.height);
1033 grub_video_set_active_render_target (render_target);
1034
1035 /* Mark virtual screen to be redrawn. */
1036 dirty_region_add_virtualscreen ();
1037
1038 grub_gfxterm_refresh (term);
1039 }
1040
1041 static void
1042 grub_virtual_screen_setcolorstate (struct grub_term_output *term,
1043 grub_term_color_state state)
1044 {
1045 switch (state)
1046 {
1047 case GRUB_TERM_COLOR_STANDARD:
1048 virtual_screen.term_color = virtual_screen.standard_color_setting;
1049 break;
1050
1051 case GRUB_TERM_COLOR_NORMAL:
1052 virtual_screen.term_color = term->normal_color;
1053 break;
1054
1055 case GRUB_TERM_COLOR_HIGHLIGHT:
1056 virtual_screen.term_color = term->highlight_color;
1057 break;
1058
1059 default:
1060 break;
1061 }
1062
1063 /* Change color to virtual terminal. */
1064 set_term_color (virtual_screen.term_color);
1065 }
1066
1067 static void
1068 grub_gfxterm_setcursor (struct grub_term_output *term __attribute__ ((unused)),
1069 int on)
1070 {
1071 if (virtual_screen.cursor_state != on)
1072 {
1073 if (virtual_screen.cursor_state)
1074 draw_cursor (0);
1075 else
1076 draw_cursor (1);
1077
1078 virtual_screen.cursor_state = on;
1079 }
1080 }
1081
1082 static void
1083 grub_gfxterm_refresh (struct grub_term_output *term __attribute__ ((unused)))
1084 {
1085 real_scroll ();
1086
1087 /* Redraw only changed regions. */
1088 dirty_region_redraw ();
1089
1090 grub_video_swap_buffers ();
1091
1092 if (window.double_repaint)
1093 dirty_region_redraw ();
1094 dirty_region_reset ();
1095 }
1096
1097 void
1098 grub_gfxterm_set_repaint_callback (grub_gfxterm_repaint_callback_t func)
1099 {
1100 repaint_callback = func;
1101 }
1102
1103 /* Option array indices. */
1104 #define BACKGROUND_CMD_ARGINDEX_MODE 0
1105
1106 static const struct grub_arg_option background_image_cmd_options[] =
1107 {
1108 {"mode", 'm', 0, "Background image mode.", "stretch|normal",
1109 ARG_TYPE_STRING},
1110 {0, 0, 0, 0, 0, 0}
1111 };
1112
1113 static grub_err_t
1114 grub_gfxterm_background_image_cmd (grub_extcmd_t cmd __attribute__ ((unused)),
1115 int argc,
1116 char **args)
1117 {
1118 struct grub_arg_list *state = cmd->state;
1119
1120 /* Check that we have video adapter active. */
1121 if (grub_video_get_info(NULL) != GRUB_ERR_NONE)
1122 return grub_errno;
1123
1124 /* Destroy existing background bitmap if loaded. */
1125 if (bitmap)
1126 {
1127 grub_video_bitmap_destroy (bitmap);
1128 bitmap = 0;
1129
1130 /* Mark whole screen as dirty. */
1131 dirty_region_add (0, 0, window.width, window.height);
1132 }
1133
1134 /* If filename was provided, try to load that. */
1135 if (argc >= 1)
1136 {
1137 /* Try to load new one. */
1138 grub_video_bitmap_load (&bitmap, args[0]);
1139 if (grub_errno != GRUB_ERR_NONE)
1140 return grub_errno;
1141
1142 /* Determine if the bitmap should be scaled to fit the screen. */
1143 if (!state[BACKGROUND_CMD_ARGINDEX_MODE].set
1144 || grub_strcmp (state[BACKGROUND_CMD_ARGINDEX_MODE].arg,
1145 "stretch") == 0)
1146 {
1147 if (window.width != grub_video_bitmap_get_width (bitmap)
1148 || window.height != grub_video_bitmap_get_height (bitmap))
1149 {
1150 struct grub_video_bitmap *scaled_bitmap;
1151 grub_video_bitmap_create_scaled (&scaled_bitmap,
1152 window.width,
1153 window.height,
1154 bitmap,
1155 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
1156 if (grub_errno == GRUB_ERR_NONE)
1157 {
1158 /* Replace the original bitmap with the scaled one. */
1159 grub_video_bitmap_destroy (bitmap);
1160 bitmap = scaled_bitmap;
1161 }
1162 }
1163 }
1164
1165 /* If bitmap was loaded correctly, display it. */
1166 if (bitmap)
1167 {
1168 /* Determine bitmap dimensions. */
1169 bitmap_width = grub_video_bitmap_get_width (bitmap);
1170 bitmap_height = grub_video_bitmap_get_height (bitmap);
1171
1172 /* Mark whole screen as dirty. */
1173 dirty_region_add (0, 0, window.width, window.height);
1174 }
1175 }
1176
1177 /* All was ok. */
1178 grub_errno = GRUB_ERR_NONE;
1179 return grub_errno;
1180 }
1181
1182 static struct grub_term_output grub_video_term =
1183 {
1184 .name = "gfxterm",
1185 .init = grub_gfxterm_term_init,
1186 .fini = grub_gfxterm_term_fini,
1187 .putchar = grub_gfxterm_putchar,
1188 .getcharwidth = grub_gfxterm_getcharwidth,
1189 .getwh = grub_virtual_screen_getwh,
1190 .getxy = grub_virtual_screen_getxy,
1191 .gotoxy = grub_gfxterm_gotoxy,
1192 .cls = grub_gfxterm_cls,
1193 .setcolorstate = grub_virtual_screen_setcolorstate,
1194 .setcursor = grub_gfxterm_setcursor,
1195 .refresh = grub_gfxterm_refresh,
1196 .flags = GRUB_TERM_CODE_TYPE_VISUAL_GLYPHS,
1197 .normal_color = GRUB_TERM_DEFAULT_NORMAL_COLOR,
1198 .highlight_color = GRUB_TERM_DEFAULT_HIGHLIGHT_COLOR,
1199 .next = 0
1200 };
1201
1202 static grub_extcmd_t background_image_cmd_handle;
1203
1204 GRUB_MOD_INIT(gfxterm)
1205 {
1206 grub_term_register_output ("gfxterm", &grub_video_term);
1207 background_image_cmd_handle =
1208 grub_register_extcmd ("background_image",
1209 grub_gfxterm_background_image_cmd,
1210 GRUB_COMMAND_FLAG_BOTH,
1211 N_("[-m (stretch|normal)] FILE"),
1212 N_("Load background image for active terminal."),
1213 background_image_cmd_options);
1214 }
1215
1216 GRUB_MOD_FINI(gfxterm)
1217 {
1218 grub_unregister_extcmd (background_image_cmd_handle);
1219 grub_term_unregister_output (&grub_video_term);
1220 }