From 87ef54ee2fc42464bc31f12b8fb9ca139ed6b8b1 Mon Sep 17 00:00:00 2001 From: sf-exg Date: Sat, 15 Jul 2017 08:16:22 +0000 Subject: Account for focus fading when enabling/disabling reverse video. Patch by Daniel Hahler. --- src/screen.C | 3697 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3697 insertions(+) create mode 100644 src/screen.C diff --git a/src/screen.C b/src/screen.C new file mode 100644 index 0000000..f3c6d57 --- /dev/null +++ b/src/screen.C @@ -0,0 +1,3697 @@ +/*---------------------------------------------------------------------------* + * File: screen.C + *---------------------------------------------------------------------------* + * + * Copyright (c) 1997-2001 Geoff Wing + * Copyright (c) 2003-2007 Marc Lehmann + * Copyright (c) 2015 Emanuele Giaquinta + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + *--------------------------------------------------------------------------*/ + +/* + * This file handles _all_ screen updates and selections + */ + +#include "../config.h" /* NECESSARY */ +#include "rxvt.h" /* NECESSARY */ +#include "rxvtperl.h" /* NECESSARY */ + +#include + +static inline void +fill_text (text_t *start, text_t value, int len) +{ + while (len--) + *start++ = value; +} + +/* ------------------------------------------------------------------------- */ +#define TABSIZE 8 /* default tab size */ + +/* ------------------------------------------------------------------------- * + * GENERAL SCREEN AND SELECTION UPDATE ROUTINES * + * ------------------------------------------------------------------------- */ +#define ZERO_SCROLLBACK() \ + if (option (Opt_scrollTtyOutput)) \ + view_start = 0 +#define CLEAR_SELECTION() \ + selection.beg.row = selection.beg.col \ + = selection.end.row = selection.end.col = 0 +#define CLEAR_ALL_SELECTION() \ + selection.beg.row = selection.beg.col \ + = selection.mark.row = selection.mark.col \ + = selection.end.row = selection.end.col = 0 + +#define ROW_AND_COL_IS_AFTER(A, B, C, D) \ + (((A) > (C)) || (((A) == (C)) && ((B) > (D)))) +#define ROW_AND_COL_IS_BEFORE(A, B, C, D) \ + (((A) < (C)) || (((A) == (C)) && ((B) < (D)))) +#define ROW_AND_COL_IN_ROW_AFTER(A, B, C, D) \ + (((A) == (C)) && ((B) > (D))) +#define ROW_AND_COL_IN_ROW_AT_OR_AFTER(A, B, C, D) \ + (((A) == (C)) && ((B) >= (D))) +#define ROW_AND_COL_IN_ROW_BEFORE(A, B, C, D) \ + (((A) == (C)) && ((B) < (D))) +#define ROW_AND_COL_IN_ROW_AT_OR_BEFORE(A, B, C, D) \ + (((A) == (C)) && ((B) <= (D))) + +/* these must be row_col_t */ +#define ROWCOL_IS_AFTER(X, Y) \ + ROW_AND_COL_IS_AFTER ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IS_BEFORE(X, Y) \ + ROW_AND_COL_IS_BEFORE ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_AFTER(X, Y) \ + ROW_AND_COL_IN_ROW_AFTER ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_BEFORE(X, Y) \ + ROW_AND_COL_IN_ROW_BEFORE ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_AT_OR_AFTER(X, Y) \ + ROW_AND_COL_IN_ROW_AT_OR_AFTER ((X).row, (X).col, (Y).row, (Y).col) +#define ROWCOL_IN_ROW_AT_OR_BEFORE(X, Y) \ + ROW_AND_COL_IN_ROW_AT_OR_BEFORE ((X).row, (X).col, (Y).row, (Y).col) + +/* + * CLEAR_CHARS: clear chars starting from pixel position + */ +#define CLEAR_CHARS(x, y, num) \ + if (mapped) \ + XClearArea (dpy, vt, x, y, \ + (unsigned int)Width2Pixel (num), \ + (unsigned int)Height2Pixel (1), False) + +/* ------------------------------------------------------------------------- * + * SCREEN `COMMON' ROUTINES * + * ------------------------------------------------------------------------- */ + +/* Fill part/all of a line with blanks. */ +void +rxvt_term::scr_blank_line (line_t &l, unsigned int col, unsigned int width, rend_t efs) const NOTHROW +{ + if (!l.valid ()) + { + l.alloc (); + col = 0; + width = ncol; + } + + l.touch (); + + efs &= ~RS_baseattrMask; // remove italic etc. fontstyles + efs = SET_FONT (efs, FONTSET (efs)->find_font (' ')); + + text_t *et = l.t + col; + rend_t *er = l.r + col; + + while (width--) + { + *et++ = ' '; + *er++ = efs; + } +} + +/* ------------------------------------------------------------------------- */ +/* Fill a full line with blanks - make sure it is allocated first */ +void +rxvt_term::scr_blank_screen_mem (line_t &l, rend_t efs) const NOTHROW +{ + scr_blank_line (l, 0, ncol, efs); + + l.l = 0; + l.f = 0; +} + +// nuke a single wide character at the given column +void +rxvt_term::scr_kill_char (line_t &l, int col) const NOTHROW +{ + // find begin + while (col > 0 && l.t[col] == NOCHAR) + col--; + + rend_t rend = l.r[col] & ~RS_baseattrMask; + rend = SET_FONT (rend, FONTSET (rend)->find_font (' ')); + + l.touch (); + + // found start, nuke + do { + l.t[col] = ' '; + l.r[col] = rend; + col++; + } while (col < ncol && l.t[col] == NOCHAR); +} + +// set the rendition of a single wide character beginning at the given column +void +rxvt_term::scr_set_char_rend (line_t &l, int col, rend_t rend) +{ + do { + l.r[col] = rend; + col++; + } while (col < ncol && l.t[col] == NOCHAR); +} + +/* ------------------------------------------------------------------------- * + * SCREEN INITIALISATION * + * ------------------------------------------------------------------------- */ + +void +rxvt_term::scr_alloc () NOTHROW +{ + int tsize = sizeof (text_t) * ncol; + int rsize = sizeof (rend_t) * ncol; + + // we assume that rend_t size is a sufficient alignment + // factor for text_t and line_t values, and we only + // need to adjust tsize. + tsize = (tsize + sizeof (rend_t) - 1); + tsize -= tsize % sizeof (rend_t); + + int all_rows = total_rows + nrow + nrow; + + chunk_size = (sizeof (line_t) + rsize + tsize) * all_rows; + chunk = chunk_alloc (chunk_size, 0); + + char *base = (char *)chunk + sizeof (line_t) * all_rows; + + for (int row = 0; row < all_rows; ++row) + { + line_t &l = ((line_t *)chunk) [row]; + + l.t = (text_t *)base; base += tsize; + l.r = (rend_t *)base; base += rsize; + l.l = -1; + l.f = 0; + } + + drawn_buf = (line_t *)chunk; + swap_buf = drawn_buf + nrow; + row_buf = swap_buf + nrow; +} + +void +rxvt_term::copy_line (line_t &dst, line_t &src) +{ + scr_blank_screen_mem (dst, DEFAULT_RSTYLE); + dst.l = min (src.l, ncol); + memcpy (dst.t, src.t, sizeof (text_t) * dst.l); + memcpy (dst.r, src.r, sizeof (rend_t) * dst.l); + dst.f = src.f; +} + +void ecb_cold +rxvt_term::scr_reset () +{ +#if ENABLE_OVERLAY + scr_overlay_off (); +#endif + + view_start = 0; + num_scr = 0; + + if (ncol == 0) + ncol = 80; + + if (nrow == 0) + nrow = 24; + + if (ncol == prev_ncol && nrow == prev_nrow) + return; + + // we need at least two lines for wrapping to work correctly + while (nrow + saveLines < 2) + { + //TODO//FIXME + saveLines++; + prev_nrow--; + top_row--; + } + + want_refresh = 1; + + int prev_total_rows = prev_nrow + saveLines; + total_rows = nrow + saveLines; + + screen.tscroll = 0; + screen.bscroll = nrow - 1; + + void *prev_chunk = chunk; + size_t prev_chunk_size = chunk_size; + line_t *prev_drawn_buf = drawn_buf; + line_t *prev_swap_buf = swap_buf; + line_t *prev_row_buf = row_buf; + + scr_alloc (); + + if (!prev_row_buf) + { + top_row = 0; + term_start = 0; + + memset (charsets, 'B', sizeof (charsets)); + rstyle = DEFAULT_RSTYLE; + screen.flags = Screen_DefaultFlags; + screen.cur.row = screen.cur.col = 0; + screen.charset = 0; + current_screen = PRIMARY; + scr_cursor (SAVE); + +#if NSCREENS + swap.flags = Screen_DefaultFlags; + swap.cur.row = swap.cur.col = 0; + swap.charset = 0; + current_screen = SECONDARY; + scr_cursor (SAVE); + current_screen = PRIMARY; +#endif + + selection.text = NULL; + selection.len = 0; + selection.op = SELECTION_CLEAR; + selection.screen = PRIMARY; + selection.clicks = 0; + selection.clip_text = NULL; + selection.clip_len = 0; + } + else + { + /* + * add or delete rows as appropriate + */ + + int common_col = min (prev_ncol, ncol); + + for (int row = min (nrow, prev_nrow); row--; ) + { + scr_blank_screen_mem (drawn_buf [row], DEFAULT_RSTYLE); + memcpy (drawn_buf [row].t, prev_drawn_buf [row].t, sizeof (text_t) * common_col); + memcpy (drawn_buf [row].r, prev_drawn_buf [row].r, sizeof (rend_t) * common_col); + + copy_line (swap_buf [row], prev_swap_buf [row]); + } + + int p = MOD (term_start + prev_nrow, prev_total_rows); // previous row + int pend = MOD (term_start + top_row , prev_total_rows); + int q = total_rows; // rewrapped row + + if (top_row) + { + // Re-wrap lines. This is rather ugly, possibly because I am too dumb + // to come up with a lean and mean algorithm. + // TODO: maybe optimise when width didn't change + + row_col_t ocur = screen.cur; + ocur.row = MOD (term_start + ocur.row, prev_total_rows); + + do + { + p = MOD (p - 1, prev_total_rows); + assert (prev_row_buf [MOD (p, prev_total_rows)].t); + int plines = 1; + int llen = prev_row_buf [MOD (p, prev_total_rows)].l; + + while (p != pend && prev_row_buf [MOD (p - 1, prev_total_rows)].is_longer ()) + { + p = MOD (p - 1, prev_total_rows); + + plines++; + llen += prev_ncol; + } + + int qlines = max (0, (llen - 1) / ncol) + 1; + + // drop partial lines completely + if (q < qlines) + break; + + q -= qlines; + + int lofs = 0; + line_t *qline; + + // re-assemble the full line by destination lines + for (int qrow = q; qlines--; qrow++) + { + qline = row_buf + qrow; + qline->alloc (); // redundant with next line + qline->l = ncol; + qline->is_longer (1); + + int qcol = 0; + + // see below for cursor adjustment rationale + if (p == ocur.row) + screen.cur.row = q - (total_rows - nrow); + + // fill a single destination line + while (lofs < llen && qcol < ncol) + { + int prow = lofs / prev_ncol; + int pcol = lofs % prev_ncol; + + prow = MOD (p + prow, prev_total_rows); + + // we only adjust the cursor _row_ and put it into + // the topmost line of "long line" it was in, as + // this seems to upset applications/shells/readline + // least. + if (prow == ocur.row) + screen.cur.row = q - (total_rows - nrow); + + line_t &pline = prev_row_buf [prow]; + + int len = min (min (prev_ncol - pcol, ncol - qcol), llen - lofs); + + memcpy (qline->t + qcol, pline.t + pcol, len * sizeof (text_t)); + memcpy (qline->r + qcol, pline.r + pcol, len * sizeof (rend_t)); + + lofs += len; + qcol += len; + } + } + + qline->l = llen ? MOD (llen - 1, ncol) + 1 : 0; + qline->is_longer (0); + scr_blank_line (*qline, qline->l, ncol - qline->l, DEFAULT_RSTYLE); + } + while (p != pend && q > 0); + + term_start = total_rows - nrow; + top_row = q - term_start; + + // make sure all terminal lines exist + while (top_row > 0) + scr_blank_screen_mem (ROW (--top_row), DEFAULT_RSTYLE); + } + else + { + // if no scrollback exists (yet), wing, instead of wrap + + for (int row = min (nrow, prev_nrow); row--; ) + { + line_t &src = prev_row_buf [MOD (term_start + row, prev_total_rows)]; + line_t &dst = row_buf [row]; + + copy_line (dst, src); + } + + for (int row = prev_nrow; row < nrow; row++) + scr_blank_screen_mem (row_buf [row], DEFAULT_RSTYLE); + + term_start = 0; + } + + clamp_it (screen.cur.row, 0, nrow - 1); + clamp_it (screen.cur.col, 0, ncol - 1); + } + + for (int row = nrow; row--; ) + { + if (!ROW (row).valid ()) scr_blank_screen_mem (ROW (row), DEFAULT_RSTYLE); + if (!swap_buf [row].valid ()) scr_blank_screen_mem (swap_buf [row], DEFAULT_RSTYLE); + if (!drawn_buf [row].valid ()) scr_blank_screen_mem (drawn_buf [row], DEFAULT_RSTYLE); + } + + chunk_free (prev_chunk, prev_chunk_size); + + free (tabs); + tabs = (char *)rxvt_malloc (ncol); + + for (int col = ncol; col--; ) + tabs [col] = col % TABSIZE == 0; + + CLEAR_ALL_SELECTION (); + + prev_nrow = nrow; + prev_ncol = ncol; + + tt_winch (); + + HOOK_INVOKE ((this, HOOK_RESET, DT_END)); +} + +void ecb_cold +rxvt_term::scr_release () NOTHROW +{ + chunk_free (chunk, chunk_size); + chunk = 0; + row_buf = 0; + + free (tabs); + tabs = 0; +} + +/* ------------------------------------------------------------------------- */ +/* + * Hard/Soft reset + */ +void ecb_cold +rxvt_term::scr_poweron () +{ + scr_release (); + + prev_nrow = prev_ncol = 0; + rvideo_mode = false; + scr_soft_reset (); + scr_reset (); + + scr_clear (true); + scr_refresh (); +} + +void ecb_cold +rxvt_term::scr_soft_reset () NOTHROW +{ + /* only affects modes, nothing drastic such as clearing the screen */ +#if ENABLE_OVERLAY + scr_overlay_off (); +#endif + + if (current_screen != PRIMARY) + scr_swap_screen (); + + scr_scroll_region (0, MAX_ROWS - 1); + scr_rendition (0, ~RS_None); + scr_insert_mode (0); +} + +/* ------------------------------------------------------------------------- * + * PROCESS SCREEN COMMANDS * + * ------------------------------------------------------------------------- */ +/* + * Save and Restore cursor + * XTERM_SEQ: Save cursor : ESC 7 + * XTERM_SEQ: Restore cursor: ESC 8 + */ +void +rxvt_term::scr_cursor (cursor_mode mode) NOTHROW +{ + screen_t *s; + +#if NSCREENS && !defined(NO_SECONDARY_SCREEN_CURSOR) + if (current_screen == SECONDARY) + s = &swap; + else +#endif + s = &screen; + + switch (mode) + { + case SAVE: + s->s_cur.row = screen.cur.row; + s->s_cur.col = screen.cur.col; + s->s_rstyle = rstyle; + s->s_charset = screen.charset; + s->s_charset_char = charsets[screen.charset]; + break; + + case RESTORE: + want_refresh = 1; + screen.cur.row = s->s_cur.row; + screen.cur.col = s->s_cur.col; + screen.flags &= ~Screen_WrapNext; + rstyle = s->s_rstyle; + screen.charset = s->s_charset; + charsets[screen.charset] = s->s_charset_char; + set_font_style (); + break; + } + + /* boundary check in case screen size changed between SAVE and RESTORE */ + min_it (s->cur.row, nrow - 1); + min_it (s->cur.col, ncol - 1); + assert (s->cur.row >= 0); + assert (s->cur.col >= 0); +} + +void +rxvt_term::scr_swap_screen () NOTHROW +{ + if (!option (Opt_secondaryScreen)) + return; + + for (int i = prev_nrow; i--; ) + ::swap (ROW(i), swap_buf [i]); + + ::swap (screen.cur, swap.cur); + + screen.cur.row = clamp (screen.cur.row, 0, prev_nrow - 1); + screen.cur.col = clamp (screen.cur.col, 0, prev_ncol - 1); +} + +/* ------------------------------------------------------------------------- */ +/* + * Swap between primary and secondary screens + * XTERM_SEQ: Primary screen : ESC [ ? 4 7 h + * XTERM_SEQ: Secondary screen: ESC [ ? 4 7 l + */ +void +rxvt_term::scr_change_screen (int scrn) +{ + if (scrn == current_screen) + return; + + want_refresh = 1; + view_start = 0; + + /* check for boundary cross */ + row_col_t pos; + pos.row = pos.col = 0; + if (ROWCOL_IS_BEFORE (selection.beg, pos) + && ROWCOL_IS_AFTER (selection.end, pos)) + CLEAR_SELECTION (); + + current_screen = scrn; + +#if NSCREENS + if (option (Opt_secondaryScreen)) + { + num_scr = 0; + + scr_swap_screen (); + + ::swap (screen.charset, swap.charset); + ::swap (screen.flags, swap.flags); + screen.flags |= Screen_VisibleCursor; + swap.flags |= Screen_VisibleCursor; + } + else +#endif + if (option (Opt_secondaryScroll)) + scr_scroll_text (0, prev_nrow - 1, prev_nrow); +} + +// clear WrapNext indicator, solidifying position on next line +void +rxvt_term::scr_do_wrap () NOTHROW +{ + if (!(screen.flags & Screen_WrapNext)) + return; + + screen.flags &= ~Screen_WrapNext; + + screen.cur.col = 0; + + if (screen.cur.row == screen.bscroll) + scr_scroll_text (screen.tscroll, screen.bscroll, 1); + else if (screen.cur.row < nrow - 1) + screen.cur.row++; +} + +/* ------------------------------------------------------------------------- */ +/* + * Change the colour for following text + */ +void +rxvt_term::scr_color (unsigned int color, int fgbg) NOTHROW +{ + if (!IN_RANGE_INC (color, minCOLOR, maxTermCOLOR24)) + color = fgbg; + + if (fgbg == Color_fg) + rstyle = SET_FGCOLOR (rstyle, color); + else + rstyle = SET_BGCOLOR (rstyle, color); +} + +/* ------------------------------------------------------------------------- */ +/* + * Change the rendition style for following text + */ +void +rxvt_term::scr_rendition (int set, int style) NOTHROW +{ + if (set) + rstyle |= style; + else if (style == ~RS_None) + rstyle = DEFAULT_RSTYLE; + else + rstyle &= ~style; +} + +/* ------------------------------------------------------------------------- */ +/* + * Scroll text between and inclusive, by lines + * count positive ==> scroll up + * count negative ==> scroll down + */ +int ecb_hot +rxvt_term::scr_scroll_text (int row1, int row2, int count) NOTHROW +{ + if (count == 0 || (row1 > row2)) + return 0; + + want_refresh = 1; + num_scr += count; + + if (count > 0 + && row1 == 0 + && (current_screen == PRIMARY || option (Opt_secondaryScroll))) + { + min_it (count, total_rows - (nrow - (row2 + 1))); + + top_row = max (top_row - count, -saveLines); + + // sever bottommost line + { + line_t &l = ROW(row2); + l.is_longer (0); + l.touch (); + } + + // scroll everything up 'count' lines + term_start = (term_start + count) % total_rows; + + // now copy lines below the scroll region bottom to the + // bottom of the screen again, so they look as if they + // hadn't moved. + for (int i = nrow; --i > row2; ) + { + line_t &l1 = ROW(i - count); + line_t &l2 = ROW(i); + + ::swap (l1, l2); + l2.touch (); + } + + // erase newly scrolled-in lines + for (int i = count; i--; ) + { + line_t &l = ROW(row2 - i); + + // optimise if already cleared, can be significant on slow machines + // could be rolled into scr_blank_screen_mem + if (l.r && l.l < ncol - 1 && !((l.r[l.l + 1] ^ rstyle) & (RS_fgMask | RS_bgMask))) + { + scr_blank_line (l, 0, l.l, rstyle); + l.l = 0; + l.f = 0; + } + else + scr_blank_screen_mem (l, rstyle); + } + + // move and/or clear selection, if any + if (selection.op && current_screen == selection.screen + && selection.beg.row <= row2) + { + selection.beg.row -= count; + selection.end.row -= count; + selection.mark.row -= count; + + selection_check (0); + } + + // finally move the view window, if desired + if (option (Opt_scrollWithBuffer) + && view_start != 0 + && view_start != -saveLines) + scr_page (count); + + if (SHOULD_INVOKE (HOOK_SCROLL_BACK)) + HOOK_INVOKE ((this, HOOK_SCROLL_BACK, DT_INT, count, DT_INT, top_row, DT_END)); + } + else + { + if (selection.op && current_screen == selection.screen) + { + if ((selection.beg.row < row1 && selection.end.row > row1) + || (selection.beg.row < row2 && selection.end.row > row2) + || (selection.beg.row - count < row1 && selection.beg.row >= row1) + || (selection.beg.row - count > row2 && selection.beg.row <= row2) + || (selection.end.row - count < row1 && selection.end.row >= row1) + || (selection.end.row - count > row2 && selection.end.row <= row2)) + { + CLEAR_ALL_SELECTION (); + selection.op = SELECTION_CLEAR; + } + else if (selection.end.row >= row1 && selection.end.row <= row2) + { + /* move selected region too */ + selection.beg.row -= count; + selection.end.row -= count; + selection.mark.row -= count; + + selection_check (0); + } + } + + // use a simple and robust scrolling algorithm, this + // part of scr_scroll_text is not time-critical. + + // sever line above scroll region + if (row1) + { + line_t &l = ROW(row1 - 1); + l.is_longer (0); + l.touch (); + } + + int rows = row2 - row1 + 1; + + min_it (count, rows); + + line_t *temp_buf = rxvt_temp_buf (rows); + + for (int row = 0; row < rows; row++) + { + temp_buf [row] = ROW(row1 + (row + count + rows) % rows); + + if (!IN_RANGE_EXC (row + count, 0, rows)) + scr_blank_screen_mem (temp_buf [row], rstyle); + } + + for (int row = 0; row < rows; row++) + ROW(row1 + row) = temp_buf [row]; + + // sever bottommost line + { + line_t &l = ROW(row2); + l.is_longer (0); + l.touch (); + } + } + + return count; +} + +/* ------------------------------------------------------------------------- */ +/* + * Add text given in of length to screen struct + */ +void ecb_hot +rxvt_term::scr_add_lines (const wchar_t *str, int len, int minlines) NOTHROW +{ + if (len <= 0) /* sanity */ + return; + + bool checksel; + unicode_t c; + int ncol = this->ncol; + const wchar_t *strend = str + len; + + want_refresh = 1; + ZERO_SCROLLBACK (); + + if (minlines > 0) + { + minlines += screen.cur.row - screen.bscroll; + min_it (minlines, screen.cur.row - top_row); + + if (minlines > 0 + && screen.tscroll == 0 + && screen.bscroll == nrow - 1) + { + /* _at least_ this many lines need to be scrolled */ + scr_scroll_text (screen.tscroll, screen.bscroll, minlines); + screen.cur.row -= minlines; + } + } + + assert (screen.cur.col < ncol); + assert (screen.cur.row < nrow + && screen.cur.row >= top_row); + int row = screen.cur.row; + + checksel = selection.op && current_screen == selection.screen ? 1 : 0; + + line_t *line = &ROW(row); + + while (str < strend) + { + c = (unicode_t)*str++; // convert to rxvt-unicodes representation + + if (ecb_unlikely (c < 0x20)) + if (c == C0_LF) + { + max_it (line->l, screen.cur.col); + + screen.flags &= ~Screen_WrapNext; + + if (screen.cur.row == screen.bscroll) + scr_scroll_text (screen.tscroll, screen.bscroll, 1); + else if (screen.cur.row < (nrow - 1)) + row = ++screen.cur.row; + + line = &ROW(row); /* _must_ refresh */ + continue; + } + else if (c == C0_CR) + { + max_it (line->l, screen.cur.col); + + screen.flags &= ~Screen_WrapNext; + screen.cur.col = 0; + continue; + } + else if (c == C0_HT) + { + scr_tab (1, true); + continue; + } + + if (ecb_unlikely ( + checksel /* see if we're writing within selection */ + && !ROWCOL_IS_BEFORE (screen.cur, selection.beg) + && ROWCOL_IS_BEFORE (screen.cur, selection.end) + )) + { + checksel = 0; + /* + * If we wrote anywhere in the selected area, kill the selection + * XXX: should we kill the mark too? Possibly, but maybe that + * should be a similar check. + */ + CLEAR_SELECTION (); + } + + if (ecb_unlikely (screen.flags & Screen_WrapNext)) + { + scr_do_wrap (); + + line->l = ncol; + line->is_longer (1); + + row = screen.cur.row; + line = &ROW(row); /* _must_ refresh */ + } + + // some utf-8 decoders "decode" surrogate characters: let's fix this. + if (ecb_unlikely (IN_RANGE_INC (c, 0xd800, 0xdfff))) + c = 0xfffd; + + // rely on wcwidth to tell us the character width, do wcwidth before + // further replacements, as wcwidth might return -1 for the line + // drawing characters below as they might be invalid in the current + // locale. + int width = WCWIDTH (c); + + if (ecb_unlikely (charsets [screen.charset] == '0')) // DEC SPECIAL + { + // vt100 special graphics and line drawing + // 5f-7e standard vt100 + // 40-5e rxvt extension for extra curses acs chars + static uint16_t vt100_0[62] = { // 41 .. 7e + 0x2191, 0x2193, 0x2192, 0x2190, 0x2588, 0x259a, 0x2603, // 41-47 hi mr. snowman! + 0, 0, 0, 0, 0, 0, 0, 0, // 48-4f + 0, 0, 0, 0, 0, 0, 0, 0, // 50-57 + 0, 0, 0, 0, 0, 0, 0, 0x0020, // 58-5f + 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1, // 60-67 + 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0x23ba, // 68-6f + 0x23bb, 0x2500, 0x23bc, 0x23bd, 0x251c, 0x2524, 0x2534, 0x252c, // 70-77 + 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, // 78-7e + }; + + if (c >= 0x41 && c <= 0x7e && vt100_0[c - 0x41]) + { + c = vt100_0[c - 0x41]; + width = 1; // vt100 line drawing characters are always single-width + } + } + + if (ecb_unlikely (screen.flags & Screen_Insert)) + scr_insdel_chars (width, INSERT); + + if (width != 0) + { +#if !UNICODE_3 + // trim characters we can't store directly :( + if (c >= 0x10000) +# if ENABLE_COMBINING + c = rxvt_composite.compose (c); // map to lower 16 bits +# else + c = 0xfffd; +# endif +#endif + + rend_t rend = SET_FONT (rstyle, FONTSET (rstyle)->find_font (c)); + + // if the character doesn't fit into the remaining columns... + if (ecb_unlikely (screen.cur.col > ncol - width && ncol >= width)) + { + if (screen.flags & Screen_Autowrap) + { + // ... artificially enlargen the previous one + c = NOCHAR; + // and try the same character next loop iteration + --str; + } + else + screen.cur.col = ncol - width; + } + + // nuke the character at this position, if required + // due to wonderful coincidences everywhere else in this loop + // we never have to check for overwriting a wide char itself, + // only its tail. + if (ecb_unlikely (line->t[screen.cur.col] == NOCHAR)) + scr_kill_char (*line, screen.cur.col); + + line->touch (); + + do + { + line->t[screen.cur.col] = c; + line->r[screen.cur.col] = rend; + + if (ecb_likely (screen.cur.col < ncol - 1)) + screen.cur.col++; + else + { + line->l = ncol; + if (screen.flags & Screen_Autowrap) + screen.flags |= Screen_WrapNext; + + goto end_of_line; + } + + c = NOCHAR; + } + while (ecb_unlikely (--width > 0)); + + // pad with spaces when overwriting wide character with smaller one + for (int c = screen.cur.col; ecb_unlikely (c < ncol && line->t[c] == NOCHAR); c++) + { + line->t[c] = ' '; + line->r[c] = rend; + } + +end_of_line: + ; + } +#if ENABLE_COMBINING + else // width == 0 + { + if (c != 0xfeff) // ignore BOM + { + // handle combining characters + // we just tag the accent on the previous on-screen character. + // this is arguably not correct, but also arguably not wrong. + // we don't handle double-width characters nicely yet. + line_t *linep; + text_t *tp; + rend_t *rp; + + if (screen.cur.col > 0) + { + linep = line; + tp = line->t + screen.cur.col - 1; + rp = line->r + screen.cur.col - 1; + } + else if (screen.cur.row > 0 + && ROW(screen.cur.row - 1).is_longer ()) + { + linep = &ROW(screen.cur.row - 1); + tp = linep->t + ncol - 1; + rp = linep->r + ncol - 1; + } + else + continue; + + linep->touch (); + + while (*tp == NOCHAR && tp > linep->t) + tp--, rp--; + + // first try to find a precomposed character + unicode_t n = rxvt_compose (*tp, c); + if (n == NOCHAR) + n = rxvt_composite.compose (*tp, c); + + *tp = n; + *rp = SET_FONT (*rp, FONTSET (*rp)->find_font (*tp)); + } + } +#endif + } + + max_it (line->l, screen.cur.col); + + assert (screen.cur.row >= 0); +} + +/* ------------------------------------------------------------------------- */ +/* + * Process Backspace. Move back the cursor back a position, wrap if have to + * XTERM_SEQ: CTRL-H + */ +void +rxvt_term::scr_backspace () NOTHROW +{ + if (screen.cur.col == 0) + { + if (screen.cur.row > 0) + { +#ifdef TERMCAP_HAS_BW + screen.cur.col = ncol - 1; + --screen.cur.row; + + want_refresh = 1; +#endif + } + } + else if (screen.flags & Screen_WrapNext) + screen.flags &= ~Screen_WrapNext; + else + scr_gotorc (0, -1, RELATIVE); +} + +/* ------------------------------------------------------------------------- */ +/* + * Process Horizontal Tab + * count: +ve = forward; -ve = backwards + * XTERM_SEQ: CTRL-I + */ +void +rxvt_term::scr_tab (int count, bool ht) NOTHROW +{ + int i, x; + + want_refresh = 1; + i = x = screen.cur.col; + + if (count == 0) + return; + else if (count > 0) + { + line_t &l = ROW(screen.cur.row); + rend_t base_rend = l.r[i]; + ht &= l.t[i] == ' '; + + for (; ++i < ncol; ) + if (tabs[i]) + { + x = i; + + if (!--count) + break; + } + else + ht &= l.t[i] == ' ' + && RS_SAME (l.r[i], base_rend); + + if (count) + x = ncol - 1; + + // store horizontal tab commands as characters inside the text + // buffer so they can be selected and pasted. + if (ht && option (Opt_pastableTabs)) + { + base_rend = SET_FONT (base_rend, 0); + + l.touch (x); + + i = screen.cur.col; + + l.t[i] = '\t'; + l.r[i] = base_rend; + + while (++i < x) + { + l.t[i] = NOCHAR; + l.r[i] = base_rend; + } + } + } + else /* if (count < 0) */ + { + for (; --i >= 0; ) + if (tabs[i]) + { + x = i; + if (!++count) + break; + } + + if (count) + x = 0; + } + + if (x != screen.cur.col) + scr_gotorc (0, x, R_RELATIVE); +} + +/* ------------------------------------------------------------------------- */ +/* + * Process DEC Back Index + * XTERM_SEQ: ESC 6 + * Move cursor left in row. If we're at the left boundary, shift everything + * in that row right. Clear left column. + */ +#if !ENABLE_MINIMAL +void ecb_cold +rxvt_term::scr_backindex () NOTHROW +{ + if (screen.cur.col > 0) + scr_gotorc (0, -1, R_RELATIVE | C_RELATIVE); + else + scr_insdel_chars (1, INSERT); +} +#endif +/* ------------------------------------------------------------------------- */ +/* + * Process DEC Forward Index + * XTERM_SEQ: ESC 9 + * Move cursor right in row. If we're at the right boundary, shift everything + * in that row left. Clear right column. + */ +#if !ENABLE_MINIMAL +void ecb_cold +rxvt_term::scr_forwardindex () NOTHROW +{ + if (screen.cur.col < ncol - 1) + scr_gotorc (0, 1, R_RELATIVE | C_RELATIVE); + else + { + line_t &l = ROW(screen.cur.row); + + l.touch (); + l.is_longer (0); + + scr_gotorc (0, 0, R_RELATIVE); + scr_insdel_chars (1, DELETE); + scr_gotorc (0, ncol - 1, R_RELATIVE); + } +} +#endif + +/* ------------------------------------------------------------------------- */ +/* + * Goto Row/Column + */ +void +rxvt_term::scr_gotorc (int row, int col, int relative) NOTHROW +{ + want_refresh = 1; + ZERO_SCROLLBACK (); + + screen.cur.col = relative & C_RELATIVE ? screen.cur.col + col : col; + clamp_it (screen.cur.col, 0, ncol - 1); + + screen.flags &= ~Screen_WrapNext; + + if (relative & R_RELATIVE) + { + if (row > 0) + { + if (screen.cur.row <= screen.bscroll + && (screen.cur.row + row) > screen.bscroll) + screen.cur.row = screen.bscroll; + else + screen.cur.row += row; + } + else if (row < 0) + { + if (screen.cur.row >= screen.tscroll + && (screen.cur.row + row) < screen.tscroll) + screen.cur.row = screen.tscroll; + else + screen.cur.row += row; + } + } + else + { + if (screen.flags & Screen_Relative) + { + /* relative origin mode */ + screen.cur.row = row + screen.tscroll; + min_it (screen.cur.row, screen.bscroll); + } + else + screen.cur.row = row; + } + + clamp_it (screen.cur.row, 0, nrow - 1); +} + +/* ------------------------------------------------------------------------- */ +/* + * direction should be UP or DN + */ +void +rxvt_term::scr_index (enum page_dirn direction) NOTHROW +{ + want_refresh = 1; + ZERO_SCROLLBACK (); + + screen.flags &= ~Screen_WrapNext; + + if ((screen.cur.row == screen.bscroll && direction == UP) + || (screen.cur.row == screen.tscroll && direction == DN)) + scr_scroll_text (screen.tscroll, screen.bscroll, direction); + else + screen.cur.row += direction; + + clamp_it (screen.cur.row, 0, nrow - 1); + selection_check (0); +} + +/* ------------------------------------------------------------------------- */ +/* + * Erase part or whole of a line + * XTERM_SEQ: Clear line to right: ESC [ 0 K + * XTERM_SEQ: Clear line to left : ESC [ 1 K + * XTERM_SEQ: Clear whole line : ESC [ 2 K + * extension: clear to right unless wrapped: ESC [ 3 K + */ +void +rxvt_term::scr_erase_line (int mode) NOTHROW +{ + unsigned int col, num; + + want_refresh = 1; + ZERO_SCROLLBACK (); + + selection_check (1); + + line_t &line = ROW(screen.cur.row); + + line.touch (); + line.is_longer (0); + + switch (mode) + { + case 3: + if (screen.flags & Screen_WrapNext) + return; + + /* fall through */ + + case 0: /* erase to end of line */ + col = screen.cur.col; + num = ncol - col; + min_it (line.l, col); + + if (ROWCOL_IN_ROW_AT_OR_AFTER (selection.beg, screen.cur) + || ROWCOL_IN_ROW_AT_OR_AFTER (selection.end, screen.cur)) + CLEAR_SELECTION (); + break; + + case 1: /* erase to beginning of line */ + col = 0; + num = screen.cur.col + 1; + + if (ROWCOL_IN_ROW_AT_OR_BEFORE (selection.beg, screen.cur) + || ROWCOL_IN_ROW_AT_OR_BEFORE (selection.end, screen.cur)) + CLEAR_SELECTION (); + break; + + case 2: /* erase whole line */ + col = 0; + num = ncol; + line.l = 0; + if (selection.beg.row <= screen.cur.row + && selection.end.row >= screen.cur.row) + CLEAR_SELECTION (); + break; + default: + return; + } + + scr_blank_line (line, col, num, rstyle); +} + +/* ------------------------------------------------------------------------- */ +/* + * Erase part or whole of the screen + * XTERM_SEQ: Clear screen after cursor : ESC [ 0 J + * XTERM_SEQ: Clear screen before cursor: ESC [ 1 J + * XTERM_SEQ: Clear whole screen : ESC [ 2 J + */ +void +rxvt_term::scr_erase_screen (int mode) NOTHROW +{ + int num; + int32_t row; + rend_t ren; + XGCValues gcvalue; + + want_refresh = 1; + ZERO_SCROLLBACK (); + + switch (mode) + { + case 0: /* erase to end of screen */ + scr_erase_line (0); + row = screen.cur.row + 1; /* possible OOB */ + num = nrow - row; + break; + case 1: /* erase to beginning of screen */ + scr_erase_line (1); + row = 0; + num = screen.cur.row; + break; + case 2: /* erase whole screen */ + row = 0; + num = nrow; + break; + default: + return; + } + + if (selection.op && current_screen == selection.screen + && ((selection.beg.row >= row && selection.beg.row <= row + num) + || (selection.end.row >= row + && selection.end.row <= row + num))) + CLEAR_SELECTION (); + + if (row >= nrow) /* Out Of Bounds */ + return; + + min_it (num, nrow - row); + + if (rstyle & (RS_Blink | RS_RVid | RS_Uline)) + ren = (rend_t) ~RS_None; + else if (GET_BASEBG (rstyle) == Color_bg) + { + ren = DEFAULT_RSTYLE; + + if (mapped) + XClearArea (dpy, vt, 0, + Row2Pixel (row - view_start), (unsigned int)vt_width, + (unsigned int)Height2Pixel (num), False); + } + else + { + ren = rstyle & (RS_fgMask | RS_bgMask); + + if (mapped) + { + gcvalue.foreground = pix_colors[bgcolor_of (rstyle)]; + XChangeGC (dpy, gc, GCForeground, &gcvalue); + XFillRectangle (dpy, vt, gc, + 0, Row2Pixel (row - view_start), + (unsigned int)vt_width, + (unsigned int)Height2Pixel (num)); + gcvalue.foreground = pix_colors[Color_fg]; + XChangeGC (dpy, gc, GCForeground, &gcvalue); + } + } + + for (; num--; row++) + { + scr_blank_screen_mem (ROW(row), rstyle); + + if (row - view_start < nrow) + scr_blank_line (drawn_buf [row - view_start], 0, ncol, ren); + } +} + +#if !ENABLE_MINIMAL +void +rxvt_term::scr_erase_savelines () NOTHROW +{ + want_refresh = 1; + ZERO_SCROLLBACK (); + + top_row = 0; +} +#endif + +/* ------------------------------------------------------------------------- */ +/* + * Fill the screen with `E's + * XTERM_SEQ: Screen Alignment Test: ESC # 8 + */ +void ecb_cold +rxvt_term::scr_E () NOTHROW +{ + rend_t fs; + + want_refresh = 1; + ZERO_SCROLLBACK (); + + num_scr_allow = 0; + + row_col_t pos; + pos.row = pos.col = 0; + if (ROWCOL_IS_AFTER (selection.end, pos)) + CLEAR_SELECTION (); + + fs = SET_FONT (rstyle, FONTSET (rstyle)->find_font ('E')); + for (int row = nrow; row--; ) + { + line_t &line = ROW(row); + + fill_text (line.t, 'E', ncol); + rend_t *r1 = line.r; + + for (int j = ncol; j--; ) + *r1++ = fs; + + line.is_longer (0); + line.touch (ncol); + } +} + +/* ------------------------------------------------------------------------- */ +/* + * Insert/Delete lines + */ +void +rxvt_term::scr_insdel_lines (int count, int insdel) NOTHROW +{ + int end; + + ZERO_SCROLLBACK (); + + selection_check (1); + + if (screen.cur.row > screen.bscroll) + return; + + end = screen.bscroll - screen.cur.row + 1; + if (count > end) + { + if (insdel == DELETE) + return; + else if (insdel == INSERT) + count = end; + } + + scr_do_wrap (); + + scr_scroll_text (screen.cur.row, screen.bscroll, insdel * count); +} + +/* ------------------------------------------------------------------------- */ +/* + * Insert/Delete characters from the current position + */ +void +rxvt_term::scr_insdel_chars (int count, int insdel) NOTHROW +{ + want_refresh = 1; + ZERO_SCROLLBACK (); + + if (count <= 0) + return; + + scr_do_wrap (); + + selection_check (1); + min_it (count, ncol - screen.cur.col); + + int row = screen.cur.row; + + line_t *line = &ROW(row); + + line->touch (); + line->is_longer (0); + + // nuke wide spanning the start + if (line->t[screen.cur.col] == NOCHAR) + scr_kill_char (*line, screen.cur.col); + + switch (insdel) + { + case INSERT: + line->l = min (line->l + count, ncol); + + if (line->t[screen.cur.col] == NOCHAR) + scr_kill_char (*line, screen.cur.col); + + for (int col = ncol - 1; (col - count) >= screen.cur.col; col--) + { + line->t[col] = line->t[col - count]; + line->r[col] = line->r[col - count]; + } + + if (selection.op && current_screen == selection.screen + && ROWCOL_IN_ROW_AT_OR_AFTER (selection.beg, screen.cur)) + { + if (selection.end.row != screen.cur.row + || (selection.end.col + count >= ncol)) + CLEAR_SELECTION (); + else + { + /* shift selection */ + selection.beg.col += count; + selection.mark.col += count; /* XXX: yes? */ + selection.end.col += count; + } + } + + scr_blank_line (*line, screen.cur.col, count, rstyle); + break; + + case ERASE: + screen.cur.col += count; /* don't worry if > ncol */ + selection_check (1); + screen.cur.col -= count; + + // nuke wide char after the end + if (screen.cur.col + count < ncol && line->t[screen.cur.col + count] == NOCHAR) + scr_kill_char (*line, screen.cur.col + count); + + scr_blank_line (*line, screen.cur.col, count, rstyle); + break; + + case DELETE: + line->l = max (line->l - count, 0); + + // nuke wide char spanning the end + if (screen.cur.col + count < ncol && line->t[screen.cur.col + count] == NOCHAR) + scr_kill_char (*line, screen.cur.col + count); + + for (int col = screen.cur.col; (col + count) < ncol; col++) + { + line->t[col] = line->t[col + count]; + line->r[col] = line->r[col + count]; + } + + scr_blank_line (*line, ncol - count, count, rstyle); + + if (selection.op && current_screen == selection.screen + && ROWCOL_IN_ROW_AT_OR_AFTER (selection.beg, screen.cur)) + { + if (selection.end.row != screen.cur.row + || (screen.cur.col >= selection.beg.col - count) + || selection.end.col >= ncol) + CLEAR_SELECTION (); + else + { + /* shift selection */ + selection.beg.col -= count; + selection.mark.col -= count; /* XXX: yes? */ + selection.end.col -= count; + } + } + + break; + } +} + +/* ------------------------------------------------------------------------- */ +/* + * Set the scrolling region + * XTERM_SEQ: Set region - inclusive: ESC [ ; r + */ +void ecb_cold +rxvt_term::scr_scroll_region (int top, int bot) NOTHROW +{ + max_it (top, 0); + min_it (bot, nrow - 1); + + if (top > bot) + return; + + screen.tscroll = top; + screen.bscroll = bot; + scr_gotorc (0, 0, 0); +} + +/* ------------------------------------------------------------------------- */ +/* + * Make the cursor visible/invisible + * XTERM_SEQ: Make cursor visible : ESC [ ? 25 h + * XTERM_SEQ: Make cursor invisible: ESC [ ? 25 l + */ +void ecb_cold +rxvt_term::scr_cursor_visible (int mode) NOTHROW +{ + want_refresh = 1; + + if (mode) + screen.flags |= Screen_VisibleCursor; + else + screen.flags &= ~Screen_VisibleCursor; +} + +/* ------------------------------------------------------------------------- */ +/* + * Set/unset automatic wrapping + * XTERM_SEQ: Set Wraparound : ESC [ ? 7 h + * XTERM_SEQ: Unset Wraparound: ESC [ ? 7 l + */ +void ecb_cold +rxvt_term::scr_autowrap (int mode) NOTHROW +{ + if (mode) + screen.flags |= Screen_Autowrap; + else + screen.flags &= ~(Screen_Autowrap | Screen_WrapNext); +} + +/* ------------------------------------------------------------------------- */ +/* + * Set/unset margin origin mode + * Absolute mode: line numbers are counted relative to top margin of screen + * and the cursor can be moved outside the scrolling region. + * Relative mode: line numbers are relative to top margin of scrolling region + * and the cursor cannot be moved outside. + * XTERM_SEQ: Set Absolute: ESC [ ? 6 h + * XTERM_SEQ: Set Relative: ESC [ ? 6 l + */ +void ecb_cold +rxvt_term::scr_relative_origin (int mode) NOTHROW +{ + if (mode) + screen.flags |= Screen_Relative; + else + screen.flags &= ~Screen_Relative; + + scr_gotorc (0, 0, 0); +} + +/* ------------------------------------------------------------------------- */ +/* + * Set insert/replace mode + * XTERM_SEQ: Set Insert mode : ESC [ ? 4 h + * XTERM_SEQ: Set Replace mode: ESC [ ? 4 l + */ +void ecb_cold +rxvt_term::scr_insert_mode (int mode) NOTHROW +{ + if (mode) + screen.flags |= Screen_Insert; + else + screen.flags &= ~Screen_Insert; +} + +/* ------------------------------------------------------------------------- */ +/* + * Set/Unset tabs + * XTERM_SEQ: Set tab at current column : ESC H + * XTERM_SEQ: Clear tab at current column: ESC [ 0 g + * XTERM_SEQ: Clear all tabs : ESC [ 3 g + */ +void ecb_cold +rxvt_term::scr_set_tab (int mode) NOTHROW +{ + if (mode < 0) + memset (tabs, 0, ncol); + else if (screen.cur.col < ncol) + tabs [screen.cur.col] = !!mode; +} + +/* ------------------------------------------------------------------------- */ +/* + * Set reverse/normal video + * XTERM_SEQ: Reverse video: ESC [ ? 5 h + * XTERM_SEQ: Normal video : ESC [ ? 5 l + */ +void +rxvt_term::scr_rvideo_mode (bool on) NOTHROW +{ + rvideo_mode = on; + +#ifndef NO_BELL + on ^= rvideo_bell; +#endif + + if (rvideo_state != on) + { + rvideo_state = on; + +#if OFF_FOCUS_FADING + if (rs[Rs_fade]) + { + ::swap (pix_colors_focused[Color_fg], pix_colors_focused[Color_bg]); + ::swap (pix_colors_unfocused[Color_fg], pix_colors_unfocused[Color_bg]); + } + else +#endif + ::swap (pix_colors[Color_fg], pix_colors[Color_bg]); +#ifdef HAVE_IMG + if (bg_img == 0) +#endif + XSetWindowBackground (dpy, vt, pix_colors[Color_bg]); + + XGCValues gcvalue; + gcvalue.foreground = pix_colors[Color_fg]; + gcvalue.background = pix_colors[Color_bg]; + XChangeGC (dpy, gc, GCBackground | GCForeground, &gcvalue); + + scr_clear (); + scr_touch (true); + } +} + +/* ------------------------------------------------------------------------- */ +/* + * Report current cursor position + * XTERM_SEQ: Report position: ESC [ 6 n + */ +void ecb_cold +rxvt_term::scr_report_position () NOTHROW +{ + tt_printf ("\033[%d;%dR", screen.cur.row + 1, screen.cur.col + 1); +} + +/* ------------------------------------------------------------------------- * + * FONTS * + * ------------------------------------------------------------------------- */ + +/* + * Set font style + */ +void ecb_cold +rxvt_term::set_font_style () NOTHROW +{ +#if 0 + switch (charsets [screen.charset]) + { + case '0': /* DEC Special Character & Line Drawing Set */ + break; + case 'A': /* United Kingdom (UK) */ + break; + case 'B': /* United States (USASCII) */ + break; + case '<': /* Multinational character set */ + break; + case '5': /* Finnish character set */ + break; + case 'C': /* Finnish character set */ + break; + case 'K': /* German character set */ + break; + } +#endif +} + +/* ------------------------------------------------------------------------- */ +/* + * Choose a font + * XTERM_SEQ: Invoke G0 character set: CTRL-O + * XTERM_SEQ: Invoke G1 character set: CTRL-N + * XTERM_SEQ: Invoke G2 character set: ESC N + * XTERM_SEQ: Invoke G3 character set: ESC O + */ +void ecb_cold +rxvt_term::scr_charset_choose (int set) NOTHROW +{ + screen.charset = set; + set_font_style (); +} + +/* ------------------------------------------------------------------------- */ +/* + * Set a font + * XTERM_SEQ: Set G0 character set: ESC ( + * XTERM_SEQ: Set G1 character set: ESC ) + * XTERM_SEQ: Set G2 character set: ESC * + * XTERM_SEQ: Set G3 character set: ESC + + * See set_font_style for possible values for + */ +void +rxvt_term::scr_charset_set (int set, unsigned int ch) NOTHROW +{ + charsets[set] = (unsigned char)ch; + set_font_style (); +} + + +/* ------------------------------------------------------------------------- * + * MAJOR SCREEN MANIPULATION * + * ------------------------------------------------------------------------- */ + +/* + * refresh matching text. + */ +bool ecb_cold +rxvt_term::scr_refresh_rend (rend_t mask, rend_t value) NOTHROW +{ + bool found = false; + + for (int i = 0; i < nrow; i++) + { + rend_t *drp = drawn_buf[i].r; + + for (int col = 0; col < ncol; col++, drp++) + if ((*drp & mask) == value) + { + found = true; + *drp = ~value; + } + } + + return found; +} + +/* + * Refresh an area + */ +enum { + PART_BEG = 0, + PART_END, + RC_COUNT +}; + +void ecb_hot +rxvt_term::scr_expose (int x, int y, int ewidth, int eheight, bool refresh) NOTHROW +{ + int i; + row_col_t rc[RC_COUNT]; + + if (!drawn_buf) /* sanity check */ + return; + +#ifndef NO_SLOW_LINK_SUPPORT + if (refresh_type == FAST_REFRESH && !display->is_local) + { + y = 0; + eheight = height; + } +#endif + + /* round down */ + rc[PART_BEG].col = Pixel2Col (x); + rc[PART_BEG].row = Pixel2Row (y); + /* round up */ + rc[PART_END].col = Pixel2Width (x + ewidth + fwidth - 1); + rc[PART_END].row = Pixel2Row (y + eheight + fheight - 1); + + /* sanity checks */ + for (i = PART_BEG; i < RC_COUNT; i++) + { + min_it (rc[i].col, ncol - 1); + min_it (rc[i].row, nrow - 1); + } + + for (i = rc[PART_BEG].row; i <= rc[PART_END].row; i++) + fill_text (&drawn_buf[i].t[rc[PART_BEG].col], 0, rc[PART_END].col - rc[PART_BEG].col + 1); + + num_scr_allow = 0; + + if (refresh) + scr_refresh (); +} + +/* ------------------------------------------------------------------------- */ +/* + * Refresh the entire screen + */ +void +rxvt_term::scr_touch (bool refresh) NOTHROW +{ + scr_expose (0, 0, vt_width, vt_height, refresh); +} + +/* ------------------------------------------------------------------------- */ +/* + * Move the display so that the line represented by scrollbar value Y is at + * the top of the screen + */ +void +rxvt_term::scr_move_to (int y, int len) NOTHROW +{ + // lerp (y, 0, len, top_row, nrow - 1) + scr_changeview (top_row + (nrow - 1 - top_row) * y / len); +} + +/* ------------------------------------------------------------------------- */ +/* + * Page the screen up/down nlines + * direction should be UP or DN + */ +bool +rxvt_term::scr_page (int nlines) NOTHROW +{ + return scr_changeview (view_start - nlines); +} + +bool +rxvt_term::scr_changeview (int new_view_start) NOTHROW +{ + clamp_it (new_view_start, top_row, 0); + + if (new_view_start == view_start) + return false; + + num_scr += new_view_start - view_start; + view_start = new_view_start; + want_refresh = 1; + + HOOK_INVOKE ((this, HOOK_VIEW_CHANGE, DT_INT, view_start, DT_END)); + + return true; +} + +#ifndef NO_BELL +void +rxvt_term::bell_cb (ev::timer &w, int revents) +{ + rvideo_bell = false; + scr_rvideo_mode (rvideo_mode); + refresh_check (); +} +#endif + +/* ------------------------------------------------------------------------- */ +void +rxvt_term::scr_bell () NOTHROW +{ +#ifndef NO_BELL + +# ifndef NO_MAPALERT +# ifdef MAPALERT_OPTION + if (option (Opt_mapAlert)) +# endif + XMapWindow (dpy, parent); +# endif + +# if ENABLE_FRILLS + if (option (Opt_urgentOnBell)) + set_urgency (1); +# endif + + if (option (Opt_visualBell)) + { + rvideo_bell = true; + scr_rvideo_mode (rvideo_mode); + flush (); + + bell_ev.start (VISUAL_BELL_DURATION); + } + else + XBell (dpy, 0); + HOOK_INVOKE ((this, HOOK_BELL, DT_END)); +#endif +} + +/* ------------------------------------------------------------------------- */ +void ecb_cold +rxvt_term::scr_printscreen (int fullhist) NOTHROW +{ +#ifdef PRINTPIPE + int nrows, row_start; + FILE *fd = popen_printer (); + + if (!fd) + return; + + if (fullhist) + { + nrows = nrow - top_row; + row_start = top_row; + } + else + { + nrows = nrow; + row_start = view_start; + } + + wctomb (0, 0); + + for (int r1 = row_start; r1 < row_start + nrows; r1++) + { + text_t *tp = ROW(r1).t; + int len = ROW(r1).l; + + for (int i = len >= 0 ? len : ncol - 1; i--; ) //TODO//FIXME//LEN + { + char mb[MB_LEN_MAX]; + text_t t = *tp++; + if (t == NOCHAR) + continue; + + len = wctomb (mb, t); + + if (len <= 0) + { + mb[0] = ' '; + len = 1; + } + + fwrite (mb, 1, len, fd); + } + + fputc ('\n', fd); + } + + pclose_printer (fd); +#endif +} + +/* ------------------------------------------------------------------------- */ +/* + * Refresh the screen + * drawn_text/drawn_rend contain the screen information before the update. + * screen.text/screen.rend contain what the screen will change to. + */ +void ecb_hot +rxvt_term::scr_refresh () NOTHROW +{ + int16_t col, row, /* column/row we're processing */ + ocrow; /* old cursor row */ + int i; /* tmp */ + rend_t ccol1, /* Cursor colour */ + ccol2; /* Cursor colour2 */ + rend_t cur_rend; + int cur_col; + int cursorwidth; + + want_refresh = 0; /* screen is current */ + + if (refresh_type == NO_REFRESH || !mapped) + return; + + /* + * A: set up vars + */ + refresh_count = 0; + + unsigned int old_screen_flags = screen.flags; + bool have_bg = 0; +#ifdef HAVE_IMG + have_bg = bg_img != 0; +#endif + ocrow = oldcursor.row; /* is there an old outline cursor on screen? */ + + /* + * B: reverse any characters which are selected + */ + scr_reverse_selection (); + + HOOK_INVOKE ((this, HOOK_REFRESH_BEGIN, DT_END)); +#if ENABLE_OVERLAY + scr_swap_overlay (); +#endif + + bool showcursor = screen.flags & Screen_VisibleCursor; + + /* + * C: set the cursor character (s) + */ + { +#ifdef CURSOR_BLINK + if (hidden_cursor) + showcursor = 0; +#endif + + if (showcursor) + { + int col = screen.cur.col; + + while (col && ROW(screen.cur.row).t[col] == NOCHAR) + col--; + + cursorwidth = 1; + while (col + cursorwidth < ncol + && ROW(screen.cur.row).t[col + cursorwidth] == NOCHAR) + cursorwidth++; + + cur_rend = ROW(screen.cur.row).r[col]; + cur_col = col; + +#ifndef NO_CURSORCOLOR + if (ISSET_PIXCOLOR (Color_cursor)) + ccol1 = Color_cursor; + else +#endif +#ifdef CURSOR_COLOR_IS_RENDITION_COLOR + ccol1 = fgcolor_of (rstyle); +#else + ccol1 = Color_fg; +#endif + +#ifndef NO_CURSORCOLOR + if (ISSET_PIXCOLOR (Color_cursor2)) + ccol2 = Color_cursor2; + else +#endif +#ifdef CURSOR_COLOR_IS_RENDITION_COLOR + ccol2 = bgcolor_of (rstyle); +#else + ccol2 = Color_bg; +#endif + + if (focus && cursor_type == 0) + { + rend_t rend = cur_rend; + + rend ^= RS_RVid; + rend = SET_FGCOLOR (rend, ccol1); + rend = SET_BGCOLOR (rend, ccol2); + + scr_set_char_rend (ROW(screen.cur.row), cur_col, rend); + } + } + + /* make sure no outline cursor is left around */ + if (ocrow != -1 && ocrow < nrow && oldcursor.col < ncol) + drawn_buf[ocrow].r[oldcursor.col] ^= (RS_RVid | RS_Uline); + + // save the current cursor coordinates if the cursor is visible + // and either the window is unfocused or the cursor style is + // underline or vertical bar, so as to clear the outline cursor in + // the next refresh if the cursor moves or becomes invisible + if (showcursor && (!focus || cursor_type != 0) && screen.cur.row - view_start < nrow) + { + oldcursor.row = screen.cur.row - view_start; + oldcursor.col = screen.cur.col; + } + else + oldcursor.row = -1; + } + +#ifndef NO_SLOW_LINK_SUPPORT + /* + * D: CopyArea pass - very useful for slower links + * This has been deliberately kept simple. + */ + if (!display->is_local + && refresh_type == FAST_REFRESH && num_scr_allow && num_scr + && abs (num_scr) < nrow && !have_bg) + { + int16_t nits; + int i = num_scr; + int j; + int len, wlen; + + j = nrow; + wlen = len = -1; + row = i > 0 ? 0 : j - 1; + + for (; j-- >= 0; row += (i > 0 ? 1 : -1)) + { + if (row + i >= 0 && row + i < nrow && row + i != ocrow) + { + line_t s = ROW(view_start + row); + line_t d = drawn_buf[row]; + line_t d2 = drawn_buf[row + i]; + + for (nits = 0, col = ncol; col--; ) + if (s.t[col] != d2.t[col] || s.r[col] != d2.r[col]) + nits--; + else if (s.t[col] != d.t[col] || s.r[col] != d.r[col]) + nits++; + + if (nits > 8) /* XXX: arbitrary choice */ + { + for (col = ncol; col--; ) + { + *d.t++ = *d2.t++; + *d.r++ = *d2.r++; + } + + if (len == -1) + len = row; + + wlen = row; + continue; + } + } + + if (len >= 0) + { + /* also comes here at end if needed because of >= above */ + if (wlen < len) + ::swap (wlen, len); + + XGCValues gcv; + + gcv.graphics_exposures = 1; XChangeGC (dpy, gc, GCGraphicsExposures, &gcv); + XCopyArea (dpy, vt, vt, + gc, 0, Row2Pixel (len + i), + (unsigned int)this->width, + (unsigned int)Height2Pixel (wlen - len + 1), + 0, Row2Pixel (len)); + gcv.graphics_exposures = 0; XChangeGC (dpy, gc, GCGraphicsExposures, &gcv); + + len = -1; + } + } + } +#endif + + /* + * E: main pass across every character + */ + for (row = 0; row < nrow; row++) + { + text_t *stp = ROW(view_start + row).t; + rend_t *srp = ROW(view_start + row).r; + text_t *dtp = drawn_buf[row].t; + rend_t *drp = drawn_buf[row].r; + + /* + * E2: OK, now the real pass + */ + int ypixel = (int)Row2Pixel (row); + + for (col = 0; col < ncol; col++) + { + /* compare new text with old - if exactly the same then continue */ + if (stp[col] == dtp[col] && RS_SAME (srp[col], drp[col])) + continue; + + // redraw one or more characters + + // seek to the beginning of wide characters + while (ecb_unlikely (stp[col] == NOCHAR && col > 0)) + --col; + + rend_t rend = srp[col]; /* screen rendition (target rendition) */ + text_t *text = stp + col; + int count = 1; + + dtp[col] = stp[col]; + drp[col] = rend; + + int xpixel = Col2Pixel (col); + + for (i = 0; ++col < ncol; ) + { + if (stp[col] == NOCHAR) + { + dtp[col] = stp[col]; + drp[col] = srp[col]; + + count++; + i++; + + continue; + } + + if (!RS_SAME (rend, srp[col])) + break; + + count++; + + if (stp[col] != dtp[col] + || !RS_SAME (srp[col], drp[col])) + { + if (have_bg && (i++ > count / 2)) + break; + + dtp[col] = stp[col]; + drp[col] = rend; + i = 0; + } + else if (have_bg || (stp[col] != ' ' && ++i >= 16)) + break; + } + + col--; /* went one too far. move back */ + count -= i; /* dump any matching trailing chars */ + + // sometimes we optimize away the trailing NOCHAR's, add them back + while (ecb_unlikely (i && text[count] == NOCHAR)) + count++, i--; + + /* + * Determine the attributes for the string + */ + int fore = fgcolor_of (rend); // desired foreground + int back = bgcolor_of (rend); // desired background + + // only do special processing if any attributes are set, which is unlikely + if (ecb_unlikely (rend & (RS_baseattrMask | RS_Careful | RS_Sel))) + { + bool invert = rend & RS_RVid; + +#ifndef NO_BOLD_UNDERLINE_REVERS