emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Support 24-bit terminal colors.


From: Rami Ylimäki
Subject: [PATCH] Support 24-bit terminal colors.
Date: Tue, 30 Aug 2016 23:11:46 +0300

Based on previous work by Rüdiger Sonderfeld, Christian Hopps and
Charles Strahan.

Currently it's not possible to detect whether terminal supports 24-bit
colors. Therefore following methods can be used to force 24-bit mode on:

emacs -nw --color=rgb
emacsclient -nw -F "((tty-color-mode . rgb))"

* lisp/server.el (server-process-filter): Pass 'tty-color-mode frame
  parameter from command line to server-create-tty-frame.
  (server-create-tty-frame): Pass 'tty-color-mode frame parameter to
  make-frame.

* lisp/term/tty-colors.el (tty-color-mode-alist): Add 'rgb entry to
  support 24-bit terminals. This enables the --color=rgb option as well.

* lisp/term/xterm.el (xterm-register-default-colors): Define all named
  tty colors. Add xterm-rgb-support-b to check whether 24-bit mode has
  been forced on.

* src/dispextern.h: Define bit FACE_TTY_NONINDEXED_COLOR to separate
  indexed and rgb component based tty color encodings. The rgb component
  based color "index" is simply represented by a 24-bit rgb triplet with
  FACE_TTY_NONINDEXED_COLOR set.

* src/termchar.h (tty_display_info): Remove TN_max_pairs field
  describing maximum number of color pairs. The field is unused and
  would also be inconvenient with 24-bit colors. Add new tty control
  functions TS_set_rgb_foreground and TS_set_rgb_background for
  outputting rgb component based tty colors.

* src/term.c (turn_on_face): Output indexed or rgb component based tty
  colors by detecting whether FACE_TTY_NONINDEXED_COLOR has been set.
  (tty_default_color_capabilities): Add TS_set_rgb_foreground and
  TS_set_rgb_background.
  (tty_setup_colors): Initialize tty_display_info for 24-bit terminals.

* src/xfaces.c (tty_lookup_color): Use rgb component based tty color
  encoding on 24-bit terminals.
  (map_tty_color): Use rgb component based tty color encoding for face
  foreground and background on 24-bit terminals.
---
 doc/man/emacs.1.in      |  2 +-
 lisp/server.el          | 14 ++++++--
 lisp/term/tty-colors.el |  3 +-
 lisp/term/xterm.el      | 16 +++++++++
 src/dispextern.h        |  3 ++
 src/term.c              | 89 +++++++++++++++++++++++++++++++++++--------------
 src/termchar.h          |  7 ++--
 src/xfaces.c            | 51 ++++++++++++++++++++++++++--
 8 files changed, 149 insertions(+), 36 deletions(-)

diff --git a/doc/man/emacs.1.in b/doc/man/emacs.1.in
index 3b1566f..79d48fb 100644
--- a/doc/man/emacs.1.in
+++ b/doc/man/emacs.1.in
@@ -252,7 +252,7 @@ Set additional X resources.
 Override color mode for character terminals;
 .I mode
 defaults to "auto", and can also be "never", "auto", "always",
-or a mode name like "ansi8".
+or a mode name like "ansi8". Use "rgb" for 24-bit colors.
 .TP
 .BI \-bw " pixels\fR,\fP " \-\-border\-width " pixels"
 Set the
diff --git a/lisp/server.el b/lisp/server.el
index 5300984..5ecd483 100644
--- a/lisp/server.el
+++ b/lisp/server.el
@@ -792,11 +792,13 @@ This handles splitting the command if it would be bigger 
than
       (setq prefix "-print-nonl "))
     (server-send-string proc (concat prefix qtext "\n"))))
 
-(defun server-create-tty-frame (tty type proc)
+(defun server-create-tty-frame (tty type color-mode proc)
   (unless tty
     (error "Invalid terminal device"))
   (unless type
     (error "Invalid terminal type"))
+  (unless color-mode
+    (error "Invalid terminal color mode"))
   (add-to-list 'frame-inherited-parameters 'client)
   (let ((frame
          (server-with-environment
@@ -812,6 +814,7 @@ This handles splitting the command if it would be bigger 
than
            (make-frame `((window-system . nil)
                          (tty . ,tty)
                          (tty-type . ,type)
+                         (tty-color-mode . ,color-mode)
                          ;; Ignore nowait here; we always need to
                          ;; clean up opened ttys when the client dies.
                          (client . ,proc)
@@ -1055,6 +1058,8 @@ The following commands are accepted by the client:
                (coding-system (and (default-value 'enable-multibyte-characters)
                                    (or file-name-coding-system
                                        default-file-name-coding-system)))
+               (tty-color-mode 'default)
+
                nowait     ; t if emacsclient does not want to wait for us.
                frame      ; Frame opened for the client (if any).
                display    ; Open frame on this display.
@@ -1089,7 +1094,10 @@ The following commands are accepted by the client:
                  (let ((alist (pop args-left)))
                    (if coding-system
                        (setq alist (decode-coding-string alist coding-system)))
-                   (setq frame-parameters (car (read-from-string alist)))))
+                   (setq frame-parameters (car (read-from-string alist)))
+                   (let ((color-mode (cdr (assoc 'tty-color-mode 
frame-parameters))))
+                     (if (assoc color-mode tty-color-mode-alist)
+                         (setq tty-color-mode color-mode)))))
 
                 ;; -display DISPLAY:
                 ;; Open X frames on the given display instead of the default.
@@ -1241,7 +1249,7 @@ The following commands are accepted by the client:
                                                       frame-parameters))
                   ;; When resuming on a tty, tty-name is nil.
                   (tty-name
-                   (server-create-tty-frame tty-name tty-type proc))))
+                   (server-create-tty-frame tty-name tty-type tty-color-mode 
proc))))
 
             (process-put
              proc 'continuation
diff --git a/lisp/term/tty-colors.el b/lisp/term/tty-colors.el
index a886950..b9b69cd 100644
--- a/lisp/term/tty-colors.el
+++ b/lisp/term/tty-colors.el
@@ -764,7 +764,8 @@
     (auto . 0)
     (ansi8 . 8)
     (always . 8)
-    (yes . 8))
+    (yes . 8)
+    (rgb . 16777216))
   "An alist of supported standard tty color modes and their aliases.")
 
 (defun tty-color-alist (&optional _frame)
diff --git a/lisp/term/xterm.el b/lisp/term/xterm.el
index 01c0113..e0f3eb5 100644
--- a/lisp/term/xterm.el
+++ b/lisp/term/xterm.el
@@ -905,6 +905,14 @@ hitting screen's max DCS length."
   "Convert an 8-bit primary color value PRIM to a corresponding 16-bit value."
   (logior prim (lsh prim 8)))
 
+(defun xterm-rgb-support-p ()
+  "Check whether 24-bit colors have been forced on."
+  (or
+   ;; emacs -nw --color=rgb
+   (eql (cdr (assoc 'tty-color-mode default-frame-alist)) 'rgb)
+   ;; emacsclient -nw -F "((tty-color-mode . rgb))"
+   (eql (cdr (assoc 'tty-color-mode (frame-parameters))) 'rgb)))
+
 (defun xterm-register-default-colors (colors)
   "Register the default set of colors for xterm or compatible emulator.
 
@@ -930,6 +938,14 @@ versions of xterm."
     ;; are more colors to support, compute them now.
     (when (> ncolors 0)
       (cond
+       ((xterm-rgb-support-p) ; 24-bit xterm
+       ;; all named tty colors
+       (let ((idx (length xterm-standard-colors)))
+         (mapc (lambda (color)
+                 (unless (assoc (car color) xterm-standard-colors)
+                   (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))
diff --git a/src/dispextern.h b/src/dispextern.h
index f2c42de..35d2106 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -1751,6 +1751,9 @@ struct face
 
 #define FACE_TTY_DEFAULT_BG_COLOR ((unsigned long) -3)
 
+/* Color index bit indicating absence of palette.  */
+#define FACE_TTY_NONINDEXED_COLOR ((unsigned long) (1 << 24))
+
 /* True if COLOR is a specified (i.e., nondefault) foreground or
    background color for a tty face.  */
 
diff --git a/src/term.c b/src/term.c
index d54ff11..dc6028d 100644
--- a/src/term.c
+++ b/src/term.c
@@ -1891,6 +1891,43 @@ produce_glyphless_glyph (struct it *it, Lisp_Object 
acronym)
    ? (tty->TN_no_color_video & (ATTR)) == 0             \
    : 1)
 
+static char *
+tparam_color (struct tty_display_info *tty, unsigned long color, bool 
is_foreground)
+{
+  const char *ts = is_foreground
+                  ? tty->TS_set_foreground
+                  : tty->TS_set_background;
+  return ts ? tparam (ts, NULL, 0, color, 0, 0, 0) : NULL;
+}
+
+static char *
+tparam_rgb (struct tty_display_info *tty, unsigned long color, bool 
is_foreground)
+{
+  const char *ts = is_foreground
+                  ? tty->TS_set_rgb_foreground
+                  : tty->TS_set_rgb_background;
+  const int red = (color >> 16) & 0xFF;
+  const int green = (color >> 8) & 0xFF;
+  const int blue = color & 0xFF;
+  return ts ? tparam (ts, NULL, 0, red, green, blue, 0) : NULL;
+}
+
+static void
+turn_on_color (struct tty_display_info *tty, unsigned long color, bool 
is_foreground)
+{
+  if (face_tty_specified_color (color))
+    {
+      char *p = color & FACE_TTY_NONINDEXED_COLOR
+               ? tparam_rgb (tty, color, is_foreground)
+               : tparam_color (tty, color, is_foreground);
+      if (p)
+       {
+         OUTPUT (tty, p);
+         xfree (p);
+        }
+    }
+}
+
 /* Turn appearances of face FACE_ID on tty frame F on.
    FACE_ID is a realized face ID number, in the face cache.  */
 
@@ -1930,24 +1967,8 @@ turn_on_face (struct frame *f, int face_id)
 
   if (tty->TN_max_colors > 0)
     {
-      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);
-       }
+      turn_on_color (tty, fg, !tty->standout_mode);
+      turn_on_color (tty, bg, tty->standout_mode);
     }
 }
 
@@ -2050,11 +2071,12 @@ TERMINAL does not refer to a text terminal.  */)
    to work around an HPUX compiler bug (?). See
    http://lists.gnu.org/archive/html/emacs-devel/2007-08/msg00410.html  */
 static int default_max_colors;
-static int default_max_pairs;
 static int default_no_color_video;
 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.  */
@@ -2067,8 +2089,9 @@ tty_default_color_capabilities (struct tty_display_info 
*tty, bool save)
       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;
     }
   else
@@ -2076,8 +2099,9 @@ tty_default_color_capabilities (struct tty_display_info 
*tty, bool save)
       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;
     }
 }
@@ -2097,9 +2121,10 @@ tty_setup_colors (struct tty_display_info *tty, int mode)
     {
       case -1:  /* no colors at all */
        tty->TN_max_colors = 0;
-       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_orig_pair = NULL;
+       tty->TS_set_foreground = tty->TS_set_background = NULL;
+       tty->TS_set_rgb_foreground = tty->TS_set_rgb_background = NULL;
        break;
       case 0:   /* default colors, if any */
       default:
@@ -2107,6 +2132,7 @@ tty_setup_colors (struct tty_display_info *tty, int mode)
        break;
       case 8:  /* 8 standard ANSI colors */
        tty->TS_orig_pair = "\033[0m";
+       tty->TS_set_rgb_foreground = tty->TS_set_rgb_background = NULL;
 #ifdef TERMINFO
        tty->TS_set_foreground = "\033[3%p1%dm";
        tty->TS_set_background = "\033[4%p1%dm";
@@ -2115,9 +2141,21 @@ tty_setup_colors (struct tty_display_info *tty, int mode)
        tty->TS_set_background = "\033[4%dm";
 #endif
        tty->TN_max_colors = 8;
-       tty->TN_max_pairs = 64;
        tty->TN_no_color_video = 0;
        break;
+      case 16777216:
+       tty->TS_orig_pair = "\033[0m";
+       tty->TS_set_foreground = tty->TS_set_background = NULL;
+#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
+       tty->TN_max_colors = 16777216;
+       tty->TN_no_color_video = 0;
+        break;
     }
 }
 
@@ -4137,7 +4175,6 @@ use the Bourne shell command 'TERM=...; export TERM' 
(C-shell:\n\
        }
 
       tty->TN_max_colors = tgetnum ("Co");
-      tty->TN_max_pairs = tgetnum ("pa");
 
       tty->TN_no_color_video = tgetnum ("NC");
       if (tty->TN_no_color_video == -1)
@@ -4514,6 +4551,8 @@ bigger, or it may make it blink, or it may do nothing at 
all.  */);
   default_orig_pair = NULL;
   default_set_foreground = NULL;
   default_set_background = NULL;
+  default_set_rgb_foreground = NULL;
+  default_set_rgb_background = NULL;
 #endif /* !DOS_NT */
 
   encode_terminal_src = NULL;
diff --git a/src/termchar.h b/src/termchar.h
index 35b30fb..81fe96e 100644
--- a/src/termchar.h
+++ b/src/termchar.h
@@ -149,10 +149,6 @@ struct tty_display_info
 
   int TN_max_colors;            /* "Co" -- number of colors.  */
 
-  /* "pa" -- max. number of color pairs on screen.  Not handled yet.
-     Could be a problem if not equal to TN_max_colors * TN_max_colors.  */
-  int TN_max_pairs;
-
   /* "op" -- SVr4 set default pair to its original value.  */
   const char *TS_orig_pair;
 
@@ -160,6 +156,9 @@ struct tty_display_info
      1 param, the color index.  */
   const char *TS_set_foreground;
   const char *TS_set_background;
+  /* 3 params: red, green and blue. */
+  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. */
diff --git a/src/xfaces.c b/src/xfaces.c
index 0a1315d..e4650fd 100644
--- a/src/xfaces.c
+++ b/src/xfaces.c
@@ -832,6 +832,45 @@ parse_rgb_list (Lisp_Object rgb_list, XColor *color)
   return true;
 }
 
+static bool
+parse_and_encode_rgb_list (Lisp_Object rgb_list, XColor *color)
+{
+  if (!parse_rgb_list (rgb_list, color))
+    return false;
+
+  color->pixel = FACE_TTY_NONINDEXED_COLOR
+                | (color->red / 256) << 16
+                | (color->green / 256) << 8
+                | (color->blue / 256);
+  return true;
+}
+
+static bool
+tty_supports_rgb (struct frame *f)
+{
+  return f->output_method == output_termcap
+        && f->output_data.tty->display_info->TS_set_rgb_foreground
+        && f->output_data.tty->display_info->TS_set_rgb_background;
+}
+
+static bool
+tty_lookup_rgb (Lisp_Object color, XColor *tty_color, XColor *std_color)
+{
+  if (NILP (Ffboundp (Qtty_color_standard_values)))
+    return false;
+
+  if (!NILP (Ffboundp (Qtty_color_canonicalize)))
+    color = call1 (Qtty_color_canonicalize, color);
+
+  color = call1 (Qtty_color_standard_values, color);
+  if (!parse_and_encode_rgb_list (color, tty_color))
+    return false;
+
+  if (std_color)
+    *std_color = *tty_color;
+
+  return true;
+}
 
 /* Lookup on frame F the color described by the lisp string COLOR.
    The resulting tty color is returned in TTY_COLOR; if STD_COLOR is
@@ -844,6 +883,9 @@ tty_lookup_color (struct frame *f, Lisp_Object color, 
XColor *tty_color,
 {
   Lisp_Object frame, color_desc;
 
+  if (tty_supports_rgb (f))
+    return tty_lookup_rgb (color, tty_color, std_color);
+
   if (!STRINGP (color) || NILP (Ffboundp (Qtty_color_desc)))
     return false;
 
@@ -5707,8 +5749,12 @@ map_tty_color (struct frame *f, struct face *face,
          CONSP (def)))
     {
       /* 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)));
+        (NAME INDEX R G B).  We need the (R G B) or INDEX part.  */
+      XColor tty_color;
+      if (tty_supports_rgb (f) && parse_and_encode_rgb_list (XCDR (XCDR 
(def)), &tty_color))
+       pixel = tty_color.pixel;
+      else
+       pixel = XINT (XCAR (XCDR (def)));
     }
 
   if (pixel == default_pixel && STRINGP (color))
@@ -6395,6 +6441,7 @@ syms_of_xfaces (void)
   DEFSYM (Qwindow_divider_first_pixel, "window-divider-first-pixel");
   DEFSYM (Qwindow_divider_last_pixel, "window-divider-last-pixel");
   DEFSYM (Qtty_color_desc, "tty-color-desc");
+  DEFSYM (Qtty_color_canonicalize, "tty-color-canonicalize");
   DEFSYM (Qtty_color_standard_values, "tty-color-standard-values");
   DEFSYM (Qtty_color_by_index, "tty-color-by-index");
 
-- 
2.7.4




reply via email to

[Prev in Thread] Current Thread [Next in Thread]