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
20 FILE_LICENCE ( GPL2_OR_LATER );
27 #include <ipxe/console.h>
28 #include <ipxe/settings.h>
29 #include <ipxe/editbox.h>
30 #include <ipxe/keys.h>
31 #include <ipxe/ansicol.h>
32 #include <ipxe/settings_ui.h>
36 * Option configuration console
42 #define SETTINGS_LIST_ROW 3U
43 #define SETTINGS_LIST_COL 1U
44 #define SETTINGS_LIST_ROWS ( LINES - 6U - SETTINGS_LIST_ROW )
45 #define INFO_ROW ( LINES - 5U )
46 #define ALERT_ROW ( LINES - 2U )
47 #define INSTRUCTION_ROW ( LINES - 2U )
48 #define INSTRUCTION_PAD " "
50 /** Layout of text within a setting widget */
51 #define SETTING_ROW_TEXT( cols ) struct { \
55 char settings[ cols - 1 - 1 - 1 - 1 ]; \
59 char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \
64 } __attribute__ (( packed ))
66 /** A setting row widget */
67 struct setting_row_widget {
68 /** Target configuration settings block
70 * Valid only for rows that lead to new settings blocks.
72 struct settings *settings;
73 /** Configuration setting origin
75 * Valid only for rows that represent individual settings.
77 struct settings *origin;
78 /** Configuration setting
80 * Valid only for rows that represent individual settings.
82 struct setting setting;
87 /** Edit box widget used for editing setting */
88 struct edit_box editbox;
89 /** Editing in progress flag */
91 /** Buffer for setting's value */
92 char value[256]; /* enough size for a DHCP string */
95 /** A settings widget */
96 struct setting_widget {
98 struct settings *settings;
100 unsigned int num_rows;
101 /** Current row index */
102 unsigned int current;
103 /** Index of the first visible row, for scrolling. */
104 unsigned int first_visible;
106 struct setting_row_widget row;
110 * Select a setting row
112 * @v widget Setting widget
113 * @v index Index of setting row
114 * @ret count Number of settings rows
116 static unsigned int select_setting_row ( struct setting_widget *widget,
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 ( &widget->row, 0, sizeof ( widget->row ) );
126 widget->current = index;
127 widget->row.row = ( SETTINGS_LIST_ROW + index - widget->first_visible );
128 widget->row.col = SETTINGS_LIST_COL;
130 /* Include parent settings block, if applicable */
131 if ( widget->settings->parent && ( count++ == index ) ) {
132 widget->row.settings = widget->settings->parent;
133 snprintf ( widget->row.value, sizeof ( widget->row.value ),
137 /* Include any child settings blocks, if applicable */
138 list_for_each_entry ( settings, &widget->settings->children, siblings ){
139 if ( count++ == index ) {
140 widget->row.settings = settings;
141 snprintf ( widget->row.value,
142 sizeof ( widget->row.value ), "%s/",
147 /* Include any applicable settings */
148 for_each_table_entry ( setting, SETTINGS ) {
150 /* Skip inapplicable settings */
151 if ( ! setting_applies ( widget->settings, setting ) )
154 /* Skip duplicate settings */
155 if ( previous && ( setting_cmp ( setting, previous ) == 0 ) )
159 /* Read current setting value and origin */
160 if ( count++ == index ) {
161 fetchf_setting ( widget->settings, setting,
163 &widget->row.setting,
165 sizeof ( widget->row.value ) );
169 /* Initialise edit box */
170 init_editbox ( &widget->row.editbox, widget->row.value,
171 sizeof ( widget->row.value ), NULL, widget->row.row,
173 offsetof ( typeof ( *text ), u.setting.value ) ),
174 sizeof ( text->u.setting.value ), 0 );
180 * Copy string without NUL termination
182 * @v dest Destination
184 * @v len Maximum length of destination
185 * @ret len Length of (unterminated) string
187 static size_t string_copy ( char *dest, const char *src, size_t len ) {
190 src_len = strlen ( src );
193 memcpy ( dest, src, len );
200 * @v widget Setting widget
202 static void draw_setting_row ( struct setting_widget *widget ) {
203 SETTING_ROW_TEXT ( COLS ) text;
204 unsigned int curs_offset;
207 /* Fill row with spaces */
208 memset ( &text, ' ', sizeof ( text ) );
211 /* Construct row content */
212 if ( widget->row.settings ) {
214 /* Construct space-padded name */
215 curs_offset = ( offsetof ( typeof ( text ), u.settings ) +
216 string_copy ( text.u.settings,
218 sizeof ( text.u.settings ) ) );
222 /* Construct dot-padded name */
223 memset ( text.u.setting.name, '.',
224 sizeof ( text.u.setting.name ) );
225 string_copy ( text.u.setting.name, widget->row.setting.name,
226 sizeof ( text.u.setting.name ) );
228 /* Construct space-padded value */
229 value = widget->row.value;
231 value = "<not specified>";
232 curs_offset = ( offsetof ( typeof ( text ), u.setting.value ) +
233 string_copy ( text.u.setting.value, value,
234 sizeof ( text.u.setting.value )));
238 if ( ( widget->row.origin == widget->settings ) ||
239 ( widget->row.settings != NULL ) ) {
242 mvprintw ( widget->row.row, widget->row.col, "%s", text.start );
244 move ( widget->row.row, widget->row.col + curs_offset );
248 * Edit setting widget
250 * @v widget Setting widget
251 * @v key Key pressed by user
252 * @ret key Key returned to application, or zero
254 static int edit_setting ( struct setting_widget *widget, int key ) {
255 assert ( widget->row.setting.name != NULL );
256 widget->row.editing = 1;
257 return edit_editbox ( &widget->row.editbox, key );
261 * Save setting widget value back to configuration settings
263 * @v widget Setting widget
265 static int save_setting ( struct setting_widget *widget ) {
266 assert ( widget->row.setting.name != NULL );
267 return storef_setting ( widget->settings, &widget->row.setting,
272 * Print message centred on specified row
275 * @v fmt printf() format string
276 * @v args printf() argument list
278 static void vmsg ( unsigned int row, const char *fmt, va_list args ) {
282 len = vsnprintf ( buf, sizeof ( buf ), fmt, args );
283 mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf );
287 * Print message centred on specified row
290 * @v fmt printf() format string
291 * @v .. printf() arguments
293 static void msg ( unsigned int row, const char *fmt, ... ) {
296 va_start ( args, fmt );
297 vmsg ( row, fmt, args );
302 * Clear message on specified row
306 static void clearmsg ( unsigned int row ) {
312 * Print alert message
314 * @v fmt printf() format string
315 * @v args printf() argument list
317 static void valert ( const char *fmt, va_list args ) {
318 clearmsg ( ALERT_ROW );
319 color_set ( CPAIR_ALERT, NULL );
320 vmsg ( ALERT_ROW, fmt, args );
322 color_set ( CPAIR_NORMAL, NULL );
323 clearmsg ( ALERT_ROW );
327 * Print alert message
329 * @v fmt printf() format string
330 * @v ... printf() arguments
332 static void alert ( const char *fmt, ... ) {
335 va_start ( args, fmt );
336 valert ( fmt, args );
343 * @v widget Setting widget
345 static void draw_title_row ( struct setting_widget *widget ) {
348 clearmsg ( TITLE_ROW );
349 name = settings_name ( widget->settings );
351 msg ( TITLE_ROW, "iPXE configuration settings%s%s",
352 ( name[0] ? " - " : "" ), name );
357 * Draw information row
359 * @v widget Setting widget
361 static void draw_info_row ( struct setting_widget *widget ) {
364 /* Draw nothing unless this row represents a setting */
365 clearmsg ( INFO_ROW );
366 clearmsg ( INFO_ROW + 1 );
367 if ( ! widget->row.setting.name )
370 /* Determine a suitable setting name */
371 setting_name ( ( widget->row.origin ?
372 widget->row.origin : widget->settings ),
373 &widget->row.setting, buf, sizeof ( buf ) );
377 msg ( INFO_ROW, "%s - %s", buf, widget->row.setting.description );
379 color_set ( CPAIR_URL, NULL );
380 msg ( ( INFO_ROW + 1 ), "http://ipxe.org/cfg/%s",
381 widget->row.setting.name );
382 color_set ( CPAIR_NORMAL, NULL );
386 * Draw instruction row
388 * @v widget Setting widget
390 static void draw_instruction_row ( struct setting_widget *widget ) {
392 clearmsg ( INSTRUCTION_ROW );
393 if ( widget->row.editing ) {
394 msg ( INSTRUCTION_ROW,
395 "Enter - accept changes" INSTRUCTION_PAD
396 "Ctrl-C - discard changes" );
398 msg ( INSTRUCTION_ROW,
399 "%sCtrl-X - exit configuration utility",
400 ( ( widget->row.origin == widget->settings ) ?
401 "Ctrl-D - delete setting" INSTRUCTION_PAD : "" ) );
408 * @v widget Setting widget
409 * @v index Index of setting row
411 static void reveal_setting_row ( struct setting_widget *widget,
412 unsigned int index ) {
415 /* Simply return if setting N is already on-screen. */
416 if ( index - widget->first_visible < SETTINGS_LIST_ROWS )
419 /* Jump scroll to make the specified setting row visible. */
420 while ( widget->first_visible < index )
421 widget->first_visible += SETTINGS_LIST_ROWS;
422 while ( widget->first_visible > index )
423 widget->first_visible -= SETTINGS_LIST_ROWS;
425 /* Draw ellipses before and/or after the settings list to
426 * represent any invisible settings.
428 mvaddstr ( SETTINGS_LIST_ROW - 1,
429 SETTINGS_LIST_COL + 1,
430 widget->first_visible > 0 ? "..." : " " );
431 mvaddstr ( SETTINGS_LIST_ROW + SETTINGS_LIST_ROWS,
432 SETTINGS_LIST_COL + 1,
433 ( ( widget->first_visible + SETTINGS_LIST_ROWS )
434 < widget->num_rows ? "..." : " " ) );
436 /* Draw visible settings. */
437 for ( i = 0; i < SETTINGS_LIST_ROWS; i++ ) {
438 if ( ( widget->first_visible + i ) < widget->num_rows ) {
439 select_setting_row ( widget,
440 widget->first_visible + i );
441 draw_setting_row ( widget );
443 clearmsg ( SETTINGS_LIST_ROW + i );
451 * @v widget Setting widget
452 * @v settings Settings block
454 static void init_widget ( struct setting_widget *widget,
455 struct settings *settings ) {
457 widget->settings = settings_target ( settings );
458 widget->num_rows = select_setting_row ( widget, 0 );
459 widget->first_visible = SETTINGS_LIST_ROWS;
460 draw_title_row ( widget );
461 reveal_setting_row ( widget, 0 );
462 select_setting_row ( widget, 0 );
465 static int main_loop ( struct settings *settings ) {
466 struct setting_widget widget;
473 /* Print initial screen content */
474 color_set ( CPAIR_NORMAL, NULL );
475 memset ( &widget, 0, sizeof ( widget ) );
476 init_widget ( &widget, settings );
480 /* Redraw rows if necessary */
482 draw_info_row ( &widget );
483 draw_instruction_row ( &widget );
484 color_set ( ( widget.row.editing ?
485 CPAIR_EDIT : CPAIR_SELECT ), NULL );
486 draw_setting_row ( &widget );
487 color_set ( CPAIR_NORMAL, NULL );
488 curs_set ( widget.row.editing );
492 if ( widget.row.editing ) {
495 assert ( widget.row.setting.name != NULL );
497 /* Redraw edit box */
498 color_set ( CPAIR_EDIT, NULL );
499 draw_editbox ( &widget.row.editbox );
500 color_set ( CPAIR_NORMAL, NULL );
502 /* Process keypress */
503 key = edit_setting ( &widget, getkey ( 0 ) );
507 if ( ( rc = save_setting ( &widget ) ) != 0 )
508 alert ( " %s ", strerror ( rc ) );
511 select_setting_row ( &widget, widget.current );
521 /* Process keypress */
532 move = ( widget.first_visible -
533 widget.current - 1 );
536 move = ( widget.first_visible - widget.current
537 + SETTINGS_LIST_ROWS );
540 move = -widget.num_rows;
543 move = +widget.num_rows;
546 if ( ! widget.row.setting.name )
548 if ( ( rc = delete_setting ( widget.settings,
549 &widget.row.setting ) ) != 0 ) {
550 alert ( " %s ", strerror ( rc ) );
552 select_setting_row ( &widget, widget.current );
559 if ( widget.row.settings ) {
560 init_widget ( &widget,
561 widget.row.settings );
566 if ( widget.row.setting.name ) {
567 edit_setting ( &widget, key );
573 next = ( widget.current + move );
574 if ( ( int ) next < 0 )
576 if ( next >= widget.num_rows )
577 next = ( widget.num_rows - 1 );
578 if ( next != widget.current ) {
579 draw_setting_row ( &widget );
581 reveal_setting_row ( &widget, next );
582 select_setting_row ( &widget, next );
589 int settings_ui ( struct settings *settings ) {
594 color_set ( CPAIR_NORMAL, NULL );
598 rc = main_loop ( settings );