[golan] Do not assume all devices are identical
[ipxe.git] / src / hci / tui / settings_ui.c
1 /*
2 * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
3 *
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.
8 *
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.
13 *
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
17 * 02110-1301, USA.
18 *
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.
22 */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 #include <stdio.h>
27 #include <stdarg.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <curses.h>
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>
39
40 /** @file
41 *
42 * Option configuration console
43 *
44 */
45
46 /* Screen layout */
47 #define TITLE_ROW 1U
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 " "
55
56 /** Layout of text within a setting row */
57 #define SETTING_ROW_TEXT( cols ) struct { \
58 char start[0]; \
59 char pad1[1]; \
60 union { \
61 char settings[ cols - 1 - 1 - 1 - 1 ]; \
62 struct { \
63 char name[15]; \
64 char pad2[1]; \
65 char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \
66 } setting; \
67 } u; \
68 char pad3[1]; \
69 char nul; \
70 } __attribute__ (( packed ))
71
72 /** A settings user interface row */
73 struct settings_ui_row {
74 /** Target configuration settings block
75 *
76 * Valid only for rows that lead to new settings blocks.
77 */
78 struct settings *settings;
79 /** Configuration setting origin
80 *
81 * Valid only for rows that represent individual settings.
82 */
83 struct settings *origin;
84 /** Configuration setting
85 *
86 * Valid only for rows that represent individual settings.
87 */
88 struct setting setting;
89 /** Screen row */
90 unsigned int row;
91 /** Edit box widget used for editing setting */
92 struct edit_box editbox;
93 /** Editing in progress flag */
94 int editing;
95 /** Buffer for setting's value */
96 char value[256]; /* enough size for a DHCP string */
97 };
98
99 /** A settings user interface */
100 struct settings_ui {
101 /** Settings block */
102 struct settings *settings;
103 /** Jump scroller */
104 struct jump_scroller scroll;
105 /** Current row */
106 struct settings_ui_row row;
107 };
108
109 /**
110 * Select a setting
111 *
112 * @v ui Settings user interface
113 * @v index Index of setting row
114 * @ret count Number of setting rows
115 */
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;
123
124 /* Initialise structure */
125 memset ( &ui->row, 0, sizeof ( ui->row ) );
126 ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first );
127
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 ),
132 "../" );
133 }
134
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 );
141 }
142 }
143
144 /* Include any applicable settings */
145 for_each_table_entry ( setting, SETTINGS ) {
146
147 /* Skip inapplicable settings */
148 if ( ! setting_applies ( ui->settings, setting ) )
149 continue;
150
151 /* Skip duplicate settings */
152 if ( previous && ( setting_cmp ( setting, previous ) == 0 ) )
153 continue;
154 previous = setting;
155
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 ) );
161 }
162 }
163
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 );
170
171 return count;
172 }
173
174 /**
175 * Copy string without NUL termination
176 *
177 * @v dest Destination
178 * @v src Source
179 * @v len Maximum length of destination
180 * @ret len Length of (unterminated) string
181 */
182 static size_t string_copy ( char *dest, const char *src, size_t len ) {
183 size_t src_len;
184
185 src_len = strlen ( src );
186 if ( len > src_len )
187 len = src_len;
188 memcpy ( dest, src, len );
189 return len;
190 }
191
192 /**
193 * Draw setting row
194 *
195 * @v ui Settings UI
196 */
197 static void draw_setting_row ( struct settings_ui *ui ) {
198 SETTING_ROW_TEXT ( COLS ) text;
199 unsigned int curs_offset;
200 char *value;
201
202 /* Fill row with spaces */
203 memset ( &text, ' ', sizeof ( text ) );
204 text.nul = '\0';
205
206 /* Construct row content */
207 if ( ui->row.settings ) {
208
209 /* Construct space-padded name */
210 curs_offset = ( offsetof ( typeof ( text ), u.settings ) +
211 string_copy ( text.u.settings,
212 ui->row.value,
213 sizeof ( text.u.settings ) ) );
214
215 } else {
216
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 ) );
222
223 /* Construct space-padded value */
224 value = ui->row.value;
225 if ( ! *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 )));
230 }
231
232 /* Print row */
233 if ( ( ui->row.origin == ui->settings ) || ( ui->row.settings != NULL ))
234 attron ( A_BOLD );
235 mvprintw ( ui->row.row, SETTINGS_LIST_COL, "%s", text.start );
236 attroff ( A_BOLD );
237 move ( ui->row.row, ( SETTINGS_LIST_COL + curs_offset ) );
238 }
239
240 /**
241 * Edit setting ui
242 *
243 * @v ui Settings UI
244 * @v key Key pressed by user
245 * @ret key Key returned to application, or zero
246 */
247 static int edit_setting ( struct settings_ui *ui, int key ) {
248 assert ( ui->row.setting.name != NULL );
249 ui->row.editing = 1;
250 return edit_editbox ( &ui->row.editbox, key );
251 }
252
253 /**
254 * Save setting ui value back to configuration settings
255 *
256 * @v ui Settings UI
257 */
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 );
261 }
262
263 /**
264 * Print message centred on specified row
265 *
266 * @v row Row
267 * @v fmt printf() format string
268 * @v args printf() argument list
269 */
270 static void vmsg ( unsigned int row, const char *fmt, va_list args ) {
271 char buf[COLS];
272 size_t len;
273
274 len = vsnprintf ( buf, sizeof ( buf ), fmt, args );
275 mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf );
276 }
277
278 /**
279 * Print message centred on specified row
280 *
281 * @v row Row
282 * @v fmt printf() format string
283 * @v .. printf() arguments
284 */
285 static void msg ( unsigned int row, const char *fmt, ... ) {
286 va_list args;
287
288 va_start ( args, fmt );
289 vmsg ( row, fmt, args );
290 va_end ( args );
291 }
292
293 /**
294 * Clear message on specified row
295 *
296 * @v row Row
297 */
298 static void clearmsg ( unsigned int row ) {
299 move ( row, 0 );
300 clrtoeol();
301 }
302
303 /**
304 * Print alert message
305 *
306 * @v fmt printf() format string
307 * @v args printf() argument list
308 */
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 );
313 sleep ( 2 );
314 color_set ( CPAIR_NORMAL, NULL );
315 clearmsg ( ALERT_ROW );
316 }
317
318 /**
319 * Print alert message
320 *
321 * @v fmt printf() format string
322 * @v ... printf() arguments
323 */
324 static void alert ( const char *fmt, ... ) {
325 va_list args;
326
327 va_start ( args, fmt );
328 valert ( fmt, args );
329 va_end ( args );
330 }
331
332 /**
333 * Draw title row
334 *
335 * @v ui Settings UI
336 */
337 static void draw_title_row ( struct settings_ui *ui ) {
338 const char *name;
339
340 clearmsg ( TITLE_ROW );
341 name = settings_name ( ui->settings );
342 attron ( A_BOLD );
343 msg ( TITLE_ROW, PRODUCT_SHORT_NAME " configuration settings%s%s",
344 ( name[0] ? " - " : "" ), name );
345 attroff ( A_BOLD );
346 }
347
348 /**
349 * Draw information row
350 *
351 * @v ui Settings UI
352 */
353 static void draw_info_row ( struct settings_ui *ui ) {
354 char buf[32];
355
356 /* Draw nothing unless this row represents a setting */
357 clearmsg ( INFO_ROW );
358 clearmsg ( INFO_ROW + 1 );
359 if ( ! ui->row.setting.name )
360 return;
361
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 ) );
366
367 /* Draw row */
368 attron ( A_BOLD );
369 msg ( INFO_ROW, "%s - %s", buf, ui->row.setting.description );
370 attroff ( A_BOLD );
371 color_set ( CPAIR_URL, NULL );
372 msg ( ( INFO_ROW + 1 ), PRODUCT_SETTING_URI, ui->row.setting.name );
373 color_set ( CPAIR_NORMAL, NULL );
374 }
375
376 /**
377 * Draw instruction row
378 *
379 * @v ui Settings UI
380 */
381 static void draw_instruction_row ( struct settings_ui *ui ) {
382
383 clearmsg ( INSTRUCTION_ROW );
384 if ( ui->row.editing ) {
385 msg ( INSTRUCTION_ROW,
386 "Enter - accept changes" INSTRUCTION_PAD
387 "Ctrl-C - discard changes" );
388 } else {
389 msg ( INSTRUCTION_ROW,
390 "%sCtrl-X - exit configuration utility",
391 ( ( ui->row.origin == ui->settings ) ?
392 "Ctrl-D - delete setting" INSTRUCTION_PAD : "" ) );
393 }
394 }
395
396 /**
397 * Draw the current block of setting rows
398 *
399 * @v ui Settings UI
400 */
401 static void draw_setting_rows ( struct settings_ui *ui ) {
402 unsigned int i;
403
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 );
412
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 );
418 } else {
419 clearmsg ( SETTINGS_LIST_ROW + i );
420 }
421 }
422 }
423
424 /**
425 * Select settings block
426 *
427 * @v ui Settings UI
428 * @v settings Settings block
429 */
430 static void select_settings ( struct settings_ui *ui,
431 struct settings *settings ) {
432
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 );
441 }
442
443 static int main_loop ( struct settings *settings ) {
444 struct settings_ui ui;
445 unsigned int previous;
446 int redraw = 1;
447 int move;
448 int key;
449 int rc;
450
451 /* Print initial screen content */
452 color_set ( CPAIR_NORMAL, NULL );
453 memset ( &ui, 0, sizeof ( ui ) );
454 select_settings ( &ui, settings );
455
456 while ( 1 ) {
457
458 /* Redraw rows if necessary */
459 if ( redraw ) {
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 );
467 redraw = 0;
468 }
469
470 /* Edit setting, if we are currently editing */
471 if ( ui.row.editing ) {
472
473 /* Sanity check */
474 assert ( ui.row.setting.name != NULL );
475
476 /* Redraw edit box */
477 color_set ( CPAIR_EDIT, NULL );
478 draw_editbox ( &ui.row.editbox );
479 color_set ( CPAIR_NORMAL, NULL );
480
481 /* Process keypress */
482 key = edit_setting ( &ui, getkey ( 0 ) );
483 switch ( key ) {
484 case CR:
485 case LF:
486 if ( ( rc = save_setting ( &ui ) ) != 0 )
487 alert ( " %s ", strerror ( rc ) );
488 /* Fall through */
489 case CTRL_C:
490 select_setting_row ( &ui, ui.scroll.current );
491 redraw = 1;
492 break;
493 default:
494 /* Do nothing */
495 break;
496 }
497
498 continue;
499 }
500
501 /* Otherwise, navigate through settings */
502 key = getkey ( 0 );
503 move = jump_scroll_key ( &ui.scroll, key );
504 if ( move ) {
505 previous = ui.scroll.current;
506 jump_scroll_move ( &ui.scroll, move );
507 if ( ui.scroll.current != previous ) {
508 draw_setting_row ( &ui );
509 redraw = 1;
510 if ( jump_scroll ( &ui.scroll ) )
511 draw_setting_rows ( &ui );
512 select_setting_row ( &ui, ui.scroll.current );
513 }
514 continue;
515 }
516
517 /* Handle non-navigation keys */
518 switch ( key ) {
519 case CTRL_D:
520 if ( ! ui.row.setting.name )
521 break;
522 if ( ( rc = delete_setting ( ui.settings,
523 &ui.row.setting ) ) != 0 ){
524 alert ( " %s ", strerror ( rc ) );
525 }
526 select_setting_row ( &ui, ui.scroll.current );
527 redraw = 1;
528 break;
529 case CTRL_X:
530 return 0;
531 case CR:
532 case LF:
533 if ( ui.row.settings ) {
534 select_settings ( &ui, ui.row.settings );
535 redraw = 1;
536 }
537 /* Fall through */
538 default:
539 if ( ui.row.setting.name ) {
540 edit_setting ( &ui, key );
541 redraw = 1;
542 }
543 break;
544 }
545 }
546 }
547
548 int settings_ui ( struct settings *settings ) {
549 int rc;
550
551 initscr();
552 start_color();
553 color_set ( CPAIR_NORMAL, NULL );
554 curs_set ( 0 );
555 erase();
556
557 rc = main_loop ( settings );
558
559 endwin();
560
561 return rc;
562 }