Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
True color (24-bit) terminal support for Emacs 24.4
This diff is a modified version of a diff written by Rüdiger Sonderfeld.
The original diff can be found here:
http://emacs.1067599.n5.nabble.com/RFC-Add-tty-True-Color-support-tt299962.html
To enable the feature one must set one of 2 environment variables either
ITERM_24BIT or KONSOLE_DBUS_SESSION. The former will enable ITU T.416 mode, the
latter will use semi-colon seperators used by other terminals (and also
supported by iterm development branch).
I've added ITERM_24BIT as an environment variable to set that will cause emacs
to use the : seperated values as specified by ITU T.416. These are apparently
better to use and supported by iterm development branch.
Other chagnes:
- modified map_tty_color to set the color into the pixel field.
- modified the colors defined to be the standard X11 list.
- modified tty-color-desc to add missing colors on demand.
*** ./lisp/term/xterm.el.orig 2014-06-20 13:28:33.000000000 -0400
--- ./lisp/term/xterm.el 2015-02-23 07:01:48.000000000 -0500
***************
*** 674,679 ****
--- 674,688 ----
;; are more colors to support, compute them now.
(when (> ncolors 0)
(cond
+ ((= (display-color-cells (selected-frame)) 16777216) ; 24-bit xterm
+ (let ((idx (length xterm-standard-colors)))
+ ;; Insert standard X colors after the standard xterm ones
+ (mapc (lambda (color)
+ (if (not (assoc (car color) xterm-standard-colors))
+ (progn
+ (tty-color-define (car color) idx (cdr color))
+ (setq idx (1+ idx)))))
+ color-name-rgb-alist)))
((= ncolors 240) ; 256-color xterm
;; 216 non-gray colors first
(let ((r 0) (g 0) (b 0))
*** ./src/dispextern.h.orig 2014-09-15 23:13:46.000000000 -0400
--- ./src/dispextern.h 2015-02-23 07:01:48.000000000 -0500
***************
*** 1739,1747 ****
INLINE bool
face_tty_specified_color (unsigned long color)
{
! return color < FACE_TTY_DEFAULT_BG_COLOR;
}
/* Non-zero if FACE was realized for unibyte use. */
#define FACE_UNIBYTE_P(FACE) ((FACE)->charset < 0)
--- 1739,1753 ----
INLINE bool
face_tty_specified_color (unsigned long color)
{
! return (color < FACE_TTY_DEFAULT_BG_COLOR);
}
+ INLINE bool
+ face_tty_specified_24_bit_color (unsigned long color)
+ {
+ /* 24 bit colors have 24th but not 25th bit set */
+ return ((color & (0x03 << 24)) == (0x01 << 24));
+ }
/* Non-zero if FACE was realized for unibyte use. */
#define FACE_UNIBYTE_P(FACE) ((FACE)->charset < 0)
*** ./src/term.c.orig 2014-06-04 11:47:40.000000000 -0400
--- ./src/term.c 2015-02-23 07:01:48.000000000 -0500
***************
*** 1915,1932 ****
const char *ts;
char *p;
! ts = tty->standout_mode ? tty->TS_set_background : tty->TS_set_foreground;
if (face_tty_specified_color (fg) && ts)
{
! p = tparam (ts, NULL, 0, fg, 0, 0, 0);
OUTPUT (tty, p);
xfree (p);
}
! ts = tty->standout_mode ? tty->TS_set_foreground : tty->TS_set_background;
if (face_tty_specified_color (bg) && ts)
{
! p = tparam (ts, NULL, 0, bg, 0, 0, 0);
OUTPUT (tty, p);
xfree (p);
}
--- 1915,1954 ----
const char *ts;
char *p;
! if (face_tty_specified_24_bit_color(fg))
! ts = tty->standout_mode ? tty->TS_set_rgb_background : tty->TS_set_rgb_foreground;
! else
! ts = tty->standout_mode ? tty->TS_set_background : tty->TS_set_foreground;
if (face_tty_specified_color (fg) && ts)
{
! if (!face_tty_specified_24_bit_color(fg))
! p = tparam (ts, NULL, 0, fg, 0, 0, 0);
! else
! {
! const unsigned char r = (fg >> 16) & 0xFF,
! g = (fg >> 8) & 0xFF,
! b = fg & 0xFF;
! p = tparam (ts, NULL, 0, (int)r, (int)g, (int)b, 0);
! }
OUTPUT (tty, p);
xfree (p);
}
! if (face_tty_specified_24_bit_color(bg))
! ts = tty->standout_mode ? tty->TS_set_rgb_foreground : tty->TS_set_rgb_background;
! else
! ts = tty->standout_mode ? tty->TS_set_foreground : tty->TS_set_background;
if (face_tty_specified_color (bg) && ts)
{
! if (!face_tty_specified_24_bit_color(bg))
! p = tparam (ts, NULL, 0, bg, 0, 0, 0);
! else
! {
! const unsigned char r = (bg >> 16) & 0xFF,
! g = (bg >> 8) & 0xFF,
! b = bg & 0xFF;
! p = tparam (ts, NULL, 0, (int)r, (int)g, (int)b, 0);
! }
OUTPUT (tty, p);
xfree (p);
}
***************
*** 2028,2033 ****
--- 2050,2057 ----
struct terminal *t = get_tty_terminal (terminal, 0);
if (!t)
return make_number (0);
+ else if (t->display_info.tty->TS_set_rgb_foreground)
+ return make_number (16777216); /* 24 bit True Color */
else
return make_number (t->display_info.tty->TN_max_colors);
}
***************
*** 2043,2048 ****
--- 2067,2074 ----
static char *default_orig_pair;
static char *default_set_foreground;
static char *default_set_background;
+ static char *default_set_rgb_foreground;
+ static char *default_set_rgb_background;
/* Save or restore the default color-related capabilities of this
terminal. */
***************
*** 2055,2060 ****
--- 2081,2088 ----
dupstring (&default_orig_pair, tty->TS_orig_pair);
dupstring (&default_set_foreground, tty->TS_set_foreground);
dupstring (&default_set_background, tty->TS_set_background);
+ dupstring (&default_set_rgb_foreground, tty->TS_set_rgb_foreground);
+ dupstring (&default_set_rgb_background, tty->TS_set_rgb_background);
default_max_colors = tty->TN_max_colors;
default_max_pairs = tty->TN_max_pairs;
default_no_color_video = tty->TN_no_color_video;
***************
*** 2064,2069 ****
--- 2092,2099 ----
tty->TS_orig_pair = default_orig_pair;
tty->TS_set_foreground = default_set_foreground;
tty->TS_set_background = default_set_background;
+ tty->TS_set_rgb_foreground = default_set_rgb_foreground;
+ tty->TS_set_rgb_background = default_set_rgb_background;
tty->TN_max_colors = default_max_colors;
tty->TN_max_pairs = default_max_pairs;
tty->TN_no_color_video = default_no_color_video;
***************
*** 2088,2093 ****
--- 2118,2124 ----
tty->TN_max_pairs = 0;
tty->TN_no_color_video = 0;
tty->TS_set_foreground = tty->TS_set_background = tty->TS_orig_pair = NULL;
+ tty->TS_set_rgb_foreground = tty->TS_set_rgb_background = NULL;
break;
case 0: /* default colors, if any */
default:
***************
*** 2102,2111 ****
--- 2133,2161 ----
tty->TS_set_foreground = "\033[3%dm";
tty->TS_set_background = "\033[4%dm";
#endif
+ tty->TS_set_rgb_foreground = NULL;
+ tty->TS_set_rgb_background = NULL;
tty->TN_max_colors = 8;
tty->TN_max_pairs = 64;
tty->TN_no_color_video = 0;
break;
+ case 16777216: /* RGB colors */
+ tty->TS_orig_pair = "\033[0m";
+ #ifdef TERMINFO
+ tty->TS_set_foreground = "\033[3%p1%dm";
+ tty->TS_set_background = "\033[4%p1%dm";
+ tty->TS_set_rgb_foreground = "\033[38;2;%p1%d;%p2%d;%p3%dm";
+ tty->TS_set_rgb_background = "\033[48;2;%p1%d;%p2%d;%p3%dm";
+ #else
+ tty->TS_set_foreground = "\033[3%dm";
+ tty->TS_set_background = "\033[4%dm";
+ tty->TS_set_rgb_foreground = "\033[38;2;%d;%d;%dm";
+ tty->TS_set_rgb_background = "\033[48;2;%d;%d;%dm";
+ #endif
+ tty->TN_max_colors = 16777216;
+ /*tty->TN_max_pairs = 64; TODO */
+ tty->TN_no_color_video = 0;
+ break;
}
}
***************
*** 4201,4206 ****
--- 4251,4286 ----
tty->TN_no_color_video = tgetnum ("NC");
if (tty->TN_no_color_video == -1)
tty->TN_no_color_video = 0;
+
+ /* TODO Reliable way to detect: Konsole, iTerm2, st */
+ if (getenv ("KONSOLE_DBUS_SESSION"))
+ {
+ /* TODO This should be extracted from terminfo/termcap. */
+ #ifdef TERMINFO
+ tty->TS_set_rgb_foreground = "\033[38;2;%p1%d;%p2%d;%p3%dm";
+ tty->TS_set_rgb_background = "\033[48;2;%p1%d;%p2%d;%p3%dm";
+ #else
+ tty->TS_set_rgb_foreground = "\033[38;2;%d;%d;%dm";
+ tty->TS_set_rgb_background = "\033[48;2;%d;%d;%dm";
+ #endif
+ }
+ else if (getenv ("ITERM_24BIT"))
+ {
+ /* XXX chopps use ITU T.421 ':' separator */
+ /* TODO This should be extracted from terminfo/termcap. */
+ #ifdef TERMINFO
+ tty->TS_set_rgb_foreground = "\033[38:2:%p1%d:%p2%d:%p3%dm";
+ tty->TS_set_rgb_background = "\033[48:2:%p1%d:%p2%d:%p3%dm";
+ #else
+ tty->TS_set_rgb_foreground = "\033[38:2:%d:%d:%dm";
+ tty->TS_set_rgb_background = "\033[48:2:%d:%d:%dm";
+ #endif
+ }
+ else
+ {
+ tty->TS_set_rgb_foreground = NULL;
+ tty->TS_set_rgb_background = NULL;
+ }
}
tty_default_color_capabilities (tty, 1);
*** ./src/termchar.h.orig 2014-03-21 01:34:40.000000000 -0400
--- ./src/termchar.h 2015-02-23 07:01:48.000000000 -0500
***************
*** 157,162 ****
--- 157,166 ----
const char *TS_set_foreground;
const char *TS_set_background;
+ /* Support for 24bit RGB color terminals. */
+ const char *TS_set_rgb_foreground;
+ const char *TS_set_rgb_background;
+
int TF_hazeltine; /* termcap hz flag. */
int TF_insmode_motion; /* termcap mi flag: can move while in insert mode. */
int TF_standout_motion; /* termcap mi flag: can move while in standout mode. */
*** ./src/xfaces.c.orig 2014-10-09 12:07:28.000000000 -0400
--- ./src/xfaces.c 2015-02-23 07:02:06.000000000 -0500
***************
*** 382,388 ****
/* TTY color-related functions (defined in tty-colors.el). */
! static Lisp_Object Qtty_color_desc, Qtty_color_by_index, Qtty_color_standard_values;
/* The name of the function used to compute colors on TTYs. */
--- 382,388 ----
/* TTY color-related functions (defined in tty-colors.el). */
! static Lisp_Object Qtty_color_desc, Qtty_color_by_index, Qtty_color_standard_values, Qtty_color_canonicalize;
/* The name of the function used to compute colors on TTYs. */
***************
*** 943,996 ****
if (!STRINGP (color) || NILP (Ffboundp (Qtty_color_desc)))
return 0;
! XSETFRAME (frame, f);
! color_desc = call2 (Qtty_color_desc, color, frame);
! if (CONSP (color_desc) && CONSP (XCDR (color_desc)))
{
! Lisp_Object rgb;
! if (! INTEGERP (XCAR (XCDR (color_desc))))
! return 0;
! tty_color->pixel = XINT (XCAR (XCDR (color_desc)));
! rgb = XCDR (XCDR (color_desc));
! if (! parse_rgb_list (rgb, tty_color))
! return 0;
! /* Should we fill in STD_COLOR too? */
! if (std_color)
! {
! /* Default STD_COLOR to the same as TTY_COLOR. */
! *std_color = *tty_color;
! /* Do a quick check to see if the returned descriptor is
! actually _exactly_ equal to COLOR, otherwise we have to
! lookup STD_COLOR separately. If it's impossible to lookup
! a standard color, we just give up and use TTY_COLOR. */
! if ((!STRINGP (XCAR (color_desc))
! || NILP (Fstring_equal (color, XCAR (color_desc))))
! && !NILP (Ffboundp (Qtty_color_standard_values)))
! {
! /* Look up STD_COLOR separately. */
! rgb = call1 (Qtty_color_standard_values, color);
! if (! parse_rgb_list (rgb, std_color))
! return 0;
! }
! }
! return 1;
}
- else if (NILP (Fsymbol_value (intern ("tty-defined-color-alist"))))
- /* We were called early during startup, and the colors are not
- yet set up in tty-defined-color-alist. Don't return a failure
- indication, since this produces the annoying "Unable to
- load color" messages in the *Messages* buffer. */
- return 1;
- else
- /* tty-color-desc seems to have returned a bad value. */
- return 0;
}
/* A version of defined_color for non-X frames. */
--- 943,1022 ----
if (!STRINGP (color) || NILP (Ffboundp (Qtty_color_desc)))
return 0;
! if (f->output_method == output_termcap
! && f->output_data.tty->display_info->TS_set_rgb_foreground
! && !NILP (Ffboundp (Qtty_color_standard_values)))
! {
! /* Terminal supports 3 byte RGB colors. */
! if (!NILP (Ffboundp (Qtty_color_canonicalize)))
! color = call1(Qtty_color_canonicalize, color);
!
! color_desc = call1 (Qtty_color_standard_values, color);
! if (! parse_rgb_list (color_desc, tty_color))
! return 0;
!
! /* Map XColor to 3 byte values. */
! tty_color->pixel = 1 << 24 /* Set bit 24 to mark RGB values. */
! | (tty_color->red / 256) << 16
! | (tty_color->green / 256) << 8
! | (tty_color->blue / 256);
!
! if (std_color)
! *std_color = *tty_color;
! return 1;
! }
! else
{
! XSETFRAME (frame, f);
! color_desc = call2 (Qtty_color_desc, color, frame);
! if (CONSP (color_desc) && CONSP (XCDR (color_desc)))
! {
! Lisp_Object rgb;
! if (! INTEGERP (XCAR (XCDR (color_desc))))
! return 0;
! tty_color->pixel = XINT (XCAR (XCDR (color_desc)));
! rgb = XCDR (XCDR (color_desc));
! if (! parse_rgb_list (rgb, tty_color))
! return 0;
! /* Should we fill in STD_COLOR too? */
! if (std_color)
! {
! /* Default STD_COLOR to the same as TTY_COLOR. */
! *std_color = *tty_color;
! /* Do a quick check to see if the returned descriptor is
! actually _exactly_ equal to COLOR, otherwise we have to
! lookup STD_COLOR separately. If it's impossible to lookup
! a standard color, we just give up and use TTY_COLOR. */
! if ((!STRINGP (XCAR (color_desc))
! || NILP (Fstring_equal (color, XCAR (color_desc))))
! && !NILP (Ffboundp (Qtty_color_standard_values)))
! {
! /* Look up STD_COLOR separately. */
! rgb = call1 (Qtty_color_standard_values, color);
! if (! parse_rgb_list (rgb, std_color))
! return 0;
! }
! }
!
! return 1;
! }
! else if (NILP (Fsymbol_value (intern ("tty-defined-color-alist"))))
! /* We were called early during startup, and the colors are not
! yet set up in tty-defined-color-alist. Don't return a failure
! indication, since this produces the annoying "Unable to
! load color" messages in the *Messages* buffer. */
! return 1;
! else
! /* tty-color-desc seems to have returned a bad value. */
! return 0;
}
}
/* A version of defined_color for non-X frames. */
***************
*** 1008,1014 ****
color_def->green = 0;
if (*color_name)
! status = tty_lookup_color (f, build_string (color_name), color_def, NULL);
if (color_def->pixel == FACE_TTY_DEFAULT_COLOR && *color_name)
{
--- 1034,1042 ----
color_def->green = 0;
if (*color_name)
! {
! status = tty_lookup_color (f, build_string (color_name), color_def, NULL);
! }
if (color_def->pixel == FACE_TTY_DEFAULT_COLOR && *color_name)
{
***************
*** 5780,5785 ****
--- 5808,5814 ----
unsigned long default_pixel =
foreground_p ? FACE_TTY_DEFAULT_FG_COLOR : FACE_TTY_DEFAULT_BG_COLOR;
unsigned long pixel = default_pixel;
+ XColor true_color;
#ifdef MSDOS
unsigned long default_other_pixel =
foreground_p ? FACE_TTY_DEFAULT_BG_COLOR : FACE_TTY_DEFAULT_FG_COLOR;
***************
*** 5798,5804 ****
{
/* Associations in tty-defined-color-alist are of the form
(NAME INDEX R G B). We need the INDEX part. */
! pixel = XINT (XCAR (XCDR (def)));
}
if (pixel == default_pixel && STRINGP (color))
--- 5827,5844 ----
{
/* Associations in tty-defined-color-alist are of the form
(NAME INDEX R G B). We need the INDEX part. */
! if (f->output_method == output_termcap
! && f->output_data.tty->display_info->TS_set_rgb_foreground
! && parse_rgb_list (XCDR (XCDR(def)), &true_color))
! {
! /* Map XColor to 3 byte values. */
! pixel = 1 << 24 /* Set bit 24 to mark RGB values. */
! | (true_color.red / 256) << 16
! | (true_color.green / 256) << 8
! | (true_color.blue / 256);
! }
! else
! pixel = XINT (XCAR (XCDR (def)));
}
if (pixel == default_pixel && STRINGP (color))
***************
*** 6460,6465 ****
--- 6500,6506 ----
DEFSYM (Qwindow_divider, "window-divider");
DEFSYM (Qwindow_divider_first_pixel, "window-divider-first-pixel");
DEFSYM (Qwindow_divider_last_pixel, "window-divider-last-pixel");
+ DEFSYM (Qtty_color_canonicalize, "tty-color-canonicalize");
DEFSYM (Qtty_color_desc, "tty-color-desc");
DEFSYM (Qtty_color_standard_values, "tty-color-standard-values");
DEFSYM (Qtty_color_by_index, "tty-color-by-index");
@jneen

This comment has been minimized.

Copy link

commented Oct 15, 2015

I applied this patch and now terminal emacs is 2-tone when I set either environment variable. Is there a setting or a --configure flag I need to massage somewhere?

@jneen

This comment has been minimized.

Copy link

commented Oct 15, 2015

Whoops, nevermind, it's working now - for those who come along later, check that your $TERM isn't set to something silly like xterm-256color. Simply xterm worked for me in gnome-terminal.

@hlian

This comment has been minimized.

Copy link

commented Feb 22, 2016

everybody who contributed to this patch is doing God's work

@cstrahan

This comment has been minimized.

Copy link

commented Feb 29, 2016

Could you explain why there's

     tty->TS_set_rgb_foreground = "\033[38;2;%p1%d;%p2%d;%p3%dm";

vs

     tty->TS_set_rgb_foreground = "\033[38;2;%d;%d;%dm";

?

It looks like the former also prints some pointers, but I can't imagine why...

It looks like tmux's true color support is hard coded to use the latter snippet: tmux/tmux@427b820

@cstrahan

This comment has been minimized.

Copy link

commented Feb 29, 2016

Oh, spoke too soon. Just read src/tparam.c.

@cstrahan

This comment has been minimized.

Copy link

commented Feb 29, 2016

So, in the ifdef TERMINFO case, we're using %pN to substitute positional params (corresponding to red, green and blue values). But, if I'm reading this right, when TERMINFO isn't defined, we still print the params in the same order. If I understood that correctly, why bother with the #ifdef?

@cstrahan

This comment has been minimized.

Copy link

commented Feb 29, 2016

(I'd like to get this upstreamed, but I think I should first understand the current state of this patch.)

@u8y7541

This comment has been minimized.

Copy link

commented Jun 30, 2016

How do you apply the patch? I'm sorry, I'm a noob at this.

What file do I have to apply it to?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.