1 /* view.c - Graphical menu interface MVC view. */
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008 Free Software Foundation, Inc.
6 * GRUB is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GRUB is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
20 #include <grub/types.h>
21 #include <grub/file.h>
22 #include <grub/misc.h>
26 #include <grub/normal.h>
27 #include <grub/video.h>
28 #include <grub/gui_string_util.h>
29 #include <grub/gfxterm.h>
30 #include <grub/bitmap.h>
31 #include <grub/bitmap_scale.h>
32 #include <grub/term.h>
33 #include <grub/gfxwidgets.h>
34 #include <grub/time.h>
35 #include <grub/menu.h>
36 #include <grub/menu_viewer.h>
37 #include <grub/gfxmenu_view.h>
39 #include <grub/icon_manager.h>
41 /* The component ID identifying GUI components to be updated as the timeout
43 #define TIMEOUT_COMPONENT_ID "__timeout__"
45 static grub_gfxmenu_view_t term_view
;
47 static void init_terminal (grub_gfxmenu_view_t view
);
48 static void destroy_terminal (void);
49 static grub_err_t
set_graphics_mode (void);
50 static grub_err_t
set_text_mode (void);
52 /* Create a new view object, loading the theme specified by THEME_PATH and
53 associating MODEL with the view. */
55 grub_gfxmenu_view_new (const char *theme_path
, grub_gfxmenu_model_t model
)
57 grub_gfxmenu_view_t view
;
59 struct grub_video_mode_info mode_info
;
61 view
= grub_malloc (sizeof (*view
));
66 grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY
);
67 grub_video_get_viewport ((unsigned *) &view
->screen
.x
,
68 (unsigned *) &view
->screen
.y
,
69 (unsigned *) &view
->screen
.width
,
70 (unsigned *) &view
->screen
.height
);
72 err
= grub_video_get_info (&mode_info
);
79 view
->double_repaint
= (mode_info
.mode_type
80 & GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED
)
81 && !(mode_info
.mode_type
& GRUB_VIDEO_MODE_TYPE_UPDATING_SWAP
);
84 /* Clear the screen; there may be garbage left over in video memory, and
85 loading the menu style (particularly the background) can take a while. */
86 grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
87 view
->screen
.x
, view
->screen
.y
,
88 view
->screen
.width
, view
->screen
.height
);
89 grub_video_swap_buffers ();
91 grub_font_t default_font
;
92 grub_gui_color_t default_fg_color
;
93 grub_gui_color_t default_bg_color
;
95 default_font
= grub_font_get ("Helvetica 12");
96 default_fg_color
= grub_gui_color_rgb (0, 0, 0);
97 default_bg_color
= grub_gui_color_rgb (255, 255, 255);
102 view
->title_font
= default_font
;
103 view
->message_font
= default_font
;
104 view
->terminal_font_name
= grub_strdup ("Fixed 10");
105 view
->title_color
= default_fg_color
;
106 view
->message_color
= default_bg_color
;
107 view
->message_bg_color
= default_fg_color
;
108 view
->desktop_image
= 0;
109 view
->desktop_color
= default_bg_color
;
110 view
->terminal_box
= grub_gfxmenu_create_box (0, 0);
111 view
->title_text
= grub_strdup ("GRUB Boot Menu");
112 view
->progress_message_text
= 0;
113 view
->theme_path
= 0;
114 view
->last_seconds_remaining
= -2;
116 /* Set the timeout bar's frame. */
117 view
->progress_message_frame
.width
= view
->screen
.width
* 4 / 5;
118 view
->progress_message_frame
.height
= 50;
119 view
->progress_message_frame
.x
= view
->screen
.x
120 + (view
->screen
.width
- view
->progress_message_frame
.width
) / 2;
121 view
->progress_message_frame
.y
= view
->screen
.y
122 + view
->screen
.height
- 90 - 20 - view
->progress_message_frame
.height
;
124 if (grub_gfxmenu_view_load_theme (view
, theme_path
) != 0)
126 grub_gfxmenu_view_destroy (view
);
130 init_terminal (view
);
135 /* Destroy the view object. All used memory is freed. */
137 grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view
)
141 grub_video_bitmap_destroy (view
->desktop_image
);
142 if (view
->terminal_box
)
143 view
->terminal_box
->destroy (view
->terminal_box
);
144 grub_free (view
->terminal_font_name
);
145 grub_free (view
->title_text
);
146 grub_free (view
->progress_message_text
);
147 grub_free (view
->theme_path
);
149 view
->canvas
->component
.ops
->destroy (view
->canvas
);
156 /* Sets MESSAGE as the progress message for the view.
157 MESSAGE can be 0, in which case no message is displayed. */
159 set_progress_message (grub_gfxmenu_view_t view
, const char *message
)
161 grub_free (view
->progress_message_text
);
163 view
->progress_message_text
= grub_strdup (message
);
165 view
->progress_message_text
= 0;
169 redraw_background (grub_gfxmenu_view_t view
,
170 const grub_video_rect_t
*bounds
)
172 if (view
->desktop_image
)
174 struct grub_video_bitmap
*img
= view
->desktop_image
;
175 grub_video_blit_bitmap (img
, GRUB_VIDEO_BLIT_REPLACE
,
176 bounds
->x
, bounds
->y
,
177 bounds
->x
- view
->screen
.x
,
178 bounds
->y
- view
->screen
.y
,
179 bounds
->width
, bounds
->height
);
183 grub_video_fill_rect (grub_gui_map_color (view
->desktop_color
),
184 bounds
->x
, bounds
->y
,
185 bounds
->width
, bounds
->height
);
190 draw_title (grub_gfxmenu_view_t view
)
192 if (! view
->title_text
)
195 /* Center the title. */
196 int title_width
= grub_font_get_string_width (view
->title_font
,
198 int x
= (view
->screen
.width
- title_width
) / 2;
199 int y
= 40 + grub_font_get_ascent (view
->title_font
);
200 grub_font_draw_string (view
->title_text
,
202 grub_gui_map_color (view
->title_color
),
206 struct progress_value_data
216 update_timeout_visit (grub_gui_component_t component
,
219 struct progress_value_data
*pv
;
220 pv
= (struct progress_value_data
*) userdata
;
222 component
->ops
->set_property (component
, "visible", pv
->visible
);
223 component
->ops
->set_property (component
, "start", pv
->start
);
224 component
->ops
->set_property (component
, "end", pv
->end
);
225 component
->ops
->set_property (component
, "value", pv
->value
);
226 component
->ops
->set_property (component
, "text", pv
->text
);
231 update_timeout (grub_gfxmenu_view_t view
, int is_init
)
239 struct progress_value_data pv
;
240 int seconds_remaining_rounded_up
;
242 auto void redraw_timeout_visit (grub_gui_component_t component
,
243 void *userdata
__attribute__ ((unused
)));
245 auto void redraw_timeout_visit (grub_gui_component_t component
,
246 void *userdata
__attribute__ ((unused
)))
248 grub_video_rect_t bounds
;
249 component
->ops
->get_bounds (component
, &bounds
);
250 grub_gfxmenu_view_redraw (view
, &bounds
);
253 timeout
= grub_gfxmenu_model_get_timeout_ms (view
->model
);
256 remaining
= grub_gfxmenu_model_get_timeout_remaining_ms (view
->model
);
257 seconds_remaining_rounded_up
= (remaining
+ 999) / 1000;
261 seconds_remaining_rounded_up
= -1;
265 if (view
->last_seconds_remaining
== seconds_remaining_rounded_up
&& !is_init
)
268 view
->last_seconds_remaining
= seconds_remaining_rounded_up
;
270 pv
.visible
= timeout
> 0 ? "true" : "false";
271 grub_sprintf (startbuf
, "%d", -timeout
);
274 grub_sprintf (valuebuf
, "%d", remaining
> 0 ? -remaining
: 0);
277 grub_sprintf (msgbuf
,
278 "The highlighted entry will be booted automatically in %d s.",
279 seconds_remaining_rounded_up
);
282 grub_gui_find_by_id ((grub_gui_component_t
) view
->canvas
,
283 TIMEOUT_COMPONENT_ID
, update_timeout_visit
, &pv
);
286 grub_gui_find_by_id ((grub_gui_component_t
) view
->canvas
,
287 TIMEOUT_COMPONENT_ID
, redraw_timeout_visit
, &pv
);
288 grub_video_swap_buffers ();
289 if (view
->double_repaint
)
290 grub_gui_find_by_id ((grub_gui_component_t
) view
->canvas
,
291 TIMEOUT_COMPONENT_ID
, redraw_timeout_visit
, &pv
);
296 grub_gfxmenu_redraw_timeout (grub_gfxmenu_view_t view
)
298 update_timeout (view
, 0);
302 update_menu_visit (grub_gui_component_t component
,
305 grub_gfxmenu_view_t view
;
307 if (component
->ops
->is_instance (component
, "list"))
309 grub_gui_list_t list
= (grub_gui_list_t
) component
;
310 list
->ops
->set_view_info (list
, view
->theme_path
, view
->model
);
314 /* Update any boot menu components with the current menu model and
317 update_menu_components (grub_gfxmenu_view_t view
)
319 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
320 update_menu_visit
, view
);
324 draw_message (grub_gfxmenu_view_t view
)
326 char *text
= view
->progress_message_text
;
327 grub_video_rect_t f
= view
->progress_message_frame
;
331 grub_font_t font
= view
->message_font
;
332 grub_video_color_t color
= grub_gui_map_color (view
->message_color
);
335 grub_video_fill_rect (color
,
336 f
.x
-1, f
.y
-1, f
.width
+2, f
.height
+2);
338 grub_video_fill_rect (grub_gui_map_color (view
->message_bg_color
),
339 f
.x
, f
.y
, f
.width
, f
.height
);
341 /* Center the text. */
342 int text_width
= grub_font_get_string_width (font
, text
);
343 int x
= f
.x
+ (f
.width
- text_width
) / 2;
344 int y
= (f
.y
+ (f
.height
- grub_font_get_descent (font
)) / 2
345 + grub_font_get_ascent (font
) / 2);
346 grub_font_draw_string (text
, font
, color
, x
, y
);
350 grub_gfxmenu_view_redraw (grub_gfxmenu_view_t view
,
351 const grub_video_rect_t
*region
)
353 grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY
);
355 redraw_background (view
, region
);
357 view
->canvas
->component
.ops
->paint (view
->canvas
, region
);
359 if (grub_video_have_common_points (&view
->progress_message_frame
, region
))
364 grub_gfxmenu_view_draw (grub_gfxmenu_view_t view
)
366 update_timeout (view
, 1);
367 update_menu_components (view
);
369 grub_gfxmenu_view_redraw (view
, &view
->screen
);
370 grub_video_swap_buffers ();
371 if (view
->double_repaint
)
372 grub_gfxmenu_view_redraw (view
, &view
->screen
);
376 redraw_menu_visit (grub_gui_component_t component
,
379 grub_gfxmenu_view_t view
;
381 if (component
->ops
->is_instance (component
, "list"))
383 grub_gui_list_t list
;
384 grub_video_rect_t bounds
;
386 list
= (grub_gui_list_t
) component
;
387 component
->ops
->get_bounds (component
, &bounds
);
388 grub_gfxmenu_view_redraw (view
, &bounds
);
393 grub_gfxmenu_redraw_menu (grub_gfxmenu_view_t view
)
395 update_menu_components (view
);
397 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
398 redraw_menu_visit
, view
);
399 grub_video_swap_buffers ();
400 if (view
->double_repaint
)
402 grub_gui_iterate_recursively ((grub_gui_component_t
) view
->canvas
,
403 redraw_menu_visit
, view
);
408 set_graphics_mode (void)
410 const char *modestr
= grub_env_get ("gfxmode");
411 if (!modestr
|| !modestr
[0])
413 return grub_video_set_mode (modestr
, GRUB_VIDEO_MODE_TYPE_PURE_TEXT
, 0);
419 return grub_video_restore ();
422 static int term_target_width
;
423 static int term_target_height
;
424 static int term_initialized
;
425 static grub_term_output_t term_original
;
428 draw_terminal_box (grub_gfxmenu_view_t view
)
430 grub_gfxmenu_box_t term_box
;
434 term_box
= term_view
->terminal_box
;
438 termx
= term_view
->screen
.x
+ term_view
->screen
.width
* (10 - 7) / 10 / 2;
439 termy
= term_view
->screen
.y
+ term_view
->screen
.height
* (10 - 7) / 10 / 2;
441 term_box
->set_content_size (term_box
, term_target_width
,
444 term_box
->draw (term_box
,
445 termx
- term_box
->get_left_pad (term_box
),
446 termy
- term_box
->get_top_pad (term_box
));
447 grub_video_swap_buffers ();
448 if (view
->double_repaint
)
449 term_box
->draw (term_box
,
450 termx
- term_box
->get_left_pad (term_box
),
451 termy
- term_box
->get_top_pad (term_box
));
455 init_terminal (grub_gfxmenu_view_t view
)
460 term_original
= grub_term_get_current_output ();
462 term_target_width
= view
->screen
.width
* 7 / 10;
463 term_target_height
= view
->screen
.height
* 7 / 10;
465 termx
= view
->screen
.x
+ view
->screen
.width
* (10 - 7) / 10 / 2;
466 termy
= view
->screen
.y
+ view
->screen
.height
* (10 - 7) / 10 / 2;
468 /* Note: currently there is no API for changing the gfxterm font
469 on the fly, so whatever font the initially loaded theme specifies
470 will be permanent. */
471 grub_gfxterm_init_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY
, termx
, termy
,
472 term_target_width
, term_target_height
,
473 view
->double_repaint
, view
->terminal_font_name
, 3);
474 if (grub_errno
!= GRUB_ERR_NONE
)
476 term_initialized
= 1;
480 grub_term_set_current_output (grub_gfxterm_get_term ());
484 static void destroy_terminal (void)
486 if (term_initialized
)
487 grub_gfxterm_destroy_window ();
489 grub_term_set_current_output (term_original
);
494 notify_booting (grub_menu_entry_t entry
, void *userdata
)
496 grub_gfxmenu_view_t view
= (grub_gfxmenu_view_t
) userdata
;
498 char *s
= grub_malloc (100 + grub_strlen (entry
->title
));
502 grub_sprintf (s
, "Booting '%s'", entry
->title
);
503 set_progress_message (view
, s
);
505 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
506 grub_video_swap_buffers ();
507 if (view
->double_repaint
)
508 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
512 notify_fallback (grub_menu_entry_t entry
, void *userdata
)
514 grub_gfxmenu_view_t view
= (grub_gfxmenu_view_t
) userdata
;
516 char *s
= grub_malloc (100 + grub_strlen (entry
->title
));
520 grub_sprintf (s
, "Falling back to '%s'", entry
->title
);
521 set_progress_message (view
, s
);
523 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
524 grub_video_swap_buffers ();
525 if (view
->double_repaint
)
526 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
530 notify_execution_failure (void *userdata
__attribute__ ((unused
)))
535 static struct grub_menu_execute_callback execute_callback
=
537 .notify_booting
= notify_booting
,
538 .notify_fallback
= notify_fallback
,
539 .notify_failure
= notify_execution_failure
543 grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view
,
544 grub_menu_entry_t entry
)
546 draw_terminal_box (view
);
548 grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view
->model
),
549 entry
, &execute_callback
, (void *) view
);
551 if (set_graphics_mode () != GRUB_ERR_NONE
)
552 return 0; /* Failure. */
554 /* If we returned, there was a failure. */
555 set_progress_message (view
,
556 "Unable to automatically boot. "
557 "Press SPACE to continue.");
558 grub_gfxmenu_view_draw (view
);
559 while (GRUB_TERM_ASCII_CHAR(grub_getkey ()) != ' ')
561 /* Wait for SPACE to be pressed. */
564 set_progress_message (view
, 0); /* Clear the message. */
566 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
567 grub_video_swap_buffers ();
568 if (view
->double_repaint
)
569 grub_gfxmenu_view_redraw (view
, &view
->progress_message_frame
);
575 grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view
,
576 grub_menu_entry_t entry
)
578 draw_terminal_box (view
);
580 grub_menu_execute_entry (entry
);
581 if (grub_errno
!= GRUB_ERR_NONE
)
582 grub_wait_after_message ();
584 if (set_graphics_mode () != GRUB_ERR_NONE
)
585 return 0; /* Failure. */
587 grub_gfxmenu_view_draw (view
);
592 grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view
)
594 draw_terminal_box (view
);
595 grub_cmdline_run (1);
596 grub_gfxmenu_view_draw (view
);