You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

settings_ui.c 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  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. FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  24. #include <stdio.h>
  25. #include <stdarg.h>
  26. #include <unistd.h>
  27. #include <string.h>
  28. #include <curses.h>
  29. #include <ipxe/console.h>
  30. #include <ipxe/settings.h>
  31. #include <ipxe/editbox.h>
  32. #include <ipxe/keys.h>
  33. #include <ipxe/ansicol.h>
  34. #include <ipxe/jumpscroll.h>
  35. #include <ipxe/settings_ui.h>
  36. #include <config/branding.h>
  37. /** @file
  38. *
  39. * Option configuration console
  40. *
  41. */
  42. /* Screen layout */
  43. #define TITLE_ROW 1U
  44. #define SETTINGS_LIST_ROW 3U
  45. #define SETTINGS_LIST_COL 1U
  46. #define SETTINGS_LIST_ROWS ( LINES - 6U - SETTINGS_LIST_ROW )
  47. #define INFO_ROW ( LINES - 5U )
  48. #define ALERT_ROW ( LINES - 2U )
  49. #define INSTRUCTION_ROW ( LINES - 2U )
  50. #define INSTRUCTION_PAD " "
  51. /** Layout of text within a setting row */
  52. #define SETTING_ROW_TEXT( cols ) struct { \
  53. char start[0]; \
  54. char pad1[1]; \
  55. union { \
  56. char settings[ cols - 1 - 1 - 1 - 1 ]; \
  57. struct { \
  58. char name[15]; \
  59. char pad2[1]; \
  60. char value[ cols - 1 - 15 - 1 - 1 - 1 - 1 ]; \
  61. } setting; \
  62. } u; \
  63. char pad3[1]; \
  64. char nul; \
  65. } __attribute__ (( packed ))
  66. /** A settings user interface row */
  67. struct settings_ui_row {
  68. /** Target configuration settings block
  69. *
  70. * Valid only for rows that lead to new settings blocks.
  71. */
  72. struct settings *settings;
  73. /** Configuration setting origin
  74. *
  75. * Valid only for rows that represent individual settings.
  76. */
  77. struct settings *origin;
  78. /** Configuration setting
  79. *
  80. * Valid only for rows that represent individual settings.
  81. */
  82. struct setting setting;
  83. /** Screen row */
  84. unsigned int row;
  85. /** Edit box widget used for editing setting */
  86. struct edit_box editbox;
  87. /** Editing in progress flag */
  88. int editing;
  89. /** Buffer for setting's value */
  90. char value[256]; /* enough size for a DHCP string */
  91. };
  92. /** A settings user interface */
  93. struct settings_ui {
  94. /** Settings block */
  95. struct settings *settings;
  96. /** Jump scroller */
  97. struct jump_scroller scroll;
  98. /** Current row */
  99. struct settings_ui_row row;
  100. };
  101. /**
  102. * Select a setting
  103. *
  104. * @v ui Settings user interface
  105. * @v index Index of setting row
  106. * @ret count Number of setting rows
  107. */
  108. static unsigned int select_setting_row ( struct settings_ui *ui,
  109. unsigned int index ) {
  110. SETTING_ROW_TEXT ( COLS ) *text;
  111. struct settings *settings;
  112. struct setting *setting;
  113. struct setting *previous = NULL;
  114. unsigned int count = 0;
  115. /* Initialise structure */
  116. memset ( &ui->row, 0, sizeof ( ui->row ) );
  117. ui->row.row = ( SETTINGS_LIST_ROW + index - ui->scroll.first );
  118. /* Include parent settings block, if applicable */
  119. if ( ui->settings->parent && ( count++ == index ) ) {
  120. ui->row.settings = ui->settings->parent;
  121. snprintf ( ui->row.value, sizeof ( ui->row.value ),
  122. "../" );
  123. }
  124. /* Include any child settings blocks, if applicable */
  125. list_for_each_entry ( settings, &ui->settings->children, siblings ) {
  126. if ( count++ == index ) {
  127. ui->row.settings = settings;
  128. snprintf ( ui->row.value, sizeof ( ui->row.value ),
  129. "%s/", settings->name );
  130. }
  131. }
  132. /* Include any applicable settings */
  133. for_each_table_entry ( setting, SETTINGS ) {
  134. /* Skip inapplicable settings */
  135. if ( ! setting_applies ( ui->settings, setting ) )
  136. continue;
  137. /* Skip duplicate settings */
  138. if ( previous && ( setting_cmp ( setting, previous ) == 0 ) )
  139. continue;
  140. previous = setting;
  141. /* Read current setting value and origin */
  142. if ( count++ == index ) {
  143. fetchf_setting ( ui->settings, setting, &ui->row.origin,
  144. &ui->row.setting, ui->row.value,
  145. sizeof ( ui->row.value ) );
  146. }
  147. }
  148. /* Initialise edit box */
  149. init_editbox ( &ui->row.editbox, ui->row.value,
  150. sizeof ( ui->row.value ), NULL, ui->row.row,
  151. ( SETTINGS_LIST_COL +
  152. offsetof ( typeof ( *text ), u.setting.value ) ),
  153. sizeof ( text->u.setting.value ), 0 );
  154. return count;
  155. }
  156. /**
  157. * Copy string without NUL termination
  158. *
  159. * @v dest Destination
  160. * @v src Source
  161. * @v len Maximum length of destination
  162. * @ret len Length of (unterminated) string
  163. */
  164. static size_t string_copy ( char *dest, const char *src, size_t len ) {
  165. size_t src_len;
  166. src_len = strlen ( src );
  167. if ( len > src_len )
  168. len = src_len;
  169. memcpy ( dest, src, len );
  170. return len;
  171. }
  172. /**
  173. * Draw setting row
  174. *
  175. * @v ui Settings UI
  176. */
  177. static void draw_setting_row ( struct settings_ui *ui ) {
  178. SETTING_ROW_TEXT ( COLS ) text;
  179. unsigned int curs_offset;
  180. char *value;
  181. /* Fill row with spaces */
  182. memset ( &text, ' ', sizeof ( text ) );
  183. text.nul = '\0';
  184. /* Construct row content */
  185. if ( ui->row.settings ) {
  186. /* Construct space-padded name */
  187. curs_offset = ( offsetof ( typeof ( text ), u.settings ) +
  188. string_copy ( text.u.settings,
  189. ui->row.value,
  190. sizeof ( text.u.settings ) ) );
  191. } else {
  192. /* Construct dot-padded name */
  193. memset ( text.u.setting.name, '.',
  194. sizeof ( text.u.setting.name ) );
  195. string_copy ( text.u.setting.name, ui->row.setting.name,
  196. sizeof ( text.u.setting.name ) );
  197. /* Construct space-padded value */
  198. value = ui->row.value;
  199. if ( ! *value )
  200. value = "<not specified>";
  201. curs_offset = ( offsetof ( typeof ( text ), u.setting.value ) +
  202. string_copy ( text.u.setting.value, value,
  203. sizeof ( text.u.setting.value )));
  204. }
  205. /* Print row */
  206. if ( ( ui->row.origin == ui->settings ) || ( ui->row.settings != NULL ))
  207. attron ( A_BOLD );
  208. mvprintw ( ui->row.row, SETTINGS_LIST_COL, "%s", text.start );
  209. attroff ( A_BOLD );
  210. move ( ui->row.row, ( SETTINGS_LIST_COL + curs_offset ) );
  211. }
  212. /**
  213. * Edit setting ui
  214. *
  215. * @v ui Settings UI
  216. * @v key Key pressed by user
  217. * @ret key Key returned to application, or zero
  218. */
  219. static int edit_setting ( struct settings_ui *ui, int key ) {
  220. assert ( ui->row.setting.name != NULL );
  221. ui->row.editing = 1;
  222. return edit_editbox ( &ui->row.editbox, key );
  223. }
  224. /**
  225. * Save setting ui value back to configuration settings
  226. *
  227. * @v ui Settings UI
  228. */
  229. static int save_setting ( struct settings_ui *ui ) {
  230. assert ( ui->row.setting.name != NULL );
  231. return storef_setting ( ui->settings, &ui->row.setting, ui->row.value );
  232. }
  233. /**
  234. * Print message centred on specified row
  235. *
  236. * @v row Row
  237. * @v fmt printf() format string
  238. * @v args printf() argument list
  239. */
  240. static void vmsg ( unsigned int row, const char *fmt, va_list args ) {
  241. char buf[COLS];
  242. size_t len;
  243. len = vsnprintf ( buf, sizeof ( buf ), fmt, args );
  244. mvprintw ( row, ( ( COLS - len ) / 2 ), "%s", buf );
  245. }
  246. /**
  247. * Print message centred on specified row
  248. *
  249. * @v row Row
  250. * @v fmt printf() format string
  251. * @v .. printf() arguments
  252. */
  253. static void msg ( unsigned int row, const char *fmt, ... ) {
  254. va_list args;
  255. va_start ( args, fmt );
  256. vmsg ( row, fmt, args );
  257. va_end ( args );
  258. }
  259. /**
  260. * Clear message on specified row
  261. *
  262. * @v row Row
  263. */
  264. static void clearmsg ( unsigned int row ) {
  265. move ( row, 0 );
  266. clrtoeol();
  267. }
  268. /**
  269. * Print alert message
  270. *
  271. * @v fmt printf() format string
  272. * @v args printf() argument list
  273. */
  274. static void valert ( const char *fmt, va_list args ) {
  275. clearmsg ( ALERT_ROW );
  276. color_set ( CPAIR_ALERT, NULL );
  277. vmsg ( ALERT_ROW, fmt, args );
  278. sleep ( 2 );
  279. color_set ( CPAIR_NORMAL, NULL );
  280. clearmsg ( ALERT_ROW );
  281. }
  282. /**
  283. * Print alert message
  284. *
  285. * @v fmt printf() format string
  286. * @v ... printf() arguments
  287. */
  288. static void alert ( const char *fmt, ... ) {
  289. va_list args;
  290. va_start ( args, fmt );
  291. valert ( fmt, args );
  292. va_end ( args );
  293. }
  294. /**
  295. * Draw title row
  296. *
  297. * @v ui Settings UI
  298. */
  299. static void draw_title_row ( struct settings_ui *ui ) {
  300. const char *name;
  301. clearmsg ( TITLE_ROW );
  302. name = settings_name ( ui->settings );
  303. attron ( A_BOLD );
  304. msg ( TITLE_ROW, PRODUCT_SHORT_NAME " configuration settings%s%s",
  305. ( name[0] ? " - " : "" ), name );
  306. attroff ( A_BOLD );
  307. }
  308. /**
  309. * Draw information row
  310. *
  311. * @v ui Settings UI
  312. */
  313. static void draw_info_row ( struct settings_ui *ui ) {
  314. char buf[32];
  315. /* Draw nothing unless this row represents a setting */
  316. clearmsg ( INFO_ROW );
  317. clearmsg ( INFO_ROW + 1 );
  318. if ( ! ui->row.setting.name )
  319. return;
  320. /* Determine a suitable setting name */
  321. setting_name ( ( ui->row.origin ?
  322. ui->row.origin : ui->settings ),
  323. &ui->row.setting, buf, sizeof ( buf ) );
  324. /* Draw row */
  325. attron ( A_BOLD );
  326. msg ( INFO_ROW, "%s - %s", buf, ui->row.setting.description );
  327. attroff ( A_BOLD );
  328. color_set ( CPAIR_URL, NULL );
  329. msg ( ( INFO_ROW + 1 ), PRODUCT_SETTING_URI, ui->row.setting.name );
  330. color_set ( CPAIR_NORMAL, NULL );
  331. }
  332. /**
  333. * Draw instruction row
  334. *
  335. * @v ui Settings UI
  336. */
  337. static void draw_instruction_row ( struct settings_ui *ui ) {
  338. clearmsg ( INSTRUCTION_ROW );
  339. if ( ui->row.editing ) {
  340. msg ( INSTRUCTION_ROW,
  341. "Enter - accept changes" INSTRUCTION_PAD
  342. "Ctrl-C - discard changes" );
  343. } else {
  344. msg ( INSTRUCTION_ROW,
  345. "%sCtrl-X - exit configuration utility",
  346. ( ( ui->row.origin == ui->settings ) ?
  347. "Ctrl-D - delete setting" INSTRUCTION_PAD : "" ) );
  348. }
  349. }
  350. /**
  351. * Draw the current block of setting rows
  352. *
  353. * @v ui Settings UI
  354. */
  355. static void draw_setting_rows ( struct settings_ui *ui ) {
  356. unsigned int i;
  357. /* Draw ellipses before and/or after the list as necessary */
  358. color_set ( CPAIR_SEPARATOR, NULL );
  359. mvaddstr ( ( SETTINGS_LIST_ROW - 1 ), ( SETTINGS_LIST_COL + 1 ),
  360. jump_scroll_is_first ( &ui->scroll ) ? " " : "..." );
  361. mvaddstr ( ( SETTINGS_LIST_ROW + SETTINGS_LIST_ROWS ),
  362. ( SETTINGS_LIST_COL + 1 ),
  363. jump_scroll_is_last ( &ui->scroll ) ? " " : "..." );
  364. color_set ( CPAIR_NORMAL, NULL );
  365. /* Draw visible settings. */
  366. for ( i = 0 ; i < SETTINGS_LIST_ROWS ; i++ ) {
  367. if ( ( ui->scroll.first + i ) < ui->scroll.count ) {
  368. select_setting_row ( ui, ( ui->scroll.first + i ) );
  369. draw_setting_row ( ui );
  370. } else {
  371. clearmsg ( SETTINGS_LIST_ROW + i );
  372. }
  373. }
  374. }
  375. /**
  376. * Select settings block
  377. *
  378. * @v ui Settings UI
  379. * @v settings Settings block
  380. */
  381. static void select_settings ( struct settings_ui *ui,
  382. struct settings *settings ) {
  383. ui->settings = settings_target ( settings );
  384. ui->scroll.count = select_setting_row ( ui, 0 );
  385. ui->scroll.rows = SETTINGS_LIST_ROWS;
  386. ui->scroll.current = 0;
  387. ui->scroll.first = 0;
  388. draw_title_row ( ui );
  389. draw_setting_rows ( ui );
  390. select_setting_row ( ui, 0 );
  391. }
  392. static int main_loop ( struct settings *settings ) {
  393. struct settings_ui ui;
  394. unsigned int previous;
  395. int redraw = 1;
  396. int move;
  397. int key;
  398. int rc;
  399. /* Print initial screen content */
  400. color_set ( CPAIR_NORMAL, NULL );
  401. memset ( &ui, 0, sizeof ( ui ) );
  402. select_settings ( &ui, settings );
  403. while ( 1 ) {
  404. /* Redraw rows if necessary */
  405. if ( redraw ) {
  406. draw_info_row ( &ui );
  407. draw_instruction_row ( &ui );
  408. color_set ( ( ui.row.editing ?
  409. CPAIR_EDIT : CPAIR_SELECT ), NULL );
  410. draw_setting_row ( &ui );
  411. color_set ( CPAIR_NORMAL, NULL );
  412. curs_set ( ui.row.editing );
  413. redraw = 0;
  414. }
  415. /* Edit setting, if we are currently editing */
  416. if ( ui.row.editing ) {
  417. /* Sanity check */
  418. assert ( ui.row.setting.name != NULL );
  419. /* Redraw edit box */
  420. color_set ( CPAIR_EDIT, NULL );
  421. draw_editbox ( &ui.row.editbox );
  422. color_set ( CPAIR_NORMAL, NULL );
  423. /* Process keypress */
  424. key = edit_setting ( &ui, getkey ( 0 ) );
  425. switch ( key ) {
  426. case CR:
  427. case LF:
  428. if ( ( rc = save_setting ( &ui ) ) != 0 )
  429. alert ( " %s ", strerror ( rc ) );
  430. /* Fall through */
  431. case CTRL_C:
  432. select_setting_row ( &ui, ui.scroll.current );
  433. redraw = 1;
  434. break;
  435. default:
  436. /* Do nothing */
  437. break;
  438. }
  439. continue;
  440. }
  441. /* Otherwise, navigate through settings */
  442. key = getkey ( 0 );
  443. move = jump_scroll_key ( &ui.scroll, key );
  444. if ( move ) {
  445. previous = ui.scroll.current;
  446. jump_scroll_move ( &ui.scroll, move );
  447. if ( ui.scroll.current != previous ) {
  448. draw_setting_row ( &ui );
  449. redraw = 1;
  450. if ( jump_scroll ( &ui.scroll ) )
  451. draw_setting_rows ( &ui );
  452. select_setting_row ( &ui, ui.scroll.current );
  453. }
  454. continue;
  455. }
  456. /* Handle non-navigation keys */
  457. switch ( key ) {
  458. case CTRL_D:
  459. if ( ! ui.row.setting.name )
  460. break;
  461. if ( ( rc = delete_setting ( ui.settings,
  462. &ui.row.setting ) ) != 0 ){
  463. alert ( " %s ", strerror ( rc ) );
  464. }
  465. select_setting_row ( &ui, ui.scroll.current );
  466. redraw = 1;
  467. break;
  468. case CTRL_X:
  469. return 0;
  470. case CR:
  471. case LF:
  472. if ( ui.row.settings ) {
  473. select_settings ( &ui, ui.row.settings );
  474. redraw = 1;
  475. }
  476. /* Fall through */
  477. default:
  478. if ( ui.row.setting.name ) {
  479. edit_setting ( &ui, key );
  480. redraw = 1;
  481. }
  482. break;
  483. }
  484. }
  485. }
  486. int settings_ui ( struct settings *settings ) {
  487. int rc;
  488. initscr();
  489. start_color();
  490. color_set ( CPAIR_NORMAL, NULL );
  491. curs_set ( 0 );
  492. erase();
  493. rc = main_loop ( settings );
  494. endwin();
  495. return rc;
  496. }