/* * Copyright (C) 2006 Michael Brown . * * 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 #include #include #include #include #include #include #include #include #include #include #include #include /** @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 = ""; 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; }