1 /* $OpenBSD: lib_addch.c,v 1.6 2023/10/17 09:52:08 nicm Exp $ */ 2 3 /**************************************************************************** 4 * Copyright 2019-2021,2022 Thomas E. Dickey * 5 * Copyright 1998-2016,2017 Free Software Foundation, Inc. * 6 * * 7 * Permission is hereby granted, free of charge, to any person obtaining a * 8 * copy of this software and associated documentation files (the * 9 * "Software"), to deal in the Software without restriction, including * 10 * without limitation the rights to use, copy, modify, merge, publish, * 11 * distribute, distribute with modifications, sublicense, and/or sell * 12 * copies of the Software, and to permit persons to whom the Software is * 13 * furnished to do so, subject to the following conditions: * 14 * * 15 * The above copyright notice and this permission notice shall be included * 16 * in all copies or substantial portions of the Software. * 17 * * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 19 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 21 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 22 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 23 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 24 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 25 * * 26 * Except as contained in this notice, the name(s) of the above copyright * 27 * holders shall not be used in advertising or otherwise to promote the * 28 * sale, use or other dealings in this Software without prior written * 29 * authorization. * 30 ****************************************************************************/ 31 32 /* 33 ** lib_addch.c 34 ** 35 ** The routine waddch(). 36 ** 37 */ 38 39 #include <curses.priv.h> 40 #include <ctype.h> 41 42 MODULE_ID("$Id: lib_addch.c,v 1.6 2023/10/17 09:52:08 nicm Exp $") 43 44 static const NCURSES_CH_T blankchar = NewChar(BLANK_TEXT); 45 46 /* 47 * Ugly microtweaking alert. Everything from here to end of module is 48 * likely to be speed-critical -- profiling data sure says it is! 49 * Most of the important screen-painting functions are shells around 50 * waddch(). So we make every effort to reduce function-call overhead 51 * by inlining stuff, even at the cost of making wrapped copies for 52 * export. Also we supply some internal versions that don't call the 53 * window sync hook, for use by string-put functions. 54 */ 55 56 /* Return bit mask for clearing color pair number if given ch has color */ 57 #define COLOR_MASK(ch) (~(attr_t)(((ch) & A_COLOR) ? A_COLOR : 0)) 58 59 static NCURSES_INLINE NCURSES_CH_T 60 render_char(WINDOW *win, NCURSES_CH_T ch) 61 /* compute a rendition of the given char correct for the current context */ 62 { 63 attr_t a = WINDOW_ATTRS(win); 64 int pair = GetPair(ch); 65 66 if (ISBLANK(ch) 67 && AttrOf(ch) == A_NORMAL 68 && pair == 0) { 69 /* color/pair in attrs has precedence over bkgrnd */ 70 ch = win->_nc_bkgd; 71 SetAttr(ch, a | AttrOf(win->_nc_bkgd)); 72 if ((pair = GET_WINDOW_PAIR(win)) == 0) 73 pair = GetPair(win->_nc_bkgd); 74 SetPair(ch, pair); 75 } else { 76 /* color in attrs has precedence over bkgrnd */ 77 a |= AttrOf(win->_nc_bkgd) & COLOR_MASK(a); 78 /* color in ch has precedence */ 79 if (pair == 0) { 80 if ((pair = GET_WINDOW_PAIR(win)) == 0) 81 pair = GetPair(win->_nc_bkgd); 82 } 83 AddAttr(ch, (a & COLOR_MASK(AttrOf(ch)))); 84 SetPair(ch, pair); 85 } 86 87 TR(TRACE_VIRTPUT, 88 ("render_char bkg %s (%d), attrs %s (%d) -> ch %s (%d)", 89 _tracech_t2(1, CHREF(win->_nc_bkgd)), 90 GetPair(win->_nc_bkgd), 91 _traceattr(WINDOW_ATTRS(win)), 92 GET_WINDOW_PAIR(win), 93 _tracech_t2(3, CHREF(ch)), 94 GetPair(ch))); 95 96 return (ch); 97 } 98 99 NCURSES_EXPORT(NCURSES_CH_T) 100 _nc_render(WINDOW *win, NCURSES_CH_T ch) 101 /* make render_char() visible while still allowing us to inline it below */ 102 { 103 return render_char(win, ch); 104 } 105 106 /* check if position is legal; if not, return error */ 107 #ifndef NDEBUG /* treat this like an assertion */ 108 #define CHECK_POSITION(win, x, y) \ 109 if (y > win->_maxy \ 110 || x > win->_maxx \ 111 || y < 0 \ 112 || x < 0) { \ 113 TR(TRACE_VIRTPUT, ("Alert! Win=%p _curx = %d, _cury = %d " \ 114 "(_maxx = %d, _maxy = %d)", win, x, y, \ 115 win->_maxx, win->_maxy)); \ 116 return(ERR); \ 117 } 118 #else 119 #define CHECK_POSITION(win, x, y) /* nothing */ 120 #endif 121 122 static bool 123 newline_forces_scroll(WINDOW *win, NCURSES_SIZE_T *ypos) 124 { 125 bool result = FALSE; 126 127 if (*ypos >= win->_regtop && *ypos <= win->_regbottom) { 128 if (*ypos == win->_regbottom) { 129 *ypos = win->_regbottom; 130 result = TRUE; 131 } else if (*ypos < win->_maxy) { 132 *ypos = (NCURSES_SIZE_T) (*ypos + 1); 133 } 134 } else if (*ypos < win->_maxy) { 135 *ypos = (NCURSES_SIZE_T) (*ypos + 1); 136 } 137 return result; 138 } 139 140 /* 141 * The _WRAPPED flag is useful only for telling an application that we've just 142 * wrapped the cursor. We don't do anything with this flag except set it when 143 * wrapping, and clear it whenever we move the cursor. If we try to wrap at 144 * the lower-right corner of a window, we cannot move the cursor (since that 145 * wouldn't be legal). So we return an error (which is what SVr4 does). 146 * Unlike SVr4, we can successfully add a character to the lower-right corner 147 * (Solaris 2.6 does this also, however). 148 */ 149 static int 150 wrap_to_next_line(WINDOW *win) 151 { 152 win->_flags |= _WRAPPED; 153 if (newline_forces_scroll(win, &(win->_cury))) { 154 win->_curx = win->_maxx; 155 if (!win->_scroll) 156 return (ERR); 157 scroll(win); 158 } 159 win->_curx = 0; 160 return (OK); 161 } 162 163 #if USE_WIDEC_SUPPORT 164 static int waddch_literal(WINDOW *, NCURSES_CH_T); 165 /* 166 * Fill the given number of cells with blanks using the current background 167 * rendition. This saves/restores the current x-position. 168 */ 169 static void 170 fill_cells(WINDOW *win, int count) 171 { 172 NCURSES_CH_T blank = blankchar; 173 int save_x = win->_curx; 174 int save_y = win->_cury; 175 176 while (count-- > 0) { 177 if (waddch_literal(win, blank) == ERR) 178 break; 179 } 180 win->_curx = (NCURSES_SIZE_T) save_x; 181 win->_cury = (NCURSES_SIZE_T) save_y; 182 } 183 #endif 184 185 /* 186 * Build up the bytes for a multibyte character, returning the length when 187 * complete (a positive number), -1 for error and -2 for incomplete. 188 */ 189 #if USE_WIDEC_SUPPORT 190 NCURSES_EXPORT(int) 191 _nc_build_wch(WINDOW *win, ARG_CH_T ch) 192 { 193 char *buffer = WINDOW_EXT(win, addch_work); 194 int len; 195 int x = win->_curx; 196 int y = win->_cury; 197 mbstate_t state; 198 wchar_t result; 199 200 if ((WINDOW_EXT(win, addch_used) != 0) && 201 (WINDOW_EXT(win, addch_x) != x || 202 WINDOW_EXT(win, addch_y) != y)) { 203 /* discard the incomplete multibyte character */ 204 WINDOW_EXT(win, addch_used) = 0; 205 TR(TRACE_VIRTPUT, 206 ("Alert discarded multibyte on move (%d,%d) -> (%d,%d)", 207 WINDOW_EXT(win, addch_y), WINDOW_EXT(win, addch_x), 208 y, x)); 209 } 210 WINDOW_EXT(win, addch_x) = x; 211 WINDOW_EXT(win, addch_y) = y; 212 213 /* 214 * If the background character is a wide-character, that may interfere with 215 * processing multibyte characters in this function. 216 */ 217 if (!is8bits(CharOf(CHDEREF(ch)))) { 218 if (WINDOW_EXT(win, addch_used) != 0) { 219 /* discard the incomplete multibyte character */ 220 WINDOW_EXT(win, addch_used) = 0; 221 TR(TRACE_VIRTPUT, 222 ("Alert discarded incomplete multibyte")); 223 } 224 return 1; 225 } 226 227 init_mb(state); 228 buffer[WINDOW_EXT(win, addch_used)] = (char) CharOf(CHDEREF(ch)); 229 WINDOW_EXT(win, addch_used) += 1; 230 buffer[WINDOW_EXT(win, addch_used)] = '\0'; 231 if ((len = (int) mbrtowc(&result, 232 buffer, 233 (size_t) WINDOW_EXT(win, addch_used), 234 &state)) > 0) { 235 attr_t attrs = AttrOf(CHDEREF(ch)); 236 if_EXT_COLORS(int pair = GetPair(CHDEREF(ch))); 237 SetChar(CHDEREF(ch), result, attrs); 238 if_EXT_COLORS(SetPair(CHDEREF(ch), pair)); 239 WINDOW_EXT(win, addch_used) = 0; 240 } else if (len == -1) { 241 /* 242 * An error occurred. We could either discard everything, 243 * or assume that the error was in the previous input. 244 * Try the latter. 245 */ 246 TR(TRACE_VIRTPUT, ("Alert! mbrtowc returns error")); 247 /* handle this with unctrl() */ 248 WINDOW_EXT(win, addch_used) = 0; 249 } 250 return len; 251 } 252 #endif /* USE_WIDEC_SUPPORT */ 253 254 static 255 #if !USE_WIDEC_SUPPORT /* cannot be inline if it is recursive */ 256 NCURSES_INLINE 257 #endif 258 int 259 waddch_literal(WINDOW *win, NCURSES_CH_T ch) 260 { 261 int x; 262 int y; 263 struct ldat *line; 264 265 x = win->_curx; 266 y = win->_cury; 267 268 CHECK_POSITION(win, x, y); 269 270 ch = render_char(win, ch); 271 272 line = win->_line + y; 273 274 CHANGED_CELL(line, x); 275 276 /* 277 * Build up multibyte characters until we have a wide-character. 278 */ 279 #if NCURSES_SP_FUNCS 280 #define DeriveSP() SCREEN *sp = _nc_screen_of(win); 281 #else 282 #define DeriveSP() /*nothing */ 283 #endif 284 if_WIDEC({ 285 DeriveSP(); 286 if (WINDOW_EXT(win, addch_used) != 0 || !Charable(ch)) { 287 int len = _nc_build_wch(win, CHREF(ch)); 288 289 if (len >= -1) { 290 attr_t attr = AttrOf(ch); 291 292 /* handle EILSEQ (i.e., when len >= -1) */ 293 if (len == -1 && is8bits(CharOf(ch))) { 294 const char *s = NCURSES_SP_NAME(unctrl) 295 (NCURSES_SP_ARGx (chtype) CharOf(ch)); 296 297 if (s[1] != '\0') { 298 int rc = OK; 299 while (*s != '\0') { 300 rc = waddch(win, UChar(*s) | attr); 301 if (rc != OK) 302 break; 303 ++s; 304 } 305 return rc; 306 } 307 } 308 if (len == -1) 309 return waddch(win, ' ' | attr); 310 } else { 311 return OK; 312 } 313 } 314 }); 315 316 /* 317 * Non-spacing characters are added to the current cell. 318 * 319 * Spacing characters that are wider than one column require some display 320 * adjustments. 321 */ 322 if_WIDEC({ 323 int len = _nc_wacs_width(CharOf(ch)); 324 int i; 325 int j; 326 327 if (len == 0) { /* non-spacing */ 328 if ((x > 0 && y >= 0) 329 || (win->_maxx >= 0 && win->_cury >= 1)) { 330 NCURSES_CH_T *dst; 331 wchar_t *chars; 332 if (x > 0 && y >= 0) { 333 for (j = x - 1; j >= 0; --j) { 334 if (!isWidecExt(win->_line[y].text[j])) { 335 win->_curx = (NCURSES_SIZE_T) j; 336 break; 337 } 338 } 339 dst = &(win->_line[y].text[j]); 340 } else { 341 dst = &(win->_line[y - 1].text[win->_maxx]); 342 } 343 chars = dst->chars; 344 for (i = 0; i < CCHARW_MAX; ++i) { 345 if (chars[i] == 0) { 346 TR(TRACE_VIRTPUT, 347 ("adding non-spacing %s (level %d)", 348 _tracech_t(CHREF(ch)), i)); 349 chars[i] = CharOf(ch); 350 break; 351 } 352 } 353 } 354 goto testwrapping; 355 } else if (len > 1) { /* multi-column characters */ 356 /* 357 * Check if the character will fit on the current line. If it does 358 * not fit, fill in the remainder of the line with blanks. and 359 * move to the next line. 360 */ 361 if (len > win->_maxx + 1) { 362 TR(TRACE_VIRTPUT, ("character will not fit")); 363 return ERR; 364 } else if (x + len > win->_maxx + 1) { 365 int count = win->_maxx + 1 - x; 366 TR(TRACE_VIRTPUT, ("fill %d remaining cells", count)); 367 fill_cells(win, count); 368 if (wrap_to_next_line(win) == ERR) 369 return ERR; 370 x = win->_curx; 371 y = win->_cury; 372 CHECK_POSITION(win, x, y); 373 line = win->_line + y; 374 } 375 /* 376 * Check for cells which are orphaned by adding this character, set 377 * those to blanks. 378 * 379 * FIXME: this actually could fill j-i cells, more complicated to 380 * setup though. 381 */ 382 for (i = 0; i < len; ++i) { 383 if (isWidecBase(win->_line[y].text[x + i])) { 384 break; 385 } else if (isWidecExt(win->_line[y].text[x + i])) { 386 for (j = i; x + j <= win->_maxx; ++j) { 387 if (!isWidecExt(win->_line[y].text[x + j])) { 388 TR(TRACE_VIRTPUT, ("fill %d orphan cells", j)); 389 fill_cells(win, j); 390 break; 391 } 392 } 393 break; 394 } 395 } 396 /* 397 * Finally, add the cells for this character. 398 */ 399 for (i = 0; i < len; ++i) { 400 NCURSES_CH_T value = ch; 401 SetWidecExt(value, i); 402 TR(TRACE_VIRTPUT, ("multicolumn %d:%d (%d,%d)", 403 i + 1, len, 404 win->_begy + y, win->_begx + x)); 405 line->text[x] = value; 406 CHANGED_CELL(line, x); 407 ++x; 408 } 409 goto testwrapping; 410 } 411 }); 412 413 /* 414 * Single-column characters. 415 */ 416 line->text[x++] = ch; 417 /* 418 * This label is used only for wide-characters. 419 */ 420 if_WIDEC( 421 testwrapping: 422 ); 423 424 TR(TRACE_VIRTPUT, ("cell (%d, %d..%d) = %s", 425 win->_cury, win->_curx, x - 1, 426 _tracech_t(CHREF(line->text[win->_curx])))); 427 428 if (x > win->_maxx) { 429 return wrap_to_next_line(win); 430 } 431 win->_curx = (NCURSES_SIZE_T) x; 432 return OK; 433 } 434 435 static NCURSES_INLINE int 436 waddch_nosync(WINDOW *win, const NCURSES_CH_T ch) 437 /* the workhorse function -- add a character to the given window */ 438 { 439 NCURSES_SIZE_T x, y; 440 chtype t = (chtype) CharOf(ch); 441 #if USE_WIDEC_SUPPORT || NCURSES_SP_FUNCS || USE_REENTRANT 442 SCREEN *sp = _nc_screen_of(win); 443 #endif 444 const char *s = NCURSES_SP_NAME(unctrl) (NCURSES_SP_ARGx t); 445 int tabsize = 8; 446 447 /* 448 * If we are using the alternate character set, forget about locale. 449 * Otherwise, if unctrl() returns a single-character or the locale 450 * claims the code is printable (and not also a control character), 451 * treat it that way. 452 */ 453 if ((AttrOf(ch) & A_ALTCHARSET) 454 || ( 455 #if USE_WIDEC_SUPPORT 456 (sp != 0 && sp->_legacy_coding) && 457 #endif 458 s[1] == 0 459 ) 460 || ( 461 (isprint((int) t) && !iscntrl((int) t)) 462 #if USE_WIDEC_SUPPORT 463 || ((sp == 0 || !sp->_legacy_coding) && 464 (WINDOW_EXT(win, addch_used) 465 || !_nc_is_charable(CharOf(ch)))) 466 #endif 467 )) { 468 return waddch_literal(win, ch); 469 } 470 471 /* 472 * Handle carriage control and other codes that are not printable, or are 473 * known to expand to more than one character according to unctrl(). 474 */ 475 x = win->_curx; 476 y = win->_cury; 477 CHECK_POSITION(win, x, y); 478 479 switch (t) { 480 case '\t': 481 #if USE_REENTRANT 482 tabsize = *ptrTabsize(sp); 483 #else 484 tabsize = TABSIZE; 485 #endif 486 x = (NCURSES_SIZE_T) (x + (tabsize - (x % tabsize))); 487 /* 488 * Space-fill the tab on the bottom line so that we'll get the 489 * "correct" cursor position. 490 */ 491 if ((!win->_scroll && (y == win->_regbottom)) 492 || (x <= win->_maxx)) { 493 NCURSES_CH_T blank = blankchar; 494 AddAttr(blank, AttrOf(ch)); 495 while (win->_curx < x) { 496 if (waddch_literal(win, blank) == ERR) 497 return (ERR); 498 } 499 break; 500 } else { 501 wclrtoeol(win); 502 win->_flags |= _WRAPPED; 503 if (newline_forces_scroll(win, &y)) { 504 x = win->_maxx; 505 if (win->_scroll) { 506 scroll(win); 507 x = 0; 508 } 509 } else { 510 x = 0; 511 } 512 } 513 break; 514 case '\n': 515 wclrtoeol(win); 516 if (newline_forces_scroll(win, &y)) { 517 if (win->_scroll) 518 scroll(win); 519 else 520 return (ERR); 521 } 522 /* FALLTHRU */ 523 case '\r': 524 x = 0; 525 win->_flags &= ~_WRAPPED; 526 break; 527 case '\b': 528 if (x == 0) 529 return (OK); 530 x--; 531 win->_flags &= ~_WRAPPED; 532 break; 533 default: 534 while (*s) { 535 NCURSES_CH_T sch; 536 SetChar(sch, UChar(*s++), AttrOf(ch)); 537 if_EXT_COLORS(SetPair(sch, GetPair(ch))); 538 if (waddch_literal(win, sch) == ERR) 539 return ERR; 540 } 541 return (OK); 542 } 543 544 win->_curx = x; 545 win->_cury = y; 546 547 return (OK); 548 } 549 550 NCURSES_EXPORT(int) 551 _nc_waddch_nosync(WINDOW *win, const NCURSES_CH_T c) 552 /* export copy of waddch_nosync() so the string-put functions can use it */ 553 { 554 return (waddch_nosync(win, c)); 555 } 556 557 /* 558 * The versions below call _nc_synchook(). We wanted to avoid this in the 559 * version exported for string puts; they'll call _nc_synchook once at end 560 * of run. 561 */ 562 563 /* These are actual entry points */ 564 565 NCURSES_EXPORT(int) 566 waddch(WINDOW *win, const chtype ch) 567 { 568 int code = ERR; 569 NCURSES_CH_T wch; 570 SetChar2(wch, ch); 571 572 TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_CALLED("waddch(%p, %s)"), (void *) win, 573 _tracechtype(ch))); 574 575 if (win && (waddch_nosync(win, wch) != ERR)) { 576 _nc_synchook(win); 577 code = OK; 578 } 579 580 TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_RETURN("%d"), code)); 581 return (code); 582 } 583 584 NCURSES_EXPORT(int) 585 wechochar(WINDOW *win, const chtype ch) 586 { 587 int code = ERR; 588 NCURSES_CH_T wch; 589 SetChar2(wch, ch); 590 591 TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_CALLED("wechochar(%p, %s)"), 592 (void *) win, 593 _tracechtype(ch))); 594 595 if (win && (waddch_nosync(win, wch) != ERR)) { 596 bool save_immed = win->_immed; 597 win->_immed = TRUE; 598 _nc_synchook(win); 599 win->_immed = save_immed; 600 code = OK; 601 } 602 TR(TRACE_VIRTPUT | TRACE_CCALLS, (T_RETURN("%d"), code)); 603 return (code); 604 } 605