9b91868693771727c2ae214d0c3c77fefa8aa3f8
[profile/ivi/syslinux.git] / com32 / modules / menu.c
1 #ident "$Id$"
2 /* ----------------------------------------------------------------------- *
3  *   
4  *   Copyright 2004 H. Peter Anvin - All Rights Reserved
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., 53 Temple Place Ste 330,
9  *   Boston MA 02111-1307, USA; either version 2 of the License, or
10  *   (at your option) any later version; incorporated herein by reference.
11  *
12  * ----------------------------------------------------------------------- */
13
14 /*
15  * menu.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 #define _GNU_SOURCE             /* Needed for asprintf() on Linux */
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 <time.h>
29 #include <sys/times.h>
30 #include <unistd.h>
31 #ifdef __COM32__
32 #include <com32.h>
33 #endif
34
35 #include "menu.h"
36
37 #ifndef CLK_TCK
38 # define CLK_TCK sysconf(_SC_CLK_TCK)
39 #endif
40
41 struct menu_attrib {
42   const char *border;           /* Border area */
43   const char *title;            /* Title bar */
44   const char *unsel;            /* Unselected menu item */
45   const char *hotkey;           /* Unselected hotkey */
46   const char *sel;              /* Selected */
47   const char *hotsel;           /* Selected hotkey */
48   const char *more;             /* [More] tag */
49   const char *tabmsg;           /* Press [Tab] message */
50   const char *cmdmark;          /* Command line marker */
51   const char *cmdline;          /* Command line */
52   const char *screen;           /* Rest of the screen */
53 };
54
55 const struct menu_attrib default_attrib = {
56   .border  = "\033[0;30;44m",
57   .title   = "\033[1;36;44m",
58   .unsel   = "\033[0;37;44m",
59   .hotkey  = "\033[1;37;44m",
60   .sel     = "\033[0;30;47m",
61   .hotsel  = "\033[1;30;47m",
62   .more    = "\033[0;37;44m",
63   .tabmsg  = "\033[0;31;40m",
64   .cmdmark = "\033[1;36;40m",
65   .cmdline = "\033[0;37;40m",
66   .screen  = "\033[0;37;40m",
67 };
68
69 const struct menu_attrib *menu_attrib = &default_attrib;
70
71 #define WIDTH           80
72 #define MARGIN          10
73 #define MENU_ROWS       12
74 #define TABMSG_ROW      18
75 #define CMDLINE_ROW     20
76 #define END_ROW         24
77
78 char *pad_line(const char *text, int align, int width)
79 {
80   static char buffer[256];
81   int n, p;
82
83   if ( width >= (int) sizeof buffer )
84     return NULL;                /* Can't do it */
85
86   n = strlen(text);
87   if ( n >= width )
88     n = width;
89
90   memset(buffer, ' ', width);
91   buffer[width] = 0;
92   p = ((width-n)*align)>>1;
93   memcpy(buffer+p, text, n);
94
95   return buffer;
96 }
97
98 /* Display an entry, with possible hotkey highlight.  Assumes
99    that the current attribute is the non-hotkey one, and will
100    guarantee that as an exit condition as well. */
101 void display_entry(const struct menu_entry *entry, const char *attrib,
102                    const char *hotattrib, int width)
103 {
104   const char *p = entry->displayname;
105
106   while ( width ) {
107     if ( *p ) {
108       if ( *p == '^' ) {
109         p++;
110         if ( *p && (unsigned char)*p == entry->hotkey ) {
111           fputs(hotattrib, stdout);
112           putchar(*p++);
113           fputs(attrib, stdout);
114           width--;
115         }
116       } else {
117         putchar(*p++);
118         width--;
119       }
120     } else {
121       putchar(' ');
122       width--;
123     }
124   }
125 }
126
127 void draw_menu(int sel, int top)
128 {
129   int x, y;
130
131   printf("\033[1;%dH%s\311", MARGIN+1, menu_attrib->border);
132   for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
133     putchar('\315');
134   putchar('\273');
135
136   printf("\033[2;%dH\272%s %s %s\272",
137          MARGIN+1,
138          menu_attrib->title,
139          pad_line(menu_title, 1, WIDTH-2*MARGIN-4),
140          menu_attrib->border);
141
142   printf("\033[3;%dH\307", MARGIN+1);
143   for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
144     putchar('\304');
145   putchar('\266');
146
147   if ( top != 0 )
148     printf("%s\033[3;%dH[-]%s",
149            menu_attrib->more, WIDTH-MARGIN-5,
150            menu_attrib->border);
151
152   for ( y = 4 ; y < 4+MENU_ROWS ; y++ ) {
153     int i = (y-4)+top;
154
155     printf("\033[%d;%dH\272%s ",
156            y, MARGIN+1,
157            (i == sel) ? menu_attrib->sel : menu_attrib->unsel);
158
159     if ( i >= nentries ) {
160       fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout);
161     } else {
162       display_entry(&menu_entries[i],
163                     (i == sel) ? menu_attrib->sel : menu_attrib->unsel,
164                     (i == sel) ? menu_attrib->hotsel : menu_attrib->hotkey,
165                     WIDTH-2*MARGIN-4);
166     }
167     
168     printf(" %s\272", menu_attrib->border);
169   }
170
171   printf("\033[%d;%dH\310", y, MARGIN+1);
172   for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ )
173     putchar('\315');
174   putchar('\274');
175
176   if ( top < nentries-MENU_ROWS )
177     printf("%s\033[%d;%dH[+]", menu_attrib->more, y, WIDTH-MARGIN-5);
178
179   if ( allowedit )
180     printf("%s\033[%d;1H%s", menu_attrib->tabmsg, TABMSG_ROW,
181            pad_line("Press [Tab] to edit options", 1, WIDTH));
182
183   printf("%s\033[%d;1H", menu_attrib->screen, END_ROW);
184 }
185
186 const char *edit_cmdline(char *input, int top)
187 {
188   static char cmdline[MAX_CMDLINE_LEN];
189   int key, len;
190   int redraw = 2;
191
192   strncpy(cmdline, input, MAX_CMDLINE_LEN);
193   cmdline[MAX_CMDLINE_LEN-1] = '\0';
194
195   len = strlen(cmdline);
196
197   for (;;) {
198     if ( redraw > 1 ) {
199       /* Clear and redraw whole screen */
200       printf("%s\033[2J", menu_attrib->screen);
201       draw_menu(-1, top);
202     }
203
204     if ( redraw > 0 ) {
205       /* Redraw the command line */
206       printf("\033[%d;1H%s> %s%s",
207              CMDLINE_ROW, menu_attrib->cmdmark,
208              menu_attrib->cmdline, pad_line(cmdline, 0, MAX_CMDLINE_LEN-1));
209       printf("%s\033[%d;3H%s",
210              menu_attrib->cmdline, CMDLINE_ROW, cmdline);
211       redraw = 0;
212     }
213
214     key = get_key(stdin, 0);
215
216     /* FIX: should handle arrow keys and edit-in-middle */
217
218     switch( key ) {
219     case KEY_CTRL('L'):
220       redraw = 2;
221       break;
222     case KEY_ENTER:
223     case KEY_CTRL('J'):
224       return cmdline;
225     case KEY_ESC:
226     case KEY_CTRL('C'):
227       return NULL;
228     case KEY_BACKSPACE:
229     case KEY_DEL:
230     case '\x7F':
231       if ( len ) {
232         cmdline[--len] = '\0';
233         redraw = 1;
234       }
235       break;
236     case KEY_CTRL('U'):
237       if ( len ) {
238         len = 0;
239         cmdline[len] = '\0';
240         redraw = 1;
241       }
242       break;
243     case KEY_CTRL('W'):
244       if ( len ) {
245         int wasbs = (cmdline[len-1] <= ' ');
246         while ( len && (cmdline[len-1] <= ' ' || !wasbs) ) {
247           len--;
248           wasbs = wasbs || (cmdline[len-1] <= ' ');
249         }
250         cmdline[len] = '\0';
251         redraw = 1;
252       }
253       break;
254     default:
255       if ( key >= ' ' && key <= 0xFF && len < MAX_CMDLINE_LEN-1 ) {
256         cmdline[len] = key;
257         cmdline[++len] = '\0';
258         putchar(key);
259       }
260       break;
261     }
262   }
263 }
264
265 const char *run_menu(void)
266 {
267   int key;
268   int done = 0;
269   int entry = defentry;
270   int top = 0;
271   int clear = 1;
272   const char *cmdline = NULL;
273   clock_t key_timeout;
274
275   /* Convert timeout from deciseconds to clock ticks */
276   /* Note: for both key_timeout and timeout == 0 means no limit */
277   key_timeout = (clock_t)(CLK_TCK*timeout+9)/10;
278
279   printf("\033[?25l");          /* Hide cursor */
280
281   while ( !done ) {
282     if ( entry < 0 )
283       entry = 0;
284     else if ( entry >= nentries )
285       entry = nentries-1;
286
287     if ( top < 0 || top < entry-MENU_ROWS+1 )
288       top = max(0, entry-MENU_ROWS+1);
289     else if ( top > entry )
290       top = entry;
291
292     /* Start with a clear screen */
293     if ( clear )
294       printf("%s\033[2J", menu_attrib->screen);
295     clear = 0;
296
297     draw_menu(entry, top);
298
299     key = get_key(stdin, key_timeout);
300     switch ( key ) {
301     case KEY_NONE:              /* Timeout */
302       /* This is somewhat hacky, but this at least lets the user
303          know what's going on, and still deals with "phantom inputs"
304          e.g. on serial ports. */
305       if ( entry != defentry )
306         entry = defentry;
307       else {
308         cmdline = menu_entries[defentry].label;
309         done = 1;
310       }
311       break;
312     case KEY_CTRL('L'):
313       clear = 1;
314       break;
315     case KEY_ENTER:
316     case KEY_CTRL('J'):
317       cmdline = menu_entries[entry].label;
318       done = 1;
319       break;
320     case 'P':
321     case 'p':
322     case KEY_UP:
323       entry--;
324       break;
325     case 'N':
326     case 'n':
327     case KEY_DOWN:
328       entry++;
329       break;
330     case KEY_CTRL('P'):
331     case KEY_PGUP:
332       entry -= MENU_ROWS;
333       top   -= MENU_ROWS;
334       break;
335     case KEY_CTRL('N'):
336     case KEY_PGDN:
337     case ' ':
338       entry += MENU_ROWS;
339       top   += MENU_ROWS;
340       break;
341     case '-':
342       entry--;
343       top--;
344       break;
345     case '+':
346       entry++;
347       top++;
348       break;
349     case KEY_TAB:
350       if ( allowedit ) {
351         printf("\033[?25h");            /* Show cursor */
352         cmdline = edit_cmdline(menu_entries[entry].cmdline, top);
353         printf("\033[?25l");            /* Hide cursor */
354         done = !!cmdline;
355         clear = 1;              /* In case we hit [Esc] and done is null */
356       }
357       break;
358     case KEY_CTRL('C'):         /* Ctrl-C */
359     case KEY_ESC:               /* Esc */
360       if ( allowedit )
361         done = 1;
362       break;
363     default:
364       if ( key > 0 && key < 0xFF ) {
365         key &= ~0x20;           /* Upper case */
366         if ( menu_hotkeys[key] ) {
367           entry = menu_hotkeys[key] - menu_entries;
368           /* Should we commit at this point? */
369         }
370       }
371       break;
372     }
373   }
374
375   printf("\033[?25h");          /* Show cursor */
376
377   /* Return the label name so localboot and ipappend work */
378   return cmdline;
379 }
380
381
382 void __attribute__((noreturn)) execute(const char *cmdline)
383 {
384 #ifdef __COM32__
385   static com32sys_t ireg;
386
387   strcpy(__com32.cs_bounce, cmdline);
388   ireg.eax.w[0] = 0x0003;       /* Run command */
389   ireg.ebx.w[0] = OFFS(__com32.cs_bounce);
390   ireg.es = SEG(__com32.cs_bounce);
391   __intcall(0x22, &ireg, NULL);
392   exit(255);  /* Shouldn't return */
393 #else
394   /* For testing... */
395   printf("\n>>> %s\n", cmdline);
396   exit(0);
397 #endif
398 }
399
400 int main(int argc, char *argv[])
401 {
402   const char *cmdline;
403
404   (void)argc;
405
406   console_ansi_raw();
407   fputs("\033%@\033(U", stdout); /* Enable CP 437 graphics on a real console */
408
409   parse_config(argv[1]);
410
411   cmdline = run_menu();
412   printf("\033[?25h\033[%d;1H\033[0m", END_ROW);
413   if ( cmdline )
414     execute(cmdline);
415   else
416     return 0;
417 }