• Home
  • History
  • Annotate
  • only in this directory
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
42MODULE_ID("$Id: lib_addch.c,v 1.6 2023/10/17 09:52:08 nicm Exp $")
43
44static 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
59static NCURSES_INLINE NCURSES_CH_T
60render_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
99NCURSES_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
122static bool
123newline_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 */
149static int
150wrap_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
164static 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 */
169static void
170fill_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
190NCURSES_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
254static
255#if !USE_WIDEC_SUPPORT		/* cannot be inline if it is recursive */
256NCURSES_INLINE
257#endif
258int
259waddch_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
435static NCURSES_INLINE int
436waddch_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
550NCURSES_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
565NCURSES_EXPORT(int)
566waddch(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
584NCURSES_EXPORT(int)
585wechochar(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