Add a remote control command to change colors
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.
This commit is contained in:
parent
2efa83bc4d
commit
1fd84612a8
@ -522,6 +522,14 @@ have ssh do this automatically when connecting to a server, so that all
|
|||||||
terminals work transparently.
|
terminals work transparently.
|
||||||
|
|
||||||
|
|
||||||
|
=== How do I change the colors in a running kitty instance?
|
||||||
|
|
||||||
|
You can either use the
|
||||||
|
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands[OSC
|
||||||
|
terminal escape codes] to set colors or you can enable link:remote-control.asciidoc[remote control]
|
||||||
|
for kitty and use `kitty @ set-colors --help`.
|
||||||
|
|
||||||
|
|
||||||
=== How do I specify command line options for kitty on macOS?
|
=== How do I specify command line options for kitty on macOS?
|
||||||
|
|
||||||
Apple does not want you to use command line options with GUI applications. To
|
Apple does not want you to use command line options with GUI applications. To
|
||||||
|
|||||||
@ -126,24 +126,35 @@ class Boss:
|
|||||||
'tabs': list(tm.list_tabs()),
|
'tabs': list(tm.list_tabs()),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_tab_managers(self):
|
||||||
|
yield from self.os_window_map.values()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_tabs(self):
|
||||||
|
for tm in self.all_tab_managers:
|
||||||
|
yield from tm
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_windows(self):
|
||||||
|
for tab in self.all_tabs:
|
||||||
|
yield from tab
|
||||||
|
|
||||||
def match_windows(self, match):
|
def match_windows(self, match):
|
||||||
try:
|
try:
|
||||||
field, exp = match.split(':', 1)
|
field, exp = match.split(':', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
return
|
||||||
pat = re.compile(exp)
|
pat = re.compile(exp)
|
||||||
for tm in self.os_window_map.values():
|
for window in self.all_windows:
|
||||||
for tab in tm:
|
if window.matches(field, pat):
|
||||||
for window in tab:
|
yield window
|
||||||
if window.matches(field, pat):
|
|
||||||
yield window
|
|
||||||
|
|
||||||
def tab_for_window(self, window):
|
def tab_for_window(self, window):
|
||||||
for tm in self.os_window_map.values():
|
for tab in self.all_tabs:
|
||||||
for tab in tm:
|
for w in tab:
|
||||||
for w in tab:
|
if w.id == window.id:
|
||||||
if w.id == window.id:
|
return tab
|
||||||
return tab
|
|
||||||
|
|
||||||
def match_tabs(self, match):
|
def match_tabs(self, match):
|
||||||
try:
|
try:
|
||||||
@ -151,14 +162,12 @@ class Boss:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
return
|
return
|
||||||
pat = re.compile(exp)
|
pat = re.compile(exp)
|
||||||
tms = tuple(self.os_window_map.values())
|
|
||||||
found = False
|
found = False
|
||||||
if field in ('title', 'id'):
|
if field in ('title', 'id'):
|
||||||
for tm in tms:
|
for tab in self.all_tabs:
|
||||||
for tab in tm:
|
if tab.matches(field, pat):
|
||||||
if tab.matches(field, pat):
|
yield tab
|
||||||
yield tab
|
found = True
|
||||||
found = True
|
|
||||||
if not found:
|
if not found:
|
||||||
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
|
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
|
||||||
for tab in tabs:
|
for tab in tabs:
|
||||||
@ -687,3 +696,12 @@ class Boss:
|
|||||||
tm = self.active_tab_manager
|
tm = self.active_tab_manager
|
||||||
if tm is not None:
|
if tm is not None:
|
||||||
tm.move_tab(-1)
|
tm.move_tab(-1)
|
||||||
|
|
||||||
|
def patch_colors(self, spec, configured=False):
|
||||||
|
from .rgb import color_from_int
|
||||||
|
if configured:
|
||||||
|
for k, v in spec.items():
|
||||||
|
if hasattr(self.opts, k):
|
||||||
|
setattr(self.opts, k, color_from_int(v))
|
||||||
|
for tm in self.all_tab_managers:
|
||||||
|
tm.tab_bar.patch_colors(spec)
|
||||||
|
|||||||
@ -98,6 +98,43 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
|||||||
Py_RETURN_NONE;
|
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
|
color_type
|
||||||
colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
||||||
color_type t = entry & 0xFF, r;
|
color_type t = entry & 0xFF, r;
|
||||||
@ -247,6 +284,7 @@ PyTypeObject ColorProfile_Type = {
|
|||||||
|
|
||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
METHODB(default_color_table, METH_NOARGS),
|
METHODB(default_color_table, METH_NOARGS),
|
||||||
|
METHODB(patch_color_profiles, METH_VARARGS),
|
||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
@ -10,7 +11,7 @@ import types
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from .cli import emph, parse_args
|
from .cli import emph, parse_args
|
||||||
from .config import parse_send_text_bytes
|
from .config import parse_config, parse_send_text_bytes
|
||||||
from .constants import appname, version
|
from .constants import appname, version
|
||||||
from .tabs import SpecialWindow
|
from .tabs import SpecialWindow
|
||||||
from .utils import non_blocking_read, parse_address_spec, read_with_timeout
|
from .utils import non_blocking_read, parse_address_spec, read_with_timeout
|
||||||
@ -467,6 +468,69 @@ def get_text(boss, window, payload):
|
|||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
|
# set_colors {{{
|
||||||
|
@cmd(
|
||||||
|
'Set terminal colors',
|
||||||
|
'Set the terminal colors for the specified windows/tabs (defaults to active window). You can either specify the path to a conf file'
|
||||||
|
' (in the same format as kitty.conf) to read the colors from or you can specify individual colors,'
|
||||||
|
' for example: kitty @ set-colors foreground=red background=white',
|
||||||
|
options_spec='''\
|
||||||
|
--all -a
|
||||||
|
type=bool-set
|
||||||
|
By default, colors are only changed for the currently active window. This option will
|
||||||
|
cause colors to be changed in all windows.
|
||||||
|
|
||||||
|
|
||||||
|
--configured -c
|
||||||
|
type=bool-set
|
||||||
|
Also change the configured colors (i.e. the colors kitty will use for new
|
||||||
|
windows or after a reset).
|
||||||
|
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t'),
|
||||||
|
argspec='COLOR_OR_FILE ...'
|
||||||
|
)
|
||||||
|
def cmd_set_colors(global_opts, opts, args):
|
||||||
|
from .rgb import color_as_int, Color
|
||||||
|
colors = {}
|
||||||
|
for spec in args:
|
||||||
|
if '=' in spec:
|
||||||
|
colors.update(parse_config((spec.replace('=', ' '),)))
|
||||||
|
else:
|
||||||
|
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
|
||||||
|
colors.update(parse_config(f))
|
||||||
|
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
|
||||||
|
return {
|
||||||
|
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||||
|
'all': opts.all, 'configured': opts.configured, 'colors': colors
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def set_colors(boss, window, payload):
|
||||||
|
if payload['all']:
|
||||||
|
windows = tuple(boss.all_windows)
|
||||||
|
else:
|
||||||
|
windows = (window or boss.active_window,)
|
||||||
|
if payload['match_window']:
|
||||||
|
windows = tuple(boss.match_windows(payload['match_window']))
|
||||||
|
if not windows:
|
||||||
|
raise MatchError(payload['match_window'])
|
||||||
|
if payload['match_tab']:
|
||||||
|
tabs = tuple(boss.match_tabs(payload['match_tab']))
|
||||||
|
if not tabs:
|
||||||
|
raise MatchError(payload['match_tab'], 'tabs')
|
||||||
|
for tab in tabs:
|
||||||
|
windows += tuple(tab)
|
||||||
|
profiles = tuple(w.screen.color_profile for w in windows)
|
||||||
|
from .fast_data_types import patch_color_profiles
|
||||||
|
patch_color_profiles(payload['colors'], profiles, payload['configured'])
|
||||||
|
boss.patch_colors(payload['colors'], payload['configured'])
|
||||||
|
default_bg_changed = 'background' in payload['colors']
|
||||||
|
for w in windows:
|
||||||
|
if default_bg_changed:
|
||||||
|
boss.default_bg_changed_for(w.id)
|
||||||
|
w.refresh()
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
4
kitty/rgb.py
generated
4
kitty/rgb.py
generated
@ -31,6 +31,10 @@ def color_from_int(x):
|
|||||||
return Color((x >> 16) & 255, (x >> 8) & 255, x & 255)
|
return Color((x >> 16) & 255, (x >> 8) & 255, x & 255)
|
||||||
|
|
||||||
|
|
||||||
|
def color_as_int(x):
|
||||||
|
return x.red << 16 | x.green << 8 | x.blue
|
||||||
|
|
||||||
|
|
||||||
def to_color(raw, validate=False):
|
def to_color(raw, validate=False):
|
||||||
# See man XParseColor
|
# See man XParseColor
|
||||||
x = raw.strip().lower()
|
x = raw.strip().lower()
|
||||||
|
|||||||
@ -247,6 +247,7 @@ class TabBar: # {{{
|
|||||||
|
|
||||||
def __init__(self, os_window_id, opts):
|
def __init__(self, os_window_id, opts):
|
||||||
self.os_window_id = os_window_id
|
self.os_window_id = os_window_id
|
||||||
|
self.opts = opts
|
||||||
self.num_tabs = 1
|
self.num_tabs = 1
|
||||||
self.cell_width = 1
|
self.cell_width = 1
|
||||||
self.data_buffer_size = 0
|
self.data_buffer_size = 0
|
||||||
@ -277,6 +278,16 @@ class TabBar: # {{{
|
|||||||
self.active_bg = as_rgb(color_as_int(opts.active_tab_background))
|
self.active_bg = as_rgb(color_as_int(opts.active_tab_background))
|
||||||
self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground))
|
self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground))
|
||||||
|
|
||||||
|
def patch_colors(self, spec):
|
||||||
|
if 'active_tab_foreground' in spec:
|
||||||
|
self.active_fg = (spec['active_tab_foreground'] << 8) | 2
|
||||||
|
if 'active_tab_background' in spec:
|
||||||
|
self.active_bg = (spec['active_tab_background'] << 8) | 2
|
||||||
|
self.screen.color_profile.set_configured_colors(
|
||||||
|
spec.get('inactive_tab_foreground', color_as_int(self.opts.inactive_tab_foreground)),
|
||||||
|
spec.get('inactive_tab_background', color_as_int(self.opts.inactive_tab_background))
|
||||||
|
)
|
||||||
|
|
||||||
def layout(self):
|
def layout(self):
|
||||||
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
|
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
|
||||||
if tab_bar.width < 2:
|
if tab_bar.width < 2:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user