iPXE
|
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 }