1 /* $NetBSD: slk.c,v 1.8 2019/07/28 00:51:59 uwe Exp $ */ 2 3 /*- 4 * Copyright (c) 2017 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Roy Marples. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 #include <sys/cdefs.h> 33 #ifndef lint 34 __RCSID("$NetBSD: slk.c,v 1.8 2019/07/28 00:51:59 uwe Exp $"); 35 #endif /* not lint */ 36 37 #include <ctype.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #ifdef HAVE_WCHAR 41 #include <wctype.h> 42 #endif 43 44 #include "curses.h" 45 #include "curses_private.h" 46 47 /* Terminals with real soft labels have NOT been tested. 48 * If you have such a device, please let us know so this comment 49 * can be adjusted. */ 50 51 /* POSIX says that each label can be up to 8 columns. 52 * However, our implementation can allow labels to expand beyond that. */ 53 //#define SLK_SIZE_DYNAMIC 54 #ifdef SLK_SIZE_DYNAMIC 55 #define SLK_SIZE MAX_SLK_LABEL 56 #else 57 #define SLK_SIZE MAX_SLK_COLS 58 #endif 59 60 static int slk_fmt = SLK_FMT_INVAL; /* fmt of slk_init */ 61 62 /* Safe variants of public functions. */ 63 static int __slk_attroff(SCREEN *, const chtype); 64 static int __slk_attron(SCREEN *, const chtype); 65 static int __slk_attrset(SCREEN *, const chtype); 66 #ifdef HAVE_WCHAR 67 static int __slk_attr_off(SCREEN *, const attr_t, void *); 68 static int __slk_attr_on(SCREEN *, const attr_t, void *); 69 static int __slk_attr_set(SCREEN *, const attr_t, short, void *opt); 70 static int __slk_color(SCREEN *, short); 71 #endif 72 73 static int __slk_clear(SCREEN *); 74 static char *__slk_label(SCREEN *, int); 75 static int __slk_restore(SCREEN *); 76 static int __slk_set(SCREEN *, int, const char *, int); 77 static int __slk_touch(SCREEN *); 78 #ifdef HAVE_WCHAR 79 static int __slk_wset(SCREEN *, int, const wchar_t *, int); 80 #endif 81 82 /* Internal engine parts. */ 83 static int __slk_ripoffline(WINDOW *, int); 84 static int __slk_set_finalise(SCREEN *, int); 85 static int __slk_draw(SCREEN *, int); 86 static int __slk_redraw(SCREEN *); 87 88 /* 89 * slk_init -- 90 * Init Soft Label Keys. 91 */ 92 int 93 slk_init(int fmt) 94 { 95 96 switch(fmt) { 97 case SLK_FMT_3_2_3: 98 case SLK_FMT_4_4: 99 break; 100 default: 101 return ERR; 102 } 103 104 slk_fmt = fmt; 105 /* Even if the terminal supports soft label keys directly, 106 * we need to reserve a line. */ 107 return ripoffline(-1, __slk_ripoffline); 108 } 109 110 /* 111 * slk_attron -- 112 * Test and set attributes on ripped off slk window. 113 */ 114 int 115 slk_attron(const chtype attr) 116 { 117 118 return __slk_attron(_cursesi_screen, attr); 119 } 120 121 #ifdef HAVE_WCHAR 122 /* 123 * slk_attr_on -- 124 * Test and set wide attributes on ripped off slk window. 125 */ 126 int 127 slk_attr_on(const attr_t attr, void *opt) 128 { 129 130 return __slk_attr_on(_cursesi_screen, attr, opt); 131 } 132 #endif /* HAVE_WCHAR */ 133 134 /* 135 * slk_attroff -- 136 * Test and unset attributes on ripped off slk window. 137 */ 138 int 139 slk_attroff(const chtype attr) 140 { 141 142 return __slk_attroff(_cursesi_screen, attr); 143 } 144 145 #ifdef HAVE_WCHAR 146 /* 147 * slk_attr_off -- 148 * Test and unset wide attributes on ripped off slk window. 149 */ 150 int 151 slk_attr_off(const attr_t attr, void *opt) 152 { 153 154 return __slk_attr_off(_cursesi_screen, attr, opt); 155 } 156 #endif /* HAVE_WCHAR */ 157 158 /* 159 * slk_attrset -- 160 * Set attributes and color pair on ripped off slk window. 161 */ 162 int 163 slk_attrset(const chtype attr) 164 { 165 166 return __slk_attrset(_cursesi_screen, attr); 167 } 168 169 #ifdef HAVE_WCHAR 170 /* 171 * slk_attr_set -- 172 * Set wide attributes and color pair on ripped off slk window. 173 */ 174 int 175 slk_attr_set(const attr_t attr, short pair, void *opt) 176 { 177 178 return __slk_attr_set(_cursesi_screen, attr, pair, opt); 179 } 180 #endif /* HAVE_WCHAR */ 181 182 /* 183 * slk_clear -- 184 * Clear slk from the current screen. 185 */ 186 int 187 slk_clear(void) 188 { 189 190 return __slk_clear(_cursesi_screen); 191 } 192 193 #ifdef HAVE_WCHAR 194 /* 195 * slk_color -- 196 * Set color pair on ripped off slk window. 197 */ 198 int 199 slk_color(short pair) 200 { 201 202 return __slk_color(_cursesi_screen, pair); 203 } 204 #endif /* HAVE_WCHAR */ 205 206 /* 207 * slk_label -- 208 * Return a pointer to the saved label for key labnum. 209 */ 210 char * 211 slk_label(int labnum) 212 { 213 214 return __slk_label(_cursesi_screen, labnum); 215 } 216 217 /* 218 * slk_wnoutrefresh -- 219 * Add the contents of the ripped off slk window to the virtual window. 220 */ 221 int 222 slk_noutrefresh(void) 223 { 224 225 return __slk_noutrefresh(_cursesi_screen); 226 } 227 228 /* 229 * slk_refresh -- 230 * Force a refresh for the ripped off slk window. 231 */ 232 int 233 slk_refresh(void) 234 { 235 236 if (slk_noutrefresh() == ERR) 237 return ERR; 238 return doupdate(); 239 } 240 241 /* 242 * slk_restore -- 243 * Retore slk to the screen after a slk_clear. 244 */ 245 int 246 slk_restore(void) 247 { 248 249 return __slk_restore(_cursesi_screen); 250 } 251 252 /* 253 * slk_set -- 254 * Sets the text of the label specified by labnum 255 * and how it is displayed. 256 */ 257 int 258 slk_set(int labnum, const char *label, int justify) 259 { 260 261 return __slk_set(_cursesi_screen, labnum, label, justify); 262 } 263 264 /* 265 * slk_touch -- 266 * Sets the ripped off slk window as modified. 267 */ 268 int 269 slk_touch(void) 270 { 271 272 return __slk_touch(_cursesi_screen); 273 } 274 275 #ifdef HAVE_WCHAR 276 /* 277 * slk_wset -- 278 * Sets the wide text of the label specified by labnum 279 * and how it is displayed. 280 */ 281 int 282 slk_wset(int labnum, const wchar_t *label, int justify) 283 { 284 285 return __slk_wset(_cursesi_screen, labnum, label, justify); 286 } 287 #endif /* HAVE_WCHAR */ 288 289 /* 290 * __slk_attron -- 291 * Test and set attributes on ripped off slk window. 292 */ 293 static int 294 __slk_attron(SCREEN *screen, const chtype attr) 295 { 296 297 if (screen == NULL || screen->slk_window == NULL) 298 return ERR; 299 return wattron(screen->slk_window, attr); 300 } 301 302 #ifdef HAVE_WCHAR 303 /* 304 * __slk_attr_on -- 305 * Test and set wide attributes on ripped off slk window. 306 */ 307 static int 308 __slk_attr_on(SCREEN *screen, const attr_t attr, void *opt) 309 { 310 311 if (screen == NULL || screen->slk_window == NULL) 312 return ERR; 313 return wattr_on(screen->slk_window, attr, opt); 314 } 315 #endif /* HAVE_WCHAR */ 316 317 /* 318 * __slk_attroff -- 319 * Test and unset attributes on ripped off slk window. 320 */ 321 static int 322 __slk_attroff(SCREEN *screen, const chtype attr) 323 { 324 325 if (screen == NULL || screen->slk_window == NULL) 326 return ERR; 327 return wattroff(screen->slk_window, attr); 328 } 329 330 #ifdef HAVE_WCHAR 331 /* 332 * __slk_attr_off -- 333 * Test and unset wide attributes on ripped off slk window. 334 */ 335 static int 336 __slk_attr_off(SCREEN *screen, const attr_t attr, void *opt) 337 { 338 339 if (screen == NULL || screen->slk_window == NULL) 340 return ERR; 341 return wattr_off(screen->slk_window, attr, opt); 342 } 343 #endif /* HAVE_WCHAR */ 344 345 /* 346 * __slk_attrset -- 347 * Set attributes and color pair on ripped off slk window. 348 */ 349 static int 350 __slk_attrset(SCREEN *screen, const chtype attr) 351 { 352 353 if (screen == NULL || screen->slk_window == NULL) 354 return ERR; 355 return wattrset(screen->slk_window, attr); 356 } 357 358 #ifdef HAVE_WCHAR 359 /* 360 * __slk_attr_set -- 361 * Set wide attributes and color pair on ripped off slk window. 362 */ 363 static int 364 __slk_attr_set(SCREEN *screen, const attr_t attr, short pair, void *opt) 365 { 366 367 if (screen == NULL || screen->slk_window == NULL) 368 return ERR; 369 return wattr_set(screen->slk_window, attr, pair, opt); 370 } 371 #endif /* HAVE_WCHAR */ 372 373 /* 374 * __slk_clear -- 375 * Clear slk from the current screen. 376 */ 377 static int 378 __slk_clear(SCREEN *screen) 379 { 380 381 if (screen == NULL) 382 return ERR; 383 screen->slk_hidden = true; 384 if (screen->is_term_slk) { 385 if (t_label_off(screen->term) == NULL) 386 return ERR; 387 return ti_putp(screen->term, 388 ti_tiparm(screen->term, t_label_off(screen->term))); 389 } 390 if (screen->slk_window == NULL) 391 return ERR; 392 werase(screen->slk_window); 393 return wrefresh(screen->slk_window); 394 } 395 396 #ifdef HAVE_WCHAR 397 /* 398 * __slk_color -- 399 * Set color pair on ripped off slk window. 400 */ 401 static int 402 __slk_color(SCREEN *screen, short pair) 403 { 404 405 if (screen == NULL || screen->slk_window == NULL) 406 return ERR; 407 return wcolor_set(screen->slk_window, pair, NULL); 408 } 409 #endif /* HAVE_WCHAR */ 410 411 /* 412 * __slk_label -- 413 * Return a pointer to the saved label for key labnum. 414 */ 415 static char * 416 __slk_label(SCREEN *screen, int labnum) 417 { 418 419 if (screen == NULL || labnum < 1 || labnum > screen->slk_nlabels) 420 return NULL; 421 return screen->slk_labels[--labnum].text; 422 } 423 424 /* 425 * __slk_wnoutrefresh -- 426 * Add the contents of the ripped off slk window to the virtual window. 427 */ 428 int 429 __slk_noutrefresh(SCREEN *screen) 430 { 431 432 if (screen == NULL || screen->slk_window == NULL) 433 return ERR; 434 return wnoutrefresh(screen->slk_window); 435 } 436 437 /* 438 * __slk_restore -- 439 * Retore slk to the screen after a slk_clear. 440 */ 441 static int 442 __slk_restore(SCREEN *screen) 443 { 444 445 if (screen == NULL) 446 return ERR; 447 screen->slk_hidden = false; 448 if (screen->is_term_slk) { 449 if (t_label_on(screen->term) == NULL) 450 return ERR; 451 return ti_putp(screen->term, 452 ti_tiparm(screen->term, t_label_on(screen->term))); 453 } 454 if (screen->slk_window == NULL) 455 return ERR; 456 if (__slk_redraw(screen) == ERR) 457 return ERR; 458 return wrefresh(screen->slk_window); 459 } 460 461 /* 462 * __slk_set -- 463 * Sets the text of the label specified by labnum 464 * and how it is displayed. 465 */ 466 static int 467 __slk_set(SCREEN *screen, int labnum, const char *label, int justify) 468 { 469 struct __slk_label *l; 470 const char *end; 471 size_t len; 472 char *text; 473 #ifdef HAVE_WCHAR 474 wchar_t wc; 475 size_t wc_len; 476 #endif 477 478 /* Check args. */ 479 if (screen == NULL || labnum < 1 || labnum > screen->slk_nlabels) 480 return ERR; 481 switch(justify) { 482 case SLK_JUSTIFY_LEFT: 483 case SLK_JUSTIFY_CENTER: 484 case SLK_JUSTIFY_RIGHT: 485 break; 486 default: 487 return ERR; 488 } 489 if (label == NULL) 490 label = ""; 491 492 /* Skip leading whitespace. */ 493 while(isspace((unsigned char)*label)) 494 label++; 495 /* Grab end. */ 496 end = label; 497 498 #ifdef HAVE_WCHAR 499 size_t endlen = strlen(end); 500 while (*end != '\0') { 501 wc_len = mbrtowc(&wc, end, endlen, &screen->sp); 502 if ((ssize_t)wc_len < 0) 503 return ERR; 504 if (!iswprint((wint_t)wc)) 505 break; 506 end += wc_len; 507 endlen -= wc_len; 508 } 509 #else 510 while(isprint((unsigned char)*end)) 511 end++; 512 #endif 513 len = end - label; 514 515 /* Take a backup, in-case we can grow the label. */ 516 if ((text = strndup(label, len)) == NULL) 517 return ERR; 518 519 /* All checks out, assign. */ 520 l = &screen->slk_labels[--labnum]; /* internal zero based index */ 521 l->text = text; 522 l->justify = justify; 523 524 __slk_set_finalise(screen, labnum); 525 return OK; 526 } 527 528 /* 529 * __slk_touch -- 530 * Sets the ripped off slk window as modified. 531 */ 532 static int 533 __slk_touch(SCREEN *screen) 534 { 535 536 if (screen == NULL || screen->slk_window == NULL) 537 return ERR; 538 return touchwin(screen->slk_window); 539 } 540 541 542 #ifdef HAVE_WCHAR 543 /* 544 * __slk_wset -- 545 * Sets the wide text of the label specified by labnum 546 * and how it is displayed. 547 */ 548 static int 549 __slk_wset(SCREEN *screen, int labnum, const wchar_t *label, int justify) 550 { 551 const wchar_t *olabel; 552 size_t len; 553 char *str; 554 int result = ERR; 555 556 if (screen == NULL) 557 return ERR; 558 olabel = label; 559 if ((len = wcsrtombs(NULL, &olabel, 0, &screen->sp)) == -1) 560 return ERR; 561 len++; /* We need to store the NULL character. */ 562 if ((str = malloc(len)) == NULL) 563 return ERR; 564 olabel = label; 565 if (wcsrtombs(str, &olabel, len, &screen->sp) == -1) 566 goto out; 567 result = __slk_set(screen, labnum, str, justify); 568 out: 569 free(str); 570 return result; 571 } 572 #endif /* HAVE_WCHAR */ 573 574 575 /* 576 * __slk_init -- 577 * Allocate structures. 578 */ 579 int 580 __slk_init(SCREEN *screen) 581 { 582 583 __slk_free(screen); /* safety */ 584 585 screen->slk_format = slk_fmt; 586 if (slk_fmt == SLK_FMT_INVAL) 587 return OK; 588 slk_fmt = SLK_FMT_INVAL; 589 590 switch(screen->slk_format) { 591 case SLK_FMT_3_2_3: 592 case SLK_FMT_4_4: 593 screen->slk_nlabels = 8; 594 break; 595 default: /* impossible */ 596 return ERR; 597 } 598 599 screen->slk_labels = calloc(screen->slk_nlabels, 600 sizeof(*screen->slk_labels)); 601 if (screen->slk_labels == NULL) 602 return ERR; 603 604 screen->is_term_slk = 605 t_plab_norm(screen->term) != NULL && 606 t_num_labels(screen->term) > 0; 607 if (screen->is_term_slk) { 608 __unripoffline(__slk_ripoffline); 609 screen->slk_nlabels = t_num_labels(screen->term); 610 screen->slk_label_len = t_label_width(screen->term); 611 /* XXX label_height, label_format? */ 612 } 613 614 return OK; 615 } 616 617 /* 618 * __slk_free -- 619 * Free allocates resources. 620 */ 621 void 622 __slk_free(SCREEN *screen) 623 { 624 int i; 625 626 if (screen->slk_window != NULL) 627 delwin(screen->slk_window); 628 for (i = 0; i < screen->slk_nlabels; i++) 629 free(screen->slk_labels[i].text); 630 free(screen->slk_labels); 631 } 632 633 /* 634 * __slk_ripoffline -- 635 * ripoffline callback to accept a WINDOW to create our keys. 636 */ 637 static int 638 __slk_ripoffline(WINDOW *window, int cols) 639 { 640 641 if (window == NULL) 642 return ERR; 643 window->screen->slk_window = window; 644 wattron(window, 645 (t_no_color_video(window->screen->term) & 1) == 0 646 ? A_STANDOUT : A_REVERSE); 647 __slk_resize(window->screen, cols); 648 return OK; 649 } 650 651 /* 652 * __slk_resize -- 653 * Size and position the labels in the ripped off slk window. 654 */ 655 int 656 __slk_resize(SCREEN *screen, int cols) 657 { 658 int x = 0; 659 struct __slk_label *l; 660 661 if (screen == NULL) 662 return ERR; 663 if (screen->is_term_slk || screen->slk_nlabels == 0) 664 return OK; 665 666 screen->slk_label_len = (cols / screen->slk_nlabels) - 1; 667 if (screen->slk_label_len > SLK_SIZE) 668 screen->slk_label_len = SLK_SIZE; 669 670 l = screen->slk_labels; 671 672 switch(screen->slk_format) { 673 case SLK_FMT_3_2_3: 674 /* Left 3 */ 675 (l++)->x = x; 676 (l++)->x = (x += screen->slk_label_len + 1); 677 (l++)->x = (x += screen->slk_label_len + 1); 678 679 /* Middle 2 */ 680 x = cols / 2; 681 (l++)->x = x -(screen->slk_label_len + 1); 682 (l++)->x = x + 1; 683 684 /* Right 3 */ 685 x = (cols - ((screen->slk_label_len + 1) * 3)) + 1; 686 (l++)->x = x; 687 (l++)->x = (x += screen->slk_label_len + 1); 688 (l++)->x = (x += screen->slk_label_len + 1); 689 break; 690 691 case SLK_FMT_4_4: 692 { 693 int i, half; 694 695 half = screen->slk_nlabels / 2; 696 for (i = 0; i < screen->slk_nlabels; i++) { 697 (l++)->x = x; 698 x += screen->slk_label_len; 699 /* Split labels in half */ 700 if (i == half - 1) 701 x = cols - (screen->slk_label_len * half) + 1; 702 } 703 break; 704 } 705 } 706 707 /* Write text to the labels. */ 708 for (x = 0; x < screen->slk_nlabels; x++) 709 __slk_set_finalise(screen, x); 710 711 return __slk_redraw(screen); 712 } 713 714 /* 715 * __slk_set_finalise -- 716 * Does the grunt work of positioning and sizing the text in the label. 717 */ 718 static int 719 __slk_set_finalise(SCREEN *screen, int labnum) 720 { 721 struct __slk_label *l; 722 size_t spc, len, width, x; 723 char *p; 724 725 l = &screen->slk_labels[labnum]; 726 spc = screen->slk_label_len; 727 728 #ifdef HAVE_WCHAR 729 len = 0; 730 width = 0; 731 if (l->text != NULL) { 732 size_t plen; 733 734 p = l->text; 735 plen = strlen(l->text); 736 while (*p != '\0') { 737 size_t mblen; 738 wchar_t wc; 739 int w; 740 741 mblen = mbrtowc(&wc, p, plen, &screen->sp); 742 if ((ssize_t)mblen < 0) 743 return ERR; 744 w = wcwidth(wc); 745 if (width + w > spc) 746 break; 747 width += w; 748 len += mblen; 749 p += mblen; 750 plen -= mblen; 751 } 752 } 753 #else 754 len = l->text == NULL ? 0 : strlen(l->text); 755 if (len > spc) 756 len = spc; 757 width = len; 758 #endif 759 760 switch(l->justify) { 761 case SLK_JUSTIFY_LEFT: 762 x = 0; 763 break; 764 case SLK_JUSTIFY_CENTER: 765 x = (spc - width) / 2; 766 if (x + width > spc) 767 x--; 768 break; 769 case SLK_JUSTIFY_RIGHT: 770 x = spc - width; 771 break; 772 default: 773 return ERR; /* impossible */ 774 } 775 776 p = l->label; 777 if (x != 0) { 778 memset(p, ' ', x); 779 p += x; 780 spc -= x; 781 } 782 if (len != 0) { 783 memcpy(p, l->text, len); 784 p += len; 785 spc -= width; 786 } 787 if (spc != 0) { 788 memset(p, ' ', spc); 789 p += spc; 790 } 791 *p = '\0'; /* Terminate for plab_norm. */ 792 793 return __slk_draw(screen, labnum); 794 } 795 796 /* 797 * __slk_draw -- 798 * Draws the specified key. 799 */ 800 static int 801 __slk_draw(SCREEN *screen, int labnum) 802 { 803 const struct __slk_label *l; 804 805 if (screen->slk_hidden) 806 return OK; 807 808 l = &screen->slk_labels[labnum]; 809 if (screen->is_term_slk) 810 return ti_putp(screen->term, 811 ti_tiparm(screen->term, 812 t_plab_norm(screen->term), labnum + 1, l->label)); 813 else if (screen->slk_window != NULL) 814 return mvwaddnstr(screen->slk_window, 0, l->x, 815 l->label, screen->slk_label_len); 816 else 817 return ERR; 818 } 819 820 /* 821 * __slk_draw -- 822 * Draws all the keys. 823 */ 824 static int 825 __slk_redraw(SCREEN *screen) 826 { 827 int i, result = OK; 828 829 for (i = 0; i < screen->slk_nlabels; i++) { 830 if (__slk_draw(screen, i) == ERR) 831 result = ERR; 832 } 833 return result; 834 } 835