2 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
31 #include <ipxe/console.h>
32 #include <ipxe/settings.h>
33 #include <ipxe/editbox.h>
34 #include <ipxe/keys.h>
35 #include <ipxe/ansicol.h>
36 #include <ipxe/jumpscroll.h>
37 #include <ipxe/settings_ui.h>
38 #include <config/branding.h>
42 * Option configuration console
48 #define SETTINGS_LIST_ROW 3U
49 #define SETTINGS_LIST_COL 1U
50 #define SETTINGS_LIST_ROWS ( LINES - 6U - SETTINGS_LIST_ROW )
51 #define INFO_ROW ( LINES - 5U )
52 #define ALERT_ROW ( LINES - 2U )
53 #define INSTRUCTION_ROW ( LINES - 2U )
54 #define INSTRUCTION_PAD " "
56 /** Layout of text within a setting row */
57 #define SETTING_ROW_TEXT( cols ) struct { \
61 char settings[ cols - 1 - 1 - 1 - 1 ]; \
65 char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \
70 } __attribute__ (( packed ))
72 /** A settings user interface row */
73 struct settings_ui_row {
74 /** Target configuration settings block
76 * Valid only for rows that lead to new settings blocks.
78 struct settings *settings;
79 /** Configuration setting origin
81 * Valid only for rows that represent individual settings.
83 struct settings *origin;
84 /** Configuration setting
86 * Valid only for rows that represent individual settings.
88 struct setting setting;
91 /** Edit box widget used for editing setting */
92 struct edit_box editbox;
93 /** Editing in progress flag */
95 /** Buffer for setting's value */
96 char value[256]; /* enough size for a DHCP string */
99 /** A settings user interface */
101 /** Settings block */
102 struct settings *settings;
104 struct jump_scroller scroll;
106 struct settings_ui_row row;
112 * @v ui Settings user interface
113 * @v index Index of setting row
114 * @ret count Number of setting rows
116 static unsigned int select_setting_row ( struct settings_ui *ui,
117 unsigned int index ) {
118 SETTING_ROW_TEXT ( COLS ) *text;
119 struct settings *settings;
120 struct setting *setting;
121 struct setting *previous = NULL;
122 unsigned int count = 0;
124 /* Initialise structure */
125 memset ( &ui->row, 0, sizeof ( ui->row ) );
126 ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first );
128 /* Include parent settings block, if applicable */
129 if ( ui->settings->parent && ( count++ == index ) ) {
130 ui->row.settings = ui->settings->parent;
131 snprintf ( ui->row.value, sizeof ( ui->row.value ),
135 /* Include any child settings blocks, if applicable */
136 list_for_each_entry ( settings, &ui->settings->children, siblings ) {
137 if ( count++ == index ) {
138 ui->row.settings = settings;
139 snprintf ( ui->row.value, sizeof ( ui->row.value ),
140 "%s/", settings->name );
144 /* Include any applicable settings */
145 for_each_table_entry ( setting, SETTINGS ) {
147 /* Skip inapplicable settings */
148 if ( ! setting_applies ( ui->settings, setting ) )
151 /* Skip duplicate settings */
152 if ( previous && ( setting_cmp ( setting, previous ) == 0 ) )
156 /* Read current setting value and origin */
157 if ( count++ == index ) {
158 fetchf_setting ( ui->settings, setting, &ui->row.origin,
159 &ui->row.setting, ui->row.value,
160 sizeof ( ui->row.value ) );
164 /* Initialise edit box */
165 init_editbox ( &ui->row.editbox, ui->row.value,
166 sizeof ( ui->row.value ), NULL, ui->row.row,
167 ( SETTINGS_LIST_COL +
168 offsetof ( typeof ( *text ), u.setting.value ) ),
169 sizeof ( text->u.setting.value ), 0 );
175 * Copy string without NUL termination
177 * @v dest Destination
179 * @v len Maximum length of destination
180 * @ret len Length of (unterminated) string
182 static size_t string_copy ( char *dest, const char *src, size_t len ) {
185 src_len = strlen ( src );
188 memcpy ( dest, src, len );
197 static void draw_setting_row ( struct settings_ui *ui ) {
198 SETTING_ROW_TEXT ( COLS ) text;
199 unsigned int curs_offset;
202 /* Fill row with spaces */
203 memset ( &text, ' ', sizeof ( text ) );
206 /* Construct row content */
207 if ( ui->row.settings ) {
209 /* Construct space-padded name */
210 curs_offset = ( offsetof ( typeof ( text ), u.settings ) +
211 string_copy ( text.u.settings,
213 sizeof ( text.u.settings ) ) );
217 /* Construct dot-padded name */
218 memset ( text.u.setting.name, '.',
219 sizeof ( text.u.setting.name ) );
220 string_copy ( text.u.setting.name, ui->row.setting.name,
221 sizeof ( text.u.setting.name ) );
223 /* Construct space-padded value */
224 value = ui->row.value;
226 value = "<not specified>";
227 curs_offset = ( offsetof ( typeof ( text ), u.setting.value ) +
228 string_copy ( text.u.setting.value, value,
229 sizeof ( text.u.setting.value )));
233 if ( ( ui->row.origin == ui->settings ) || ( ui->row.settings != NULL ))
235 mvprintw ( ui->row.row, SETTINGS_LIST_COL, "%s", text.start );
237 move ( ui->row.row, ( SETTINGS_LIST_COL + curs_offset ) );
244 * @v key Key pressed by user
245 * @ret key Key returned to application, or zero
247 static int edit_setting ( struct settings_ui *ui, int key ) {
248 assert ( ui->row.setting.name != NULL );
250 return edit_editbox ( &ui->row.editbox, key );
254 * Save setting ui value back to configuration settings
258 static int save_setting ( struct settings_ui *ui ) {
259 assert ( ui->row.setting.name != NULL );
260 return storef_setting ( ui->settings, &ui->row.setting, ui->row.value );
264 * Print message centred on specified row
267 * @v fmt printf() format string
268 * @v args printf() argument list
270 static void vmsg ( unsigned int row, const char *fmt, va_list args ) {
274 len = vsnprintf ( buf, sizeof ( buf ), fmt, args );
275 mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf );
279 * Print message centred on specified row
282 * @v fmt printf() format string
283 * @v .. printf() arguments
285 static void msg ( unsigned int row, const char *fmt, ... ) {
288 va_start ( args, fmt );
289 vmsg ( row, fmt, args );
294 * Clear message on specified row
298 static void clearmsg ( unsigned int row ) {
304 * Print alert message
306 * @v fmt printf() format string
307 * @v args printf() argument list
309 static void valert ( const char *fmt, va_list args ) {
310 clearmsg ( ALERT_ROW );
311 color_set ( CPAIR_ALERT, NULL );
312 vmsg ( ALERT_ROW, fmt, args );
314 color_set ( CPAIR_NORMAL, NULL );
315 clearmsg ( ALERT_ROW );
319 * Print alert message
321 * @v fmt printf() format string
322 * @v ... printf() arguments
324 static void alert ( const char *fmt, ... ) {
327 va_start ( args, fmt );
328 valert ( fmt, args );
337 static void draw_title_row ( struct settings_ui *ui ) {
340 clearmsg ( TITLE_ROW );
341 name = settings_name ( ui->settings );
343 msg ( TITLE_ROW, PRODUCT_SHORT_NAME " configuration settings%s%s",
344 ( name[0] ? " - " : "" ), name );
349 * Draw information row
353 static void draw_info_row ( struct settings_ui *ui ) {
356 /* Draw nothing unless this row represents a setting */
357 clearmsg ( INFO_ROW );
358 clearmsg ( INFO_ROW + 1 );
359 if ( ! ui->row.setting.name )
362 /* Determine a suitable setting name */
363 setting_name ( ( ui->row.origin ?
364 ui->row.origin : ui->settings ),
365 &ui->row.setting, buf, sizeof ( buf ) );
369 msg ( INFO_ROW, "%s - %s", buf, ui->row.setting.description );
371 color_set ( CPAIR_URL, NULL );
372 msg ( ( INFO_ROW + 1 ), PRODUCT_SETTING_URI, ui->row.setting.name );
373 color_set ( CPAIR_NORMAL, NULL );
377 * Draw instruction row
381 static void draw_instruction_row ( struct settings_ui *ui ) {
383 clearmsg ( INSTRUCTION_ROW );
384 if ( ui->row.editing ) {
385 msg ( INSTRUCTION_ROW,
386 "Enter - accept changes" INSTRUCTION_PAD
387 "Ctrl-C - discard changes" );
389 msg ( INSTRUCTION_ROW,
390 "%sCtrl-X - exit configuration utility",
391 ( ( ui->row.origin == ui->settings ) ?
392 "Ctrl-D - delete setting" INSTRUCTION_PAD : "" ) );
397 * Draw the current block of setting rows
401 static void draw_setting_rows ( struct settings_ui *ui ) {
404 /* Draw ellipses before and/or after the list as necessary */
405 color_set ( CPAIR_SEPARATOR, NULL );
406 mvaddstr ( ( SETTINGS_LIST_ROW - 1 ), ( SETTINGS_LIST_COL + 1 ),
407 jump_scroll_is_first ( &ui->scroll ) ? " " : "..." );
408 mvaddstr ( ( SETTINGS_LIST_ROW + SETTINGS_LIST_ROWS ),
409 ( SETTINGS_LIST_COL + 1 ),
410 jump_scroll_is_last ( &ui->scroll ) ? " " : "..." );
411 color_set ( CPAIR_NORMAL, NULL );
413 /* Draw visible settings. */
414 for ( i = 0 ; i < SETTINGS_LIST_ROWS ; i++ ) {
415 if ( ( ui->scroll.first + i ) < ui->scroll.count ) {
416 select_setting_row ( ui, ( ui->scroll.first + i ) );
417 draw_setting_row ( ui );
419 clearmsg ( SETTINGS_LIST_ROW + i );
425 * Select settings block
428 * @v settings Settings block
430 static void select_settings ( struct settings_ui *ui,
431 struct settings *settings ) {
433 ui->settings = settings_target ( settings );
434 ui->scroll.count = select_setting_row ( ui, 0 );
435 ui->scroll.rows = SETTINGS_LIST_ROWS;
436 ui->scroll.current = 0;
437 ui->scroll.first = 0;
438 draw_title_row ( ui );
439 draw_setting_rows ( ui );
440 select_setting_row ( ui, 0 );
443 static int main_loop ( struct settings *settings ) {
444 struct settings_ui ui;
445 unsigned int previous;
451 /* Print initial screen content */
452 color_set ( CPAIR_NORMAL, NULL );
453 memset ( &ui, 0, sizeof ( ui ) );
454 select_settings ( &ui, settings );
458 /* Redraw rows if necessary */
460 draw_info_row ( &ui );
461 draw_instruction_row ( &ui );
462 color_set ( ( ui.row.editing ?
463 CPAIR_EDIT : CPAIR_SELECT ), NULL );
464 draw_setting_row ( &ui );
465 color_set ( CPAIR_NORMAL, NULL );
466 curs_set ( ui.row.editing );
470 /* Edit setting, if we are currently editing */
471 if ( ui.row.editing ) {
474 assert ( ui.row.setting.name != NULL );
476 /* Redraw edit box */
477 color_set ( CPAIR_EDIT, NULL );
478 draw_editbox ( &ui.row.editbox );
479 color_set ( CPAIR_NORMAL, NULL );
481 /* Process keypress */
482 key = edit_setting ( &ui, getkey ( 0 ) );
486 if ( ( rc = save_setting ( &ui ) ) != 0 )
487 alert ( " %s ", strerror ( rc ) );
490 select_setting_row ( &ui, ui.scroll.current );
501 /* Otherwise, navigate through settings */
503 move = jump_scroll_key ( &ui.scroll, key );
505 previous = ui.scroll.current;
506 jump_scroll_move ( &ui.scroll, move );
507 if ( ui.scroll.current != previous ) {
508 draw_setting_row ( &ui );
510 if ( jump_scroll ( &ui.scroll ) )
511 draw_setting_rows ( &ui );
512 select_setting_row ( &ui, ui.scroll.current );
517 /* Handle non-navigation keys */
520 if ( ! ui.row.setting.name )
522 if ( ( rc = delete_setting ( ui.settings,
523 &ui.row.setting ) ) != 0 ){
524 alert ( " %s ", strerror ( rc ) );
526 select_setting_row ( &ui, ui.scroll.current );
533 if ( ui.row.settings ) {
534 select_settings ( &ui, ui.row.settings );
539 if ( ui.row.setting.name ) {
540 edit_setting ( &ui, key );
548 int settings_ui ( struct settings *settings ) {
553 color_set ( CPAIR_NORMAL, NULL );
557 rc = main_loop ( settings );