Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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