Compare commits

..

41 Commits

Author SHA1 Message Date
Kovid Goyal
08d88af2fb version 0.32.1 2024-01-26 08:33:51 +05:30
Kovid Goyal
4dfbcb539f Add basic tests for modal mappings 2024-01-25 14:42:27 +05:30
Kovid Goyal
cc0d6621a4 Also document how to set user vars from nvim 2024-01-25 14:27:55 +05:30
Kovid Goyal
d6e55f72c0 Forgot to stub out one method for the test 2024-01-25 14:18:09 +05:30
Kovid Goyal
cd30de3727 Fix #7055 2024-01-25 14:06:52 +05:30
Kovid Goyal
cec427777c Add some tests for mappings 2024-01-25 13:56:42 +05:30
Kovid Goyal
30e3ad83bc Move mapping code into its own class
Better encapsulation. Makes boss.py smaller. Allows writing tests
for mapping logic
2024-01-25 11:51:43 +05:30
Kovid Goyal
9ef6801f4c A single key shortcut should override all previous multi-key shortcuts that have that shortcut as a prefix
Fixes #7058
2024-01-25 11:24:40 +05:30
Kovid Goyal
7f1c371b6e DRYer 2024-01-25 09:00:46 +05:30
Kovid Goyal
2f7b0d1d94 Dont show multiple keys bindings in debug output when their focus on conditions are the same 2024-01-25 08:08:52 +05:30
Kovid Goyal
90e1ba7781 Fix #7051 2024-01-24 18:56:04 +05:30
Kovid Goyal
0dfe89a817 ... 2024-01-23 18:42:28 +05:30
Kovid Goyal
c76f75a154 Fix a regression in the previous release that caused overriding of existing multi-key mappings to fail
Fixes #7044
2024-01-23 15:49:30 +05:30
Kovid Goyal
f51520eb79 Clarify behavior of image id==!0 and placement id == 0
See https://github.com/kovidgoyal/kitty/discussions/7043
2024-01-23 08:41:23 +05:30
Kovid Goyal
828f4f312a Wayland+NVIDIA: Do not request an sRGB output buffer as a bug in Wayland causes kitty to not start
Fixes #7021
2024-01-22 13:22:04 +05:30
Kovid Goyal
a9c7a85d9a Clarify the behavior of functional keys with no legacy encoding
See https://github.com/kovidgoyal/kitty/discussions/7037
2024-01-22 08:35:54 +05:30
Kovid Goyal
38393b50c1 Show how to send SIGUSR1 to kitty 2024-01-22 07:36:57 +05:30
Kovid Goyal
7b6c532ac2 ... 2024-01-21 15:34:06 +05:30
Kovid Goyal
b3e74de390 More work on pager kitten 2024-01-21 14:47:56 +05:30
Kovid Goyal
1aa4d7d24b When displaying scrollback fallback to less if the user configures a pager that is not in PATH 2024-01-21 09:22:02 +05:30
Kovid Goyal
a3e324d623 When testing for cf-protection support take env into account 2024-01-21 08:42:55 +05:30
Kovid Goyal
d6116f7426 Fix #7026 2024-01-21 08:33:59 +05:30
Kovid Goyal
ab9631f045 Better fix 2024-01-21 08:27:16 +05:30
Kovid Goyal
ec0a449c63 Fix a regression in the previous release that caused kitten @ send-text with a match parameter to send text twice to the active window
Fixes #7027
2024-01-21 08:24:22 +05:30
Kovid Goyal
01ffbfdb42 Fix a regression in the previous release that caused kitten @ launch --cwd=current to fail over SSH
Fixes #7028
2024-01-21 08:06:44 +05:30
Kovid Goyal
f5621bd56c Merge branch 'dmenu-term' of https://github.com/weakish/kitty 2024-01-20 19:15:50 +05:30
weakish
708750173e Remove dmenu-term in docs
The dmenu-term link returns 404 now.
2024-01-20 08:39:05 +00:00
Kovid Goyal
20e43a3e7d Fix a regression in the previous release that caused multi-key sequences to not abort when pressing an unknown key
Fixes #7022
2024-01-20 08:13:12 +05:30
Kovid Goyal
ff4ee95eba ... 2024-01-20 06:49:49 +05:30
Kovid Goyal
2707c44f0f DRYer 2024-01-19 21:48:40 +05:30
Kovid Goyal
e7e401c8dd More work on pager kitten 2024-01-19 21:16:09 +05:30
Kovid Goyal
b0ab5bd5eb ... 2024-01-19 20:50:11 +05:30
Kovid Goyal
d75395794d ... 2024-01-19 20:46:20 +05:30
Kovid Goyal
c7d894d499 Merge branch 'fix-go-version-check' of https://github.com/Maytha8/kitty 2024-01-19 20:22:42 +05:30
Maytham Alsudany
30905db75f Explicit GO111MODULE=on when getting required Go version 2024-01-19 22:46:48 +08:00
Kovid Goyal
89c3b4f9e2 macOS: Fix a regression in the previous release that broke overriding keyboard shortcuts for actions present in the global menu bar
Fixes #7016
2024-01-19 19:44:04 +05:30
Kovid Goyal
0bd50abd77 Start work on pager kitten 2024-01-19 15:09:20 +05:30
Kovid Goyal
7038292d11 Merge branch 'master' of https://github.com/solopasha/kitty 2024-01-19 14:03:30 +05:30
Kovid Goyal
b33f8416db Fix for spurious github code scanning alert 2024-01-19 14:01:26 +05:30
Pavel Solovev
99b3d0727d Fix build with gcc14 2024-01-19 11:25:53 +03:00
Kovid Goyal
9503725a32 Fix #7013 2024-01-19 13:29:12 +05:30
32 changed files with 711 additions and 184 deletions

View File

@@ -43,6 +43,21 @@ The :doc:`ssh kitten <kittens/ssh>` is redesigned with powerful new features:
Detailed list of changes
-------------------------------------
0.32.1 [2024-02-26]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- macOS: Fix a regression in the previous release that broke overriding keyboard shortcuts for actions present in the global menu bar (:iss:`7016`)
- Fix a regression in the previous release that caused multi-key sequences to not abort when pressing an unknown key (:iss:`7022`)
- Fix a regression in the previous release that caused `kitten @ launch --cwd=current` to fail over SSH (:iss:`7028`)
- Fix a regression in the previous release that caused `kitten @ send-text` with a match tab parameter to send text twice to the active window (:iss:`7027`)
- Fix a regression in the previous release that caused overriding of existing multi-key mappings to fail (:iss:`7044`, :iss:`7058`)
- Wayland+NVIDIA: Do not request an sRGB output buffer as a bug in Wayland causes kitty to not start (:iss:`7021`)
0.32.0 [2024-01-19]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -16,10 +16,10 @@ frames-per-second. See below for an overview of all customization possibilities.
You can open the config file within kitty by pressing :sc:`edit_config_file`
(:kbd:`⌘+,` on macOS). A :file:`kitty.conf` with commented default
configurations and descriptions will be created if the file does not exist.
You can reload the config file within kitty by pressing :sc:`reload_config_file`
(:kbd:`⌃+⌘+,` on macOS) or sending kitty the ``SIGUSR1`` signal.
You can also display the current configuration by pressing :sc:`debug_config`
(:kbd:`⌥+⌘+,` on macOS).
You can reload the config file within kitty by pressing
:sc:`reload_config_file` (:kbd:`⌃+⌘+,` on macOS) or sending kitty the
``SIGUSR1`` signal with ``kill -SIGUSR1 $KITTY_PID``. You can also display the
current configuration by pressing :sc:`debug_config` (:kbd:`⌥+⌘+,` on macOS).
.. _confloc:

View File

@@ -460,7 +460,10 @@ When you specify a placement id, it will be added to the acknowledgement code
above. Every placement is uniquely identified by the pair of the ``image id``
and the ``placement id``. If you specify a placement id for an image that does
not have an id (i.e. has id=0), it will be ignored. In particular this means
there can exist multiple images with ``image id=0, placement id=0``.
there can exist multiple images with ``image id=0, placement id=0``. Not
specifying a placement id or using ``p=0`` for multiple put commands (``a=p``)
with the same non-zero image id results in multiple placements the image.
An example response::
<ESC>_Gi=<image id>,p=<placement id>;OK<ESC>\

View File

@@ -189,13 +189,6 @@ A tool to display weather information in your terminal with curl
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
View and manage the system clipboard under Wayland in your kitty terminal
.. tool_dmenu_term:
`dmenu-term <https://github.com/maximbaz/dmenu-term>`_
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Run applications on your system with fuzzy find inside a kitty window
Editor integration
-----------------------

View File

@@ -105,9 +105,7 @@ do not. When a key event produces text, the text is sent directly as UTF-8
encoded bytes. This is safe as UTF-8 contains no C0 control codes.
When the key event does not have text, the key event is encoded as an escape code. In
legacy compatibility mode (the default) this uses legacy escape codes, so old terminal
applications continue to work. Key events that could not be represented in
legacy mode are encoded using a ``CSI u`` escape code, that most terminal
programs should just ignore. For more advanced features, such as release/repeat
applications continue to work. For more advanced features, such as release/repeat
reporting etc., applications can tell the terminal they want this information by
sending an escape code to :ref:`progressively enhance <progressive_enhancement>` the data reported for
key events.
@@ -229,8 +227,10 @@ enhancement <progressive_enhancement>` mechanism described below. Some examples:
shift+a -> CSI 97 ; 2 ; 65 u # The text 'A' is reported as 65
option+a -> CSI 97 ; ; 229 u # The text 'å' is reported as 229
If multiple code points are present, they must be separated by colons.
If no known key is associated with the text the key number ``0`` must be used.
If multiple code points are present, they must be separated by colons. If no
known key is associated with the text the key number ``0`` must be used. The
associated text must not contain control codes (control codes are code points
below U+0020 and codepoints in the C0 and C1 blocks).
Non-Unicode keys
@@ -482,6 +482,12 @@ must correspond to the :kbd:`Backspace` key.
All keypad keys are reported as their equivalent non-keypad keys. To
distinguish these, use the :ref:`disambiguate <disambiguate>` flag.
Terminals may choose what they want to do about functional keys that have no
legacy encoding. kitty chooses to encode these using ``CSI u`` encoding even in
legacy mode, so that they become usable even in programs that do not
understand the full kitty keyboard protocol. However, terminals may instead choose to
ignore such keys in legacy mode instead, or have an option to control this behavior.
.. _legacy_text:
Legacy text keys

View File

@@ -193,12 +193,37 @@ details. A more practical example unmaps the key when the focused window is runn
map --when-focus-on var:in_editor
In order to make this work, you need the following lines in your :file:`.vimrc`::
In order to make this work, you need to configure your editor as show below:
let &t_ti = &t_ti . "\\033]1337;SetUserVar=in_editor=MQo\\007"
let &t_te = &t_te . "\\033]1337;SetUserVar=in_editor\\007"
.. tab:: vim
These cause vim to set the :code:`in_editor` variable in kitty and unset it when leaving vim.
In :file:`~/.vimrc` add:
.. code-block:: vim
let &t_ti = &t_ti . "\\033]1337;SetUserVar=in_editor=MQo\\007"
let &t_te = &t_te . "\\033]1337;SetUserVar=in_editor\\007"
.. tab:: neovim
In :file:`~/.config/nvim/init.lua` add:
.. code-block:: lua
vim.api.nvim_create_autocmd({ "VimEnter" }, {
group = vim.api.nvim_create_augroup("KittySetVarVimEnter", { clear = true }),
callback = function()
io.stdout:write("\x1b]1337;SetUserVar=in_editor=MQo\007")
end,
})
vim.api.nvim_create_autocmd({ "VimLeave" }, {
group = vim.api.nvim_create_augroup("KittyUnsetVarVimLeave", { clear = true }),
callback = function()
io.stdout:write("\x1b]1337;SetUserVar=in_editor\007")
end,
})
These cause the editor to set the :code:`in_editor` variable in kitty and unset it when exiting.
Sending arbitrary text or keys to the program running in kitty
--------------------------------------------------------------------------------

View File

@@ -428,7 +428,7 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
# kittens {{{
@lru_cache
def wrapped_kittens() -> Sequence[str]:
def wrapped_kittens() -> Tuple[str, ...]:
with open('shell-integration/ssh/kitty') as f:
for line in f:
if line.startswith(' wrapped_kittens="'):
@@ -465,7 +465,7 @@ def generate_extra_cli_parser(name: str, spec: str) -> None:
def kitten_clis() -> None:
from kittens.runner import get_kitten_conf_docs, get_kitten_extra_cli_parsers
for kitten in wrapped_kittens():
for kitten in wrapped_kittens() + ('pager',):
defn = get_kitten_conf_docs(kitten)
if defn is not None:
generate_conf_parser(kitten, defn)

5
glfw/context.c vendored
View File

@@ -32,7 +32,6 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
@@ -236,10 +235,10 @@ bool _glfwRefreshContextAttribs(_GLFWwindow* window,
}
}
if (!sscanf(version, "%d.%d.%d",
if (sscanf(version, "%d.%d.%d",
&window->context.major,
&window->context.minor,
&window->context.revision))
&window->context.revision) < 1)
{
if (window->context.client == GLFW_OPENGL_API)
{

View File

@@ -76,7 +76,7 @@ blur_mask(kernel_type *image_data, ssize_t width, ssize_t height, ssize_t kernel
static kernel_type*
create_shadow_mask(size_t width, size_t height, size_t margin, size_t kernel_size, kernel_type base_alpha, kernel_type sigma) {
kernel_type *mask = calloc(sizeof(kernel_type), 2 * width * height + kernel_size);
kernel_type *mask = calloc(2 * width * height + kernel_size, sizeof(kernel_type));
if (!mask) return NULL;
for (size_t y = margin; y < height - margin; y++) {
kernel_type *row = mask + y * width;

View File

125
kittens/pager/file_input.go Normal file
View File

@@ -0,0 +1,125 @@
// License: GPLv3 Copyright: 2024, Kovid Goyal, <kovid at kovidgoyal.net>
package pager
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"golang.org/x/sys/unix"
)
var _ = fmt.Print
func wait_for_file_to_grow(file_name string, limit int64) (err error) {
// TODO: Use the fsnotify package to avoid this poll
for {
time.Sleep(time.Second)
s, err := os.Stat(file_name)
if err != nil {
return err
}
if s.Size() > limit {
break
}
}
return
}
func read_input(input_file *os.File, input_file_name string, input_channel chan<- input_line_struct, follow bool, count_carriage_returns bool) {
const buf_capacity = 8192
var buf_array [buf_capacity]byte
output_buf := strings.Builder{}
output_buf.Grow(buf_capacity)
var err error
var n int
var total_read int64
var num_carriage_returns int
defer func() {
_ = input_file.Close()
last := input_line_struct{line: output_buf.String(), err: err, num_carriage_returns: num_carriage_returns}
if errors.Is(err, io.EOF) {
last.err = nil
}
if len(last.line) > 0 || last.err != nil {
input_channel <- last
}
close(input_channel)
}()
var process_chunk func([]byte)
if count_carriage_returns {
process_chunk = func(chunk []byte) {
for _, ch := range chunk {
switch ch {
case '\r':
num_carriage_returns += 1
default:
_ = output_buf.WriteByte(ch)
case '\n':
input_channel <- input_line_struct{line: output_buf.String(), num_carriage_returns: num_carriage_returns, is_a_complete_line: true}
num_carriage_returns = 0
output_buf.Reset()
output_buf.Grow(buf_capacity)
}
}
}
} else {
process_chunk = func(chunk []byte) {
for len(chunk) > 0 {
idx := bytes.IndexByte(chunk, '\n')
switch idx {
case -1:
_, _ = output_buf.Write(chunk)
chunk = nil
default:
_, _ = output_buf.Write(chunk[idx:])
chunk = chunk[idx+1:]
input_channel <- input_line_struct{line: output_buf.String(), is_a_complete_line: true}
output_buf.Reset()
output_buf.Grow(buf_capacity)
}
}
}
}
for {
for err != nil {
n, err = input_file.Read(buf_array[:])
if n > 0 {
total_read += int64(n)
process_chunk(buf_array[:n])
}
if err == unix.EAGAIN || err == unix.EINTR {
err = nil
}
}
if !follow {
break
}
if errors.Is(err, io.EOF) {
input_file.Close()
if err = wait_for_file_to_grow(input_file_name, total_read); err != nil {
break
}
if input_file, err = os.Open(input_file_name); err != nil {
break
}
var off int64
if off, err = input_file.Seek(total_read, io.SeekStart); err != nil {
break
}
if off != total_read {
err = fmt.Errorf("Failed to seek in %s to: %d", input_file_name, off)
break
}
}
}
}

74
kittens/pager/main.go Normal file
View File

@@ -0,0 +1,74 @@
// License: GPLv3 Copyright: 2024, Kovid Goyal, <kovid at kovidgoyal.net>
package pager
// TODO:
// Scroll to line when starting
// Visual mode elect with copy/paste and copy-on-select
// Mouse based wheel scroll, drag to select, drag scroll, double click to select
// Hyperlinks: Clicking should delegate to terminal and also allow user to specify action
// Keyboard hints mode for clicking hyperlinks
// Display images when used as scrollback pager
// automatic follow when input is a pipe/tty and on last line like tail -f
// syntax highlighting using chroma
import (
"fmt"
"os"
"kitty/tools/cli"
"kitty/tools/tty"
)
var _ = fmt.Print
var debugprintln = tty.DebugPrintln
var _ = debugprintln
type input_line_struct struct {
line string
num_carriage_returns int
is_a_complete_line bool
err error
}
type global_state_struct struct {
input_file_name string
opts *Options
}
var global_state global_state_struct
func main(_ *cli.Command, opts_ *Options, args []string) (rc int, err error) {
global_state.opts = opts_
input_channel := make(chan input_line_struct, 4096)
var input_file *os.File
if len(args) > 1 {
return 1, fmt.Errorf("Only a single file can be viewed at a time")
}
if len(args) == 0 {
if tty.IsTerminal(os.Stdin.Fd()) {
return 1, fmt.Errorf("STDIN is a terminal and no filename specified. See --help")
}
input_file = os.Stdin
global_state.input_file_name = "/dev/stdin"
} else {
input_file, err = os.Open(args[0])
if err != nil {
return 1, err
}
if tty.IsTerminal(input_file.Fd()) {
return 1, fmt.Errorf("%s is a terminal not paging it", args[0])
}
global_state.input_file_name = args[0]
}
follow := global_state.opts.Follow
if follow && global_state.input_file_name == "/dev/stdin" {
follow = false
}
go read_input(input_file, global_state.input_file_name, input_channel, follow, global_state.opts.Role == "scrollback")
return
}
func EntryPoint(parent *cli.Command) {
create_cmd(parent, main)
}

42
kittens/pager/main.py Normal file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
import sys
from typing import List
from kitty.cli import CompletionSpec
OPTIONS = '''
--role
default=pager
choices=pager,scrollback
The role the pager is used for. The default is a standard less like pager.
--follow
type=bool-set
Follow changes in the specified file, automatically scrolling if currently on the last line.
'''.format
help_text = '''\
Display text in a pager with various features such as searching, copy/paste, etc.
Text can some from the specified file or from STDIN. If no filename is specified
and STDIN is not a TTY, it is used.
'''
usage = '[filename]'
def main(args: List[str]) -> None:
raise SystemExit('Must be run as kitten pager')
if __name__ == '__main__':
main(sys.argv)
elif __name__ == '__doc__':
cd = sys.cli_docs # type: ignore
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['short_desc'] = 'Pretty, side-by-side diffing of files and images'
cd['args_completion'] = CompletionSpec.from_string('type:file mime:text/* group:"Text files"')

View File

@@ -91,7 +91,6 @@ from .fast_data_types import (
get_options,
get_os_window_size,
global_font_size,
is_modifier_key,
last_focused_os_window_id,
mark_os_window_for_close,
os_window_focus_counters,
@@ -105,7 +104,6 @@ from .fast_data_types import (
set_application_quit_request,
set_background_image,
set_boss,
set_ignore_os_keyboard_processing,
set_options,
set_os_window_chrome,
set_os_window_size,
@@ -117,11 +115,11 @@ from .fast_data_types import (
wrapped_kitten_names,
)
from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut
from .keys import Mappings
from .layout.base import set_layout_options
from .notify import notification_activated
from .options.types import Options
from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition, KeyMap
from .options.utils import MINIMUM_FONT_SIZE, KeyboardMode, KeyDefinition
from .os_window_size import initial_window_size_func
from .rgb import color_from_int
from .session import Session, create_sessions, get_os_window_sizing_data
@@ -297,7 +295,7 @@ class VisualSelect:
set_os_window_title(self.os_window_id, '')
boss = get_boss()
redirect_mouse_handling(False)
boss.keyboard_mode_stack = []
boss.mappings.clear_keyboard_modes()
for wid in self.window_ids:
w = boss.window_id_map.get(wid)
if w is not None:
@@ -375,27 +373,11 @@ class Boss:
set_boss(self)
self.args = args
self.mouse_handler: Optional[Callable[[WindowSystemMouseEvent], None]] = None
self.keyboard_mode_stack: List[KeyboardMode] = []
self.update_keymap(global_shortcuts)
self.mappings = Mappings(global_shortcuts)
if is_macos:
from .fast_data_types import cocoa_set_notification_activated_callback
cocoa_set_notification_activated_callback(notification_activated)
def update_keymap(self, global_shortcuts:Optional[Dict[str, SingleKey]] = None) -> None:
if global_shortcuts is None:
if is_macos:
from .main import set_cocoa_global_shortcuts
global_shortcuts = set_cocoa_global_shortcuts(get_options())
else:
global_shortcuts = {}
self.global_shortcuts_map: KeyMap = {v: [KeyDefinition(definition=k)] for k, v in global_shortcuts.items()}
self.global_shortcuts = global_shortcuts
self.keyboard_modes = get_options().keyboard_modes.copy()
km = self.keyboard_modes[''].keymap
self.keyboard_modes[''].keymap = km = km.copy()
for sc in self.global_shortcuts.values():
km.pop(sc, None)
def startup_first_child(self, os_window_id: Optional[int], startup_sessions: Iterable[Session] = ()) -> None:
si = startup_sessions or create_sessions(get_options(), self.args, default_session=get_options().startup_session)
focused_os_window = wid = 0
@@ -1341,106 +1323,16 @@ class Boss:
End the current keyboard mode switching to the previous mode.
''')
def pop_keyboard_mode(self) -> bool:
passthrough = True
if self.keyboard_mode_stack:
self.keyboard_mode_stack.pop()
if not self.keyboard_mode_stack:
set_ignore_os_keyboard_processing(False)
passthrough = False
return passthrough
return self.mappings.pop_keyboard_mode()
@ac('misc', '''
Switch to the specified keyboard mode, pushing it onto the stack of keyboard modes.
''')
def push_keyboard_mode(self, new_mode: str) -> None:
mode = self.keyboard_modes[new_mode]
self._push_keyboard_mode(mode)
def _push_keyboard_mode(self, mode: KeyboardMode) -> None:
self.keyboard_mode_stack.append(mode)
set_ignore_os_keyboard_processing(True)
self.mappings.push_keyboard_mode(new_mode)
def dispatch_possible_special_key(self, ev: KeyEvent) -> bool:
# Handles shortcuts, return True if the key was consumed
is_root_mode = not self.keyboard_mode_stack
mode = self.keyboard_modes[''] if is_root_mode else self.keyboard_mode_stack[-1]
key_action = get_shortcut(mode.keymap, ev)
if key_action is None:
if is_modifier_key(ev.key):
return False
if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev):
return True
if not is_root_mode:
if mode.on_unknown in ('beep', 'ignore'):
if mode.on_unknown == 'beep' and get_options().enable_audio_bell:
ring_bell()
return True
if mode.on_unknown == 'passthrough':
return False
if not self.pop_keyboard_mode():
if get_options().enable_audio_bell:
ring_bell()
return True
else:
final_actions = self.matching_key_actions(key_action)
if final_actions:
mode_pos = len(self.keyboard_mode_stack) - 1
if final_actions[0].is_sequence:
if not mode.is_sequence:
sm = KeyboardMode('__sequence__')
sm.on_action = 'end'
sm.is_sequence = True
for fa in final_actions:
sm.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
self._push_keyboard_mode(sm)
if self.args.debug_keyboard:
print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='', flush=True)
else:
if len(final_actions) == 1:
self.pop_keyboard_mode()
return self.combine(final_actions[0].definition)
if self.args.debug_keyboard:
print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='', flush=True)
mode.keymap.clear()
for fa in final_actions:
mode.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
return True
final_action = final_actions[0]
consumed = self.combine(final_action.definition)
if consumed and not is_root_mode and mode.on_action == 'end':
if mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode:
del self.keyboard_mode_stack[mode_pos]
if not self.keyboard_mode_stack:
set_ignore_os_keyboard_processing(False)
return consumed
return False
def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> List[KeyDefinition]:
w = self.active_window
matches = []
has_sequence_match = False
for x in candidates:
if x.options.when_focus_on:
try:
if w and w in self.match_windows(x.options.when_focus_on):
matches.append(x)
if x.is_sequence:
has_sequence_match = True
except Exception:
self.show_error(_('Invalid key mapping'), _(
'The match expression {0} is not valid for {1}').format(x.options.when_focus_on, '--when-focus-on'))
return []
else:
if x.is_sequence:
has_sequence_match = True
matches.append(x)
if has_sequence_match:
matches = [x for x in matches if x.is_sequence]
q = matches[-1].options.when_focus_on
matches = [x for x in matches if x.options.when_focus_on == q]
else:
matches = [matches[-1]]
return matches
return self.mappings.dispatch_possible_special_key(ev)
def cancel_current_visual_select(self) -> None:
if self.current_visual_select:
@@ -1487,7 +1379,7 @@ class Boss:
if ch in string.digits:
km.keymap[SingleKey(mods=mods, key=fmap[f'KP_{ch}'])].append(ac)
if len(self.current_visual_select.window_ids) > 1:
self._push_keyboard_mode(km)
self.mappings._push_keyboard_mode(km)
redirect_mouse_handling(True)
self.mouse_handler = self.visual_window_select_mouse_handler
else:
@@ -1853,7 +1745,11 @@ class Boss:
cmd = list(map(prepare_arg, get_options().scrollback_pager))
if not os.path.isabs(cmd[0]):
cmd[0] = which(cmd[0]) or cmd[0]
resolved_exe = which(cmd[0])
if not resolved_exe:
log_error(f'The scrollback_pager {cmd[0]} was not found in PATH, falling back to less')
resolved_exe = which('less') or 'less'
cmd[0] = resolved_exe
if os.path.basename(cmd[0]) == 'less':
cmd.append('-+F') # reset --quit-if-one-screen
@@ -2618,7 +2514,7 @@ class Boss:
if is_macos:
from .fast_data_types import cocoa_clear_global_shortcuts
cocoa_clear_global_shortcuts()
self.update_keymap()
self.mappings.update_keymap()
if is_macos:
from .fast_data_types import cocoa_recreate_global_menu
cocoa_recreate_global_menu()

View File

@@ -118,7 +118,10 @@ def finalize_keys(opts: Options, accumulate_bad_lines: Optional[List[BadLine]] =
dl = defn.definition_location
accumulate_bad_lines.append(BadLine(dl.number, dl.line, KeyError(kerr), dl.file))
continue
m.keymap[defn.trigger].append(defn)
items = m.keymap[defn.trigger]
if defn.is_sequence:
items = m.keymap[defn.trigger] = [kd for kd in items if defn.rest != kd.rest or defn.options.when_focus_on != kd.options.when_focus_on]
items.append(defn)
opts.keyboard_modes = modes

View File

@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 32, 0)
version: Version = Version(0, 32, 1)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat

View File

@@ -10,7 +10,7 @@ import time
from contextlib import suppress
from functools import partial
from pprint import pformat
from typing import IO, Callable, Dict, Iterable, Iterator, Optional, Set, TypeVar
from typing import IO, Callable, Dict, Iterator, Optional, Sequence, Set, TypeVar
from kittens.tui.operations import colored, styled
@@ -59,9 +59,9 @@ def compare_maps(
added = set(ef) - set(ei)
removed = set(ei) - set(ef)
changed = {k for k in set(ef) & set(ei) if ef[k] != ei[k]}
which = 'shortcuts' if isinstance(next(iter(initial)), Shortcut) else 'mouse actions'
which = 'shortcuts' if isinstance(next(iter(initial or final)), Shortcut) else 'mouse actions'
if mode_name and (added or removed or changed):
print(f'{title("Changes in keyboard mode: + " + mode_name)}')
print(f'{title("Changes in keyboard mode: " + mode_name)}')
print_mapping_changes(ef, added, f'Added {which}:', print)
print_mapping_changes(ei, removed, f'Removed {which}:', print)
print_mapping_changes(ef, changed, f'Changed {which}:', print)
@@ -109,8 +109,15 @@ def compare_opts(opts: KittyOpts, print: Print) -> None:
return Shortcut((v.trigger,) + v.rest)
return Shortcut((k,))
def as_str(defns: Iterable[KeyDefinition]) -> str:
return ', '.join(d.human_repr() for d in defns)
def as_str(defns: Sequence[KeyDefinition]) -> str:
seen = set()
uniq = []
for d in reversed(defns):
key = d.unique_identity_within_keymap
if key not in seen:
seen.add(key)
uniq.append(d)
return ', '.join(d.human_repr() for d in uniq)
for kmn, initial_ in default_opts.keyboard_modes.items():
initial = {as_sc(k, v[0]): as_str(v) for k, v in initial_.keymap.items()}

View File

@@ -345,7 +345,7 @@ render_run(RenderCtx *ctx, RenderState *rs) {
if (pbm.rows > bm_height) {
double ratio = pbm.width / (double)pbm.rows;
bm_width = (unsigned)(ratio * bm_height);
buf = calloc(sizeof(pixel), (size_t)bm_height * bm_width);
buf = calloc((size_t)bm_height * bm_width, sizeof(pixel));
if (!buf) break;
downsample_32bit_image(pbm.buf, pbm.width, pbm.rows, pbm.stride, buf, bm_width, bm_height);
pbm.buf = buf; pbm.stride = 4 * bm_width; pbm.width = bm_width; pbm.rows = bm_height;
@@ -442,7 +442,7 @@ render_single_line(FreeTypeRenderCtx ctx_, const char *text, unsigned sz_px, pix
if (!hb_buffer_pre_allocate(hb_buffer, 512)) { PyErr_NoMemory(); return false; }
size_t text_len = strlen(text);
char_type *unicode = calloc(sizeof(char_type), text_len + 1);
char_type *unicode = calloc(text_len + 1, sizeof(char_type));
if (!unicode) { PyErr_NoMemory(); return false; }
bool ok = false;
text_len = decode_utf8_string(text, text_len, unicode);

View File

@@ -38,6 +38,10 @@ check_for_gl_error(void UNUSED *ret, const char *name, GLADapiproc UNUSED funcpt
}
}
static bool is_nvidia = false;
bool is_nvidia_gpu_driver(void) { return is_nvidia; }
void
gl_init(void) {
static bool glad_loaded = false;
@@ -59,7 +63,9 @@ gl_init(void) {
glad_loaded = true;
int gl_major = GLAD_VERSION_MAJOR(gl_version);
int gl_minor = GLAD_VERSION_MINOR(gl_version);
if (global_state.debug_rendering) printf("GL version string: '%s' Detected version: %d.%d\n", glGetString(GL_VERSION), gl_major, gl_minor);
const char *gvs = (const char*)glGetString(GL_VERSION);
if (strstr(gvs, "NVIDIA")) is_nvidia = true;
if (global_state.debug_rendering) printf("GL version string: '%s' Detected version: %d.%d\n", gvs, gl_major, gl_minor);
if (gl_major < OPENGL_REQUIRED_VERSION_MAJOR || (gl_major == OPENGL_REQUIRED_VERSION_MAJOR && gl_minor < OPENGL_REQUIRED_VERSION_MINOR)) {
fatal("OpenGL version is %d.%d, version >= 3.3 required for kitty", gl_major, gl_minor);
}

View File

@@ -56,3 +56,4 @@ void bind_vao_uniform_buffer(ssize_t vao_idx, size_t bufnum, GLuint block_index)
void unbind_vertex_array(void);
void unbind_program(void);
GLuint compile_shaders(GLenum shader_type, GLsizei count, const GLchar * const * string);
bool is_nvidia_gpu_driver(void);

View File

@@ -1075,7 +1075,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args, PyObject *kw) {
glfwSetIMECursorPositionCallback(get_ime_cursor_position);
glfwSetSystemColorThemeChangeCallback(on_system_color_scheme_change);
// Request SRGB output buffer
glfwWindowHint(GLFW_SRGB_CAPABLE, true);
// Prevents kitty from starting on Wayland + NVIDIA, sigh: https://github.com/kovidgoyal/kitty/issues/7021
// Remove after https://github.com/NVIDIA/egl-wayland/issues/85 is fixed.
if (!global_state.is_wayland || !is_nvidia_gpu_driver()) glfwWindowHint(GLFW_SRGB_CAPABLE, true);
#ifdef __APPLE__
cocoa_set_activation_policy(OPT(macos_hide_from_tasks));
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);

View File

@@ -392,7 +392,7 @@ static void
pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
PagerHistoryBuf *ph = self->pagerhist;
if (!ph->ringbuf || !ringbuf_bytes_used(ph->ringbuf)) return;
PagerHistoryBuf *nph = calloc(sizeof(PagerHistoryBuf), 1);
PagerHistoryBuf *nph = calloc(1, sizeof(PagerHistoryBuf));
if (!nph) return;
nph->maximum_size = ph->maximum_size;
nph->ringbuf = ringbuf_new(MIN(ph->maximum_size, ringbuf_capacity(ph->ringbuf) + 4096));

View File

@@ -1,12 +1,32 @@
#!/usr/bin/env python
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from typing import List, Optional
from gettext import gettext as _
from typing import TYPE_CHECKING, Any, Dict, Iterable, Iterator, List, Optional
from .fast_data_types import GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_HYPER, GLFW_MOD_META, GLFW_MOD_SHIFT, GLFW_MOD_SUPER, KeyEvent, SingleKey
from .options.utils import KeyDefinition, KeyMap
from .constants import is_macos
from .fast_data_types import (
GLFW_MOD_ALT,
GLFW_MOD_CONTROL,
GLFW_MOD_HYPER,
GLFW_MOD_META,
GLFW_MOD_SHIFT,
GLFW_MOD_SUPER,
KeyEvent,
SingleKey,
get_boss,
get_options,
is_modifier_key,
ring_bell,
set_ignore_os_keyboard_processing,
)
from .options.types import Options
from .options.utils import KeyboardMode, KeyDefinition, KeyMap
from .typing import ScreenType
if TYPE_CHECKING:
from .window import Window
mod_mask = GLFW_MOD_ALT | GLFW_MOD_CONTROL | GLFW_MOD_SHIFT | GLFW_MOD_SUPER | GLFW_MOD_META | GLFW_MOD_HYPER
@@ -37,3 +57,171 @@ def shortcut_matches(s: SingleKey, ev: KeyEvent) -> bool:
if ev.shifted_key and mods & GLFW_MOD_SHIFT and (mods & ~GLFW_MOD_SHIFT) == smods and ev.shifted_key == s.key:
return True
return False
class Mappings:
' Manage all keyboard mappings '
def __init__(self, global_shortcuts:Optional[Dict[str, SingleKey]] = None) -> None:
self.keyboard_mode_stack: List[KeyboardMode] = []
self.update_keymap(global_shortcuts)
def update_keymap(self, global_shortcuts:Optional[Dict[str, SingleKey]] = None) -> None:
if global_shortcuts is None:
global_shortcuts = self.set_cocoa_global_shortcuts(self.get_options()) if is_macos else {}
self.global_shortcuts_map: KeyMap = {v: [KeyDefinition(definition=k)] for k, v in global_shortcuts.items()}
self.global_shortcuts = global_shortcuts
self.keyboard_modes = self.get_options().keyboard_modes.copy()
km = self.keyboard_modes[''].keymap
self.keyboard_modes[''].keymap = km = km.copy()
for sc in self.global_shortcuts.values():
km.pop(sc, None)
def clear_keyboard_modes(self) -> None:
self.keyboard_mode_stack = []
def pop_keyboard_mode(self) -> bool:
passthrough = True
if self.keyboard_mode_stack:
self.keyboard_mode_stack.pop()
if not self.keyboard_mode_stack:
self.set_ignore_os_keyboard_processing(False)
passthrough = False
return passthrough
def _push_keyboard_mode(self, mode: KeyboardMode) -> None:
self.keyboard_mode_stack.append(mode)
self.set_ignore_os_keyboard_processing(True)
def push_keyboard_mode(self, new_mode: str) -> None:
mode = self.keyboard_modes[new_mode]
self._push_keyboard_mode(mode)
def matching_key_actions(self, candidates: Iterable[KeyDefinition]) -> List[KeyDefinition]:
w = self.get_active_window()
matches = []
has_sequence_match = False
for x in candidates:
if x.options.when_focus_on:
try:
if w and w in self.match_windows(x.options.when_focus_on):
matches.append(x)
if x.is_sequence:
has_sequence_match = True
except Exception:
self.show_error(_('Invalid key mapping'), _(
'The match expression {0} is not valid for {1}').format(x.options.when_focus_on, '--when-focus-on'))
return []
else:
if x.is_sequence:
has_sequence_match = True
matches.append(x)
if has_sequence_match:
terminal_matches = [x for x in matches if not x.rest]
if terminal_matches:
matches = [terminal_matches[-1]]
else:
matches = [x for x in matches if x.is_sequence]
q = matches[-1].options.when_focus_on
matches = [x for x in matches if x.options.when_focus_on == q]
else:
matches = [matches[-1]]
return matches
def dispatch_possible_special_key(self, ev: KeyEvent) -> bool:
# Handles shortcuts, return True if the key was consumed
is_root_mode = not self.keyboard_mode_stack
mode = self.keyboard_modes[''] if is_root_mode else self.keyboard_mode_stack[-1]
key_action = get_shortcut(mode.keymap, ev)
if key_action is None:
if is_modifier_key(ev.key):
return False
if self.global_shortcuts_map and get_shortcut(self.global_shortcuts_map, ev):
return True
if not is_root_mode:
if mode.sequence_keys is not None:
self.pop_keyboard_mode()
w = self.get_active_window()
if w is not None:
w.send_key_sequence(*mode.sequence_keys)
return False
if mode.on_unknown in ('beep', 'ignore'):
if mode.on_unknown == 'beep':
self.ring_bell()
return True
if mode.on_unknown == 'passthrough':
return False
if not self.pop_keyboard_mode():
self.ring_bell()
return True
else:
final_actions = self.matching_key_actions(key_action)
if final_actions:
mode_pos = len(self.keyboard_mode_stack) - 1
if final_actions[0].is_sequence:
if mode.sequence_keys is None:
sm = KeyboardMode('__sequence__')
sm.on_action = 'end'
sm.sequence_keys = [ev]
for fa in final_actions:
sm.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
self._push_keyboard_mode(sm)
self.debug_print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='')
else:
if len(final_actions) == 1:
self.pop_keyboard_mode()
consumed = self.combine(final_actions[0].definition)
if not consumed:
w = self.get_active_window()
if w is not None:
w.send_key_sequence(*mode.sequence_keys)
return consumed
mode.sequence_keys.append(ev)
self.debug_print('\n\x1b[35mKeyPress\x1b[m matched sequence prefix, ', end='')
mode.keymap.clear()
for fa in final_actions:
mode.keymap[fa.rest[0]].append(fa.shift_sequence_and_copy())
return True
final_action = final_actions[0]
consumed = self.combine(final_action.definition)
if consumed and not is_root_mode and mode.on_action == 'end':
if mode_pos < len(self.keyboard_mode_stack) and self.keyboard_mode_stack[mode_pos] is mode:
del self.keyboard_mode_stack[mode_pos]
if not self.keyboard_mode_stack:
self.set_ignore_os_keyboard_processing(False)
return consumed
return False
# System integration {{{
def get_active_window(self) -> Optional['Window']:
return get_boss().active_window
def match_windows(self, expr: str) -> Iterator['Window']:
return get_boss().match_windows(expr)
def show_error(self, title: str, msg: str) -> None:
return get_boss().show_error(title, msg)
def ring_bell(self) -> None:
if self.get_options().enable_audio_bell:
ring_bell()
def combine(self, action_definition: str) -> bool:
return get_boss().combine(action_definition)
def set_ignore_os_keyboard_processing(self, on: bool) -> None:
set_ignore_os_keyboard_processing(on)
def get_options(self) -> Options:
return get_options()
def debug_print(self, *args: Any, end: str = '\n') -> None:
b = get_boss()
if b.args.debug_keyboard:
print(*args, end=end, flush=True)
def set_cocoa_global_shortcuts(self, opts: Options) -> Dict[str, SingleKey]:
from .main import set_cocoa_global_shortcuts
return set_cocoa_global_shortcuts(opts)
# }}}

View File

@@ -219,11 +219,11 @@ def set_cocoa_global_shortcuts(opts: Options) -> Dict[str, SingleKey]:
if is_macos:
from collections import defaultdict
func_map = defaultdict(list)
for k, v in opts.keyboard_modes[''].keymap.items():
for kd in v:
if kd.is_suitable_for_global_shortcut:
parts = tuple(kd.definition.split())
func_map[parts].append(k)
for single_key, v in opts.keyboard_modes[''].keymap.items():
kd = v[-1] # the last definition is the active one
if kd.is_suitable_for_global_shortcut:
parts = tuple(kd.definition.split())
func_map[parts].append(single_key)
for ac in ('new_os_window', 'close_os_window', 'close_tab', 'edit_config_file', 'previous_tab',
'next_tab', 'new_tab', 'new_window', 'close_window', 'toggle_macos_secure_keyboard_entry', 'toggle_fullscreen',

View File

@@ -534,7 +534,7 @@ dispatch_possible_click(Window *w, int button, int modifiers) {
Screen *screen = w->render_data.screen;
int count = multi_click_count(w, button);
if (release_is_click(w, button)) {
PendingClick *pc = calloc(sizeof(PendingClick), 1);
PendingClick *pc = calloc(1, sizeof(PendingClick));
if (pc) {
const ClickQueue *q = &w->click_queues[button];
pc->press_num = q->length ? q->clicks[q->length - 1].num : 0;

View File

@@ -1207,6 +1207,14 @@ class KeyDefinition(BaseDefinition):
def is_suitable_for_global_shortcut(self) -> bool:
return not self.options.when_focus_on and not self.options.mode and not self.options.new_mode and not self.is_sequence
@property
def full_key_sequence_to_trigger(self) -> Tuple[SingleKey, ...]:
return (self.trigger,) + self.rest
@property
def unique_identity_within_keymap(self) -> Tuple[Tuple[SingleKey, ...], str]:
return self.full_key_sequence_to_trigger, self.options.when_focus_on
def __repr__(self) -> str:
return self.pretty_repr('is_sequence', 'trigger', 'rest', 'options')
@@ -1234,7 +1242,7 @@ class KeyboardMode:
on_unknown: OnUnknown = get_args(OnUnknown)[0]
on_action : OnAction = get_args(OnAction)[0]
is_sequence: bool = False
sequence_keys: Optional[List[defines.KeyEvent]] = None
def __init__(self, name: str = '') -> None:
self.name = name

View File

@@ -392,6 +392,7 @@ class RemoteCommand:
tabs = tuple(boss.match_tabs(payload_get(tab_match_name)))
if not tabs:
raise MatchError(payload_get(tab_match_name), 'tabs')
windows = []
for tab in tabs:
windows += list(tab)
return windows

View File

@@ -1048,7 +1048,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
static bool
attach_shaders(PyObject *sources, GLuint program_id, GLenum shader_type) {
RAII_ALLOC(const GLchar*, c_sources, calloc(sizeof(GLchar*), PyTuple_GET_SIZE(sources)));
RAII_ALLOC(const GLchar*, c_sources, calloc(PyTuple_GET_SIZE(sources), sizeof(GLchar*)));
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(sources); i++) {
PyObject *temp = PyTuple_GET_ITEM(sources, i);
if (!PyUnicode_Check(temp)) { PyErr_SetString(PyExc_TypeError, "shaders must be strings"); return false; }

View File

@@ -178,8 +178,8 @@ class CwdRequest:
env.pop(k, None)
for k in (
'HOME', 'USER', 'TEMP', 'TMP', 'TMPDIR', 'PATH', 'PWD', 'OLDPWD', 'KITTY_INSTALLATION_DIR',
'HOSTNAME', 'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'KITTY_WINDOW_ID', 'KITTY_STDIO_FORWARDED',
'KITTY_PID', 'KITTY_PUBLIC_KEY', 'KITTY_WINDOW_ID', 'TERMINFO', 'XDG_RUNTIME_DIR', 'XDG_VTNR',
'HOSTNAME', 'SSH_AUTH_SOCK', 'SSH_AGENT_PID', 'KITTY_STDIO_FORWARDED',
'KITTY_PUBLIC_KEY', 'TERMINFO', 'XDG_RUNTIME_DIR', 'XDG_VTNR',
'XDG_DATA_DIRS', 'XAUTHORITY', 'EDITOR', 'VISUAL',
):
env.pop(k, None)
@@ -913,6 +913,17 @@ class Window:
passthrough = False
return passthrough
def send_key_sequence(self, *keys: KeyEvent, synthesize_release_events: bool = True) -> None:
for key in keys:
enc = self.encoded_key(key)
if enc:
self.write_to_child(enc)
if synthesize_release_events and key.action != GLFW_RELEASE:
rkey = KeyEvent(key=key.key, mods=key.mods, action=GLFW_RELEASE)
enc = self.encoded_key(rkey)
if enc:
self.write_to_child(enc)
@ac('debug', 'Show a dump of the current lines in the scrollback + screen with their line attributes')
def dump_lines_with_attrs(self) -> None:
strings: List[str] = []

View File

@@ -5,6 +5,7 @@ from functools import partial
import kitty.fast_data_types as defines
from kitty.key_encoding import EventType, KeyEvent, decode_key_event, encode_key_event
from kitty.keys import Mappings
from . import BaseTest
@@ -504,3 +505,119 @@ class TestKeys(BaseTest):
self.ae(enc(mods=defines.GLFW_MOD_SHIFT), '<4;1;1M')
self.ae(enc(mods=defines.GLFW_MOD_ALT), '<8;1;1M')
self.ae(enc(mods=defines.GLFW_MOD_CONTROL), '<16;1;1M')
def test_mapping(self):
from kitty.config import load_config
from kitty.options.utils import parse_shortcut
af = self.assertFalse
class Window:
def __init__(self, id=1):
self.key_seqs = []
self.id = id
def send_key_sequence(self, *s):
self.key_seqs.extend(s)
class TM(Mappings):
def __init__(self, *lines, active_window = Window()):
self.active_window = active_window
self.windows = [active_window]
bad_lines = []
self.options = load_config(overrides=lines, accumulate_bad_lines=bad_lines)
af(bad_lines)
super().__init__()
def get_active_window(self):
return self.active_window
def match_windows(self, expr: str):
for w in self.windows:
if str(w.id) == expr:
yield w
def show_error(self, title: str, msg: str) -> None:
pass
def ring_bell(self) -> None:
pass
def debug_print(self, *args, end: str = '\n') -> None:
pass
def combine(self, action_definition: str) -> bool:
self.actions.append(action_definition)
if action_definition.startswith('push_keyboard_mode '):
self.push_keyboard_mode(action_definition.partition(' ')[2])
elif action_definition == 'pop_keyboard_mode':
self.pop_keyboard_mode()
return bool(action_definition)
def set_ignore_os_keyboard_processing(self, on: bool) -> None:
pass
def set_cocoa_global_shortcuts(self, opts):
return {}
def get_options(self):
return self.options
def __call__(self, *keys: str):
self.actions = []
self.active_window.key_seqs = []
consumed = []
for key in keys:
sk = parse_shortcut(key)
ev = defines.KeyEvent(sk.key, 0, 0, sk.mods)
consumed.append(self.dispatch_possible_special_key(ev))
return consumed
tm = TM('map ctrl+a new_window_with_cwd')
self.ae(tm('ctrl+a'), [True])
self.ae(tm.actions, ['new_window_with_cwd'])
tm = TM('map ctrl+f>2 set_font_size 20')
self.ae(tm('ctrl+f', '2'), [True, True])
self.ae(tm.actions, ['set_font_size 20'])
af(tm.active_window.key_seqs)
# unmatched multi key mapping should send all keys to child
self.ae(tm('ctrl+f', '1'), [True, False])
af(tm.actions)
self.ae(len(tm.active_window.key_seqs), 1) # ctrl+f should have been sent to the window
# multi-key mapping that is unmapped should send all keys to child
tm = TM('map kitty_mod+p>f')
self.ae(tm('ctrl+shift+p', 'f'), [True, False])
self.ae(len(tm.active_window.key_seqs), 1)
# unmap
tm = TM('map kitty_mod+enter')
self.ae(tm('ctrl+shift+enter'), [False])
# single key mapping overrides all multi-key mappings with same prefix
tm = TM('map kitty_mod+p new_window')
self.ae(tm('ctrl+shift+p', 'f'), [True, False])
self.ae(tm.actions, ['new_window'])
# changing a multi key mapping
tm = TM('map kitty_mod+p>f new_window')
self.ae(tm('ctrl+shift+p', 'f'), [True, True])
self.ae(tm.actions, ['new_window'])
# different behavior with focus selection
tm = TM('map --when-focus-on 2 kitty_mod+t')
tm.windows.append(Window(2))
self.ae(tm('ctrl+shift+t'), [True])
tm.active_window = tm.windows[1]
self.ae(tm('ctrl+shift+t'), [False])
# modal mappings
tm = TM('map --new-mode mw --on-unknown end kitty_mod+f7', 'map --mode mw left neighboring_window left', 'map --mode mw right neighboring_window right')
self.ae(tm('ctrl+shift+f7'), [True])
self.ae(tm.actions, ['push_keyboard_mode mw'])
self.ae(tm('right'), [True])
self.ae(tm.actions, ['neighboring_window right'])
self.ae(tm('left'), [True])
self.ae(tm.actions, ['neighboring_window left'])
self.ae(tm('x'), [True])
af(tm.keyboard_mode_stack)

View File

@@ -454,6 +454,11 @@ def init_env(
# Universal build fails with -fcf-protection clang is not smart enough to filter it out for the ARM part
intel_control_flow_protection = '-fcf-protection=full' if ccver >= (9, 0) and not build_universal_binary else ''
control_flow_protection = arm_control_flow_protection if is_arm else intel_control_flow_protection
env_cflags = shlex.split(os.environ.get('CFLAGS', ''))
env_cppflags = shlex.split(os.environ.get('CPPFLAGS', ''))
env_ldflags = shlex.split(os.environ.get('LDFLAGS', ''))
if control_flow_protection and not test_compile(cc, control_flow_protection, *env_cppflags, *env_cflags, ldflags=env_ldflags):
control_flow_protection = ''
cflags_ = os.environ.get(
'OVERRIDE_CFLAGS', (
f'-Wextra {float_conversion} -Wno-missing-field-initializers -Wall -Wstrict-prototypes {std}'
@@ -470,9 +475,9 @@ def init_env(
)
ldflags = shlex.split(ldflags_)
ldflags.append('-shared')
cppflags += shlex.split(os.environ.get('CPPFLAGS', ''))
cflags += shlex.split(os.environ.get('CFLAGS', ''))
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
cppflags += env_cppflags
cflags += env_cflags
ldflags += env_ldflags
if not debug and not sanitize and not is_openbsd and link_time_optimization:
# See https://github.com/google/sanitizers/issues/647
cflags.append('-flto')
@@ -971,7 +976,7 @@ def build_static_kittens(
go = shutil.which('go')
if not go:
raise SystemExit('The go tool was not found on this system. Install Go')
required_go_version = subprocess.check_output([go] + 'list -f {{.GoVersion}} -m'.split()).decode().strip()
required_go_version = subprocess.check_output([go] + 'list -f {{.GoVersion}} -m'.split(), env=dict(os.environ, GO111MODULE="on")).decode().strip()
current_go_version = subprocess.check_output([go, 'version']).decode().strip().split()[2][2:]
if parse_go_version(required_go_version) > parse_go_version(current_go_version):
raise SystemExit(f'The version of go on this system ({current_go_version}) is too old. go >= {required_go_version} is needed')

View File

@@ -214,16 +214,16 @@ def get_data():
tf.extractall(tdir)
with open(tdir + '/data.sh') as f:
env_vars = f.read()
apply_env_vars(env_vars)
data_dir = os.environ.pop('KITTY_SSH_KITTEN_DATA_DIR')
if not os.path.isabs(data_dir):
data_dir = os.path.join(HOME, data_dir)
data_dir = os.path.abspath(data_dir)
shell_integration_dir = os.path.join(data_dir, 'shell-integration')
compile_terminfo(tdir + '/home')
move(tdir + '/home', HOME)
if os.path.exists(tdir + '/root'):
move(tdir + '/root', '/')
apply_env_vars(env_vars)
data_dir = os.environ.pop('KITTY_SSH_KITTEN_DATA_DIR')
if not os.path.isabs(data_dir):
data_dir = os.path.join(HOME, data_dir)
data_dir = os.path.abspath(data_dir)
shell_integration_dir = os.path.join(data_dir, 'shell-integration')
compile_terminfo(tdir + '/home')
move(tdir + '/home', HOME)
if os.path.exists(tdir + '/root'):
move(tdir + '/root', '/')
def exec_zsh_with_integration():