]> git.proxmox.com Git - grub2.git/blob - gfxmenu/view.c
Compute list size automatically
[grub2.git] / gfxmenu / view.c
1 /* view.c - Graphical menu interface MVC view. */
2 /*
3 * GRUB -- GRand Unified Bootloader
4 * Copyright (C) 2008 Free Software Foundation, Inc.
5 *
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.
10 *
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.
15 *
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/>.
18 */
19
20 #include <grub/types.h>
21 #include <grub/file.h>
22 #include <grub/misc.h>
23 #include <grub/mm.h>
24 #include <grub/err.h>
25 #include <grub/dl.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>
38 #include <grub/gui.h>
39 #include <grub/icon_manager.h>
40
41 /* The component ID identifying GUI components to be updated as the timeout
42 status changes. */
43 #define TIMEOUT_COMPONENT_ID "__timeout__"
44
45 static grub_gfxmenu_view_t term_view;
46
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);
51
52 /* Create a new view object, loading the theme specified by THEME_PATH and
53 associating MODEL with the view. */
54 grub_gfxmenu_view_t
55 grub_gfxmenu_view_new (const char *theme_path, grub_gfxmenu_model_t model)
56 {
57 grub_gfxmenu_view_t view;
58 grub_err_t err;
59 struct grub_video_mode_info mode_info;
60
61 view = grub_malloc (sizeof (*view));
62 if (! view)
63 return 0;
64
65 set_graphics_mode ();
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);
71
72 err = grub_video_get_info (&mode_info);
73 if (err)
74 {
75 grub_free (view);
76 return 0;
77 }
78 else
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);
82
83
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 ();
90
91 grub_font_t default_font;
92 grub_gui_color_t default_fg_color;
93 grub_gui_color_t default_bg_color;
94
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);
98
99 view->model = model;
100 view->canvas = 0;
101
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;
115
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;
123
124 if (grub_gfxmenu_view_load_theme (view, theme_path) != 0)
125 {
126 grub_gfxmenu_view_destroy (view);
127 return 0;
128 }
129
130 init_terminal (view);
131
132 return view;
133 }
134
135 /* Destroy the view object. All used memory is freed. */
136 void
137 grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
138 {
139 if (!view)
140 return;
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);
148 if (view->canvas)
149 view->canvas->component.ops->destroy (view->canvas);
150 grub_free (view);
151
152 set_text_mode ();
153 destroy_terminal ();
154 }
155
156 /* Sets MESSAGE as the progress message for the view.
157 MESSAGE can be 0, in which case no message is displayed. */
158 static void
159 set_progress_message (grub_gfxmenu_view_t view, const char *message)
160 {
161 grub_free (view->progress_message_text);
162 if (message)
163 view->progress_message_text = grub_strdup (message);
164 else
165 view->progress_message_text = 0;
166 }
167
168 static void
169 redraw_background (grub_gfxmenu_view_t view,
170 const grub_video_rect_t *bounds)
171 {
172 if (view->desktop_image)
173 {
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);
180 }
181 else
182 {
183 grub_video_fill_rect (grub_gui_map_color (view->desktop_color),
184 bounds->x, bounds->y,
185 bounds->width, bounds->height);
186 }
187 }
188
189 static void
190 draw_title (grub_gfxmenu_view_t view)
191 {
192 if (! view->title_text)
193 return;
194
195 /* Center the title. */
196 int title_width = grub_font_get_string_width (view->title_font,
197 view->title_text);
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,
201 view->title_font,
202 grub_gui_map_color (view->title_color),
203 x, y);
204 }
205
206 struct progress_value_data
207 {
208 const char *visible;
209 const char *start;
210 const char *end;
211 const char *value;
212 const char *text;
213 };
214
215 static void
216 update_timeout_visit (grub_gui_component_t component,
217 void *userdata)
218 {
219 struct progress_value_data *pv;
220 pv = (struct progress_value_data *) userdata;
221
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);
227 }
228
229
230 static inline void
231 update_timeout (grub_gfxmenu_view_t view, int is_init)
232 {
233 char startbuf[20];
234 char valuebuf[20];
235 char msgbuf[120];
236
237 int timeout;
238 int remaining;
239 struct progress_value_data pv;
240 int seconds_remaining_rounded_up;
241
242 auto void redraw_timeout_visit (grub_gui_component_t component,
243 void *userdata __attribute__ ((unused)));
244
245 auto void redraw_timeout_visit (grub_gui_component_t component,
246 void *userdata __attribute__ ((unused)))
247 {
248 grub_video_rect_t bounds;
249 component->ops->get_bounds (component, &bounds);
250 grub_gfxmenu_view_redraw (view, &bounds);
251 }
252
253 timeout = grub_gfxmenu_model_get_timeout_ms (view->model);
254 if (timeout > 0)
255 {
256 remaining = grub_gfxmenu_model_get_timeout_remaining_ms (view->model);
257 seconds_remaining_rounded_up = (remaining + 999) / 1000;
258 }
259 else
260 {
261 seconds_remaining_rounded_up = -1;
262 remaining = -1;
263 }
264
265 if (view->last_seconds_remaining == seconds_remaining_rounded_up && !is_init)
266 return;
267
268 view->last_seconds_remaining = seconds_remaining_rounded_up;
269
270 pv.visible = timeout > 0 ? "true" : "false";
271 grub_sprintf (startbuf, "%d", -timeout);
272 pv.start = startbuf;
273 pv.end = "0";
274 grub_sprintf (valuebuf, "%d", remaining > 0 ? -remaining : 0);
275 pv.value = valuebuf;
276
277 grub_sprintf (msgbuf,
278 "The highlighted entry will be booted automatically in %d s.",
279 seconds_remaining_rounded_up);
280 pv.text = msgbuf;
281
282 grub_gui_find_by_id ((grub_gui_component_t) view->canvas,
283 TIMEOUT_COMPONENT_ID, update_timeout_visit, &pv);
284 if (!is_init)
285 {
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);
292 }
293 }
294
295 void
296 grub_gfxmenu_redraw_timeout (grub_gfxmenu_view_t view)
297 {
298 update_timeout (view, 0);
299 }
300
301 static void
302 update_menu_visit (grub_gui_component_t component,
303 void *userdata)
304 {
305 grub_gfxmenu_view_t view;
306 view = userdata;
307 if (component->ops->is_instance (component, "list"))
308 {
309 grub_gui_list_t list = (grub_gui_list_t) component;
310 list->ops->set_view_info (list, view->theme_path, view->model);
311 }
312 }
313
314 /* Update any boot menu components with the current menu model and
315 theme path. */
316 static void
317 update_menu_components (grub_gfxmenu_view_t view)
318 {
319 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
320 update_menu_visit, view);
321 }
322
323 static void
324 draw_message (grub_gfxmenu_view_t view)
325 {
326 char *text = view->progress_message_text;
327 grub_video_rect_t f = view->progress_message_frame;
328 if (! text)
329 return;
330
331 grub_font_t font = view->message_font;
332 grub_video_color_t color = grub_gui_map_color (view->message_color);
333
334 /* Border. */
335 grub_video_fill_rect (color,
336 f.x-1, f.y-1, f.width+2, f.height+2);
337 /* Fill. */
338 grub_video_fill_rect (grub_gui_map_color (view->message_bg_color),
339 f.x, f.y, f.width, f.height);
340
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);
347 }
348
349 void
350 grub_gfxmenu_view_redraw (grub_gfxmenu_view_t view,
351 const grub_video_rect_t *region)
352 {
353 grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
354
355 redraw_background (view, region);
356 if (view->canvas)
357 view->canvas->component.ops->paint (view->canvas, region);
358 draw_title (view);
359 if (grub_video_have_common_points (&view->progress_message_frame, region))
360 draw_message (view);
361 }
362
363 void
364 grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
365 {
366 update_timeout (view, 1);
367 update_menu_components (view);
368
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);
373 }
374
375 static void
376 redraw_menu_visit (grub_gui_component_t component,
377 void *userdata)
378 {
379 grub_gfxmenu_view_t view;
380 view = userdata;
381 if (component->ops->is_instance (component, "list"))
382 {
383 grub_gui_list_t list;
384 grub_video_rect_t bounds;
385
386 list = (grub_gui_list_t) component;
387 component->ops->get_bounds (component, &bounds);
388 grub_gfxmenu_view_redraw (view, &bounds);
389 }
390 }
391
392 void
393 grub_gfxmenu_redraw_menu (grub_gfxmenu_view_t view)
394 {
395 update_menu_components (view);
396
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)
401 {
402 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
403 redraw_menu_visit, view);
404 }
405 }
406
407 static grub_err_t
408 set_graphics_mode (void)
409 {
410 const char *modestr = grub_env_get ("gfxmode");
411 if (!modestr || !modestr[0])
412 modestr = "auto";
413 return grub_video_set_mode (modestr, GRUB_VIDEO_MODE_TYPE_PURE_TEXT, 0);
414 }
415
416 static grub_err_t
417 set_text_mode (void)
418 {
419 return grub_video_restore ();
420 }
421
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;
426
427 static void
428 draw_terminal_box (grub_gfxmenu_view_t view)
429 {
430 grub_gfxmenu_box_t term_box;
431 int termx;
432 int termy;
433
434 term_box = term_view->terminal_box;
435 if (!term_box)
436 return;
437
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;
440
441 term_box->set_content_size (term_box, term_target_width,
442 term_target_height);
443
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));
452 }
453
454 static void
455 init_terminal (grub_gfxmenu_view_t view)
456 {
457 int termx;
458 int termy;
459
460 term_original = grub_term_get_current_output ();
461
462 term_target_width = view->screen.width * 7 / 10;
463 term_target_height = view->screen.height * 7 / 10;
464
465 termx = view->screen.x + view->screen.width * (10 - 7) / 10 / 2;
466 termy = view->screen.y + view->screen.height * (10 - 7) / 10 / 2;
467
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)
475 return;
476 term_initialized = 1;
477
478 term_view = view;
479
480 grub_term_set_current_output (grub_gfxterm_get_term ());
481 grub_refresh ();
482 }
483
484 static void destroy_terminal (void)
485 {
486 if (term_initialized)
487 grub_gfxterm_destroy_window ();
488 if (term_original)
489 grub_term_set_current_output (term_original);
490 }
491
492
493 static void
494 notify_booting (grub_menu_entry_t entry, void *userdata)
495 {
496 grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
497
498 char *s = grub_malloc (100 + grub_strlen (entry->title));
499 if (!s)
500 return;
501
502 grub_sprintf (s, "Booting '%s'", entry->title);
503 set_progress_message (view, s);
504 grub_free (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);
509 }
510
511 static void
512 notify_fallback (grub_menu_entry_t entry, void *userdata)
513 {
514 grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
515
516 char *s = grub_malloc (100 + grub_strlen (entry->title));
517 if (!s)
518 return;
519
520 grub_sprintf (s, "Falling back to '%s'", entry->title);
521 set_progress_message (view, s);
522 grub_free (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);
527 }
528
529 static void
530 notify_execution_failure (void *userdata __attribute__ ((unused)))
531 {
532 }
533
534
535 static struct grub_menu_execute_callback execute_callback =
536 {
537 .notify_booting = notify_booting,
538 .notify_fallback = notify_fallback,
539 .notify_failure = notify_execution_failure
540 };
541
542 int
543 grub_gfxmenu_view_execute_with_fallback (grub_gfxmenu_view_t view,
544 grub_menu_entry_t entry)
545 {
546 draw_terminal_box (view);
547
548 grub_menu_execute_with_fallback (grub_gfxmenu_model_get_menu (view->model),
549 entry, &execute_callback, (void *) view);
550
551 if (set_graphics_mode () != GRUB_ERR_NONE)
552 return 0; /* Failure. */
553
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 ()) != ' ')
560 {
561 /* Wait for SPACE to be pressed. */
562 }
563
564 set_progress_message (view, 0); /* Clear the message. */
565
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);
570
571 return 1; /* Ok. */
572 }
573
574 int
575 grub_gfxmenu_view_execute_entry (grub_gfxmenu_view_t view,
576 grub_menu_entry_t entry)
577 {
578 draw_terminal_box (view);
579
580 grub_menu_execute_entry (entry);
581 if (grub_errno != GRUB_ERR_NONE)
582 grub_wait_after_message ();
583
584 if (set_graphics_mode () != GRUB_ERR_NONE)
585 return 0; /* Failure. */
586
587 grub_gfxmenu_view_draw (view);
588 return 1; /* Ok. */
589 }
590
591 void
592 grub_gfxmenu_view_run_terminal (grub_gfxmenu_view_t view)
593 {
594 draw_terminal_box (view);
595 grub_cmdline_run (1);
596 grub_gfxmenu_view_draw (view);
597 }