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
);