+ zstate = 0;
+ makeliteral_chr(b, c, &zstate);
+ }
+
+ z.chr = 0;
+ zstate = 0;
+ makeliteral_chr(b, &z, &zstate);
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used);
+
+static unsigned char *compressline(termline *ldata)
+{
+ struct buf buffer = { NULL, 0, 0 }, *b = &buffer;
+
+ /*
+ * First, store the column count, 7 bits at a time, least
+ * significant `digit' first, with the high bit set on all but
+ * the last.
+ */
+ {
+ int n = ldata->cols;
+ while (n >= 128) {
+ add(b, (unsigned char)((n & 0x7F) | 0x80));
+ n >>= 7;
+ }
+ add(b, (unsigned char)(n));
+ }
+
+ /*
+ * Next store the lattrs; same principle.
+ */
+ {
+ int n = ldata->lattr;
+ while (n >= 128) {
+ add(b, (unsigned char)((n & 0x7F) | 0x80));
+ n >>= 7;
+ }
+ add(b, (unsigned char)(n));
+ }
+
+ /*
+ * Now we store a sequence of separate run-length encoded
+ * fragments, each containing exactly as many symbols as there
+ * are columns in the ldata.
+ *
+ * All of these have a common basic format:
+ *
+ * - a byte 00-7F indicates that X+1 literals follow it
+ * - a byte 80-FF indicates that a single literal follows it
+ * and expects to be repeated (X-0x80)+2 times.
+ *
+ * The format of the `literals' varies between the fragments.
+ */
+ makerle(b, ldata, makeliteral_chr);
+ makerle(b, ldata, makeliteral_attr);
+ makerle(b, ldata, makeliteral_cc);
+
+ /*
+ * Diagnostics: ensure that the compressed data really does
+ * decompress to the right thing.
+ *
+ * This is a bit performance-heavy for production code.
+ */
+#ifdef TERM_CC_DIAGS
+#ifndef CHECK_SB_COMPRESSION
+ {
+ int dused;
+ termline *dcl;
+ int i;
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+ for (i = 0; i < b->len; i++) {
+ printf(" %02x ", b->data[i]);
+ }
+ printf("\n");
+#endif
+
+ dcl = decompressline(b->data, &dused);
+ assert(b->len == dused);
+ assert(ldata->cols == dcl->cols);
+ assert(ldata->lattr == dcl->lattr);
+ for (i = 0; i < ldata->cols; i++)
+ assert(termchars_equal(&ldata->chars[i], &dcl->chars[i]));
+
+#ifdef DIAGNOSTIC_SB_COMPRESSION
+ printf("%d cols (%d bytes) -> %d bytes (factor of %g)\n",
+ ldata->cols, 4 * ldata->cols, dused,
+ (double)dused / (4 * ldata->cols));
+#endif
+
+ freeline(dcl);
+ }
+#endif
+#endif /* TERM_CC_DIAGS */
+
+ /*
+ * Trim the allocated memory so we don't waste any, and return.
+ */
+ return sresize(b->data, b->len, unsigned char);
+}
+
+static void readrle(struct buf *b, termline *ldata,
+ void (*readliteral)(struct buf *b, termchar *c,
+ termline *ldata, unsigned long *state))
+{
+ int n = 0;
+ unsigned long state = 0;
+
+ while (n < ldata->cols) {
+ int hdr = get(b);
+
+ if (hdr >= 0x80) {
+ /* A run. */
+
+ int pos = b->len, count = hdr + 2 - 0x80;
+ while (count--) {
+ assert(n < ldata->cols);
+ b->len = pos;
+ readliteral(b, ldata->chars + n, ldata, &state);
+ n++;
+ }
+ } else {
+ /* Just a sequence of consecutive literals. */
+
+ int count = hdr + 1;
+ while (count--) {
+ assert(n < ldata->cols);
+ readliteral(b, ldata->chars + n, ldata, &state);
+ n++;
+ }
+ }
+ }
+
+ assert(n == ldata->cols);
+}
+static void readliteral_chr(struct buf *b, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ int byte;
+
+ /*
+ * 00000000-0000007F: 0xxxxxxx
+ * 00000080-00003FFF: 10xxxxxx xxxxxxxx
+ * 00004000-001FFFFF: 110xxxxx xxxxxxxx xxxxxxxx
+ * 00200000-0FFFFFFF: 1110xxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ * 10000000-FFFFFFFF: 11110ZZZ xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
+ */
+
+ byte = get(b);
+ if (byte < 0x80) {
+ c->chr = byte | *state;
+ } else if (byte < 0xC0) {
+ c->chr = (byte &~ 0xC0) << 8;
+ c->chr |= get(b);
+ } else if (byte < 0xE0) {
+ c->chr = (byte &~ 0xE0) << 16;
+ c->chr |= get(b) << 8;
+ c->chr |= get(b);
+ } else if (byte < 0xF0) {
+ c->chr = (byte &~ 0xF0) << 24;
+ c->chr |= get(b) << 16;
+ c->chr |= get(b) << 8;
+ c->chr |= get(b);
+ } else {
+ assert(byte == 0xF0);
+ c->chr = get(b) << 24;
+ c->chr |= get(b) << 16;
+ c->chr |= get(b) << 8;
+ c->chr |= get(b);
+ }
+ *state = c->chr & ~0xFF;
+}
+static void readliteral_attr(struct buf *b, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ unsigned val, attr, colourbits;
+
+ val = get(b) << 8;
+ val |= get(b);
+
+ if (val >= 0x8000) {
+ val &= ~0x8000;
+ val <<= 16;
+ val |= get(b) << 8;
+ val |= get(b);
+ }
+
+ colourbits = (val >> (32-9)) & 0xFF;
+ attr = (val & ((1<<(32-9))-1));
+
+ attr = (((attr >> (ATTR_FGSHIFT + 4)) << (ATTR_FGSHIFT + 8)) |
+ (attr & ((1 << (ATTR_FGSHIFT + 4))-1)));
+ attr = (((attr >> (ATTR_BGSHIFT + 4)) << (ATTR_BGSHIFT + 8)) |
+ (attr & ((1 << (ATTR_BGSHIFT + 4))-1)));
+
+ attr |= (colourbits >> 4) << (ATTR_BGSHIFT + 4);
+ attr |= (colourbits & 0xF) << (ATTR_FGSHIFT + 4);
+
+ c->attr = attr;
+}
+static void readliteral_cc(struct buf *b, termchar *c, termline *ldata,
+ unsigned long *state)
+{
+ termchar n;
+ unsigned long zstate;
+ int x = c - ldata->chars;
+
+ c->cc_next = 0;
+
+ while (1) {
+ zstate = 0;
+ readliteral_chr(b, &n, ldata, &zstate);
+ if (!n.chr)
+ break;
+ add_cc(ldata, x, n.chr);
+ }
+}
+
+static termline *decompressline(unsigned char *data, int *bytes_used)
+{
+ int ncols, byte, shift;
+ struct buf buffer, *b = &buffer;
+ termline *ldata;
+
+ b->data = data;
+ b->len = 0;
+
+ /*
+ * First read in the column count.
+ */
+ ncols = shift = 0;
+ do {
+ byte = get(b);
+ ncols |= (byte & 0x7F) << shift;
+ shift += 7;
+ } while (byte & 0x80);
+
+ /*
+ * Now create the output termline.
+ */
+ ldata = snew(termline);
+ ldata->chars = snewn(ncols, termchar);
+ ldata->cols = ldata->size = ncols;
+ ldata->temporary = TRUE;
+ ldata->cc_free = 0;
+
+ /*
+ * We must set all the cc pointers in ldata->chars to 0 right
+ * now, so that cc diagnostics that verify the integrity of the
+ * whole line will make sense while we're in the middle of
+ * building it up.
+ */
+ {
+ int i;
+ for (i = 0; i < ldata->cols; i++)
+ ldata->chars[i].cc_next = 0;
+ }
+
+ /*
+ * Now read in the lattr.
+ */
+ ldata->lattr = shift = 0;
+ do {
+ byte = get(b);
+ ldata->lattr |= (byte & 0x7F) << shift;
+ shift += 7;
+ } while (byte & 0x80);
+
+ /*
+ * Now we read in each of the RLE streams in turn.
+ */
+ readrle(b, ldata, readliteral_chr);
+ readrle(b, ldata, readliteral_attr);
+ readrle(b, ldata, readliteral_cc);
+
+ /* Return the number of bytes read, for diagnostic purposes. */
+ if (bytes_used)
+ *bytes_used = b->len;
+
+ return ldata;
+}
+
+/*
+ * Resize a line to make it `cols' columns wide.
+ */
+static void resizeline(Terminal *term, termline *line, int cols)
+{
+ int i, oldcols;
+
+ if (line->cols != cols) {
+
+ oldcols = line->cols;
+
+ /*
+ * This line is the wrong length, which probably means it
+ * hasn't been accessed since a resize. Resize it now.
+ *
+ * First, go through all the characters that will be thrown
+ * out in the resize (if we're shrinking the line) and
+ * return their cc lists to the cc free list.
+ */
+ for (i = cols; i < oldcols; i++)
+ clear_cc(line, i);
+
+ /*
+ * If we're shrinking the line, we now bodily move the
+ * entire cc section from where it started to where it now
+ * needs to be. (We have to do this before the resize, so
+ * that the data we're copying is still there. However, if
+ * we're expanding, we have to wait until _after_ the
+ * resize so that the space we're copying into is there.)
+ */
+ if (cols < oldcols)
+ memmove(line->chars + cols, line->chars + oldcols,
+ (line->size - line->cols) * TSIZE);
+
+ /*
+ * Now do the actual resize, leaving the _same_ amount of
+ * cc space as there was to begin with.
+ */
+ line->size += cols - oldcols;
+ line->chars = sresize(line->chars, line->size, TTYPE);
+ line->cols = cols;
+
+ /*
+ * If we're expanding the line, _now_ we move the cc
+ * section.
+ */
+ if (cols > oldcols)
+ memmove(line->chars + cols, line->chars + oldcols,
+ (line->size - line->cols) * TSIZE);
+
+ /*
+ * Go through what's left of the original line, and adjust
+ * the first cc_next pointer in each list. (All the
+ * subsequent ones are still valid because they are
+ * relative offsets within the cc block.) Also do the same
+ * to the head of the cc_free list.
+ */
+ for (i = 0; i < oldcols && i < cols; i++)
+ if (line->chars[i].cc_next)
+ line->chars[i].cc_next += cols - oldcols;
+ if (line->cc_free)
+ line->cc_free += cols - oldcols;
+
+ /*
+ * And finally fill in the new space with erase chars. (We
+ * don't have to worry about cc lists here, because we
+ * _know_ the erase char doesn't have one.)
+ */
+ for (i = oldcols; i < cols; i++)
+ line->chars[i] = term->basic_erase_char;
+
+#ifdef TERM_CC_DIAGS
+ cc_check(line);
+#endif
+ }
+}
+
+/*
+ * Get the number of lines in the scrollback.
+ */
+static int sblines(Terminal *term)
+{
+ int sblines = count234(term->scrollback);
+ if (term->erase_to_scrollback &&
+ term->alt_which && term->alt_screen) {
+ sblines += term->alt_sblines;
+ }
+ return sblines;
+}
+
+/*
+ * Retrieve a line of the screen or of the scrollback, according to
+ * whether the y coordinate is non-negative or negative
+ * (respectively).
+ */
+static termline *lineptr(Terminal *term, int y, int lineno, int screen)
+{
+ termline *line;
+ tree234 *whichtree;
+ int treeindex;
+
+ if (y >= 0) {
+ whichtree = term->screen;
+ treeindex = y;
+ } else {
+ int altlines = 0;
+
+ assert(!screen);
+
+ if (term->erase_to_scrollback &&
+ term->alt_which && term->alt_screen) {
+ altlines = term->alt_sblines;
+ }
+ if (y < -altlines) {
+ whichtree = term->scrollback;
+ treeindex = y + altlines + count234(term->scrollback);
+ } else {
+ whichtree = term->alt_screen;
+ treeindex = y + term->alt_sblines;
+ /* treeindex = y + count234(term->alt_screen); */
+ }
+ }
+ if (whichtree == term->scrollback) {
+ unsigned char *cline = index234(whichtree, treeindex);
+ line = decompressline(cline, NULL);
+ } else {
+ line = index234(whichtree, treeindex);
+ }
+
+ /* We assume that we don't screw up and retrieve something out of range. */
+ if (line == NULL) {
+ fatalbox("line==NULL in terminal.c\n"
+ "lineno=%d y=%d w=%d h=%d\n"
+ "count(scrollback=%p)=%d\n"
+ "count(screen=%p)=%d\n"
+ "count(alt=%p)=%d alt_sblines=%d\n"
+ "whichtree=%p treeindex=%d\n\n"
+ "Please contact <putty@projects.tartarus.org> "
+ "and pass on the above information.",
+ lineno, y, term->cols, term->rows,
+ term->scrollback, count234(term->scrollback),
+ term->screen, count234(term->screen),
+ term->alt_screen, count234(term->alt_screen), term->alt_sblines,
+ whichtree, treeindex);
+ }
+ assert(line != NULL);
+
+ /*
+ * Here we resize lines to _at least_ the right length, but we
+ * don't truncate them. Truncation is done as a side effect of
+ * modifying the line.
+ *
+ * The point of this policy is to try to arrange that resizing the
+ * terminal window repeatedly - e.g. successive steps in an X11
+ * opaque window-resize drag, or resizing as a side effect of
+ * retiling by tiling WMs such as xmonad - does not throw away
+ * data gratuitously. Specifically, we want a sequence of resize
+ * operations with no terminal output between them to have the
+ * same effect as a single resize to the ultimate terminal size,
+ * and also (for the case in which xmonad narrows a window that's
+ * scrolling things) we want scrolling up new text at the bottom
+ * of a narrowed window to avoid truncating lines further up when
+ * the window is re-widened.
+ */
+ if (term->cols > line->cols)
+ resizeline(term, line, term->cols);
+
+ return line;
+}
+
+#define lineptr(x) (lineptr)(term,x,__LINE__,FALSE)
+#define scrlineptr(x) (lineptr)(term,x,__LINE__,TRUE)
+
+/*
+ * Coerce a termline to the terminal's current width. Unlike the
+ * optional resize in lineptr() above, this is potentially destructive
+ * of text, since it can shrink as well as grow the line.
+ *
+ * We call this whenever a termline is actually going to be modified.
+ * Helpfully, putting a single call to this function in check_boundary
+ * deals with _nearly_ all such cases, leaving only a few things like
+ * bulk erase and ESC#8 to handle separately.
+ */
+static void check_line_size(Terminal *term, termline *line)
+{
+ if (term->cols != line->cols) /* trivial optimisation */
+ resizeline(term, line, term->cols);
+}
+
+static void term_schedule_tblink(Terminal *term);
+static void term_schedule_cblink(Terminal *term);
+
+static void term_timer(void *ctx, unsigned long now)
+{
+ Terminal *term = (Terminal *)ctx;
+ int update = FALSE;
+
+ if (term->tblink_pending && now == term->next_tblink) {
+ term->tblinker = !term->tblinker;
+ term->tblink_pending = FALSE;
+ term_schedule_tblink(term);
+ update = TRUE;
+ }
+
+ if (term->cblink_pending && now == term->next_cblink) {
+ term->cblinker = !term->cblinker;
+ term->cblink_pending = FALSE;
+ term_schedule_cblink(term);
+ update = TRUE;
+ }
+
+ if (term->in_vbell && now == term->vbell_end) {
+ term->in_vbell = FALSE;
+ update = TRUE;
+ }
+
+ if (update ||
+ (term->window_update_pending && now == term->next_update))
+ term_update(term);
+}