1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * util.c 4 * 5 * ORIGINAL AUTHOR: Savio Lam (lam836@cs.cuhk.hk) 6 * MODIFIED FOR LINUX KERNEL CONFIG BY: William Roadcap (roadcap@cfw.com) 7 */ 8 9 #include <stdarg.h> 10 11 #include "dialog.h" 12 13 /* Needed in signal handler in mconf.c */ 14 int saved_x, saved_y; 15 16 struct dialog_info dlg; 17 18 static void set_mono_theme(void) 19 { 20 dlg.screen.atr = A_NORMAL; 21 dlg.shadow.atr = A_NORMAL; 22 dlg.dialog.atr = A_NORMAL; 23 dlg.title.atr = A_BOLD; 24 dlg.border.atr = A_NORMAL; 25 dlg.button_active.atr = A_REVERSE; 26 dlg.button_inactive.atr = A_DIM; 27 dlg.button_key_active.atr = A_REVERSE; 28 dlg.button_key_inactive.atr = A_BOLD; 29 dlg.button_label_active.atr = A_REVERSE; 30 dlg.button_label_inactive.atr = A_NORMAL; 31 dlg.inputbox.atr = A_NORMAL; 32 dlg.position_indicator.atr = A_BOLD; 33 dlg.menubox.atr = A_NORMAL; 34 dlg.menubox_border.atr = A_NORMAL; 35 dlg.item.atr = A_NORMAL; 36 dlg.item_selected.atr = A_REVERSE; 37 dlg.tag.atr = A_BOLD; 38 dlg.tag_selected.atr = A_REVERSE; 39 dlg.tag_key.atr = A_BOLD; 40 dlg.tag_key_selected.atr = A_REVERSE; 41 dlg.check.atr = A_BOLD; 42 dlg.check_selected.atr = A_REVERSE; 43 dlg.uarrow.atr = A_BOLD; 44 dlg.darrow.atr = A_BOLD; 45 } 46 47 #define DLG_COLOR(dialog, f, b, h) \ 48 do { \ 49 dlg.dialog.fg = (f); \ 50 dlg.dialog.bg = (b); \ 51 dlg.dialog.hl = (h); \ 52 } while (0) 53 54 static void set_classic_theme(void) 55 { 56 DLG_COLOR(screen, COLOR_CYAN, COLOR_BLUE, true); 57 DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, true); 58 DLG_COLOR(dialog, COLOR_BLACK, COLOR_WHITE, false); 59 DLG_COLOR(title, COLOR_YELLOW, COLOR_WHITE, true); 60 DLG_COLOR(border, COLOR_WHITE, COLOR_WHITE, true); 61 DLG_COLOR(button_active, COLOR_WHITE, COLOR_BLUE, true); 62 DLG_COLOR(button_inactive, COLOR_BLACK, COLOR_WHITE, false); 63 DLG_COLOR(button_key_active, COLOR_WHITE, COLOR_BLUE, true); 64 DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_WHITE, false); 65 DLG_COLOR(button_label_active, COLOR_YELLOW, COLOR_BLUE, true); 66 DLG_COLOR(button_label_inactive, COLOR_BLACK, COLOR_WHITE, true); 67 DLG_COLOR(inputbox, COLOR_BLACK, COLOR_WHITE, false); 68 DLG_COLOR(position_indicator, COLOR_YELLOW, COLOR_WHITE, true); 69 DLG_COLOR(menubox, COLOR_BLACK, COLOR_WHITE, false); 70 DLG_COLOR(menubox_border, COLOR_WHITE, COLOR_WHITE, true); 71 DLG_COLOR(item, COLOR_BLACK, COLOR_WHITE, false); 72 DLG_COLOR(item_selected, COLOR_WHITE, COLOR_BLUE, true); 73 DLG_COLOR(tag, COLOR_YELLOW, COLOR_WHITE, true); 74 DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_BLUE, true); 75 DLG_COLOR(tag_key, COLOR_YELLOW, COLOR_WHITE, true); 76 DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_BLUE, true); 77 DLG_COLOR(check, COLOR_BLACK, COLOR_WHITE, false); 78 DLG_COLOR(check_selected, COLOR_WHITE, COLOR_BLUE, true); 79 DLG_COLOR(uarrow, COLOR_GREEN, COLOR_WHITE, true); 80 DLG_COLOR(darrow, COLOR_GREEN, COLOR_WHITE, true); 81 } 82 83 static void set_blackbg_theme(void) 84 { 85 DLG_COLOR(screen, COLOR_RED, COLOR_BLACK, true); 86 DLG_COLOR(shadow, COLOR_BLACK, COLOR_BLACK, false); 87 DLG_COLOR(dialog, COLOR_WHITE, COLOR_BLACK, false); 88 DLG_COLOR(title, COLOR_RED, COLOR_BLACK, false); 89 DLG_COLOR(border, COLOR_BLACK, COLOR_BLACK, true); 90 91 DLG_COLOR(button_active, COLOR_YELLOW, COLOR_RED, false); 92 DLG_COLOR(button_inactive, COLOR_YELLOW, COLOR_BLACK, false); 93 DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_RED, true); 94 DLG_COLOR(button_key_inactive, COLOR_RED, COLOR_BLACK, false); 95 DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_RED, false); 96 DLG_COLOR(button_label_inactive, COLOR_WHITE, COLOR_BLACK, false); 97 98 DLG_COLOR(inputbox, COLOR_YELLOW, COLOR_BLACK, false); 99 100 DLG_COLOR(position_indicator, COLOR_RED, COLOR_BLACK, false); 101 102 DLG_COLOR(menubox, COLOR_YELLOW, COLOR_BLACK, false); 103 DLG_COLOR(menubox_border, COLOR_BLACK, COLOR_BLACK, true); 104 105 DLG_COLOR(item, COLOR_WHITE, COLOR_BLACK, false); 106 DLG_COLOR(item_selected, COLOR_WHITE, COLOR_RED, false); 107 108 DLG_COLOR(tag, COLOR_RED, COLOR_BLACK, false); 109 DLG_COLOR(tag_selected, COLOR_YELLOW, COLOR_RED, true); 110 DLG_COLOR(tag_key, COLOR_RED, COLOR_BLACK, false); 111 DLG_COLOR(tag_key_selected, COLOR_YELLOW, COLOR_RED, true); 112 113 DLG_COLOR(check, COLOR_YELLOW, COLOR_BLACK, false); 114 DLG_COLOR(check_selected, COLOR_YELLOW, COLOR_RED, true); 115 116 DLG_COLOR(uarrow, COLOR_RED, COLOR_BLACK, false); 117 DLG_COLOR(darrow, COLOR_RED, COLOR_BLACK, false); 118 } 119 120 static void set_bluetitle_theme(void) 121 { 122 set_classic_theme(); 123 DLG_COLOR(title, COLOR_BLUE, COLOR_WHITE, true); 124 DLG_COLOR(button_key_active, COLOR_YELLOW, COLOR_BLUE, true); 125 DLG_COLOR(button_label_active, COLOR_WHITE, COLOR_BLUE, true); 126 DLG_COLOR(position_indicator, COLOR_BLUE, COLOR_WHITE, true); 127 DLG_COLOR(tag, COLOR_BLUE, COLOR_WHITE, true); 128 DLG_COLOR(tag_key, COLOR_BLUE, COLOR_WHITE, true); 129 130 } 131 132 /* 133 * Select color theme 134 */ 135 static int set_theme(const char *theme) 136 { 137 int use_color = 1; 138 if (!theme) 139 set_bluetitle_theme(); 140 else if (strcmp(theme, "classic") == 0) 141 set_classic_theme(); 142 else if (strcmp(theme, "bluetitle") == 0) 143 set_bluetitle_theme(); 144 else if (strcmp(theme, "blackbg") == 0) 145 set_blackbg_theme(); 146 else if (strcmp(theme, "mono") == 0) 147 use_color = 0; 148 149 return use_color; 150 } 151 152 static void init_one_color(struct dialog_color *color) 153 { 154 static int pair = 0; 155 156 pair++; 157 init_pair(pair, color->fg, color->bg); 158 if (color->hl) 159 color->atr = A_BOLD | COLOR_PAIR(pair); 160 else 161 color->atr = COLOR_PAIR(pair); 162 } 163 164 static void init_dialog_colors(void) 165 { 166 init_one_color(&dlg.screen); 167 init_one_color(&dlg.shadow); 168 init_one_color(&dlg.dialog); 169 init_one_color(&dlg.title); 170 init_one_color(&dlg.border); 171 init_one_color(&dlg.button_active); 172 init_one_color(&dlg.button_inactive); 173 init_one_color(&dlg.button_key_active); 174 init_one_color(&dlg.button_key_inactive); 175 init_one_color(&dlg.button_label_active); 176 init_one_color(&dlg.button_label_inactive); 177 init_one_color(&dlg.inputbox); 178 init_one_color(&dlg.position_indicator); 179 init_one_color(&dlg.menubox); 180 init_one_color(&dlg.menubox_border); 181 init_one_color(&dlg.item); 182 init_one_color(&dlg.item_selected); 183 init_one_color(&dlg.tag); 184 init_one_color(&dlg.tag_selected); 185 init_one_color(&dlg.tag_key); 186 init_one_color(&dlg.tag_key_selected); 187 init_one_color(&dlg.check); 188 init_one_color(&dlg.check_selected); 189 init_one_color(&dlg.uarrow); 190 init_one_color(&dlg.darrow); 191 } 192 193 /* 194 * Setup for color display 195 */ 196 static void color_setup(const char *theme) 197 { 198 int use_color; 199 200 use_color = set_theme(theme); 201 if (use_color && has_colors()) { 202 start_color(); 203 init_dialog_colors(); 204 } else 205 set_mono_theme(); 206 } 207 208 /* 209 * Set window to attribute 'attr' 210 */ 211 void attr_clear(WINDOW * win, int height, int width, chtype attr) 212 { 213 int i, j; 214 215 wattrset(win, attr); 216 for (i = 0; i < height; i++) { 217 wmove(win, i, 0); 218 for (j = 0; j < width; j++) 219 waddch(win, ' '); 220 } 221 touchwin(win); 222 } 223 224 void dialog_clear(void) 225 { 226 int lines, columns; 227 228 lines = getmaxy(stdscr); 229 columns = getmaxx(stdscr); 230 231 attr_clear(stdscr, lines, columns, dlg.screen.atr); 232 /* Display background title if it exists ... - SLH */ 233 if (dlg.backtitle != NULL) { 234 int i, len = 0, skip = 0; 235 struct subtitle_list *pos; 236 237 wattrset(stdscr, dlg.screen.atr); 238 mvwaddstr(stdscr, 0, 1, (char *)dlg.backtitle); 239 240 for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { 241 /* 3 is for the arrow and spaces */ 242 len += strlen(pos->text) + 3; 243 } 244 245 wmove(stdscr, 1, 1); 246 if (len > columns - 2) { 247 const char *ellipsis = "[...] "; 248 waddstr(stdscr, ellipsis); 249 skip = len - (columns - 2 - strlen(ellipsis)); 250 } 251 252 for (pos = dlg.subtitles; pos != NULL; pos = pos->next) { 253 if (skip == 0) 254 waddch(stdscr, ACS_RARROW); 255 else 256 skip--; 257 258 if (skip == 0) 259 waddch(stdscr, ' '); 260 else 261 skip--; 262 263 if (skip < strlen(pos->text)) { 264 waddstr(stdscr, pos->text + skip); 265 skip = 0; 266 } else 267 skip -= strlen(pos->text); 268 269 if (skip == 0) 270 waddch(stdscr, ' '); 271 else 272 skip--; 273 } 274 275 for (i = len + 1; i < columns - 1; i++) 276 waddch(stdscr, ACS_HLINE); 277 } 278 wnoutrefresh(stdscr); 279 } 280 281 /* 282 * Do some initialization for dialog 283 */ 284 int init_dialog(const char *backtitle) 285 { 286 int height, width; 287 288 initscr(); /* Init curses */ 289 290 /* Get current cursor position for signal handler in mconf.c */ 291 getyx(stdscr, saved_y, saved_x); 292 293 getmaxyx(stdscr, height, width); 294 if (height < WINDOW_HEIGTH_MIN || width < WINDOW_WIDTH_MIN) { 295 endwin(); 296 return -ERRDISPLAYTOOSMALL; 297 } 298 299 dlg.backtitle = backtitle; 300 color_setup(getenv("MENUCONFIG_COLOR")); 301 302 keypad(stdscr, TRUE); 303 cbreak(); 304 noecho(); 305 dialog_clear(); 306 307 return 0; 308 } 309 310 void set_dialog_backtitle(const char *backtitle) 311 { 312 dlg.backtitle = backtitle; 313 } 314 315 void set_dialog_subtitles(struct subtitle_list *subtitles) 316 { 317 dlg.subtitles = subtitles; 318 } 319 320 /* 321 * End using dialog functions. 322 */ 323 void end_dialog(int x, int y) 324 { 325 /* move cursor back to original position */ 326 move(y, x); 327 refresh(); 328 endwin(); 329 } 330 331 /* Print the title of the dialog. Center the title and truncate 332 * tile if wider than dialog (- 2 chars). 333 **/ 334 void print_title(WINDOW *dialog, const char *title, int width) 335 { 336 if (title) { 337 int tlen = MIN(width - 2, strlen(title)); 338 wattrset(dialog, dlg.title.atr); 339 mvwaddch(dialog, 0, (width - tlen) / 2 - 1, ' '); 340 mvwaddnstr(dialog, 0, (width - tlen)/2, title, tlen); 341 waddch(dialog, ' '); 342 } 343 } 344 345 /* 346 * Print a string of text in a window, automatically wrap around to the 347 * next line if the string is too long to fit on one line. Newline 348 * characters '\n' are properly processed. We start on a new line 349 * if there is no room for at least 4 nonblanks following a double-space. 350 */ 351 void print_autowrap(WINDOW * win, const char *prompt, int width, int y, int x) 352 { 353 int newl, cur_x, cur_y; 354 int prompt_len, room, wlen; 355 char tempstr[MAX_LEN + 1], *word, *sp, *sp2, *newline_separator = 0; 356 357 strcpy(tempstr, prompt); 358 359 prompt_len = strlen(tempstr); 360 361 if (prompt_len <= width - x * 2) { /* If prompt is short */ 362 wmove(win, y, (width - prompt_len) / 2); 363 waddstr(win, tempstr); 364 } else { 365 cur_x = x; 366 cur_y = y; 367 newl = 1; 368 word = tempstr; 369 while (word && *word) { 370 sp = strpbrk(word, "\n "); 371 if (sp && *sp == '\n') 372 newline_separator = sp; 373 374 if (sp) 375 *sp++ = 0; 376 377 /* Wrap to next line if either the word does not fit, 378 or it is the first word of a new sentence, and it is 379 short, and the next word does not fit. */ 380 room = width - cur_x; 381 wlen = strlen(word); 382 if (wlen > room || 383 (newl && wlen < 4 && sp 384 && wlen + 1 + strlen(sp) > room 385 && (!(sp2 = strpbrk(sp, "\n ")) 386 || wlen + 1 + (sp2 - sp) > room))) { 387 cur_y++; 388 cur_x = x; 389 } 390 wmove(win, cur_y, cur_x); 391 waddstr(win, word); 392 getyx(win, cur_y, cur_x); 393 394 /* Move to the next line if the word separator was a newline */ 395 if (newline_separator) { 396 cur_y++; 397 cur_x = x; 398 newline_separator = 0; 399 } else 400 cur_x++; 401 402 if (sp && *sp == ' ') { 403 cur_x++; /* double space */ 404 while (*++sp == ' ') ; 405 newl = 1; 406 } else 407 newl = 0; 408 word = sp; 409 } 410 } 411 } 412 413 /* 414 * Print a button 415 */ 416 void print_button(WINDOW * win, const char *label, int y, int x, int selected) 417 { 418 int i, temp; 419 420 wmove(win, y, x); 421 wattrset(win, selected ? dlg.button_active.atr 422 : dlg.button_inactive.atr); 423 waddstr(win, "<"); 424 temp = strspn(label, " "); 425 label += temp; 426 wattrset(win, selected ? dlg.button_label_active.atr 427 : dlg.button_label_inactive.atr); 428 for (i = 0; i < temp; i++) 429 waddch(win, ' '); 430 wattrset(win, selected ? dlg.button_key_active.atr 431 : dlg.button_key_inactive.atr); 432 waddch(win, label[0]); 433 wattrset(win, selected ? dlg.button_label_active.atr 434 : dlg.button_label_inactive.atr); 435 waddstr(win, (char *)label + 1); 436 wattrset(win, selected ? dlg.button_active.atr 437 : dlg.button_inactive.atr); 438 waddstr(win, ">"); 439 wmove(win, y, x + temp + 1); 440 } 441 442 /* 443 * Draw a rectangular box with line drawing characters 444 */ 445 void 446 draw_box(WINDOW * win, int y, int x, int height, int width, 447 chtype box, chtype border) 448 { 449 int i, j; 450 451 wattrset(win, 0); 452 for (i = 0; i < height; i++) { 453 wmove(win, y + i, x); 454 for (j = 0; j < width; j++) 455 if (!i && !j) 456 waddch(win, border | ACS_ULCORNER); 457 else if (i == height - 1 && !j) 458 waddch(win, border | ACS_LLCORNER); 459 else if (!i && j == width - 1) 460 waddch(win, box | ACS_URCORNER); 461 else if (i == height - 1 && j == width - 1) 462 waddch(win, box | ACS_LRCORNER); 463 else if (!i) 464 waddch(win, border | ACS_HLINE); 465 else if (i == height - 1) 466 waddch(win, box | ACS_HLINE); 467 else if (!j) 468 waddch(win, border | ACS_VLINE); 469 else if (j == width - 1) 470 waddch(win, box | ACS_VLINE); 471 else 472 waddch(win, box | ' '); 473 } 474 } 475 476 /* 477 * Draw shadows along the right and bottom edge to give a more 3D look 478 * to the boxes 479 */ 480 void draw_shadow(WINDOW * win, int y, int x, int height, int width) 481 { 482 int i; 483 484 if (has_colors()) { /* Whether terminal supports color? */ 485 wattrset(win, dlg.shadow.atr); 486 wmove(win, y + height, x + 2); 487 for (i = 0; i < width; i++) 488 waddch(win, winch(win) & A_CHARTEXT); 489 for (i = y + 1; i < y + height + 1; i++) { 490 wmove(win, i, x + width); 491 waddch(win, winch(win) & A_CHARTEXT); 492 waddch(win, winch(win) & A_CHARTEXT); 493 } 494 wnoutrefresh(win); 495 } 496 } 497 498 /* 499 * Return the position of the first alphabetic character in a string. 500 */ 501 int first_alpha(const char *string, const char *exempt) 502 { 503 int i, in_paren = 0, c; 504 505 for (i = 0; i < strlen(string); i++) { 506 c = tolower(string[i]); 507 508 if (strchr("<[(", c)) 509 ++in_paren; 510 if (strchr(">])", c) && in_paren > 0) 511 --in_paren; 512 513 if ((!in_paren) && isalpha(c) && strchr(exempt, c) == 0) 514 return i; 515 } 516 517 return 0; 518 } 519 520 /* 521 * ncurses uses ESC to detect escaped char sequences. This resutl in 522 * a small timeout before ESC is actually delivered to the application. 523 * lxdialog suggest <ESC> <ESC> which is correctly translated to two 524 * times esc. But then we need to ignore the second esc to avoid stepping 525 * out one menu too much. Filter away all escaped key sequences since 526 * keypad(FALSE) turn off ncurses support for escape sequences - and that's 527 * needed to make notimeout() do as expected. 528 */ 529 int on_key_esc(WINDOW *win) 530 { 531 int key; 532 int key2; 533 int key3; 534 535 nodelay(win, TRUE); 536 keypad(win, FALSE); 537 key = wgetch(win); 538 key2 = wgetch(win); 539 do { 540 key3 = wgetch(win); 541 } while (key3 != ERR); 542 nodelay(win, FALSE); 543 keypad(win, TRUE); 544 if (key == KEY_ESC && key2 == ERR) 545 return KEY_ESC; 546 else if (key != ERR && key != KEY_ESC && key2 == ERR) 547 ungetch(key); 548 549 return -1; 550 } 551 552 /* redraw screen in new size */ 553 int on_key_resize(void) 554 { 555 dialog_clear(); 556 return KEY_RESIZE; 557 } 558 559 struct dialog_list *item_cur; 560 struct dialog_list item_nil; 561 struct dialog_list *item_head; 562 563 void item_reset(void) 564 { 565 struct dialog_list *p, *next; 566 567 for (p = item_head; p; p = next) { 568 next = p->next; 569 free(p); 570 } 571 item_head = NULL; 572 item_cur = &item_nil; 573 } 574 575 void item_make(const char *fmt, ...) 576 { 577 va_list ap; 578 struct dialog_list *p = malloc(sizeof(*p)); 579 580 if (item_head) 581 item_cur->next = p; 582 else 583 item_head = p; 584 item_cur = p; 585 memset(p, 0, sizeof(*p)); 586 587 va_start(ap, fmt); 588 vsnprintf(item_cur->node.str, sizeof(item_cur->node.str), fmt, ap); 589 va_end(ap); 590 } 591 592 void item_add_str(const char *fmt, ...) 593 { 594 va_list ap; 595 size_t avail; 596 597 avail = sizeof(item_cur->node.str) - strlen(item_cur->node.str); 598 599 va_start(ap, fmt); 600 vsnprintf(item_cur->node.str + strlen(item_cur->node.str), 601 avail, fmt, ap); 602 item_cur->node.str[sizeof(item_cur->node.str) - 1] = '\0'; 603 va_end(ap); 604 } 605 606 void item_set_tag(char tag) 607 { 608 item_cur->node.tag = tag; 609 } 610 void item_set_data(void *ptr) 611 { 612 item_cur->node.data = ptr; 613 } 614 615 void item_set_selected(int val) 616 { 617 item_cur->node.selected = val; 618 } 619 620 int item_activate_selected(void) 621 { 622 item_foreach() 623 if (item_is_selected()) 624 return 1; 625 return 0; 626 } 627 628 void *item_data(void) 629 { 630 return item_cur->node.data; 631 } 632 633 char item_tag(void) 634 { 635 return item_cur->node.tag; 636 } 637 638 int item_count(void) 639 { 640 int n = 0; 641 struct dialog_list *p; 642 643 for (p = item_head; p; p = p->next) 644 n++; 645 return n; 646 } 647 648 void item_set(int n) 649 { 650 int i = 0; 651 item_foreach() 652 if (i++ == n) 653 return; 654 } 655 656 int item_n(void) 657 { 658 int n = 0; 659 struct dialog_list *p; 660 661 for (p = item_head; p; p = p->next) { 662 if (p == item_cur) 663 return n; 664 n++; 665 } 666 return 0; 667 } 668 669 const char *item_str(void) 670 { 671 return item_cur->node.str; 672 } 673 674 int item_is_selected(void) 675 { 676 return (item_cur->node.selected != 0); 677 } 678 679 int item_is_tag(char tag) 680 { 681 return (item_cur->node.tag == tag); 682 } 683