diff --git a/kitty/colors.c b/kitty/colors.c index 507049dea..67231cb77 100644 --- a/kitty/colors.c +++ b/kitty/colors.c @@ -5,6 +5,7 @@ * Distributed under terms of the GPL3 license. */ +#define EXTRA_INIT if (PyModule_AddFunctions(module, module_methods) != 0) return false; #include "data-types.h" #include @@ -29,23 +30,29 @@ static uint32_t FG_BG_256[256] = { 0xffffff, // 15 }; -PyObject* create_256_color_table() { - // colors 16..232: the 6x6x6 color cube - const uint8_t valuerange[6] = {0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; - uint8_t i, j=16; - for(i = 0; i < 217; i++, j++) { - uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6]; - FG_BG_256[j] = (r << 16) | (g << 8) | b; - } - // colors 233..255: grayscale - for(i = 1; i < 24; i++, j++) { - uint8_t v = 8 + i * 10; - FG_BG_256[j] = (v << 16) | (v << 8) | v; +static inline void +init_FG_BG_table() { + if (UNLIKELY(FG_BG_256[255] == 0)) { + // colors 16..232: the 6x6x6 color cube + const uint8_t valuerange[6] = {0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; + uint8_t i, j=16; + for(i = 0; i < 217; i++, j++) { + uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6]; + FG_BG_256[j] = (r << 16) | (g << 8) | b; + } + // colors 233..255: grayscale + for(i = 1; i < 24; i++, j++) { + uint8_t v = 8 + i * 10; + FG_BG_256[j] = (v << 16) | (v << 8) | v; + } } +} - PyObject *ans = PyTuple_New(255); +PyObject* create_256_color_table() { + init_FG_BG_table(); + PyObject *ans = PyTuple_New(arraysz(FG_BG_256)); if (ans == NULL) return PyErr_NoMemory(); - for (i=0; i < 255; i++) { + for (size_t i=0; i < arraysz(FG_BG_256); i++) { PyObject *temp = PyLong_FromUnsignedLong(FG_BG_256[i]); if (temp == NULL) { Py_CLEAR(ans); return NULL; } PyTuple_SET_ITEM(ans, i, temp); @@ -59,7 +66,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) { self = (ColorProfile *)type->tp_alloc(type, 0); if (self != NULL) { - if (FG_BG_256[255] == 0) create_256_color_table(); + init_FG_BG_table(); memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256)); memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256)); self->dirty = true; @@ -80,12 +87,10 @@ alloc_color_profile() { static PyObject* update_ansi_color_table(ColorProfile *self, PyObject *val) { -#define update_ansi_color_table_doc "Update the 16 basic colors" - index_type i; - +#define update_ansi_color_table_doc "Update the 256 basic colors" if (!PyList_Check(val)) { PyErr_SetString(PyExc_TypeError, "color table must be a list"); return NULL; } - if (PyList_GET_SIZE(val) != 16) { PyErr_SetString(PyExc_TypeError, "color table must have 16 items"); return NULL; } - for (i = 0; i < 16; i++) { + if (PyList_GET_SIZE(val) != arraysz(FG_BG_256)) { PyErr_SetString(PyExc_TypeError, "color table must have 256 items"); return NULL; } + for (size_t i = 0; i < arraysz(FG_BG_256); i++) { self->color_table[i] = PyLong_AsUnsignedLong(PyList_GET_ITEM(val, i)); self->orig_color_table[i] = self->color_table[i]; } @@ -184,6 +189,10 @@ color_table_address(ColorProfile *self, PyObject *a UNUSED) { return PyLong_FromVoidPtr((void*)self->color_table); } +static PyObject* +default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) { + return create_256_color_table(); +} // Boilerplate {{{ @@ -236,5 +245,11 @@ PyTypeObject ColorProfile_Type = { .tp_new = new, }; +static PyMethodDef module_methods[] = { + METHODB(default_color_table, METH_NOARGS), + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + INIT_TYPE(ColorProfile) // }}} diff --git a/kitty/config.py b/kitty/config.py index bac077733..f35fb76f5 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -20,6 +20,7 @@ from .config_utils import ( from .constants import cache_dir, defconf from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from .layout import all_layouts +from .rgb import color_from_int from .utils import log_error MINIMUM_FONT_SIZE = 4 @@ -333,8 +334,8 @@ for name in ( ' selection_foreground selection_background url_color' ).split(): type_map[name] = to_color -for i in range(16): - type_map['color%d' % i] = to_color +for i in range(256): + type_map['color{}'.format(i)] = to_color for a in ('active', 'inactive'): for b in ('foreground', 'background'): type_map['%s_tab_%s' % (a, b)] = to_color @@ -376,7 +377,17 @@ def parse_config(lines, check_keys=True): return ans -Options, defaults = init_config(default_config_path, parse_config) +def parse_defaults(lines, check_keys=False): + ans = parse_config(lines, check_keys) + dfctl = defines.default_color_table() + + for i in range(16, 256): + k = 'color{}'.format(i) + ans.setdefault(k, color_from_int(dfctl[i])) + return ans + + +Options, defaults = init_config(default_config_path, parse_defaults) actions = frozenset(all_key_actions) | frozenset( 'combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'. split() @@ -447,7 +458,7 @@ def load_config(*paths, overrides=None) -> Options: if overrides is not None: vals = parse_config(overrides) ans = merge_configs(ans, vals) - return Options(**ans) + return Options(ans) def build_ansi_color_table(opts: Options = defaults): @@ -458,7 +469,7 @@ def build_ansi_color_table(opts: Options = defaults): def col(i): return as_int(getattr(opts, 'color{}'.format(i))) - return list(map(col, range(16))) + return list(map(col, range(256))) def atomic_save(data, path): diff --git a/kitty/config_utils.py b/kitty/config_utils.py index e95edfbf8..9ff1a1206 100644 --- a/kitty/config_utils.py +++ b/kitty/config_utils.py @@ -4,7 +4,6 @@ import os import re -from collections import namedtuple from .rgb import to_color as as_color from .utils import log_error @@ -80,9 +79,25 @@ def parse_config_base( _parse(lines, type_map, special_handling, ans, all_keys) +def create_options_class(keys): + keys = tuple(sorted(keys)) + slots = keys + ('_fields',) + + def __init__(self, kw): + for k, v in kw.items(): + setattr(self, k, v) + + def _asdict(self): + return {k: getattr(self, k) for k in self._fields} + + ans = type('Options', (), {'__slots__': slots, '__init__': __init__, '_asdict': _asdict}) + ans._fields = keys + return ans + + def init_config(defaults_path, parse_config): with open(defaults_path, encoding='utf-8', errors='replace') as f: defaults = parse_config(f, check_keys=False) - Options = namedtuple('Defaults', ','.join(defaults.keys())) - defaults = Options(**defaults) + Options = create_options_class(defaults.keys()) + defaults = Options(defaults) return Options, defaults diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 49eee41d2..c9349b22f 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -247,7 +247,8 @@ selection_foreground #000000 selection_background #FFFACD # The 16 terminal colors. There are 8 basic colors, each color has a dull and -# bright version. +# bright version. You can also set the remaining colors from the 256 color table +# as color16 to color256. # black color0 #000000 diff --git a/kitty/rgb.py b/kitty/rgb.py index d52e4f5bd..1af967f48 100644 --- a/kitty/rgb.py +++ b/kitty/rgb.py @@ -27,6 +27,10 @@ def parse_rgb(spec): return Color(*map(parse_single_color, colors)) +def color_from_int(x): + return Color((x >> 16) & 255, (x >> 8) & 255, x & 255) + + def to_color(raw, validate=False): # See man XParseColor x = raw.strip().lower()