Compare commits

...

37 Commits

Author SHA1 Message Date
Kovid Goyal
72f92b395f version 0.26.5 2022-11-07 15:36:42 +05:30
Kovid Goyal
c9c8eb6e94 ... 2022-11-05 11:37:07 +05:30
Kovid Goyal
59ded41f7a When no OS Window is focused the active_* should return those belonging to the last focused OS Window 2022-11-05 11:34:10 +05:30
Kovid Goyal
10ad56885e Properly specify semantics of active and focused
active means active in parent. focused means actually has keyboard
focus.
2022-11-04 10:38:18 +05:30
Kovid Goyal
d1eb9340ac Output the actual tab id in its repr 2022-11-04 09:29:33 +05:30
Kovid Goyal
a856c64104 Have the semantics of is_focused match that of state:focused otherwise things are liable to get confusing
The previous value is now available as is_active_window
2022-11-03 22:32:07 +05:30
Kovid Goyal
c0b11c5656 Also change the behavior of state:focused matching for tabs 2022-11-03 21:56:23 +05:30
Kovid Goyal
935c4ded6b Remote control: When matching window by state:focused match the window belonging to the OS window that was last focused
Fixes #5602
2022-11-03 21:52:20 +05:30
Kovid Goyal
f5de08d5fa Splits layout: Add a new mappable action to move the active window to the screen edge
Fixes #5643
2022-11-03 17:40:04 +05:30
Kovid Goyal
9d471782dd Increase some more timeouts 2022-11-03 13:13:14 +05:30
Kovid Goyal
dea3df66f8 system python appears broken on macos-latest image
The pip scripts install directory is not on PATH
2022-11-03 13:05:03 +05:30
Kovid Goyal
ec80a1532c Try to make mypy happy with the contortions needed for importlib.resources 2022-11-03 12:41:13 +05:30
Kovid Goyal
93563f1280 More pointless code churn thanks to importlib.resources
They've deprecated a bunch of API but the suggested replacement is
either not present in versions of python that are not EOLed or doesn't
actually work.
2022-11-03 12:13:47 +05:30
Kovid Goyal
e309e54002 Allow using the re module in tab title templates
Fix #5639
2022-11-01 22:25:58 +05:30
Kovid Goyal
0b3228ab16 ... 2022-11-01 12:51:36 +05:30
Kovid Goyal
3f28cf45d4 Dont use the deprecated importlib.resources API 2022-11-01 12:38:24 +05:30
Kovid Goyal
88b829fc9b cleanup comment 2022-11-01 12:22:05 +05:30
Kovid Goyal
458ac6953f Update changelog for last PR 2022-11-01 12:21:09 +05:30
Kovid Goyal
a6855bde3c Merge branch 'no_cursor_update_if_not_changed' of https://github.com/wengxt/kitty 2022-11-01 12:20:18 +05:30
Kovid Goyal
f6f2f00c53 Add a note about wayland semantics 2022-11-01 12:13:23 +05:30
Kovid Goyal
00324c96fc ... 2022-10-31 22:09:01 +05:30
Kovid Goyal
2435a8ccfd Wayland GNOME: Fix incorrect window size when in some circumstances when switching between windows with window decorations disabled
Only call wl_surface_commit() after a resize when the correct size buffer is
attached to the surface. This is ensured by setting a flag on the window that prevents
all surface commits till it is cleared. The flag is cleared at next
eglSwapBuffers().

I dont actually understand if this guarantees that the
buffer size is always correct. For example, if the back buffer is
latched when wl_egl_resize_window() is called, the backbuffer will be
correct only after two swaps (I think). Or maybe the old back buffer is
discarded, I cant find any documentation about it.

All I can say is that doing it this way seems to fix the issue.

Thanks to @jadahl for his help with tracking down the root cause.

Fixes #4802
2022-10-31 21:59:01 +05:30
Kovid Goyal
de122ed727 Bind eglQuerySurface 2022-10-31 21:03:39 +05:30
Kovid Goyal
c2ead407ae ... 2022-10-31 18:14:38 +05:30
Kovid Goyal
16b322616a Fix cursor position at x=0 changing to x=1 on resize
Fixes #5635
2022-10-31 08:03:42 +05:30
Kovid Goyal
e0b4c7edc5 Increase the timeout for shell integration tests
Needed for ARM running non-natively
2022-10-30 11:09:36 +05:30
Kovid Goyal
a8ab4eaf23 Use a larger timeout when running prewarm test 2022-10-30 10:36:10 +05:30
Kovid Goyal
3847837bd0 Forgot to run gen-config.py 2022-10-19 20:50:53 +05:30
Kovid Goyal
1747bbbbcb ssh kitten: Allow using absolute paths for the location of transferred data
Fixes #5607
2022-10-19 20:43:53 +05:30
Kovid Goyal
90e80477e9 Fix background_tint with background_opacity < 1 causing colors in cell area and padding to not match 2022-10-19 19:24:37 +05:30
Kovid Goyal
4f90110a7c Fix ghosting when using background_tint under GNOME+Wayland
The problem was that on Wayland if the buffer contains pixels with alpha
< 1 they are blended with something, even if the window is opaque. Under
mutter that something was the previous frame, under sway it was the
background/whatever is under the window.

So when blending the tint color, use a blend mode that results in opaque
pixels.

Fixes #5605
2022-10-19 19:02:05 +05:30
Kovid Goyal
28c616d3ff Move blend mode selection into draw_tint 2022-10-19 17:13:46 +05:30
Kovid Goyal
f5126c664c Wayland KDE: Fix abort when pasting into Firefox
Dunno why kwin uses drag and drop callbacks when pasting into Firefox on
Wayland, but then this is Wayland, so every stupid thing imaginable will
be done at least once.

Fixes #5603
2022-10-19 11:28:33 +05:30
Kovid Goyal
0f340086c0 Fix resize_draw_strategy=static
The text is now static again but the background image is still scaled, cant be
bothered about that. Fixes #5601
2022-10-19 08:26:31 +05:30
Kovid Goyal
5c34f16ab4 Fix #5600 2022-10-19 07:45:27 +05:30
Kovid Goyal
03fe928e3f show progress while compressing release tarballs 2022-10-17 09:55:09 +05:30
Weng Xuetian
7fd119d0eb Fix message recursion between wayland compositor (#5360)
1. Only update preedit if preedit changed.
2. Only send cursor update if cursor position is not changed.
2022-08-11 15:45:45 -07:00
36 changed files with 376 additions and 170 deletions

View File

@@ -27,11 +27,11 @@ jobs:
sanitize: 0
- python: b
pyver: "3.9"
pyver: "3.11"
sanitize: 1
- python: c
pyver: "3.10"
pyver: "3.9"
sanitize: 1
@@ -76,7 +76,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: 3.9
python-version: "3.10"
- name: Install build-only deps
run: pip install -r docs/requirements.txt flake8 mypy types-requests types-docutils
@@ -128,6 +128,11 @@ jobs:
with:
fetch-depth: 0 # needed for :commit: docs role
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.10"
- name: Build kitty
run: python3 .github/workflows/ci.py build

View File

@@ -220,7 +220,7 @@ def create_tarfile(env, compression_level='9'):
print('Compressing archive...')
ans = f'{dist.rpartition(".")[0]}.txz'
start_time = time.time()
subprocess.check_call(['xz', '--threads=0', '-f', f'-{compression_level}', dist])
subprocess.check_call(['xz', '--verbose', '--threads=0', '-f', f'-{compression_level}', dist])
secs = time.time() - start_time
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
os.rename(f'{dist}.xz', ans)

View File

@@ -35,6 +35,27 @@ mouse anywhere in the current command to move the cursor there. See
Detailed list of changes
-------------------------------------
0.26.5 [2022-11-07]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Splits layout: Add a new mappable action to move the active window to the screen edge (:iss:`5643`)
- ssh kitten: Allow using absolute paths for the location of transferred data (:iss:`5607`)
- Fix a regression in the previous release that caused a :opt:`resize_draw_strategy` of ``static`` to not work (:iss:`5601`)
- Wayland KDE: Fix abort when pasting into Firefox (:iss:`5603`)
- Wayland GNOME: Fix ghosting when using :opt:`background_tint` (:iss:`5605`)
- Fix cursor position at x=0 changing to x=1 on resize (:iss:`5635`)
- Wayland GNOME: Fix incorrect window size in some circumstances when switching between windows with window decorations disabled (:iss:`4802`)
- Wayland: Fix high CPU usage when using some input methods (:pull:`5369`)
- Remote control: When matching window by `state:focused` and no window currently has keyboard focus, match the window belonging to the OS window that was last focused (:iss:`5602`)
0.26.4 [2022-10-17]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -277,7 +277,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
from kitty.rc.base import (
RemoteCommand, all_command_names, command_for_name
)
field_pat = re.compile(r'\s*([a-zA-Z0-9_+/]+)\s*:\s*(.+)')
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
p(name)

View File

@@ -167,6 +167,12 @@ define a few extra key bindings in :file:`kitty.conf`::
map shift+right move_window right
map shift+down move_window down
# Move the active window to the indicated screen edge
map ctrl+shift+up layout_action move_to_screen_edge top
map ctrl+shift+left layout_action move_to_screen_edge left
map ctrl+shift+right layout_action move_to_screen_edge right
map ctrl+shift+down layout_action move_to_screen_edge bottom
# Switch focus to the neighboring window in the indicated direction
map ctrl+left neighboring_window left
map ctrl+right neighboring_window right

3
glfw/context.c vendored
View File

@@ -478,6 +478,9 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
}
window->context.swapBuffers(window);
#ifdef _GLFW_WAYLAND
_glfwWaylandAfterBufferSwap(window);
#endif
}
GLFWAPI void glfwSwapInterval(int interval)

1
glfw/egl_context.c vendored
View File

@@ -326,6 +326,7 @@ bool _glfwInitEGL(void)
glfw_dlsym(_glfw.egl.SwapBuffers, _glfw.egl.handle, "eglSwapBuffers");
glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval");
glfw_dlsym(_glfw.egl.QueryString, _glfw.egl.handle, "eglQueryString");
glfw_dlsym(_glfw.egl.QuerySurface, _glfw.egl.handle, "eglQuerySurface");
glfw_dlsym(_glfw.egl.GetProcAddress, _glfw.egl.handle, "eglGetProcAddress");
if (!_glfw.egl.GetConfigAttrib ||

3
glfw/egl_context.h vendored
View File

@@ -132,6 +132,7 @@ typedef EGLBoolean (EGLAPIENTRY * PFN_eglMakeCurrent)(EGLDisplay,EGLSurface,EGLS
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint);
typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
typedef const char* (EGLAPIENTRY * PFN_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*);
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
#define eglGetConfigs _glfw.egl.GetConfigs
@@ -149,6 +150,7 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglSwapBuffers _glfw.egl.SwapBuffers
#define eglSwapInterval _glfw.egl.SwapInterval
#define eglQueryString _glfw.egl.QueryString
#define eglQuerySurface _glfw.egl.QuerySurface
#define eglGetProcAddress _glfw.egl.GetProcAddress
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
@@ -211,6 +213,7 @@ typedef struct _GLFWlibraryEGL
PFN_eglSwapBuffers SwapBuffers;
PFN_eglSwapInterval SwapInterval;
PFN_eglQueryString QueryString;
PFN_eglQuerySurface QuerySurface;
PFN_eglGetProcAddress GetProcAddress;
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;

2
glfw/wl_platform.h vendored
View File

@@ -152,6 +152,7 @@ typedef struct _GLFWwindowWayland
bool hovered;
bool transparent;
struct wl_surface* surface;
bool waiting_for_swap_to_commit;
struct wl_egl_window* native;
struct wl_callback* callback;
@@ -369,6 +370,7 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwWaylandAfterBufferSwap(_GLFWwindow *window);
void _glfwSetupWaylandDataDevice(void);
void _glfwSetupWaylandPrimarySelectionDevice(void);
void animateCursorImage(id_type timer_id, void *data);

37
glfw/wl_text_input.c vendored
View File

@@ -9,12 +9,15 @@
#include "internal.h"
#include "wayland-text-input-unstable-v3-client-protocol.h"
#include <stdlib.h>
#include <string.h>
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
static struct zwp_text_input_v3* text_input;
static struct zwp_text_input_manager_v3* text_input_manager;
static char *pending_pre_edit = NULL;
static char *current_pre_edit = NULL;
static char *pending_commit = NULL;
static int last_cursor_left = 0, last_cursor_top = 0, last_cursor_width = 0, last_cursor_height = 0;
uint32_t commit_serial = 0;
static void commit(void) {
@@ -90,12 +93,20 @@ text_input_done(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, u
if (serial > commit_serial) _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
return;
}
if (pending_pre_edit) {
send_text(pending_pre_edit, GLFW_IME_PREEDIT_CHANGED);
if ((pending_pre_edit == NULL && current_pre_edit == NULL) ||
(pending_pre_edit && current_pre_edit && strcmp(pending_pre_edit, current_pre_edit) == 0)) {
free(pending_pre_edit); pending_pre_edit = NULL;
} else {
// Clear pre-edit text
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
free(current_pre_edit);
current_pre_edit = pending_pre_edit;
pending_pre_edit = NULL;
if (current_pre_edit) {
send_text(current_pre_edit, GLFW_IME_PREEDIT_CHANGED);
} else {
// Clear pre-edit text
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
}
}
if (pending_commit) {
send_text(pending_commit, GLFW_IME_COMMIT_TEXT);
@@ -133,6 +144,7 @@ _glfwWaylandDestroyTextInput(void) {
if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
text_input = NULL; text_input_manager = NULL;
free(pending_pre_edit); pending_pre_edit = NULL;
free(current_pre_edit); current_pre_edit = NULL;
free(pending_commit); pending_commit = NULL;
}
@@ -146,10 +158,11 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
zwp_text_input_v3_enable(text_input);
zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
} else {
if (pending_pre_edit) {
free(pending_pre_edit); pending_pre_edit = NULL;
if (current_pre_edit) {
// Clear pre-edit text
send_text(NULL, GLFW_IME_PREEDIT_CHANGED);
free(pending_pre_edit); pending_pre_edit = NULL;
free(current_pre_edit); current_pre_edit = NULL;
}
if (pending_commit) {
free(pending_commit); pending_commit = NULL;
@@ -161,9 +174,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
case GLFW_IME_UPDATE_CURSOR_POSITION: {
const int scale = w->wl.scale;
const int left = ev->cursor.left / scale, top = ev->cursor.top / scale, width = ev->cursor.width / scale, height = ev->cursor.height / scale;
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
commit();
if (left != last_cursor_left || top != last_cursor_top || width != last_cursor_width || height != last_cursor_height) {
last_cursor_left = left;
last_cursor_top = top;
last_cursor_width = width;
last_cursor_height = height;
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
commit();
}
}
break;
}

87
glfw/wl_window.c vendored
View File

@@ -251,6 +251,15 @@ static bool checkScaleChange(_GLFWwindow* window)
return false;
}
static void
commit_window_surface_if_safe(_GLFWwindow *window) {
// we only commit if the buffer attached to the surface is the correct size,
// which means that at least one frame is drawn after resizeFramebuffer()
if (!window->wl.waiting_for_swap_to_commit) {
wl_surface_commit(window->wl.surface);
}
}
// Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
{
@@ -262,7 +271,7 @@ static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
wl_surface_set_opaque_region(window->wl.surface, region);
if (commit_surface) wl_surface_commit(window->wl.surface);
if (commit_surface) commit_window_surface_if_safe(window);
wl_region_destroy(region);
}
@@ -287,9 +296,20 @@ resizeFramebuffer(_GLFWwindow* window) {
debug("Resizing framebuffer to: %dx%d at scale: %d\n", window->wl.width, window->wl.height, scale);
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
if (!window->wl.transparent) setOpaqueRegion(window, false);
window->wl.waiting_for_swap_to_commit = true;
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
}
void
_glfwWaylandAfterBufferSwap(_GLFWwindow* window) {
if (window->wl.waiting_for_swap_to_commit) {
debug("Waiting for swap to commit: swap has happened\n");
window->wl.waiting_for_swap_to_commit = false;
// this is not really needed, since I think eglSwapBuffers() calls wl_surface_commit()
// but lets be safe. See https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/egl/drivers/dri2/platform_wayland.c#L1510
wl_surface_commit(window->wl.surface);
}
}
static const char*
clipboard_mime(void) {
@@ -301,9 +321,9 @@ clipboard_mime(void) {
}
static bool
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height, bool *scale_changed) {
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height) {
bool size_changed = width != window->wl.width || height != window->wl.height;
*scale_changed = checkScaleChange(window);
bool scale_changed = checkScaleChange(window);
if (size_changed) {
_glfwInputWindowSize(window, width, height);
@@ -311,7 +331,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
resizeFramebuffer(window);
}
if (*scale_changed) {
if (scale_changed) {
debug("Scale changed to %d in dispatchChangesAfterConfigure\n", window->wl.scale);
if (!size_changed) resizeFramebuffer(window);
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
@@ -319,7 +339,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
_glfwInputWindowDamage(window);
return size_changed || *scale_changed;
return size_changed || scale_changed;
}
static void
@@ -568,6 +588,15 @@ static void xdgSurfaceHandleConfigure(void* data,
struct xdg_surface* surface,
uint32_t serial)
{
// The poorly documented pattern Wayland requires is:
// 1) ack the configure,
// 2) set the window geometry
// 3) attach a new buffer of the correct size to the surface
// 4) only then commit the surface.
// buffer is attached only by eglSwapBuffers,
// so we set a flag to not commit the surface till the next swapbuffers. Note that
// wl_egl_window_resize() does not actually resize the buffer until the next draw call
// or buffer state query.
_GLFWwindow* window = data;
xdg_surface_ack_configure(surface, serial);
if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) {
@@ -600,11 +629,10 @@ static void xdgSurfaceHandleConfigure(void* data,
}
bool resized = false;
bool scale_changed = false;
if (window->wl.pending_state) {
int width = window->wl.pending.width, height = window->wl.pending.height;
set_csd_window_geometry(window, &width, &height);
resized = dispatchChangesAfterConfigure(window, width, height, &scale_changed);
resized = dispatchChangesAfterConfigure(window, width, height);
if (window->wl.decorations.serverSide) {
free_csd_surfaces(window);
} else {
@@ -614,14 +642,7 @@ static void xdgSurfaceHandleConfigure(void* data,
}
inform_compositor_of_window_geometry(window, "configure");
// we need to swap buffers here to ensure the buffer attached to the surface is a multiple
// of the new scale. See https://github.com/kovidgoyal/kitty/issues/5467
if (scale_changed) swap_buffers(window);
// if a resize happened there will be a commit at the next render frame so
// dont commit here, GNOME doesnt like it and its not really needed anyway
if (!resized) wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
window->wl.pending_state = 0;
}
@@ -1021,7 +1042,7 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
window->wl.width = w; window->wl.height = h;
resizeFramebuffer(window);
ensure_csd_resources(window);
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
inform_compositor_of_window_geometry(window, "SetWindowSize");
}
}
@@ -1038,7 +1059,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
maxwidth = maxheight = 0;
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
}
}
@@ -1273,7 +1294,7 @@ void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
}
else
wl_surface_set_input_region(window->wl.surface, 0);
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
}
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
@@ -1337,7 +1358,7 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
zwp_locked_pointer_v1_set_cursor_position_hint(
window->wl.pointerLock.lockedPointer,
wl_fixed_from_double(x), wl_fixed_from_double(y));
wl_surface_commit(window->wl.surface);
commit_window_surface_if_safe(window);
}
}
@@ -1691,9 +1712,18 @@ static void primary_selection_source_canceled(void *data UNUSED, struct zwp_prim
zwp_primary_selection_source_v1_destroy(primary_selection_source);
}
// KWin aborts if we don't define these even though they are not used for copy/paste
static void dummy_data_source_target(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, const char* mime_type UNUSED) {
}
static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, uint dnd_action UNUSED) {
}
static const struct wl_data_source_listener data_source_listener = {
.send = _glfwSendClipboardText,
.cancelled = data_source_canceled,
.target = dummy_data_source_target,
.action = dummy_data_source_action,
};
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
@@ -2232,12 +2262,19 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long
_GLFWwindow* window = (_GLFWwindow*) handle;
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
window->wl.frameCallbackData.id = id;
window->wl.frameCallbackData.callback = callback;
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
if (window->wl.frameCallbackData.current_wl_callback) {
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
wl_surface_commit(window->wl.surface);
if (window->wl.waiting_for_swap_to_commit) {
callback(id);
window->wl.frameCallbackData.id = 0;
window->wl.frameCallbackData.callback = NULL;
window->wl.frameCallbackData.current_wl_callback = NULL;
} else {
window->wl.frameCallbackData.id = id;
window->wl.frameCallbackData.callback = callback;
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
if (window->wl.frameCallbackData.current_wl_callback) {
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
commit_window_surface_if_safe(window);
}
}
}

View File

@@ -40,10 +40,9 @@ shell or a :program:`python` executable. If the default :program:`sh` is not
available or broken, using an alternate interpreter can be useful.
''')
opt('remote_dir', '.local/share/kitty-ssh-kitten', option_type='relative_dir', long_text='''
opt('remote_dir', '.local/share/kitty-ssh-kitten', long_text='''
The location on the remote host where the files needed for this kitten are
installed. The location is relative to the HOME directory. Absolute paths or
paths that resolve to a location outside the HOME are not allowed.
installed. Relative paths are resolved with respect to :code:`$HOME`.
''')
opt('+copy', '', option_type='copy', add_to_default=False, long_text=f'''

View File

@@ -1,7 +1,7 @@
# generated by gen-config.py DO NOT edit
import typing
from kittens.ssh.options.utils import copy, env, hostname, relative_dir
from kittens.ssh.options.utils import copy, env, hostname
from kitty.conf.utils import merge_dicts, to_bool
@@ -39,7 +39,7 @@ class Parser:
ans['login_shell'] = str(val)
def remote_dir(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
ans['remote_dir'] = relative_dir(val)
ans['remote_dir'] = str(val)
def remote_kitty(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
val = val.lower()

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import posixpath
from typing import Any, Dict, Iterable, Optional, Tuple
from ..copy import CopyInstruction, parse_copy_instructions
@@ -9,16 +8,6 @@ from ..copy import CopyInstruction, parse_copy_instructions
DELETE_ENV_VAR = '_delete_this_env_var_'
def relative_dir(val: str) -> str:
if posixpath.isabs(val):
raise ValueError(f'Absolute paths not allowed. {val} is invalid.')
base = '/ffjdg'
q = posixpath.normpath(posixpath.join(base, val))
if q == base or not q.startswith(base):
raise ValueError(f'Paths that escape their parent dir are not allowed. {val} is not valid')
return posixpath.normpath(val)
def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]:
val = val.strip()
if val:

View File

@@ -12,8 +12,8 @@ from functools import partial
from gettext import gettext as _
from time import monotonic
from typing import (
TYPE_CHECKING, Any, Callable, Container, Dict, Iterable, Iterator, List,
Optional, Set, Tuple, Union
TYPE_CHECKING, Any, Callable, Container, Dict, Iterable, Iterator, List, Optional,
Set, Tuple, Union,
)
from weakref import WeakValueDictionary
@@ -21,15 +21,15 @@ from .child import cached_process_data, default_env, set_default_env
from .cli import create_opts, parse_args
from .cli_stub import CLIOptions
from .clipboard import (
Clipboard, get_clipboard_string, get_primary_selection,
set_clipboard_string, set_primary_selection
Clipboard, get_clipboard_string, get_primary_selection, set_clipboard_string,
set_primary_selection,
)
from .conf.utils import BadLine, KeyAction, to_cmdline
from .config import common_opts_as_dict, prepare_config_file_for_editing
from .constants import (
RC_ENCRYPTION_PROTOCOL_VERSION, appname, cache_dir, clear_handled_signals,
config_dir, handled_signals, is_macos, is_wayland, kitty_exe,
logo_png_file, supports_primary_selection, website_url
config_dir, handled_signals, is_macos, is_wayland, kitty_exe, logo_png_file,
supports_primary_selection, website_url,
)
from .fast_data_types import (
CLOSE_BEING_CONFIRMED, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT,
@@ -37,15 +37,15 @@ from .fast_data_types import (
IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, ChildMonitor, Color,
EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update,
background_opacity_of, change_background_opacity, change_os_window_state,
cocoa_set_menubar_title, create_os_window,
current_application_quit_request, current_os_window, destroy_global_data,
focus_os_window, get_boss, get_options, get_os_window_size,
global_font_size, mark_os_window_for_close, os_window_font_size,
patch_global_colors, redirect_mouse_handling, ring_bell,
run_with_activation_token, safe_pipe, send_data_to_peer,
set_application_quit_request, set_background_image, set_boss,
set_in_sequence_mode, set_options, set_os_window_size, set_os_window_title,
thread_write, toggle_fullscreen, toggle_maximized, toggle_secure_input
cocoa_set_menubar_title, create_os_window, current_application_quit_request,
current_focused_os_window_id, current_os_window, destroy_global_data,
focus_os_window, get_boss, get_options, get_os_window_size, global_font_size,
last_focused_os_window_id, mark_os_window_for_close, os_window_font_size,
patch_global_colors, redirect_mouse_handling, ring_bell, run_with_activation_token,
safe_pipe, send_data_to_peer, set_application_quit_request, set_background_image,
set_boss, set_in_sequence_mode, set_options, set_os_window_size,
set_os_window_title, thread_write, toggle_fullscreen, toggle_maximized,
toggle_secure_input,
)
from .key_encoding import get_name_to_functional_number_map
from .keys import get_shortcut, shortcut_matches
@@ -57,16 +57,14 @@ from .os_window_size import initial_window_size_func
from .prewarm import PrewarmProcess
from .rgb import color_from_int
from .session import Session, create_sessions, get_os_window_sizing_data
from .tabs import (
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
)
from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
from .types import _T, AsyncResponse, WindowSystemMouseEvent, ac
from .typing import PopenType, TypedDict
from .utils import (
cleanup_ssh_control_masters, func_name, get_editor, get_new_os_window_size,
is_path_in_temp_dir, less_version, log_error, macos_version, open_url,
parse_address_spec, parse_uri_list, platform_window_id, remove_socket_file,
safe_print, single_instance, startup_notification_handler, which
safe_print, single_instance, startup_notification_handler, which,
)
from .window import CommandOutput, CwdRequest, Window
@@ -80,6 +78,8 @@ class OSWindowDict(TypedDict):
id: int
platform_window_id: Optional[int]
is_focused: bool
is_active: bool
last_focused: bool
tabs: List[TabDict]
wm_class: str
wm_name: str
@@ -227,7 +227,7 @@ class VisualSelect:
t = tm.tab_for_id(self.prev_tab_id)
if t is not tm.active_tab and t is not None:
tm.set_active_tab(t)
if current_os_window() != self.prev_os_window_id and self.prev_os_window_id is not None:
if current_focused_os_window_id() != self.prev_os_window_id and self.prev_os_window_id is not None:
focus_os_window(self.prev_os_window_id, True)
@@ -287,9 +287,7 @@ class Boss:
self.mouse_handler: Optional[Callable[[WindowSystemMouseEvent], None]] = None
self.update_keymap()
if is_macos:
from .fast_data_types import (
cocoa_set_notification_activated_callback
)
from .fast_data_types import cocoa_set_notification_activated_callback
cocoa_set_notification_activated_callback(notification_activated)
def update_keymap(self) -> None:
@@ -345,14 +343,15 @@ class Boss:
def list_os_windows(self, self_window: Optional[Window] = None) -> Iterator[OSWindowDict]:
with cached_process_data():
active_tab, active_window = self.active_tab, self.active_window
active_tab_manager = self.active_tab_manager
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'platform_window_id': platform_window_id(os_window_id),
'is_focused': tm is active_tab_manager,
'tabs': list(tm.list_tabs(active_tab, active_window, self_window)),
'is_active': tm is active_tab_manager,
'is_focused': current_focused_os_window_id() == os_window_id,
'last_focused': os_window_id == last_focused_os_window_id(),
'tabs': list(tm.list_tabs(self_window)),
'wm_class': tm.wm_class,
'wm_name': tm.wm_name
}
@@ -377,6 +376,10 @@ class Boss:
return
from .search_query_parser import search
tab = self.active_tab
if current_focused_os_window_id() <= 0:
tm = self.os_window_map.get(last_focused_os_window_id())
if tm is not None:
tab = tm.active_tab
def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]:
return {wid for wid in candidates if self.window_id_map[wid].matches_query(location, query, tab)}
@@ -398,6 +401,8 @@ class Boss:
return self.all_tabs
from .search_query_parser import search
tm = self.active_tab_manager
if current_focused_os_window_id() <= 0:
tm = self.os_window_map.get(last_focused_os_window_id()) or tm
tim = {t.id: t for t in self.all_tabs}
def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]:
@@ -424,7 +429,7 @@ class Boss:
if tab is not self.active_tab:
tm.set_active_tab(tab, for_keep_focus=window.tabref() if for_keep_focus else None)
tab.set_active_window(w, for_keep_focus=window if for_keep_focus else None)
if switch_os_window_if_needed and current_os_window() != os_window_id:
if switch_os_window_if_needed and current_focused_os_window_id() != os_window_id:
focus_os_window(os_window_id, True)
return os_window_id
return None
@@ -534,9 +539,7 @@ class Boss:
return True
def remote_cmd_permission_received(self, pcmd: Dict[str, Any], window_id: int, peer_id: int, choice: str) -> None:
from .remote_control import (
encode_response_for_peer, set_user_password_allowed
)
from .remote_control import encode_response_for_peer, set_user_password_allowed
response: RCResponse = None
window = self.window_id_map.get(window_id)
choice = choice or 'r'
@@ -587,9 +590,7 @@ class Boss:
self.show_error(_('remote_control mapping failed'), tb)
def call_remote_control(self, active_window: Optional[Window], args: Tuple[str, ...]) -> 'ResponseType':
from .rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli
)
from .rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
from .remote_control import parse_rc_args
aa = list(args)
silent = False
@@ -1088,8 +1089,14 @@ class Boss:
@property
def active_tab_manager(self) -> Optional[TabManager]:
os_window_id = current_os_window()
return None if os_window_id is None else self.os_window_map.get(os_window_id)
os_window_id = current_focused_os_window_id()
if os_window_id <= 0:
os_window_id = last_focused_os_window_id()
if os_window_id <= 0:
q = current_os_window()
if q is not None:
os_window_id = q
return self.os_window_map.get(os_window_id)
@property
def active_tab(self) -> Optional[Tab]:
@@ -2248,9 +2255,7 @@ class Boss:
log_error(f'Failed to process update check data {raw!r}, with error: {e}')
def dbus_notification_callback(self, activated: bool, a: int, b: Union[int, str]) -> None:
from .notify import (
dbus_notification_activated, dbus_notification_created
)
from .notify import dbus_notification_activated, dbus_notification_created
if activated:
assert isinstance(b, str)
dbus_notification_activated(a, b)
@@ -2284,9 +2289,7 @@ class Boss:
map f5 set_colors --configured /path/to/some/config/file/colors.conf
''')
def set_colors(self, *args: str) -> None:
from kitty.rc.base import (
PayloadGetter, command_for_name, parse_subcommand_cli
)
from kitty.rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
from kitty.remote_control import parse_rc_args
c = command_for_name('set_colors')
try:

View File

@@ -690,6 +690,11 @@ render_os_window(OSWindow *os_window, unsigned int active_window_id, color_type
float x_ratio = 1, y_ratio = 1;
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window);
br->is_dirty = false;
bool static_live_resize_in_progress = os_window->live_resize.in_progress && OPT(resize_draw_strategy) == RESIZE_DRAW_STATIC;
if (static_live_resize_in_progress) {
x_ratio = (float) os_window->viewport_width / (float) os_window->live_resize.width;
y_ratio = (float) os_window->viewport_height / (float) os_window->live_resize.height;
}
if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(TD.vao_idx, 0, &TD, x_ratio, y_ratio, os_window, true, false, NULL);
for (unsigned int i = 0; i < tab->num_windows; i++) {
Window *w = tab->windows + i;

View File

@@ -6,7 +6,7 @@ import os
import pwd
import sys
from contextlib import suppress
from typing import TYPE_CHECKING, Any, Iterable, NamedTuple, Optional, Set
from typing import TYPE_CHECKING, Any, Iterator, NamedTuple, Optional, Set
from .types import run_once
@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 26, 4)
version: Version = Version(0, 26, 5)
str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat
@@ -219,15 +219,28 @@ def running_in_kitty(set_val: Optional[bool] = None) -> bool:
return bool(getattr(running_in_kitty, 'ans', False))
def list_kitty_resources(package: str = 'kitty') -> Iterable[str]:
from importlib.resources import contents
return contents(package)
def list_kitty_resources(package: str = 'kitty') -> Iterator[str]:
try:
if sys.version_info[:2] < (3, 10):
raise ImportError('importlib.resources.files() doesnt work with frozen builds on python 3.9')
from importlib.resources import files
except ImportError:
from importlib.resources import contents
return iter(contents(package))
else:
return (path.name for path in files(package).iterdir())
def read_kitty_resource(name: str, package_name: str = 'kitty') -> bytes:
from importlib.resources import read_binary
return read_binary(package_name, name)
try:
if sys.version_info[:2] < (3, 10):
raise ImportError('importlib.resources.files() doesnt work with frozen builds on python 3.9')
from importlib.resources import files
except ImportError:
from importlib.resources import read_binary
return read_binary(package_name, name)
else:
return (files(package_name) / name).read_bytes()
def website_url(doc_name: str = '', website: str = 'https://sw.kovidgoyal.net/kitty/') -> str:

View File

@@ -786,6 +786,14 @@ def current_os_window() -> Optional[int]:
pass
def last_focused_os_window_id() -> int:
pass
def current_focused_os_window_id() -> int:
pass
def cocoa_set_menubar_title(title: str) -> None:
pass

View File

@@ -312,14 +312,6 @@ dpi_change_callback(GLFWwindow *w, float x_scale UNUSED, float y_scale UNUSED) {
window->live_resize.last_resize_event_at = monotonic();
global_state.callback_os_window = NULL;
request_tick_callback();
if (global_state.is_wayland) {
// because of the stupidity of Wayland design, the GLFW wayland backend
// will need to swap buffers immediately after a scale change to ensure
// the buffer size is a multiple of the scale. So blank the new buffer to
// ensure we dont leak any unintialized pixels to the screen. The OpenGL viewport
// will already have been resized to its new size in framebuffer_size_callback
blank_os_window(window);
}
}
static void
@@ -1539,8 +1531,8 @@ request_frame_render(OSWindow *w) {
// Some Wayland compositors are too fragile to handle multiple
// render frame requests, see https://github.com/kovidgoyal/kitty/issues/2329
if (w->render_state != RENDER_FRAME_REQUESTED) {
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
w->render_state = RENDER_FRAME_REQUESTED;
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
}
}

View File

@@ -414,6 +414,16 @@ class Splits(Layout):
def pairs_root(self, root: Pair) -> None:
self._pairs_root = root
def remove_windows(self, *windows_to_remove: int) -> None:
root = self.pairs_root
for pair in root.self_and_descendants():
pair.remove_windows(windows_to_remove)
root.collapse_redundant_pairs()
if root.one is None or root.two is None:
q = root.one or root.two
if isinstance(q, Pair):
self.pairs_root = q
def do_layout(self, all_windows: WindowList) -> None:
groups = tuple(all_windows.iter_all_layoutable_groups())
window_count = len(groups)
@@ -421,15 +431,8 @@ class Splits(Layout):
all_present_window_ids = frozenset(w.id for w in groups)
already_placed_window_ids = frozenset(root.all_window_ids())
windows_to_remove = already_placed_window_ids - all_present_window_ids
if windows_to_remove:
for pair in root.self_and_descendants():
pair.remove_windows(windows_to_remove)
root.collapse_redundant_pairs()
if root.one is None or root.two is None:
q = root.one or root.two
if isinstance(q, Pair):
root = self.pairs_root = q
self.remove_windows(*windows_to_remove)
id_window_map = {w.id: w for w in groups}
id_idx_map = {w.id: i for i, w in enumerate(groups)}
windows_to_add = all_present_window_ids - already_placed_window_ids
@@ -552,6 +555,23 @@ class Splits(Layout):
if swap:
pair.one, pair.two = pair.two, pair.one
return True
elif action_name == 'move_to_screen_edge':
args = args or ('left',)
which = args[0]
horizontal = which in ('left', 'right')
wg = all_windows.active_group
if wg is not None:
self.remove_windows(wg.id)
new_root = Pair(horizontal)
if which in ('left', 'top'):
new_root.balanced_add(wg.id)
new_root.two = self.pairs_root
else:
new_root.one = self.pairs_root
new_root.two = wg.id
self.pairs_root = new_root
return True
return None
def layout_state(self) -> Dict[str, Any]:

View File

@@ -81,7 +81,7 @@ def test_spawn() -> None:
q = ctx.Queue()
p = ctx.Process(target=q.put, args=('hello',))
p.start()
x = q.get(timeout=2)
x = q.get(timeout=8)
assert x == 'hello'
p.join()
finally:

View File

@@ -38,7 +38,7 @@ if TYPE_CHECKING:
error_events = select.POLLERR | select.POLLNVAL | select.POLLHUP
TIMEOUT = 15.0 if os.environ.get('CI') == 'true' else 5.0
TIMEOUT = 5.0
def restore_python_signal_handlers() -> None:

View File

@@ -100,7 +100,7 @@ or a name and value, for example, :code:`env:MY_ENV_VAR=2`.
The field :code:`state` matches on the state of the window. Supported states are:
:code:`active`, :code:`focused`, :code:`needs_attention`, :code:`parent_active` and :code:`parent_focused`.
Active windows are the windows that are active in their parent tab. There is only one focused window and it is the
window to which keyboard events are delivered.
window to which keyboard events are delivered. If no window is focused, the last focused window is matched.
Note that you can use the :ref:`kitty @ ls <at-ls>` command to get a list of windows.
'''
@@ -134,7 +134,7 @@ variables are matched.
The field :code:`state` matches on the state of the tab. Supported states are:
:code:`active`, :code:`focused`, :code:`needs_attention`, :code:`parent_active` and :code:`parent_focused`.
Active tabs are the tabs that are active in their parent OS window. There is only one focused tab
and it is the tab to which keyboard events are delivered.
and it is the tab to which keyboard events are delivered. If no tab is focused, the last focused tab is matched.
Note that you can use the :ref:`kitty @ ls <at-ls>` command to get a list of tabs.
'''

View File

@@ -81,7 +81,7 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf
for (TrackCursor *t = track; !t->is_sentinel; t++) {
if (t->is_tracked_line && src_x <= t->x && t->x < src_x + num) {
t->y = dest_y;
t->x = dest_x + (t->x - src_x + 1);
t->x = dest_x + (t->x - src_x + (t->x > 0));
}
}
src_x += num; dest_x += num;

View File

@@ -368,6 +368,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
if (n == NULL) return false;
Py_CLEAR(self->main_linebuf); self->main_linebuf = n;
if (is_main) setup_cursor(cursor);
/* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor.after.x, cursor.after.y, cursor.is_beyond_content); */
setup_cursor(main_saved_cursor);
grman_resize(self->main_grman, self->lines, lines, self->columns, columns);
@@ -396,7 +397,6 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
clear_selection(&self->selections);
clear_selection(&self->url_ranges);
self->last_visited_prompt.is_set = false;
/* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor_x, cursor_y, cursor_is_beyond_content); */
#define S(c, w) c->x = MIN(w.after.x, self->columns - 1); c->y = MIN(w.after.y, self->lines - 1);
S(self->cursor, cursor);
S((&(self->main_savepoint.cursor)), main_saved_cursor);

View File

@@ -12,6 +12,7 @@
#include "window_logo.h"
#define BLEND_ONTO_OPAQUE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blending onto opaque colors
#define BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // blending onto opaque colors with final color having alpha 1
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BLIT_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, NUM_PROGRAMS };
@@ -531,7 +532,7 @@ has_bgimage(OSWindow *w) {
static void
draw_tint(bool premult, Screen *screen, const CellRenderData *crd) {
// On GNOME+Wayland this causes ghosting while rendering. Yet another GNOME bug, does not occur under KDE, Hyprland or Sway.
if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT }
bind_program(TINT_PROGRAM);
color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg).rgb;
#define C(shift) ((((GLfloat)((window_bg >> shift) & 0xFF)) / 255.0f)) * premult_factor
@@ -721,7 +722,6 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWind
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
} else if (OPT(background_tint) > 0) {
BLEND_ONTO_OPAQUE;
draw_tint(false, screen, crd);
BLEND_ONTO_OPAQUE;
}
@@ -758,7 +758,6 @@ static void
draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *os_window, const CellRenderData *crd, const WindowLogoRenderData *wl) {
if (OPT(background_tint) > 0.f) {
glEnable(GL_BLEND);
BLEND_PREMULT;
draw_tint(true, screen, crd);
glDisable(GL_BLEND);
}
@@ -982,7 +981,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
BLEND_ONTO_OPAQUE;
background_opacity = 1.0f;
tint_opacity = OPT(background_tint);
tint_premult = 1.0f;
tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f;
}
if (num_border_rects) {
@@ -1007,6 +1006,10 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
color_type default_bg = (num_visible_windows > 1 && !all_windows_have_same_bg) ? OPT(background) : active_window_bg;
glUniform3f(border_uniform_locations[BORDER_default_bg], CV3(default_bg));
#undef CV3
if (has_bgimage(w)) {
if (w->is_semi_transparent) { BLEND_PREMULT; }
else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT; }
}
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects);
unbind_vertex_array();
unbind_program();

View File

@@ -96,7 +96,7 @@ pt_to_px(double pt, id_type os_window_id) {
OSWindow*
current_os_window() {
current_os_window(void) {
if (global_state.callback_os_window) return global_state.callback_os_window;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
if (global_state.os_windows[i].is_focused) return global_state.os_windows + i;
@@ -104,6 +104,29 @@ current_os_window() {
return global_state.os_windows;
}
static id_type
last_focused_os_window_id(void) {
id_type ans = 0, max_fc_count = 0;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = &global_state.os_windows[i];
if (w->last_focused_counter > max_fc_count) {
ans = w->id; max_fc_count = w->last_focused_counter;
}
}
return ans;
}
static id_type
current_focused_os_window_id(void) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = &global_state.os_windows[i];
if (w->is_focused) { return w->id; }
}
return 0;
}
OSWindow*
os_window_for_kitty_window(id_type kitty_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
@@ -665,6 +688,15 @@ PYWRAP0(next_window_id) {
return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1);
}
PYWRAP0(last_focused_os_window_id) {
return PyLong_FromUnsignedLongLong(last_focused_os_window_id());
}
PYWRAP0(current_focused_os_window_id) {
return PyLong_FromUnsignedLongLong(current_focused_os_window_id());
}
PYWRAP1(handle_for_window_id) {
id_type os_window_id;
PA("K", &os_window_id);
@@ -1266,6 +1298,8 @@ KK5I(add_borders_rect)
static PyMethodDef module_methods[] = {
MW(current_os_window, METH_NOARGS),
MW(next_window_id, METH_NOARGS),
MW(last_focused_os_window_id, METH_NOARGS),
MW(current_focused_os_window_id, METH_NOARGS),
MW(set_options, METH_VARARGS),
MW(get_options, METH_NOARGS),
MW(click_mouse_url, METH_VARARGS),

View File

@@ -2,6 +2,7 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re
from functools import lru_cache, partial, wraps
from string import Formatter as StringFormatter
from typing import (
@@ -189,7 +190,7 @@ class TabAccessor:
safe_builtins = {
'max': max, 'min': min, 'str': str, 'repr': repr, 'abs': abs, 'len': len,
'max': max, 'min': min, 'str': str, 'repr': repr, 'abs': abs, 'len': len, 're': re,
}

View File

@@ -10,8 +10,8 @@ from contextlib import suppress
from operator import attrgetter
from time import monotonic
from typing import (
Any, Deque, Dict, Generator, Iterable, Iterator, List,
NamedTuple, Optional, Sequence, Set, Tuple, Union
Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional,
Sequence, Set, Tuple, Union,
)
from .borders import Border, Borders
@@ -19,11 +19,11 @@ from .child import Child
from .cli_stub import CLIOptions
from .constants import appname, kitty_exe
from .fast_data_types import (
GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE,
add_tab, attach_window, current_os_window, detach_window, get_boss,
get_click_interval, get_options, mark_tab_bar_dirty, next_window_id,
remove_tab, remove_window, ring_bell, set_active_tab, set_active_window,
swap_tabs, sync_os_window_title
GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, add_tab,
attach_window, current_focused_os_window_id, detach_window, get_boss,
get_click_interval, get_options, last_focused_os_window_id, mark_tab_bar_dirty,
next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
set_active_window, swap_tabs, sync_os_window_title,
)
from .layout.base import Layout
from .layout.interface import create_layout_object_for, evict_cached_layouts
@@ -46,6 +46,7 @@ class TabMouseEvent(NamedTuple):
class TabDict(TypedDict):
id: int
is_focused: bool
is_active_tab: bool
title: str
layout: str
layout_state: Dict[str, Any]
@@ -698,9 +699,13 @@ class Tab: # {{{
def move_window_backward(self) -> None:
self.move_window(-1)
def list_windows(self, active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:
def list_windows(self, self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:
active_window = self.active_window
for w in self:
yield w.as_dict(is_focused=w is active_window, is_self=w is self_window)
yield w.as_dict(
is_active_window=w is active_window,
is_focused=w.os_window_id == current_focused_os_window_id() and w is active_window,
is_self=w is self_window)
def matches_query(self, field: str, query: str, active_tab_manager: Optional['TabManager'] = None) -> bool:
if field == 'title':
@@ -724,9 +729,10 @@ class Tab: # {{{
return False
if field == 'state':
if query == 'active':
return active_tab_manager is not None and self is active_tab_manager.active_tab
tm = self.tab_manager_ref()
return tm is not None and self is tm.active_tab
if query == 'focused':
return active_tab_manager is not None and self is active_tab_manager.active_tab and self.os_window_id == current_os_window()
return active_tab_manager is not None and self is active_tab_manager.active_tab and self.os_window_id == last_focused_os_window_id()
if query == 'needs_attention':
for w in self:
if w.needs_attention:
@@ -734,7 +740,7 @@ class Tab: # {{{
if query == 'parent_active':
return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager
if query == 'parent_focused':
return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager and self.os_window_id == current_os_window()
return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager and self.os_window_id == last_focused_os_window_id()
return False
return False
@@ -758,7 +764,7 @@ class Tab: # {{{
self.windows = WindowList(self)
def __repr__(self) -> str:
return f'Tab(title={self.effective_title}, id={hex(id(self))})'
return f'Tab(title={self.effective_title}, id={self.id})'
def make_active(self) -> None:
tm = self.tab_manager_ref()
@@ -934,17 +940,19 @@ class TabManager: # {{{
def __len__(self) -> int:
return len(self.tabs)
def list_tabs(self, active_tab: Optional[Tab], active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[TabDict, None, None]:
def list_tabs(self, self_window: Optional[Window] = None) -> Generator[TabDict, None, None]:
active_tab = self.active_tab
for tab in self:
yield {
'id': tab.id,
'is_focused': tab is active_tab,
'is_focused': tab is active_tab and tab.os_window_id == current_focused_os_window_id(),
'is_active_tab': tab is active_tab,
'title': tab.name or tab.title,
'layout': str(tab.current_layout.name),
'layout_state': tab.current_layout.layout_state(),
'layout_opts': tab.current_layout.layout_opts.serialized(),
'enabled_layouts': tab.enabled_layouts,
'windows': list(tab.list_windows(active_window, self_window)),
'windows': list(tab.list_windows(self_window)),
'active_window_history': list(tab.windows.active_window_history),
}

View File

@@ -36,11 +36,12 @@ from .fast_data_types import (
NUM_UNDERLINE_STYLES, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
STRIKETHROUGH, TINT_PROGRAM, Color, KeyEvent, Screen, add_timer, add_window,
cell_size_for_window, click_mouse_cmd_output, click_mouse_url, compile_program,
current_os_window, encode_key_for_tty, get_boss, get_click_interval, get_options,
init_cell_program, mark_os_window_dirty, mouse_selection,
move_cursor_to_mouse_if_in_prompt, pt_to_px, set_titlebar_color, set_window_logo,
set_window_padding, set_window_render_data, update_ime_position_for_window,
update_window_title, update_window_visibility, wakeup_main_loop,
current_focused_os_window_id, encode_key_for_tty, get_boss, get_click_interval,
get_options, init_cell_program, last_focused_os_window_id, mark_os_window_dirty,
mouse_selection, move_cursor_to_mouse_if_in_prompt, pt_to_px, set_titlebar_color,
set_window_logo, set_window_padding, set_window_render_data,
update_ime_position_for_window, update_window_title, update_window_visibility,
wakeup_main_loop,
)
from .keys import keyboard_mode_name, mod_mask
from .notify import (
@@ -138,6 +139,7 @@ def compile_match_query(exp: str, is_simple: bool = True) -> MatchPatternType:
class WindowDict(TypedDict):
id: int
is_focused: bool
is_active_window: bool
title: str
pid: Optional[int]
cwd: str
@@ -614,10 +616,11 @@ class Window:
def __repr__(self) -> str:
return f'Window(title={self.title}, id={self.id})'
def as_dict(self, is_focused: bool = False, is_self: bool = False) -> WindowDict:
def as_dict(self, is_focused: bool = False, is_self: bool = False, is_active_window: bool = False) -> WindowDict:
return dict(
id=self.id,
is_focused=is_focused,
is_active_window=is_active_window,
title=self.title,
pid=self.child.pid,
cwd=self.child.current_cwd or self.child.cwd,
@@ -705,15 +708,20 @@ class Window:
return False
if field == 'state':
if query == 'active':
return active_tab is not None and self is active_tab.active_window
tab = self.tabref()
return tab is not None and tab.active_window is self
if query == 'focused':
return active_tab is not None and self is active_tab.active_window and current_os_window() == self.os_window_id
return active_tab is not None and self is active_tab.active_window and last_focused_os_window_id() == self.os_window_id
if query == 'needs_attention':
return self.needs_attention
if query == 'parent_active':
return active_tab is not None and self.tabref() is active_tab
tab = self.tabref()
if tab is not None:
tm = tab.tab_manager_ref()
return tm is not None and tm.active_tab is tab
return False
if query == 'parent_focused':
return active_tab is not None and self.tabref() is active_tab and current_os_window() == self.os_window_id
return active_tab is not None and self.tabref() is active_tab and last_focused_os_window_id() == self.os_window_id
return False
pat = compile_match_query(query, field != 'env')
return self.matches(field, pat)
@@ -919,7 +927,7 @@ class Window:
tab = self.tabref()
if tab is not None:
tab.relayout_borders()
elif self.os_window_id == current_os_window():
elif self.os_window_id == current_focused_os_window_id():
# Cancel IME composition after loses focus
update_ime_position_for_window(self.id, False, -1)
@@ -1354,7 +1362,7 @@ class Window:
self.destroyed = True
del self.kitten_result_processors
if hasattr(self, 'screen'):
if self.is_active and self.os_window_id == current_os_window():
if self.is_active and self.os_window_id == current_focused_os_window_id():
# Cancel IME composition when window is destroyed
update_ime_position_for_window(self.id, False, -1)
# Remove cycles so that screen is de-allocated immediately

View File

@@ -4,9 +4,20 @@
import importlib
import os
import shutil
import sys
import unittest
from importlib.resources import contents
from typing import Callable, Generator, NoReturn, Sequence, Set
from typing import Callable, Generator, Iterator, NoReturn, Sequence, Set
def contents(package: str) -> Iterator[str]:
try:
if sys.version_info[:2] < (3, 10):
raise ImportError('importlib.resources.files() doesnt work with frozen builds on python 3.9')
from importlib.resources import files
except ImportError:
from importlib.resources import contents
return iter(contents(package))
return (path.name for path in files(package).iterdir())
def itertests(suite: unittest.TestSuite) -> Generator[unittest.TestCase, None, None]:

View File

@@ -50,7 +50,7 @@ import os, json; from kitty.utils import *; from kitty.fast_data_types import ge
'pid': os.getpid(),
'font_family': get_options().font_family,
'stdin': sys.stdin.read(),
}, indent=2), "ALL_OUTPUT_PRESENT", sep="")"""], cwd=cwd, env=env, stdin_data=stdin_data)
}, indent=2), "ALL_OUTPUT_PRESENT", sep="")"""], cwd=cwd, env=env, stdin_data=stdin_data, timeout=15.0)
self.assertFalse(pty.screen_contents().strip())
p.mark_child_as_ready(child.child_id)
pty.wait_till(lambda: 'ALL_OUTPUT_PRESENT' in pty.screen_contents())
@@ -140,7 +140,7 @@ import os, json; from kitty.utils import *; from kitty.fast_data_types import ge
# macOS does not send SIGCHLD when child is continued
# https://stackoverflow.com/questions/48487935/sigchld-is-sent-on-sigcont-on-linux-but-not-on-macos
p.stdin.close()
p.wait(1)
p.wait(3)
for fd, event in poll.poll(0):
read_signals(signal_read_fd, lambda si: None)
finally:

View File

@@ -332,6 +332,15 @@ class TestScreen(BaseTest):
s.resize(s.lines - 1, s.columns)
self.ae(x_before, s.cursor.x)
s = self.create_screen()
s.draw('abc')
b = s.cursor.x
s.resize(7, s.columns)
self.assertEqual(s.cursor.x, b)
s.cursor.x = 0
s.resize(5, s.columns)
self.assertEqual(s.cursor.x, 0)
def test_scrollback_fill_after_resize(self):
def prepare_screen(content=()):
ans = self.create_screen(options={'scrollback_fill_enlarged_window': True})

View File

@@ -275,13 +275,13 @@ copy --exclude */w.* d1
if 'bzip2' in q:
raise ValueError('Untarring failed with screen contents:\n' + q)
return 'UNTAR_DONE' in q
pty.wait_till(check_untar_or_fail)
pty.wait_till(check_untar_or_fail, timeout=30)
self.assertTrue(os.path.exists(os.path.join(home_dir, '.terminfo/kitty.terminfo')))
if SHELL_INTEGRATION_VALUE != 'enabled':
pty.wait_till(lambda: len(pty.screen_contents().splitlines()) > 1)
pty.wait_till(lambda: len(pty.screen_contents().splitlines()) > 1, timeout=30)
self.assertEqual(pty.screen.cursor.shape, 0)
else:
pty.wait_till(lambda: pty.screen.cursor.shape == CURSOR_BEAM)
pty.wait_till(lambda: pty.screen.cursor.shape == CURSOR_BEAM, timeout=30)
return pty
finally:
with suppress(FileNotFoundError):

View File

@@ -205,7 +205,10 @@ def get_data():
with open(tdir + '/data.sh') as f:
env_vars = f.read()
apply_env_vars(env_vars)
data_dir = os.path.join(HOME, os.environ.pop('KITTY_SSH_KITTEN_DATA_DIR'))
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)

View File

@@ -103,7 +103,10 @@ untar_and_read_env() {
. "$tdir/bootstrap-utils.sh"
. "$tdir/data.sh"
[ -z "$KITTY_SSH_KITTEN_DATA_DIR" ] && die "Failed to read SSH data from tty"
data_dir="$HOME/$KITTY_SSH_KITTEN_DATA_DIR"
case "$KITTY_SSH_KITTEN_DATA_DIR" in
/*) data_dir="$KITTY_SSH_KITTEN_DATA_DIR" ;;
*) data_dir="$HOME/$KITTY_SSH_KITTEN_DATA_DIR"
esac
shell_integration_dir="$data_dir/shell-integration"
unset KITTY_SSH_KITTEN_DATA_DIR
login_shell="$KITTY_LOGIN_SHELL"