1 /* ----------------------------------------------------------------------- *
2 *
3 * Copyright 2004-2008 H. Peter Anvin - All Rights Reserved
4 * Copyright 2009-2014 Intel Corporation; author: H. Peter Anvin
5 *
6 * This program 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, Inc., 51 Franklin St, Fifth Floor,
9 * Boston MA 02110-1301, USA; either version 2 of the License, or
10 * (at your option) any later version; incorporated herein by reference.
11 *
12 * ----------------------------------------------------------------------- */
13
14 /*
15 * menumain.c
16 *
17 * Simple menu system which displays a list and allows the user to select
18 * a command line and/or edit it.
19 */
20
21 #include <ctype.h>
22 #include <string.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <consoles.h>
26 #include <getkey.h>
27 #include <minmax.h>
28 #include <setjmp.h>
29 #include <limits.h>
30 #include <com32.h>
31 #include <core.h>
32 #include <syslinux/adv.h>
33 #include <syslinux/boot.h>
34
35 #include "menu.h"
36
37 /* The symbol "cm" always refers to the current menu across this file... */
38 static struct menu *cm;
39
40 const struct menu_parameter mparm[NPARAMS] = {
41 [P_WIDTH] = {"width", 0},
42 [P_MARGIN] = {"margin", 10},
43 [P_PASSWD_MARGIN] = {"passwordmargin", 3},
44 [P_MENU_ROWS] = {"rows", 12},
45 [P_TABMSG_ROW] = {"tabmsgrow", 18},
46 [P_CMDLINE_ROW] = {"cmdlinerow", 18},
47 [P_END_ROW] = {"endrow", -1},
48 [P_PASSWD_ROW] = {"passwordrow", 11},
49 [P_TIMEOUT_ROW] = {"timeoutrow", 20},
50 [P_HELPMSG_ROW] = {"helpmsgrow", 22},
51 [P_HELPMSGEND_ROW] = {"helpmsgendrow", -1},
52 [P_HSHIFT] = {"hshift", 0},
53 [P_VSHIFT] = {"vshift", 0},
54 [P_HIDDEN_ROW] = {"hiddenrow", -2},
55 };
56
57 /* These macros assume "cm" is a pointer to the current menu */
58 #define WIDTH (cm->mparm[P_WIDTH])
59 #define MARGIN (cm->mparm[P_MARGIN])
60 #define PASSWD_MARGIN (cm->mparm[P_PASSWD_MARGIN])
61 #define MENU_ROWS (cm->mparm[P_MENU_ROWS])
62 #define TABMSG_ROW (cm->mparm[P_TABMSG_ROW]+VSHIFT)
63 #define CMDLINE_ROW (cm->mparm[P_CMDLINE_ROW]+VSHIFT)
64 #define END_ROW (cm->mparm[P_END_ROW])
65 #define PASSWD_ROW (cm->mparm[P_PASSWD_ROW]+VSHIFT)
66 #define TIMEOUT_ROW (cm->mparm[P_TIMEOUT_ROW]+VSHIFT)
67 #define HELPMSG_ROW (cm->mparm[P_HELPMSG_ROW]+VSHIFT)
68 #define HELPMSGEND_ROW (cm->mparm[P_HELPMSGEND_ROW])
69 #define HSHIFT (cm->mparm[P_HSHIFT])
70 #define VSHIFT (cm->mparm[P_VSHIFT])
71 #define HIDDEN_ROW (cm->mparm[P_HIDDEN_ROW])
72
pad_line(const char * text,int align,int width)73 static char *pad_line(const char *text, int align, int width)
74 {
75 static char buffer[MAX_CMDLINE_LEN];
76 int n, p;
77
78 if (width >= (int)sizeof buffer)
79 return NULL; /* Can't do it */
80
81 n = strlen(text);
82 if (n >= width)
83 n = width;
84
85 memset(buffer, ' ', width);
86 buffer[width] = 0;
87 p = ((width - n) * align) >> 1;
88 memcpy(buffer + p, text, n);
89
90 return buffer;
91 }
92
93 /* Display an entry, with possible hotkey highlight. Assumes
94 that the current attribute is the non-hotkey one, and will
95 guarantee that as an exit condition as well. */
96 static void
display_entry(const struct menu_entry * entry,const char * attrib,const char * hotattrib,int width)97 display_entry(const struct menu_entry *entry, const char *attrib,
98 const char *hotattrib, int width)
99 {
100 const char *p = entry->displayname;
101 char marker;
102
103 if (!p)
104 p = "";
105
106 switch (entry->action) {
107 case MA_SUBMENU:
108 marker = '>';
109 break;
110 case MA_EXIT:
111 marker = '<';
112 break;
113 default:
114 marker = 0;
115 break;
116 }
117
118 if (marker)
119 width -= 2;
120
121 while (width) {
122 if (*p) {
123 if (*p == '^') {
124 p++;
125 if (*p && ((unsigned char)*p & ~0x20) == entry->hotkey) {
126 fputs(hotattrib, stdout);
127 putchar(*p++);
128 fputs(attrib, stdout);
129 width--;
130 }
131 } else {
132 putchar(*p++);
133 width--;
134 }
135 } else {
136 putchar(' ');
137 width--;
138 }
139 }
140
141 if (marker) {
142 putchar(' ');
143 putchar(marker);
144 }
145 }
146
draw_row(int y,int sel,int top,int sbtop,int sbbot)147 static void draw_row(int y, int sel, int top, int sbtop, int sbbot)
148 {
149 int i = (y - 4 - VSHIFT) + top;
150 int dis = (i < cm->nentries) && is_disabled(cm->menu_entries[i]);
151
152 printf("\033[%d;%dH\1#1\016x\017%s ",
153 y, MARGIN + 1 + HSHIFT,
154 (i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3");
155
156 if (i >= cm->nentries) {
157 fputs(pad_line("", 0, WIDTH - 2 * MARGIN - 4), stdout);
158 } else {
159 display_entry(cm->menu_entries[i],
160 (i == sel) ? "\1#5" : dis ? "\2#17" : "\1#3",
161 (i == sel) ? "\1#6" : dis ? "\2#17" : "\1#4",
162 WIDTH - 2 * MARGIN - 4);
163 }
164
165 if (cm->nentries <= MENU_ROWS) {
166 printf(" \1#1\016x\017");
167 } else if (sbtop > 0) {
168 if (y >= sbtop && y <= sbbot)
169 printf(" \1#7\016a\017");
170 else
171 printf(" \1#1\016x\017");
172 } else {
173 putchar(' '); /* Don't modify the scrollbar */
174 }
175 }
176
177 static jmp_buf timeout_jump;
178
mygetkey(clock_t timeout)179 int mygetkey(clock_t timeout)
180 {
181 clock_t t0, t;
182 clock_t tto, to;
183 int key;
184
185 if (!totaltimeout)
186 return get_key(stdin, timeout);
187
188 for (;;) {
189 tto = min(totaltimeout, INT_MAX);
190 to = timeout ? min(tto, timeout) : tto;
191
192 t0 = times(NULL);
193 key = get_key(stdin, to);
194 t = times(NULL) - t0;
195
196 if (totaltimeout <= t)
197 longjmp(timeout_jump, 1);
198
199 totaltimeout -= t;
200
201 if (key != KEY_NONE)
202 return key;
203
204 if (timeout) {
205 if (timeout <= t)
206 return KEY_NONE;
207
208 timeout -= t;
209 }
210 }
211 }
212
ask_passwd(const char * menu_entry)213 static int ask_passwd(const char *menu_entry)
214 {
215 char user_passwd[WIDTH], *p;
216 int done;
217 int key;
218 int x;
219 int rv;
220
221 printf("\033[%d;%dH\2#11\016l", PASSWD_ROW, PASSWD_MARGIN + 1);
222 for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
223 putchar('q');
224
225 printf("k\033[%d;%dHx", PASSWD_ROW + 1, PASSWD_MARGIN + 1);
226 for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
227 putchar(' ');
228
229 printf("x\033[%d;%dHm", PASSWD_ROW + 2, PASSWD_MARGIN + 1);
230 for (x = 2; x <= WIDTH - 2 * PASSWD_MARGIN - 1; x++)
231 putchar('q');
232
233 printf("j\017\033[%d;%dH\2#12 %s \033[%d;%dH\2#13",
234 PASSWD_ROW, (WIDTH - (strlen(cm->messages[MSG_PASSPROMPT]) + 2)) / 2,
235 cm->messages[MSG_PASSPROMPT], PASSWD_ROW + 1, PASSWD_MARGIN + 3);
236
237 drain_keyboard();
238
239 /* Actually allow user to type a password, then compare to the SHA1 */
240 done = 0;
241 p = user_passwd;
242
243 while (!done) {
244 key = mygetkey(0);
245
246 switch (key) {
247 case KEY_ENTER:
248 case KEY_CTRL('J'):
249 done = 1;
250 break;
251
252 case KEY_ESC:
253 case KEY_CTRL('C'):
254 p = user_passwd; /* No password entered */
255 done = 1;
256 break;
257
258 case KEY_BACKSPACE:
259 case KEY_DEL:
260 case KEY_DELETE:
261 if (p > user_passwd) {
262 printf("\b \b");
263 p--;
264 }
265 break;
266
267 case KEY_CTRL('U'):
268 while (p > user_passwd) {
269 printf("\b \b");
270 p--;
271 }
272 break;
273
274 default:
275 if (key >= ' ' && key <= 0xFF &&
276 (p - user_passwd) < WIDTH - 2 * PASSWD_MARGIN - 5) {
277 *p++ = key;
278 putchar('*');
279 }
280 break;
281 }
282 }
283
284 if (p == user_passwd)
285 return 0; /* No password entered */
286
287 *p = '\0';
288
289 rv = (cm->menu_master_passwd &&
290 passwd_compare(cm->menu_master_passwd, user_passwd))
291 || (menu_entry && passwd_compare(menu_entry, user_passwd));
292
293 /* Clean up */
294 memset(user_passwd, 0, WIDTH);
295 drain_keyboard();
296
297 return rv;
298 }
299
draw_menu(int sel,int top,int edit_line)300 static void draw_menu(int sel, int top, int edit_line)
301 {
302 int x, y;
303 int sbtop = 0, sbbot = 0;
304 const char *tabmsg;
305 int tabmsg_len;
306
307 if (cm->nentries > MENU_ROWS) {
308 int sblen = max(MENU_ROWS * MENU_ROWS / cm->nentries, 1);
309 sbtop = (MENU_ROWS - sblen + 1) * top / (cm->nentries - MENU_ROWS + 1);
310 sbbot = sbtop + sblen - 1;
311 sbtop += 4;
312 sbbot += 4; /* Starting row of scrollbar */
313 }
314
315 printf("\033[%d;%dH\1#1\016l", VSHIFT + 1, HSHIFT + MARGIN + 1);
316 for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
317 putchar('q');
318
319 printf("k\033[%d;%dH\1#1x\017\1#2 %s \1#1\016x",
320 VSHIFT + 2,
321 HSHIFT + MARGIN + 1, pad_line(cm->title, 1, WIDTH - 2 * MARGIN - 4));
322
323 printf("\033[%d;%dH\1#1t", VSHIFT + 3, HSHIFT + MARGIN + 1);
324 for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
325 putchar('q');
326 fputs("u\017", stdout);
327
328 for (y = 4 + VSHIFT; y < 4 + VSHIFT + MENU_ROWS; y++)
329 draw_row(y, sel, top, sbtop, sbbot);
330
331 printf("\033[%d;%dH\1#1\016m", y, HSHIFT + MARGIN + 1);
332 for (x = 2 + HSHIFT; x <= (WIDTH - 2 * MARGIN - 1) + HSHIFT; x++)
333 putchar('q');
334 fputs("j\017", stdout);
335
336 if (edit_line && cm->allowedit && !cm->menu_master_passwd)
337 tabmsg = cm->messages[MSG_TAB];
338 else
339 tabmsg = cm->messages[MSG_NOTAB];
340
341 tabmsg_len = strlen(tabmsg);
342
343 printf("\1#8\033[%d;%dH%s",
344 TABMSG_ROW, 1 + HSHIFT + ((WIDTH - tabmsg_len) >> 1), tabmsg);
345 printf("\1#0\033[%d;1H", END_ROW);
346 }
347
clear_screen(void)348 static void clear_screen(void)
349 {
350 fputs("\033e\033%@\033)0\033(B\1#0\033[?25l\033[2J", stdout);
351 }
352
display_help(const char * text)353 static void display_help(const char *text)
354 {
355 int row;
356 const char *p;
357
358 if (!text) {
359 text = "";
360 printf("\1#0\033[%d;1H", HELPMSG_ROW);
361 } else {
362 printf("\2#16\033[%d;1H", HELPMSG_ROW);
363 }
364
365 for (p = text, row = HELPMSG_ROW; *p && row <= HELPMSGEND_ROW; p++) {
366 switch (*p) {
367 case '\r':
368 case '\f':
369 case '\v':
370 case '\033':
371 break;
372 case '\n':
373 printf("\033[K\033[%d;1H", ++row);
374 break;
375 default:
376 putchar(*p);
377 }
378 }
379
380 fputs("\033[K", stdout);
381
382 while (row <= HELPMSGEND_ROW) {
383 printf("\033[K\033[%d;1H", ++row);
384 }
385 }
386
show_fkey(int key)387 static void show_fkey(int key)
388 {
389 int fkey;
390
391 while (1) {
392 switch (key) {
393 case KEY_F1:
394 fkey = 0;
395 break;
396 case KEY_F2:
397 fkey = 1;
398 break;
399 case KEY_F3:
400 fkey = 2;
401 break;
402 case KEY_F4:
403 fkey = 3;
404 break;
405 case KEY_F5:
406 fkey = 4;
407 break;
408 case KEY_F6:
409 fkey = 5;
410 break;
411 case KEY_F7:
412 fkey = 6;
413 break;
414 case KEY_F8:
415 fkey = 7;
416 break;
417 case KEY_F9:
418 fkey = 8;
419 break;
420 case KEY_F10:
421 fkey = 9;
422 break;
423 case KEY_F11:
424 fkey = 10;
425 break;
426 case KEY_F12:
427 fkey = 11;
428 break;
429 default:
430 fkey = -1;
431 break;
432 }
433
434 if (fkey == -1)
435 break;
436
437 if (cm->fkeyhelp[fkey].textname)
438 key = show_message_file(cm->fkeyhelp[fkey].textname,
439 cm->fkeyhelp[fkey].background);
440 else
441 break;
442 }
443 }
444
edit_cmdline(const char * input,int top)445 static const char *edit_cmdline(const char *input, int top)
446 {
447 static char cmdline[MAX_CMDLINE_LEN];
448 int key, len, prev_len, cursor;
449 int redraw = 1; /* We enter with the menu already drawn */
450
451 strlcpy(cmdline, input, MAX_CMDLINE_LEN);
452 cmdline[MAX_CMDLINE_LEN - 1] = '\0';
453
454 len = cursor = strlen(cmdline);
455 prev_len = 0;
456
457 for (;;) {
458 if (redraw > 1) {
459 /* Clear and redraw whole screen */
460 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
461 to avoid confusing the Linux console */
462 clear_screen();
463 draw_menu(-1, top, 1);
464 prev_len = 0;
465 }
466
467 if (redraw > 0) {
468 /* Redraw the command line */
469 printf("\033[?25l\033[%d;1H\1#9> \2#10%s",
470 CMDLINE_ROW, pad_line(cmdline, 0, max(len, prev_len)));
471 printf("\2#10\033[%d;3H%s\033[?25h",
472 CMDLINE_ROW, pad_line(cmdline, 0, cursor));
473 prev_len = len;
474 redraw = 0;
475 }
476
477 key = mygetkey(0);
478
479 switch (key) {
480 case KEY_CTRL('L'):
481 redraw = 2;
482 break;
483
484 case KEY_ENTER:
485 case KEY_CTRL('J'):
486 return cmdline;
487
488 case KEY_ESC:
489 case KEY_CTRL('C'):
490 return NULL;
491
492 case KEY_BACKSPACE:
493 case KEY_DEL:
494 if (cursor) {
495 memmove(cmdline + cursor - 1, cmdline + cursor,
496 len - cursor + 1);
497 len--;
498 cursor--;
499 redraw = 1;
500 }
501 break;
502
503 case KEY_CTRL('D'):
504 case KEY_DELETE:
505 if (cursor < len) {
506 memmove(cmdline + cursor, cmdline + cursor + 1, len - cursor);
507 len--;
508 redraw = 1;
509 }
510 break;
511
512 case KEY_CTRL('U'):
513 if (len) {
514 len = cursor = 0;
515 cmdline[len] = '\0';
516 redraw = 1;
517 }
518 break;
519
520 case KEY_CTRL('W'):
521 if (cursor) {
522 int prevcursor = cursor;
523
524 while (cursor && my_isspace(cmdline[cursor - 1]))
525 cursor--;
526
527 while (cursor && !my_isspace(cmdline[cursor - 1]))
528 cursor--;
529
530 memmove(cmdline + cursor, cmdline + prevcursor,
531 len - prevcursor + 1);
532 len -= (prevcursor - cursor);
533 redraw = 1;
534 }
535 break;
536
537 case KEY_LEFT:
538 case KEY_CTRL('B'):
539 if (cursor) {
540 cursor--;
541 redraw = 1;
542 }
543 break;
544
545 case KEY_RIGHT:
546 case KEY_CTRL('F'):
547 if (cursor < len) {
548 putchar(cmdline[cursor++]);
549 }
550 break;
551
552 case KEY_CTRL('K'):
553 if (cursor < len) {
554 cmdline[len = cursor] = '\0';
555 redraw = 1;
556 }
557 break;
558
559 case KEY_HOME:
560 case KEY_CTRL('A'):
561 if (cursor) {
562 cursor = 0;
563 redraw = 1;
564 }
565 break;
566
567 case KEY_END:
568 case KEY_CTRL('E'):
569 if (cursor != len) {
570 cursor = len;
571 redraw = 1;
572 }
573 break;
574
575 case KEY_F1:
576 case KEY_F2:
577 case KEY_F3:
578 case KEY_F4:
579 case KEY_F5:
580 case KEY_F6:
581 case KEY_F7:
582 case KEY_F8:
583 case KEY_F9:
584 case KEY_F10:
585 case KEY_F11:
586 case KEY_F12:
587 show_fkey(key);
588 redraw = 1;
589 break;
590
591 default:
592 if (key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN - 1) {
593 if (cursor == len) {
594 cmdline[len] = key;
595 cmdline[++len] = '\0';
596 cursor++;
597 putchar(key);
598 prev_len++;
599 } else {
600 memmove(cmdline + cursor + 1, cmdline + cursor,
601 len - cursor + 1);
602 cmdline[cursor++] = key;
603 len++;
604 redraw = 1;
605 }
606 }
607 break;
608 }
609 }
610 }
611
print_timeout_message(int tol,int row,const char * msg)612 static void print_timeout_message(int tol, int row, const char *msg)
613 {
614 static int last_msg_len = 0;
615 char buf[256];
616 int nc = 0, nnc, padc;
617 const char *tp = msg;
618 char tc;
619 char *tq = buf;
620
621 while ((size_t) (tq - buf) < (sizeof buf - 16) && (tc = *tp)) {
622 tp++;
623 if (tc == '#') {
624 nnc = sprintf(tq, "\2#15%d\2#14", tol);
625 tq += nnc;
626 nc += nnc - 8; /* 8 formatting characters */
627 } else if (tc == '{') {
628 /* Deal with {singular[,dual],plural} constructs */
629 struct {
630 const char *s, *e;
631 } tx[3];
632 const char *tpp;
633 int n = 0;
634
635 memset(tx, 0, sizeof tx);
636
637 tx[0].s = tp;
638
639 while (*tp && *tp != '}') {
640 if (*tp == ',' && n < 2) {
641 tx[n].e = tp;
642 n++;
643 tx[n].s = tp + 1;
644 }
645 tp++;
646 }
647 tx[n].e = tp;
648
649 if (*tp)
650 tp++; /* Skip final bracket */
651
652 if (!tx[1].s)
653 tx[1] = tx[0];
654 if (!tx[2].s)
655 tx[2] = tx[1];
656
657 /* Now [0] is singular, [1] is dual, and [2] is plural,
658 even if the user only specified some of them. */
659
660 switch (tol) {
661 case 1:
662 n = 0;
663 break;
664 case 2:
665 n = 1;
666 break;
667 default:
668 n = 2;
669 break;
670 }
671
672 for (tpp = tx[n].s; tpp < tx[n].e; tpp++) {
673 if ((size_t) (tq - buf) < (sizeof buf)) {
674 *tq++ = *tpp;
675 nc++;
676 }
677 }
678 } else {
679 *tq++ = tc;
680 nc++;
681 }
682 }
683 *tq = '\0';
684
685 if (nc >= last_msg_len) {
686 padc = 0;
687 } else {
688 padc = (last_msg_len - nc + 1) >> 1;
689 }
690
691 printf("\033[%d;%dH\2#14%*s%s%*s", row,
692 HSHIFT + 1 + ((WIDTH - nc) >> 1) - padc,
693 padc, "", buf, padc, "");
694
695 last_msg_len = nc;
696 }
697
698 /* Set the background screen, etc. */
prepare_screen_for_menu(void)699 static void prepare_screen_for_menu(void)
700 {
701 console_color_table = cm->color_table;
702 console_color_table_size = menu_color_table_size;
703 set_background(cm->menu_background);
704 }
705
do_hidden_menu(void)706 static const char *do_hidden_menu(void)
707 {
708 int key;
709 int timeout_left, this_timeout;
710
711 clear_screen();
712
713 if (!setjmp(timeout_jump)) {
714 timeout_left = cm->timeout;
715
716 while (!cm->timeout || timeout_left) {
717 int tol = timeout_left / CLK_TCK;
718
719 print_timeout_message(tol, HIDDEN_ROW, cm->messages[MSG_AUTOBOOT]);
720
721 this_timeout = min(timeout_left, CLK_TCK);
722 key = mygetkey(this_timeout);
723
724 if (key != KEY_NONE) {
725 /* Clear the message from the screen */
726 print_timeout_message(0, HIDDEN_ROW, "");
727 return hide_key[key]; /* NULL if no MENU HIDEKEY in effect */
728 }
729
730 timeout_left -= this_timeout;
731 }
732 }
733
734 /* Clear the message from the screen */
735 print_timeout_message(0, HIDDEN_ROW, "");
736
737 if (cm->ontimeout)
738 return cm->ontimeout;
739 else
740 return cm->menu_entries[cm->defentry]->cmdline; /* Default entry */
741 }
742
run_menu(void)743 static const char *run_menu(void)
744 {
745 int key;
746 int done = 0;
747 volatile int entry = cm->curentry;
748 int prev_entry = -1;
749 volatile int top = cm->curtop;
750 int prev_top = -1;
751 int clear = 1, to_clear;
752 const char *cmdline = NULL;
753 volatile clock_t key_timeout, timeout_left, this_timeout;
754 const struct menu_entry *me;
755 bool hotkey = false;
756
757 /* Note: for both key_timeout and timeout == 0 means no limit */
758 timeout_left = key_timeout = cm->timeout;
759
760 /* If we're in shiftkey mode, exit immediately unless a shift key
761 is pressed */
762 if (shiftkey && !shift_is_held()) {
763 return cm->menu_entries[cm->defentry]->cmdline;
764 } else {
765 shiftkey = 0;
766 }
767
768 /* Do this before hiddenmenu handling, so we show the background */
769 prepare_screen_for_menu();
770
771 /* Handle hiddenmenu */
772 if (hiddenmenu) {
773 cmdline = do_hidden_menu();
774 if (cmdline)
775 return cmdline;
776
777 /* Otherwise display the menu now; the timeout has already been
778 cancelled, since the user pressed a key. */
779 hiddenmenu = 0;
780 key_timeout = 0;
781 }
782
783 /* Handle both local and global timeout */
784 if (setjmp(timeout_jump)) {
785 entry = cm->defentry;
786
787 if (top < 0 || top < entry - MENU_ROWS + 1)
788 top = max(0, entry - MENU_ROWS + 1);
789 else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
790 top = min(entry, max(0, cm->nentries - MENU_ROWS));
791
792 draw_menu(cm->ontimeout ? -1 : entry, top, 1);
793 cmdline =
794 cm->ontimeout ? cm->ontimeout : cm->menu_entries[entry]->cmdline;
795 done = 1;
796 }
797
798 while (!done) {
799 if (entry <= 0) {
800 entry = 0;
801 while (entry < cm->nentries && is_disabled(cm->menu_entries[entry]))
802 entry++;
803 }
804 if (entry >= cm->nentries - 1) {
805 entry = cm->nentries - 1;
806 while (entry > 0 && is_disabled(cm->menu_entries[entry]))
807 entry--;
808 }
809
810 me = cm->menu_entries[entry];
811
812 if (top < 0 || top < entry - MENU_ROWS + 1)
813 top = max(0, entry - MENU_ROWS + 1);
814 else if (top > entry || top > max(0, cm->nentries - MENU_ROWS))
815 top = min(entry, max(0, cm->nentries - MENU_ROWS));
816
817 /* Start with a clear screen */
818 if (clear) {
819 /* Clear and redraw whole screen */
820 /* Enable ASCII on G0 and DEC VT on G1; do it in this order
821 to avoid confusing the Linux console */
822 if (clear >= 2)
823 prepare_screen_for_menu();
824 clear_screen();
825 clear = 0;
826 prev_entry = prev_top = -1;
827 }
828
829 if (top != prev_top) {
830 draw_menu(entry, top, 1);
831 display_help(me->helptext);
832 } else if (entry != prev_entry) {
833 draw_row(prev_entry - top + 4 + VSHIFT, entry, top, 0, 0);
834 draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
835 display_help(me->helptext);
836 }
837
838 prev_entry = entry;
839 prev_top = top;
840 cm->curentry = entry;
841 cm->curtop = top;
842
843 /* Cursor movement cancels timeout */
844 if (entry != cm->defentry)
845 key_timeout = 0;
846
847 if (key_timeout) {
848 int tol = timeout_left / CLK_TCK;
849 print_timeout_message(tol, TIMEOUT_ROW, cm->messages[MSG_AUTOBOOT]);
850 to_clear = 1;
851 } else {
852 to_clear = 0;
853 }
854
855 if (hotkey && me->immediate) {
856 /* If the hotkey was flagged immediate, simulate pressing ENTER */
857 key = KEY_ENTER;
858 } else {
859 this_timeout = min(min(key_timeout, timeout_left),
860 (clock_t) CLK_TCK);
861 key = mygetkey(this_timeout);
862
863 if (key != KEY_NONE) {
864 timeout_left = key_timeout;
865 if (to_clear)
866 printf("\033[%d;1H\1#0\033[K", TIMEOUT_ROW);
867 }
868 }
869
870 hotkey = false;
871
872 switch (key) {
873 case KEY_NONE: /* Timeout */
874 /* This is somewhat hacky, but this at least lets the user
875 know what's going on, and still deals with "phantom inputs"
876 e.g. on serial ports.
877
878 Warning: a timeout will boot the default entry without any
879 password! */
880 if (key_timeout) {
881 if (timeout_left <= this_timeout)
882 longjmp(timeout_jump, 1);
883
884 timeout_left -= this_timeout;
885 }
886 break;
887
888 case KEY_CTRL('L'):
889 clear = 1;
890 break;
891
892 case KEY_ENTER:
893 case KEY_CTRL('J'):
894 key_timeout = 0; /* Cancels timeout */
895 if (me->passwd) {
896 clear = 1;
897 done = ask_passwd(me->passwd);
898 } else {
899 done = 1;
900 }
901 cmdline = NULL;
902 if (done) {
903 switch (me->action) {
904 case MA_CMD:
905 cmdline = me->cmdline;
906 break;
907 case MA_SUBMENU:
908 case MA_GOTO:
909 case MA_EXIT:
910 done = 0;
911 clear = 2;
912 cm = me->submenu;
913 entry = cm->curentry;
914 top = cm->curtop;
915 break;
916 case MA_QUIT:
917 /* Quit menu system */
918 done = 1;
919 clear = 1;
920 draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
921 break;
922 case MA_HELP:
923 key = show_message_file(me->cmdline, me->background);
924 /* If the exit was an F-key, display that help screen */
925 show_fkey(key);
926 done = 0;
927 clear = 1;
928 break;
929 default:
930 done = 0;
931 break;
932 }
933 }
934 if (done && !me->passwd) {
935 /* Only save a new default if we don't have a password... */
936 if (me->save && me->label) {
937 syslinux_setadv(ADV_MENUSAVE, strlen(me->label), me->label);
938 syslinux_adv_write();
939 }
940 }
941 break;
942
943 case KEY_UP:
944 case KEY_CTRL('P'):
945 while (entry > 0) {
946 entry--;
947 if (entry < top)
948 top -= MENU_ROWS;
949 if (!is_disabled(cm->menu_entries[entry]))
950 break;
951 }
952 break;
953
954 case KEY_DOWN:
955 case KEY_CTRL('N'):
956 while (entry < cm->nentries - 1) {
957 entry++;
958 if (entry >= top + MENU_ROWS)
959 top += MENU_ROWS;
960 if (!is_disabled(cm->menu_entries[entry]))
961 break;
962 }
963 break;
964
965 case KEY_PGUP:
966 case KEY_LEFT:
967 case KEY_CTRL('B'):
968 case '<':
969 entry -= MENU_ROWS;
970 top -= MENU_ROWS;
971 while (entry > 0 && is_disabled(cm->menu_entries[entry])) {
972 entry--;
973 if (entry < top)
974 top -= MENU_ROWS;
975 }
976 break;
977
978 case KEY_PGDN:
979 case KEY_RIGHT:
980 case KEY_CTRL('F'):
981 case '>':
982 case ' ':
983 entry += MENU_ROWS;
984 top += MENU_ROWS;
985 while (entry < cm->nentries - 1
986 && is_disabled(cm->menu_entries[entry])) {
987 entry++;
988 if (entry >= top + MENU_ROWS)
989 top += MENU_ROWS;
990 }
991 break;
992
993 case '-':
994 while (entry > 0) {
995 entry--;
996 top--;
997 if (!is_disabled(cm->menu_entries[entry]))
998 break;
999 }
1000 break;
1001
1002 case '+':
1003 while (entry < cm->nentries - 1) {
1004 entry++;
1005 top++;
1006 if (!is_disabled(cm->menu_entries[entry]))
1007 break;
1008 }
1009 break;
1010
1011 case KEY_CTRL('A'):
1012 case KEY_HOME:
1013 top = entry = 0;
1014 break;
1015
1016 case KEY_CTRL('E'):
1017 case KEY_END:
1018 entry = cm->nentries - 1;
1019 top = max(0, cm->nentries - MENU_ROWS);
1020 break;
1021
1022 case KEY_F1:
1023 case KEY_F2:
1024 case KEY_F3:
1025 case KEY_F4:
1026 case KEY_F5:
1027 case KEY_F6:
1028 case KEY_F7:
1029 case KEY_F8:
1030 case KEY_F9:
1031 case KEY_F10:
1032 case KEY_F11:
1033 case KEY_F12:
1034 show_fkey(key);
1035 clear = 1;
1036 break;
1037
1038 case KEY_TAB:
1039 if (cm->allowedit && me->action == MA_CMD) {
1040 int ok = 1;
1041
1042 key_timeout = 0; /* Cancels timeout */
1043 draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
1044
1045 if (cm->menu_master_passwd) {
1046 ok = ask_passwd(NULL);
1047 clear_screen();
1048 draw_menu(-1, top, 0);
1049 } else {
1050 /* Erase [Tab] message and help text */
1051 printf("\033[%d;1H\1#0\033[K", TABMSG_ROW);
1052 display_help(NULL);
1053 }
1054
1055 if (ok) {
1056 cmdline = edit_cmdline(me->cmdline, top);
1057 done = !!cmdline;
1058 clear = 1; /* In case we hit [Esc] and done is null */
1059 } else {
1060 draw_row(entry - top + 4 + VSHIFT, entry, top, 0, 0);
1061 }
1062 }
1063 break;
1064 case KEY_CTRL('C'): /* Ctrl-C */
1065 case KEY_ESC: /* Esc */
1066 if (cm->parent) {
1067 cm = cm->parent;
1068 clear = 2;
1069 entry = cm->curentry;
1070 top = cm->curtop;
1071 } else if (cm->allowedit) {
1072 done = 1;
1073 clear = 1;
1074 key_timeout = 0;
1075
1076 draw_row(entry - top + 4 + VSHIFT, -1, top, 0, 0);
1077
1078 if (cm->menu_master_passwd)
1079 done = ask_passwd(NULL);
1080 }
1081 break;
1082 default:
1083 if (key > 0 && key < 0xFF) {
1084 key &= ~0x20; /* Upper case */
1085 if (cm->menu_hotkeys[key]) {
1086 key_timeout = 0;
1087 entry = cm->menu_hotkeys[key]->entry;
1088 /* Should we commit at this point? */
1089 hotkey = true;
1090 }
1091 }
1092 break;
1093 }
1094 }
1095
1096 printf("\033[?25h"); /* Show cursor */
1097
1098 /* Return the label name so localboot and ipappend work */
1099 return cmdline;
1100 }
1101
main(int argc,char * argv[])1102 int main(int argc, char *argv[])
1103 {
1104 const char *cmdline;
1105 struct menu *m;
1106 int rows, cols;
1107 int i;
1108
1109 (void)argc;
1110
1111 parse_configs(argv + 1);
1112
1113 /*
1114 * We don't start the console until we have parsed the configuration
1115 * file, since the configuration file might impact the console
1116 * configuration, e.g. MENU RESOLUTION.
1117 */
1118 start_console();
1119 if (getscreensize(1, &rows, &cols)) {
1120 /* Unknown screen size? */
1121 rows = 24;
1122 cols = 80;
1123 }
1124
1125 /* Some postprocessing for all menus */
1126 for (m = menu_list; m; m = m->next) {
1127 if (!m->mparm[P_WIDTH])
1128 m->mparm[P_WIDTH] = cols;
1129
1130 /* If anyone has specified negative parameters, consider them
1131 relative to the bottom row of the screen. */
1132 for (i = 0; i < NPARAMS; i++)
1133 if (m->mparm[i] < 0)
1134 m->mparm[i] = max(m->mparm[i] + rows, 0);
1135 }
1136
1137 cm = start_menu;
1138
1139 if (!cm->nentries) {
1140 fputs("Initial menu has no LABEL entries!\n", stdout);
1141 return 1; /* Error! */
1142 }
1143
1144 for (;;) {
1145 local_cursor_enable(true);
1146 cmdline = run_menu();
1147
1148 if (clearmenu)
1149 clear_screen();
1150
1151 local_cursor_enable(false);
1152 printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
1153
1154 if (cmdline) {
1155 uint32_t type = parse_image_type(cmdline);
1156
1157 execute(cmdline, type, false);
1158 if (cm->onerror) {
1159 type = parse_image_type(cm->onerror);
1160 execute(cm->onerror, type, true);
1161 }
1162 } else {
1163 return 0; /* Exit */
1164 }
1165 }
1166 }
1167