From b6ca50111172847f44d9527b90f98e37a13dc8d8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 22 Jul 2024 13:21:45 +0530 Subject: [PATCH] Document the new color control protocol --- docs/changelog.rst | 2 + docs/color-stack.rst | 143 +++++++++++++++++++++++++++++++++++- docs/conf.py | 15 ++++ kitty/options/definition.py | 2 + kitty/rgb.py | 24 ------ 5 files changed, 161 insertions(+), 25 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5abc24e52..1779d156a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -80,6 +80,8 @@ Detailed list of changes - A new option :opt:`second_transparent_bg` to make a second background color semi-transparent via :opt:`background_opacity`. Useful for things like cursor line highlight in editors (:iss:`7646`) +- A new protocol to allow terminal applications to change colors in the terminal more robustly than with the legacy XTerm protocol (:ref:`color_control`) + - Sessions: A new command ``focus_matching_window`` to shift focus to a specific window, useful when creating complex layouts with splits (:disc:`7635`) - Wayland: Allow fractional scales less than one (:pull:`7549`) diff --git a/docs/color-stack.rst b/docs/color-stack.rst index b1ca3d60d..4d116b976 100644 --- a/docs/color-stack.rst +++ b/docs/color-stack.rst @@ -1,5 +1,8 @@ +Color control +==================== + Saving and restoring colors -============================== +------------------------------ It is often useful for a full screen application with its own color themes to set the default foreground, background, selection and cursor colors and the ANSI @@ -24,3 +27,141 @@ foreground, selection background, selection foreground and cursor color and the promoting interoperability, kitty added support for xterm's escape codes as well, and changed this extension to also save/restore the entire ANSI color table. + +.. _color_control: + +Setting and querying colors +------------------------------- + +While there exists a legacy protocol developed by XTerm for querying and +setting colors, as with most XTerm protocols it suffers from the usual design +limitations of being under specified and in-sufficient. XTerm implements +querying of colors using OSC 4,5,6,10-19,104,105,106,110-119. This absurd +profusion of numbers is completely unnecessary, redundant and requires adding +two new numbers for every new color. Also XTerm's protocol doesn't handle the +case of colors that are unknown to the terminal or that are not a set value, +for example, many terminals implement selection as a reverse video effect not a +fixed color. The XTerm protocol has no way to query for this condition. The +protocol also doesn't actually specify the format in which colors are reported, +deferring to a man page for X11! + +Instead kitty has developed a single number based protocol that addresses all +these shortcomings and is future proof by virtue of using string keys rather +than numbers. The syntax of the escape code is:: + + 21 ; key=value ; key=value ; ... + +The spaces in the above definition are for reading clarity and should be ignored. +Here, ```` is the two bytes ``0x1b (ESC)`` and ``0x5b ([)``. ``ST`` is +either `0x7 (BEL)` or the two bytes ``0x1b (ESC)`` and ``0x5c (\\)``. + +``key`` is a number from 0-255 to query or set the color values from the +terminals ANSI color table, or one of the strings in the table below for +special colors: + +================================= =============================================== =============================== +key meaning dynamic +================================= =============================================== =============================== +foreground The default foreground text color Not applicable +background The default background text color Not applicable +selection_background The background color of selections Reverse video +selection_foreground The foreground color of selections Reverse video +cursor The color of the text cursor Foreground color +cursor_text The color of text under the cursor Background color +visual_bell The color of a visual bell Automatic color selection based on current screen colors +second_transparent_background A color that might be rendered semi-transparent No second color is made transparent + in addition to the default background color +================================= =============================================== =============================== + +In this table the third column shows what effect setting the color to *dynamic* +has in kitty and many other terminal emulators. It is advisory only, terminal +emulators may not support dynamic colors for these or they may have other +effects. Setting the ANSI color table colors to dynamic is not allowed. + +Querying current color values +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To query colors values, the client program sends this escape code with the +``value`` field set to ``?`` (the byte ``0x3f``). The terminal then responds +with the same escape code, but with the ``?`` replaced by the :ref:`encoded +color value `. If the queried color is one that +does not have a defined value, for example, if the terminal is using a reverse +video effect or a gradient or similar, then the value must be empty, that is +the response contains only the key and ``=``, no value. For example, if the +client sends:: + + 21 ; foreground=? ; cursor=? + +The terminal responds:: + + 21 ; foreground=rgb:ff/00/00 ; cursor= + +This indicates that the foreground color is red and the cursor color is +undefined (typically the cursor takes the color of the text under it and the +text takes the color of the background). + +If the terminal does not know a field that a client send to it for a query it +must respond back with the ``field=?``, that is, it must send back a question +mark as the value. + + +Setting color values +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To set a color value, the client program sends this escape code with the +``value`` field set to either an :ref:`encoded color value +` or the empty value. The empty value means +the terminal should use a dynamic color for example reverse video for +selections or similar. To reset a color to its default value (i.e. the value it +would have if it was never set) the client program should send just the key +name with no ``=`` and no value. For example:: + + 21 ; foreground=green ; cursor= ; background + +This sets the foreground to the color green, sets the cursor color to dynamic +(usually meaning the cursor takes the color of the text under it) and resets +the background color to its default value. + +To check if setting succeeded, the client can simply query the color, in fact +the two can be combined into a single escape code, for example:: + + 21 ; foreground=white ; foreground=? + +The terminal will change the foreground color and reply with the new foreground +color. + + +.. _color_control_color_encoding: + +Color value encoding +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The color encoding is inherited from the scheme used by XTerm, for +compatibility, but a sane, rigorously specified subset is chosen. + +RGB colors are encoded in one of three forms: + +rgb:// + , , := h | hh | hhh | hhhh + h := single hexadecimal digits (case insignificant) + Note that h indicates the value scaled in 4 bits, hh the value scaled in 8 bits, hhh the value scaled in 12 bits, and hhhh the value scaled + in 16 bits, respectively. + +# + h := single hexadecimal digits (case insignificant) + #RGB (4 bits each) + #RRGGBB (8 bits each) + #RRRGGGBBB (12 bits each) + #RRRRGGGGBBBB (16 bits each) + The R, G, and B represent single hexadecimal digits. When fewer than 16 bits each are specified, they represent the most significant bits + of the value (unlike the “rgb:” syntax, in which values are scaled). For example, the string ``#3a7`` is the same as ``#3000a0007000``. + +rgbi:// + red, green, and blue are floating-point values between 0.0 and 1.0, inclusive. The input format for these values is an optional + sign, a string of numbers possibly containing a decimal point, and an optional exponent field containing an E or e followed by a possibly + signed integer string. + +In addition, the following color names are accepted (case-insensitively) corresponding to the +specified RGB values. + +.. include:: generated/color-names.rst diff --git a/docs/conf.py b/docs/conf.py index da38a1ca1..06faae7ca 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -289,6 +289,20 @@ if you specify a program-to-run you can use the special placeholder # }}} +def write_color_names_table() -> None: # {{{ + from kitty.rgb import color_names + def s(c: Any) -> str: + return f'{c.red:02x}/{c.green:02x}/{c.blue:02x}' + with open('generated/color-names.rst', 'w') as f: + p = partial(print, file=f) + p('=' * 50, '=' * 20) + p('Name'.ljust(50), 'RGB value') + p('=' * 50, '=' * 20) + for name, col in color_names.items(): + p(name.ljust(50), s(col)) + p('=' * 50, '=' * 20) +# }}} + def write_remote_control_protocol_docs() -> None: # {{{ from kitty.rc.base import RemoteCommand, all_command_names, command_for_name field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)') @@ -750,6 +764,7 @@ def setup(app: Any) -> None: kn = all_kitten_names() write_cli_docs(kn) write_remote_control_protocol_docs() + write_color_names_table() write_conf_docs(app, kn) app.add_config_value('string_replacements', {}, True) app.connect('source-read', replace_string) diff --git a/kitty/options/definition.py b/kitty/options/definition.py index 94bb565fc..5a8c6310d 100644 --- a/kitty/options/definition.py +++ b/kitty/options/definition.py @@ -1522,6 +1522,8 @@ When the background color matches this color, :opt:`background_opacity` is appli to render it as semi-transparent, just as for colors matching the background color. Useful in more complex UIs like editors where you could want more than a single background color to be rendered as transparent, for instance, for a cursor highlight line background. +Terminal applications can set this color using :ref:`The kitty color control ` +escape code. ''') opt('dynamic_background_opacity', 'no', diff --git a/kitty/rgb.py b/kitty/rgb.py index 9ee9e134b..8c2259f0a 100644 --- a/kitty/rgb.py +++ b/kitty/rgb.py @@ -845,27 +845,3 @@ color_names = { 'yellow4': Color(139, 139, 0), 'yellowgreen': Color(154, 205, 50)} # END_DATA_SECTION }}} - -if __name__ == '__main__': - # Read RGB color table from specified rgb.txt file - import pprint - import sys - data = {} - with open(sys.argv[-1]) as f: - for line in f: - line = line.strip() - if not line or line.startswith('!'): - continue - parts = line.split() - r, g, b = map(int, parts[:3]) - name = ' '.join(parts[3:]).lower() - data[name] = data[name.replace(' ', '')] = r, g, b - formatted_data = pprint.pformat(data).replace('{', '{\n ').replace('(', 'Color(') - with open(__file__, 'r+') as src: - raw = src.read() - raw = re.sub( - r'^# BEGIN_DATA_SECTION {{{$.*^# END_DATA_SECTION }}}', - '# BEGIN_DATA_SECTION {{{\ncolor_names = %s\n# END_DATA_SECTION }}}' % formatted_data, - raw, flags=re.DOTALL | re.MULTILINE - ) - src.seek(0), src.truncate(), src.write(raw)