xref: /openbsd/lib/libcurses/base/lib_addch.c (revision c7ef0cfc)
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