mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-14 12:37:48 +02:00
Compare commits
48 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad328bfeaa | ||
|
|
df229dafa0 | ||
|
|
1a38b60463 | ||
|
|
b63e523098 | ||
|
|
2bcb32d611 | ||
|
|
fbdc2b44e0 | ||
|
|
be1000669f | ||
|
|
48f053b8cc | ||
|
|
4a48e96e81 | ||
|
|
8fabc47776 | ||
|
|
7b477ccca8 | ||
|
|
9be6b9c374 | ||
|
|
499eb3c3c2 | ||
|
|
2a6870b21f | ||
|
|
57aa591a90 | ||
|
|
e0998fcbb1 | ||
|
|
608a497421 | ||
|
|
f4bec5f4ab | ||
|
|
68649d78df | ||
|
|
5babab18a0 | ||
|
|
6c4cb4c1d6 | ||
|
|
1968d0b8e0 | ||
|
|
6f0366d42f | ||
|
|
192bd8a211 | ||
|
|
946342c4fb | ||
|
|
185645f84b | ||
|
|
af01cf92cc | ||
|
|
4d8b34cab8 | ||
|
|
ecc44dffeb | ||
|
|
a8b28ca32b | ||
|
|
56fc4eddbd | ||
|
|
e3239fdcdf | ||
|
|
dea7752df1 | ||
|
|
0e1737b0e6 | ||
|
|
3dd09236aa | ||
|
|
d5fe1333e2 | ||
|
|
f155d23a1e | ||
|
|
c153ea8acc | ||
|
|
a6c7744119 | ||
|
|
50d5deb9fe | ||
|
|
6c3c36c5b0 | ||
|
|
14c16fa943 | ||
|
|
f17a5934e2 | ||
|
|
6bd8c71e30 | ||
|
|
521d3a86f0 | ||
|
|
88aa4d1de3 | ||
|
|
06a45dcf36 | ||
|
|
63de42aaef |
@@ -282,9 +282,9 @@
|
||||
"name": "wayland",
|
||||
"os": "linux",
|
||||
"unix": {
|
||||
"filename": "wayland-1.22.0.tar.xz",
|
||||
"hash": "sha256:1540af1ea698a471c2d8e9d288332c7e0fd360c8f1d12936ebb7e7cbc2425842",
|
||||
"urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.22.0/downloads/{filename}"]
|
||||
"filename": "wayland-1.23.0.tar.xz",
|
||||
"hash": "sha256:05b3e1574d3e67626b5974f862f36b5b427c7ceeb965cb36a4e6c2d342e45ab2",
|
||||
"urls": ["https://gitlab.freedesktop.org/wayland/wayland/-/releases/1.23.0/downloads/{filename}"]
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ particular desktop, but it should work for most major desktop environments.
|
||||
# If you want to open text files and images in kitty via your file manager also add the kitty-open.desktop file
|
||||
cp ~/.local/kitty.app/share/applications/kitty-open.desktop ~/.local/share/applications/
|
||||
# Update the paths to the kitty and its icon in the kitty desktop file(s)
|
||||
sed -i "s|Icon=kitty|Icon=/home/$USER/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty*.desktop
|
||||
sed -i "s|Exec=kitty|Exec=/home/$USER/.local/kitty.app/bin/kitty|g" ~/.local/share/applications/kitty*.desktop
|
||||
sed -i "s|Icon=kitty|Icon=$(readlink -f ~)/.local/kitty.app/share/icons/hicolor/256x256/apps/kitty.png|g" ~/.local/share/applications/kitty*.desktop
|
||||
sed -i "s|Exec=kitty|Exec=$(readlink -f ~)/.local/kitty.app/bin/kitty|g" ~/.local/share/applications/kitty*.desktop
|
||||
# Make xdg-terminal-exec (and hence desktop environments that support it use kitty)
|
||||
echo 'kitty.desktop' > ~/.config/xdg-terminals.list
|
||||
|
||||
|
||||
@@ -50,6 +50,23 @@ consumption to do the same tasks.
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.35.2 [2024-06-22]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- A new option, :opt:`window_logo_scale` to specify how window logos are scaled with respect to the size of the window containing the logo (:pull:`7534`)
|
||||
|
||||
- A new option, :opt:`cursor_shape_unfocused` to specify the shape of the text cursor in unfocused OS windows (:pull:`7544`)
|
||||
|
||||
- Remote control: Fix empty password not working (:iss:`7538`)
|
||||
|
||||
- Wayland: Fix regression in 0.34.0 causing flickering on window resize on NVIDIA drivers (:iss:`7493`)
|
||||
|
||||
- Wayland labwc: Fix kitty timing out waiting for compositor to quit fucking around with scales on labwc (:iss:`7540`)
|
||||
|
||||
- Fix :opt:`scrollback_indicator_opacity` not actually controlling the opacity (:iss:`7557`)
|
||||
|
||||
- URL detection: Fix IPv6 hostnames breaking URL detection (:iss:`7565`)
|
||||
|
||||
0.35.1 [2024-05-31]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ Some programs and libraries that use the kitty graphics protocol:
|
||||
|
||||
* `termpdf.py <https://github.com/dsanson/termpdf.py>`_ - a terminal PDF/DJVU/CBR viewer
|
||||
* `ranger <https://github.com/ranger/ranger>`_ - a terminal file manager, with image previews
|
||||
* `Yazi <https://github.com/sxyazi/yazi>`_ - Blazing fast terminal file manager written in Rust, based on async I/O
|
||||
* :doc:`kitty-diff <kittens/diff>` - a side-by-side terminal diff program with support for images
|
||||
* `tpix <https://github.com/jesvedberg/tpix>`_ - a statically compiled binary that can be used to display images and easily installed on remote servers without root access
|
||||
* `mpv <https://github.com/mpv-player/mpv/commit/874e28f4a41a916bb567a882063dd2589e9234e1>`_ - A video player that can play videos in the terminal
|
||||
|
||||
@@ -46,6 +46,13 @@ graphics protocol.
|
||||
Another terminal file manager, with previews of file contents powered by kitty's
|
||||
graphics protocol.
|
||||
|
||||
.. _tool_yazi:
|
||||
|
||||
`Yazi <https://github.com/sxyazi/yazi>`_
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Blazing fast terminal file manager, with built-in kitty graphics protocol support
|
||||
(implemented both Classic protocol and Unicode placeholders).
|
||||
|
||||
.. _tool_hunter:
|
||||
|
||||
`hunter <https://github.com/rabite0/hunter>`_
|
||||
|
||||
@@ -42,6 +42,7 @@ In addition to kitty, this protocol is also implemented in:
|
||||
<https://github.com/dankamongmen/notcurses/issues/2131>`__
|
||||
* The `crossterm library
|
||||
<https://github.com/crossterm-rs/crossterm/pull/688>`__
|
||||
* The `textual library <https://github.com/Textualize/textual/pull/4631>`__
|
||||
* The `Vim text editor <https://github.com/vim/vim/commit/63a2e360cca2c70ab0a85d14771d3259d4b3aafa>`__
|
||||
* The `Emacs text editor via the kkp package <https://github.com/benjaminor/kkp>`__
|
||||
* The `Neovim text editor <https://github.com/neovim/neovim/pull/18181>`__
|
||||
@@ -49,7 +50,7 @@ In addition to kitty, this protocol is also implemented in:
|
||||
* The `dte text editor <https://gitlab.com/craigbarnes/dte/-/issues/138>`__
|
||||
* The `Helix text editor <https://github.com/helix-editor/helix/pull/4939>`__
|
||||
* The `far2l file manager <https://github.com/elfmz/far2l/commit/e1f2ee0ef2b8332e5fa3ad7f2e4afefe7c96fc3b>`__
|
||||
* The `yazi file manager <https://github.com/sxyazi/yazi>`__
|
||||
* The `Yazi file manager <https://github.com/sxyazi/yazi>`__
|
||||
* The `awrit web browser <https://github.com/chase/awrit>`__
|
||||
* The `nushell shell <https://github.com/nushell/nushell/pull/10540>`__
|
||||
* The `fish shell <https://github.com/fish-shell/fish-shell/commit/8bf8b10f685d964101f491b9cc3da04117a308b4>`__
|
||||
|
||||
@@ -287,10 +287,11 @@ If you wish to run a more complex script, you can use::
|
||||
|
||||
In this script you can use ``kitten @`` to run as many remote
|
||||
control commands as you like and process their output.
|
||||
:ac:`remote_control_script` is really just an alias for the
|
||||
:ac:`remote_control_script` is similar to the
|
||||
:ac:`launch` command with ``--type=background --allow-remote-control``.
|
||||
For more advanced usage, including fine grained permissions, setting
|
||||
env vars, etc. see the docs for the :doc:`launch <launch>` command.
|
||||
env vars, command line interpolation, passing data to STDIN, etc.
|
||||
the :doc:`launch <launch>` command should be used.
|
||||
|
||||
.. note:: You do not need :opt:`allow_remote_control` to use these mappings,
|
||||
as they are not actual remote programs, but are simply a way to reuse the
|
||||
|
||||
3
glfw/context.c
vendored
3
glfw/context.c
vendored
@@ -476,9 +476,6 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _GLFW_WAYLAND
|
||||
_glfwWaylandBeforeBufferSwap(window);
|
||||
#endif
|
||||
window->context.swapBuffers(window);
|
||||
#ifdef _GLFW_WAYLAND
|
||||
_glfwWaylandAfterBufferSwap(window);
|
||||
|
||||
3
glfw/wl_platform.h
vendored
3
glfw/wl_platform.h
vendored
@@ -185,9 +185,6 @@ typedef struct _GLFWwindowWayland
|
||||
struct zwlr_layer_surface_v1* zwlr_layer_surface_v1;
|
||||
} layer_shell;
|
||||
|
||||
struct {
|
||||
int width, height, dirty;
|
||||
} framebuffer_size_at_last_resize;
|
||||
/* information about axis events on current frame */
|
||||
struct
|
||||
{
|
||||
|
||||
27
glfw/wl_window.c
vendored
27
glfw/wl_window.c
vendored
@@ -370,27 +370,21 @@ wait_for_swap_to_commit(_GLFWwindow *window) {
|
||||
|
||||
static void
|
||||
resizeFramebuffer(_GLFWwindow* window) {
|
||||
GLFWwindow *ctx = glfwGetCurrentContext();
|
||||
const bool ctx_changed = ctx != (GLFWwindow*)window;
|
||||
if (ctx_changed) glfwMakeContextCurrent((GLFWwindow*)window);
|
||||
double scale = _glfwWaylandWindowScale(window);
|
||||
int scaled_width = (int)round(window->wl.width * scale);
|
||||
int scaled_height = (int)round(window->wl.height * scale);
|
||||
debug("Resizing framebuffer of window: %llu to: %dx%d window size: %dx%d at scale: %.3f\n",
|
||||
window->id, scaled_width, scaled_height, window->wl.width, window->wl.height, scale);
|
||||
wl_egl_window_resize(window->wl.native, scaled_width, scaled_height, 0, 0);
|
||||
update_regions(window);
|
||||
wait_for_swap_to_commit(window);
|
||||
window->wl.framebuffer_size_at_last_resize.width = scaled_width;
|
||||
window->wl.framebuffer_size_at_last_resize.height = scaled_height;
|
||||
window->wl.framebuffer_size_at_last_resize.dirty = true;
|
||||
if (ctx_changed) glfwMakeContextCurrent(ctx);
|
||||
_glfwInputFramebufferSize(window, scaled_width, scaled_height);
|
||||
}
|
||||
|
||||
void
|
||||
_glfwWaylandBeforeBufferSwap(_GLFWwindow* window) {
|
||||
if (window->wl.framebuffer_size_at_last_resize.dirty) {
|
||||
wl_egl_window_resize(window->wl.native, window->wl.framebuffer_size_at_last_resize.width, window->wl.framebuffer_size_at_last_resize.height, 0, 0);
|
||||
window->wl.framebuffer_size_at_last_resize.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_glfwWaylandAfterBufferSwap(_GLFWwindow* window) {
|
||||
if (window->wl.temp_buffer_used_during_window_creation) {
|
||||
@@ -555,7 +549,10 @@ fractional_scale_preferred_scale(void *data, struct wp_fractional_scale_v1 *wp_f
|
||||
debug("Fractional scale requested: %u/120 = %.2f for window %llu\n", scale, scale / 120., window->id);
|
||||
window->wl.fractional_scale = scale;
|
||||
// Hyprland sends a fraction scale = 1 event before configuring the xdg surface and then another after with the correct scale
|
||||
window->wl.window_fully_created = window->wl.once.surface_configured || scale != 120;
|
||||
// labwc doesnt support preferred buffer scale, so we assume it's done fucking around with scales even if the scale is 120
|
||||
// As far as I can tell from googling labwc has no way to specify scales other
|
||||
// than 1 anyway, so no way to test what it will do in such cases. Sigh, more half baked Wayland shit.
|
||||
window->wl.window_fully_created = window->wl.once.surface_configured || scale != 120 || !_glfw.wl.has_preferred_buffer_scale;
|
||||
apply_scale_changes(window, true, true);
|
||||
}
|
||||
|
||||
@@ -1108,8 +1105,10 @@ create_window_desktop_surface(_GLFWwindow* window)
|
||||
}
|
||||
|
||||
#ifdef XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION
|
||||
if (_glfw.wl.xdg_wm_base_version < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION)
|
||||
memset(&window->wl.wm_capabilities, 0xff, sizeof(window->wl.wm_capabilities));
|
||||
if (_glfw.wl.xdg_wm_base_version < XDG_TOPLEVEL_WM_CAPABILITIES_SINCE_VERSION) {
|
||||
window->wl.wm_capabilities.maximize = true; window->wl.wm_capabilities.minimize = true; window->wl.wm_capabilities.fullscreen = true;
|
||||
window->wl.wm_capabilities.window_menu = true;
|
||||
}
|
||||
#endif
|
||||
xdg_toplevel_add_listener(window->wl.xdg.toplevel, &xdgToplevelListener, window);
|
||||
if (_glfw.wl.decorationManager) {
|
||||
|
||||
6
go.mod
6
go.mod
@@ -12,11 +12,11 @@ require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/kovidgoyal/imaging v1.6.3
|
||||
github.com/seancfoley/ipaddress-go v1.6.0
|
||||
github.com/shirou/gopsutil/v3 v3.24.4
|
||||
github.com/shirou/gopsutil/v3 v3.24.5
|
||||
github.com/zeebo/xxh3 v1.0.2
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b
|
||||
golang.org/x/image v0.16.0
|
||||
golang.org/x/sys v0.20.0
|
||||
golang.org/x/image v0.17.0
|
||||
golang.org/x/sys v0.21.0
|
||||
howett.net/plist v1.0.1
|
||||
)
|
||||
|
||||
|
||||
26
go.sum
26
go.sum
@@ -8,7 +8,6 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc
|
||||
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I=
|
||||
github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
@@ -19,7 +18,6 @@ github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be h1:FNPYI8/ifKGW7kdB
|
||||
github.com/edwvee/exiffix v0.0.0-20240229113213-0dbb146775be/go.mod h1:G3dK5MziX9e4jUa8PWjowCOPCcyQwxsZ5a0oYA73280=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
@@ -32,12 +30,10 @@ github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/q
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/kovidgoyal/imaging v1.6.3 h1:iNPpv7ygiaB/NOztc6APMT7yr9UwBS+rOZwIbAdtyY8=
|
||||
github.com/kovidgoyal/imaging v1.6.3/go.mod h1:sHvcLOOVhJuto2IoNdPLEqnAUoL5ZfHEF0PpNH+882g=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a h1:N9zuLhTvBSRt0gWSiJswwQ2HqDmtX/ZCDJURnKUt1Ik=
|
||||
github.com/lufia/plan9stats v0.0.0-20230326075908-cb1d2100619a/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b h1:0LFwY6Q3gMACTjAbMZBjXAqTOzOwFaj2Ld6cjeQ7Rig=
|
||||
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
@@ -46,19 +42,12 @@ github.com/seancfoley/bintree v1.3.1 h1:cqmmQK7Jm4aw8gna0bP+huu5leVOgHGSJBEpUx3E
|
||||
github.com/seancfoley/bintree v1.3.1/go.mod h1:hIUabL8OFYyFVTQ6azeajbopogQc2l5C/hiXMcemWNU=
|
||||
github.com/seancfoley/ipaddress-go v1.6.0 h1:9z7yGmOnV4P2ML/dlR/kCJiv5tp8iHOOetJvxJh/R5w=
|
||||
github.com/seancfoley/ipaddress-go v1.6.0/go.mod h1:TQRZgv+9jdvzHmKoPGBMxyiaVmoI0rYpfEk8Q/sL/Iw=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU=
|
||||
github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
@@ -74,21 +63,18 @@ github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaD
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI=
|
||||
golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw=
|
||||
golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs=
|
||||
golang.org/x/image v0.17.0 h1:nTRVVdajgB8zCMZVsViyzhnMKPwYeroEERRC64JuLco=
|
||||
golang.org/x/image v0.17.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
|
||||
|
||||
@@ -188,7 +188,7 @@ func run_set_loop(opts *Options, args []string) (err error) {
|
||||
to_process := make([]*Input, len(args))
|
||||
defer func() {
|
||||
for _, i := range inputs {
|
||||
if i.src != nil {
|
||||
if i != nil && i.src != nil {
|
||||
rc, ok := i.src.(io.Closer)
|
||||
if ok {
|
||||
rc.Close()
|
||||
|
||||
@@ -119,7 +119,7 @@ func (self *ErrInvalidSSHArgs) Error() string {
|
||||
}
|
||||
|
||||
func PassthroughArgs() map[string]bool {
|
||||
return map[string]bool{"-N": true, "-n": true, "-f": true, "-G": true, "-T": true}
|
||||
return map[string]bool{"-N": true, "-n": true, "-f": true, "-G": true, "-T": true, "-V": true}
|
||||
}
|
||||
|
||||
func ParseSSHArgs(args []string, extra_args ...string) (ssh_args []string, server_args []string, passthrough bool, found_extra_args []string, err error) {
|
||||
|
||||
@@ -33,7 +33,7 @@ from typing import (
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from .child import cached_process_data, default_env, set_default_env
|
||||
from .cli import create_opts, parse_args
|
||||
from .cli import create_opts, green, parse_args
|
||||
from .cli_stub import CLIOptions
|
||||
from .clipboard import (
|
||||
Clipboard,
|
||||
@@ -733,7 +733,7 @@ class Boss:
|
||||
|
||||
For example::
|
||||
|
||||
map f1 remote_control_script arg1 arg2 ...
|
||||
map f1 remote_control_script /path/to/script arg1 arg2 ...
|
||||
|
||||
See :ref:`rc_mapping` for details.
|
||||
''')
|
||||
@@ -822,6 +822,15 @@ class Boss:
|
||||
args.directory = os.path.join(data['cwd'], args.directory)
|
||||
focused_os_window = os_window_id = 0
|
||||
for session in create_sessions(opts, args, respect_cwd=True):
|
||||
if not session.has_non_background_processes:
|
||||
# background only do not create and OS Window
|
||||
from .launch import LaunchSpec, launch
|
||||
for tab in session.tabs:
|
||||
for window in tab.windows:
|
||||
if window.is_background_process:
|
||||
assert isinstance(window.launch_spec, LaunchSpec)
|
||||
launch(get_boss(), window.launch_spec.opts, window.launch_spec.args)
|
||||
continue
|
||||
os_window_id = self.add_os_window(
|
||||
session, wclass=args.cls, wname=args.name, opts_for_size=opts, startup_id=startup_id,
|
||||
override_title=args.title or None)
|
||||
@@ -1110,7 +1119,7 @@ class Boss:
|
||||
'Are you sure you want to close this tab? It is running the {} program and {} other programs.', num)
|
||||
else:
|
||||
msg = _('Are you sure you want to close this tab? It is running the {} program')
|
||||
msg = msg.format(active_program or program or 'shell', num)
|
||||
msg = msg.format(green(active_program or program or 'shell'), num)
|
||||
w = self.confirm(msg, self.handle_close_tab_confirmation, tab.id, window=tab.active_window, title=_('Close tab?'))
|
||||
tab.confirm_close_window_id = w.id
|
||||
|
||||
@@ -1752,7 +1761,7 @@ class Boss:
|
||||
'Are you sure you want to close this OS window? It is running the {} program and {} other programs.', num)
|
||||
else:
|
||||
msg = _('Are you sure you want to close this OS window? It is running the {} program')
|
||||
msg = msg.format(active_program or program or 'shell', num)
|
||||
msg = msg.format(green(active_program or program or 'shell'), num)
|
||||
w = self.confirm(msg, self.handle_close_os_window_confirmation, os_window_id, window=tm.active_window, title=_('Close OS window'))
|
||||
tm.confirm_close_window_id = w.id
|
||||
|
||||
@@ -1819,7 +1828,7 @@ class Boss:
|
||||
'Are you sure you want to quit kitty? It is running the {} program and {} other programs.', num)
|
||||
else:
|
||||
msg = _('Are you sure you want to quit kitty? It is running the {} program')
|
||||
msg = msg.format(active_program or program or 'shell', num)
|
||||
msg = msg.format(green(active_program or program or 'shell'), num)
|
||||
w = self.confirm(msg, self.handle_quit_confirmation, window=tm.active_window, title=_('Quit kitty?'))
|
||||
self.quit_confirmation_window_id = w.id
|
||||
set_application_quit_request(CLOSE_BEING_CONFIRMED)
|
||||
|
||||
@@ -22,7 +22,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 35, 1)
|
||||
version: Version = Version(0, 35, 2)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -21,7 +21,7 @@ from .fast_data_types import Color, SingleKey, num_users, opengl_version_string,
|
||||
from .options.types import Options as KittyOpts
|
||||
from .options.types import defaults
|
||||
from .options.utils import KeyboardMode, KeyDefinition
|
||||
from .rgb import color_as_sharp
|
||||
from .rgb import color_as_sharp, color_from_int
|
||||
from .types import MouseEvent, Shortcut, mod_to_names
|
||||
|
||||
AnyEvent = TypeVar('AnyEvent', MouseEvent, Shortcut)
|
||||
@@ -101,6 +101,15 @@ def compare_opts(opts: KittyOpts, print: Print) -> None:
|
||||
else:
|
||||
if f == 'kitty_mod':
|
||||
print(fmt.format(f), '+'.join(mod_to_names(getattr(opts, f))))
|
||||
elif f in ('wayland_titlebar_color', 'macos_titlebar_color'):
|
||||
if val == 0:
|
||||
cval = 'system'
|
||||
elif val == 1:
|
||||
cval = 'background'
|
||||
else:
|
||||
col = color_from_int(val >> 8)
|
||||
cval = color_as_sharp(col) + ' ' + styled(' ', bg=col)
|
||||
colors.append(fmt.format(f) + ' ' + cval)
|
||||
else:
|
||||
print(fmt.format(f), str(getattr(opts, f)))
|
||||
|
||||
|
||||
21
kitty/keys.c
21
kitty/keys.c
@@ -11,6 +11,10 @@
|
||||
#include "glfw-wrapper.h"
|
||||
#include <structmember.h>
|
||||
|
||||
#ifndef __APPLE__
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#endif
|
||||
|
||||
// python KeyEvent object {{{
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
@@ -44,6 +48,19 @@ is_modifier_key(const uint32_t key) {
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
|
||||
static bool
|
||||
is_no_action_key(const uint32_t key, const uint32_t native_key) {
|
||||
switch (native_key) {
|
||||
#ifndef __APPLE__
|
||||
case XKB_KEY_XF86Fn:
|
||||
case XKB_KEY_XF86WakeUp:
|
||||
return true;
|
||||
#endif
|
||||
default:
|
||||
return is_modifier_key(key);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
dealloc(PyKeyEvent* self) {
|
||||
Py_CLEAR(self->key); Py_CLEAR(self->shifted_key); Py_CLEAR(self->alternate_key);
|
||||
@@ -163,7 +180,7 @@ on_key_input(GLFWkeyevent *ev) {
|
||||
}
|
||||
}
|
||||
if (!w) { debug("no active window, ignoring\n"); return; }
|
||||
if (OPT(mouse_hide_wait) < 0 && !is_modifier_key(key)) hide_mouse(global_state.callback_os_window);
|
||||
if (OPT(mouse_hide_wait) < 0 && !is_no_action_key(key, native_key)) hide_mouse(global_state.callback_os_window);
|
||||
Screen *screen = w->render_data.screen;
|
||||
id_type active_window_id = w->id;
|
||||
|
||||
@@ -227,7 +244,7 @@ on_key_input(GLFWkeyevent *ev) {
|
||||
debug("discarding repeat key event as DECARM is off\n");
|
||||
return;
|
||||
}
|
||||
if (screen->scrolled_by && action == GLFW_PRESS && !is_modifier_key(key)) {
|
||||
if (screen->scrolled_by && action == GLFW_PRESS && !is_no_action_key(key, native_key)) {
|
||||
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
|
||||
}
|
||||
char encoded_key[KEY_BUFFER_SIZE] = {0};
|
||||
|
||||
41
kitty/line.c
41
kitty/line.c
@@ -50,6 +50,11 @@ cell_text(CPUCell *cell) {
|
||||
|
||||
// URL detection {{{
|
||||
|
||||
static bool
|
||||
is_hostname_char(char_type ch) {
|
||||
return ch == '[' || ch == ']' || is_url_char(ch);
|
||||
}
|
||||
|
||||
static index_type
|
||||
find_colon_slash(Line *self, index_type x, index_type limit) {
|
||||
// Find :// at or before x
|
||||
@@ -60,7 +65,7 @@ find_colon_slash(Line *self, index_type x, index_type limit) {
|
||||
if (pos < limit) return 0;
|
||||
do {
|
||||
char_type ch = self->cpu_cells[pos].ch;
|
||||
if (!is_url_char(ch)) return false;
|
||||
if (!is_hostname_char(ch)) return false;
|
||||
if (pos == x) {
|
||||
if (ch == ':') {
|
||||
if (pos + 2 < self->xnum && self->cpu_cells[pos+1].ch == '/' && self->cpu_cells[pos + 2].ch == '/') state = SECOND_SLASH;
|
||||
@@ -108,9 +113,15 @@ has_url_prefix_at(Line *self, index_type at, index_type min_prefix_len, index_ty
|
||||
#define MIN_URL_LEN 5
|
||||
|
||||
static bool
|
||||
has_url_beyond(Line *self, index_type x) {
|
||||
has_url_beyond_colon_slash(Line *self, index_type x) {
|
||||
unsigned num_of_slashes = 0;
|
||||
for (index_type i = x; i < MIN(x + MIN_URL_LEN + 3, self->xnum); i++) {
|
||||
if (!is_url_char(self->cpu_cells[i].ch)) return false;
|
||||
const char_type ch = self->cpu_cells[i].ch;
|
||||
if (num_of_slashes < 3) {
|
||||
if (!is_hostname_char(ch)) return false;
|
||||
if (ch == '/') num_of_slashes++;
|
||||
}
|
||||
else { if (!is_url_char(ch)) return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -123,30 +134,40 @@ line_url_start_at(Line *self, index_type x) {
|
||||
index_type ds_pos = 0, t;
|
||||
// First look for :// ahead of x
|
||||
ds_pos = find_colon_slash(self, x + OPT(url_prefixes).max_prefix_len + 3, x < 2 ? 0 : x - 2);
|
||||
if (ds_pos != 0 && has_url_beyond(self, ds_pos)) {
|
||||
if (ds_pos != 0 && has_url_beyond_colon_slash(self, ds_pos)) {
|
||||
if (has_url_prefix_at(self, ds_pos, ds_pos > x ? ds_pos - x: 0, &t)) return t;
|
||||
}
|
||||
ds_pos = find_colon_slash(self, x, 0);
|
||||
if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond(self, ds_pos)) return self->xnum;
|
||||
if (ds_pos == 0 || self->xnum < ds_pos + MIN_URL_LEN + 3 || !has_url_beyond_colon_slash(self, ds_pos)) return self->xnum;
|
||||
if (has_url_prefix_at(self, ds_pos, 0, &t)) return t;
|
||||
return self->xnum;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_pos_ok_for_url(Line *self, index_type x, bool in_hostname, index_type last_hostname_char_pos) {
|
||||
if (x >= self->xnum) return false;
|
||||
if (in_hostname && x <= last_hostname_char_pos) return is_hostname_char(self->cpu_cells[x].ch);
|
||||
return is_url_char(self->cpu_cells[x].ch);
|
||||
}
|
||||
|
||||
index_type
|
||||
line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars) {
|
||||
line_url_end_at(Line *self, index_type x, bool check_short, char_type sentinel, bool next_line_starts_with_url_chars, bool in_hostname, index_type last_hostname_char_pos) {
|
||||
index_type ans = x;
|
||||
if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0;
|
||||
if (sentinel) { while (ans < self->xnum && self->cpu_cells[ans].ch != sentinel && is_url_char(self->cpu_cells[ans].ch)) ans++; }
|
||||
else { while (ans < self->xnum && is_url_char(self->cpu_cells[ans].ch)) ans++; }
|
||||
#define pos_ok(x) is_pos_ok_for_url(self, x, in_hostname, last_hostname_char_pos)
|
||||
if (sentinel) { while (ans < self->xnum && self->cpu_cells[ans].ch != sentinel && pos_ok(ans)) ans++; }
|
||||
else { while (ans < self->xnum && pos_ok(ans)) ans++; }
|
||||
if (ans) ans--;
|
||||
if (ans < self->xnum - 1 || !next_line_starts_with_url_chars) {
|
||||
while (ans > x && can_strip_from_end_of_url(self->cpu_cells[ans].ch)) ans--;
|
||||
}
|
||||
#undef pos_ok
|
||||
return ans;
|
||||
}
|
||||
|
||||
bool
|
||||
line_startswith_url_chars(Line *self) {
|
||||
line_startswith_url_chars(Line *self, bool in_hostname) {
|
||||
if (in_hostname) return is_hostname_char(self->cpu_cells[0].ch);
|
||||
return is_url_char(self->cpu_cells[0].ch);
|
||||
}
|
||||
|
||||
@@ -163,7 +184,7 @@ url_end_at(Line *self, PyObject *args) {
|
||||
unsigned int x, sentinel = 0;
|
||||
int next_line_starts_with_url_chars = 0;
|
||||
if (!PyArg_ParseTuple(args, "I|Ip", &x, &sentinel, &next_line_starts_with_url_chars)) return NULL;
|
||||
return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel, next_line_starts_with_url_chars));
|
||||
return PyLong_FromUnsignedLong((unsigned long)line_url_end_at(self, x, true, sentinel, next_line_starts_with_url_chars, false, self->xnum));
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
@@ -89,8 +89,8 @@ void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, hy
|
||||
void line_right_shift(Line *, unsigned int , unsigned int );
|
||||
void line_add_combining_char(CPUCell *, GPUCell *, uint32_t , unsigned int );
|
||||
index_type line_url_start_at(Line *self, index_type x);
|
||||
index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool);
|
||||
bool line_startswith_url_chars(Line*);
|
||||
index_type line_url_end_at(Line *self, index_type x, bool, char_type, bool, bool, index_type);
|
||||
bool line_startswith_url_chars(Line*, bool);
|
||||
bool line_as_ansi(Line *self, ANSIBuf *output, const GPUCell**, index_type start_at, index_type stop_before, char_type prefix_char) __attribute__((nonnull));
|
||||
unsigned int line_length(Line *self);
|
||||
size_t cell_as_unicode(CPUCell *cell, bool include_cc, Py_UCS4 *buf, char_type);
|
||||
|
||||
@@ -296,7 +296,7 @@ opt('cursor', '#cccccc',
|
||||
option_type='to_color_or_none',
|
||||
long_text='''
|
||||
Default cursor color. If set to the special value :code:`none` the cursor will
|
||||
be rendered with a "reverse video" effect. It's color will be the color of the
|
||||
be rendered with a "reverse video" effect. Its color will be the color of the
|
||||
text in the cell it is over and the text will be rendered with the background
|
||||
color of the cell. Note that if the program running in the terminal sets a
|
||||
cursor color, this takes precedence. Also, the cursor colors are modified if
|
||||
@@ -330,6 +330,12 @@ cursor shape to :code:`beam` at shell prompts. You can avoid this by setting
|
||||
'''
|
||||
)
|
||||
|
||||
opt('cursor_shape_unfocused', 'hollow', option_type='to_cursor_unfocused_shape', ctype='int', long_text='''
|
||||
Defines the text cursor shape when the OS window is not focused. The unfocused
|
||||
cursor shape can be one of :code:`block`, :code:`beam`, :code:`underline`,
|
||||
:code:`hollow`.
|
||||
''')
|
||||
|
||||
opt('cursor_beam_thickness', '1.5',
|
||||
option_type='positive_float', ctype='float',
|
||||
long_text='The thickness of the beam cursor (in pts).'
|
||||
@@ -356,6 +362,7 @@ Stop blinking cursor after the specified number of seconds of keyboard
|
||||
inactivity. Set to zero to never stop blinking.
|
||||
'''
|
||||
)
|
||||
|
||||
egr() # }}}
|
||||
|
||||
|
||||
@@ -1160,6 +1167,17 @@ faded and one being fully opaque.
|
||||
'''
|
||||
)
|
||||
|
||||
opt('window_logo_scale', '0', option_type='window_logo_scale', ctype='!window_logo_scale', long_text='''
|
||||
The percentage (0-100] of the window size to which the logo should scale. Using a single
|
||||
number means the logo is scaled to that percentage of the shortest window dimension, while preseving
|
||||
aspect ratio of the logo image.
|
||||
|
||||
Using two numbers means the width and height of the logo are scaled to the respective
|
||||
percentage of the window's width and height.
|
||||
|
||||
Using zero as the percentage disables scaling in that dimension. A single zero (the default)
|
||||
disables all scaling of the window logo.
|
||||
''')
|
||||
|
||||
opt('resize_debounce_time', '0.1 0.5',
|
||||
option_type='resize_debounce_time', ctype='!resize_debounce_time',
|
||||
@@ -2902,9 +2920,9 @@ doesn't work, kitty will cycle through various known editors (:program:`vim`,
|
||||
opt('close_on_child_death', 'no',
|
||||
option_type='to_bool', ctype='bool',
|
||||
long_text='''
|
||||
Close the window when the child process (shell) exits. With the default value
|
||||
Close the window when the child process (usually the shell) exits. With the default value
|
||||
:code:`no`, the terminal will remain open when the child exits as long as there
|
||||
are still processes outputting to the terminal (for example disowned or
|
||||
are still other processes outputting to the terminal (for example disowned or
|
||||
backgrounded processes). When enabled with :code:`yes`, the window will close as
|
||||
soon as the child process exits. Note that setting it to :code:`yes` means that
|
||||
any background processes still using the terminal can fail silently because
|
||||
|
||||
13
kitty/options/parse.py
generated
13
kitty/options/parse.py
generated
@@ -17,8 +17,9 @@ from kitty.options.utils import (
|
||||
remote_control_password, resize_debounce_time, scrollback_lines, scrollback_pager_history_size,
|
||||
shell_integration, store_multiple, symbol_map, tab_activity_symbol, tab_bar_edge,
|
||||
tab_bar_margin_height, tab_bar_min_tabs, tab_fade, tab_font_style, tab_separator,
|
||||
tab_title_template, titlebar_color, to_cursor_shape, to_font_size, to_layout_names, to_modifiers,
|
||||
url_prefixes, url_style, visual_window_select_characters, window_border_width, window_size
|
||||
tab_title_template, titlebar_color, to_cursor_shape, to_cursor_unfocused_shape, to_font_size,
|
||||
to_layout_names, to_modifiers, url_prefixes, url_style, visual_window_select_characters,
|
||||
window_border_width, window_logo_scale, window_size
|
||||
)
|
||||
|
||||
|
||||
@@ -919,6 +920,9 @@ class Parser:
|
||||
def cursor_shape(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['cursor_shape'] = to_cursor_shape(val)
|
||||
|
||||
def cursor_shape_unfocused(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['cursor_shape_unfocused'] = to_cursor_unfocused_shape(val)
|
||||
|
||||
def cursor_stop_blinking_after(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['cursor_stop_blinking_after'] = positive_float(val)
|
||||
|
||||
@@ -1388,7 +1392,10 @@ class Parser:
|
||||
raise ValueError(f"The value {val} is not a valid choice for window_logo_position")
|
||||
ans["window_logo_position"] = val
|
||||
|
||||
choices_for_window_logo_position = frozenset(('top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right'))
|
||||
choices_for_window_logo_position = choices_for_placement_strategy
|
||||
|
||||
def window_logo_scale(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['window_logo_scale'] = window_logo_scale(val)
|
||||
|
||||
def window_margin_width(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['window_margin_width'] = edge_width(val)
|
||||
|
||||
30
kitty/options/to-c-generated.h
generated
30
kitty/options/to-c-generated.h
generated
@@ -83,6 +83,19 @@ convert_from_opts_cursor_shape(PyObject *py_opts, Options *opts) {
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_cursor_shape_unfocused(PyObject *val, Options *opts) {
|
||||
opts->cursor_shape_unfocused = PyLong_AsLong(val);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_cursor_shape_unfocused(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "cursor_shape_unfocused");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_cursor_shape_unfocused(ret, opts);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_cursor_beam_thickness(PyObject *val, Options *opts) {
|
||||
opts->cursor_beam_thickness = PyFloat_AsFloat(val);
|
||||
@@ -616,6 +629,19 @@ convert_from_opts_window_logo_alpha(PyObject *py_opts, Options *opts) {
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_window_logo_scale(PyObject *val, Options *opts) {
|
||||
window_logo_scale(val, opts);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_opts_window_logo_scale(PyObject *py_opts, Options *opts) {
|
||||
PyObject *ret = PyObject_GetAttrString(py_opts, "window_logo_scale");
|
||||
if (ret == NULL) return;
|
||||
convert_from_python_window_logo_scale(ret, opts);
|
||||
Py_DECREF(ret);
|
||||
}
|
||||
|
||||
static void
|
||||
convert_from_python_resize_debounce_time(PyObject *val, Options *opts) {
|
||||
resize_debounce_time(val, opts);
|
||||
@@ -1150,6 +1176,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_cursor_shape(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_cursor_shape_unfocused(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_cursor_beam_thickness(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_cursor_underline_thickness(py_opts, opts);
|
||||
@@ -1232,6 +1260,8 @@ convert_opts_from_python_opts(PyObject *py_opts, Options *opts) {
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_window_logo_alpha(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_window_logo_scale(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_resize_debounce_time(py_opts, opts);
|
||||
if (PyErr_Occurred()) return false;
|
||||
convert_from_opts_resize_in_steps(py_opts, opts);
|
||||
|
||||
@@ -338,6 +338,12 @@ tab_bar_margin_height(PyObject *val, Options *opts) {
|
||||
opts->tab_bar_margin_height.inner = PyFloat_AsDouble(PyTuple_GET_ITEM(val, 1));
|
||||
}
|
||||
|
||||
static void
|
||||
window_logo_scale(PyObject *src, Options *opts) {
|
||||
opts->window_logo_scale.width = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 0));
|
||||
opts->window_logo_scale.height = PyFloat_AsFloat(PyTuple_GET_ITEM(src, 1));
|
||||
}
|
||||
|
||||
static void
|
||||
resize_debounce_time(PyObject *src, Options *opts) {
|
||||
opts->resize_debounce_time.on_end = s_double_to_monotonic_t(PyFloat_AsDouble(PyTuple_GET_ITEM(src, 0)));
|
||||
|
||||
6
kitty/options/types.py
generated
6
kitty/options/types.py
generated
@@ -34,7 +34,7 @@ choices_for_tab_switch_strategy = typing.Literal['last', 'left', 'previous', 'ri
|
||||
choices_for_terminfo_type = typing.Literal['path', 'direct', 'none']
|
||||
choices_for_undercurl_style = typing.Literal['thin-sparse', 'thin-dense', 'thick-sparse', 'thick-dense']
|
||||
choices_for_underline_hyperlinks = typing.Literal['hover', 'always', 'never']
|
||||
choices_for_window_logo_position = typing.Literal['top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right']
|
||||
choices_for_window_logo_position = choices_for_placement_strategy
|
||||
|
||||
option_names = ( # {{{
|
||||
'action_alias',
|
||||
@@ -330,6 +330,7 @@ option_names = ( # {{{
|
||||
'cursor_beam_thickness',
|
||||
'cursor_blink_interval',
|
||||
'cursor_shape',
|
||||
'cursor_shape_unfocused',
|
||||
'cursor_stop_blinking_after',
|
||||
'cursor_text_color',
|
||||
'cursor_underline_thickness',
|
||||
@@ -459,6 +460,7 @@ option_names = ( # {{{
|
||||
'window_logo_alpha',
|
||||
'window_logo_path',
|
||||
'window_logo_position',
|
||||
'window_logo_scale',
|
||||
'window_margin_width',
|
||||
'window_padding_width',
|
||||
'window_resize_step_cells',
|
||||
@@ -502,6 +504,7 @@ class Options:
|
||||
cursor_beam_thickness: float = 1.5
|
||||
cursor_blink_interval: float = -1.0
|
||||
cursor_shape: int = 1
|
||||
cursor_shape_unfocused: int = 0
|
||||
cursor_stop_blinking_after: float = 15.0
|
||||
cursor_text_color: typing.Optional[kitty.fast_data_types.Color] = Color(17, 17, 17)
|
||||
cursor_underline_thickness: float = 2.0
|
||||
@@ -619,6 +622,7 @@ class Options:
|
||||
window_logo_alpha: float = 0.5
|
||||
window_logo_path: typing.Optional[str] = None
|
||||
window_logo_position: choices_for_window_logo_position = 'bottom-right'
|
||||
window_logo_scale: typing.Tuple[float, float] = (0, -1.0)
|
||||
window_margin_width: FloatEdges = FloatEdges(left=0, top=0, right=0, bottom=0)
|
||||
window_padding_width: FloatEdges = FloatEdges(left=0, top=0, right=0, bottom=0)
|
||||
window_resize_step_cells: int = 2
|
||||
|
||||
@@ -44,7 +44,7 @@ from kitty.conf.utils import (
|
||||
unit_float,
|
||||
)
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, Color, Shlex, SingleKey
|
||||
from kitty.fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, NO_CURSOR_SHAPE, Color, Shlex, SingleKey
|
||||
from kitty.fonts import FontFeature, FontModification, ModificationType, ModificationUnit, ModificationValue
|
||||
from kitty.key_names import character_key_name_aliases, functional_key_name_aliases, get_key_name_lookup
|
||||
from kitty.rgb import color_as_int
|
||||
@@ -524,6 +524,12 @@ cshapes = {
|
||||
'beam': CURSOR_BEAM,
|
||||
'underline': CURSOR_UNDERLINE
|
||||
}
|
||||
cshapes_unfocused = {
|
||||
'block': CURSOR_BLOCK,
|
||||
'beam': CURSOR_BEAM,
|
||||
'underline': CURSOR_UNDERLINE,
|
||||
'hollow': NO_CURSOR_SHAPE
|
||||
}
|
||||
|
||||
|
||||
def to_cursor_shape(x: str) -> int:
|
||||
@@ -537,6 +543,17 @@ def to_cursor_shape(x: str) -> int:
|
||||
)
|
||||
|
||||
|
||||
def to_cursor_unfocused_shape(x: str) -> int:
|
||||
try:
|
||||
return cshapes_unfocused[x.lower()]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
'Invalid unfocused cursor shape: {} allowed values are {}'.format(
|
||||
x, ', '.join(cshapes_unfocused)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def scrollback_lines(x: str) -> int:
|
||||
ans = int(x)
|
||||
if ans < 0:
|
||||
@@ -646,6 +663,13 @@ def resize_draw_strategy(x: str) -> int:
|
||||
return cmap.get(x.lower(), 0)
|
||||
|
||||
|
||||
def window_logo_scale(x: str) -> Tuple[float, float]:
|
||||
parts = x.split(maxsplit=1)
|
||||
if len(parts) == 1:
|
||||
return positive_float(parts[0]), -1.0
|
||||
return positive_float(parts[0]), positive_float(parts[1])
|
||||
|
||||
|
||||
def resize_debounce_time(x: str) -> Tuple[float, float]:
|
||||
parts = x.split(maxsplit=1)
|
||||
if len(parts) == 1:
|
||||
@@ -770,7 +794,7 @@ def remote_control_password(val: str, current_val: Dict[str, str]) -> Iterable[T
|
||||
# line of remote_control_password
|
||||
raise ValueError('Passwords are not allowed to start with hyphens, ignoring this password')
|
||||
if len(parts) == 1:
|
||||
yield parts[0], tuple()
|
||||
yield "", (parts[0],)
|
||||
else:
|
||||
yield parts[0], tuple(parts[1:])
|
||||
|
||||
|
||||
@@ -276,7 +276,8 @@ they will only work if this process is run within a kitty window.
|
||||
--password
|
||||
A password to use when contacting kitty. This will cause kitty to ask the user
|
||||
for permission to perform the specified action, unless the password has been
|
||||
accepted before or is pre-configured in :file:`kitty.conf`.
|
||||
accepted before or is pre-configured in :file:`kitty.conf`. To use a blank password
|
||||
specify :option:`kitten @ --use-password` as :code:`always`.
|
||||
|
||||
|
||||
--password-file
|
||||
@@ -300,7 +301,7 @@ default=if-available
|
||||
choices=if-available,never,always
|
||||
If no password is available, kitty will usually just send the remote control command
|
||||
without a password. This option can be used to force it to :code:`always` or :code:`never` use
|
||||
the supplied password.
|
||||
the supplied password. If set to always and no password is provided, the blank password is used.
|
||||
'''.format, appname=appname)
|
||||
|
||||
|
||||
|
||||
@@ -3189,25 +3189,35 @@ screen_open_url(Screen *self) {
|
||||
|
||||
// URLs {{{
|
||||
static void
|
||||
extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type sentinel, bool newlines_allowed) {
|
||||
extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type sentinel, bool newlines_allowed, index_type last_hostname_char_pos) {
|
||||
unsigned int count = 0;
|
||||
bool has_newline = false;
|
||||
index_type orig_y = *y;
|
||||
while(count++ < 10) {
|
||||
bool in_hostname = last_hostname_char_pos >= line->xnum;
|
||||
has_newline = !line->gpu_cells[line->xnum-1].attrs.next_char_was_wrapped;
|
||||
if (*x != line->xnum - 1 || (!newlines_allowed && has_newline)) break;
|
||||
bool next_line_starts_with_url_chars = false;
|
||||
line = screen_visual_line(screen, *y + 2);
|
||||
if (line) {
|
||||
next_line_starts_with_url_chars = line_startswith_url_chars(line);
|
||||
next_line_starts_with_url_chars = line_startswith_url_chars(line, in_hostname);
|
||||
has_newline = !line->attrs.is_continued;
|
||||
if (next_line_starts_with_url_chars && has_newline && !newlines_allowed) next_line_starts_with_url_chars = false;
|
||||
if (sentinel && next_line_starts_with_url_chars && line->cpu_cells[0].ch == sentinel) next_line_starts_with_url_chars = false;
|
||||
}
|
||||
line = screen_visual_line(screen, *y + 1);
|
||||
if (!line) break;
|
||||
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars);
|
||||
if (!new_x && !line_startswith_url_chars(line)) break;
|
||||
if (in_hostname) {
|
||||
for (last_hostname_char_pos = 0; last_hostname_char_pos < line->xnum; last_hostname_char_pos++) {
|
||||
if (line->cpu_cells[last_hostname_char_pos].ch == '/') {
|
||||
if (last_hostname_char_pos > 0) last_hostname_char_pos--;
|
||||
else { in_hostname = false; last_hostname_char_pos = line->xnum; }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars, in_hostname, last_hostname_char_pos);
|
||||
if (!new_x && !line_startswith_url_chars(line, in_hostname)) break;
|
||||
*y += 1; *x = new_x;
|
||||
}
|
||||
if (sentinel && *x == 0 && *y > orig_y) {
|
||||
@@ -3254,24 +3264,30 @@ screen_detect_url(Screen *screen, unsigned int x, unsigned int y) {
|
||||
}
|
||||
char_type sentinel = 0;
|
||||
bool newlines_allowed = !is_excluded_from_url('\n');
|
||||
index_type last_hostname_char_pos = screen->columns;
|
||||
if (line) {
|
||||
url_start = line_url_start_at(line, x);
|
||||
if (url_start < line->xnum) {
|
||||
bool next_line_starts_with_url_chars = false;
|
||||
if (y < screen->lines - 1) {
|
||||
line = screen_visual_line(screen, y+1);
|
||||
next_line_starts_with_url_chars = line_startswith_url_chars(line);
|
||||
next_line_starts_with_url_chars = line_startswith_url_chars(line, last_hostname_char_pos >= line->xnum);
|
||||
if (next_line_starts_with_url_chars && !newlines_allowed && !line->attrs.is_continued) next_line_starts_with_url_chars = false;
|
||||
line = screen_visual_line(screen, y);
|
||||
}
|
||||
sentinel = get_url_sentinel(line, url_start);
|
||||
url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars);
|
||||
index_type slash_count = 0;
|
||||
for (index_type i = url_start; i < line->xnum; i++) {
|
||||
const char_type ch = line->cpu_cells[i].ch;
|
||||
if (ch == '/' && ++slash_count > 2) { last_hostname_char_pos = i - 1; break; }
|
||||
}
|
||||
url_end = line_url_end_at(line, x, true, sentinel, next_line_starts_with_url_chars, x <= last_hostname_char_pos, last_hostname_char_pos);
|
||||
}
|
||||
has_url = url_end > url_start;
|
||||
}
|
||||
if (has_url) {
|
||||
index_type y_extended = y;
|
||||
extend_url(screen, line, &url_end, &y_extended, sentinel, newlines_allowed);
|
||||
extend_url(screen, line, &url_end, &y_extended, sentinel, newlines_allowed, last_hostname_char_pos);
|
||||
screen_mark_url(screen, url_start, y, url_end, y_extended);
|
||||
} else {
|
||||
screen_mark_url(screen, 0, 0, 0, 0);
|
||||
|
||||
@@ -39,6 +39,11 @@ class WindowSpec:
|
||||
def __init__(self, launch_spec: Union['LaunchSpec', 'SpecialWindowInstance']):
|
||||
self.launch_spec = launch_spec
|
||||
self.resize_spec: Optional[ResizeSpec] = None
|
||||
self.is_background_process = False
|
||||
if hasattr(launch_spec, 'opts'): # LaunchSpec
|
||||
from .launch import LaunchSpec
|
||||
assert isinstance(launch_spec, LaunchSpec)
|
||||
self.is_background_process = launch_spec.opts.type == 'background'
|
||||
|
||||
|
||||
class Tab:
|
||||
@@ -53,6 +58,13 @@ class Tab:
|
||||
self.cwd: Optional[str] = None
|
||||
self.next_title: Optional[str] = None
|
||||
|
||||
@property
|
||||
def has_non_background_processes(self) -> bool:
|
||||
for w in self.windows:
|
||||
if not w.is_background_process:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Session:
|
||||
|
||||
@@ -65,6 +77,13 @@ class Session:
|
||||
self.os_window_state: Optional[str] = None
|
||||
self.focus_os_window: bool = False
|
||||
|
||||
@property
|
||||
def has_non_background_processes(self) -> bool:
|
||||
for t in self.tabs:
|
||||
if t.has_non_background_processes:
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_tab(self, opts: Options, name: str = '') -> None:
|
||||
if self.tabs and not self.tabs[-1].windows:
|
||||
del self.tabs[-1]
|
||||
|
||||
@@ -333,7 +333,18 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, c
|
||||
case CURSOR_UNDERLINE:
|
||||
rd->cursor_fg_sprite_idx = UNDERLINE_IDX; break;
|
||||
}
|
||||
} else rd->cursor_fg_sprite_idx = UNFOCUSED_IDX;
|
||||
} else {
|
||||
switch(OPT(cursor_shape_unfocused)) {
|
||||
default:
|
||||
rd->cursor_fg_sprite_idx = UNFOCUSED_IDX; break;
|
||||
case CURSOR_BEAM:
|
||||
rd->cursor_fg_sprite_idx = BEAM_IDX; break;
|
||||
case CURSOR_UNDERLINE:
|
||||
rd->cursor_fg_sprite_idx = UNDERLINE_IDX; break;
|
||||
case CURSOR_BLOCK:
|
||||
rd->cursor_fg_sprite_idx = BLOCK_IDX; break;
|
||||
}
|
||||
}
|
||||
color_type cell_fg = rd->default_fg, cell_bg = rd->default_bg;
|
||||
index_type cell_color_x = cursor->x;
|
||||
bool reversed = false;
|
||||
@@ -601,7 +612,7 @@ draw_scroll_indicator(bool premult, Screen *screen, const CellRenderData *crd) {
|
||||
if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE }
|
||||
bind_program(TINT_PROGRAM);
|
||||
const color_type bar_color = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.highlight_bg, screen->color_profile->configured.highlight_bg).rgb;
|
||||
GLfloat alpha = 0.8f;
|
||||
GLfloat alpha = OPT(scrollback_indicator_opacity);
|
||||
float frac = (float)screen->scrolled_by / (float)screen->historybuf->count;
|
||||
const GLfloat bar_height = crd->gl.dy;
|
||||
GLfloat bottom = (crd->gl.ystart - crd->gl.height);
|
||||
@@ -732,6 +743,41 @@ draw_window_logo(ssize_t vao_idx, OSWindow *os_window, const WindowLogoRenderDat
|
||||
BLEND_PREMULT;
|
||||
GLfloat logo_width_gl = gl_size(wl->instance->width, os_window->viewport_width);
|
||||
GLfloat logo_height_gl = gl_size(wl->instance->height, os_window->viewport_height);
|
||||
|
||||
if (OPT(window_logo_scale.width) > 0 || OPT(window_logo_scale.height) > 0) {
|
||||
unsigned int scaled_wl_width = os_window->viewport_width;
|
||||
unsigned int scaled_wl_height = os_window->viewport_height;
|
||||
|
||||
// [sx] Scales logo to sx % of the viewports shortest dimension, preserving aspect ratio
|
||||
if (OPT(window_logo_scale.height) < 0) {
|
||||
if (os_window->viewport_height < os_window->viewport_width) {
|
||||
scaled_wl_height = (int)(os_window->viewport_height * OPT(window_logo_scale.width) / 100);
|
||||
scaled_wl_width = wl->instance->width * scaled_wl_height / wl->instance->height;
|
||||
} else {
|
||||
scaled_wl_width = (int)(os_window->viewport_width * OPT(window_logo_scale.width) / 100);
|
||||
scaled_wl_height = wl->instance->height * scaled_wl_width / wl->instance->width;
|
||||
}
|
||||
}
|
||||
// [0 sy] Scales logo's y dimension to sy % of viewporty keeping original x dimension
|
||||
else if (OPT(window_logo_scale.width) == 0.0) {
|
||||
scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100);
|
||||
scaled_wl_width = wl->instance->width;
|
||||
}
|
||||
// [sx 0] Scales logo's x dimension to sx % of viewportx keeping original y dimension
|
||||
else if (OPT(window_logo_scale.height) == 0.0) {
|
||||
scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100);
|
||||
scaled_wl_height = wl->instance->height;
|
||||
}
|
||||
// [sx sy] Scales logo's x and y dimension to sx and sy % of viewportx and viewporty respectively
|
||||
else {
|
||||
scaled_wl_height = (int)(scaled_wl_height * OPT(window_logo_scale.height) / 100);
|
||||
scaled_wl_width = (int)(scaled_wl_width * OPT(window_logo_scale.width) / 100);
|
||||
}
|
||||
|
||||
logo_height_gl = gl_size(scaled_wl_height, os_window->viewport_height);
|
||||
logo_width_gl = gl_size(scaled_wl_width, os_window->viewport_width);
|
||||
}
|
||||
|
||||
GLfloat logo_left_gl = clamp_position_to_nearest_pixel(
|
||||
crd->gl.xstart + crd->gl.width * wl->position.canvas_x - logo_width_gl * wl->position.image_x, os_window->viewport_width);
|
||||
GLfloat logo_top_gl = clamp_position_to_nearest_pixel(
|
||||
|
||||
@@ -38,7 +38,7 @@ typedef struct {
|
||||
double wheel_scroll_multiplier, touch_scroll_multiplier;
|
||||
int wheel_scroll_min_lines;
|
||||
bool enable_audio_bell;
|
||||
CursorShape cursor_shape;
|
||||
CursorShape cursor_shape, cursor_shape_unfocused;
|
||||
float cursor_beam_thickness;
|
||||
float cursor_underline_thickness;
|
||||
unsigned int url_style;
|
||||
@@ -65,6 +65,7 @@ typedef struct {
|
||||
ImageAnchorPosition window_logo_position;
|
||||
bool background_image_linear;
|
||||
float background_tint, background_tint_gaps, window_logo_alpha;
|
||||
struct { float width, height; } window_logo_scale;
|
||||
|
||||
bool dynamic_background_opacity;
|
||||
float inactive_text_alpha;
|
||||
|
||||
@@ -221,7 +221,8 @@ class Tab: # {{{
|
||||
if window.resize_spec is not None:
|
||||
self.resize_window(*window.resize_spec)
|
||||
|
||||
self.windows.set_active_window_group_for(self.windows.all_windows[session_tab.active_window_idx])
|
||||
with suppress(IndexError):
|
||||
self.windows.set_active_window_group_for(self.windows.all_windows[session_tab.active_window_idx])
|
||||
|
||||
def serialize_state(self) -> Dict[str, Any]:
|
||||
return {
|
||||
|
||||
@@ -1028,12 +1028,12 @@ class TestScreen(BaseTest):
|
||||
url = ''.join(s.text_for_marked_url())
|
||||
self.assertEqual(expected, url)
|
||||
|
||||
def t(url, x=0, y=0, before='', after=''):
|
||||
def t(url, x=0, y=0, before='', after='', expected=''):
|
||||
s.reset()
|
||||
s.cursor.x = x
|
||||
s.cursor.y = y
|
||||
s.draw(before + url + after)
|
||||
ae(url, x=x + 1 + len(before), y=y)
|
||||
ae(expected or url, x=x + 1 + len(before), y=y)
|
||||
|
||||
|
||||
t('http://moo.com')
|
||||
@@ -1043,10 +1043,15 @@ class TestScreen(BaseTest):
|
||||
t('http://moo.com', before=st, after=e)
|
||||
for trailer in ')-=':
|
||||
t('http://moo.com' + trailer)
|
||||
for trailer in '{([]}<>':
|
||||
for trailer in '{}([<>':
|
||||
t('http://moo.com', after=trailer)
|
||||
t('http://moo.com', x=s.columns - 9)
|
||||
t('https://wraps-by-one-char.com', before='[', after=']')
|
||||
t('http://[::1]:8080')
|
||||
t('https://wr[aps-by-one-ch]ar.com')
|
||||
t('http://[::1]:8080/x', after='[')
|
||||
t('http://[::1]:8080/x]y34', expected='http://[::1]:8080/x')
|
||||
t('https://wraps-by-one-char.com[]/x', after='[')
|
||||
|
||||
def test_prompt_marking(self):
|
||||
s = self.create_screen()
|
||||
|
||||
@@ -211,7 +211,10 @@ def get_data():
|
||||
sys.stdout.write('\r\033[K')
|
||||
data = base64.standard_b64decode(data)
|
||||
with temporary_directory(dir=HOME, prefix='.kitty-ssh-kitten-untar-') as tdir, tarfile.open(fileobj=io.BytesIO(data)) as tf:
|
||||
tf.extractall(tdir)
|
||||
try:
|
||||
tf.extractall(tdir, filter='data')
|
||||
except TypeError:
|
||||
tf.extractall(tdir)
|
||||
with open(tdir + '/data.sh') as f:
|
||||
env_vars = f.read()
|
||||
apply_env_vars(env_vars)
|
||||
|
||||
@@ -32,10 +32,16 @@ const lowerhex = "0123456789abcdef"
|
||||
|
||||
var ProtocolVersion [3]int = [3]int{0, 26, 0}
|
||||
|
||||
type password struct {
|
||||
val string
|
||||
is_set bool
|
||||
}
|
||||
|
||||
type GlobalOptions struct {
|
||||
to_network, to_address, password string
|
||||
to_address_is_from_env_var bool
|
||||
already_setup bool
|
||||
to_network, to_address string
|
||||
password password
|
||||
to_address_is_from_env_var bool
|
||||
already_setup bool
|
||||
}
|
||||
|
||||
var global_options GlobalOptions
|
||||
@@ -135,15 +141,15 @@ func simple_serializer(rc *utils.RemoteControlCmd) (ans []byte, err error) {
|
||||
|
||||
type serializer_func func(rc *utils.RemoteControlCmd) ([]byte, error)
|
||||
|
||||
func create_serializer(password string, encoded_pubkey string, io_data *rc_io_data) (err error) {
|
||||
func create_serializer(password password, encoded_pubkey string, io_data *rc_io_data) (err error) {
|
||||
io_data.serializer = simple_serializer
|
||||
if password != "" {
|
||||
if password.is_set {
|
||||
encryption_version, pubkey, err := get_pubkey(encoded_pubkey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io_data.serializer = func(rc *utils.RemoteControlCmd) (ans []byte, err error) {
|
||||
ec, err := crypto.Encrypt_cmd(rc, global_options.password, pubkey, encryption_version)
|
||||
ec, err := crypto.Encrypt_cmd(rc, global_options.password.val, pubkey, encryption_version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -293,25 +299,26 @@ func send_rc_command(io_data *rc_io_data) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func get_password(password string, password_file string, password_env string, use_password string) (ans string, err error) {
|
||||
func get_password(password string, password_file string, password_env string, use_password string) (ans password, err error) {
|
||||
if use_password == "never" {
|
||||
return
|
||||
}
|
||||
if password != "" {
|
||||
ans = password
|
||||
ans.is_set, ans.val = true, password
|
||||
}
|
||||
if ans == "" && password_file != "" {
|
||||
if !ans.is_set && password_file != "" {
|
||||
if password_file == "-" {
|
||||
if tty.IsTerminal(os.Stdin.Fd()) {
|
||||
ans, err = tui.ReadPassword("Password: ", true)
|
||||
p, err := tui.ReadPassword("Password: ", true)
|
||||
if err != nil {
|
||||
return
|
||||
return ans, err
|
||||
}
|
||||
ans.is_set, ans.val = true, p
|
||||
} else {
|
||||
var q []byte
|
||||
q, err = io.ReadAll(os.Stdin)
|
||||
if err == nil {
|
||||
ans = strings.TrimRight(string(q), " \n\t")
|
||||
ans.is_set, ans.val = true, strings.TrimRight(string(q), " \n\t")
|
||||
}
|
||||
ttyf, err := os.Open(tty.Ctermid())
|
||||
if err == nil {
|
||||
@@ -323,7 +330,7 @@ func get_password(password string, password_file string, password_env string, us
|
||||
var q []byte
|
||||
q, err = os.ReadFile(password_file)
|
||||
if err == nil {
|
||||
ans = strings.TrimRight(string(q), " \n\t")
|
||||
ans.is_set, ans.val = true, strings.TrimRight(string(q), " \n\t")
|
||||
} else {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
err = nil
|
||||
@@ -334,13 +341,14 @@ func get_password(password string, password_file string, password_env string, us
|
||||
return
|
||||
}
|
||||
}
|
||||
if ans == "" && password_env != "" {
|
||||
ans = os.Getenv(password_env)
|
||||
if !ans.is_set && password_env != "" {
|
||||
ans.val, ans.is_set = os.LookupEnv(password_env)
|
||||
}
|
||||
if ans == "" && use_password == "always" {
|
||||
return ans, fmt.Errorf("No password was found")
|
||||
if !ans.is_set && use_password == "always" {
|
||||
ans.is_set = true
|
||||
return ans, nil
|
||||
}
|
||||
if len(ans) > 1024 {
|
||||
if len(ans.val) > 1024 {
|
||||
return ans, fmt.Errorf("Specified password is too long")
|
||||
}
|
||||
return ans, nil
|
||||
|
||||
@@ -58,7 +58,7 @@ func TestCommandToJSON(t *testing.T) {
|
||||
|
||||
func TestRCSerialization(t *testing.T) {
|
||||
io_data := rc_io_data{}
|
||||
err := create_serializer("", "", &io_data)
|
||||
err := create_serializer(password{"", false}, "", &io_data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func TestRCSerialization(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = create_serializer("tpw", pubkey, &io_data)
|
||||
err = create_serializer(password{"tpw", true}, pubkey, &io_data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user