diff --git a/LICENCE b/LICENCE index 7c49ceb3..30b1fe2b 100644 --- a/LICENCE +++ b/LICENCE @@ -3,7 +3,8 @@ PuTTY is copyright 1997-2017 Simon Tatham. Portions copyright Robert de Bath, Joris van Rantwijk, Delian Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus -Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A. +Kuhn, Colin Watson, Christopher Staite, Lorenz Diener, Christian +Brabandt, and CORE SDI S.A. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files diff --git a/fuzzterm.c b/fuzzterm.c index 15b5d635..480dca37 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -45,7 +45,7 @@ int from_backend(void *frontend, int is_stderr, const char *data, int len) void request_resize(void *frontend, int x, int y) { } void do_text(Context ctx, int x, int y, wchar_t * text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour tc) { int i; @@ -56,7 +56,7 @@ void do_text(Context ctx, int x, int y, wchar_t * text, int len, printf("\n"); } void do_cursor(Context ctx, int x, int y, wchar_t * text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour tc) { int i; diff --git a/putty.h b/putty.h index fd2d0250..61d6278d 100644 --- a/putty.h +++ b/putty.h @@ -592,12 +592,36 @@ void prompt_ensure_result_size(prompt_t *pr, int len); /* Burn the evidence. (Assumes _all_ strings want free()ing.) */ void free_prompts(prompts_t *p); +/* + * Data type definitions for true-colour terminal display. + * 'optionalrgb' describes a single RGB colour, which overrides the + * other colour settings if 'enabled' is nonzero, and is ignored + * otherwise. 'truecolour' contains a pair of those for foreground and + * background. + */ +typedef struct optionalrgb { + unsigned char enabled; + unsigned char r, g, b; +} optionalrgb; +extern const optionalrgb optionalrgb_none; +typedef struct truecolour { + optionalrgb fg, bg; +} truecolour; +#define optionalrgb_equal(r1,r2) ( \ + (r1).enabled==(r2).enabled && \ + (r1).r==(r2).r && (r1).g==(r2).g && (r1).b==(r2).b) +#define truecolour_equal(c1,c2) ( \ + optionalrgb_equal((c1).fg, (c2).fg) && \ + optionalrgb_equal((c1).bg, (c2).bg)) + /* * Exports from the front end. */ void request_resize(void *frontend, int, int); -void do_text(Context, int, int, wchar_t *, int, unsigned long, int); -void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int); +void do_text(Context, int, int, wchar_t *, int, unsigned long, int, + truecolour); +void do_cursor(Context, int, int, wchar_t *, int, unsigned long, int, + truecolour); int char_width(Context ctx, int uc); #ifdef OPTIMISE_SCROLL void do_scroll(Context, int, int, int); diff --git a/terminal.c b/terminal.c index f7bcbb95..e597d472 100644 --- a/terminal.c +++ b/terminal.c @@ -108,6 +108,7 @@ static void update_sbar(Terminal *); static void deselect(Terminal *); static void term_print_finish(Terminal *); static void scroll(Terminal *, int, int, int, int); +static void parse_optionalrgb(optionalrgb *out, unsigned *values); #ifdef OPTIMISE_SCROLL static void scroll_display(Terminal *, int, int, int); #endif /* OPTIMISE_SCROLL */ @@ -283,6 +284,8 @@ static int termchars_equal_override(termchar *a, termchar *b, unsigned long bchr, unsigned long battr) { /* FULL-TERMCHAR */ + if (!truecolour_equal(a->truecolour, b->truecolour)) + return FALSE; if (a->chr != bchr) return FALSE; if ((a->attr &~ DATTR_MASK) != (battr &~ DATTR_MASK)) @@ -607,6 +610,24 @@ static void makeliteral_attr(struct buf *b, termchar *c, unsigned long *state) add(b, (unsigned char)(attr & 0xFF)); } } +static void makeliteral_truecolour(struct buf *b, termchar *c, unsigned long *state) +{ + /* + * Put the used parts of the colour info into the buffer. + */ + add(b, ((c->truecolour.fg.enabled ? 1 : 0) | + (c->truecolour.bg.enabled ? 2 : 0))); + if (c->truecolour.fg.enabled) { + add(b, c->truecolour.fg.r); + add(b, c->truecolour.fg.g); + add(b, c->truecolour.fg.b); + } + if (c->truecolour.bg.enabled) { + add(b, c->truecolour.bg.r); + add(b, c->truecolour.bg.g); + add(b, c->truecolour.bg.b); + } +} static void makeliteral_cc(struct buf *b, termchar *c, unsigned long *state) { /* @@ -681,6 +702,7 @@ static unsigned char *compressline(termline *ldata) */ makerle(b, ldata, makeliteral_chr); makerle(b, ldata, makeliteral_attr); + makerle(b, ldata, makeliteral_truecolour); makerle(b, ldata, makeliteral_cc); /* @@ -826,6 +848,29 @@ static void readliteral_attr(struct buf *b, termchar *c, termline *ldata, c->attr = attr; } +static void readliteral_truecolour(struct buf *b, termchar *c, termline *ldata, + unsigned long *state) +{ + int flags = get(b); + + if (flags & 1) { + c->truecolour.fg.enabled = TRUE; + c->truecolour.fg.r = get(b); + c->truecolour.fg.g = get(b); + c->truecolour.fg.b = get(b); + } else { + c->truecolour.fg = optionalrgb_none; + } + + if (flags & 2) { + c->truecolour.bg.enabled = TRUE; + c->truecolour.bg.r = get(b); + c->truecolour.bg.g = get(b); + c->truecolour.bg.b = get(b); + } else { + c->truecolour.bg = optionalrgb_none; + } +} static void readliteral_cc(struct buf *b, termchar *c, termline *ldata, unsigned long *state) { @@ -899,6 +944,7 @@ static termline *decompressline(unsigned char *data, int *bytes_used) */ readrle(b, ldata, readliteral_chr); readrle(b, ldata, readliteral_attr); + readrle(b, ldata, readliteral_truecolour); readrle(b, ldata, readliteral_cc); /* Return the number of bytes read, for diagnostic purposes. */ @@ -1570,6 +1616,8 @@ void term_clrsb(Terminal *term) update_sbar(term); } +const optionalrgb optionalrgb_none = {0, 0, 0, 0}; + /* * Initialise the terminal. */ @@ -1646,6 +1694,8 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, term->basic_erase_char.chr = CSET_ASCII | ' '; term->basic_erase_char.attr = ATTR_DEFAULT; term->basic_erase_char.cc_next = 0; + term->basic_erase_char.truecolour.fg = optionalrgb_none; + term->basic_erase_char.truecolour.bg = optionalrgb_none; term->erase_char = term->basic_erase_char; return term; @@ -3212,6 +3262,8 @@ static void term_out(Terminal *term) clear_cc(cline, term->curs.x); cline->chars[term->curs.x].chr = c; cline->chars[term->curs.x].attr = term->curr_attr; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; term->curs.x++; @@ -3219,6 +3271,8 @@ static void term_out(Terminal *term) clear_cc(cline, term->curs.x); cline->chars[term->curs.x].chr = UCSWIDE; cline->chars[term->curs.x].attr = term->curr_attr; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; break; case 1: @@ -3229,6 +3283,8 @@ static void term_out(Terminal *term) clear_cc(cline, term->curs.x); cline->chars[term->curs.x].chr = c; cline->chars[term->curs.x].attr = term->curr_attr; + cline->chars[term->curs.x].truecolour = + term->curr_truecolour; break; case 0: @@ -3799,6 +3855,8 @@ static void term_out(Terminal *term) switch (def(term->esc_args[i], 0)) { case 0: /* restore defaults */ term->curr_attr = term->default_attr; + term->curr_truecolour = + term->basic_erase_char.truecolour; break; case 1: /* enable bold */ compatibility(VT100AVO); @@ -3860,6 +3918,7 @@ static void term_out(Terminal *term) case 36: case 37: /* foreground */ + term->curr_truecolour.fg.enabled = FALSE; term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= (term->esc_args[i] - 30)<curr_truecolour.fg.enabled = FALSE; term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= ((term->esc_args[i] - 90 + 8) << ATTR_FGSHIFT); break; case 39: /* default-foreground */ + term->curr_truecolour.fg.enabled = FALSE; term->curr_attr &= ~ATTR_FGMASK; term->curr_attr |= ATTR_DEFFG; break; @@ -3891,6 +3952,7 @@ static void term_out(Terminal *term) case 46: case 47: /* background */ + term->curr_truecolour.bg.enabled = FALSE; term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= (term->esc_args[i] - 40)<curr_truecolour.bg.enabled = FALSE; term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= ((term->esc_args[i] - 100 + 8) << ATTR_BGSHIFT); break; case 49: /* default-background */ + term->curr_truecolour.bg.enabled = FALSE; term->curr_attr &= ~ATTR_BGMASK; term->curr_attr |= ATTR_DEFBG; break; - case 38: /* xterm 256-colour mode */ + + /* + * 256-colour and true-colour + * sequences. A 256-colour + * foreground is selected by a + * sequence of 3 arguments in the + * form 38;5;n, where n is in the + * range 0-255. A true-colour RGB + * triple is selected by 5 args of + * the form 38;2;r;g;b. Replacing + * the initial 38 with 48 in both + * cases selects the same colour + * as the background. + */ + case 38: if (i+2 < term->esc_nargs && term->esc_args[i+1] == 5) { term->curr_attr &= ~ATTR_FGMASK; @@ -3921,9 +3999,16 @@ static void term_out(Terminal *term) ((term->esc_args[i+2] & 0xFF) << ATTR_FGSHIFT); i += 2; + } + if (i + 4 < term->esc_nargs && + term->esc_args[i + 1] == 2) { + parse_optionalrgb( + &term->curr_truecolour.fg, + term->esc_args + (i+2)); + i += 4; } break; - case 48: /* xterm 256-colour mode */ + case 48: if (i+2 < term->esc_nargs && term->esc_args[i+1] == 5) { term->curr_attr &= ~ATTR_BGMASK; @@ -3932,6 +4017,13 @@ static void term_out(Terminal *term) << ATTR_BGSHIFT); i += 2; } + if (i + 4 < term->esc_nargs && + term->esc_args[i+1] == 2) { + parse_optionalrgb( + &term->curr_truecolour.bg, + term->esc_args + (i+2)); + i += 4; + } break; } } @@ -4733,6 +4825,19 @@ static void term_out(Terminal *term) logflush(term->logctx); } +/* + * Small subroutine to parse three consecutive escape-sequence + * arguments representing a true-colour RGB triple into an + * optionalrgb. + */ +static void parse_optionalrgb(optionalrgb *out, unsigned *values) +{ + out->enabled = TRUE; + out->r = values[0] < 256 ? values[0] : 0; + out->g = values[1] < 256 ? values[1] : 0; + out->b = values[2] < 256 ? values[2] : 0; +} + /* * To prevent having to run the reasonably tricky bidi algorithm * too many times, we maintain a cache of the last lineful of data @@ -5035,6 +5140,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) int last_run_dirty = 0; int laststart, dirtyrect; int *backward; + truecolour tc; scrpos.y = i + term->disptop; ldata = lineptr(scrpos.y); @@ -5064,6 +5170,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) tattr = (tattr & ~(ATTR_FGMASK | ATTR_BGMASK)) | ATTR_DEFFG | ATTR_DEFBG; + tc = d->truecolour; if (!term->xterm_256_colour) { int colour; colour = (tattr & ATTR_FGMASK) >> ATTR_FGSHIFT; @@ -5131,6 +5238,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) /* FULL-TERMCHAR */ newline[j].attr = tattr; newline[j].chr = tchar; + newline[j].truecolour = tc; /* Combining characters are still read from lchars */ newline[j].cc_next = 0; } @@ -5181,6 +5289,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) term->disptext[i]->lattr); term->disptext[i]->lattr = ldata->lattr; + tc = term->erase_char.truecolour; for (j = 0; j < term->cols; j++) { unsigned long tattr, tchar; int break_run, do_copy; @@ -5194,6 +5303,9 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) break_run = ((tattr ^ attr) & term->attr_mask) != 0; + if (!truecolour_equal(newline[j].truecolour, tc)) + break_run = TRUE; + #ifdef USES_VTLINE_HACK /* Special hack for VT100 Linedraw glyphs */ if ((tchar >= 0x23BA && tchar <= 0x23BD) || @@ -5226,15 +5338,15 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) if (break_run) { if ((dirty_run || last_run_dirty) && ccount > 0) { - do_text(ctx, start, i, ch, ccount, attr, - ldata->lattr); + do_text(ctx, start, i, ch, ccount, attr, ldata->lattr, tc); if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) do_cursor(ctx, start, i, ch, ccount, attr, - ldata->lattr); + ldata->lattr, tc); } start = j; ccount = 0; attr = tattr; + tc = newline[j].truecolour; cset = CSET_OF(tchar); if (term->ucsdata->dbcs_screenfont) last_run_dirty = dirty_run; @@ -5303,6 +5415,7 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) copy_termchar(term->disptext[i], j, d); term->disptext[i]->chars[j].chr = tchar; term->disptext[i]->chars[j].attr = tattr; + term->disptext[i]->chars[j].truecolour = tc; if (start == j) term->disptext[i]->chars[j].attr |= DATTR_STARTRUN; } @@ -5324,11 +5437,9 @@ static void do_paint(Terminal *term, Context ctx, int may_optimise) } } if (dirty_run && ccount > 0) { - do_text(ctx, start, i, ch, ccount, attr, - ldata->lattr); + do_text(ctx, start, i, ch, ccount, attr, ldata->lattr, tc); if (attr & (TATTR_ACTCURS | TATTR_PASCURS)) - do_cursor(ctx, start, i, ch, ccount, attr, - ldata->lattr); + do_cursor(ctx, start, i, ch, ccount, attr, ldata->lattr, tc); } unlineptr(ldata); diff --git a/terminal.h b/terminal.h index 2ed9e6ef..21b8774a 100644 --- a/terminal.h +++ b/terminal.h @@ -40,6 +40,7 @@ struct termchar { */ unsigned long chr; unsigned long attr; + truecolour truecolour; /* * The cc_next field is used to link multiple termchars @@ -102,6 +103,7 @@ struct terminal_tag { #endif /* OPTIMISE_SCROLL */ int default_attr, curr_attr, save_attr; + truecolour curr_truecolour; termchar basic_erase_char, erase_char; bufchain inbuf; /* terminal input buffer */ diff --git a/testdata/colours.txt b/testdata/colours.txt index 2311eb83..34dff8a5 100644 --- a/testdata/colours.txt +++ b/testdata/colours.txt @@ -12,3 +12,4 @@ xterm 256: greys[48;5;236 a0a1a2a3a4a5a6a7 a8a9aaabacadaeaf b0b1b2b3b4b5b6b7 b8b9babbbcbdbebf c0c1c2c3c4c5c6c7 c8c9cacbcccdcecf d0d1d2d3d4d5d6d7 d8d9dadbdcdddedf e0e1e2e3e4e5e6e7 e8e9eaebecedeeef f0f1f2f3f4f5f6f7 f8f9fafbfcfdfeff +24-bit colour: SlateGrey OliveDrab goldenrod SaddleBrown DarkViolet (bg) diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 9e73181e..f49f92af 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -3029,6 +3029,24 @@ static void draw_set_colour(struct draw_ctx *dctx, int col) #endif } +static void draw_set_colour_rgb(struct draw_ctx *dctx, optionalrgb orgb) +{ +#ifdef DRAW_TEXT_GDK + if (dctx->uctx.type == DRAWTYPE_GDK) { + GdkColor color; + color.red = orgb.r * 256; + color.green = orgb.g * 256; + color.blue = orgb.b * 256; + gdk_gc_set_rgb_fg_color(dctx->uctx.u.gdk.gc, &color); + } +#endif +#ifdef DRAW_TEXT_CAIRO + if (dctx->uctx.type == DRAWTYPE_CAIRO) + cairo_set_source_rgb(dctx->uctx.u.cairo.cr, + orgb.r / 255.0, orgb.g / 255.0, orgb.b / 255.0); +#endif +} + static void draw_rectangle(struct draw_ctx *dctx, int filled, int x, int y, int w, int h) { @@ -3222,7 +3240,7 @@ static void draw_backing_rect(struct gui_data *inst) * We are allowed to fiddle with the contents of `text'. */ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { struct draw_ctx *dctx = (struct draw_ctx *)ctx; struct gui_data *inst = dctx->inst; @@ -3316,13 +3334,19 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, ((lattr & LATTR_MODE) == LATTR_BOT)); } - draw_set_colour(dctx, nbg); + if (truecolour.bg.enabled) + draw_set_colour_rgb(dctx, truecolour.bg); + else + draw_set_colour(dctx, nbg); draw_rectangle(dctx, TRUE, x*inst->font_width+inst->window_border, y*inst->font_height+inst->window_border, rlen*widefactor*inst->font_width, inst->font_height); - draw_set_colour(dctx, nfg); + if (truecolour.fg.enabled) + draw_set_colour_rgb(dctx, truecolour.fg); + else + draw_set_colour(dctx, nfg); if (ncombining > 1) { assert(len == 1); unifont_draw_combining(&dctx->uctx, inst->fonts[fontid], @@ -3362,13 +3386,13 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, } void do_text(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { struct draw_ctx *dctx = (struct draw_ctx *)ctx; struct gui_data *inst = dctx->inst; int widefactor; - do_text_internal(ctx, x, y, text, len, attr, lattr); + do_text_internal(ctx, x, y, text, len, attr, lattr, truecolour); if (attr & ATTR_WIDE) { widefactor = 2; @@ -3392,7 +3416,7 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, } void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { struct draw_ctx *dctx = (struct draw_ctx *)ctx; struct gui_data *inst = dctx->inst; @@ -3409,7 +3433,7 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, active = 1; } else active = 0; - do_text_internal(ctx, x, y, text, len, attr, lattr); + do_text_internal(ctx, x, y, text, len, attr, lattr, truecolour); if (attr & TATTR_COMBINING) len = 1; diff --git a/windows/window.c b/windows/window.c index 96600311..452eb771 100644 --- a/windows/window.c +++ b/windows/window.c @@ -3395,7 +3395,7 @@ static void sys_cursor_update(void) * We are allowed to fiddle with the contents of `text'. */ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { COLORREF fg, bg, t; int nfg, nbg, nfont; @@ -3522,8 +3522,16 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, if (nbg < 16) nbg |= 8; else if (nbg >= 256) nbg |= 1; } - fg = colours[nfg]; - bg = colours[nbg]; + if (truecolour.fg.enabled) + fg = RGB(truecolour.fg.r, truecolour.fg.g, truecolour.fg.b); + else + fg = colours[nfg]; + + if (truecolour.bg.enabled) + bg = RGB(truecolour.bg.r, truecolour.bg.g, truecolour.bg.b); + else + bg = colours[nbg]; + SelectObject(hdc, fonts[nfont]); SetTextColor(hdc, fg); SetBkColor(hdc, bg); @@ -3768,7 +3776,7 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len, * Wrapper that handles combining characters. */ void do_text(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { if (attr & TATTR_COMBINING) { unsigned long a = 0; @@ -3778,13 +3786,13 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, len0 = 2; if (len-len0 >= 1 && IS_LOW_VARSEL(text[len0])) { attr &= ~TATTR_COMBINING; - do_text_internal(ctx, x, y, text, len0+1, attr, lattr); + do_text_internal(ctx, x, y, text, len0+1, attr, lattr, truecolour); text += len0+1; len -= len0+1; a = TATTR_COMBINING; } else if (len-len0 >= 2 && IS_HIGH_VARSEL(text[len0], text[len0+1])) { attr &= ~TATTR_COMBINING; - do_text_internal(ctx, x, y, text, len0+2, attr, lattr); + do_text_internal(ctx, x, y, text, len0+2, attr, lattr, truecolour); text += len0+2; len -= len0+2; a = TATTR_COMBINING; @@ -3794,22 +3802,21 @@ void do_text(Context ctx, int x, int y, wchar_t *text, int len, while (len--) { if (len >= 1 && IS_SURROGATE_PAIR(text[0], text[1])) { - do_text_internal(ctx, x, y, text, 2, attr | a, lattr); + do_text_internal(ctx, x, y, text, 2, attr | a, lattr, truecolour); len--; text++; - } else { - do_text_internal(ctx, x, y, text, 1, attr | a, lattr); - } + } else + do_text_internal(ctx, x, y, text, 1, attr | a, lattr, truecolour); text++; a = TATTR_COMBINING; } } else - do_text_internal(ctx, x, y, text, len, attr, lattr); + do_text_internal(ctx, x, y, text, len, attr, lattr, truecolour); } void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, - unsigned long attr, int lattr) + unsigned long attr, int lattr, truecolour truecolour) { int fnt_width; @@ -3821,7 +3828,7 @@ void do_cursor(Context ctx, int x, int y, wchar_t *text, int len, if ((attr & TATTR_ACTCURS) && (ctype == 0 || term->big_cursor)) { if (*text != UCSWIDE) { - do_text(ctx, x, y, text, len, attr, lattr); + do_text(ctx, x, y, text, len, attr, lattr, truecolour); return; } ctype = 2;