Also add a FAQ entry about changing colors in a running kitty instance. I'm tired of all the bug reports asking for this feature. Apparently, people find it hard to google for the existing escape codes based solution.
294 lines
9.2 KiB
C
294 lines
9.2 KiB
C
/*
|
|
* colors.c
|
|
* Copyright (C) 2016 Kovid Goyal <kovid at kovidgoyal.net>
|
|
*
|
|
* Distributed under terms of the GPL3 license.
|
|
*/
|
|
|
|
#define EXTRA_INIT if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
|
#include "data-types.h"
|
|
#include <structmember.h>
|
|
|
|
PyTypeObject ColorProfile_Type;
|
|
|
|
static uint32_t FG_BG_256[256] = {
|
|
0x000000, // 0
|
|
0xcd0000, // 1
|
|
0x00cd00, // 2
|
|
0xcdcd00, // 3
|
|
0x0000ee, // 4
|
|
0xcd00cd, // 5
|
|
0x00cdcd, // 6
|
|
0xe5e5e5, // 7
|
|
0x7f7f7f, // 8
|
|
0xff0000, // 9
|
|
0x00ff00, // 10
|
|
0xffff00, // 11
|
|
0x5c5cff, // 12
|
|
0xff00ff, // 13
|
|
0x00ffff, // 14
|
|
0xffffff, // 15
|
|
};
|
|
|
|
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* create_256_color_table() {
|
|
init_FG_BG_table();
|
|
PyObject *ans = PyTuple_New(arraysz(FG_BG_256));
|
|
if (ans == NULL) return PyErr_NoMemory();
|
|
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);
|
|
}
|
|
return ans;
|
|
}
|
|
|
|
static PyObject *
|
|
new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
|
ColorProfile *self;
|
|
|
|
self = (ColorProfile *)type->tp_alloc(type, 0);
|
|
if (self != NULL) {
|
|
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;
|
|
}
|
|
return (PyObject*) self;
|
|
}
|
|
|
|
static void
|
|
dealloc(ColorProfile* self) {
|
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
|
}
|
|
|
|
ColorProfile*
|
|
alloc_color_profile() {
|
|
return (ColorProfile*)new(&ColorProfile_Type, NULL, NULL);
|
|
}
|
|
|
|
|
|
static PyObject*
|
|
update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
|
#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) != 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];
|
|
}
|
|
self->dirty = true;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
|
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
|
|
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
|
char key[32] = {0};
|
|
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
|
snprintf(key, sizeof(key) - 1, "color%zu", i);
|
|
v = PyDict_GetItemString(spec, key);
|
|
if (v) {
|
|
color_type color = PyLong_AsUnsignedLong(v);
|
|
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
|
|
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
|
|
self->color_table[i] = color;
|
|
if (change_configured) self->orig_color_table[i] = color;
|
|
self->dirty = true;
|
|
}
|
|
}
|
|
}
|
|
#define S(config_name, profile_name) { \
|
|
v = PyDict_GetItemString(spec, #config_name); \
|
|
if (v) { \
|
|
color_type color = PyLong_AsUnsignedLong(v); \
|
|
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) { \
|
|
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i); \
|
|
self->overridden.profile_name = (color << 8) | 2; \
|
|
if (change_configured) self->configured.profile_name = color; \
|
|
self->dirty = true; \
|
|
} \
|
|
} \
|
|
}
|
|
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
|
|
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
|
|
#undef S
|
|
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
color_type
|
|
colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
|
color_type t = entry & 0xFF, r;
|
|
switch(t) {
|
|
case 1:
|
|
r = (entry >> 8) & 0xff;
|
|
return self->color_table[r];
|
|
case 2:
|
|
return entry >> 8;
|
|
default:
|
|
return defval;
|
|
}
|
|
}
|
|
|
|
|
|
static PyObject*
|
|
as_color(ColorProfile *self, PyObject *val) {
|
|
#define as_color_doc "Convert the specified terminal color into an (r, g, b) tuple based on the current profile values"
|
|
if (!PyLong_Check(val)) { PyErr_SetString(PyExc_TypeError, "val must be an int"); return NULL; }
|
|
unsigned long entry = PyLong_AsUnsignedLong(val);
|
|
unsigned int t = entry & 0xFF;
|
|
uint8_t r;
|
|
uint32_t col = 0;
|
|
PyObject *ans = NULL;
|
|
switch(t) {
|
|
case 1:
|
|
r = (entry >> 8) & 0xff;
|
|
col = self->color_table[r];
|
|
break;
|
|
case 2:
|
|
col = entry >> 8;
|
|
break;
|
|
default:
|
|
ans = Py_None; Py_INCREF(Py_None);
|
|
}
|
|
if (ans == NULL) ans = Py_BuildValue("BBB", (unsigned char)(col >> 16), (unsigned char)((col >> 8) & 0xFF), (unsigned char)(col & 0xFF));
|
|
return ans;
|
|
}
|
|
|
|
static PyObject*
|
|
reset_color_table(ColorProfile *self, PyObject *a UNUSED) {
|
|
#define reset_color_table_doc "Reset all customized colors back to defaults"
|
|
memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256));
|
|
self->dirty = true;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
reset_color(ColorProfile *self, PyObject *val) {
|
|
#define reset_color_doc "Reset the specified color"
|
|
uint8_t i = PyLong_AsUnsignedLong(val) & 0xff;
|
|
self->color_table[i] = self->orig_color_table[i];
|
|
self->dirty = true;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
set_color(ColorProfile *self, PyObject *args) {
|
|
#define set_color_doc "Set the specified color"
|
|
unsigned char i;
|
|
unsigned long val;
|
|
if (!PyArg_ParseTuple(args, "Bk", &i, &val)) return NULL;
|
|
self->color_table[i] = val;
|
|
self->dirty = true;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
set_configured_colors(ColorProfile *self, PyObject *args) {
|
|
#define set_configured_colors_doc "Set the configured colors"
|
|
if (!PyArg_ParseTuple(args, "II|III", &(self->configured.default_fg), &(self->configured.default_bg), &(self->configured.cursor_color), &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
|
|
self->dirty = true;
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
void
|
|
copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size_t stride) {
|
|
size_t i;
|
|
stride = MAX(1, stride);
|
|
for (i = 0, buf = buf + offset; i < sizeof(self->color_table)/sizeof(self->color_table[0]); i++, buf += stride) {
|
|
*buf = self->color_table[i];
|
|
}
|
|
self->dirty = false;
|
|
}
|
|
|
|
static PyObject*
|
|
color_table_address(ColorProfile *self, PyObject *a UNUSED) {
|
|
#define color_table_address_doc "Pointer address to start of color table"
|
|
return PyLong_FromVoidPtr((void*)self->color_table);
|
|
}
|
|
|
|
static PyObject*
|
|
default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
|
|
return create_256_color_table();
|
|
}
|
|
|
|
// Boilerplate {{{
|
|
|
|
#define CGETSET(name) \
|
|
static PyObject* name##_get(ColorProfile *self, void UNUSED *closure) { return PyLong_FromUnsignedLong(colorprofile_to_color(self, self->overridden.name, self->configured.name)); } \
|
|
static int name##_set(ColorProfile *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } self->overridden.name = (color_type) PyLong_AsUnsignedLong(val); self->dirty = true; return 0; }
|
|
|
|
CGETSET(default_fg)
|
|
CGETSET(default_bg)
|
|
CGETSET(cursor_color)
|
|
CGETSET(highlight_fg)
|
|
CGETSET(highlight_bg)
|
|
|
|
static PyGetSetDef getsetters[] = {
|
|
GETSET(default_fg)
|
|
GETSET(default_bg)
|
|
GETSET(cursor_color)
|
|
GETSET(highlight_fg)
|
|
GETSET(highlight_bg)
|
|
{NULL} /* Sentinel */
|
|
};
|
|
|
|
|
|
static PyMemberDef members[] = {
|
|
{NULL}
|
|
};
|
|
|
|
static PyMethodDef methods[] = {
|
|
METHOD(update_ansi_color_table, METH_O)
|
|
METHOD(reset_color_table, METH_NOARGS)
|
|
METHOD(color_table_address, METH_NOARGS)
|
|
METHOD(as_color, METH_O)
|
|
METHOD(reset_color, METH_O)
|
|
METHOD(set_color, METH_VARARGS)
|
|
METHOD(set_configured_colors, METH_VARARGS)
|
|
{NULL} /* Sentinel */
|
|
};
|
|
|
|
|
|
PyTypeObject ColorProfile_Type = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
.tp_name = "fast_data_types.ColorProfile",
|
|
.tp_basicsize = sizeof(ColorProfile),
|
|
.tp_dealloc = (destructor)dealloc,
|
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
|
.tp_doc = "ColorProfile",
|
|
.tp_members = members,
|
|
.tp_methods = methods,
|
|
.tp_getset = getsetters,
|
|
.tp_new = new,
|
|
};
|
|
|
|
static PyMethodDef module_methods[] = {
|
|
METHODB(default_color_table, METH_NOARGS),
|
|
METHODB(patch_color_profiles, METH_VARARGS),
|
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
|
};
|
|
|
|
|
|
INIT_TYPE(ColorProfile)
|
|
// }}}
|