167468Snon/****************************************************************************
267468Snon * Copyright 2019,2020 Thomas E. Dickey                                     *
367468Snon * Copyright 1998-2017,2018 Free Software Foundation, Inc.                  *
467468Snon *                                                                          *
567468Snon * Permission is hereby granted, free of charge, to any person obtaining a  *
667468Snon * copy of this software and associated documentation files (the            *
767468Snon * "Software"), to deal in the Software without restriction, including      *
867468Snon * without limitation the rights to use, copy, modify, merge, publish,      *
967468Snon * distribute, distribute with modifications, sublicense, and/or sell       *
1067468Snon * copies of the Software, and to permit persons to whom the Software is    *
1167468Snon * furnished to do so, subject to the following conditions:                 *
1267468Snon *                                                                          *
1367468Snon * The above copyright notice and this permission notice shall be included  *
1467468Snon * in all copies or substantial portions of the Software.                   *
1567468Snon *                                                                          *
1667468Snon * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
1767468Snon * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
1867468Snon * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
1967468Snon * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
2067468Snon * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
2167468Snon * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
2267468Snon * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
2367468Snon *                                                                          *
2467468Snon * Except as contained in this notice, the name(s) of the above copyright   *
2567468Snon * holders shall not be used in advertising or otherwise to promote the     *
2667468Snon * sale, use or other dealings in this Software without prior written       *
2767468Snon * authorization.                                                           *
2867468Snon ****************************************************************************/
2967468Snon
3067468Snon/****************************************************************************
3167468Snon *  Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995               *
3267468Snon *     and: Eric S. Raymond <esr@snark.thyrsus.com>                         *
3367468Snon *     and: Thomas E. Dickey                        1996 on                 *
3467468Snon *     and: Juergen Pfeifer                         2009                    *
3567468Snon ****************************************************************************/
3667468Snon
3767468Snon#include <curses.priv.h>
3867468Snon
3967468Snon#include <ctype.h>
4067468Snon
4167468Snon#ifndef CUR
4267468Snon#define CUR SP_TERMTYPE
4367468Snon#endif
4467468Snon
4567468SnonMODULE_ID("$Id: lib_screen.c,v 1.100 2020/05/25 22:48:41 tom Exp $")
4667468Snon
4767468Snon#define MAX_SIZE 0x3fff		/* 16k is big enough for a window or pad */
4867468Snon
4967468Snon#define MARKER '\\'
5067468Snon#define APPEND '+'
5167468Snon#define GUTTER '|'
5267468Snon#define L_CURL '{'
5367468Snon#define R_CURL '}'
5467468Snon
5567468Snon#if USE_STRING_HACKS && HAVE_SNPRINTF
5667468Snon#define ARG_SLIMIT(name) size_t name,
5767468Snon#else
5867468Snon#define ARG_SLIMIT(name)	/* nothing */
5967468Snon#endif
6067468Snon
6167468Snon#define CUR_SLIMIT _nc_SLIMIT(limit - (size_t) (target - base))
6267468Snon#define TOP_SLIMIT _nc_SLIMIT(sizeof(buffer))
6367468Snon
6467468Snon/*
6567468Snon * Use 0x888888 as the magic number for new-format files, since it cannot be
6667848Snon * mistaken for the _cury/_curx pair of 16-bit numbers which start the old
6767468Snon * format.  It happens to be unused in the file 5.22 database (2015/03/07).
6867468Snon */
6967468Snonstatic const char my_magic[] =
7067468Snon{'\210', '\210', '\210', '\210', 0};
7170834Swollman
7267468Snon#if NCURSES_EXT_PUTWIN
7370834Swollmantypedef enum {
7467468Snon    pINT			/* int */
7567468Snon    ,pSHORT			/* short */
7667468Snon    ,pBOOL			/* bool */
7767468Snon    ,pATTR			/* attr_t */
7867468Snon    ,pCHAR			/* chtype */
7967468Snon    ,pSIZE			/* NCURSES_SIZE_T */
8092739Salfred#if NCURSES_WIDECHAR
8167468Snon    ,pCCHAR			/* cchar_t */
8267468Snon#endif
8367468Snon} PARAM_TYPE;
8467468Snon
8567468Snontypedef struct {
8667468Snon    const char name[11];
8767468Snon    attr_t attr;
8867468Snon} SCR_ATTRS;
8967468Snon
9067468Snontypedef struct {
9167468Snon    const char name[17];
9267468Snon    PARAM_TYPE type;
9367468Snon    size_t size;
9467468Snon    size_t offset;
9567468Snon} SCR_PARAMS;
9667468Snon
9767468Snon#define DATA(name) { { #name }, A_##name }
9867468Snonstatic const SCR_ATTRS scr_attrs[] =
9967468Snon{
10067468Snon    DATA(NORMAL),
10167468Snon    DATA(STANDOUT),
10267468Snon    DATA(UNDERLINE),
10367468Snon    DATA(REVERSE),
10467468Snon    DATA(BLINK),
10567468Snon    DATA(DIM),
10673087Snon    DATA(BOLD),
10773087Snon    DATA(ALTCHARSET),
10873087Snon    DATA(INVIS),
10973087Snon    DATA(PROTECT),
11073087Snon    DATA(HORIZONTAL),
11167468Snon    DATA(LEFT),
11267468Snon    DATA(LOW),
11367468Snon    DATA(RIGHT),
11467468Snon    DATA(TOP),
11567468Snon    DATA(VERTICAL),
11667468Snon
11767468Snon#ifdef A_ITALIC
11867468Snon    DATA(ITALIC),
11967468Snon#endif
12067468Snon};
12167468Snon#undef DATA
12267468Snon
12367468Snon#define sizeof2(type,name) sizeof(((type *)0)->name)
12467468Snon#define DATA(name, type) { { #name }, type, sizeof2(WINDOW, name), offsetof(WINDOW, name) }
12567468Snon
12667468Snonstatic const SCR_PARAMS scr_params[] =
12773087Snon{
12867468Snon    DATA(_cury, pSIZE),
12967468Snon    DATA(_curx, pSIZE),
13067468Snon    DATA(_maxy, pSIZE),
13167468Snon    DATA(_maxx, pSIZE),
13267468Snon    DATA(_begy, pSIZE),
13367468Snon    DATA(_begx, pSIZE),
13473087Snon    DATA(_flags, pSHORT),
13573087Snon    DATA(_attrs, pATTR),
13673087Snon    DATA(_bkgd, pCHAR),
13773087Snon    DATA(_notimeout, pBOOL),
13873087Snon    DATA(_clear, pBOOL),
13967468Snon    DATA(_leaveok, pBOOL),
14067468Snon    DATA(_scroll, pBOOL),
14173087Snon    DATA(_idlok, pBOOL),
14273087Snon    DATA(_idcok, pBOOL),
14367468Snon    DATA(_immed, pBOOL),
14467468Snon    DATA(_sync, pBOOL),
14567468Snon    DATA(_use_keypad, pBOOL),
14667468Snon    DATA(_delay, pINT),
14767468Snon    DATA(_regtop, pSIZE),
14873087Snon    DATA(_regbottom, pSIZE),
14973087Snon    DATA(_pad._pad_y, pSIZE),
15073087Snon    DATA(_pad._pad_x, pSIZE),
15173087Snon    DATA(_pad._pad_top, pSIZE),
15273087Snon    DATA(_pad._pad_left, pSIZE),
15373087Snon    DATA(_pad._pad_bottom, pSIZE),
15473087Snon    DATA(_pad._pad_right, pSIZE),
15573087Snon    DATA(_yoffset, pSIZE),
15673087Snon#if NCURSES_WIDECHAR
15773087Snon    DATA(_bkgrnd, pCCHAR),
15873087Snon#if NCURSES_EXT_COLORS
15973087Snon    DATA(_color, pINT),
16073087Snon#endif
16173087Snon#endif
16267468Snon};
16367468Snon#undef DATA
16467468Snon
16567468Snonstatic const NCURSES_CH_T blank = NewChar(BLANK_TEXT);
16667468Snon
16767468Snon/*
16867468Snon * Allocate and read a line of text.  Caller must free it.
16967468Snon */
17067468Snonstatic char *
17167468Snonread_txt(FILE *fp)
17267468Snon{
17367468Snon    size_t limit = 1024;
17467468Snon    char *result = malloc(limit);
17567468Snon    char *buffer;
17667468Snon
17767468Snon    if (result != 0) {
17867468Snon	int ch = 0;
17967468Snon	size_t used = 0;
18067468Snon
18167468Snon	clearerr(fp);
18282922Snon	result[used] = '\0';
18367468Snon	do {
18467468Snon	    if (used + 2 >= limit) {
18567468Snon		limit += 1024;
18667468Snon		buffer = realloc(result, limit);
18767468Snon		if (buffer == 0) {
18867468Snon		    free(result);
18967468Snon		    result = 0;
19067468Snon		    break;
19167468Snon		}
19267468Snon		result = buffer;
19367468Snon	    }
19467468Snon	    ch = fgetc(fp);
19567468Snon	    if (ch == EOF)
19667468Snon		break;
19767468Snon	    result[used++] = (char) ch;
19867468Snon	    result[used] = '\0';
19967468Snon	} while (ch != '\n');
20067468Snon
20167468Snon	if (ch == '\n') {
20267468Snon	    result[--used] = '\0';
20367468Snon	    T(("READ:%s", result));
20467468Snon	} else if (used == 0) {
20567468Snon	    free(result);
20667468Snon	    result = 0;
20767468Snon	}
20867468Snon    }
20967468Snon    return result;
21067468Snon}
21167468Snon
21267468Snonstatic char *
21367468Snondecode_attr(char *source, attr_t *target, int *color)
21467468Snon{
21567468Snon    bool found = FALSE;
21667468Snon
21767468Snon    T(("decode_attr   '%s'", source));
21867468Snon
21967468Snon    while (*source) {
22067468Snon	if (source[0] == MARKER && source[1] == L_CURL) {
22167468Snon	    source += 2;
22267468Snon	    found = TRUE;
22367468Snon	} else if (source[0] == R_CURL) {
22467468Snon	    source++;
22573280Smarkm	    found = FALSE;
22667468Snon	} else if (found) {
22767468Snon	    size_t n;
22867468Snon	    char *next = source;
22967468Snon
23067468Snon	    if (source[0] == GUTTER) {
23167468Snon		++next;
23267468Snon	    } else if (*next == 'C') {
23367468Snon		int value = 0;
23467468Snon		unsigned pair;
23567468Snon		next++;
23667468Snon		while (isdigit(UChar(*next))) {
23767468Snon		    value = value * 10 + (*next++ - '0');
23867468Snon		}
23967468Snon		*target &= ~A_COLOR;
24067468Snon		pair = (unsigned) ((value > 256)
24167468Snon				   ? COLOR_PAIR(255)
24267468Snon				   : COLOR_PAIR(value));
24367468Snon		*target |= pair;
24467468Snon		*color = value;
24567468Snon	    } else {
24667468Snon		while (isalnum(UChar(*next))) {
24767468Snon		    ++next;
24867468Snon		}
24967468Snon		for (n = 0; n < SIZEOF(scr_attrs); ++n) {
25067468Snon		    if ((size_t) (next - source) == strlen(scr_attrs[n].name)) {
25167468Snon			if (scr_attrs[n].attr) {
25267468Snon			    *target |= scr_attrs[n].attr;
25367468Snon			} else {
25467468Snon			    *target = A_NORMAL;
25567468Snon			}
25667468Snon			break;
25767468Snon		    }
25867468Snon		}
25967468Snon	    }
26067468Snon	    source = next;
26167468Snon	} else {
26267468Snon	    break;
26367468Snon	}
26487983Snon    }
26567468Snon    return source;
26667468Snon}
26767468Snon
26867468Snonstatic char *
26967468Snondecode_char(char *source, int *target)
27067468Snon{
27179697Snon    int limit = 0;
27267468Snon    int base = 16;
27367468Snon    const char digits[] = "0123456789abcdef";
27479697Snon
27567468Snon    T(("decode_char   '%s'", source));
27667468Snon    *target = ' ';
27779697Snon    switch (*source) {
27867468Snon    case MARKER:
27967468Snon	switch (*++source) {
28067468Snon	case APPEND:
28167468Snon	    break;
28267468Snon	case MARKER:
28367468Snon	    *target = MARKER;
28467468Snon	    ++source;
28567468Snon	    break;
28667468Snon	case 's':
28767468Snon	    *target = ' ';
28867468Snon	    ++source;
28967468Snon	    break;
29067468Snon	case '0':
29167468Snon	case '1':
29267468Snon	case '2':
29367468Snon	case '3':
29467468Snon	    base = 8;
29567468Snon	    limit = 3;
29667468Snon	    break;
29767468Snon	case 'u':
29867468Snon	    limit = 4;
29967468Snon	    ++source;
30079697Snon	    break;
30167468Snon	case 'U':
30267468Snon	    limit = 8;
30367468Snon	    ++source;
30467468Snon	    break;
30567468Snon	}
30667468Snon	if (limit) {
30767468Snon	    *target = 0;
30867468Snon	    while (limit-- > 0) {
30967468Snon		char *find = strchr(digits, *source++);
31067468Snon		int ch = (find != 0) ? (int) (find - digits) : -1;
31167468Snon		*target *= base;
31267468Snon		if (ch >= 0 && ch < base) {
31367468Snon		    *target += ch;
31467468Snon		}
31567468Snon	    }
31667468Snon	}
31767468Snon	break;
31879697Snon    default:
31967468Snon	*target = *source++;
32079697Snon	break;
32167468Snon    }
32267468Snon    return source;
32367468Snon}
324
325static char *
326decode_chtype(char *source, chtype fillin, chtype *target)
327{
328    attr_t attr = ChAttrOf(fillin);
329    int color = PAIR_NUMBER((int) attr);
330    int value;
331
332    T(("decode_chtype '%s'", source));
333    source = decode_attr(source, &attr, &color);
334    source = decode_char(source, &value);
335    *target = (ChCharOf(value) | attr | (chtype) COLOR_PAIR(color));
336    /* FIXME - ignore combining characters */
337    return source;
338}
339
340#if NCURSES_WIDECHAR
341static char *
342decode_cchar(char *source, cchar_t *fillin, cchar_t *target)
343{
344    int color;
345    attr_t attr = fillin->attr;
346    wchar_t chars[CCHARW_MAX];
347    int append = 0;
348    int value = 0;
349
350    T(("decode_cchar  '%s'", source));
351    *target = blank;
352#if NCURSES_EXT_COLORS
353    color = fillin->ext_color;
354#else
355    color = (int) PAIR_NUMBER(attr);
356#endif
357    source = decode_attr(source, &attr, &color);
358    memset(chars, 0, sizeof(chars));
359    source = decode_char(source, &value);
360    chars[0] = (wchar_t) value;
361    /* handle combining characters */
362    while (source[0] == MARKER && source[1] == APPEND) {
363	source += 2;
364	source = decode_char(source, &value);
365	if (++append < CCHARW_MAX) {
366	    chars[append] = (wchar_t) value;
367	}
368    }
369    setcchar(target, chars, attr, (short) color, &color);
370    return source;
371}
372#endif
373
374static int
375read_win(WINDOW *win, FILE *fp)
376{
377    int code = ERR;
378    size_t n;
379    int color;
380#if NCURSES_WIDECHAR
381    NCURSES_CH_T prior;
382#endif
383    chtype prior2;
384
385    memset(win, 0, sizeof(WINDOW));
386    for (;;) {
387	char *name;
388	char *value;
389	char *txt = read_txt(fp);
390
391	if (txt == 0)
392	    break;
393	if (!strcmp(txt, "rows:")) {
394	    free(txt);
395	    code = OK;
396	    break;
397	}
398	if ((value = strchr(txt, '=')) == 0) {
399	    free(txt);
400	    continue;
401	}
402	*value++ = '\0';
403	name = !strcmp(txt, "flag") ? value : txt;
404	for (n = 0; n < SIZEOF(scr_params); ++n) {
405	    if (!strcmp(name, scr_params[n].name)) {
406		void *data = (void *) ((char *) win + scr_params[n].offset);
407
408		switch (scr_params[n].type) {
409		case pATTR:
410		    (void) decode_attr(value, data, &color);
411		    break;
412		case pBOOL:
413		    *(bool *) data = TRUE;
414		    break;
415		case pCHAR:
416		    prior2 = ' ';
417		    decode_chtype(value, prior2, data);
418		    break;
419		case pINT:
420		    *(int *) data = atoi(value);
421		    break;
422		case pSHORT:
423		    *(short *) data = (short) atoi(value);
424		    break;
425		case pSIZE:
426		    *(NCURSES_SIZE_T *) data = (NCURSES_SIZE_T) atoi(value);
427		    break;
428#if NCURSES_WIDECHAR
429		case pCCHAR:
430		    prior = blank;
431		    decode_cchar(value, &prior, data);
432		    break;
433#endif
434		}
435		break;
436	    }
437	}
438	free(txt);
439    }
440    return code;
441}
442
443static int
444read_row(char *source, NCURSES_CH_T *prior, NCURSES_CH_T *target, int length)
445{
446    while (*source != '\0' && length > 0) {
447#if NCURSES_WIDECHAR
448	int len;
449
450	source = decode_cchar(source, prior, target);
451	len = _nc_wacs_width(target->chars[0]);
452	if (len > 1) {
453	    int n;
454
455	    SetWidecExt(CHDEREF(target), 0);
456	    for (n = 1; n < len; ++n) {
457		target[n] = target[0];
458		SetWidecExt(CHDEREF(target), n);
459	    }
460	    target += (len - 1);
461	    length -= (len - 1);
462	}
463#else
464	source = decode_chtype(source, *prior, target);
465#endif
466	*prior = *target;
467	++target;
468	--length;
469    }
470    while (length-- > 0) {
471	*target++ = blank;
472    }
473    /* FIXME - see what error conditions should apply if I need to return ERR */
474    return 0;
475}
476#endif /* NCURSES_EXT_PUTWIN */
477
478/*
479 * Originally, getwin/putwin used fread/fwrite, because they used binary data.
480 * The new format uses printable ASCII, which does not have as predictable
481 * sizes.  Consequently, we use buffered I/O, e.g., fgetc/fprintf, which need
482 * special handling if we want to read screen dumps from an older library.
483 */
484static int
485read_block(void *target, size_t length, FILE *fp)
486{
487    int result = 0;
488    char *buffer = target;
489
490    clearerr(fp);
491    while (length-- != 0) {
492	int ch = fgetc(fp);
493	if (ch == EOF) {
494	    result = -1;
495	    break;
496	}
497	*buffer++ = (char) ch;
498    }
499    return result;
500}
501
502NCURSES_EXPORT(WINDOW *)
503NCURSES_SP_NAME(getwin) (NCURSES_SP_DCLx FILE *filep)
504{
505    WINDOW tmp, *nwin;
506    bool old_format = FALSE;
507
508    T((T_CALLED("getwin(%p)"), (void *) filep));
509
510    if (filep == 0) {
511	returnWin(0);
512    }
513
514    /*
515     * Read the first 4 bytes to determine first if this is an old-format
516     * screen-dump, or new-format.
517     */
518    if (read_block(&tmp, (size_t) 4, filep) < 0) {
519	returnWin(0);
520    }
521    /*
522     * If this is a new-format file, and we do not support it, give up.
523     */
524    if (!memcmp(&tmp, my_magic, (size_t) 4)) {
525#if NCURSES_EXT_PUTWIN
526	if (read_win(&tmp, filep) < 0)
527#endif
528	    returnWin(0);
529    } else if (read_block(((char *) &tmp) + 4, sizeof(WINDOW) - 4, filep) < 0) {
530	returnWin(0);
531    } else {
532	old_format = TRUE;
533    }
534
535    /*
536     * Check the window-size:
537     */
538    if (tmp._maxy == 0 ||
539	tmp._maxy > MAX_SIZE ||
540	tmp._maxx == 0 ||
541	tmp._maxx > MAX_SIZE) {
542	returnWin(0);
543    }
544
545    if (tmp._flags & _ISPAD) {
546	nwin = NCURSES_SP_NAME(newpad) (NCURSES_SP_ARGx
547					tmp._maxy + 1,
548					tmp._maxx + 1);
549    } else {
550	nwin = NCURSES_SP_NAME(newwin) (NCURSES_SP_ARGx
551					tmp._maxy + 1,
552					tmp._maxx + 1, 0, 0);
553    }
554
555    /*
556     * We deliberately do not restore the _parx, _pary, or _parent
557     * fields, because the window hierarchy within which they
558     * made sense is probably gone.
559     */
560    if (nwin != 0) {
561	int n;
562	size_t linesize = sizeof(NCURSES_CH_T) * (size_t) (tmp._maxx + 1);
563
564	nwin->_curx = tmp._curx;
565	nwin->_cury = tmp._cury;
566	nwin->_maxy = tmp._maxy;
567	nwin->_maxx = tmp._maxx;
568	nwin->_begy = tmp._begy;
569	nwin->_begx = tmp._begx;
570	nwin->_yoffset = tmp._yoffset;
571	nwin->_flags = tmp._flags & ~(_SUBWIN);
572
573	WINDOW_ATTRS(nwin) = WINDOW_ATTRS(&tmp);
574	nwin->_nc_bkgd = tmp._nc_bkgd;
575
576	nwin->_notimeout = tmp._notimeout;
577	nwin->_clear = tmp._clear;
578	nwin->_leaveok = tmp._leaveok;
579	nwin->_idlok = tmp._idlok;
580	nwin->_idcok = tmp._idcok;
581	nwin->_immed = tmp._immed;
582	nwin->_scroll = tmp._scroll;
583	nwin->_sync = tmp._sync;
584	nwin->_use_keypad = tmp._use_keypad;
585	nwin->_delay = tmp._delay;
586
587	nwin->_regtop = tmp._regtop;
588	nwin->_regbottom = tmp._regbottom;
589
590	if (tmp._flags & _ISPAD)
591	    nwin->_pad = tmp._pad;
592
593	if (old_format) {
594	    T(("reading old-format screen dump"));
595	    for (n = 0; n <= nwin->_maxy; n++) {
596		if (read_block(nwin->_line[n].text, linesize, filep) < 0) {
597		    delwin(nwin);
598		    returnWin(0);
599		}
600	    }
601	}
602#if NCURSES_EXT_PUTWIN
603	else {
604	    char *txt = 0;
605	    bool success = TRUE;
606	    NCURSES_CH_T prior = blank;
607
608	    T(("reading new-format screen dump"));
609	    for (n = 0; n <= nwin->_maxy; n++) {
610		long row;
611		char *next;
612
613		if ((txt = read_txt(filep)) == 0) {
614		    T(("...failed to read string for row %d", n + 1));
615		    success = FALSE;
616		    break;
617		}
618		row = strtol(txt, &next, 10);
619		if (row != (n + 1) || *next != ':') {
620		    T(("...failed to read row-number %d", n + 1));
621		    success = FALSE;
622		    break;
623		}
624
625		if (read_row(++next, &prior, nwin->_line[n].text, tmp._maxx
626			     + 1) < 0) {
627		    T(("...failed to read cells for row %d", n + 1));
628		    success = FALSE;
629		    break;
630		}
631		free(txt);
632		txt = 0;
633	    }
634
635	    if (!success) {
636		free(txt);
637		delwin(nwin);
638		returnWin(0);
639	    }
640	}
641#endif
642	touchwin(nwin);
643    }
644    returnWin(nwin);
645}
646
647#if NCURSES_SP_FUNCS
648NCURSES_EXPORT(WINDOW *)
649getwin(FILE *filep)
650{
651    return NCURSES_SP_NAME(getwin) (CURRENT_SCREEN, filep);
652}
653#endif
654
655#if NCURSES_EXT_PUTWIN
656static void
657encode_attr(char *target, ARG_SLIMIT(limit)
658	    attr_t source,
659	    attr_t prior,
660	    int source_color,
661	    int prior_color)
662{
663#if USE_STRING_HACKS && HAVE_SNPRINTF
664    char *base = target;
665#endif
666    source &= ~A_CHARTEXT;
667    prior &= ~A_CHARTEXT;
668
669    *target = '\0';
670    if ((source != prior) || (source_color != prior_color)) {
671	size_t n;
672	bool first = TRUE;
673
674	*target++ = MARKER;
675	*target++ = L_CURL;
676
677	for (n = 0; n < SIZEOF(scr_attrs); ++n) {
678	    if ((source & scr_attrs[n].attr) != 0 ||
679		((source & ALL_BUT_COLOR) == 0 &&
680		 (scr_attrs[n].attr == A_NORMAL))) {
681		if (first) {
682		    first = FALSE;
683		} else {
684		    *target++ = '|';
685		}
686		_nc_STRCPY(target, scr_attrs[n].name, limit);
687		target += strlen(target);
688	    }
689	}
690	if (source_color != prior_color) {
691	    if (!first)
692		*target++ = '|';
693	    _nc_SPRINTF(target, CUR_SLIMIT "C%d", source_color);
694	    target += strlen(target);
695	}
696
697	*target++ = R_CURL;
698	*target = '\0';
699    }
700}
701
702static void
703encode_cell(char *target, ARG_SLIMIT(limit) CARG_CH_T source, CARG_CH_T previous)
704{
705#if USE_STRING_HACKS && HAVE_SNPRINTF
706    char *base = target;
707#endif
708#if NCURSES_WIDECHAR
709    size_t n;
710    int source_pair = GetPair(*source);
711    int previous_pair = GetPair(*previous);
712
713    *target = '\0';
714    if ((previous->attr != source->attr) || (previous_pair != source_pair)) {
715	encode_attr(target, CUR_SLIMIT
716		    source->attr,
717		    previous->attr,
718		    source_pair,
719		    previous_pair);
720    }
721    target += strlen(target);
722#if NCURSES_EXT_COLORS
723    if (previous->ext_color != source->ext_color) {
724	_nc_SPRINTF(target, CUR_SLIMIT
725		    "%c%cC%d%c", MARKER, L_CURL, source->ext_color, R_CURL);
726    }
727#endif
728    for (n = 0; n < SIZEOF(source->chars); ++n) {
729	unsigned uch = (unsigned) source->chars[n];
730	if (uch == 0)
731	    continue;
732	if (n) {
733	    *target++ = MARKER;
734	    *target++ = APPEND;
735	}
736	*target++ = MARKER;
737	if (uch > 0xffff) {
738	    _nc_SPRINTF(target, CUR_SLIMIT "U%08x", uch);
739	} else if (uch > 0xff) {
740	    _nc_SPRINTF(target, CUR_SLIMIT "u%04x", uch);
741	} else if (uch < 32 || uch >= 127) {
742	    _nc_SPRINTF(target, CUR_SLIMIT "%03o", uch & 0xff);
743	} else {
744	    switch (uch) {
745	    case ' ':
746		_nc_STRCPY(target, "s", limit);
747		break;
748	    case MARKER:
749		*target++ = MARKER;
750		*target = '\0';
751		break;
752	    default:
753		--target;
754		_nc_SPRINTF(target, CUR_SLIMIT "%c", uch);
755		break;
756	    }
757	}
758	target += strlen(target);
759    }
760#else
761    chtype ch = CharOfD(source);
762
763    *target = '\0';
764    if (AttrOfD(previous) != AttrOfD(source)) {
765	encode_attr(target, CUR_SLIMIT
766		    AttrOfD(source),
767		    AttrOfD(previous),
768		    GetPair(source),
769		    GetPair(previous));
770    }
771    target += strlen(target);
772    *target++ = MARKER;
773    if (ch < 32 || ch >= 127) {
774	_nc_SPRINTF(target, CUR_SLIMIT "%03o", UChar(ch));
775    } else {
776	switch (ch) {
777	case ' ':
778	    _nc_STRCPY(target, "s", limit);
779	    break;
780	case MARKER:
781	    *target++ = MARKER;
782	    *target = '\0';
783	    break;
784	default:
785	    --target;
786	    _nc_SPRINTF(target, CUR_SLIMIT "%c", UChar(ch));
787	    break;
788	}
789    }
790#endif
791}
792#endif
793
794NCURSES_EXPORT(int)
795putwin(WINDOW *win, FILE *filep)
796{
797    int code = ERR;
798
799    T((T_CALLED("putwin(%p,%p)"), (void *) win, (void *) filep));
800
801#if NCURSES_EXT_PUTWIN
802    if (win != 0) {
803	const char *version = curses_version();
804	char buffer[1024];
805	NCURSES_CH_T last_cell;
806	int y;
807
808	memset(&last_cell, 0, sizeof(last_cell));
809
810	clearerr(filep);
811
812	/*
813	 * Our magic number is technically nonprinting, but aside from that,
814	 * all of the file is printable ASCII.
815	 */
816#define PUTS(s) if (fputs(s, filep) == EOF || ferror(filep)) returnCode(code)
817	PUTS(my_magic);
818	PUTS(version);
819	PUTS("\n");
820	for (y = 0; y < (int) SIZEOF(scr_params); ++y) {
821	    const char *name = scr_params[y].name;
822	    const char *data = (char *) win + scr_params[y].offset;
823	    const void *dp = (const void *) data;
824	    attr_t attr;
825
826	    *buffer = '\0';
827	    if (!strncmp(name, "_pad.", (size_t) 5) && !(win->_flags & _ISPAD)) {
828		continue;
829	    }
830	    switch (scr_params[y].type) {
831	    case pATTR:
832		attr = (*(const attr_t *) dp) & ~A_CHARTEXT;
833		encode_attr(buffer, TOP_SLIMIT
834			    (*(const attr_t *) dp) & ~A_CHARTEXT,
835			    A_NORMAL,
836			    COLOR_PAIR((int) attr),
837			    0);
838		break;
839	    case pBOOL:
840		if (!(*(const bool *) data)) {
841		    continue;
842		}
843		_nc_STRCPY(buffer, name, sizeof(buffer));
844		name = "flag";
845		break;
846	    case pCHAR:
847		attr = (*(const attr_t *) dp);
848		encode_attr(buffer, TOP_SLIMIT
849			    * (const attr_t *) dp,
850			    A_NORMAL,
851			    COLOR_PAIR((int) attr),
852			    0);
853		break;
854	    case pINT:
855		if (!(*(const int *) dp))
856		    continue;
857		_nc_SPRINTF(buffer, TOP_SLIMIT
858			    "%d", *(const int *) dp);
859		break;
860	    case pSHORT:
861		if (!(*(const short *) dp))
862		    continue;
863		_nc_SPRINTF(buffer, TOP_SLIMIT
864			    "%d", *(const short *) dp);
865		break;
866	    case pSIZE:
867		if (!(*(const NCURSES_SIZE_T *) dp))
868		    continue;
869		_nc_SPRINTF(buffer, TOP_SLIMIT
870			    "%d", *(const NCURSES_SIZE_T *) dp);
871		break;
872#if NCURSES_WIDECHAR
873	    case pCCHAR:
874		encode_cell(buffer, TOP_SLIMIT
875			    (CARG_CH_T) dp, CHREF(last_cell));
876		break;
877#endif
878	    }
879	    /*
880	     * Only write non-default data.
881	     */
882	    if (*buffer != '\0') {
883		if (fprintf(filep, "%s=%s\n", name, buffer) <= 0
884		    || ferror(filep))
885		    returnCode(code);
886	    }
887	}
888	/* Write row-data */
889	fprintf(filep, "rows:\n");
890	for (y = 0; y <= win->_maxy; y++) {
891	    NCURSES_CH_T *data = win->_line[y].text;
892	    int x;
893	    if (fprintf(filep, "%d:", y + 1) <= 0
894		|| ferror(filep))
895		returnCode(code);
896	    for (x = 0; x <= win->_maxx; x++) {
897#if NCURSES_WIDECHAR
898		int len = _nc_wacs_width(data[x].chars[0]);
899		encode_cell(buffer, TOP_SLIMIT CHREF(data[x]), CHREF(last_cell));
900		last_cell = data[x];
901		PUTS(buffer);
902		if (len > 1)
903		    x += (len - 1);
904#else
905		encode_cell(buffer, TOP_SLIMIT CHREF(data[x]), CHREF(last_cell));
906		last_cell = data[x];
907		PUTS(buffer);
908#endif
909	    }
910	    PUTS("\n");
911	}
912	code = OK;
913    }
914#else
915    /*
916     * This is the original putwin():
917     * A straight binary dump is simple, but its format can depend on whether
918     * ncurses is compiled with wide-character support, and also may depend
919     * on the version of ncurses, e.g., if the WINDOW structure is extended.
920     */
921    if (win != 0) {
922	size_t len = (size_t) (win->_maxx + 1);
923	int y;
924
925	clearerr(filep);
926	if (fwrite(win, sizeof(WINDOW), (size_t) 1, filep) != 1
927	    || ferror(filep))
928	      returnCode(code);
929
930	for (y = 0; y <= win->_maxy; y++) {
931	    if (fwrite(win->_line[y].text,
932		       sizeof(NCURSES_CH_T), len, filep) != len
933		|| ferror(filep)) {
934		returnCode(code);
935	    }
936	}
937	code = OK;
938    }
939#endif
940    returnCode(code);
941}
942
943NCURSES_EXPORT(int)
944NCURSES_SP_NAME(scr_restore) (NCURSES_SP_DCLx const char *file)
945{
946    FILE *fp = 0;
947    int code = ERR;
948
949    T((T_CALLED("scr_restore(%p,%s)"), (void *) SP_PARM, _nc_visbuf(file)));
950
951    if (_nc_access(file, R_OK) >= 0
952	&& (fp = fopen(file, BIN_R)) != 0) {
953	delwin(NewScreen(SP_PARM));
954	NewScreen(SP_PARM) = getwin(fp);
955#if !USE_REENTRANT
956	newscr = NewScreen(SP_PARM);
957#endif
958	(void) fclose(fp);
959	if (NewScreen(SP_PARM) != 0) {
960	    code = OK;
961	}
962    }
963    returnCode(code);
964}
965
966#if NCURSES_SP_FUNCS
967NCURSES_EXPORT(int)
968scr_restore(const char *file)
969{
970    return NCURSES_SP_NAME(scr_restore) (CURRENT_SCREEN, file);
971}
972#endif
973
974NCURSES_EXPORT(int)
975scr_dump(const char *file)
976{
977    int result;
978    FILE *fp = 0;
979
980    T((T_CALLED("scr_dump(%s)"), _nc_visbuf(file)));
981
982    if (_nc_access(file, W_OK) < 0
983	|| (fp = fopen(file, BIN_W)) == 0) {
984	result = ERR;
985    } else {
986	(void) putwin(newscr, fp);
987	(void) fclose(fp);
988	result = OK;
989    }
990    returnCode(result);
991}
992
993NCURSES_EXPORT(int)
994NCURSES_SP_NAME(scr_init) (NCURSES_SP_DCLx const char *file)
995{
996    int code = ERR;
997
998    T((T_CALLED("scr_init(%p,%s)"), (void *) SP_PARM, _nc_visbuf(file)));
999
1000    if (SP_PARM != 0 &&
1001#ifdef USE_TERM_DRIVER
1002	InfoOf(SP_PARM).caninit
1003#else
1004	!(exit_ca_mode && non_rev_rmcup)
1005#endif
1006	) {
1007	FILE *fp = 0;
1008
1009	if (_nc_access(file, R_OK) >= 0
1010	    && (fp = fopen(file, BIN_R)) != 0) {
1011	    delwin(CurScreen(SP_PARM));
1012	    CurScreen(SP_PARM) = getwin(fp);
1013#if !USE_REENTRANT
1014	    curscr = CurScreen(SP_PARM);
1015#endif
1016	    (void) fclose(fp);
1017	    if (CurScreen(SP_PARM) != 0) {
1018		code = OK;
1019	    }
1020	}
1021    }
1022    returnCode(code);
1023}
1024
1025#if NCURSES_SP_FUNCS
1026NCURSES_EXPORT(int)
1027scr_init(const char *file)
1028{
1029    return NCURSES_SP_NAME(scr_init) (CURRENT_SCREEN, file);
1030}
1031#endif
1032
1033NCURSES_EXPORT(int)
1034NCURSES_SP_NAME(scr_set) (NCURSES_SP_DCLx const char *file)
1035{
1036    int code = ERR;
1037
1038    T((T_CALLED("scr_set(%p,%s)"), (void *) SP_PARM, _nc_visbuf(file)));
1039
1040    if (NCURSES_SP_NAME(scr_init) (NCURSES_SP_ARGx file) == OK) {
1041	delwin(NewScreen(SP_PARM));
1042	NewScreen(SP_PARM) = dupwin(curscr);
1043#if !USE_REENTRANT
1044	newscr = NewScreen(SP_PARM);
1045#endif
1046	if (NewScreen(SP_PARM) != 0) {
1047	    code = OK;
1048	}
1049    }
1050    returnCode(code);
1051}
1052
1053#if NCURSES_SP_FUNCS
1054NCURSES_EXPORT(int)
1055scr_set(const char *file)
1056{
1057    return NCURSES_SP_NAME(scr_set) (CURRENT_SCREEN, file);
1058}
1059#endif
1060