menu_text.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. /* menu_text.c - Basic text menu implementation. */
  2. /*
  3. * GRUB -- GRand Unified Bootloader
  4. * Copyright (C) 2003,2004,2005,2006,2007,2008,2009 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. #include <grub/normal.h>
  20. #include <grub/term.h>
  21. #include <grub/misc.h>
  22. #include <grub/loader.h>
  23. #include <grub/mm.h>
  24. #include <grub/time.h>
  25. #include <grub/env.h>
  26. #include <grub/menu_viewer.h>
  27. #include <grub/i18n.h>
  28. #include <grub/charset.h>
  29. static grub_uint8_t grub_color_menu_normal;
  30. static grub_uint8_t grub_color_menu_highlight;
  31. struct menu_viewer_data
  32. {
  33. int first, offset;
  34. struct grub_term_screen_geometry geo;
  35. enum {
  36. TIMEOUT_UNKNOWN,
  37. TIMEOUT_NORMAL,
  38. TIMEOUT_TERSE,
  39. TIMEOUT_TERSE_NO_MARGIN
  40. } timeout_msg;
  41. grub_menu_t menu;
  42. struct grub_term_output *term;
  43. };
  44. static inline int
  45. grub_term_cursor_x (const struct grub_term_screen_geometry *geo)
  46. {
  47. return (geo->first_entry_x + geo->entry_width);
  48. }
  49. grub_size_t
  50. grub_getstringwidth (grub_uint32_t * str, const grub_uint32_t * last_position,
  51. struct grub_term_output *term)
  52. {
  53. grub_ssize_t width = 0;
  54. while (str < last_position)
  55. {
  56. struct grub_unicode_glyph glyph;
  57. glyph.ncomb = 0;
  58. str += grub_unicode_aglomerate_comb (str, last_position - str, &glyph);
  59. width += grub_term_getcharwidth (term, &glyph);
  60. grub_unicode_destroy_glyph (&glyph);
  61. }
  62. return width;
  63. }
  64. static int
  65. grub_print_message_indented_real (const char *msg, int margin_left,
  66. int margin_right,
  67. struct grub_term_output *term, int dry_run)
  68. {
  69. grub_uint32_t *unicode_msg;
  70. grub_uint32_t *last_position;
  71. grub_size_t msg_len = grub_strlen (msg) + 2;
  72. int ret = 0;
  73. unicode_msg = grub_calloc (msg_len, sizeof (grub_uint32_t));
  74. if (!unicode_msg)
  75. return 0;
  76. msg_len = grub_utf8_to_ucs4 (unicode_msg, msg_len,
  77. (grub_uint8_t *) msg, -1, 0);
  78. last_position = unicode_msg + msg_len;
  79. *last_position = 0;
  80. if (dry_run)
  81. ret = grub_ucs4_count_lines (unicode_msg, last_position, margin_left,
  82. margin_right, term);
  83. else
  84. grub_print_ucs4_menu (unicode_msg, last_position, margin_left,
  85. margin_right, term, 0, -1, 0, 0);
  86. grub_free (unicode_msg);
  87. return ret;
  88. }
  89. void
  90. grub_print_message_indented (const char *msg, int margin_left, int margin_right,
  91. struct grub_term_output *term)
  92. {
  93. grub_print_message_indented_real (msg, margin_left, margin_right, term, 0);
  94. }
  95. static void
  96. draw_border (struct grub_term_output *term, const struct grub_term_screen_geometry *geo)
  97. {
  98. int i;
  99. grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
  100. grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1,
  101. geo->first_entry_y - 1 });
  102. grub_putcode (GRUB_UNICODE_CORNER_UL, term);
  103. for (i = 0; i < geo->entry_width + 1; i++)
  104. grub_putcode (GRUB_UNICODE_HLINE, term);
  105. grub_putcode (GRUB_UNICODE_CORNER_UR, term);
  106. for (i = 0; i < geo->num_entries; i++)
  107. {
  108. grub_term_gotoxy (term, (struct grub_term_coordinate) { geo->first_entry_x - 1,
  109. geo->first_entry_y + i });
  110. grub_putcode (GRUB_UNICODE_VLINE, term);
  111. grub_term_gotoxy (term,
  112. (struct grub_term_coordinate) { geo->first_entry_x + geo->entry_width + 1,
  113. geo->first_entry_y + i });
  114. grub_putcode (GRUB_UNICODE_VLINE, term);
  115. }
  116. grub_term_gotoxy (term,
  117. (struct grub_term_coordinate) { geo->first_entry_x - 1,
  118. geo->first_entry_y - 1 + geo->num_entries + 1 });
  119. grub_putcode (GRUB_UNICODE_CORNER_LL, term);
  120. for (i = 0; i < geo->entry_width + 1; i++)
  121. grub_putcode (GRUB_UNICODE_HLINE, term);
  122. grub_putcode (GRUB_UNICODE_CORNER_LR, term);
  123. grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
  124. grub_term_gotoxy (term,
  125. (struct grub_term_coordinate) { geo->first_entry_x - 1,
  126. (geo->first_entry_y - 1 + geo->num_entries
  127. + GRUB_TERM_MARGIN + 1) });
  128. }
  129. static int
  130. print_message (int nested, int edit, struct grub_term_output *term, int dry_run)
  131. {
  132. int ret = 0;
  133. grub_term_setcolorstate (term, GRUB_TERM_COLOR_NORMAL);
  134. if (edit)
  135. {
  136. ret += grub_print_message_indented_real (_("Minimum Emacs-like screen editing is \
  137. supported. TAB lists completions. Press Ctrl-x or F10 to boot, Ctrl-c or F2 for a \
  138. command-line or ESC to discard edits and return to the GRUB menu."),
  139. STANDARD_MARGIN, STANDARD_MARGIN,
  140. term, dry_run);
  141. }
  142. else
  143. {
  144. char *msg_translated;
  145. msg_translated = grub_xasprintf (_("Use the %C and %C keys to select which "
  146. "entry is highlighted."),
  147. GRUB_UNICODE_UPARROW,
  148. GRUB_UNICODE_DOWNARROW);
  149. if (!msg_translated)
  150. return 0;
  151. ret += grub_print_message_indented_real (msg_translated, STANDARD_MARGIN,
  152. STANDARD_MARGIN, term, dry_run);
  153. grub_free (msg_translated);
  154. if (!grub_is_cli_disabled ())
  155. {
  156. if (nested)
  157. {
  158. ret += grub_print_message_indented_real
  159. (_("Press enter to boot the selected OS, "
  160. "`e' to edit the commands before booting "
  161. "or `c' for a command-line. ESC to return previous menu."),
  162. STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
  163. }
  164. else
  165. {
  166. ret += grub_print_message_indented_real
  167. (_("Press enter to boot the selected OS, "
  168. "`e' to edit the commands before booting "
  169. "or `c' for a command-line."),
  170. STANDARD_MARGIN, STANDARD_MARGIN, term, dry_run);
  171. }
  172. }
  173. }
  174. return ret;
  175. }
  176. static void
  177. print_entry (int y, int highlight, grub_menu_entry_t entry,
  178. const struct menu_viewer_data *data)
  179. {
  180. const char *title;
  181. grub_size_t title_len;
  182. grub_ssize_t len;
  183. grub_uint32_t *unicode_title;
  184. grub_ssize_t i;
  185. grub_uint8_t old_color_normal, old_color_highlight;
  186. title = entry ? entry->title : "";
  187. title_len = grub_strlen (title);
  188. unicode_title = grub_calloc (title_len, sizeof (*unicode_title));
  189. if (! unicode_title)
  190. /* XXX How to show this error? */
  191. return;
  192. len = grub_utf8_to_ucs4 (unicode_title, title_len,
  193. (grub_uint8_t *) title, -1, 0);
  194. if (len < 0)
  195. {
  196. /* It is an invalid sequence. */
  197. grub_free (unicode_title);
  198. return;
  199. }
  200. old_color_normal = grub_term_normal_color;
  201. old_color_highlight = grub_term_highlight_color;
  202. grub_term_normal_color = grub_color_menu_normal;
  203. grub_term_highlight_color = grub_color_menu_highlight;
  204. grub_term_setcolorstate (data->term, highlight
  205. ? GRUB_TERM_COLOR_HIGHLIGHT
  206. : GRUB_TERM_COLOR_NORMAL);
  207. grub_term_gotoxy (data->term, (struct grub_term_coordinate) {
  208. data->geo.first_entry_x, y });
  209. for (i = 0; i < len; i++)
  210. if (unicode_title[i] == '\n' || unicode_title[i] == '\b'
  211. || unicode_title[i] == '\r' || unicode_title[i] == '\e')
  212. unicode_title[i] = ' ';
  213. if (data->geo.num_entries > 1)
  214. grub_putcode (highlight ? '*' : ' ', data->term);
  215. grub_print_ucs4_menu (unicode_title,
  216. unicode_title + len,
  217. 0,
  218. data->geo.right_margin,
  219. data->term, 0, 1,
  220. GRUB_UNICODE_RIGHTARROW, 0);
  221. grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
  222. grub_term_gotoxy (data->term,
  223. (struct grub_term_coordinate) {
  224. grub_term_cursor_x (&data->geo), y });
  225. grub_term_normal_color = old_color_normal;
  226. grub_term_highlight_color = old_color_highlight;
  227. grub_term_setcolorstate (data->term, GRUB_TERM_COLOR_NORMAL);
  228. grub_free (unicode_title);
  229. }
  230. static void
  231. print_entries (grub_menu_t menu, const struct menu_viewer_data *data)
  232. {
  233. grub_menu_entry_t e;
  234. int i;
  235. grub_term_gotoxy (data->term,
  236. (struct grub_term_coordinate) {
  237. data->geo.first_entry_x + data->geo.entry_width
  238. + data->geo.border + 1,
  239. data->geo.first_entry_y });
  240. if (data->geo.num_entries != 1)
  241. {
  242. if (data->first)
  243. grub_putcode (GRUB_UNICODE_UPARROW, data->term);
  244. else
  245. grub_putcode (' ', data->term);
  246. }
  247. e = grub_menu_get_entry (menu, data->first);
  248. for (i = 0; i < data->geo.num_entries; i++)
  249. {
  250. print_entry (data->geo.first_entry_y + i, data->offset == i,
  251. e, data);
  252. if (e)
  253. e = e->next;
  254. }
  255. grub_term_gotoxy (data->term,
  256. (struct grub_term_coordinate) { data->geo.first_entry_x + data->geo.entry_width
  257. + data->geo.border + 1,
  258. data->geo.first_entry_y + data->geo.num_entries - 1 });
  259. if (data->geo.num_entries == 1)
  260. {
  261. if (data->first && e)
  262. grub_putcode (GRUB_UNICODE_UPDOWNARROW, data->term);
  263. else if (data->first)
  264. grub_putcode (GRUB_UNICODE_UPARROW, data->term);
  265. else if (e)
  266. grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
  267. else
  268. grub_putcode (' ', data->term);
  269. }
  270. else
  271. {
  272. if (e)
  273. grub_putcode (GRUB_UNICODE_DOWNARROW, data->term);
  274. else
  275. grub_putcode (' ', data->term);
  276. }
  277. grub_term_gotoxy (data->term,
  278. (struct grub_term_coordinate) { grub_term_cursor_x (&data->geo),
  279. data->geo.first_entry_y + data->offset });
  280. }
  281. /* Initialize the screen. If NESTED is non-zero, assume that this menu
  282. is run from another menu or a command-line. If EDIT is non-zero, show
  283. a message for the menu entry editor. */
  284. void
  285. grub_menu_init_page (int nested, int edit,
  286. struct grub_term_screen_geometry *geo,
  287. struct grub_term_output *term)
  288. {
  289. grub_uint8_t old_color_normal, old_color_highlight;
  290. int msg_num_lines;
  291. int bottom_message = 1;
  292. int empty_lines = 1;
  293. int version_msg = 1;
  294. geo->border = 1;
  295. geo->first_entry_x = 1 /* margin */ + 1 /* border */;
  296. geo->entry_width = grub_term_width (term) - 5;
  297. geo->first_entry_y = 2 /* two empty lines*/
  298. + 1 /* GNU GRUB version text */ + 1 /* top border */;
  299. geo->timeout_lines = 2;
  300. /* 3 lines for timeout message and bottom margin. 2 lines for the border. */
  301. geo->num_entries = grub_term_height (term) - geo->first_entry_y
  302. - 1 /* bottom border */
  303. - 1 /* empty line before info message*/
  304. - geo->timeout_lines /* timeout */
  305. - 1 /* empty final line */;
  306. msg_num_lines = print_message (nested, edit, term, 1);
  307. if (geo->num_entries - msg_num_lines < 3
  308. || geo->entry_width < 10)
  309. {
  310. geo->num_entries += 4;
  311. geo->first_entry_y -= 2;
  312. empty_lines = 0;
  313. geo->first_entry_x -= 1;
  314. geo->entry_width += 1;
  315. }
  316. if (geo->num_entries - msg_num_lines < 3
  317. || geo->entry_width < 10)
  318. {
  319. geo->num_entries += 2;
  320. geo->first_entry_y -= 1;
  321. geo->first_entry_x -= 1;
  322. geo->entry_width += 2;
  323. geo->border = 0;
  324. }
  325. if (geo->entry_width <= 0)
  326. geo->entry_width = 1;
  327. if (geo->num_entries - msg_num_lines < 3
  328. && geo->timeout_lines == 2)
  329. {
  330. geo->timeout_lines = 1;
  331. geo->num_entries++;
  332. }
  333. if (geo->num_entries - msg_num_lines < 3)
  334. {
  335. geo->num_entries += 1;
  336. geo->first_entry_y -= 1;
  337. version_msg = 0;
  338. }
  339. if (geo->num_entries - msg_num_lines >= 2)
  340. geo->num_entries -= msg_num_lines;
  341. else
  342. bottom_message = 0;
  343. /* By default, use the same colors for the menu. */
  344. old_color_normal = grub_term_normal_color;
  345. old_color_highlight = grub_term_highlight_color;
  346. grub_color_menu_normal = grub_term_normal_color;
  347. grub_color_menu_highlight = grub_term_highlight_color;
  348. /* Then give user a chance to replace them. */
  349. grub_parse_color_name_pair (&grub_color_menu_normal,
  350. grub_env_get ("menu_color_normal"));
  351. grub_parse_color_name_pair (&grub_color_menu_highlight,
  352. grub_env_get ("menu_color_highlight"));
  353. if (version_msg)
  354. grub_normal_init_page (term, empty_lines);
  355. else
  356. grub_term_cls (term);
  357. grub_term_normal_color = grub_color_menu_normal;
  358. grub_term_highlight_color = grub_color_menu_highlight;
  359. if (geo->border)
  360. draw_border (term, geo);
  361. grub_term_normal_color = old_color_normal;
  362. grub_term_highlight_color = old_color_highlight;
  363. geo->timeout_y = geo->first_entry_y + geo->num_entries
  364. + geo->border + empty_lines;
  365. if (bottom_message)
  366. {
  367. grub_term_gotoxy (term,
  368. (struct grub_term_coordinate) { GRUB_TERM_MARGIN,
  369. geo->timeout_y });
  370. print_message (nested, edit, term, 0);
  371. geo->timeout_y += msg_num_lines;
  372. }
  373. geo->right_margin = grub_term_width (term)
  374. - geo->first_entry_x
  375. - geo->entry_width - 1;
  376. }
  377. static void
  378. menu_text_print_timeout (int timeout, void *dataptr)
  379. {
  380. struct menu_viewer_data *data = dataptr;
  381. char *msg_translated = 0;
  382. grub_term_gotoxy (data->term,
  383. (struct grub_term_coordinate) { 0, data->geo.timeout_y });
  384. if (data->timeout_msg == TIMEOUT_TERSE
  385. || data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN)
  386. msg_translated = grub_xasprintf (_("%ds"), timeout);
  387. else
  388. msg_translated = grub_xasprintf (_("The highlighted entry will be executed automatically in %ds."), timeout);
  389. if (!msg_translated)
  390. {
  391. grub_print_error ();
  392. grub_errno = GRUB_ERR_NONE;
  393. return;
  394. }
  395. if (data->timeout_msg == TIMEOUT_UNKNOWN)
  396. {
  397. data->timeout_msg = grub_print_message_indented_real (msg_translated,
  398. 3, 1, data->term, 1)
  399. <= data->geo.timeout_lines ? TIMEOUT_NORMAL : TIMEOUT_TERSE;
  400. if (data->timeout_msg == TIMEOUT_TERSE)
  401. {
  402. grub_free (msg_translated);
  403. msg_translated = grub_xasprintf (_("%ds"), timeout);
  404. if (grub_term_width (data->term) < 10)
  405. data->timeout_msg = TIMEOUT_TERSE_NO_MARGIN;
  406. }
  407. }
  408. grub_print_message_indented (msg_translated,
  409. data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 3,
  410. data->timeout_msg == TIMEOUT_TERSE_NO_MARGIN ? 0 : 1,
  411. data->term);
  412. grub_free (msg_translated);
  413. grub_term_gotoxy (data->term,
  414. (struct grub_term_coordinate) {
  415. grub_term_cursor_x (&data->geo),
  416. data->geo.first_entry_y + data->offset });
  417. grub_term_refresh (data->term);
  418. }
  419. static void
  420. menu_text_set_chosen_entry (int entry, void *dataptr)
  421. {
  422. struct menu_viewer_data *data = dataptr;
  423. int oldoffset = data->offset;
  424. int complete_redraw = 0;
  425. data->offset = entry - data->first;
  426. if (data->offset > data->geo.num_entries - 1)
  427. {
  428. data->first = entry - (data->geo.num_entries - 1);
  429. data->offset = data->geo.num_entries - 1;
  430. complete_redraw = 1;
  431. }
  432. if (data->offset < 0)
  433. {
  434. data->offset = 0;
  435. data->first = entry;
  436. complete_redraw = 1;
  437. }
  438. if (complete_redraw)
  439. print_entries (data->menu, data);
  440. else
  441. {
  442. print_entry (data->geo.first_entry_y + oldoffset, 0,
  443. grub_menu_get_entry (data->menu, data->first + oldoffset),
  444. data);
  445. print_entry (data->geo.first_entry_y + data->offset, 1,
  446. grub_menu_get_entry (data->menu, data->first + data->offset),
  447. data);
  448. }
  449. grub_term_refresh (data->term);
  450. }
  451. static void
  452. menu_text_fini (void *dataptr)
  453. {
  454. struct menu_viewer_data *data = dataptr;
  455. grub_term_setcursor (data->term, 1);
  456. grub_term_cls (data->term);
  457. grub_free (data);
  458. }
  459. static void
  460. menu_text_clear_timeout (void *dataptr)
  461. {
  462. struct menu_viewer_data *data = dataptr;
  463. int i;
  464. for (i = 0; i < data->geo.timeout_lines;i++)
  465. {
  466. grub_term_gotoxy (data->term, (struct grub_term_coordinate) {
  467. 0, data->geo.timeout_y + i });
  468. grub_print_spaces (data->term, grub_term_width (data->term) - 1);
  469. }
  470. if (data->geo.num_entries <= 5 && !data->geo.border)
  471. {
  472. grub_term_gotoxy (data->term,
  473. (struct grub_term_coordinate) {
  474. data->geo.first_entry_x + data->geo.entry_width
  475. + data->geo.border + 1,
  476. data->geo.first_entry_y + data->geo.num_entries - 1
  477. });
  478. grub_putcode (' ', data->term);
  479. data->geo.timeout_lines = 0;
  480. data->geo.num_entries++;
  481. print_entries (data->menu, data);
  482. }
  483. grub_term_gotoxy (data->term,
  484. (struct grub_term_coordinate) {
  485. grub_term_cursor_x (&data->geo),
  486. data->geo.first_entry_y + data->offset });
  487. grub_term_refresh (data->term);
  488. }
  489. grub_err_t
  490. grub_menu_try_text (struct grub_term_output *term,
  491. int entry, grub_menu_t menu, int nested)
  492. {
  493. struct menu_viewer_data *data;
  494. struct grub_menu_viewer *instance;
  495. instance = grub_zalloc (sizeof (*instance));
  496. if (!instance)
  497. return grub_errno;
  498. data = grub_zalloc (sizeof (*data));
  499. if (!data)
  500. {
  501. grub_free (instance);
  502. return grub_errno;
  503. }
  504. data->term = term;
  505. instance->data = data;
  506. instance->set_chosen_entry = menu_text_set_chosen_entry;
  507. instance->print_timeout = menu_text_print_timeout;
  508. instance->clear_timeout = menu_text_clear_timeout;
  509. instance->fini = menu_text_fini;
  510. data->menu = menu;
  511. data->offset = entry;
  512. data->first = 0;
  513. grub_term_setcursor (data->term, 0);
  514. grub_menu_init_page (nested, 0, &data->geo, data->term);
  515. if (data->offset > data->geo.num_entries - 1)
  516. {
  517. data->first = data->offset - (data->geo.num_entries - 1);
  518. data->offset = data->geo.num_entries - 1;
  519. }
  520. print_entries (menu, data);
  521. grub_term_refresh (data->term);
  522. grub_menu_register_viewer (instance);
  523. return GRUB_ERR_NONE;
  524. }