From 427b8204268af5548d09b830e101c59daa095df9 Mon Sep 17 00:00:00 2001
From: nicm <nicm>
Date: Fri, 29 Jan 2016 11:13:56 +0000
Subject: [PATCH] Support for RGB colour, using the extended cell mechanism to
 avoid wasting unnecessary space. The 'Tc' flag must be set in the external
 TERM entry (using terminal-overrides or a custom terminfo entry), if not tmux
 will map to the closest of the 256 or 16 colour palettes.

Mostly from Suraj N Kurapati, based on a diff originally by someone else.
---
 grid.c     |  11 +++-
 input.c    |  35 ++++++----
 tmux.1     |  12 ++--
 tmux.h     |  20 +++++-
 tty-term.c |   1 +
 tty.c      | 185 ++++++++++++++++++++++++++++++++++++++++++++---------
 6 files changed, 209 insertions(+), 55 deletions(-)

diff --git a/grid.c b/grid.c
index c24e6c6a..782032f4 100644
--- a/grid.c
+++ b/grid.c
@@ -37,7 +37,7 @@
 
 /* Default grid cell data. */
 const struct grid_cell grid_default_cell = {
-	0, 0, 8, 8, { { ' ' }, 0, 1, 1 }
+	0, 0, { .fg = 8 }, { .bg = 8 }, { { ' ' }, 0, 1, 1 }
 };
 const struct grid_cell_entry grid_default_entry = {
 	0, { .data = { 0, 8, 8, ' ' } }
@@ -284,6 +284,7 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
 	struct grid_line	*gl;
 	struct grid_cell_entry	*gce;
 	struct grid_cell 	*gcp;
+	int			 extended;
 
 	if (grid_check_y(gd, py) != 0)
 		return;
@@ -293,8 +294,12 @@ grid_set_cell(struct grid *gd, u_int px, u_int py, const struct grid_cell *gc)
 	gl = &gd->linedata[py];
 	gce = &gl->celldata[px];
 
-	if ((gce->flags & GRID_FLAG_EXTENDED) || gc->data.size != 1 ||
-	    gc->data.width != 1) {
+	extended = (gce->flags & GRID_FLAG_EXTENDED);
+	if (!extended && (gc->data.size != 1 || gc->data.width != 1))
+		extended = 1;
+	if (!extended && (gc->flags & (GRID_FLAG_FGRGB|GRID_FLAG_BGRGB)))
+		extended = 1;
+	if (extended) {
 		if (~gce->flags & GRID_FLAG_EXTENDED) {
 			gl->extddata = xreallocarray(gl->extddata,
 			    gl->extdsize + 1, sizeof *gl->extddata);
diff --git a/input.c b/input.c
index 1772a4cd..ae205024 100644
--- a/input.c
+++ b/input.c
@@ -1629,18 +1629,20 @@ input_csi_dispatch_sgr_256(struct input_ctx *ictx, int fgbg, u_int *i)
 	c = input_get(ictx, *i, 0, -1);
 	if (c == -1) {
 		if (fgbg == 38) {
-			gc->flags &= ~GRID_FLAG_FG256;
+			gc->flags &= ~(GRID_FLAG_FG256|GRID_FLAG_FGRGB);
 			gc->fg = 8;
 		} else if (fgbg == 48) {
-			gc->flags &= ~GRID_FLAG_BG256;
+			gc->flags &= ~(GRID_FLAG_BG256|GRID_FLAG_BGRGB);
 			gc->bg = 8;
 		}
 	} else {
 		if (fgbg == 38) {
 			gc->flags |= GRID_FLAG_FG256;
+			gc->flags &= ~GRID_FLAG_FGRGB;
 			gc->fg = c;
 		} else if (fgbg == 48) {
 			gc->flags |= GRID_FLAG_BG256;
+			gc->flags &= ~GRID_FLAG_BGRGB;
 			gc->bg = c;
 		}
 	}
@@ -1651,7 +1653,7 @@ void
 input_csi_dispatch_sgr_rgb(struct input_ctx *ictx, int fgbg, u_int *i)
 {
 	struct grid_cell	*gc = &ictx->cell.cell;
-	int			 c, r, g, b;
+	int			 r, g, b;
 
 	(*i)++;
 	r = input_get(ictx, *i, 0, -1);
@@ -1666,13 +1668,18 @@ input_csi_dispatch_sgr_rgb(struct input_ctx *ictx, int fgbg, u_int *i)
 	if (b == -1 || b > 255)
 		return;
 
-	c = colour_find_rgb(r, g, b);
 	if (fgbg == 38) {
-		gc->flags |= GRID_FLAG_FG256;
-		gc->fg = c;
+		gc->flags &= ~GRID_FLAG_FG256;
+		gc->flags |= GRID_FLAG_FGRGB;
+		gc->fg_rgb.r = r;
+		gc->fg_rgb.g = g;
+		gc->fg_rgb.b = b;
 	} else if (fgbg == 48) {
-		gc->flags |= GRID_FLAG_BG256;
-		gc->bg = c;
+		gc->flags &= ~GRID_FLAG_BG256;
+		gc->flags |= GRID_FLAG_BGRGB;
+		gc->bg_rgb.r = r;
+		gc->bg_rgb.g = g;
+		gc->bg_rgb.b = b;
 	}
 }
 
@@ -1754,11 +1761,11 @@ input_csi_dispatch_sgr(struct input_ctx *ictx)
 		case 35:
 		case 36:
 		case 37:
-			gc->flags &= ~GRID_FLAG_FG256;
+			gc->flags &= ~(GRID_FLAG_FG256|GRID_FLAG_FGRGB);
 			gc->fg = n - 30;
 			break;
 		case 39:
-			gc->flags &= ~GRID_FLAG_FG256;
+			gc->flags &= ~(GRID_FLAG_FG256|GRID_FLAG_FGRGB);
 			gc->fg = 8;
 			break;
 		case 40:
@@ -1769,11 +1776,11 @@ input_csi_dispatch_sgr(struct input_ctx *ictx)
 		case 45:
 		case 46:
 		case 47:
-			gc->flags &= ~GRID_FLAG_BG256;
+			gc->flags &= ~(GRID_FLAG_BG256|GRID_FLAG_BGRGB);
 			gc->bg = n - 40;
 			break;
 		case 49:
-			gc->flags &= ~GRID_FLAG_BG256;
+			gc->flags &= ~(GRID_FLAG_BG256|GRID_FLAG_BGRGB);
 			gc->bg = 8;
 			break;
 		case 90:
@@ -1784,7 +1791,7 @@ input_csi_dispatch_sgr(struct input_ctx *ictx)
 		case 95:
 		case 96:
 		case 97:
-			gc->flags &= ~GRID_FLAG_FG256;
+			gc->flags &= ~(GRID_FLAG_FG256|GRID_FLAG_FGRGB);
 			gc->fg = n;
 			break;
 		case 100:
@@ -1795,7 +1802,7 @@ input_csi_dispatch_sgr(struct input_ctx *ictx)
 		case 105:
 		case 106:
 		case 107:
-			gc->flags &= ~GRID_FLAG_BG256;
+			gc->flags &= ~(GRID_FLAG_BG256|GRID_FLAG_BGRGB);
 			gc->bg = n - 10;
 			break;
 		}
diff --git a/tmux.1 b/tmux.1
index e41ba798..e70b07c3 100644
--- a/tmux.1
+++ b/tmux.1
@@ -2921,7 +2921,7 @@ and poor for interactive programs such as shells.
 .Op Ic on | off
 .Xc
 Allow programs to change the window name using a terminal escape
-sequence (\\033k...\\033\\\\).
+sequence (\eek...\ee\e\e).
 The default is on.
 .Pp
 .It Xo Ic alternate-screen
@@ -4024,7 +4024,7 @@ This command only works from outside
 .El
 .Sh TERMINFO EXTENSIONS
 .Nm
-understands some extensions to
+understands some unofficial extensions to
 .Xr terminfo 5 :
 .Bl -tag -width Ds
 .It Em Cs , Cr
@@ -4048,10 +4048,12 @@ $ printf '\e033[4 q'
 If
 .Em Se
 is not set, \&Ss with argument 0 will be used to reset the cursor style instead.
+.It Em \&Tc
+Indicate that the terminal supports the
+.Ql direct colour
+RGB escape sequence (for example, \ee[38;2;255;255;255m).
 .It Em \&Ms
-This sequence can be used by
-.Nm
-to store the current buffer in the host terminal's selection (clipboard).
+Store the current buffer in the host terminal's selection (clipboard).
 See the
 .Em set-clipboard
 option above and the
diff --git a/tmux.h b/tmux.h
index eb3373da..8edb9209 100644
--- a/tmux.h
+++ b/tmux.h
@@ -385,6 +385,7 @@ enum tty_code_code {
 	TTYC_SMSO,	/* enter_standout_mode, so */
 	TTYC_SMUL,	/* enter_underline_mode, us */
 	TTYC_SS,	/* set cursor style, Ss */
+	TTYC_TC,	/* 24-bit "true" colour, Tc */
 	TTYC_TSL,	/* to_status_line, tsl */
 	TTYC_VPA,	/* row_address, cv */
 	TTYC_XENL,	/* eat_newline_glitch, xn */
@@ -641,16 +642,31 @@ enum utf8_state {
 #define GRID_FLAG_BG256 0x2
 #define GRID_FLAG_PADDING 0x4
 #define GRID_FLAG_EXTENDED 0x8
+#define GRID_FLAG_FGRGB 0x10
+#define GRID_FLAG_BGRGB 0x20
 
 /* Grid line flags. */
 #define GRID_LINE_WRAPPED 0x1
 
+/* Grid cell RGB colours. */
+struct grid_cell_rgb {
+	u_char	r;
+	u_char	g;
+	u_char	b;
+};
+
 /* Grid cell data. */
 struct grid_cell {
 	u_char			flags;
 	u_char			attr;
-	u_char			fg;
-	u_char			bg;
+	union {
+		u_char		fg;
+		struct grid_cell_rgb	fg_rgb;
+	};
+	union {
+		u_char		bg;
+		struct grid_cell_rgb	bg_rgb;
+	};
 	struct utf8_data	data;
 
 };
diff --git a/tty-term.c b/tty-term.c
index 8716a1a9..05ef01e7 100644
--- a/tty-term.c
+++ b/tty-term.c
@@ -251,6 +251,7 @@ const struct tty_term_code_entry tty_term_codes[] = {
 	[TTYC_SMSO] = { TTYCODE_STRING, "smso" },
 	[TTYC_SMUL] = { TTYCODE_STRING, "smul" },
 	[TTYC_SS] = { TTYCODE_STRING, "Ss" },
+	[TTYC_TC] = { TTYCODE_FLAG, "Tc" },
 	[TTYC_TSL] = { TTYCODE_STRING, "tsl" },
 	[TTYC_VPA] = { TTYCODE_STRING, "vpa" },
 	[TTYC_XENL] = { TTYCODE_FLAG, "xenl" },
diff --git a/tty.c b/tty.c
index 52521be9..c6fc2213 100644
--- a/tty.c
+++ b/tty.c
@@ -36,8 +36,15 @@ static int tty_log_fd = -1;
 void	tty_read_callback(struct bufferevent *, void *);
 void	tty_error_callback(struct bufferevent *, short, void *);
 
+static int tty_same_fg(const struct grid_cell *, const struct grid_cell *);
+static int tty_same_bg(const struct grid_cell *, const struct grid_cell *);
+static int tty_same_colours(const struct grid_cell *, const struct grid_cell *);
+static int tty_is_fg(const struct grid_cell *, int);
+static int tty_is_bg(const struct grid_cell *, int);
+
 void	tty_set_italics(struct tty *);
 int	tty_try_256(struct tty *, u_char, const char *);
+int	tty_try_rgb(struct tty *, const struct grid_cell_rgb *, const char *);
 
 void	tty_colours(struct tty *, const struct grid_cell *);
 void	tty_check_fg(struct tty *, struct grid_cell *);
@@ -61,6 +68,74 @@ void	tty_default_colours(struct grid_cell *, const struct window_pane *);
 #define tty_pane_full_width(tty, ctx) \
 	((ctx)->xoff == 0 && screen_size_x((ctx)->wp->screen) >= (tty)->sx)
 
+static int
+tty_same_fg(const struct grid_cell *gc1, const struct grid_cell *gc2)
+{
+	int	flags1, flags2;
+
+	flags1 = (gc1->flags & (GRID_FLAG_FG256|GRID_FLAG_FGRGB));
+	flags2 = (gc2->flags & (GRID_FLAG_FG256|GRID_FLAG_FGRGB));
+
+	if (flags1 != flags2)
+	    return (0);
+
+	if (flags1 & GRID_FLAG_FGRGB) {
+		if (gc1->fg_rgb.r != gc2->fg_rgb.r)
+			return (0);
+		if (gc1->fg_rgb.g != gc2->fg_rgb.g)
+			return (0);
+		if (gc1->fg_rgb.b != gc2->fg_rgb.b)
+			return (0);
+		return (1);
+	}
+	return (gc1->fg == gc2->fg);
+}
+
+static int
+tty_same_bg(const struct grid_cell *gc1, const struct grid_cell *gc2)
+{
+	int	flags1, flags2;
+
+	flags1 = (gc1->flags & (GRID_FLAG_BG256|GRID_FLAG_BGRGB));
+	flags2 = (gc2->flags & (GRID_FLAG_BG256|GRID_FLAG_BGRGB));
+
+	if (flags1 != flags2)
+	    return (0);
+
+	if (flags1 & GRID_FLAG_BGRGB) {
+		if (gc1->bg_rgb.r != gc2->bg_rgb.r)
+			return (0);
+		if (gc1->bg_rgb.g != gc2->bg_rgb.g)
+			return (0);
+		if (gc1->bg_rgb.b != gc2->bg_rgb.b)
+			return (0);
+		return (1);
+	}
+	return (gc1->bg == gc2->bg);
+}
+
+static int
+tty_same_colours(const struct grid_cell *gc1, const struct grid_cell *gc2)
+{
+	return (tty_same_fg(gc1, gc2) && tty_same_bg(gc1, gc2));
+}
+
+static int
+tty_is_fg(const struct grid_cell *gc, int c)
+{
+	if (gc->flags & (GRID_FLAG_FG256|GRID_FLAG_FGRGB))
+		return (0);
+	return (gc->fg == c);
+}
+
+static int
+tty_is_bg(const struct grid_cell *gc, int c)
+{
+	if (gc->flags & (GRID_FLAG_BG256|GRID_FLAG_BGRGB))
+		return (0);
+	return (gc->bg == c);
+}
+
 void
 tty_create_log(void)
 {
@@ -1423,12 +1498,10 @@ void
 tty_colours(struct tty *tty, const struct grid_cell *gc)
 {
 	struct grid_cell	*tc = &tty->cell;
-	u_char			 fg = gc->fg, bg = gc->bg, flags = gc->flags;
 	int			 have_ax, fg_default, bg_default;
 
 	/* No changes? Nothing is necessary. */
-	if (fg == tc->fg && bg == tc->bg &&
-	    ((flags ^ tc->flags) & (GRID_FLAG_FG256|GRID_FLAG_BG256)) == 0)
+	if (tty_same_colours(gc, tc))
 		return;
 
 	/*
@@ -1437,8 +1510,8 @@ tty_colours(struct tty *tty, const struct grid_cell *gc)
 	 * case if only one is default need to fall onward to set the other
 	 * colour.
 	 */
-	fg_default = (fg == 8 && !(flags & GRID_FLAG_FG256));
-	bg_default = (bg == 8 && !(flags & GRID_FLAG_BG256));
+	fg_default = tty_is_fg(gc, 8);
+	bg_default = tty_is_bg(gc, 8);
 	if (fg_default || bg_default) {
 		/*
 		 * If don't have AX but do have op, send sgr0 (op can't
@@ -1451,48 +1524,52 @@ tty_colours(struct tty *tty, const struct grid_cell *gc)
 		if (!have_ax && tty_term_has(tty->term, TTYC_OP))
 			tty_reset(tty);
 		else {
-			if (fg_default &&
-			    (tc->fg != 8 || tc->flags & GRID_FLAG_FG256)) {
+			if (fg_default && !tty_is_fg(tc, 8)) {
 				if (have_ax)
 					tty_puts(tty, "\033[39m");
-				else if (tc->fg != 7 ||
-				    tc->flags & GRID_FLAG_FG256)
+				else if (!tty_is_fg(tc, 7))
 					tty_putcode1(tty, TTYC_SETAF, 7);
 				tc->fg = 8;
-				tc->flags &= ~GRID_FLAG_FG256;
+				tc->flags &= ~(GRID_FLAG_FG256|GRID_FLAG_FGRGB);
 			}
-			if (bg_default &&
-			    (tc->bg != 8 || tc->flags & GRID_FLAG_BG256)) {
+			if (bg_default && !tty_is_bg(tc, 8)) {
 				if (have_ax)
 					tty_puts(tty, "\033[49m");
-				else if (tc->bg != 0 ||
-				    tc->flags & GRID_FLAG_BG256)
+				else if (!tty_is_bg(tc, 0))
 					tty_putcode1(tty, TTYC_SETAB, 0);
 				tc->bg = 8;
-				tc->flags &= ~GRID_FLAG_BG256;
+				tc->flags &= ~(GRID_FLAG_BG256|GRID_FLAG_BGRGB);
 			}
 		}
 	}
 
 	/* Set the foreground colour. */
-	if (!fg_default && (fg != tc->fg ||
-	    ((flags & GRID_FLAG_FG256) != (tc->flags & GRID_FLAG_FG256))))
+	if (!fg_default && !tty_same_fg(gc, tc))
 		tty_colours_fg(tty, gc);
 
 	/*
 	 * Set the background colour. This must come after the foreground as
 	 * tty_colour_fg() can call tty_reset().
 	 */
-	if (!bg_default && (bg != tc->bg ||
-	    ((flags & GRID_FLAG_BG256) != (tc->flags & GRID_FLAG_BG256))))
+	if (!bg_default && !tty_same_bg(gc, tc))
 		tty_colours_bg(tty, gc);
 }
 
 void
 tty_check_fg(struct tty *tty, struct grid_cell *gc)
 {
-	u_int	colours;
+	struct grid_cell_rgb	*rgb = &gc->fg_rgb;
+	u_int			 colours;
 
+	/* Is this a 24-bit colour? */
+	if (gc->flags & GRID_FLAG_FGRGB) {
+		/* Not a 24-bit terminal? Translate to 256-colour palette. */
+		if (!tty_term_flag(tty->term, TTYC_TC)) {
+			gc->flags &= ~GRID_FLAG_FGRGB;
+			gc->flags |= GRID_FLAG_FG256;
+			gc->fg = colour_find_rgb(rgb->r, rgb->g, rgb->b);
+		}
+	}
 	colours = tty_term_number(tty->term, TTYC_COLORS);
 
 	/* Is this a 256-colour colour? */
@@ -1524,8 +1601,18 @@ tty_check_fg(struct tty *tty, struct grid_cell *gc)
 void
 tty_check_bg(struct tty *tty, struct grid_cell *gc)
 {
-	u_int	colours;
+	struct grid_cell_rgb	*rgb = &gc->bg_rgb;
+	u_int			 colours;
 
+	/* Is this a 24-bit colour? */
+	if (gc->flags & GRID_FLAG_BGRGB) {
+		/* Not a 24-bit terminal? Translate to 256-colour palette. */
+		if (!tty_term_flag(tty->term, TTYC_TC)) {
+			gc->flags &= ~GRID_FLAG_BGRGB;
+			gc->flags |= GRID_FLAG_BG256;
+			gc->bg = colour_find_rgb(rgb->r, rgb->g, rgb->b);
+		}
+	}
 	colours = tty_term_number(tty->term, TTYC_COLORS);
 
 	/* Is this a 256-colour colour? */
@@ -1560,12 +1647,21 @@ tty_colours_fg(struct tty *tty, const struct grid_cell *gc)
 	u_char			 fg = gc->fg;
 	char			 s[32];
 
+	tc->flags &= ~(GRID_FLAG_FG256|GRID_FLAG_FGRGB);
+
+	/* Is this a 24-bit colour? */
+	if (gc->flags & GRID_FLAG_FGRGB) {
+		if (tty_try_rgb(tty, &gc->fg_rgb, "38") == 0)
+			goto save_fg;
+		/* Should not get here, already converted in tty_check_fg. */
+		return;
+	}
+
 	/* Is this a 256-colour colour? */
 	if (gc->flags & GRID_FLAG_FG256) {
-		/* Try as 256 colours. */
 		if (tty_try_256(tty, fg, "38") == 0)
 			goto save_fg;
-		/* Else already handled by tty_check_fg. */
+		/* Should not get here, already converted in tty_check_fg. */
 		return;
 	}
 
@@ -1581,9 +1677,12 @@ tty_colours_fg(struct tty *tty, const struct grid_cell *gc)
 
 save_fg:
 	/* Save the new values in the terminal current cell. */
-	tc->fg = fg;
-	tc->flags &= ~GRID_FLAG_FG256;
-	tc->flags |= gc->flags & GRID_FLAG_FG256;
+	if (gc->flags & GRID_FLAG_FGRGB)
+		memcpy(&tc->fg_rgb, &gc->fg_rgb, sizeof tc->fg_rgb);
+	else
+		tc->fg = fg;
+	tc->flags &= ~(GRID_FLAG_FGRGB|GRID_FLAG_FG256);
+	tc->flags |= (gc->flags & (GRID_FLAG_FG256|GRID_FLAG_FGRGB));
 }
 
 void
@@ -1593,12 +1692,19 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc)
 	u_char			 bg = gc->bg;
 	char			 s[32];
 
+	/* Is this a 24-bit colour? */
+	if (gc->flags & GRID_FLAG_BGRGB) {
+		if (tty_try_rgb(tty, &gc->bg_rgb, "48") == 0)
+			goto save_bg;
+		/* Should not get here, already converted in tty_check_bg. */
+		return;
+	}
+
 	/* Is this a 256-colour colour? */
 	if (gc->flags & GRID_FLAG_BG256) {
-		/* Try as 256 colours. */
 		if (tty_try_256(tty, bg, "48") == 0)
 			goto save_bg;
-		/* Else already handled by tty_check_bg. */
+		/* Should not get here, already converted in tty_check_bg. */
 		return;
 	}
 
@@ -1614,9 +1720,12 @@ tty_colours_bg(struct tty *tty, const struct grid_cell *gc)
 
 save_bg:
 	/* Save the new values in the terminal current cell. */
-	tc->bg = bg;
-	tc->flags &= ~GRID_FLAG_BG256;
-	tc->flags |= gc->flags & GRID_FLAG_BG256;
+	if (gc->flags & GRID_FLAG_BGRGB)
+		memcpy(&tc->bg_rgb, &gc->bg_rgb, sizeof tc->bg_rgb);
+	else
+		tc->bg = bg;
+	tc->flags &= ~(GRID_FLAG_BGRGB|GRID_FLAG_BG256);
+	tc->flags |= (gc->flags & (GRID_FLAG_BG256|GRID_FLAG_BGRGB));
 }
 
 int
@@ -1656,6 +1765,20 @@ fallback:
 	return (0);
 }
 
+int
+tty_try_rgb(struct tty *tty, const struct grid_cell_rgb *rgb, const char *type)
+{
+	char	s[32];
+
+	if (!tty_term_flag(tty->term, TTYC_TC))
+		return (-1);
+
+	xsnprintf(s, sizeof s, "\033[%s;2;%hhu;%hhu;%hhum", type, rgb->r,
+	    rgb->g, rgb->b);
+	tty_puts(tty, s);
+	return (0);
+}
+
 void
 tty_default_colours(struct grid_cell *gc, const struct window_pane *wp)
 {