📄 menu.c
字号:
#ident "$Id$"/* ----------------------------------------------------------------------- * * * Copyright 2004-2005 H. Peter Anvin - All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, Inc., 53 Temple Place Ste 330, * Boston MA 02111-1307, USA; either version 2 of the License, or * (at your option) any later version; incorporated herein by reference. * * ----------------------------------------------------------------------- *//* * menu.c * * Simple menu system which displays a list and allows the user to select * a command line and/or edit it. */#define _GNU_SOURCE /* Needed for asprintf() on Linux */#include <ctype.h>#include <string.h>#include <stdlib.h>#include <stdio.h>#include <consoles.h>#include <getkey.h>#include <minmax.h>#include <setjmp.h>#include <limits.h>#include <sha1.h>#include <base64.h>#ifdef __COM32__#include <com32.h>#endif#include "menu.h"#ifndef CLK_TCK# define CLK_TCK sysconf(_SC_CLK_TCK)#endifstruct menu_attrib { const char *border; /* Border area */ const char *title; /* Title bar */ const char *unsel; /* Unselected menu item */ const char *hotkey; /* Unselected hotkey */ const char *sel; /* Selected */ const char *hotsel; /* Selected hotkey */ const char *scrollbar; /* Scroll bar */ const char *tabmsg; /* Press [Tab] message */ const char *cmdmark; /* Command line marker */ const char *cmdline; /* Command line */ const char *screen; /* Rest of the screen */ const char *pwdborder; /* Password box border */ const char *pwdheader; /* Password box header */ const char *pwdentry; /* Password box contents */ const char *timeout_msg; /* Timeout message */ const char *timeout; /* Timeout counter */};static const struct menu_attrib default_attrib = { .border = "\033[0;30;44m", .title = "\033[1;36;44m", .unsel = "\033[0;37;44m", .hotkey = "\033[1;37;44m", .sel = "\033[0;7;37;40m", .hotsel = "\033[1;7;37;40m", .scrollbar = "\033[0;30;44m", .tabmsg = "\033[0;31;40m", .cmdmark = "\033[1;36;40m", .cmdline = "\033[0;37;40m", .screen = "\033[0;37;40m", .pwdborder = "\033[0;30;47m", .pwdheader = "\033[0;31;47m", .pwdentry = "\033[0;30;47m", .timeout_msg = "\033[0;37;40m", .timeout = "\033[1;37;40m",};static const struct menu_attrib *menu_attrib = &default_attrib;struct menu_parameter mparm[] = { { "width", 80 }, { "margin", 10 }, { "passwordmargin", 3 }, { "rows", 12 }, { "tabmsgrow", 18 }, { "cmdlinerow", 18 }, { "endrow", 24 }, { "passwordrow", 11 }, { "timeoutrow", 20 }, { NULL, 0 }};#define WIDTH mparm[0].value#define MARGIN mparm[1].value#define PASSWD_MARGIN mparm[2].value#define MENU_ROWS mparm[3].value#define TABMSG_ROW mparm[4].value#define CMDLINE_ROW mparm[5].value#define END_ROW mparm[6].value#define PASSWD_ROW mparm[7].value#define TIMEOUT_ROW mparm[8].valuestatic char *pad_line(const char *text, int align, int width){ static char buffer[MAX_CMDLINE_LEN]; int n, p; if ( width >= (int) sizeof buffer ) return NULL; /* Can't do it */ n = strlen(text); if ( n >= width ) n = width; memset(buffer, ' ', width); buffer[width] = 0; p = ((width-n)*align)>>1; memcpy(buffer+p, text, n); return buffer;}/* Display an entry, with possible hotkey highlight. Assumes that the current attribute is the non-hotkey one, and will guarantee that as an exit condition as well. */static voiddisplay_entry(const struct menu_entry *entry, const char *attrib, const char *hotattrib, int width){ const char *p = entry->displayname; while ( width ) { if ( *p ) { if ( *p == '^' ) { p++; if ( *p && ((unsigned char)*p & ~0x20) == entry->hotkey ) { fputs(hotattrib, stdout); putchar(*p++); fputs(attrib, stdout); width--; } } else { putchar(*p++); width--; } } else { putchar(' '); width--; } }}static voiddraw_row(int y, int sel, int top, int sbtop, int sbbot){ int i = (y-4)+top; printf("\033[%d;%dH%s\016x\017%s ", y, MARGIN+1, menu_attrib->border, (i == sel) ? menu_attrib->sel : menu_attrib->unsel); if ( i >= nentries ) { fputs(pad_line("", 0, WIDTH-2*MARGIN-4), stdout); } else { display_entry(&menu_entries[i], (i == sel) ? menu_attrib->sel : menu_attrib->unsel, (i == sel) ? menu_attrib->hotsel : menu_attrib->hotkey, WIDTH-2*MARGIN-4); } if ( nentries <= MENU_ROWS ) { printf(" %s\016x\017", menu_attrib->border); } else if ( sbtop > 0 ) { if ( y >= sbtop && y <= sbbot ) printf(" %s\016a\017", menu_attrib->scrollbar); else printf(" %s\016x\017", menu_attrib->border); } else { putchar(' '); /* Don't modify the scrollbar */ }}static intpasswd_compare(const char *passwd, const char *entry){ const char *p; SHA1_CTX ctx; unsigned char sha1[20], pwdsha1[20]; if ( passwd[0] != '$' ) /* Plaintext passwd, yuck! */ return !strcmp(entry, passwd); if ( strncmp(passwd, "$4$", 3) ) return 0; /* Only SHA-1 passwds supported */ SHA1Init(&ctx); if ( (p = strchr(passwd+3, '$')) ) { SHA1Update(&ctx, passwd+3, p-(passwd+3)); p++; } else { p = passwd+3; /* Assume no salt */ } SHA1Update(&ctx, entry, strlen(entry)); SHA1Final(sha1, &ctx); memset(pwdsha1, 0, 20); unbase64(pwdsha1, 20, p); return !memcmp(sha1, pwdsha1, 20);}static jmp_buf timeout_jump;static int mygetkey(clock_t timeout){ clock_t t0, t; clock_t tto, to; int key; if ( !totaltimeout ) return get_key(stdin, timeout); for (;;) { tto = min(totaltimeout, INT_MAX); to = timeout ? min(tto, timeout) : tto; t0 = times(NULL); key = get_key(stdin, to); t = times(NULL) - t0; if ( totaltimeout <= t ) longjmp(timeout_jump, 1); totaltimeout -= t; if ( key != KEY_NONE ) return key; if ( timeout ) { if ( timeout <= t ) return KEY_NONE; timeout -= t; } }}static intask_passwd(const char *menu_entry){ static const char title[] = "Password required"; char user_passwd[WIDTH], *p; int done; int key; int x; printf("\033[%d;%dH%s\016l", PASSWD_ROW, PASSWD_MARGIN+1, menu_attrib->pwdborder); for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ ) putchar('q'); printf("k\033[%d;%dHx", PASSWD_ROW+1, PASSWD_MARGIN+1); for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ ) putchar(' '); printf("x\033[%d;%dHm", PASSWD_ROW+2, PASSWD_MARGIN+1); for ( x = 2 ; x <= WIDTH-2*PASSWD_MARGIN-1 ; x++ ) putchar('q'); printf("j\017\033[%d;%dH%s %s \033[%d;%dH%s", PASSWD_ROW, (WIDTH-((int)sizeof(title)+1))/2, menu_attrib->pwdheader, title, PASSWD_ROW+1, PASSWD_MARGIN+3, menu_attrib->pwdentry); /* Actually allow user to type a password, then compare to the SHA1 */ done = 0; p = user_passwd; while ( !done ) { key = mygetkey(0); switch ( key ) { case KEY_ENTER: case KEY_CTRL('J'): done = 1; break; case KEY_ESC: case KEY_CTRL('C'): p = user_passwd; /* No password entered */ done = 1; break; case KEY_BACKSPACE: case KEY_DEL: case KEY_DELETE: if ( p > user_passwd ) { printf("\b \b"); p--; } break; case KEY_CTRL('U'): while ( p > user_passwd ) { printf("\b \b"); p--; } break; default: if ( key >= ' ' && key <= 0xFF && (p-user_passwd) < WIDTH-2*PASSWD_MARGIN-5 ) { *p++ = key; putchar('*'); } break; } } if ( p == user_passwd ) return 0; /* No password entered */ *p = '\0'; return (menu_master_passwd && passwd_compare(menu_master_passwd, user_passwd)) || (menu_entry && passwd_compare(menu_entry, user_passwd));}static voiddraw_menu(int sel, int top, int edit_line){ int x, y; int sbtop = 0, sbbot = 0; if ( nentries > MENU_ROWS ) { int sblen = MENU_ROWS*MENU_ROWS/nentries; sbtop = (MENU_ROWS-sblen+1)*top/(nentries-MENU_ROWS+1); sbbot = sbtop + sblen - 1; sbtop += 4; sbbot += 4; /* Starting row of scrollbar */ } printf("\033[1;%dH%s\016l", MARGIN+1, menu_attrib->border); for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ ) putchar('q'); printf("k\033[2;%dH%sx\017%s %s %s\016x", MARGIN+1, menu_attrib->border, menu_attrib->title, pad_line(menu_title, 1, WIDTH-2*MARGIN-4), menu_attrib->border); printf("\033[3;%dH%st", MARGIN+1, menu_attrib->border); for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ ) putchar('q'); fputs("u\017", stdout); for ( y = 4 ; y < 4+MENU_ROWS ; y++ ) draw_row(y, sel, top, sbtop, sbbot); printf("\033[%d;%dH%s\016m", y, MARGIN+1, menu_attrib->border); for ( x = 2 ; x <= WIDTH-2*MARGIN-1 ; x++ ) putchar('q'); fputs("j\017", stdout); if ( edit_line && allowedit && !menu_master_passwd ) printf("%s\033[%d;1H%s", menu_attrib->tabmsg, TABMSG_ROW, pad_line("Press [Tab] to edit options", 1, WIDTH)); printf("%s\033[%d;1H", menu_attrib->screen, END_ROW);}static const char *edit_cmdline(char *input, int top){ static char cmdline[MAX_CMDLINE_LEN]; int key, len, prev_len, cursor; int redraw = 1; /* We enter with the menu already drawn */ strncpy(cmdline, input, MAX_CMDLINE_LEN); cmdline[MAX_CMDLINE_LEN-1] = '\0'; len = cursor = strlen(cmdline); prev_len = 0; for (;;) { if ( redraw > 1 ) { /* Clear and redraw whole screen */ /* Enable ASCII on G0 and DEC VT on G1; do it in this order to avoid confusing the Linux console */ printf("\033e\033%%@\033)0\033(B%s\033[?25l\033[2J", menu_attrib->screen); draw_menu(-1, top, 1); prev_len = 0; } if ( redraw > 0 ) { /* Redraw the command line */ printf("\033[?25l\033[%d;1H%s> %s%s", CMDLINE_ROW, menu_attrib->cmdmark, menu_attrib->cmdline, pad_line(cmdline, 0, prev_len)); printf("%s\033[%d;3H%s\033[?25h", menu_attrib->cmdline, CMDLINE_ROW, pad_line(cmdline, 0, cursor)); prev_len = len; redraw = 0; } key = mygetkey(0); switch( key ) { case KEY_CTRL('L'): redraw = 2; break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -