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
render_char(WINDOW * win,NCURSES_CH_T ch)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)
_nc_render(WINDOW * win,NCURSES_CH_T ch)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
newline_forces_scroll(WINDOW * win,NCURSES_SIZE_T * ypos)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
wrap_to_next_line(WINDOW * win)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
fill_cells(WINDOW * win,int count)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)
_nc_build_wch(WINDOW * win,ARG_CH_T ch)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
waddch_literal(WINDOW * win,NCURSES_CH_T ch)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
waddch_nosync(WINDOW * win,const NCURSES_CH_T ch)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)
_nc_waddch_nosync(WINDOW * win,const NCURSES_CH_T c)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)
waddch(WINDOW * win,const chtype ch)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)
wechochar(WINDOW * win,const chtype ch)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