| /* |
| * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. |
| * |
| * 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; either version 2 of the |
| * License, or any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| * 02110-1301, USA. |
| * |
| * You can also choose to distribute this program under the terms of |
| * the Unmodified Binary Distribution Licence (as given in the file |
| * COPYING.UBDL), provided that you have satisfied its requirements. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); |
| |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <curses.h> |
| #include <ipxe/console.h> |
| #include <ipxe/settings.h> |
| #include <ipxe/editbox.h> |
| #include <ipxe/keys.h> |
| #include <ipxe/ansicol.h> |
| #include <ipxe/jumpscroll.h> |
| #include <ipxe/settings_ui.h> |
| #include <config/branding.h> |
| |
| /** @file |
| * |
| * Option configuration console |
| * |
| */ |
| |
| /* Screen layout */ |
| #define TITLE_ROW 1U |
| #define SETTINGS_LIST_ROW 3U |
| #define SETTINGS_LIST_COL 1U |
| #define SETTINGS_LIST_ROWS ( LINES - 6U - SETTINGS_LIST_ROW ) |
| #define INFO_ROW ( LINES - 5U ) |
| #define ALERT_ROW ( LINES - 2U ) |
| #define INSTRUCTION_ROW ( LINES - 2U ) |
| #define INSTRUCTION_PAD " " |
| |
| /** Layout of text within a setting row */ |
| #define SETTING_ROW_TEXT( cols ) struct { \ |
| char start[0]; \ |
| char pad1[1]; \ |
| union { \ |
| char settings[ cols - 1 - 1 - 1 - 1 ]; \ |
| struct { \ |
| char name[15]; \ |
| char pad2[1]; \ |
| char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \ |
| } setting; \ |
| } u; \ |
| char pad3[1]; \ |
| char nul; \ |
| } __attribute__ (( packed )) |
| |
| /** A settings user interface row */ |
| struct settings_ui_row { |
| /** Target configuration settings block |
| * |
| * Valid only for rows that lead to new settings blocks. |
| */ |
| struct settings *settings; |
| /** Configuration setting origin |
| * |
| * Valid only for rows that represent individual settings. |
| */ |
| struct settings *origin; |
| /** Configuration setting |
| * |
| * Valid only for rows that represent individual settings. |
| */ |
| struct setting setting; |
| /** Screen row */ |
| unsigned int row; |
| /** Edit box widget used for editing setting */ |
| struct edit_box editbox; |
| /** Editing in progress flag */ |
| int editing; |
| /** Buffer for setting's value */ |
| char value[256]; /* enough size for a DHCP string */ |
| }; |
| |
| /** A settings user interface */ |
| struct settings_ui { |
| /** Settings block */ |
| struct settings *settings; |
| /** Jump scroller */ |
| struct jump_scroller scroll; |
| /** Current row */ |
| struct settings_ui_row row; |
| }; |
| |
| /** |
| * Select a setting |
| * |
| * @v ui Settings user interface |
| * @v index Index of setting row |
| * @ret count Number of setting rows |
| */ |
| static unsigned int select_setting_row ( struct settings_ui *ui, |
| unsigned int index ) { |
| SETTING_ROW_TEXT ( COLS ) *text; |
| struct settings *settings; |
| struct setting *setting; |
| struct setting *previous = NULL; |
| unsigned int count = 0; |
| |
| /* Initialise structure */ |
| memset ( &ui->row, 0, sizeof ( ui->row ) ); |
| ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first ); |
| |
| /* Include parent settings block, if applicable */ |
| if ( ui->settings->parent && ( count++ == index ) ) { |
| ui->row.settings = ui->settings->parent; |
| snprintf ( ui->row.value, sizeof ( ui->row.value ), |
| "../" ); |
| } |
| |
| /* Include any child settings blocks, if applicable */ |
| list_for_each_entry ( settings, &ui->settings->children, siblings ) { |
| if ( count++ == index ) { |
| ui->row.settings = settings; |
| snprintf ( ui->row.value, sizeof ( ui->row.value ), |
| "%s/", settings->name ); |
| } |
| } |
| |
| /* Include any applicable settings */ |
| for_each_table_entry ( setting, SETTINGS ) { |
| |
| /* Skip inapplicable settings */ |
| if ( ! setting_applies ( ui->settings, setting ) ) |
| continue; |
| |
| /* Skip duplicate settings */ |
| if ( previous && ( setting_cmp ( setting, previous ) == 0 ) ) |
| continue; |
| previous = setting; |
| |
| /* Read current setting value and origin */ |
| if ( count++ == index ) { |
| fetchf_setting ( ui->settings, setting, &ui->row.origin, |
| &ui->row.setting, ui->row.value, |
| sizeof ( ui->row.value ) ); |
| } |
| } |
| |
| /* Initialise edit box */ |
| init_editbox ( &ui->row.editbox, ui->row.value, |
| sizeof ( ui->row.value ), NULL, ui->row.row, |
| ( SETTINGS_LIST_COL + |
| offsetof ( typeof ( *text ), u.setting.value ) ), |
| sizeof ( text->u.setting.value ), 0 ); |
| |
| return count; |
| } |
| |
| /** |
| * Copy string without NUL termination |
| * |
| * @v dest Destination |
| * @v src Source |
| * @v len Maximum length of destination |
| * @ret len Length of (unterminated) string |
| */ |
| static size_t string_copy ( char *dest, const char *src, size_t len ) { |
| size_t src_len; |
| |
| src_len = strlen ( src ); |
| if ( len > src_len ) |
| len = src_len; |
| memcpy ( dest, src, len ); |
| return len; |
| } |
| |
| /** |
| * Draw setting row |
| * |
| * @v ui Settings UI |
| */ |
| static void draw_setting_row ( struct settings_ui *ui ) { |
| SETTING_ROW_TEXT ( COLS ) text; |
| unsigned int curs_offset; |
| char *value; |
| |
| /* Fill row with spaces */ |
| memset ( &text, ' ', sizeof ( text ) ); |
| text.nul = '\0'; |
| |
| /* Construct row content */ |
| if ( ui->row.settings ) { |
| |
| /* Construct space-padded name */ |
| curs_offset = ( offsetof ( typeof ( text ), u.settings ) + |
| string_copy ( text.u.settings, |
| ui->row.value, |
| sizeof ( text.u.settings ) ) ); |
| |
| } else { |
| |
| /* Construct dot-padded name */ |
| memset ( text.u.setting.name, '.', |
| sizeof ( text.u.setting.name ) ); |
| string_copy ( text.u.setting.name, ui->row.setting.name, |
| sizeof ( text.u.setting.name ) ); |
| |
| /* Construct space-padded value */ |
| value = ui->row.value; |
| if ( ! *value ) |
| value = "<not specified>"; |
| curs_offset = ( offsetof ( typeof ( text ), u.setting.value ) + |
| string_copy ( text.u.setting.value, value, |
| sizeof ( text.u.setting.value ))); |
| } |
| |
| /* Print row */ |
| if ( ( ui->row.origin == ui->settings ) || ( ui->row.settings != NULL )) |
| attron ( A_BOLD ); |
| mvprintw ( ui->row.row, SETTINGS_LIST_COL, "%s", text.start ); |
| attroff ( A_BOLD ); |
| move ( ui->row.row, ( SETTINGS_LIST_COL + curs_offset ) ); |
| } |
| |
| /** |
| * Edit setting ui |
| * |
| * @v ui Settings UI |
| * @v key Key pressed by user |
| * @ret key Key returned to application, or zero |
| */ |
| static int edit_setting ( struct settings_ui *ui, int key ) { |
| assert ( ui->row.setting.name != NULL ); |
| ui->row.editing = 1; |
| return edit_editbox ( &ui->row.editbox, key ); |
| } |
| |
| /** |
| * Save setting ui value back to configuration settings |
| * |
| * @v ui Settings UI |
| */ |
| static int save_setting ( struct settings_ui *ui ) { |
| assert ( ui->row.setting.name != NULL ); |
| return storef_setting ( ui->settings, &ui->row.setting, ui->row.value ); |
| } |
| |
| /** |
| * Print message centred on specified row |
| * |
| * @v row Row |
| * @v fmt printf() format string |
| * @v args printf() argument list |
| */ |
| static void vmsg ( unsigned int row, const char *fmt, va_list args ) { |
| char buf[COLS]; |
| size_t len; |
| |
| len = vsnprintf ( buf, sizeof ( buf ), fmt, args ); |
| mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf ); |
| } |
| |
| /** |
| * Print message centred on specified row |
| * |
| * @v row Row |
| * @v fmt printf() format string |
| * @v .. printf() arguments |
| */ |
| static void msg ( unsigned int row, const char *fmt, ... ) { |
| va_list args; |
| |
| va_start ( args, fmt ); |
| vmsg ( row, fmt, args ); |
| va_end ( args ); |
| } |
| |
| /** |
| * Clear message on specified row |
| * |
| * @v row Row |
| */ |
| static void clearmsg ( unsigned int row ) { |
| move ( row, 0 ); |
| clrtoeol(); |
| } |
| |
| /** |
| * Print alert message |
| * |
| * @v fmt printf() format string |
| * @v args printf() argument list |
| */ |
| static void valert ( const char *fmt, va_list args ) { |
| clearmsg ( ALERT_ROW ); |
| color_set ( CPAIR_ALERT, NULL ); |
| vmsg ( ALERT_ROW, fmt, args ); |
| sleep ( 2 ); |
| color_set ( CPAIR_NORMAL, NULL ); |
| clearmsg ( ALERT_ROW ); |
| } |
| |
| /** |
| * Print alert message |
| * |
| * @v fmt printf() format string |
| * @v ... printf() arguments |
| */ |
| static void alert ( const char *fmt, ... ) { |
| va_list args; |
| |
| va_start ( args, fmt ); |
| valert ( fmt, args ); |
| va_end ( args ); |
| } |
| |
| /** |
| * Draw title row |
| * |
| * @v ui Settings UI |
| */ |
| static void draw_title_row ( struct settings_ui *ui ) { |
| const char *name; |
| |
| clearmsg ( TITLE_ROW ); |
| name = settings_name ( ui->settings ); |
| attron ( A_BOLD ); |
| msg ( TITLE_ROW, PRODUCT_SHORT_NAME " configuration settings%s%s", |
| ( name[0] ? " - " : "" ), name ); |
| attroff ( A_BOLD ); |
| } |
| |
| /** |
| * Draw information row |
| * |
| * @v ui Settings UI |
| */ |
| static void draw_info_row ( struct settings_ui *ui ) { |
| char buf[32]; |
| |
| /* Draw nothing unless this row represents a setting */ |
| clearmsg ( INFO_ROW ); |
| clearmsg ( INFO_ROW + 1 ); |
| if ( ! ui->row.setting.name ) |
| return; |
| |
| /* Determine a suitable setting name */ |
| setting_name ( ( ui->row.origin ? |
| ui->row.origin : ui->settings ), |
| &ui->row.setting, buf, sizeof ( buf ) ); |
| |
| /* Draw row */ |
| attron ( A_BOLD ); |
| msg ( INFO_ROW, "%s - %s", buf, ui->row.setting.description ); |
| attroff ( A_BOLD ); |
| color_set ( CPAIR_URL, NULL ); |
| msg ( ( INFO_ROW + 1 ), PRODUCT_SETTING_URI, ui->row.setting.name ); |
| color_set ( CPAIR_NORMAL, NULL ); |
| } |
| |
| /** |
| * Draw instruction row |
| * |
| * @v ui Settings UI |
| */ |
| static void draw_instruction_row ( struct settings_ui *ui ) { |
| |
| clearmsg ( INSTRUCTION_ROW ); |
| if ( ui->row.editing ) { |
| msg ( INSTRUCTION_ROW, |
| "Enter - accept changes" INSTRUCTION_PAD |
| "Ctrl-C - discard changes" ); |
| } else { |
| msg ( INSTRUCTION_ROW, |
| "%sCtrl-X - exit configuration utility", |
| ( ( ui->row.origin == ui->settings ) ? |
| "Ctrl-D - delete setting" INSTRUCTION_PAD : "" ) ); |
| } |
| } |
| |
| /** |
| * Draw the current block of setting rows |
| * |
| * @v ui Settings UI |
| */ |
| static void draw_setting_rows ( struct settings_ui *ui ) { |
| unsigned int i; |
| |
| /* Draw ellipses before and/or after the list as necessary */ |
| color_set ( CPAIR_SEPARATOR, NULL ); |
| mvaddstr ( ( SETTINGS_LIST_ROW - 1 ), ( SETTINGS_LIST_COL + 1 ), |
| jump_scroll_is_first ( &ui->scroll ) ? " " : "..." ); |
| mvaddstr ( ( SETTINGS_LIST_ROW + SETTINGS_LIST_ROWS ), |
| ( SETTINGS_LIST_COL + 1 ), |
| jump_scroll_is_last ( &ui->scroll ) ? " " : "..." ); |
| color_set ( CPAIR_NORMAL, NULL ); |
| |
| /* Draw visible settings. */ |
| for ( i = 0 ; i < SETTINGS_LIST_ROWS ; i++ ) { |
| if ( ( ui->scroll.first + i ) < ui->scroll.count ) { |
| select_setting_row ( ui, ( ui->scroll.first + i ) ); |
| draw_setting_row ( ui ); |
| } else { |
| clearmsg ( SETTINGS_LIST_ROW + i ); |
| } |
| } |
| } |
| |
| /** |
| * Select settings block |
| * |
| * @v ui Settings UI |
| * @v settings Settings block |
| */ |
| static void select_settings ( struct settings_ui *ui, |
| struct settings *settings ) { |
| |
| ui->settings = settings_target ( settings ); |
| ui->scroll.count = select_setting_row ( ui, 0 ); |
| ui->scroll.rows = SETTINGS_LIST_ROWS; |
| ui->scroll.current = 0; |
| ui->scroll.first = 0; |
| draw_title_row ( ui ); |
| draw_setting_rows ( ui ); |
| select_setting_row ( ui, 0 ); |
| } |
| |
| static int main_loop ( struct settings *settings ) { |
| struct settings_ui ui; |
| unsigned int previous; |
| int redraw = 1; |
| int move; |
| int key; |
| int rc; |
| |
| /* Print initial screen content */ |
| color_set ( CPAIR_NORMAL, NULL ); |
| memset ( &ui, 0, sizeof ( ui ) ); |
| select_settings ( &ui, settings ); |
| |
| while ( 1 ) { |
| |
| /* Redraw rows if necessary */ |
| if ( redraw ) { |
| draw_info_row ( &ui ); |
| draw_instruction_row ( &ui ); |
| color_set ( ( ui.row.editing ? |
| CPAIR_EDIT : CPAIR_SELECT ), NULL ); |
| draw_setting_row ( &ui ); |
| color_set ( CPAIR_NORMAL, NULL ); |
| curs_set ( ui.row.editing ); |
| redraw = 0; |
| } |
| |
| /* Edit setting, if we are currently editing */ |
| if ( ui.row.editing ) { |
| |
| /* Sanity check */ |
| assert ( ui.row.setting.name != NULL ); |
| |
| /* Redraw edit box */ |
| color_set ( CPAIR_EDIT, NULL ); |
| draw_editbox ( &ui.row.editbox ); |
| color_set ( CPAIR_NORMAL, NULL ); |
| |
| /* Process keypress */ |
| key = edit_setting ( &ui, getkey ( 0 ) ); |
| switch ( key ) { |
| case CR: |
| case LF: |
| if ( ( rc = save_setting ( &ui ) ) != 0 ) |
| alert ( " %s ", strerror ( rc ) ); |
| /* Fall through */ |
| case CTRL_C: |
| select_setting_row ( &ui, ui.scroll.current ); |
| redraw = 1; |
| break; |
| default: |
| /* Do nothing */ |
| break; |
| } |
| |
| continue; |
| } |
| |
| /* Otherwise, navigate through settings */ |
| key = getkey ( 0 ); |
| move = jump_scroll_key ( &ui.scroll, key ); |
| if ( move ) { |
| previous = ui.scroll.current; |
| jump_scroll_move ( &ui.scroll, move ); |
| if ( ui.scroll.current != previous ) { |
| draw_setting_row ( &ui ); |
| redraw = 1; |
| if ( jump_scroll ( &ui.scroll ) ) |
| draw_setting_rows ( &ui ); |
| select_setting_row ( &ui, ui.scroll.current ); |
| } |
| continue; |
| } |
| |
| /* Handle non-navigation keys */ |
| switch ( key ) { |
| case CTRL_D: |
| if ( ! ui.row.setting.name ) |
| break; |
| if ( ( rc = delete_setting ( ui.settings, |
| &ui.row.setting ) ) != 0 ){ |
| alert ( " %s ", strerror ( rc ) ); |
| } |
| select_setting_row ( &ui, ui.scroll.current ); |
| redraw = 1; |
| break; |
| case CTRL_X: |
| return 0; |
| case CR: |
| case LF: |
| if ( ui.row.settings ) { |
| select_settings ( &ui, ui.row.settings ); |
| redraw = 1; |
| } |
| /* Fall through */ |
| default: |
| if ( ui.row.setting.name ) { |
| edit_setting ( &ui, key ); |
| redraw = 1; |
| } |
| break; |
| } |
| } |
| } |
| |
| int settings_ui ( struct settings *settings ) { |
| int rc; |
| |
| initscr(); |
| start_color(); |
| color_set ( CPAIR_NORMAL, NULL ); |
| curs_set ( 0 ); |
| erase(); |
| |
| rc = main_loop ( settings ); |
| |
| endwin(); |
| |
| return rc; |
| } |