iPXE
settings_ui.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdio.h>
00027 #include <stdarg.h>
00028 #include <unistd.h>
00029 #include <string.h>
00030 #include <curses.h>
00031 #include <ipxe/console.h>
00032 #include <ipxe/settings.h>
00033 #include <ipxe/editbox.h>
00034 #include <ipxe/keys.h>
00035 #include <ipxe/ansicol.h>
00036 #include <ipxe/jumpscroll.h>
00037 #include <ipxe/settings_ui.h>
00038 #include <config/branding.h>
00039 
00040 /** @file
00041  *
00042  * Option configuration console
00043  *
00044  */
00045 
00046 /* Screen layout */
00047 #define TITLE_ROW               1U
00048 #define SETTINGS_LIST_ROW       3U
00049 #define SETTINGS_LIST_COL       1U
00050 #define SETTINGS_LIST_ROWS      ( LINES - 6U - SETTINGS_LIST_ROW )
00051 #define INFO_ROW                ( LINES - 5U )
00052 #define ALERT_ROW               ( LINES - 2U )
00053 #define INSTRUCTION_ROW         ( LINES - 2U )
00054 #define INSTRUCTION_PAD "     "
00055 
00056 /** Layout of text within a setting row */
00057 #define SETTING_ROW_TEXT( cols ) struct {                               \
00058         char start[0];                                                  \
00059         char pad1[1];                                                   \
00060         union {                                                         \
00061                 char settings[ cols - 1 - 1 - 1 - 1 ];                  \
00062                 struct {                                                \
00063                         char name[15];                                  \
00064                         char pad2[1];                                   \
00065                         char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ];    \
00066                 } setting;                                              \
00067         } u;                                                            \
00068         char pad3[1];                                                   \
00069         char nul;                                                       \
00070 } __attribute__ (( packed ))
00071 
00072 /** A settings user interface row */
00073 struct settings_ui_row {
00074         /** Target configuration settings block
00075          *
00076          * Valid only for rows that lead to new settings blocks.
00077          */
00078         struct settings *settings;
00079         /** Configuration setting origin
00080          *
00081          * Valid only for rows that represent individual settings.
00082          */
00083         struct settings *origin;
00084         /** Configuration setting
00085          *
00086          * Valid only for rows that represent individual settings.
00087          */
00088         struct setting setting;
00089         /** Screen row */
00090         unsigned int row;
00091         /** Edit box widget used for editing setting */
00092         struct edit_box editbox;
00093         /** Editing in progress flag */
00094         int editing;
00095         /** Buffer for setting's value */
00096         char value[256]; /* enough size for a DHCP string */
00097 };
00098 
00099 /** A settings user interface */
00100 struct settings_ui {
00101         /** Settings block */
00102         struct settings *settings;
00103         /** Jump scroller */
00104         struct jump_scroller scroll;
00105         /** Current row */
00106         struct settings_ui_row row;
00107 };
00108 
00109 /**
00110  * Select a setting
00111  *
00112  * @v ui                Settings user interface
00113  * @v index             Index of setting row
00114  * @ret count           Number of setting rows
00115  */
00116 static unsigned int select_setting_row ( struct settings_ui *ui,
00117                                          unsigned int index ) {
00118         SETTING_ROW_TEXT ( COLS ) *text;
00119         struct settings *settings;
00120         struct setting *setting;
00121         struct setting *previous = NULL;
00122         unsigned int count = 0;
00123 
00124         /* Initialise structure */
00125         memset ( &ui->row, 0, sizeof ( ui->row ) );
00126         ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first );
00127 
00128         /* Include parent settings block, if applicable */
00129         if ( ui->settings->parent && ( count++ == index ) ) {
00130                 ui->row.settings = ui->settings->parent;
00131                 snprintf ( ui->row.value, sizeof ( ui->row.value ),
00132                            "../" );
00133         }
00134 
00135         /* Include any child settings blocks, if applicable */
00136         list_for_each_entry ( settings, &ui->settings->children, siblings ) {
00137                 if ( count++ == index ) {
00138                         ui->row.settings = settings;
00139                         snprintf ( ui->row.value, sizeof ( ui->row.value ),
00140                                    "%s/", settings->name );
00141                 }
00142         }
00143 
00144         /* Include any applicable settings */
00145         for_each_table_entry ( setting, SETTINGS ) {
00146 
00147                 /* Skip inapplicable settings */
00148                 if ( ! setting_applies ( ui->settings, setting ) )
00149                         continue;
00150 
00151                 /* Skip duplicate settings */
00152                 if ( previous && ( setting_cmp ( setting, previous ) == 0 ) )
00153                         continue;
00154                 previous = setting;
00155 
00156                 /* Read current setting value and origin */
00157                 if ( count++ == index ) {
00158                         fetchf_setting ( ui->settings, setting, &ui->row.origin,
00159                                          &ui->row.setting, ui->row.value,
00160                                          sizeof ( ui->row.value ) );
00161                 }
00162         }
00163 
00164         /* Initialise edit box */
00165         init_editbox ( &ui->row.editbox, ui->row.value,
00166                        sizeof ( ui->row.value ), NULL, ui->row.row,
00167                        ( SETTINGS_LIST_COL +
00168                          offsetof ( typeof ( *text ), u.setting.value ) ),
00169                        sizeof ( text->u.setting.value ), 0 );
00170 
00171         return count;
00172 }
00173 
00174 /**
00175  * Copy string without NUL termination
00176  *
00177  * @v dest              Destination
00178  * @v src               Source
00179  * @v len               Maximum length of destination
00180  * @ret len             Length of (unterminated) string
00181  */
00182 static size_t string_copy ( char *dest, const char *src, size_t len ) {
00183         size_t src_len;
00184 
00185         src_len = strlen ( src );
00186         if ( len > src_len )
00187                 len = src_len;
00188         memcpy ( dest, src, len );
00189         return len;
00190 }
00191 
00192 /**
00193  * Draw setting row
00194  *
00195  * @v ui                Settings UI
00196  */
00197 static void draw_setting_row ( struct settings_ui *ui ) {
00198         SETTING_ROW_TEXT ( COLS ) text;
00199         unsigned int curs_offset;
00200         char *value;
00201 
00202         /* Fill row with spaces */
00203         memset ( &text, ' ', sizeof ( text ) );
00204         text.nul = '\0';
00205 
00206         /* Construct row content */
00207         if ( ui->row.settings ) {
00208 
00209                 /* Construct space-padded name */
00210                 curs_offset = ( offsetof ( typeof ( text ), u.settings ) +
00211                                 string_copy ( text.u.settings,
00212                                               ui->row.value,
00213                                               sizeof ( text.u.settings ) ) );
00214 
00215         } else {
00216 
00217                 /* Construct dot-padded name */
00218                 memset ( text.u.setting.name, '.',
00219                          sizeof ( text.u.setting.name ) );
00220                 string_copy ( text.u.setting.name, ui->row.setting.name,
00221                               sizeof ( text.u.setting.name ) );
00222 
00223                 /* Construct space-padded value */
00224                 value = ui->row.value;
00225                 if ( ! *value )
00226                         value = "<not specified>";
00227                 curs_offset = ( offsetof ( typeof ( text ), u.setting.value ) +
00228                                 string_copy ( text.u.setting.value, value,
00229                                               sizeof ( text.u.setting.value )));
00230         }
00231 
00232         /* Print row */
00233         if ( ( ui->row.origin == ui->settings ) || ( ui->row.settings != NULL ))
00234                 attron ( A_BOLD );
00235         mvprintw ( ui->row.row, SETTINGS_LIST_COL, "%s", text.start );
00236         attroff ( A_BOLD );
00237         move ( ui->row.row, ( SETTINGS_LIST_COL + curs_offset ) );
00238 }
00239 
00240 /**
00241  * Edit setting ui
00242  *
00243  * @v ui                Settings UI
00244  * @v key               Key pressed by user
00245  * @ret key             Key returned to application, or zero
00246  */
00247 static int edit_setting ( struct settings_ui *ui, int key ) {
00248         assert ( ui->row.setting.name != NULL );
00249         ui->row.editing = 1;
00250         return edit_editbox ( &ui->row.editbox, key );
00251 }
00252 
00253 /**
00254  * Save setting ui value back to configuration settings
00255  *
00256  * @v ui                Settings UI
00257  */
00258 static int save_setting ( struct settings_ui *ui ) {
00259         assert ( ui->row.setting.name != NULL );
00260         return storef_setting ( ui->settings, &ui->row.setting, ui->row.value );
00261 }
00262 
00263 /**
00264  * Print message centred on specified row
00265  *
00266  * @v row               Row
00267  * @v fmt               printf() format string
00268  * @v args              printf() argument list
00269  */
00270 static void vmsg ( unsigned int row, const char *fmt, va_list args ) {
00271         char buf[COLS];
00272         size_t len;
00273 
00274         len = vsnprintf ( buf, sizeof ( buf ), fmt, args );
00275         mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf );
00276 }
00277 
00278 /**
00279  * Print message centred on specified row
00280  *
00281  * @v row               Row
00282  * @v fmt               printf() format string
00283  * @v ..                printf() arguments
00284  */
00285 static void msg ( unsigned int row, const char *fmt, ... ) {
00286         va_list args;
00287 
00288         va_start ( args, fmt );
00289         vmsg ( row, fmt, args );
00290         va_end ( args );
00291 }
00292 
00293 /**
00294  * Clear message on specified row
00295  *
00296  * @v row               Row
00297  */
00298 static void clearmsg ( unsigned int row ) {
00299         move ( row, 0 );
00300         clrtoeol();
00301 }
00302 
00303 /**
00304  * Print alert message
00305  *
00306  * @v fmt               printf() format string
00307  * @v args              printf() argument list
00308  */
00309 static void valert ( const char *fmt, va_list args ) {
00310         clearmsg ( ALERT_ROW );
00311         color_set ( CPAIR_ALERT, NULL );
00312         vmsg ( ALERT_ROW, fmt, args );
00313         sleep ( 2 );
00314         color_set ( CPAIR_NORMAL, NULL );
00315         clearmsg ( ALERT_ROW );
00316 }
00317 
00318 /**
00319  * Print alert message
00320  *
00321  * @v fmt               printf() format string
00322  * @v ...               printf() arguments
00323  */
00324 static void alert ( const char *fmt, ... ) {
00325         va_list args;
00326 
00327         va_start ( args, fmt );
00328         valert ( fmt, args );
00329         va_end ( args );
00330 }
00331 
00332 /**
00333  * Draw title row
00334  *
00335  * @v ui                Settings UI
00336  */
00337 static void draw_title_row ( struct settings_ui *ui ) {
00338         const char *name;
00339 
00340         clearmsg ( TITLE_ROW );
00341         name = settings_name ( ui->settings );
00342         attron ( A_BOLD );
00343         msg ( TITLE_ROW, PRODUCT_SHORT_NAME " configuration settings%s%s",
00344               ( name[0] ? " - " : "" ), name );
00345         attroff ( A_BOLD );
00346 }
00347 
00348 /**
00349  * Draw information row
00350  *
00351  * @v ui                Settings UI
00352  */
00353 static void draw_info_row ( struct settings_ui *ui ) {
00354         char buf[32];
00355 
00356         /* Draw nothing unless this row represents a setting */
00357         clearmsg ( INFO_ROW );
00358         clearmsg ( INFO_ROW + 1 );
00359         if ( ! ui->row.setting.name )
00360                 return;
00361 
00362         /* Determine a suitable setting name */
00363         setting_name ( ( ui->row.origin ?
00364                          ui->row.origin : ui->settings ),
00365                        &ui->row.setting, buf, sizeof ( buf ) );
00366 
00367         /* Draw row */
00368         attron ( A_BOLD );
00369         msg ( INFO_ROW, "%s - %s", buf, ui->row.setting.description );
00370         attroff ( A_BOLD );
00371         color_set ( CPAIR_URL, NULL );
00372         msg ( ( INFO_ROW + 1 ), PRODUCT_SETTING_URI, ui->row.setting.name );
00373         color_set ( CPAIR_NORMAL, NULL );
00374 }
00375 
00376 /**
00377  * Draw instruction row
00378  *
00379  * @v ui                Settings UI
00380  */
00381 static void draw_instruction_row ( struct settings_ui *ui ) {
00382 
00383         clearmsg ( INSTRUCTION_ROW );
00384         if ( ui->row.editing ) {
00385                 msg ( INSTRUCTION_ROW,
00386                       "Enter - accept changes" INSTRUCTION_PAD
00387                       "Ctrl-C - discard changes" );
00388         } else {
00389                 msg ( INSTRUCTION_ROW,
00390                       "%sCtrl-X - exit configuration utility",
00391                       ( ( ui->row.origin == ui->settings ) ?
00392                         "Ctrl-D - delete setting" INSTRUCTION_PAD : "" ) );
00393         }
00394 }
00395 
00396 /**
00397  * Draw the current block of setting rows
00398  *
00399  * @v ui                Settings UI
00400  */
00401 static void draw_setting_rows ( struct settings_ui *ui ) {
00402         unsigned int i;
00403 
00404         /* Draw ellipses before and/or after the list as necessary */
00405         color_set ( CPAIR_SEPARATOR, NULL );
00406         mvaddstr ( ( SETTINGS_LIST_ROW - 1 ), ( SETTINGS_LIST_COL + 1 ),
00407                    jump_scroll_is_first ( &ui->scroll ) ? "   " : "..." );
00408         mvaddstr ( ( SETTINGS_LIST_ROW + SETTINGS_LIST_ROWS ),
00409                    ( SETTINGS_LIST_COL + 1 ),
00410                    jump_scroll_is_last ( &ui->scroll ) ? "   " : "..." );
00411         color_set ( CPAIR_NORMAL, NULL );
00412 
00413         /* Draw visible settings. */
00414         for ( i = 0 ; i < SETTINGS_LIST_ROWS ; i++ ) {
00415                 if ( ( ui->scroll.first + i ) < ui->scroll.count ) {
00416                         select_setting_row ( ui, ( ui->scroll.first + i ) );
00417                         draw_setting_row ( ui );
00418                 } else {
00419                         clearmsg ( SETTINGS_LIST_ROW + i );
00420                 }
00421         }
00422 }
00423 
00424 /**
00425  * Select settings block
00426  *
00427  * @v ui                Settings UI
00428  * @v settings          Settings block
00429  */
00430 static void select_settings ( struct settings_ui *ui,
00431                               struct settings *settings ) {
00432 
00433         ui->settings = settings_target ( settings );
00434         ui->scroll.count = select_setting_row ( ui, 0 );
00435         ui->scroll.rows = SETTINGS_LIST_ROWS;
00436         ui->scroll.current = 0;
00437         ui->scroll.first = 0;
00438         draw_title_row ( ui );
00439         draw_setting_rows ( ui );
00440         select_setting_row ( ui, 0 );
00441 }
00442 
00443 static int main_loop ( struct settings *settings ) {
00444         struct settings_ui ui;
00445         unsigned int previous;
00446         int redraw = 1;
00447         int move;
00448         int key;
00449         int rc;
00450 
00451         /* Print initial screen content */
00452         color_set ( CPAIR_NORMAL, NULL );
00453         memset ( &ui, 0, sizeof ( ui ) );
00454         select_settings ( &ui, settings );
00455 
00456         while ( 1 ) {
00457 
00458                 /* Redraw rows if necessary */
00459                 if ( redraw ) {
00460                         draw_info_row ( &ui );
00461                         draw_instruction_row ( &ui );
00462                         color_set ( ( ui.row.editing ?
00463                                       CPAIR_EDIT : CPAIR_SELECT ), NULL );
00464                         draw_setting_row ( &ui );
00465                         color_set ( CPAIR_NORMAL, NULL );
00466                         curs_set ( ui.row.editing );
00467                         redraw = 0;
00468                 }
00469 
00470                 /* Edit setting, if we are currently editing */
00471                 if ( ui.row.editing ) {
00472 
00473                         /* Sanity check */
00474                         assert ( ui.row.setting.name != NULL );
00475 
00476                         /* Redraw edit box */
00477                         color_set ( CPAIR_EDIT, NULL );
00478                         draw_editbox ( &ui.row.editbox );
00479                         color_set ( CPAIR_NORMAL, NULL );
00480 
00481                         /* Process keypress */
00482                         key = edit_setting ( &ui, getkey ( 0 ) );
00483                         switch ( key ) {
00484                         case CR:
00485                         case LF:
00486                                 if ( ( rc = save_setting ( &ui ) ) != 0 )
00487                                         alert ( " %s ", strerror ( rc ) );
00488                                 /* Fall through */
00489                         case CTRL_C:
00490                                 select_setting_row ( &ui, ui.scroll.current );
00491                                 redraw = 1;
00492                                 break;
00493                         default:
00494                                 /* Do nothing */
00495                                 break;
00496                         }
00497 
00498                         continue;
00499                 }
00500 
00501                 /* Otherwise, navigate through settings */
00502                 key = getkey ( 0 );
00503                 move = jump_scroll_key ( &ui.scroll, key );
00504                 if ( move ) {
00505                         previous = ui.scroll.current;
00506                         jump_scroll_move ( &ui.scroll, move );
00507                         if ( ui.scroll.current != previous ) {
00508                                 draw_setting_row ( &ui );
00509                                 redraw = 1;
00510                                 if ( jump_scroll ( &ui.scroll ) )
00511                                         draw_setting_rows ( &ui );
00512                                 select_setting_row ( &ui, ui.scroll.current );
00513                         }
00514                         continue;
00515                 }
00516 
00517                 /* Handle non-navigation keys */
00518                 switch ( key ) {
00519                 case CTRL_D:
00520                         if ( ! ui.row.setting.name )
00521                                 break;
00522                         if ( ( rc = delete_setting ( ui.settings,
00523                                                      &ui.row.setting ) ) != 0 ){
00524                                 alert ( " %s ", strerror ( rc ) );
00525                         }
00526                         select_setting_row ( &ui, ui.scroll.current );
00527                         redraw = 1;
00528                         break;
00529                 case CTRL_X:
00530                         return 0;
00531                 case CR:
00532                 case LF:
00533                         if ( ui.row.settings ) {
00534                                 select_settings ( &ui, ui.row.settings );
00535                                 redraw = 1;
00536                         }
00537                         /* Fall through */
00538                 default:
00539                         if ( ui.row.setting.name ) {
00540                                 edit_setting ( &ui, key );
00541                                 redraw = 1;
00542                         }
00543                         break;
00544                 }
00545         }
00546 }
00547 
00548 int settings_ui ( struct settings *settings ) {
00549         int rc;
00550 
00551         initscr();
00552         start_color();
00553         color_set ( CPAIR_NORMAL, NULL );
00554         curs_set ( 0 );
00555         erase();
00556         
00557         rc = main_loop ( settings );
00558 
00559         endwin();
00560 
00561         return rc;
00562 }