Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a52127c55 | ||
|
|
4920431c3a | ||
|
|
773f26b61d | ||
|
|
16e77d7329 | ||
|
|
2cec0908a5 | ||
|
|
b3ef62c188 | ||
|
|
3385f6fe67 | ||
|
|
0170bf636b | ||
|
|
b2561fd61e | ||
|
|
64b48945d4 | ||
|
|
bbfd463357 | ||
|
|
2179dab8ed | ||
|
|
801ead7f7f | ||
|
|
9e4834d58b | ||
|
|
f38e089de0 | ||
|
|
b252504306 | ||
|
|
81bcdaf48a | ||
|
|
44f654ff86 | ||
|
|
84cfa9382f | ||
|
|
c6da374aa9 | ||
|
|
d9fe0b2540 | ||
|
|
5a905dfe7f | ||
|
|
09b6d6e62b | ||
|
|
6aa26cf75f | ||
|
|
bae8915881 | ||
|
|
9c46de319e | ||
|
|
994eecadb7 | ||
|
|
f7f9ceec34 | ||
|
|
527255e3a1 | ||
|
|
0333da991d | ||
|
|
2bf8e1b1a3 | ||
|
|
ffc717717c | ||
|
|
2c8414db5d | ||
|
|
32473d7dc7 | ||
|
|
079f10eebd | ||
|
|
ae1f305a81 | ||
|
|
0b1f5209d5 | ||
|
|
fc9980903d | ||
|
|
ad87131926 | ||
|
|
4dfe1b664f | ||
|
|
d7b2ac97b5 | ||
|
|
fd9dac4ca4 | ||
|
|
8ca9348b07 | ||
|
|
5cc18fdb4b | ||
|
|
51b6b325b9 | ||
|
|
b3cc6f3802 | ||
|
|
1295094715 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
tags
|
||||
build
|
||||
linux-package
|
||||
kitty.app
|
||||
logo/*.iconset
|
||||
compile_commands.json
|
||||
glad/out
|
||||
|
||||
@@ -3,6 +3,29 @@ Changelog
|
||||
|
||||
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
version 0.8.2 [2018-03-17]
|
||||
-----------------------------
|
||||
|
||||
- Allow extending existing selections by right clicking
|
||||
|
||||
- Add a configurable keyboard shortcut and remote command to set the font size to a specific value
|
||||
|
||||
- Add an option to have kitty close the window when the main processes running in it exits, even if there are still background processes writing to that terminal
|
||||
|
||||
- Add configurable keyboard shortcuts to switch to a specific layout
|
||||
|
||||
- Add a keyboard shortcut to edit the kitty config file easily
|
||||
|
||||
- macOS: Fix restoring of window size not correct on Retina screens
|
||||
|
||||
- macOS: Add a facility to specify command line arguments when running kitty from the GUI
|
||||
|
||||
- Add a focus-tab remote command
|
||||
|
||||
- Fix screen not being refreshed immediately after moving a window.
|
||||
|
||||
- Fix a crash when getting the contents of the scrollback buffer as text
|
||||
|
||||
version 0.8.1 [2018-03-09]
|
||||
-----------------------------
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
:sc_close_window: pass:quotes[`ctrl+shift+w`]
|
||||
:sc_copy_to_clipboard: pass:quotes[`ctrl+shift+c`]
|
||||
:sc_decrease_font_size: pass:quotes[`ctrl+shift+minus`]
|
||||
:sc_edit_config_file: pass:quotes[`ctrl+shift+f2`]
|
||||
:sc_eighth_window: pass:quotes[`ctrl+shift+8`]
|
||||
:sc_fifth_window: pass:quotes[`ctrl+shift+5`]
|
||||
:sc_first_window: pass:quotes[`ctrl+shift+1`]
|
||||
@@ -234,6 +235,7 @@ windows are:
|
||||
|Input unicode character | {sc_input_unicode_character}
|
||||
|Click URL using the keyboard | {sc_run_simple_kitten_text_url_hints}
|
||||
|Pass current selection to program | {sc_pass_selection_to_program}
|
||||
|Edit kitty config file| {sc_edit_config_file}
|
||||
|===
|
||||
|
||||
|
||||
@@ -241,10 +243,11 @@ windows are:
|
||||
|
||||
You can control kitty from the command line/scripts by sending it messages.
|
||||
You can tell kitty to open/close/rename tabs and windows. You can even send
|
||||
arbitrary input/text to any specified window. Messages can be sent using
|
||||
`kitty @`. Note that you must set `allow_remote_control yes` in your
|
||||
kitty.conf to use this feature. It even works over SSH connections. This
|
||||
feature is best illustrated with a link:remote-control.asciidoc[tutorial].
|
||||
arbitrary input/text to any specified window. Messages can be sent using `kitty
|
||||
@`. Note that you must set `allow_remote_control yes` in your
|
||||
link:kitty/kitty.conf[kitty.conf] to use this feature. It even works over SSH
|
||||
connections. This feature is best illustrated with a
|
||||
link:remote-control.asciidoc[tutorial].
|
||||
|
||||
|
||||
== The scrollback buffer
|
||||
@@ -286,8 +289,9 @@ image::hints_mode.png?raw=true[URL hints mode]
|
||||
|
||||
== Miscellaneous features
|
||||
|
||||
You can double click to select a word and triple click to select a line. You can also
|
||||
hold down `ctrl+shift` and click on a URL to open it in a browser.
|
||||
* You can also hold down `ctrl+shift` and click on a URL to open it in a browser.
|
||||
* You can double click to select a word and triple click to select a line.
|
||||
* You can right click to extend a previous selection
|
||||
|
||||
|
||||
== Layouts
|
||||
@@ -300,7 +304,9 @@ Currently, there are five layouts available,
|
||||
* Horizontal -- All windows are shown side-by-side
|
||||
* Vertical -- All windows are shown one below the other
|
||||
|
||||
You can switch between layouts using the {sc_next_layout} key combination.
|
||||
You can switch between layouts using the {sc_next_layout} key combination. You can
|
||||
also create shortcuts to select particular layouts, and choose which layouts
|
||||
you want to enable/disable, see link:kitty/kitty.conf[kitty.conf] for examples.
|
||||
|
||||
Writing a new layout only requires about fifty lines of code, so if there is
|
||||
some layout you want, take a look at link:kitty/layout.py[layout.py] and submit
|
||||
@@ -309,13 +315,13 @@ a pull request!
|
||||
|
||||
== Configuration
|
||||
|
||||
kitty is highly customizable, everything from keyboard shortcuts, to
|
||||
painting frames-per-second. See the heavily commented
|
||||
link:kitty/kitty.conf[default config file]. By default kitty looks for a
|
||||
config file in the OS config directory (usually
|
||||
`~/.config/kitty/kitty.conf` on Linux and
|
||||
kitty is highly customizable, everything from keyboard shortcuts, to painting
|
||||
frames-per-second. See the heavily commented link:kitty/kitty.conf[default
|
||||
config file]. By default kitty looks for a config file in the OS config
|
||||
directory (usually `~/.config/kitty/kitty.conf` on Linux and
|
||||
`~/Library/Preferences/kitty/kitty.conf` on macOS) but you can pass a specific
|
||||
path via the `--config` option.
|
||||
path via the `--config` option or use the `KITTY_CONFIG_DIRECTORY` environment
|
||||
variable.
|
||||
|
||||
== Startup Sessions
|
||||
|
||||
@@ -449,12 +455,12 @@ let the character overflow into neighboring cells, which is fine if the
|
||||
neighboring cell is empty, but looks terrible if it is not.
|
||||
|
||||
Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
|
||||
use unicode characters from the private use area to represent symbols. Often
|
||||
misuse unicode characters from the private use area to represent symbols. Often
|
||||
these symbols are square and should be rendered in two cells. However, since
|
||||
private use area symbols all have their width set to one in the unicode
|
||||
standard, kitty renders them either smaller or truncated. The exception (as of
|
||||
kitty 0.8.1) is if these characters are followed by a space or empty cell in
|
||||
which case kitty makes use of the extra cell to render them in two cells.
|
||||
standard, kitty renders them either smaller or truncated. The exception is if
|
||||
these characters are followed by a space or empty cell in which case kitty
|
||||
makes use of the extra cell to render them in two cells.
|
||||
|
||||
=== How do I build kitty.app on macOS?
|
||||
|
||||
@@ -489,10 +495,11 @@ let &t_ut=''
|
||||
See link:https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc#setting-text-styles-colors-in-arbitrary-regions-of-the-screen[here]
|
||||
for why kitty does not support background color erase.
|
||||
|
||||
== I get errors about the terminal being unknown or opening the terminal failing when SSHing into a different computer?
|
||||
=== I get errors about the terminal being unknown or opening the terminal failing when SSHing into a different computer?
|
||||
|
||||
This happens because the kitty terminfo files are not available on the server. Either install kitty on the server,
|
||||
or simply copy over the terminfo files, using:
|
||||
This happens because the kitty terminfo files are not available on the server.
|
||||
Either install kitty on the server, or simply copy over the terminfo files,
|
||||
using:
|
||||
|
||||
....
|
||||
ssh myserver mkdir -p \~/.terminfo
|
||||
@@ -500,8 +507,24 @@ infocmp xterm-kitty | ssh myserver "cat > /tmp/kitty.terminfo"
|
||||
ssh myserver tic -x -o \~/.terminfo /tmp/kitty.terminfo
|
||||
....
|
||||
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to have ssh do this
|
||||
automatically when connecting to a server.
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have ssh do this automatically when connecting to a server, so that all
|
||||
terminals work transparently.
|
||||
|
||||
|
||||
=== 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
|
||||
workaround that limitation, kitty will read command line options from the file
|
||||
`~/Library/Preferences/kitty/macos-launch-services-cmdline` when it is launched
|
||||
from the GUI, i.e. by clicking the kitty application icon or using `open -a kitty`.
|
||||
Note that this file is *only read* when running via the GUI.
|
||||
|
||||
You can, of course, also run kitty from a terminal with command line options, using:
|
||||
`/Applications/kitty.app/Contents/MacOS/kitty`.
|
||||
|
||||
And within kitty itself, you can always run kitty using just `kitty` as it
|
||||
cleverly adds itself to the PATH.
|
||||
|
||||
== Resources on terminal behavior
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty
|
||||
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h\nkittens/unicode_input/names.h') kitty kittens
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -11,7 +10,7 @@ from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.config import cached_values_for
|
||||
from kitty.constants import config_dir
|
||||
from kitty.constants import config_dir, editor
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import (
|
||||
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
|
||||
@@ -426,7 +425,6 @@ class UnicodeInput(Handler):
|
||||
if not os.path.exists(favorites_path):
|
||||
with open(favorites_path, 'wb') as f:
|
||||
f.write(serialize_favorites(load_favorites()).encode('utf-8'))
|
||||
editor = shlex.split(os.environ.get('EDITOR', 'vim'))
|
||||
with self.suspend():
|
||||
p = subprocess.Popen(editor + [favorites_path])
|
||||
if p.wait() == 0:
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "names.h"
|
||||
|
||||
static PyObject*
|
||||
all_words(PyObject *self UNUSED) {
|
||||
all_words(PYNOARG) {
|
||||
PyObject *ans = PyTuple_New(arraysz(idx_to_word));
|
||||
if (!ans) return NULL;
|
||||
for (size_t i = 0; i < arraysz(idx_to_word); i++) {
|
||||
|
||||
@@ -10,8 +10,10 @@ from gettext import gettext as _
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from .cli import create_opts, parse_args
|
||||
from .config import MINIMUM_FONT_SIZE, initial_window_size
|
||||
from .constants import appname, set_boss
|
||||
from .config import (
|
||||
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
|
||||
)
|
||||
from .constants import appname, editor, set_boss
|
||||
from .fast_data_types import (
|
||||
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
|
||||
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
|
||||
@@ -122,7 +124,10 @@ class Boss:
|
||||
}
|
||||
|
||||
def match_windows(self, match):
|
||||
field, exp = match.split(':', 1)
|
||||
try:
|
||||
field, exp = match.split(':', 1)
|
||||
except ValueError:
|
||||
return
|
||||
pat = re.compile(exp)
|
||||
for tm in self.os_window_map.values():
|
||||
for tab in tm:
|
||||
@@ -138,7 +143,10 @@ class Boss:
|
||||
return tab
|
||||
|
||||
def match_tabs(self, match):
|
||||
field, exp = match.split(':', 1)
|
||||
try:
|
||||
field, exp = match.split(':', 1)
|
||||
except ValueError:
|
||||
return
|
||||
pat = re.compile(exp)
|
||||
tms = tuple(self.os_window_map.values())
|
||||
found = False
|
||||
@@ -188,7 +196,9 @@ class Boss:
|
||||
response = handle_cmd(self, window, cmd)
|
||||
except Exception as err:
|
||||
import traceback
|
||||
response = {'ok': False, 'error': str(err), 'tb': traceback.format_exc()}
|
||||
response = {'ok': False, 'error': str(err)}
|
||||
if not getattr(err, 'hide_traceback', False):
|
||||
response['tb'] = traceback.format_exc()
|
||||
else:
|
||||
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
|
||||
return response
|
||||
@@ -286,19 +296,16 @@ class Boss:
|
||||
tm.resize()
|
||||
|
||||
def increase_font_size(self):
|
||||
self.change_font_size(
|
||||
self.set_font_size(
|
||||
min(
|
||||
self.opts.font_size * 5, self.current_font_size +
|
||||
self.opts.font_size_delta))
|
||||
|
||||
def decrease_font_size(self):
|
||||
self.change_font_size(
|
||||
max(
|
||||
MINIMUM_FONT_SIZE, self.current_font_size -
|
||||
self.opts.font_size_delta))
|
||||
self.set_font_size(self.current_font_size - self.opts.font_size_delta)
|
||||
|
||||
def restore_font_size(self):
|
||||
self.change_font_size(self.opts.font_size)
|
||||
self.set_font_size(self.opts.font_size)
|
||||
|
||||
def _change_font_size(self, new_size=None, on_dpi_change=False):
|
||||
if new_size is not None:
|
||||
@@ -316,7 +323,8 @@ class Boss:
|
||||
tm.refresh_sprite_positions()
|
||||
glfw_post_empty_event()
|
||||
|
||||
def change_font_size(self, new_size):
|
||||
def set_font_size(self, new_size):
|
||||
new_size = max(MINIMUM_FONT_SIZE, new_size)
|
||||
if new_size == self.current_font_size:
|
||||
return
|
||||
self._change_font_size(new_size)
|
||||
@@ -410,6 +418,13 @@ class Boss:
|
||||
SpecialWindow(
|
||||
self.opts.scrollback_pager, data, _('History'), overlay_for=window.id))
|
||||
|
||||
def edit_config_file(self, *a):
|
||||
confpath = prepare_config_file_for_editing()
|
||||
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
|
||||
# small delay.
|
||||
cmd = ['kitty', '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + editor + [confpath]
|
||||
self.new_os_window(*cmd)
|
||||
|
||||
def input_unicode_character(self):
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
@@ -507,6 +522,11 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.goto_tab(tab_num - 1)
|
||||
|
||||
def set_active_tab(self, tab):
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.set_active_tab(tab)
|
||||
|
||||
def next_tab(self):
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
|
||||
@@ -71,7 +71,7 @@ static unsigned long remove_notify[MAX_CHILDREN] = {0};
|
||||
static size_t add_queue_count = 0, remove_queue_count = 0;
|
||||
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
|
||||
static pthread_mutex_t children_lock;
|
||||
static bool signal_received = false;
|
||||
static bool kill_signal_received = false;
|
||||
static ChildMonitor *the_monitor = NULL;
|
||||
static uint8_t drain_buf[1024];
|
||||
static int signal_fds[2], wakeup_fds[2];
|
||||
@@ -139,10 +139,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
if (!self_pipe(wakeup_fds)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (!self_pipe(signal_fds)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
struct sigaction act = {.sa_handler=handle_signal};
|
||||
if (sigaction(SIGINT, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (sigaction(SIGTERM, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (siginterrupt(SIGINT, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (siginterrupt(SIGTERM, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
#define SA(which) { if (sigaction(which, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError); if (siginterrupt(which, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);}
|
||||
SA(SIGINT); SA(SIGTERM); SA(SIGCHLD);
|
||||
#undef SA
|
||||
self = (ChildMonitor *)type->tp_alloc(type, 0);
|
||||
self->talk_fd = talk_fd;
|
||||
self->listen_fd = listen_fd;
|
||||
@@ -199,8 +198,9 @@ static void wakeup_talk_loop(bool);
|
||||
static bool talk_thread_started = false;
|
||||
|
||||
static PyObject *
|
||||
start(ChildMonitor *self) {
|
||||
start(PyObject *s, PyObject *a UNUSED) {
|
||||
#define start_doc "start() -> Start the I/O thread"
|
||||
ChildMonitor *self = (ChildMonitor*)s;
|
||||
if (self->talk_fd > -1 || self->listen_fd > -1) {
|
||||
if (pthread_create(&self->talk_thread, NULL, talk_loop, self) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
talk_thread_started = true;
|
||||
@@ -213,7 +213,7 @@ start(ChildMonitor *self) {
|
||||
|
||||
|
||||
static PyObject *
|
||||
wakeup(ChildMonitor UNUSED *self) {
|
||||
wakeup(PYNOARG) {
|
||||
#define wakeup_doc "wakeup() -> wakeup the ChildMonitor I/O thread, forcing it to exit from poll() if it is waiting there."
|
||||
wakeup_io_loop(false);
|
||||
Py_RETURN_NONE;
|
||||
@@ -286,10 +286,11 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
shutdown_monitor(ChildMonitor *self) {
|
||||
shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
#define shutdown_monitor_doc "shutdown_monitor() -> Shutdown the monitor loop."
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGTERM, SIG_DFL);
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
self->shutting_down = true;
|
||||
wakeup_talk_loop(false);
|
||||
wakeup_io_loop(false);
|
||||
@@ -344,7 +345,7 @@ parse_input(ChildMonitor *self) {
|
||||
} else fatal("Out of memory");
|
||||
}
|
||||
|
||||
if (UNLIKELY(signal_received)) {
|
||||
if (UNLIKELY(kill_signal_received)) {
|
||||
global_state.close_all_windows = true;
|
||||
} else {
|
||||
count = self->count;
|
||||
@@ -568,7 +569,8 @@ simple_render_screen(PyObject UNUSED *self, PyObject *args) {
|
||||
static inline bool
|
||||
prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows) {
|
||||
#define TD os_window->tab_bar_render_data
|
||||
bool needs_render = false;
|
||||
bool needs_render = os_window->needs_render;
|
||||
os_window->needs_render = false;
|
||||
if (TD.screen && os_window->num_tabs > 1) {
|
||||
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
|
||||
}
|
||||
@@ -734,7 +736,7 @@ process_pending_resizes(double now) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
main_loop(ChildMonitor *self) {
|
||||
main_loop(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
#define main_loop_doc "The main thread loop"
|
||||
bool has_open_windows = true;
|
||||
|
||||
@@ -753,7 +755,7 @@ main_loop(ChildMonitor *self) {
|
||||
OSWindow *os_window = global_state.os_windows + w - 1;
|
||||
if (should_os_window_close(os_window)) {
|
||||
destroy_os_window(os_window);
|
||||
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->viewport_width, os_window->viewport_height);
|
||||
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
|
||||
for (size_t t=0; t < os_window->num_tabs; t++) {
|
||||
Tab *tab = os_window->tabs + t;
|
||||
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
|
||||
@@ -873,6 +875,58 @@ drain_fd(int fd) {
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
read_signals(int fd, bool *kill_signal, bool *child_died) {
|
||||
static char buf[256];
|
||||
while(true) {
|
||||
ssize_t len = read(fd, buf, sizeof(buf));
|
||||
if (len < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno != EIO) perror("Call to read() from read_signals() failed");
|
||||
break;
|
||||
}
|
||||
for (ssize_t i = 0; i < len; i++) {
|
||||
switch(buf[i]) {
|
||||
case SIGCHLD:
|
||||
*child_died = true; break;
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
*kill_signal = true; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
mark_child_for_removal(ChildMonitor *self, pid_t pid) {
|
||||
children_mutex(lock);
|
||||
for (size_t i = 0; i < self->count; i++) {
|
||||
if (children[i].pid == pid) {
|
||||
children[i].needs_removal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
children_mutex(unlock);
|
||||
}
|
||||
|
||||
static inline void
|
||||
reap_children(ChildMonitor *self, bool enable_close_on_child_death) {
|
||||
int status;
|
||||
pid_t pid;
|
||||
(void)self;
|
||||
while(true) {
|
||||
pid = waitpid(-1, &status, WNOHANG);
|
||||
if (pid == -1) {
|
||||
if (errno != EINTR) break;
|
||||
} else if (pid > 0) {
|
||||
if (enable_close_on_child_death) mark_child_for_removal(self, pid);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
write_to_child(int fd, Screen *screen) {
|
||||
size_t written = 0;
|
||||
@@ -924,10 +978,10 @@ io_loop(void *data) {
|
||||
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
|
||||
if (fds[1].revents && POLLIN) {
|
||||
data_received = true;
|
||||
drain_fd(fds[1].fd);
|
||||
children_mutex(lock);
|
||||
signal_received = true;
|
||||
children_mutex(unlock);
|
||||
bool kill_signal = false, child_died = false;
|
||||
read_signals(fds[1].fd, &kill_signal, &child_died);
|
||||
if (kill_signal) { children_mutex(lock); kill_signal_received = true; children_mutex(unlock); }
|
||||
if (child_died) reap_children(self, OPT(close_on_child_death));
|
||||
}
|
||||
for (i = 0; i < self->count; i++) {
|
||||
if (fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) {
|
||||
@@ -1256,7 +1310,7 @@ PyTypeObject ChildMonitor_Type = {
|
||||
};
|
||||
|
||||
static PyObject*
|
||||
safe_pipe(PyObject *self UNUSED) {
|
||||
safe_pipe(PYNOARG) {
|
||||
int fds[2] = {0};
|
||||
if (!self_pipe(fds)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
return Py_BuildValue("ii", fds[0], fds[1]);
|
||||
|
||||
@@ -286,8 +286,9 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
lines.append('')
|
||||
a((' ' * leading_indent) + j.join(lines))
|
||||
|
||||
usage = usage or '[program-to-run ...]'
|
||||
a('{}: {} [options] {}'.format(title('Usage'), bold(yellow(appname)), usage))
|
||||
usage = '[program-to-run ...]' if usage is None else usage
|
||||
optstring = '[options] ' if seq else ''
|
||||
a('{}: {} {}{}'.format(title('Usage'), bold(yellow(appname)), optstring, usage))
|
||||
a('')
|
||||
message = message or (
|
||||
'Run the |G {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal'
|
||||
|
||||
@@ -59,11 +59,35 @@ find_app_name(void) {
|
||||
return @"kitty";
|
||||
}
|
||||
|
||||
@interface GlobalMenuTarget : NSObject
|
||||
+ (GlobalMenuTarget *) shared_instance;
|
||||
@end
|
||||
|
||||
@implementation GlobalMenuTarget
|
||||
|
||||
- (void) show_preferences : (id)sender {
|
||||
(void)sender;
|
||||
call_boss(edit_config_file, NULL);
|
||||
}
|
||||
|
||||
+ (GlobalMenuTarget *) shared_instance
|
||||
{
|
||||
static GlobalMenuTarget *sharedGlobalMenuTarget = nil;
|
||||
@synchronized(self)
|
||||
{
|
||||
if (!sharedGlobalMenuTarget)
|
||||
sharedGlobalMenuTarget = [[GlobalMenuTarget alloc] init];
|
||||
return sharedGlobalMenuTarget;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void
|
||||
cocoa_create_global_menu(void) {
|
||||
NSString* app_name = find_app_name();
|
||||
NSMenu* bar = [[NSMenu alloc] init];
|
||||
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
|
||||
[NSApp setMainMenu:bar];
|
||||
|
||||
NSMenuItem* appMenuItem =
|
||||
@@ -75,6 +99,9 @@ cocoa_create_global_menu(void) {
|
||||
action:@selector(orderFrontStandardAboutPanel:)
|
||||
keyEquivalent:@""];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
NSMenuItem* preferences_menu_item = [[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(show_preferences:) keyEquivalent:@","];
|
||||
[preferences_menu_item setTarget:global_menu_target];
|
||||
[appMenu addItem:preferences_menu_item];
|
||||
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"];
|
||||
@@ -124,7 +151,7 @@ cocoa_create_global_menu(void) {
|
||||
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
|
||||
[NSApp setWindowsMenu:windowMenu];
|
||||
[windowMenu release];
|
||||
|
||||
[preferences_menu_item release];
|
||||
|
||||
[bar release];
|
||||
}
|
||||
|
||||
@@ -133,7 +133,7 @@ as_color(ColorProfile *self, PyObject *val) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
reset_color_table(ColorProfile *self) {
|
||||
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;
|
||||
@@ -179,7 +179,7 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
color_table_address(ColorProfile *self) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ from .config_utils import (
|
||||
init_config, parse_config_base, positive_float, positive_int, to_bool,
|
||||
to_color, unit_float
|
||||
)
|
||||
from .constants import cache_dir
|
||||
from .constants import cache_dir, defconf
|
||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||
from .layout import all_layouts
|
||||
from .utils import log_error
|
||||
@@ -111,6 +111,10 @@ def parse_key_action(action):
|
||||
args = rest.split(' ', 2)
|
||||
elif func == 'goto_tab':
|
||||
args = (max(0, int(rest)), )
|
||||
elif func == 'goto_layout':
|
||||
args = [rest]
|
||||
elif func == 'set_font_size':
|
||||
args = (float(rest),)
|
||||
elif func in shlex_actions:
|
||||
args = shlex.split(rest)
|
||||
return KeyAction(func, args)
|
||||
@@ -270,6 +274,7 @@ type_map = {
|
||||
'repaint_delay': positive_int,
|
||||
'input_delay': positive_int,
|
||||
'sync_to_monitor': to_bool,
|
||||
'close_on_child_death': to_bool,
|
||||
'window_border_width': positive_float,
|
||||
'window_margin_width': positive_float,
|
||||
'window_padding_width': positive_float,
|
||||
@@ -348,7 +353,7 @@ def parse_config(lines, check_keys=True):
|
||||
|
||||
Options, defaults = init_config(default_config_path, parse_config)
|
||||
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset(
|
||||
'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
|
||||
'combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
|
||||
split()
|
||||
)
|
||||
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
|
||||
@@ -463,3 +468,27 @@ def initial_window_size(opts, cached_values):
|
||||
except Exception:
|
||||
log_error('Invalid cached window size, ignoring')
|
||||
return w, h
|
||||
|
||||
|
||||
def commented_out_default_config():
|
||||
with open(default_config_path, encoding='utf-8', errors='replace') as f:
|
||||
config = f.read()
|
||||
lines = []
|
||||
for line in config.splitlines():
|
||||
if line.strip() and not line.startswith('#'):
|
||||
line = '# ' + line
|
||||
lines.append(line)
|
||||
config = '\n'.join(lines)
|
||||
return config
|
||||
|
||||
|
||||
def prepare_config_file_for_editing():
|
||||
if not os.path.exists(defconf):
|
||||
d = os.path.dirname(defconf)
|
||||
try:
|
||||
os.makedirs(d)
|
||||
except FileExistsError:
|
||||
pass
|
||||
with open(defconf, 'w') as f:
|
||||
f.write(commented_out_default_config())
|
||||
return defconf
|
||||
|
||||
@@ -56,7 +56,7 @@ def parse_config_base(
|
||||
|
||||
|
||||
def init_config(defaults_path, parse_config):
|
||||
with open(defaults_path, encoding='utf-8') as f:
|
||||
with open(defaults_path, encoding='utf-8', errors='replace') as f:
|
||||
defaults = parse_config(f.read().splitlines(), check_keys=False)
|
||||
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
||||
defaults = Options(**defaults)
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
|
||||
import os
|
||||
import pwd
|
||||
import shlex
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from .fast_data_types import set_boss as set_c_boss
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 8, 1)
|
||||
version = (0, 8, 2)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
editor = shlex.split(os.environ.get('EDITOR', 'vim'))
|
||||
|
||||
|
||||
ScreenGeometry = namedtuple('ScreenGeometry', 'xstart ystart xnum ynum dx dy')
|
||||
|
||||
@@ -253,7 +253,7 @@ cursor_as_sgr(Cursor *self, Cursor *prev) {
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
reset_display_attrs(Cursor *self) {
|
||||
reset_display_attrs(Cursor *self, PyObject *a UNUSED) {
|
||||
#define reset_display_attrs_doc "Reset all display attributes to unset"
|
||||
cursor_reset_display_attrs(self);
|
||||
Py_RETURN_NONE;
|
||||
@@ -272,7 +272,7 @@ void cursor_copy_to(Cursor *src, Cursor *dest) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
copy(Cursor *self);
|
||||
copy(Cursor *self, PyObject*);
|
||||
#define copy_doc "Create a clone of this cursor"
|
||||
|
||||
// Boilerplate {{{
|
||||
@@ -342,7 +342,7 @@ cursor_copy(Cursor *self) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
copy(Cursor *self) {
|
||||
copy(Cursor *self, PyObject *a UNUSED) {
|
||||
return (PyObject*)cursor_copy(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <stddef.h>
|
||||
#include <termios.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#ifdef WITH_PROFILER
|
||||
#include <gperftools/profiler.h>
|
||||
#endif
|
||||
@@ -88,31 +87,6 @@ pyset_iutf8(PyObject UNUSED *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_sigchld(int UNUSED signum, siginfo_t *sinfo, void UNUSED *unused) {
|
||||
if (sinfo->si_code != CLD_EXITED) return;
|
||||
int sav_errno = errno, status;
|
||||
while(true) {
|
||||
if (waitpid(sinfo->si_pid, &status, WNOHANG) == -1) {
|
||||
if (errno != EINTR) break;
|
||||
} else break;
|
||||
}
|
||||
// wakeup I/O loop as without this on macOS sometimes poll() does not detect the fd close, so
|
||||
// kitty does not detect child death.
|
||||
wakeup_io_loop(true);
|
||||
errno = sav_errno;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
install_sigchld_handler(PyObject UNUSED *self) {
|
||||
struct sigaction sa;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sa.sa_sigaction = handle_sigchld;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
if (sigaction(SIGCHLD, &sa, NULL) == -1) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#ifdef WITH_PROFILER
|
||||
static PyObject*
|
||||
start_profiler(PyObject UNUSED *self, PyObject *args) {
|
||||
@@ -135,7 +109,6 @@ static PyMethodDef module_methods[] = {
|
||||
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
|
||||
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
|
||||
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||
{"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""},
|
||||
#ifdef __APPLE__
|
||||
METHODB(user_cache_dir, METH_NOARGS),
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define OPENGL_REQUIRED_VERSION_MAJOR 3
|
||||
#define OPENGL_REQUIRED_VERSION_MINOR 3
|
||||
#define UNUSED __attribute__ ((unused))
|
||||
#define PYNOARG PyObject *__a1 UNUSED, PyObject *__a2 UNUSED
|
||||
#define EXPORTED __attribute__ ((visibility ("default")))
|
||||
#define LIKELY(x) __builtin_expect (!!(x), 1)
|
||||
#define UNLIKELY(x) __builtin_expect (!!(x), 0)
|
||||
@@ -208,7 +209,7 @@ typedef struct {
|
||||
|
||||
#define clear_sprite_position(cell) (cell).sprite_x = 0; (cell).sprite_y = 0; (cell).sprite_z = 0;
|
||||
|
||||
#define left_shift_line(line, at, num) \
|
||||
#define left_shift_line(line, at, num) { \
|
||||
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
|
||||
COPY_CELL(line, __i__ + (num), line, __i__) \
|
||||
} \
|
||||
@@ -216,7 +217,8 @@ typedef struct {
|
||||
(line)->cells[(at)].ch = BLANK_CHAR; \
|
||||
(line)->cells[(at)].attrs = BLANK_CHAR ? 1 : 0; \
|
||||
clear_sprite_position((line)->cells[(at)]); \
|
||||
}
|
||||
}\
|
||||
}
|
||||
|
||||
#define ensure_space_for(base, array, type, num, capacity, initial_cap, zero_mem) \
|
||||
if ((base)->capacity < num) { \
|
||||
|
||||
@@ -1066,7 +1066,7 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
current_fonts(PyObject UNUSED *self) {
|
||||
current_fonts(PYNOARG) {
|
||||
PyObject *ans = PyDict_New();
|
||||
if (!ans) return NULL;
|
||||
#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fonts.fonts[val].face) != 0) { goto error; }}
|
||||
@@ -1097,7 +1097,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
||||
static Py_UCS4 char_buf[2 + arraysz(cell.cc_idx)];
|
||||
if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL;
|
||||
cell.ch = char_buf[0];
|
||||
for (unsigned i = 0; i + 1 < PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
|
||||
for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
|
||||
if (bold) cell.attrs |= 1 << BOLD_SHIFT;
|
||||
if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
|
||||
ssize_t ans = fallback_font(&cell);
|
||||
|
||||
@@ -532,7 +532,8 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
display_name(Face *self) {
|
||||
display_name(PyObject *s, PyObject *a UNUSED) {
|
||||
Face *self = (Face*)s;
|
||||
const char *psname = FT_Get_Postscript_Name(self->face);
|
||||
if (psname) return Py_BuildValue("s", psname);
|
||||
Py_INCREF(self->path);
|
||||
@@ -540,8 +541,8 @@ display_name(Face *self) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
extra_data(Face *self) {
|
||||
return PyLong_FromVoidPtr(self->extra_data);
|
||||
extra_data(PyObject *self, PyObject *a UNUSED) {
|
||||
return PyLong_FromVoidPtr(((Face*)self)->extra_data);
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
24
kitty/glfw.c
24
kitty/glfw.c
@@ -30,6 +30,8 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
|
||||
window->has_pending_resizes = false;
|
||||
window->viewport_width = MAX(window->viewport_width, 100);
|
||||
window->viewport_height = MAX(window->viewport_height, 100);
|
||||
window->window_width = MAX(w, 100);
|
||||
window->window_height = MAX(h, 100);
|
||||
if (notify_boss) {
|
||||
call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
|
||||
}
|
||||
@@ -481,7 +483,7 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_terminate(PyObject UNUSED *self) {
|
||||
glfw_terminate(PYNOARG) {
|
||||
glfwTerminate();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -499,13 +501,13 @@ glfw_wait_events(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_post_empty_event(PyObject UNUSED *self) {
|
||||
glfw_post_empty_event(PYNOARG) {
|
||||
glfwPostEmptyEvent();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_poll_events(PyObject UNUSED *self) {
|
||||
glfw_poll_events(PYNOARG) {
|
||||
glfwPollEvents();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -523,7 +525,7 @@ get_physical_dpi(GLFWmonitor *m) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_get_physical_dpi(PyObject UNUSED *self) {
|
||||
glfw_get_physical_dpi(PYNOARG) {
|
||||
GLFWmonitor *m = glfwGetPrimaryMonitor();
|
||||
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
|
||||
return get_physical_dpi(m);
|
||||
@@ -548,14 +550,14 @@ glfw_window_hint(PyObject UNUSED *self, PyObject *args) {
|
||||
// }}}
|
||||
|
||||
static PyObject*
|
||||
get_clipboard_string(PyObject UNUSED *self) {
|
||||
get_clipboard_string(PYNOARG) {
|
||||
OSWindow *w = current_os_window();
|
||||
if (w) return Py_BuildValue("s", glfwGetClipboardString(w->handle));
|
||||
return Py_BuildValue("s", "");
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_content_scale_for_window(PyObject UNUSED *self) {
|
||||
get_content_scale_for_window(PYNOARG) {
|
||||
OSWindow *w = global_state.callback_os_window ? global_state.callback_os_window : global_state.os_windows;
|
||||
float xscale, yscale;
|
||||
glfwGetWindowContentScale(w->handle, &xscale, &yscale);
|
||||
@@ -572,7 +574,7 @@ set_clipboard_string(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
toggle_fullscreen(PyObject UNUSED *self) {
|
||||
toggle_fullscreen(PYNOARG) {
|
||||
GLFWmonitor *monitor;
|
||||
OSWindow *w = current_os_window();
|
||||
if (!w) Py_RETURN_NONE;
|
||||
@@ -662,14 +664,14 @@ should_os_window_close(OSWindow* w) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
primary_monitor_size(PyObject UNUSED *self) {
|
||||
primary_monitor_size(PYNOARG) {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
return Py_BuildValue("ii", mode->width, mode->height);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
primary_monitor_content_scale(PyObject UNUSED *self) {
|
||||
primary_monitor_content_scale(PYNOARG) {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
float xscale, yscale;
|
||||
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||
@@ -677,7 +679,7 @@ primary_monitor_content_scale(PyObject UNUSED *self) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
x11_display(PyObject UNUSED *self) {
|
||||
x11_display(PYNOARG) {
|
||||
if (glfwGetX11Display) {
|
||||
return PyLong_FromVoidPtr(glfwGetX11Display());
|
||||
} else log_error("Failed to load glfwGetX11Display");
|
||||
@@ -699,7 +701,7 @@ x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_primary_selection(PyObject UNUSED *self) {
|
||||
get_primary_selection(PYNOARG) {
|
||||
if (glfwGetX11SelectionString) {
|
||||
return Py_BuildValue("y", glfwGetX11SelectionString());
|
||||
} else log_error("Failed to load glfwGetX11SelectionString");
|
||||
|
||||
@@ -509,7 +509,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
|
||||
static inline const char*
|
||||
create_add_response(GraphicsManager UNUSED *self, bool data_loaded, uint32_t iid) {
|
||||
static char rbuf[sizeof(add_response)/sizeof(add_response[0])];
|
||||
static char rbuf[sizeof(add_response)/sizeof(add_response[0]) + 64];
|
||||
if (iid) {
|
||||
if (!has_add_respose) {
|
||||
if (!data_loaded) return NULL;
|
||||
|
||||
@@ -161,7 +161,7 @@ __str__(HistoryBuf *self) {
|
||||
if (lines == NULL) return PyErr_NoMemory();
|
||||
for (index_type i = 0; i < self->count; i++) {
|
||||
init_line(self, index_of(self, i), self->line);
|
||||
PyObject *t = PyObject_Str((PyObject*)self->line);
|
||||
PyObject *t = line_as_unicode(self->line);
|
||||
if (t == NULL) { Py_CLEAR(lines); return NULL; }
|
||||
PyTuple_SET_ITEM(lines, i, t);
|
||||
}
|
||||
@@ -215,7 +215,7 @@ as_text(HistoryBuf *self, PyObject *args) {
|
||||
|
||||
|
||||
static PyObject*
|
||||
dirty_lines(HistoryBuf *self) {
|
||||
dirty_lines(HistoryBuf *self, PyObject *a UNUSED) {
|
||||
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
||||
PyObject *ans = PyList_New(0);
|
||||
for (index_type i = 0; i < self->ynum; i++) {
|
||||
|
||||
10
kitty/keys.h
generated
10
kitty/keys.h
generated
@@ -1747,6 +1747,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
return "\x01\x0d";
|
||||
} // end switch(key)
|
||||
} // end switch(mods)
|
||||
break;
|
||||
|
||||
case 2: // REPEAT
|
||||
switch (mods & 0xf) {
|
||||
@@ -3005,8 +3006,10 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
return "\x01\x0d";
|
||||
} // end switch(key)
|
||||
} // end switch(mods)
|
||||
break;
|
||||
|
||||
} // end switch(action) in mode NORMAL
|
||||
break;
|
||||
|
||||
|
||||
case APPLICATION:
|
||||
@@ -4271,6 +4274,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
return "\x01\x0d";
|
||||
} // end switch(key)
|
||||
} // end switch(mods)
|
||||
break;
|
||||
|
||||
case 2: // REPEAT
|
||||
switch (mods & 0xf) {
|
||||
@@ -5529,8 +5533,10 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
return "\x01\x0d";
|
||||
} // end switch(key)
|
||||
} // end switch(mods)
|
||||
break;
|
||||
|
||||
} // end switch(action) in mode APPLICATION
|
||||
break;
|
||||
|
||||
|
||||
case EXTENDED:
|
||||
@@ -9134,6 +9140,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
return "\x05\x4b\x72\x50\x42\x68";
|
||||
} // end switch(key)
|
||||
} // end switch(mods)
|
||||
break;
|
||||
|
||||
case 1: // PRESS
|
||||
switch (mods & 0xf) {
|
||||
@@ -12738,6 +12745,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
return "\x05\x4b\x70\x50\x42\x68";
|
||||
} // end switch(key)
|
||||
} // end switch(mods)
|
||||
break;
|
||||
|
||||
case 2: // REPEAT
|
||||
switch (mods & 0xf) {
|
||||
@@ -16342,8 +16350,10 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
|
||||
return "\x05\x4b\x74\x50\x42\x68";
|
||||
} // end switch(key)
|
||||
} // end switch(mods)
|
||||
break;
|
||||
|
||||
} // end switch(action) in mode EXTENDED
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -336,13 +336,15 @@ def generate_key_table():
|
||||
ind('return NULL;')
|
||||
i -= 2
|
||||
i -= 1
|
||||
ind('} // end switch(mods)\n')
|
||||
ind('} // end switch(mods)')
|
||||
ind('break;\n')
|
||||
i -= 1
|
||||
else:
|
||||
ind('return NULL;\n')
|
||||
i -= 1
|
||||
i -= 1
|
||||
ind('}} // end switch(action) in mode {}\n\n'.format(mode))
|
||||
ind('}} // end switch(action) in mode {}'.format(mode))
|
||||
ind('break;\n\n')
|
||||
i -= 1
|
||||
i -= 1
|
||||
ind('}')
|
||||
|
||||
417
kitty/kitty.conf
417
kitty/kitty.conf
@@ -1,5 +1,6 @@
|
||||
# vim:fileencoding=utf-8:ft=conf
|
||||
|
||||
# Fonts {{{
|
||||
# Font family. You can also specify different fonts for the
|
||||
# bold/italic/bold-italic variants. By default they are derived automatically,
|
||||
# by the OSes font system. Setting them manually is useful for font families
|
||||
@@ -11,6 +12,7 @@
|
||||
#
|
||||
# You can get a list of full family names available on your computer by running
|
||||
# kitty list-fonts
|
||||
# The default values shown below rely on your OS to choose an appropriate monospace font family.
|
||||
font_family monospace
|
||||
italic_font auto
|
||||
bold_font auto
|
||||
@@ -32,12 +34,189 @@ font_size_delta 2
|
||||
adjust_line_height 0
|
||||
adjust_column_width 0
|
||||
|
||||
# Symbol mapping (special font for specified unicode code points). Map the
|
||||
# specified unicode codepoints to a particular font. Useful if you need special
|
||||
# rendering for some symbols, such as for Powerline. Avoids the need for
|
||||
# patched fonts. Each unicode code point is specified in the form U+<code point
|
||||
# in hexadecimal>. You can specify multiple code points, separated by commas
|
||||
# and ranges separated by hyphens. symbol_map itself can be specified multiple times.
|
||||
# Syntax is:
|
||||
#
|
||||
# symbol_map codepoints Font Family Name
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
|
||||
|
||||
# Change the sizes of the lines used for the box drawing unicode characters
|
||||
# These values are in pts. They will be scaled by the monitor DPI to arrive at
|
||||
# a pixel value. There must be four values corresponding to thin, normal, thick,
|
||||
# and very thick lines;
|
||||
box_drawing_scale 0.001, 1, 1.5, 2
|
||||
# }}}
|
||||
|
||||
|
||||
# Cursor customization {{{
|
||||
# The cursor color
|
||||
cursor #cccccc
|
||||
|
||||
# The cursor shape can be one of (block, beam, underline)
|
||||
cursor_shape block
|
||||
|
||||
# The interval (in seconds) at which to blink the cursor. Set to zero to
|
||||
# disable blinking. Note that numbers smaller than repaint_delay will be
|
||||
# limited to repaint_delay.
|
||||
cursor_blink_interval 0.5
|
||||
|
||||
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
|
||||
# zero to never stop blinking.
|
||||
cursor_stop_blinking_after 15.0
|
||||
# }}}
|
||||
|
||||
|
||||
# Scrollback {{{
|
||||
# Number of lines of history to keep in memory for scrolling back
|
||||
scrollback_lines 2000
|
||||
|
||||
# Program with which to view scrollback in a new window. The scrollback buffer is passed as
|
||||
# STDIN to this program. If you change it, make sure the program you use can
|
||||
# handle ANSI escape sequences for colors and text formatting.
|
||||
scrollback_pager less +G -R
|
||||
|
||||
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative
|
||||
# numbers to change scroll direction.
|
||||
wheel_scroll_multiplier 5.0
|
||||
# }}}
|
||||
|
||||
|
||||
# Mouse {{{
|
||||
# The color and style for highlighting URLs on mouse-over. url_style can be one of:
|
||||
# none, single, double, curly
|
||||
url_color #0087BD
|
||||
url_style curly
|
||||
|
||||
# The modifier keys to press when clicking with the mouse on URLs to open the URL
|
||||
open_url_modifiers ctrl+shift
|
||||
|
||||
# The program with which to open URLs that are clicked on. The special value "default" means to
|
||||
# use the operating system's default URL handler.
|
||||
open_url_with default
|
||||
|
||||
# Copy to clipboard on select. With this enabled, simply selecting text with
|
||||
# the mouse will cause the text to be copied to clipboard. Useful on platforms
|
||||
# such as macOS/Wayland that do not have the concept of primary selections. Note
|
||||
# that this is a security risk, as all programs, including websites open in your
|
||||
# browser can read the contents of the clipboard.
|
||||
copy_on_select no
|
||||
|
||||
# The modifiers to use rectangular selection (i.e. to select text in a
|
||||
# rectangular block with the mouse)
|
||||
rectangle_select_modifiers ctrl+alt
|
||||
|
||||
# Characters considered part of a word when double clicking. In addition to these characters
|
||||
# any character that is marked as an alpha-numeric character in the unicode
|
||||
# database will be matched.
|
||||
select_by_word_characters :@-./_~?&=%+#
|
||||
|
||||
# The interval between successive clicks to detect double/triple clicks (in seconds)
|
||||
click_interval 0.5
|
||||
|
||||
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
|
||||
# zero to disable mouse cursor hiding.
|
||||
mouse_hide_wait 3.0
|
||||
|
||||
# Set the active window to the window under the mouse when moving the mouse around
|
||||
focus_follows_mouse no
|
||||
# }}}
|
||||
|
||||
|
||||
# Performance tuning {{{
|
||||
# Delay (in milliseconds) between screen updates. Decreasing it, increases
|
||||
# frames-per-second (FPS) at the cost of more CPU usage. The default value
|
||||
# yields ~100 FPS which is more than sufficient for most uses. Note that to
|
||||
# actually achieve 100FPS you have to either set sync_to_monitor to no or use a
|
||||
# monitor with a high refresh rate.
|
||||
repaint_delay 10
|
||||
|
||||
# Delay (in milliseconds) before input from the program running in the terminal
|
||||
# is processed. Note that decreasing it will increase responsiveness, but also
|
||||
# increase CPU usage and might cause flicker in full screen programs that
|
||||
# redraw the entire screen on each loop, because kitty is so fast that partial
|
||||
# screen updates will be drawn.
|
||||
input_delay 3
|
||||
|
||||
# Sync screen updates to the refresh rate of the monitor. This prevents
|
||||
# tearing (https://en.wikipedia.org/wiki/Screen_tearing) when scrolling. However,
|
||||
# it limits the rendering speed to the refresh rate of your monitor. With a
|
||||
# very high speed mouse/high keyboard repeat rate, you may notice some slight input latency.
|
||||
# If so, set this to no.
|
||||
sync_to_monitor yes
|
||||
# }}}
|
||||
|
||||
|
||||
# Audio/visual bell {{{
|
||||
# Visual bell duration. Flash the screen when a bell occurs for the specified number of
|
||||
# seconds. Set to zero to disable.
|
||||
visual_bell_duration 0.0
|
||||
|
||||
# Enable/disable the audio bell. Useful in environments that require silence.
|
||||
enable_audio_bell yes
|
||||
# }}}
|
||||
|
||||
|
||||
# Window layout {{{
|
||||
# If enabled, the window size will be remembered so that new instances of kitty will have the same
|
||||
# size as the previous instance. If disabled, the window will initially have size configured
|
||||
# by initial_window_width/height, in pixels.
|
||||
remember_window_size yes
|
||||
initial_window_width 640
|
||||
initial_window_height 400
|
||||
|
||||
# The enabled window layouts. A comma separated list of layout names. The special value * means
|
||||
# all layouts. The first listed layout will be used as the startup layout.
|
||||
# For a list of available layouts, see the README.
|
||||
enabled_layouts *
|
||||
|
||||
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||
# Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.
|
||||
window_border_width 1
|
||||
|
||||
# The window margin (in pts) (blank area outside the border)
|
||||
window_margin_width 0
|
||||
|
||||
# The window padding (in pts) (blank area between the text and the window border)
|
||||
window_padding_width 0
|
||||
|
||||
# The color for the border of the active window
|
||||
active_border_color #00ff00
|
||||
|
||||
# The color for the border of inactive windows
|
||||
inactive_border_color #cccccc
|
||||
|
||||
# Fade the text in inactive windows by the specified amount (a number between
|
||||
# zero and one, with 0 being fully faded).
|
||||
inactive_text_alpha 1.0
|
||||
# }}}
|
||||
|
||||
|
||||
# Tab bar {{{
|
||||
# Which edge to show the tab bar on, top or bottom
|
||||
tab_bar_edge bottom
|
||||
|
||||
# The separator between tabs in the tab bar
|
||||
tab_separator " ┇"
|
||||
|
||||
# Tab bar colors and styles
|
||||
active_tab_foreground #000
|
||||
active_tab_background #eee
|
||||
active_tab_font_style bold-italic
|
||||
inactive_tab_foreground #444
|
||||
inactive_tab_background #999
|
||||
inactive_tab_font_style normal
|
||||
# }}}
|
||||
|
||||
|
||||
# Color scheme {{{
|
||||
# The foreground color
|
||||
foreground #dddddd
|
||||
|
||||
@@ -62,155 +241,6 @@ selection_foreground #000000
|
||||
# The background for selections
|
||||
selection_background #FFFACD
|
||||
|
||||
# The color and style for highlighting URLs on mouse-over. url_style can be one of:
|
||||
# none, single, double, curly
|
||||
url_color #0087BD
|
||||
url_style curly
|
||||
|
||||
# The cursor color
|
||||
cursor #cccccc
|
||||
|
||||
# The cursor shape can be one of (block, beam, underline)
|
||||
cursor_shape block
|
||||
|
||||
# The interval (in seconds) at which to blink the cursor. Set to zero to
|
||||
# disable blinking. Note that numbers smaller than repaint_delay will be
|
||||
# limited to repaint_delay.
|
||||
cursor_blink_interval 0.5
|
||||
|
||||
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
|
||||
# zero to never stop blinking.
|
||||
cursor_stop_blinking_after 15.0
|
||||
|
||||
# Number of lines of history to keep in memory for scrolling back
|
||||
scrollback_lines 2000
|
||||
|
||||
# Program with which to view scrollback in a new window. The scrollback buffer is passed as
|
||||
# STDIN to this program. If you change it, make sure the program you use can
|
||||
# handle ANSI escape sequences for colors and text formatting.
|
||||
scrollback_pager less +G -R
|
||||
|
||||
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative
|
||||
# numbers to change scroll direction.
|
||||
wheel_scroll_multiplier 5.0
|
||||
|
||||
# The interval between successive clicks to detect double/triple clicks (in seconds)
|
||||
click_interval 0.5
|
||||
|
||||
# Characters considered part of a word when double clicking. In addition to these characters
|
||||
# any character that is marked as an alpha-numeric character in the unicode
|
||||
# database will be matched.
|
||||
select_by_word_characters :@-./_~?&=%+#
|
||||
|
||||
# The shell program to execute. The default value of . means
|
||||
# to use whatever shell is set as the default shell for the current user.
|
||||
# Note that on macOS if you change this, you might need to add --login to
|
||||
# ensure that the shell starts in interactive mode and reads its rc files.
|
||||
shell .
|
||||
|
||||
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
|
||||
# zero to disable mouse cursor hiding.
|
||||
mouse_hide_wait 3.0
|
||||
|
||||
# Set the active window to the window under the mouse when moving the mouse around
|
||||
focus_follows_mouse no
|
||||
|
||||
# The enabled window layouts. A comma separated list of layout names. The special value * means
|
||||
# all layouts. The first listed layout will be used as the startup layout.
|
||||
# For a list of available layouts, see the file layouts.py
|
||||
enabled_layouts *
|
||||
|
||||
# If enabled, the window size will be remembered so that new instances of kitty will have the same
|
||||
# size as the previous instance. If disabled, the window will initially have size configured
|
||||
# by initial_window_width/height, in pixels.
|
||||
remember_window_size yes
|
||||
initial_window_width 640
|
||||
initial_window_height 400
|
||||
|
||||
# Delay (in milliseconds) between screen updates. Decreasing it, increases
|
||||
# frames-per-second (FPS) at the cost of more CPU usage. The default value
|
||||
# yields ~100 FPS which is more than sufficient for most uses. Note that to
|
||||
# actually achieve 100FPS you have to either set sync_to_monitor to no or use a
|
||||
# monitor with a high refresh rate.
|
||||
repaint_delay 10
|
||||
|
||||
# Delay (in milliseconds) before input from the program running in the terminal
|
||||
# is processed. Note that decreasing it will increase responsiveness, but also
|
||||
# increase CPU usage and might cause flicker in full screen programs that
|
||||
# redraw the entire screen on each loop, because kitty is so fast that partial
|
||||
# screen updates will be drawn.
|
||||
input_delay 3
|
||||
|
||||
# Sync screen updates to the refresh rate of the monitor. This prevents
|
||||
# tearing (https://en.wikipedia.org/wiki/Screen_tearing) when scrolling. However,
|
||||
# it limits the rendering speed to the refresh rate of your monitor. With a
|
||||
# very high speed mouse/high keyboard repeat rate, you may notice some slight input latency.
|
||||
# If so, set this to no.
|
||||
sync_to_monitor yes
|
||||
|
||||
# Visual bell duration. Flash the screen when a bell occurs for the specified number of
|
||||
# seconds. Set to zero to disable.
|
||||
visual_bell_duration 0.0
|
||||
|
||||
# Enable/disable the audio bell. Useful in environments that require silence.
|
||||
enable_audio_bell yes
|
||||
|
||||
# The modifier keys to press when clicking with the mouse on URLs to open the URL
|
||||
open_url_modifiers ctrl+shift
|
||||
|
||||
# The program with which to open URLs that are clicked on. The special value "default" means to
|
||||
# use the operating system's default URL handler.
|
||||
open_url_with default
|
||||
|
||||
# The modifiers to use rectangular selection (i.e. to select text in a
|
||||
# rectangular block with the mouse)
|
||||
rectangle_select_modifiers ctrl+alt
|
||||
|
||||
|
||||
# Allow other programs to control kitty. If you turn this on other programs can
|
||||
# control all aspects of kitty, including sending text to kitty windows,
|
||||
# opening new windows, closing windows, reading the content of windows, etc.
|
||||
# Note that this even works over ssh connections.
|
||||
allow_remote_control no
|
||||
|
||||
# The value of the TERM environment variable to set
|
||||
term xterm-kitty
|
||||
|
||||
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||
# Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.
|
||||
window_border_width 1
|
||||
|
||||
# The window margin (in pts) (blank area outside the border)
|
||||
window_margin_width 0
|
||||
|
||||
# The window padding (in pts) (blank area between the text and the window border)
|
||||
window_padding_width 0
|
||||
|
||||
# The color for the border of the active window
|
||||
active_border_color #00ff00
|
||||
|
||||
# The color for the border of inactive windows
|
||||
inactive_border_color #cccccc
|
||||
|
||||
# Fade the text in inactive windows by the specified amount (a number between
|
||||
# zero and one, with 0 being fully faded).
|
||||
inactive_text_alpha 1.0
|
||||
|
||||
# Which edge to show the tab bar on, top or bottom
|
||||
tab_bar_edge bottom
|
||||
|
||||
# The separator between tabs in the tab bar
|
||||
tab_separator " ┇"
|
||||
|
||||
# Tab bar colors and styles
|
||||
active_tab_foreground #000
|
||||
active_tab_background #eee
|
||||
active_tab_font_style bold-italic
|
||||
inactive_tab_foreground #444
|
||||
inactive_tab_background #999
|
||||
inactive_tab_font_style normal
|
||||
|
||||
|
||||
# The 16 terminal colors. There are 8 basic colors, each color has a dull and
|
||||
# bright version.
|
||||
|
||||
@@ -245,9 +275,42 @@ color14 #14ffff
|
||||
# white
|
||||
color7 #dddddd
|
||||
color15 #ffffff
|
||||
# }}}
|
||||
|
||||
|
||||
# Key mapping
|
||||
# Advanced {{{
|
||||
|
||||
# The shell program to execute. The default value of . means
|
||||
# to use whatever shell is set as the default shell for the current user.
|
||||
# Note that on macOS if you change this, you might need to add --login to
|
||||
# ensure that the shell starts in interactive mode and reads its startup rc files.
|
||||
shell .
|
||||
|
||||
# Close the window when the child process (shell) exits. If no (the default),
|
||||
# the terminal will remain open when the child exits as long as there are still
|
||||
# processes outputting to the terminal (for example disowned or backgrounded
|
||||
# processes). If yes, the window will close as soon as the child process exits.
|
||||
# Note that setting it to yes means that any background processes still using
|
||||
# the terminal can fail silently because their stdout/stderr/stdin no longer
|
||||
# work.
|
||||
close_on_child_death no
|
||||
|
||||
# Allow other programs to control kitty. If you turn this on other programs can
|
||||
# control all aspects of kitty, including sending text to kitty windows,
|
||||
# opening new windows, closing windows, reading the content of windows, etc.
|
||||
# Note that this even works over ssh connections.
|
||||
allow_remote_control no
|
||||
|
||||
# The value of the TERM environment variable to set. Changing this can break
|
||||
# many terminal programs, only change it if you know what you are doing, not
|
||||
# because you read some advice on Stack Overflow to change it.
|
||||
term xterm-kitty
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
# Keyboard shortcuts {{{
|
||||
# For a list of key names, see: http://www.glfw.org/docs/latest/group__keys.html
|
||||
# For a list of modifier names, see: http://www.glfw.org/docs/latest/group__mods.html
|
||||
#
|
||||
@@ -261,7 +324,7 @@ color15 #ffffff
|
||||
# map ctrl+shift+e combine : new_window : next_layout
|
||||
# this will create a new window and switch to the next available layout
|
||||
|
||||
# Clipboard
|
||||
# Clipboard {{{
|
||||
map ctrl+shift+v paste_from_clipboard
|
||||
map ctrl+shift+s paste_from_selection
|
||||
map ctrl+shift+c copy_to_clipboard
|
||||
@@ -271,8 +334,9 @@ map shift+insert paste_from_selection
|
||||
# you can specify your own, for example:
|
||||
# map ctrl+shift+o pass_selection_to_program firefox
|
||||
map ctrl+shift+o pass_selection_to_program
|
||||
# }}}
|
||||
|
||||
# Scrolling
|
||||
# Scrolling {{{
|
||||
map ctrl+shift+up scroll_line_up
|
||||
map ctrl+shift+down scroll_line_down
|
||||
map ctrl+shift+k scroll_line_up
|
||||
@@ -282,8 +346,9 @@ map ctrl+shift+page_down scroll_page_down
|
||||
map ctrl+shift+home scroll_home
|
||||
map ctrl+shift+end scroll_end
|
||||
map ctrl+shift+h show_scrollback
|
||||
# }}}
|
||||
|
||||
# Window management
|
||||
# Window management {{{
|
||||
map ctrl+shift+enter new_window
|
||||
map ctrl+shift+n new_os_window
|
||||
map ctrl+shift+w close_window
|
||||
@@ -317,9 +382,9 @@ map ctrl+shift+0 tenth_window
|
||||
# You can open a new window with the current working directory set to the
|
||||
# working directory of the current window using
|
||||
# map ctrl+alt+enter new_window_with_cwd
|
||||
# }}}
|
||||
|
||||
|
||||
# Tab management
|
||||
# Tab management {{{
|
||||
map ctrl+shift+right next_tab
|
||||
map ctrl+shift+left previous_tab
|
||||
map ctrl+shift+t new_tab
|
||||
@@ -333,15 +398,28 @@ map ctrl+shift+, move_tab_backward
|
||||
|
||||
# Just as with new_window above, you can also pass the name of arbitrary
|
||||
# commands to run when using new_tab and use new_tab_with_cwd.
|
||||
# }}}
|
||||
|
||||
# Layout management {{{
|
||||
# You can create shortcuts to switch to specific layouts
|
||||
# map ctrl+alt+1 goto_layout tall
|
||||
# map ctrl+alt+2 goto_layout stack
|
||||
# }}}
|
||||
|
||||
# Miscellaneous
|
||||
# Font sizes {{{
|
||||
map ctrl+shift+equal increase_font_size
|
||||
map ctrl+shift+minus decrease_font_size
|
||||
map ctrl+shift+backspace restore_font_size
|
||||
# To setup shortcuts for specific font sizes, follow the example below:
|
||||
# map ctrl+shift+f6 set_font_size 10.0
|
||||
# map ctrl+shift+f7 set_font_size 20.5
|
||||
# }}}
|
||||
|
||||
# Miscellaneous {{{
|
||||
map ctrl+shift+f11 toggle_fullscreen
|
||||
map ctrl+shift+u input_unicode_character
|
||||
# Open a currently visible URL using the keyboard. The program used ot open the URL is specified in open_url_with.
|
||||
map ctrl+shift+f2 edit_config_file
|
||||
# Open a currently visible URL using the keyboard. The program used to open the URL is specified in open_url_with.
|
||||
# You can customize how the URLs are detected and opened by specifying command line options to
|
||||
# url_hints. For example:
|
||||
# map ctrl+shift+e run_simple_kitten text url_hints --program firefox --regex "http://[^ ]+"
|
||||
@@ -363,30 +441,12 @@ map ctrl+shift+e run_simple_kitten text url_hints
|
||||
# to the start of the line (same as pressing the Home key):
|
||||
# map ctrl+alt+a send_text normal Word\x1b[H
|
||||
# map ctrl+alt+a send_text application Word\x1bOH
|
||||
# }}}
|
||||
|
||||
# Symbol mapping (special font for specified unicode code points). Map the
|
||||
# specified unicode codepoints to a particular font. Useful if you need special
|
||||
# rendering for some symbols, such as for Powerline. Avoids the need for
|
||||
# patched fonts. Each unicode code point is specified in the form U+<code point
|
||||
# in hexadecimal>. You can specify multiple code points, separated by commas
|
||||
# and ranges separated by hyphens. symbol_map itself can be specified multiple times.
|
||||
# Syntax is:
|
||||
#
|
||||
# symbol_map codepoints Font Family Name
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
|
||||
# }}}
|
||||
|
||||
|
||||
# OS specific tweaks
|
||||
|
||||
# Copy to clipboard on select. With this enabled, simply selecting text with
|
||||
# the mouse will cause the text to be copied to clipboard. Useful on platforms
|
||||
# such as macOS/Wayland that do not have the concept of primary selections. Note
|
||||
# that this is a security risk, as all programs, including websites open in your
|
||||
# browser can read the contents of the clipboard.
|
||||
copy_on_select no
|
||||
# OS specific tweaks {{{
|
||||
|
||||
# Change the color of the kitty window's titlebar on macOS. A value of "system"
|
||||
# means to use the default system color, a value of "background" means to use
|
||||
@@ -406,3 +466,4 @@ macos_option_as_alt yes
|
||||
# The number is a percentage of maximum volume.
|
||||
# See man XBell for details.
|
||||
x11_bell_volume 80
|
||||
# }}}
|
||||
|
||||
@@ -46,7 +46,7 @@ linebuf_mark_line_clean(LineBuf *self, index_type y) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
clear(LineBuf *self) {
|
||||
clear(LineBuf *self, PyObject *a UNUSED) {
|
||||
#define clear_doc "Clear all lines in this LineBuf"
|
||||
linebuf_clear(self, BLANK_CHAR);
|
||||
Py_RETURN_NONE;
|
||||
@@ -167,7 +167,7 @@ set_continued(LineBuf *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
dirty_lines(LineBuf *self) {
|
||||
dirty_lines(LineBuf *self, PyObject *a UNUSED) {
|
||||
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
||||
PyObject *ans = PyList_New(0);
|
||||
for (index_type i = 0; i < self->ynum; i++) {
|
||||
@@ -424,7 +424,7 @@ __str__(LineBuf *self) {
|
||||
if (lines == NULL) return PyErr_NoMemory();
|
||||
for (index_type i = 0; i < self->ynum; i++) {
|
||||
init_line(self, self->line, self->line_map[i]);
|
||||
PyObject *t = PyObject_Str((PyObject*)self->line);
|
||||
PyObject *t = line_as_unicode(self->line);
|
||||
if (t == NULL) { Py_CLEAR(lines); return NULL; }
|
||||
PyTuple_SET_ITEM(lines, i, t);
|
||||
}
|
||||
|
||||
12
kitty/line.c
12
kitty/line.c
@@ -199,8 +199,8 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc
|
||||
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
as_unicode(Line* self) {
|
||||
PyObject *
|
||||
line_as_unicode(Line* self) {
|
||||
return unicode_in_range(self, 0, xlimit_for_line(self), true, 0);
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_ansi(Line* self) {
|
||||
as_ansi(Line* self, PyObject *a UNUSED) {
|
||||
#define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting"
|
||||
static Py_UCS4 t[5120] = {0};
|
||||
index_type num = line_as_ansi(self, t, 5120);
|
||||
@@ -272,7 +272,7 @@ as_ansi(Line* self) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
is_continued(Line* self) {
|
||||
is_continued(Line* self, PyObject *a UNUSED) {
|
||||
#define is_continued_doc "Return the line's continued flag"
|
||||
PyObject *ans = self->continued ? Py_True : Py_False;
|
||||
Py_INCREF(ans);
|
||||
@@ -281,7 +281,7 @@ is_continued(Line* self) {
|
||||
|
||||
static PyObject*
|
||||
__repr__(Line* self) {
|
||||
PyObject *s = as_unicode(self);
|
||||
PyObject *s = line_as_unicode(self);
|
||||
if (s == NULL) return NULL;
|
||||
PyObject *ans = PyObject_Repr(s);
|
||||
Py_CLEAR(s);
|
||||
@@ -570,7 +570,7 @@ PyTypeObject Line_Type = {
|
||||
.tp_basicsize = sizeof(Line),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_repr = (reprfunc)__repr__,
|
||||
.tp_str = (reprfunc)as_unicode,
|
||||
.tp_str = (reprfunc)line_as_unicode,
|
||||
.tp_as_sequence = &sequence_methods,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_richcompare = richcmp,
|
||||
|
||||
@@ -57,10 +57,10 @@ unsigned int line_length(Line *self);
|
||||
size_t cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type);
|
||||
size_t cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type);
|
||||
PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char);
|
||||
PyObject* line_as_unicode(Line *);
|
||||
|
||||
void linebuf_init_line(LineBuf *, index_type);
|
||||
void linebuf_clear(LineBuf *, char_type ch);
|
||||
void linebuf_init_line(LineBuf *, index_type);
|
||||
void linebuf_index(LineBuf* self, index_type top, index_type bottom);
|
||||
void linebuf_reverse_index(LineBuf *self, index_type top, index_type bottom);
|
||||
void linebuf_clear_line(LineBuf *self, index_type y);
|
||||
@@ -105,7 +105,7 @@ void historybuf_clear(HistoryBuf *self);
|
||||
index_type num = line_as_ansi(line, buf, columns * 100 - 2); \
|
||||
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num); \
|
||||
} else { \
|
||||
t = PyObject_Str((PyObject*)line); \
|
||||
t = line_as_unicode(line); \
|
||||
} \
|
||||
if (t == NULL) goto end; \
|
||||
ret = PyObject_CallFunctionObjArgs(callback, t, NULL); \
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
import locale
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
@@ -12,10 +11,10 @@ from .borders import load_borders_program
|
||||
from .boss import Boss
|
||||
from .cli import create_opts, parse_args
|
||||
from .config import cached_values_for, initial_window_size
|
||||
from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file
|
||||
from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file, config_dir
|
||||
from .fast_data_types import (
|
||||
create_os_window, glfw_init, glfw_terminate, install_sigchld_handler,
|
||||
set_default_window_icon, set_options, show_window
|
||||
create_os_window, glfw_init, glfw_terminate, set_default_window_icon,
|
||||
set_options, show_window
|
||||
)
|
||||
from .fonts.box_drawing import set_scale
|
||||
from .utils import (
|
||||
@@ -93,6 +92,20 @@ def setup_profiling(args):
|
||||
print('To view the graphical call data, use: kcachegrind', cg)
|
||||
|
||||
|
||||
def macos_cmdline():
|
||||
try:
|
||||
with open(os.path.join(config_dir, 'macos-launch-services-cmdline')) as f:
|
||||
raw = f.read()
|
||||
except FileNotFoundError:
|
||||
return []
|
||||
import shlex
|
||||
raw = raw.strip()
|
||||
ans = shlex.split(raw)
|
||||
if ans and ans[0] == 'kitty':
|
||||
del ans[0]
|
||||
return ans
|
||||
|
||||
|
||||
def _main():
|
||||
try:
|
||||
sys.setswitchinterval(1000.0) # we have only a single python thread
|
||||
@@ -124,11 +137,13 @@ def _main():
|
||||
if rpath and rpath not in items:
|
||||
os.environ['PATH'] += os.pathsep + rpath
|
||||
|
||||
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
|
||||
args = sys.argv[1:]
|
||||
if is_macos and os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
|
||||
os.chdir(os.path.expanduser('~'))
|
||||
args = macos_cmdline()
|
||||
if not os.path.isdir(os.getcwd()):
|
||||
os.chdir(os.path.expanduser('~'))
|
||||
args, rest = parse_args()
|
||||
args, rest = parse_args(args=args)
|
||||
args.args = rest
|
||||
if getattr(args, 'detach', False):
|
||||
detach()
|
||||
@@ -152,9 +167,7 @@ def _main():
|
||||
try:
|
||||
with setup_profiling(args):
|
||||
# Avoid needing to launch threads to reap zombies
|
||||
install_sigchld_handler()
|
||||
run_app(opts, args)
|
||||
signal.signal(signal.SIGCHLD, signal.SIG_DFL)
|
||||
finally:
|
||||
glfw_terminate()
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ typedef enum MouseActions { PRESS, RELEASE, DRAG, MOVE } MouseAction;
|
||||
#define MOTION_INDICATOR (1 << 5)
|
||||
#define EXTRA_BUTTON_INDICATOR (1 << 6)
|
||||
|
||||
static int last_multi_clicks = 0;
|
||||
|
||||
static inline unsigned int
|
||||
button_map(int button) {
|
||||
switch(button) {
|
||||
@@ -147,6 +149,18 @@ drag_scroll(Window *w, OSWindow *frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void
|
||||
extend_selection(Window *w) {
|
||||
Screen *screen = w->render_data.screen;
|
||||
index_type start, end;
|
||||
if (screen_has_selection(screen)) {
|
||||
bool found_selectable_word = screen_selection_range_for_word(screen, w->mouse_cell_x, w->mouse_cell_y, &start, &end);
|
||||
if (last_multi_clicks >= 2 && found_selectable_word) screen_update_selection(screen, end, w->mouse_cell_y, true);
|
||||
else screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
|
||||
call_boss(set_primary_selection, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
extend_url(Screen *screen, Line *line, index_type *x, index_type *y) {
|
||||
unsigned int count = 0;
|
||||
@@ -246,9 +260,11 @@ HANDLER(add_click) {
|
||||
q->length++;
|
||||
// Now dispatch the multi-click if any
|
||||
if (q->length > 2 && N(1).at - N(3).at <= 2 * OPT(click_interval)) {
|
||||
last_multi_clicks = 3;
|
||||
multi_click(w, 3);
|
||||
q->length = 0;
|
||||
} else if (q->length > 1 && N(1).at - N(2).at <= OPT(click_interval)) {
|
||||
last_multi_clicks = 2;
|
||||
multi_click(w, 2);
|
||||
}
|
||||
#undef N
|
||||
@@ -278,6 +294,7 @@ HANDLER(handle_button_event) {
|
||||
switch(button) {
|
||||
case GLFW_MOUSE_BUTTON_LEFT:
|
||||
update_drag(true, w, is_release, modifiers);
|
||||
last_multi_clicks = 0;
|
||||
if (is_release) {
|
||||
if (modifiers == (int)OPT(open_url_modifiers)) {
|
||||
open_url(w);
|
||||
@@ -289,6 +306,9 @@ HANDLER(handle_button_event) {
|
||||
case GLFW_MOUSE_BUTTON_MIDDLE:
|
||||
if (is_release && !modifiers) { call_boss(paste_from_selection, NULL); return; }
|
||||
break;
|
||||
case GLFW_MOUSE_BUTTON_RIGHT:
|
||||
if (is_release) { extend_selection(w); }
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
int sz = encode_mouse_event(w, button, is_release ? RELEASE : PRESS, modifiers);
|
||||
|
||||
@@ -16,10 +16,19 @@ from .tabs import SpecialWindow
|
||||
from .utils import non_blocking_read, parse_address_spec, read_with_timeout
|
||||
|
||||
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False):
|
||||
class MatchError(ValueError):
|
||||
|
||||
hide_traceback = True
|
||||
|
||||
def __init__(self, expression, target='windows'):
|
||||
ValueError.__init__(self, 'No matching {} for expression: {}'.format(target, expression))
|
||||
|
||||
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...'):
|
||||
|
||||
def w(func):
|
||||
func.short_desc = short_desc
|
||||
func.argspec = argspec
|
||||
func.desc = desc or short_desc
|
||||
func.name = func.__name__[4:].replace('_', '-')
|
||||
func.options_spec = options_spec
|
||||
@@ -31,29 +40,10 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False):
|
||||
|
||||
|
||||
def parse_subcommand_cli(func, args):
|
||||
opts, items = parse_args(args[1:], (func.options_spec or '\n').format, '...', func.desc, '{} @ {}'.format(appname, func.name))
|
||||
opts, items = parse_args(args[1:], (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name))
|
||||
return opts, items
|
||||
|
||||
|
||||
@cmd(
|
||||
'List all tabs/windows',
|
||||
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
||||
' operating system {appname} windows. Each OS window has an |_ id| and a list'
|
||||
' of |_ tabs|. Each tab has its own |_ id|, a |_ title| and a list of |_ windows|.'
|
||||
' Each window has an |_ id|, |_ title|, |_ current working directory|, |_ process id (PID)| and'
|
||||
' |_ command-line| of the process running in the window.\n\n'
|
||||
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname)
|
||||
)
|
||||
def cmd_ls(global_opts, opts, args):
|
||||
pass
|
||||
|
||||
|
||||
def ls(boss, window):
|
||||
data = list(boss.list_os_windows())
|
||||
data = json.dumps(data, indent=2, sort_keys=True)
|
||||
return data
|
||||
|
||||
|
||||
MATCH_WINDOW_OPTION = '''\
|
||||
--match -m
|
||||
The window to match. Match specifications are of the form:
|
||||
@@ -74,6 +64,47 @@ for that window is used.
|
||||
'''
|
||||
|
||||
|
||||
# ls {{{
|
||||
@cmd(
|
||||
'List all tabs/windows',
|
||||
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
||||
' operating system {appname} windows. Each OS window has an |_ id| and a list'
|
||||
' of |_ tabs|. Each tab has its own |_ id|, a |_ title| and a list of |_ windows|.'
|
||||
' Each window has an |_ id|, |_ title|, |_ current working directory|, |_ process id (PID)| and'
|
||||
' |_ command-line| of the process running in the window.\n\n'
|
||||
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
|
||||
argspec=''
|
||||
)
|
||||
def cmd_ls(global_opts, opts, args):
|
||||
pass
|
||||
|
||||
|
||||
def ls(boss, window):
|
||||
data = list(boss.list_os_windows())
|
||||
data = json.dumps(data, indent=2, sort_keys=True)
|
||||
return data
|
||||
# }}}
|
||||
|
||||
|
||||
# set_font_size {{{
|
||||
@cmd(
|
||||
'Set the font size in all windows',
|
||||
'Sets the font size to the specified size, in pts.',
|
||||
argspec='FONT_SIZE'
|
||||
)
|
||||
def cmd_set_font_size(global_opts, opts, args):
|
||||
try:
|
||||
return {'size': float(args[0])}
|
||||
except IndexError:
|
||||
raise SystemExit('No font size specified')
|
||||
|
||||
|
||||
def set_font_size(boss, window, payload):
|
||||
boss.set_font_size(payload['size'])
|
||||
# }}}
|
||||
|
||||
|
||||
# send_text {{{
|
||||
@cmd(
|
||||
'Send arbitrary text to specified windows',
|
||||
'Send arbitrary text to specified windows. The text follows Python'
|
||||
@@ -92,7 +123,8 @@ not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end
|
||||
Path to a file whose contents you wish to send. Note that in this case the file contents
|
||||
are sent as is, not interpreted for escapes.
|
||||
''',
|
||||
no_response=True
|
||||
no_response=True,
|
||||
argspec='[TEXT TO SEND]'
|
||||
)
|
||||
def cmd_send_text(global_opts, opts, args):
|
||||
limit = 1024
|
||||
@@ -162,15 +194,18 @@ def send_text(boss, window, payload):
|
||||
for window in windows:
|
||||
if window is not None:
|
||||
window.write_to_child(data)
|
||||
# }}}
|
||||
|
||||
|
||||
# set_window_title {{{
|
||||
@cmd(
|
||||
'Set the window title',
|
||||
'Set the title for the specified window(s). If you use the |_ --match| option'
|
||||
' the title will be set for all matched windows. By default, only the window'
|
||||
' in which the command is run is affected. If you do not specify a title, the'
|
||||
' last title set by the child process running in the window will be used.',
|
||||
options_spec=MATCH_WINDOW_OPTION
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='TITLE ...'
|
||||
)
|
||||
def cmd_set_window_title(global_opts, opts, args):
|
||||
return {'title': ' '.join(args), 'match': opts.match}
|
||||
@@ -182,19 +217,22 @@ def set_window_title(boss, window, payload):
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise ValueError('No matching windows for expression: {}'.format(match))
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
window.set_title(payload['title'])
|
||||
# }}}
|
||||
|
||||
|
||||
# set_tab_title {{{
|
||||
@cmd(
|
||||
'Set the tab title',
|
||||
'Set the title for the specified tab(s). If you use the |_ --match| option'
|
||||
' the title will be set for all matched tabs. By default, only the tab'
|
||||
' in which the command is run is affected. If you do not specify a title, the'
|
||||
' title of the currently active window in the tab is used.',
|
||||
options_spec=MATCH_TAB_OPTION
|
||||
options_spec=MATCH_TAB_OPTION,
|
||||
argspec='TITLE ...'
|
||||
)
|
||||
def cmd_set_tab_title(global_opts, opts, args):
|
||||
return {'title': ' '.join(args), 'match': opts.match}
|
||||
@@ -205,21 +243,24 @@ def set_tab_title(boss, window, payload):
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise ValueError('No matching windows for expression: {}'.format(match))
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
tab.set_title(payload['title'])
|
||||
# }}}
|
||||
|
||||
|
||||
# close_window {{{
|
||||
@cmd(
|
||||
'Close the specified window(s)',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the window this command is run in, rather than the active window.
|
||||
'''
|
||||
''',
|
||||
argspec=''
|
||||
)
|
||||
def cmd_close_window(global_opts, opts, args):
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
@@ -230,21 +271,24 @@ def close_window(boss, window, payload):
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise ValueError('No matching windows for expression: {}'.format(match))
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload['self'] else boss.active_window]
|
||||
for window in windows:
|
||||
if window:
|
||||
boss.close_window(window)
|
||||
# }}}
|
||||
|
||||
|
||||
# close_tab {{{
|
||||
@cmd(
|
||||
'Close the specified tab(s)',
|
||||
options_spec=MATCH_TAB_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the tab this command is run in, rather than the active tab.
|
||||
'''
|
||||
''',
|
||||
argspec=''
|
||||
)
|
||||
def cmd_close_tab(global_opts, opts, args):
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
@@ -255,15 +299,17 @@ def close_tab(boss, window, payload):
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise ValueError('No matching windows for expression: {}'.format(match))
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window and payload['self'] else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if window:
|
||||
if tab:
|
||||
boss.close_tab(tab)
|
||||
# }}}
|
||||
|
||||
|
||||
# new_window {{{
|
||||
@cmd(
|
||||
'Open new window',
|
||||
'Open a new window in the specified tab. If you use the |_ --match| option'
|
||||
@@ -294,7 +340,8 @@ Open a new tab
|
||||
|
||||
--tab-title
|
||||
When using --new-tab set the title of the tab.
|
||||
'''
|
||||
''',
|
||||
argspec='[CMD ...]'
|
||||
)
|
||||
def cmd_new_window(global_opts, opts, args):
|
||||
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
|
||||
@@ -319,7 +366,7 @@ def new_window(boss, window, payload):
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise ValueError('No matching windows for expression: {}'.format(match))
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.active_tab]
|
||||
tab = tabs[0]
|
||||
@@ -327,11 +374,14 @@ def new_window(boss, window, payload):
|
||||
if payload['keep_focus'] and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return str(w.id)
|
||||
# }}}
|
||||
|
||||
|
||||
# focus_window {{{
|
||||
@cmd(
|
||||
'Focus the specified window',
|
||||
options_spec=MATCH_WINDOW_OPTION
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='',
|
||||
)
|
||||
def cmd_focus_window(global_opts, opts, args):
|
||||
return {'match': opts.match}
|
||||
@@ -343,13 +393,36 @@ def focus_window(boss, window, payload):
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise ValueError('No matching windows for expression: {}'.format(match))
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
boss.set_active_window(window)
|
||||
break
|
||||
# }}}
|
||||
|
||||
|
||||
# focus_tab {{{
|
||||
@cmd(
|
||||
'Focus the specified tab',
|
||||
'The active window in the specified tab will be focused.',
|
||||
options_spec=MATCH_TAB_OPTION,
|
||||
argspec='',
|
||||
)
|
||||
def cmd_focus_tab(global_opts, opts, args):
|
||||
return {'match': opts.match}
|
||||
|
||||
|
||||
def focus_tab(boss, window, payload):
|
||||
match = payload['match']
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
tab = tabs[0]
|
||||
boss.set_active_tab(tab)
|
||||
# }}}
|
||||
|
||||
|
||||
# get_text {{{
|
||||
@cmd(
|
||||
'Get text from the specified window',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
@@ -370,7 +443,8 @@ getting the current selection, the result is always plain text.
|
||||
--self
|
||||
type=bool-set
|
||||
If specified get text from the window this command is run in, rather than the active window.
|
||||
'''
|
||||
''',
|
||||
argspec=''
|
||||
)
|
||||
def cmd_get_text(global_opts, opts, args):
|
||||
return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self}
|
||||
@@ -381,7 +455,7 @@ def get_text(boss, window, payload):
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise ValueError('No matching windows for expression: {}'.format(match))
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload['self'] else boss.active_window]
|
||||
window = windows[0]
|
||||
@@ -390,6 +464,7 @@ def get_text(boss, window, payload):
|
||||
else:
|
||||
ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=True)
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
|
||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
||||
|
||||
@@ -219,7 +219,7 @@ screen_change_scrollback_size(Screen *self, unsigned int size) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
reset_callbacks(Screen *self) {
|
||||
reset_callbacks(Screen *self, PyObject *a UNUSED) {
|
||||
Py_CLEAR(self->callbacks);
|
||||
self->callbacks = Py_None;
|
||||
Py_INCREF(self->callbacks);
|
||||
@@ -1397,6 +1397,13 @@ apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, Selection
|
||||
|
||||
}
|
||||
|
||||
bool
|
||||
screen_has_selection(Screen *self) {
|
||||
SelectionBoundary start, end;
|
||||
selection_limits_(selection, &start, &end);
|
||||
return !is_selection_empty(self, start.x, start.y, end.x, end.y);
|
||||
}
|
||||
|
||||
void
|
||||
screen_apply_selection(Screen *self, void *address, size_t size) {
|
||||
memset(address, 0, size);
|
||||
@@ -1444,8 +1451,8 @@ screen_open_url(Screen *self) {
|
||||
// }}}
|
||||
|
||||
// Python interface {{{
|
||||
#define WRAP0(name) static PyObject* name(Screen *self) { screen_##name(self); Py_RETURN_NONE; }
|
||||
#define WRAP0x(name) static PyObject* xxx_##name(Screen *self) { screen_##name(self); Py_RETURN_NONE; }
|
||||
#define WRAP0(name) static PyObject* name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; }
|
||||
#define WRAP0x(name) static PyObject* xxx_##name(Screen *self, PyObject *a UNUSED) { screen_##name(self); Py_RETURN_NONE; }
|
||||
#define WRAP1(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v); Py_RETURN_NONE; }
|
||||
#define WRAP1B(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; int b=false; if(!PyArg_ParseTuple(args, "|Ip", &v, &b)) return NULL; screen_##name(self, v, b); Py_RETURN_NONE; }
|
||||
#define WRAP1E(name, defval, ...) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v, __VA_ARGS__); Py_RETURN_NONE; }
|
||||
@@ -1458,7 +1465,7 @@ as_text(Screen *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
refresh_sprite_positions(Screen *self) {
|
||||
refresh_sprite_positions(Screen *self, PyObject *a UNUSED) {
|
||||
self->is_dirty = true;
|
||||
for (index_type i = 0; i < self->lines; i++) {
|
||||
linebuf_mark_line_dirty(self->main_linebuf, i);
|
||||
@@ -1572,13 +1579,13 @@ set_mode(Screen *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
reset_dirty(Screen *self) {
|
||||
reset_dirty(Screen *self, PyObject *a UNUSED) {
|
||||
screen_reset_dirty(self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
is_using_alternate_linebuf(Screen *self) {
|
||||
is_using_alternate_linebuf(Screen *self, PyObject *a UNUSED) {
|
||||
if (self->linebuf == self->alt_linebuf) Py_RETURN_TRUE;
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
@@ -1631,7 +1638,7 @@ change_scrollback_size(Screen *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
text_for_selection(Screen *self) {
|
||||
text_for_selection(Screen *self, PyObject *a UNUSED) {
|
||||
FullSelectionBoundary start, end;
|
||||
full_selection_limits_(selection, &start, &end);
|
||||
PyObject *ans = NULL;
|
||||
@@ -1747,26 +1754,26 @@ screen_update_selection(Screen *self, index_type x, index_type y, bool ended) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
mark_as_dirty(Screen *self) {
|
||||
mark_as_dirty(Screen *self, PyObject *a UNUSED) {
|
||||
self->is_dirty = true;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
current_char_width(Screen *self) {
|
||||
current_char_width(Screen *self, PyObject *a UNUSED) {
|
||||
#define current_char_width_doc "The width of the character under the cursor"
|
||||
return PyLong_FromUnsignedLong(screen_current_char_width(self));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
is_main_linebuf(Screen *self) {
|
||||
is_main_linebuf(Screen *self, PyObject *a UNUSED) {
|
||||
PyObject *ans = (self->linebuf == self->main_linebuf) ? Py_True : Py_False;
|
||||
Py_INCREF(ans);
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
toggle_alt_screen(Screen *self) {
|
||||
toggle_alt_screen(Screen *self, PyObject *a UNUSED) {
|
||||
screen_toggle_screen_buffer(self);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ void report_device_status(Screen *self, unsigned int which, bool UNUSED);
|
||||
void report_mode_status(Screen *self, unsigned int which, bool);
|
||||
void screen_apply_selection(Screen *self, void *address, size_t size);
|
||||
bool screen_is_selection_dirty(Screen *self);
|
||||
bool screen_has_selection(Screen*);
|
||||
bool screen_invert_colors(Screen *self);
|
||||
void screen_update_cell_data(Screen *self, void *address, size_t sz);
|
||||
bool screen_is_cursor_visible(Screen *self);
|
||||
|
||||
@@ -587,9 +587,8 @@ end:
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self)
|
||||
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
|
||||
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
|
||||
#define PYWRAP2(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args, PyObject *kw)
|
||||
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
|
||||
#define ONE_INT(name) PYWRAP1(name) { name(PyLong_AsSsize_t(args)); Py_RETURN_NONE; }
|
||||
#define TWO_INT(name) PYWRAP1(name) { int a, b; PA("ii", &a, &b); name(a, b); Py_RETURN_NONE; }
|
||||
|
||||
@@ -201,6 +201,7 @@ static inline void
|
||||
set_active_tab(id_type os_window_id, unsigned int idx) {
|
||||
WITH_OS_WINDOW(os_window_id)
|
||||
os_window->active_tab = idx;
|
||||
os_window->needs_render = true;
|
||||
END_WITH_OS_WINDOW
|
||||
}
|
||||
|
||||
@@ -208,6 +209,7 @@ static inline void
|
||||
set_active_window(id_type os_window_id, id_type tab_id, unsigned int idx) {
|
||||
WITH_TAB(os_window_id, tab_id)
|
||||
tab->active_window = idx;
|
||||
osw->needs_render = true;
|
||||
END_WITH_TAB;
|
||||
}
|
||||
|
||||
@@ -268,9 +270,8 @@ os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
|
||||
|
||||
|
||||
// Python API {{{
|
||||
#define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self)
|
||||
#define PYWRAP0(name) static PyObject* py##name(PYNOARG)
|
||||
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
|
||||
#define PYWRAP2(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args, PyObject *kw)
|
||||
#define PA(fmt, ...) if(!PyArg_ParseTuple(args, fmt, __VA_ARGS__)) return NULL;
|
||||
#define ONE_UINT(name) PYWRAP1(name) { name((unsigned int)PyLong_AsUnsignedLong(args)); Py_RETURN_NONE; }
|
||||
#define TWO_UINT(name) PYWRAP1(name) { unsigned int a, b; PA("II", &a, &b); name(a, b); Py_RETURN_NONE; }
|
||||
@@ -353,6 +354,7 @@ PYWRAP1(set_options) {
|
||||
S(repaint_delay, repaint_delay);
|
||||
S(input_delay, repaint_delay);
|
||||
S(sync_to_monitor, PyObject_IsTrue);
|
||||
S(close_on_child_death, PyObject_IsTrue);
|
||||
S(macos_option_as_alt, PyObject_IsTrue);
|
||||
S(macos_hide_titlebar, PyObject_IsTrue);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ typedef struct {
|
||||
float inactive_text_alpha;
|
||||
Edge tab_bar_edge;
|
||||
bool sync_to_monitor;
|
||||
bool close_on_child_death;
|
||||
} Options;
|
||||
|
||||
typedef struct {
|
||||
@@ -97,11 +98,11 @@ typedef struct {
|
||||
void *handle;
|
||||
id_type id;
|
||||
OSWindowGeometry before_fullscreen;
|
||||
int viewport_width, viewport_height;
|
||||
int viewport_width, viewport_height, window_width, window_height;
|
||||
double viewport_x_ratio, viewport_y_ratio;
|
||||
Tab *tabs;
|
||||
unsigned int active_tab, num_tabs, capacity, last_active_tab, last_num_tabs, last_active_window_id;
|
||||
bool focused_at_last_render;
|
||||
bool focused_at_last_render, needs_render;
|
||||
ScreenRenderData tab_bar_render_data;
|
||||
bool is_focused;
|
||||
double cursor_blink_zero_time, last_mouse_activity_at;
|
||||
|
||||
@@ -17,7 +17,7 @@ from .fast_data_types import (
|
||||
)
|
||||
from .layout import Rect, all_layouts
|
||||
from .session import resolved_shell
|
||||
from .utils import color_as_int
|
||||
from .utils import color_as_int, log_error
|
||||
from .window import Window, calculate_gl_geometry
|
||||
|
||||
TabbarData = namedtuple('TabbarData', 'title is_active is_last')
|
||||
@@ -38,7 +38,7 @@ class Tab: # {{{
|
||||
raise Exception('No OS window with id {} found, or tab counter has wrapped'.format(self.os_window_id))
|
||||
self.opts, self.args = tab_manager.opts, tab_manager.args
|
||||
self.name = getattr(session_tab, 'name', '')
|
||||
self.enabled_layouts = list(getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts)
|
||||
self.enabled_layouts = [x.lower() for x in getattr(session_tab, 'enabled_layouts', None) or self.opts.enabled_layouts]
|
||||
self.borders = Borders(self.os_window_id, self.id, self.opts)
|
||||
self.windows = deque()
|
||||
self.active_window_idx = 0
|
||||
@@ -116,6 +116,14 @@ class Tab: # {{{
|
||||
self.current_layout = self.create_layout_object(nl)
|
||||
self.relayout()
|
||||
|
||||
def goto_layout(self, layout_name):
|
||||
layout_name = layout_name.lower()
|
||||
if layout_name not in self.enabled_layouts:
|
||||
log_error('Unknown or disabled layout: {}'.format(layout_name))
|
||||
return
|
||||
self.current_layout = self.create_layout_object(layout_name)
|
||||
self.relayout()
|
||||
|
||||
def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None):
|
||||
if cmd is None:
|
||||
if use_shell:
|
||||
|
||||
@@ -94,7 +94,7 @@ int main(int argc, char *argv[]) {
|
||||
#ifdef FOR_LAUNCHER
|
||||
num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../Frameworks/kitty");
|
||||
#else
|
||||
num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../lib/kitty");
|
||||
num = snprintf(lib, PATH_MAX, "%s%s%s%s", exe_dir, "/../", LIB_DIR_NAME, "/kitty");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -57,9 +57,14 @@ Let's change the title of the current tab:
|
||||
|
||||
kitty @ set-tab-title Master Tab
|
||||
|
||||
Now lets switch to the newly opened tab.
|
||||
Now lets switch to the newly opened tab:
|
||||
|
||||
kitty @ focus-window --match title:New
|
||||
kitty @ focus-tab --match title:New
|
||||
|
||||
Similarly, to focus the previously opened output window (which will also switch
|
||||
back to the old tab, automatically):
|
||||
|
||||
kitty @ focus-window --match title:Output
|
||||
|
||||
You can get a listing of available tabs and windows, by running:
|
||||
|
||||
|
||||
54
setup.py
54
setup.py
@@ -3,6 +3,7 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
@@ -484,18 +485,23 @@ def build_linux_launcher(args, launcher_dir='.', for_bundle=False, sh_launcher=F
|
||||
cflags.append('-DPYVER="{}"'.format(sysconfig.get_python_version()))
|
||||
elif sh_launcher:
|
||||
cflags.append('-DFOR_LAUNCHER')
|
||||
cflags.append('-DLIB_DIR_NAME="{}"'.format(args.libdir_name.strip('/')))
|
||||
pylib = get_python_flags(cflags)
|
||||
exe = 'kitty-profile' if args.profile else 'kitty'
|
||||
cflags += shlex.split(os.environ.get('CFLAGS', ''))
|
||||
ldflags = shlex.split(os.environ.get('LDFLAGS', ''))
|
||||
cmd = [env.cc] + cflags + [
|
||||
'linux-launcher.c', '-o',
|
||||
os.path.join(launcher_dir, exe)
|
||||
] + libs + pylib
|
||||
] + ldflags + libs + pylib
|
||||
run_tool(cmd)
|
||||
|
||||
|
||||
def package(args, for_bundle=False, sh_launcher=False): # {{{
|
||||
ddir = args.prefix
|
||||
libdir = os.path.join(ddir, 'lib', 'kitty')
|
||||
if for_bundle or sh_launcher:
|
||||
args.libdir_name = 'lib'
|
||||
libdir = os.path.join(ddir, args.libdir_name.strip('/'), 'kitty')
|
||||
if os.path.exists(libdir):
|
||||
shutil.rmtree(libdir)
|
||||
os.makedirs(os.path.join(libdir, 'logo'))
|
||||
@@ -592,14 +598,37 @@ Categories=System;
|
||||
|
||||
|
||||
def clean():
|
||||
for f in subprocess.check_output(
|
||||
'git ls-files --others --ignored --exclude-from=.gitignore'.split()
|
||||
).decode('utf-8').splitlines():
|
||||
if f.startswith('logo/kitty.iconset') or f.startswith('dev/'):
|
||||
continue
|
||||
os.unlink(f)
|
||||
if os.sep in f and not os.listdir(os.path.dirname(f)):
|
||||
os.rmdir(os.path.dirname(f))
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
if os.path.exists('.git'):
|
||||
for f in subprocess.check_output(
|
||||
'git ls-files --others --ignored --exclude-from=.gitignore'.split()
|
||||
).decode('utf-8').splitlines():
|
||||
if f.startswith('logo/kitty.iconset') or f.startswith('dev/'):
|
||||
continue
|
||||
os.unlink(f)
|
||||
if os.sep in f and not os.listdir(os.path.dirname(f)):
|
||||
os.rmdir(os.path.dirname(f))
|
||||
return
|
||||
# Not a git checkout, clean manually
|
||||
|
||||
def safe_remove(*entries):
|
||||
for x in entries:
|
||||
if os.path.exists(x):
|
||||
if os.path.isdir(x):
|
||||
shutil.rmtree(x)
|
||||
else:
|
||||
os.unlink(x)
|
||||
|
||||
safe_remove('build', 'compile_commands.json', 'linux-package', 'kitty.app')
|
||||
for root, dirs, files in os.walk('.'):
|
||||
remove_dirs = {d for d in dirs if d == '__pycache__'}
|
||||
[(shutil.rmtree(os.path.join(root, d)), dirs.remove(d)) for d in remove_dirs]
|
||||
for f in files:
|
||||
ext = f.rpartition('.')[-1]
|
||||
if ext in ('so', 'dylib', 'pyc', 'pyo'):
|
||||
os.unlink(os.path.join(root, f))
|
||||
for x in glob.glob('glfw/wayland-*-protocol.[ch]'):
|
||||
os.unlink(x)
|
||||
|
||||
|
||||
def option_parser():
|
||||
@@ -650,6 +679,11 @@ def option_parser():
|
||||
action='store_true',
|
||||
help='Use the -pg compile flag to add profiling information'
|
||||
)
|
||||
p.add_argument(
|
||||
'--libdir-name',
|
||||
default='lib',
|
||||
help='The name of the directory inside --prefix in which to store compiled files. Defaults to "lib"'
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user