]> glassweightruler.freedombox.rocks Git - Ventoy.git/blob - GRUB2/MOD_SRC/grub-2.04/grub-core/gfxmenu/view.c
1.1.01
[Ventoy.git] / GRUB2 / MOD_SRC / grub-2.04 / grub-core / 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/gfxterm.h>
29 #include <grub/bitmap.h>
30 #include <grub/bitmap_scale.h>
31 #include <grub/term.h>
32 #include <grub/gfxwidgets.h>
33 #include <grub/time.h>
34 #include <grub/menu.h>
35 #include <grub/menu_viewer.h>
36 #include <grub/gfxmenu_view.h>
37 #include <grub/gui_string_util.h>
38 #include <grub/icon_manager.h>
39 #include <grub/i18n.h>
40 #include <grub/charset.h>
41
42 static void
43 init_terminal (grub_gfxmenu_view_t view);
44 static void
45 init_background (grub_gfxmenu_view_t view);
46 static grub_gfxmenu_view_t term_view;
47
48 /* Create a new view object, loading the theme specified by THEME_PATH and
49 associating MODEL with the view. */
50 grub_gfxmenu_view_t
51 grub_gfxmenu_view_new (const char *theme_path,
52 int width, int height)
53 {
54 grub_gfxmenu_view_t view;
55 grub_font_t default_font;
56 grub_video_rgba_color_t default_fg_color;
57 grub_video_rgba_color_t default_bg_color;
58
59 view = grub_malloc (sizeof (*view));
60 if (! view)
61 return 0;
62
63 while (grub_gfxmenu_timeout_notifications)
64 {
65 struct grub_gfxmenu_timeout_notify *p;
66 p = grub_gfxmenu_timeout_notifications;
67 grub_gfxmenu_timeout_notifications = grub_gfxmenu_timeout_notifications->next;
68 grub_free (p);
69 }
70
71 view->screen.x = 0;
72 view->screen.y = 0;
73 view->screen.width = width;
74 view->screen.height = height;
75
76 view->need_to_check_sanity = 1;
77 view->terminal_border = 3;
78 view->terminal_rect.width = view->screen.width * 7 / 10;
79 view->terminal_rect.height = view->screen.height * 7 / 10;
80 view->terminal_rect.x = view->screen.x + (view->screen.width
81 - view->terminal_rect.width) / 2;
82 view->terminal_rect.y = view->screen.y + (view->screen.height
83 - view->terminal_rect.height) / 2;
84
85 default_font = grub_font_get ("Unknown Regular 16");
86 default_fg_color = grub_video_rgba_color_rgb (0, 0, 0);
87 default_bg_color = grub_video_rgba_color_rgb (255, 255, 255);
88
89 view->canvas = 0;
90
91 view->title_font = default_font;
92 view->message_font = default_font;
93 view->terminal_font_name = grub_strdup ("Fixed 10");
94 view->title_color = default_fg_color;
95 view->message_color = default_bg_color;
96 view->message_bg_color = default_fg_color;
97 view->raw_desktop_image = 0;
98 view->scaled_desktop_image = 0;
99 view->desktop_image_scale_method = GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH;
100 view->desktop_image_h_align = GRUB_VIDEO_BITMAP_H_ALIGN_CENTER;
101 view->desktop_image_v_align = GRUB_VIDEO_BITMAP_V_ALIGN_CENTER;
102 view->desktop_color = default_bg_color;
103 view->terminal_box = grub_gfxmenu_create_box (0, 0);
104 view->title_text = grub_strdup (_("GRUB Boot Menu"));
105 view->progress_message_text = 0;
106 view->theme_path = 0;
107
108 /* Set the timeout bar's frame. */
109 view->progress_message_frame.width = view->screen.width * 4 / 5;
110 view->progress_message_frame.height = 50;
111 view->progress_message_frame.x = view->screen.x
112 + (view->screen.width - view->progress_message_frame.width) / 2;
113 view->progress_message_frame.y = view->screen.y
114 + view->screen.height - 90 - 20 - view->progress_message_frame.height;
115
116 if (grub_gfxmenu_view_load_theme (view, theme_path) != 0)
117 {
118 grub_gfxmenu_view_destroy (view);
119 return 0;
120 }
121
122 return view;
123 }
124
125 /* Destroy the view object. All used memory is freed. */
126 void
127 grub_gfxmenu_view_destroy (grub_gfxmenu_view_t view)
128 {
129 if (!view)
130 return;
131 while (grub_gfxmenu_timeout_notifications)
132 {
133 struct grub_gfxmenu_timeout_notify *p;
134 p = grub_gfxmenu_timeout_notifications;
135 grub_gfxmenu_timeout_notifications = grub_gfxmenu_timeout_notifications->next;
136 grub_free (p);
137 }
138 grub_video_bitmap_destroy (view->raw_desktop_image);
139 grub_video_bitmap_destroy (view->scaled_desktop_image);
140 if (view->terminal_box)
141 view->terminal_box->destroy (view->terminal_box);
142 grub_free (view->terminal_font_name);
143 grub_free (view->title_text);
144 grub_free (view->progress_message_text);
145 grub_free (view->theme_path);
146 if (view->menu_title_offset)
147 grub_free (view->menu_title_offset);
148 if (view->canvas)
149 view->canvas->component.ops->destroy (view->canvas);
150 grub_free (view);
151 }
152
153 static void
154 redraw_background (grub_gfxmenu_view_t view,
155 const grub_video_rect_t *bounds)
156 {
157 if (view->scaled_desktop_image)
158 {
159 struct grub_video_bitmap *img = view->scaled_desktop_image;
160 grub_video_blit_bitmap (img, GRUB_VIDEO_BLIT_REPLACE,
161 bounds->x, bounds->y,
162 bounds->x - view->screen.x,
163 bounds->y - view->screen.y,
164 bounds->width, bounds->height);
165 }
166 else
167 {
168 grub_video_fill_rect (grub_video_map_rgba_color (view->desktop_color),
169 bounds->x, bounds->y,
170 bounds->width, bounds->height);
171 }
172 }
173
174 static void
175 draw_title (grub_gfxmenu_view_t view)
176 {
177 if (! view->title_text)
178 return;
179
180 /* Center the title. */
181 int title_width = grub_font_get_string_width (view->title_font,
182 view->title_text);
183 int x = (view->screen.width - title_width) / 2;
184 int y = 40 + grub_font_get_ascent (view->title_font);
185 grub_font_draw_string (view->title_text,
186 view->title_font,
187 grub_video_map_rgba_color (view->title_color),
188 x, y);
189 }
190
191 struct progress_value_data
192 {
193 int visible;
194 int start;
195 int end;
196 int value;
197 };
198
199 struct grub_gfxmenu_timeout_notify *grub_gfxmenu_timeout_notifications;
200
201 static void
202 update_timeouts (int visible, int start, int value, int end)
203 {
204 struct grub_gfxmenu_timeout_notify *cur;
205
206 for (cur = grub_gfxmenu_timeout_notifications; cur; cur = cur->next)
207 cur->set_state (cur->self, visible, start, value, end);
208 }
209
210 static void
211 redraw_timeouts (struct grub_gfxmenu_view *view)
212 {
213 struct grub_gfxmenu_timeout_notify *cur;
214
215 for (cur = grub_gfxmenu_timeout_notifications; cur; cur = cur->next)
216 {
217 grub_video_rect_t bounds;
218 cur->self->ops->get_bounds (cur->self, &bounds);
219 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED);
220 grub_gfxmenu_view_redraw (view, &bounds);
221 }
222 }
223
224 void
225 grub_gfxmenu_print_timeout (int timeout, void *data)
226 {
227 struct grub_gfxmenu_view *view = data;
228
229 if (view->first_timeout == -1)
230 view->first_timeout = timeout;
231
232 update_timeouts (1, -view->first_timeout, -timeout, 0);
233 redraw_timeouts (view);
234 grub_video_swap_buffers ();
235 if (view->double_repaint)
236 redraw_timeouts (view);
237 }
238
239 void
240 grub_gfxmenu_clear_timeout (void *data)
241 {
242 struct grub_gfxmenu_view *view = data;
243
244 update_timeouts (0, 1, 0, 0);
245 redraw_timeouts (view);
246 grub_video_swap_buffers ();
247 if (view->double_repaint)
248 redraw_timeouts (view);
249 }
250
251 static void
252 update_menu_visit (grub_gui_component_t component,
253 void *userdata)
254 {
255 grub_gfxmenu_view_t view;
256 view = userdata;
257 if (component->ops->is_instance (component, "list"))
258 {
259 grub_gui_list_t list = (grub_gui_list_t) component;
260 list->ops->set_view_info (list, view);
261 }
262 }
263
264 /* Update any boot menu components with the current menu model and
265 theme path. */
266 static void
267 update_menu_components (grub_gfxmenu_view_t view)
268 {
269 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
270 update_menu_visit, view);
271 }
272
273 static void
274 refresh_menu_visit (grub_gui_component_t component,
275 void *userdata)
276 {
277 grub_gfxmenu_view_t view;
278 view = userdata;
279 if (component->ops->is_instance (component, "list"))
280 {
281 grub_gui_list_t list = (grub_gui_list_t) component;
282 list->ops->refresh_list (list, view);
283 }
284 }
285
286 /* Refresh list information (useful for submenus) */
287 static void
288 refresh_menu_components (grub_gfxmenu_view_t view)
289 {
290 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
291 refresh_menu_visit, view);
292 }
293
294 static void
295 draw_message (grub_gfxmenu_view_t view)
296 {
297 char *text = view->progress_message_text;
298 grub_video_rect_t f = view->progress_message_frame;
299 if (! text)
300 return;
301
302 grub_font_t font = view->message_font;
303 grub_video_color_t color = grub_video_map_rgba_color (view->message_color);
304
305 /* Border. */
306 grub_video_fill_rect (color,
307 f.x-1, f.y-1, f.width+2, f.height+2);
308 /* Fill. */
309 grub_video_fill_rect (grub_video_map_rgba_color (view->message_bg_color),
310 f.x, f.y, f.width, f.height);
311
312 /* Center the text. */
313 int text_width = grub_font_get_string_width (font, text);
314 int x = f.x + (f.width - text_width) / 2;
315 int y = (f.y + (f.height - grub_font_get_descent (font)) / 2
316 + grub_font_get_ascent (font) / 2);
317 grub_font_draw_string (text, font, color, x, y);
318 }
319
320 void
321 grub_gfxmenu_view_redraw (grub_gfxmenu_view_t view,
322 const grub_video_rect_t *region)
323 {
324 if (grub_video_have_common_points (&view->terminal_rect, region))
325 grub_gfxterm_schedule_repaint ();
326
327 grub_video_set_active_render_target (GRUB_VIDEO_RENDER_TARGET_DISPLAY);
328 grub_video_area_status_t area_status;
329 grub_video_get_area_status (&area_status);
330 if (area_status == GRUB_VIDEO_AREA_ENABLED)
331 grub_video_set_region (region->x, region->y,
332 region->width, region->height);
333
334 redraw_background (view, region);
335 if (view->canvas)
336 view->canvas->component.ops->paint (view->canvas, region);
337 draw_title (view);
338 if (grub_video_have_common_points (&view->progress_message_frame, region))
339 draw_message (view);
340
341 if (area_status == GRUB_VIDEO_AREA_ENABLED)
342 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED);
343 }
344
345 void
346 grub_gfxmenu_view_draw (grub_gfxmenu_view_t view)
347 {
348 init_terminal (view);
349
350 init_background (view);
351
352 /* Clear the screen; there may be garbage left over in video memory. */
353 grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
354 view->screen.x, view->screen.y,
355 view->screen.width, view->screen.height);
356 grub_video_swap_buffers ();
357 if (view->double_repaint)
358 grub_video_fill_rect (grub_video_map_rgb (0, 0, 0),
359 view->screen.x, view->screen.y,
360 view->screen.width, view->screen.height);
361
362 refresh_menu_components (view);
363 update_menu_components (view);
364
365 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED);
366 grub_gfxmenu_view_redraw (view, &view->screen);
367 grub_video_swap_buffers ();
368 if (view->double_repaint)
369 {
370 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED);
371 grub_gfxmenu_view_redraw (view, &view->screen);
372 }
373
374 }
375
376 static void
377 redraw_menu_visit (grub_gui_component_t component,
378 void *userdata)
379 {
380 grub_gfxmenu_view_t view;
381 view = userdata;
382 if (component->ops->is_instance (component, "list"))
383 {
384 grub_video_rect_t bounds;
385
386 component->ops->get_bounds (component, &bounds);
387 grub_video_set_area_status (GRUB_VIDEO_AREA_ENABLED);
388 grub_gfxmenu_view_redraw (view, &bounds);
389 }
390 }
391
392 extern int g_menu_update_mode;
393
394 static void grub_gfxmenu_update_all(grub_gfxmenu_view_t view)
395 {
396 grub_video_set_area_status(GRUB_VIDEO_AREA_DISABLED);
397 grub_gfxmenu_view_redraw(view, &view->screen);
398 }
399
400 void
401 grub_gfxmenu_redraw_menu (grub_gfxmenu_view_t view)
402 {
403 update_menu_components (view);
404
405 if (g_menu_update_mode)
406 grub_gfxmenu_update_all(view);
407 else
408 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
409 redraw_menu_visit, view);
410
411 grub_video_swap_buffers ();
412 if (view->double_repaint)
413 {
414 if (g_menu_update_mode)
415 grub_gfxmenu_update_all(view);
416 else
417 grub_gui_iterate_recursively ((grub_gui_component_t) view->canvas,
418 redraw_menu_visit, view);
419 }
420 }
421
422
423 void
424 grub_gfxmenu_set_chosen_entry (int entry, void *data)
425 {
426 grub_gfxmenu_view_t view = data;
427
428 view->selected = entry;
429 grub_gfxmenu_redraw_menu (view);
430
431
432 }
433
434 void
435 grub_gfxmenu_scroll_chosen_entry (void *data, int diren)
436 {
437 grub_gfxmenu_view_t view = data;
438 const char *item_title;
439 int off;
440 int max;
441
442 if (!view->menu->size)
443 return;
444
445 item_title = grub_menu_get_entry (view->menu, view->selected)->title;
446 off = view->menu_title_offset[view->selected] + diren;
447 max = grub_utf8_get_num_code (item_title, grub_strlen(item_title));
448
449 if (diren == 1000000)
450 off = (max >= 20) ? (max - 20) : 0;
451 else if (off < 0)
452 off = 0;
453 else if (off > max)
454 off = max;
455
456 view->menu_title_offset[view->selected] = off;
457 grub_gfxmenu_redraw_menu (view);
458 }
459
460 static void
461 grub_gfxmenu_draw_terminal_box (void)
462 {
463 grub_gfxmenu_box_t term_box;
464
465 term_box = term_view->terminal_box;
466 if (!term_box)
467 return;
468
469 grub_video_set_area_status (GRUB_VIDEO_AREA_DISABLED);
470
471 term_box->set_content_size (term_box, term_view->terminal_rect.width,
472 term_view->terminal_rect.height);
473
474 term_box->draw (term_box,
475 term_view->terminal_rect.x - term_box->get_left_pad (term_box),
476 term_view->terminal_rect.y - term_box->get_top_pad (term_box));
477 }
478
479 static void
480 get_min_terminal (grub_font_t terminal_font,
481 unsigned int border_width,
482 unsigned int *min_terminal_width,
483 unsigned int *min_terminal_height)
484 {
485 struct grub_font_glyph *glyph;
486 glyph = grub_font_get_glyph (terminal_font, 'M');
487 *min_terminal_width = (glyph? glyph->device_width : 8) * 80
488 + 2 * border_width;
489 *min_terminal_height = grub_font_get_max_char_height (terminal_font) * 24
490 + 2 * border_width;
491 }
492
493 static void
494 terminal_sanity_check (grub_gfxmenu_view_t view)
495 {
496 if (!view->need_to_check_sanity)
497 return;
498
499 /* terminal_font was checked before in the init_terminal function. */
500 grub_font_t terminal_font = grub_font_get (view->terminal_font_name);
501
502 /* Non-negative numbers below. */
503 int scr_x = view->screen.x;
504 int scr_y = view->screen.y;
505 int scr_width = view->screen.width;
506 int scr_height = view->screen.height;
507 int term_x = view->terminal_rect.x;
508 int term_y = view->terminal_rect.y;
509 int term_width = view->terminal_rect.width;
510 int term_height = view->terminal_rect.height;
511
512 /* Check that border_width isn't too big. */
513 unsigned int border_width = view->terminal_border;
514 unsigned int min_terminal_width;
515 unsigned int min_terminal_height;
516 get_min_terminal (terminal_font, border_width,
517 &min_terminal_width, &min_terminal_height);
518 if (border_width > 3 && ((int) min_terminal_width >= scr_width
519 || (int) min_terminal_height >= scr_height))
520 {
521 border_width = 3;
522 get_min_terminal (terminal_font, border_width,
523 &min_terminal_width, &min_terminal_height);
524 }
525
526 /* Sanity checks. */
527 if (term_width > scr_width)
528 term_width = scr_width;
529 if (term_height > scr_height)
530 term_height = scr_height;
531
532 if (scr_width <= (int) min_terminal_width
533 || scr_height <= (int) min_terminal_height)
534 {
535 /* The screen resulution is too low. Use all space, except a small border
536 to show the user, that it is a window. Then center the window. */
537 term_width = scr_width - 6 * border_width;
538 term_height = scr_height - 6 * border_width;
539 term_x = scr_x + (scr_width - term_width) / 2;
540 term_y = scr_y + (scr_height - term_height) / 2;
541 }
542 else if (term_width < (int) min_terminal_width
543 || term_height < (int) min_terminal_height)
544 {
545 /* The screen resolution is big enough. Make sure, that terminal screen
546 dimensions aren't less than minimal values. Then center the window. */
547 term_width = (int) min_terminal_width;
548 term_height = (int) min_terminal_height;
549 term_x = scr_x + (scr_width - term_width) / 2;
550 term_y = scr_y + (scr_height - term_height) / 2;
551 }
552
553 /* At this point w and h are satisfying. */
554 if (term_x + term_width > scr_width)
555 term_x = scr_width - term_width;
556 if (term_y + term_height > scr_height)
557 term_y = scr_height - term_height;
558
559 /* Write down corrected data. */
560 view->terminal_rect.x = (unsigned int) term_x;
561 view->terminal_rect.y = (unsigned int) term_y;
562 view->terminal_rect.width = (unsigned int) term_width;
563 view->terminal_rect.height = (unsigned int) term_height;
564 view->terminal_border = border_width;
565
566 view->need_to_check_sanity = 0;
567 }
568
569 static void
570 init_terminal (grub_gfxmenu_view_t view)
571 {
572 grub_font_t terminal_font;
573
574 terminal_font = grub_font_get (view->terminal_font_name);
575 if (!terminal_font)
576 {
577 grub_error (GRUB_ERR_BAD_FONT, "no font loaded");
578 return;
579 }
580
581 /* Check that terminal window size and position are sane. */
582 terminal_sanity_check (view);
583
584 term_view = view;
585
586 /* Note: currently there is no API for changing the gfxterm font
587 on the fly, so whatever font the initially loaded theme specifies
588 will be permanent. */
589 grub_gfxterm_set_window (GRUB_VIDEO_RENDER_TARGET_DISPLAY,
590 view->terminal_rect.x,
591 view->terminal_rect.y,
592 view->terminal_rect.width,
593 view->terminal_rect.height,
594 view->double_repaint,
595 terminal_font,
596 view->terminal_border);
597 grub_gfxterm_decorator_hook = grub_gfxmenu_draw_terminal_box;
598 }
599
600 static void
601 init_background (grub_gfxmenu_view_t view)
602 {
603 if (view->scaled_desktop_image || (!view->raw_desktop_image))
604 return;
605
606 struct grub_video_bitmap *scaled_bitmap;
607 if (view->desktop_image_scale_method ==
608 GRUB_VIDEO_BITMAP_SELECTION_METHOD_STRETCH)
609 grub_video_bitmap_create_scaled (&scaled_bitmap,
610 view->screen.width,
611 view->screen.height,
612 view->raw_desktop_image,
613 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST);
614 else
615 grub_video_bitmap_scale_proportional (&scaled_bitmap,
616 view->screen.width,
617 view->screen.height,
618 view->raw_desktop_image,
619 GRUB_VIDEO_BITMAP_SCALE_METHOD_BEST,
620 view->desktop_image_scale_method,
621 view->desktop_image_v_align,
622 view->desktop_image_h_align);
623 if (! scaled_bitmap)
624 return;
625 view->scaled_desktop_image = scaled_bitmap;
626
627 }
628
629 /* FIXME: previously notifications were displayed in special case.
630 Is it necessary?
631 */
632 #if 0
633 /* Sets MESSAGE as the progress message for the view.
634 MESSAGE can be 0, in which case no message is displayed. */
635 static void
636 set_progress_message (grub_gfxmenu_view_t view, const char *message)
637 {
638 grub_free (view->progress_message_text);
639 if (message)
640 view->progress_message_text = grub_strdup (message);
641 else
642 view->progress_message_text = 0;
643 }
644
645 static void
646 notify_booting (grub_menu_entry_t entry, void *userdata)
647 {
648 grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
649
650 char *s = grub_malloc (100 + grub_strlen (entry->title));
651 if (!s)
652 return;
653
654 grub_sprintf (s, "Booting '%s'", entry->title);
655 set_progress_message (view, s);
656 grub_free (s);
657 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
658 grub_video_swap_buffers ();
659 if (view->double_repaint)
660 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
661 }
662
663 static void
664 notify_fallback (grub_menu_entry_t entry, void *userdata)
665 {
666 grub_gfxmenu_view_t view = (grub_gfxmenu_view_t) userdata;
667
668 char *s = grub_malloc (100 + grub_strlen (entry->title));
669 if (!s)
670 return;
671
672 grub_sprintf (s, "Falling back to '%s'", entry->title);
673 set_progress_message (view, s);
674 grub_free (s);
675 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
676 grub_video_swap_buffers ();
677 if (view->double_repaint)
678 grub_gfxmenu_view_redraw (view, &view->progress_message_frame);
679 }
680
681 static void
682 notify_execution_failure (void *userdata __attribute__ ((unused)))
683 {
684 }
685
686
687 static struct grub_menu_execute_callback execute_callback =
688 {
689 .notify_booting = notify_booting,
690 .notify_fallback = notify_fallback,
691 .notify_failure = notify_execution_failure
692 };
693
694 #endif