From: "Rami Ylimäki" <rami.ylimaki@vincit.com>
To: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
Subject: Re: [PATCH] Support 24-bit terminal colors.
Date: Wed, 31 Aug 2016 20:47:10 +0300 [thread overview]
Message-ID: <CAHmfpf96B1jmg9MXokjpvmX9wW0NM79Ef=hLo_TGwG+8UV+JHA@mail.gmail.com> (raw)
[-- Attachment #1: Type: text/plain, Size: 14511 bytes --]
2016-08-31 17:21 GMT+03:00 Eli Zaretskii <eliz@gnu.org>:
> > From: Rami Ylimäki <rjy@iki.fi>
> > Date: Tue, 30 Aug 2016 23:11:46 +0300
> >
> > Based on previous work by Rüdiger Sonderfeld, Christian Hopps and
> > Charles Strahan.
>
> Some of these people don't have copyright assignments on file, so how
> much of the code is actually taken from what they wrote?
>
The code is mostly based on the original patch [1] from Rüdiger.
[1] https://lists.gnu.org/archive/html/emacs-devel/2013-10/msg00461.html
> I also don't find your assignment, but maybe once you adjudicate the
> comments below, the number of changes will be small enough to accept
> without an assignment. Nevertheless, I encourage you to start your
> legal paperwork rolling, so that we could continue receiving your
> contributions even if this one doesn't need it.
>
Sure, I can assign copyright. I believe you can send me instructions and
necessary documents to sign?
> > 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))"
>
> That is a screw. Is there really no way of detecting true-color
> support? Not even a way that's specific to each kind of terminal
> emulator that supports this mode?
>
I sent mail to bug-ncurses about this. I think 24-bit terminal support has
become more widespread and de facto standard since Rüdiger contacted them
so that they might be willing to standardize new terminal capabilities
regarding this.
> Asking the user to specify a command-line argument each time is so
> annoying that I'm tempted to say this feature is not yet ready for a
> complex multi-terminal program such as Emacs, and we should wait for
> some kind of standard to emerge before we dump this on our users.
>
> Can we devise some customizable data structure that stores names of
> terminals that support true-color? Then users could set this list
> from their ~/.emacs init files, and have any frame open on those
> terminals automatically support 24-bit colors. (If we go this way,
> the data structure will have to be consulted by startup.el at the
> right place, so that the initial frame is already set up correctly.)
>
Yeah, let's see first whether the terminfo guys are willing to add
xterm-24bit entry and related capabilities first.
> In any case, if we do agree to specify this via a command-line
> argument, the same command-line argument should IMO be supported by
> emacsclient; asking users to use Lisp is less user-friendly, IMO.
>
Ok.
> * 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.
>
> This part is IMO wrong: the true-color support is a property of the
> terminal, not of a frame. So we should use terminal-parameters for
> communicating the support, and each frame on that terminal should
> thereafter automatically be set to use 24-bit colors.
>
Got it.
> 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.")
>
> I think 'rgb' is too general to be useful mnemonically. How about
> '24bit' or 'true-color' instead?
>
24bit sounds good to me if we can't avoid the entry.
> > +(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)))
>
> Not sure why this function is needed. Why not simply look at the
> number of supported colors, i.e. what display-color-cells returns?
With the changes, xterm-register-default-colors is called when the
auto-detected color count is still in effect. That function is just a dirty
workaround.
> > (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)))
>
> Why don't you use the RGB spec of the color, encoded in an integer as
> the value of IDX here? Won't that make your job easier in other
> places, and avoid special-casing the true-color terminal?
>
Good point.
> > +/* Color index bit indicating absence of palette. */
> > +#define FACE_TTY_NONINDEXED_COLOR ((unsigned long) (1 << 24))
>
> Once again, not sure why this bit is needed, since you have the number
> of colors that can be used for the same purpose.
You are right, it's not needed.
> > +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);
> > }
> > }
>
> Is this refactoring really needed? You now have two extra function
> call levels, in a function that is called _a_lot_ in the inner-most
> loops of the display engine.
>
> AFAIU, the only difference in the true-color case is that the 3 last
> arguments of tparam need to be computed by bitwise operations rather
> than set to zero. Adding 2 extra levels of indirection just for that
> sounds excessive to me. I'd keep that inline.
>
Ok.
> > @@ -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);
>
> Why do we need 2 new attributes here? Can't we reuse
> TS_set_foreground and TS_set_background instead? They seem to be
> unused in the 24-bit color case.
>
Yeah, they aren't needed.
> > - default_max_pairs = tty->TN_max_pairs;
> [...]
> > @@ -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");
> [...]
> > - /* "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;
> > -
>
> If deletion of this attribute is not necessary for the patch to work,
> it should be done in a separate commit.
>
It's not needed and is a separate change.
> > + 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;
>
> Do all true-color terminals support the same escape sequences for
> 24-bit colors? Is there any standard documents we could rely upon for
> hard-coding them here?
>
AFAIK, there are no documents. However, it seems [2] that all or almost all
24-bit terminals support those sequences (semicolon separated rgb values in
range 0-255) in practice.
[2] https://gist.github.com/XVilka/8346728
>
> > +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;
> > +}
>
> Once again, why not just look at the number of supported colors?
Yeah.
> > +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_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)));
> > }
>
> Sorry, I don't understand why this complexity is needed. AFAIU, a
> true-color terminal can produce color directly from its RGB
> components, without the need to look up that RGB descriptor via
> tty-color-desc. In addition, support for named colors, such as
> "burlywood1", is still required for these terminals, since most color
> specifications in Emacs are of that variety. Is my understanding
> correct?
>
> If I'm right, then you could either (a) extend tty-color-desc, such
> that it would cons the (NAME INDEX R G B) list for a true-color
> terminal and hand it back to tty_lookup_color; or (b) if you wanted to
> be more efficient, generate the pixel value directly on the C level by
> parsing the color spec if it is of the form "#RRGGBB", and otherwise
> calling tty-color-desc as usual. Assuming that tty-color-alist is set
> correctly for the true-color terminal, and stores the RGB values for
> each color, that will do the job, eliminating the need for all these
> support functions: parse_and_encode_rgb_list, tty_supports_rgb, and
> tty_lookup_rgb, and also save you 2 extra calls to Lisp. Am I missing
> something?
>
Not missing anything. Thanks for detailed review and good advice.
> Thanks.
>
[-- Attachment #2: Type: text/html, Size: 20394 bytes --]
next reply other threads:[~2016-08-31 17:47 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2016-08-31 17:47 Rami Ylimäki [this message]
2016-08-31 18:14 ` [PATCH] Support 24-bit terminal colors Eli Zaretskii
-- strict thread matches above, loose matches on Subject: below --
2016-08-30 20:55 Rami Ylimäki
2016-08-30 21:03 ` Daniel Colascione
2016-08-30 22:07 ` Clément Pit--Claudel
2016-08-30 22:37 ` Daniel Colascione
2016-08-30 22:47 ` Clément Pit--Claudel
2016-08-31 21:42 ` Richard Stallman
2016-09-01 2:39 ` Eli Zaretskii
2016-09-19 4:32 ` Charles Strahan
2016-09-19 22:28 ` Richard Stallman
2016-09-23 13:46 ` Clément Pit--Claudel
2016-09-25 17:16 ` Richard Stallman
2016-09-25 19:45 ` Clément Pit--Claudel
2016-09-26 0:10 ` Chad Brown
2016-09-26 17:52 ` Richard Stallman
2016-11-07 0:12 ` Charles Strahan
2016-09-23 13:48 ` Clément Pit--Claudel
2016-09-24 9:05 ` Kalle Olavi Niemitalo
2016-08-30 22:08 ` Rami Ylimäki
2016-08-30 20:11 Rami Ylimäki
2016-08-31 14:21 ` Eli Zaretskii
2016-09-04 5:45 ` David Caldwell
2016-09-04 8:11 ` Yuri Khan
2016-09-04 14:18 ` Eli Zaretskii
2016-09-04 16:44 ` Rami Ylimäki
2016-09-04 17:51 ` Eli Zaretskii
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to='CAHmfpf96B1jmg9MXokjpvmX9wW0NM79Ef=hLo_TGwG+8UV+JHA@mail.gmail.com' \
--to=rami.ylimaki@vincit.com \
--cc=eliz@gnu.org \
--cc=emacs-devel@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.