Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72f92b395f | ||
|
|
c9c8eb6e94 | ||
|
|
59ded41f7a | ||
|
|
10ad56885e | ||
|
|
d1eb9340ac | ||
|
|
a856c64104 | ||
|
|
c0b11c5656 | ||
|
|
935c4ded6b | ||
|
|
f5de08d5fa | ||
|
|
9d471782dd | ||
|
|
dea3df66f8 | ||
|
|
ec80a1532c | ||
|
|
93563f1280 | ||
|
|
e309e54002 | ||
|
|
0b3228ab16 | ||
|
|
3f28cf45d4 | ||
|
|
88b829fc9b | ||
|
|
458ac6953f | ||
|
|
a6855bde3c | ||
|
|
f6f2f00c53 | ||
|
|
00324c96fc | ||
|
|
2435a8ccfd | ||
|
|
de122ed727 | ||
|
|
c2ead407ae | ||
|
|
16b322616a | ||
|
|
e0b4c7edc5 | ||
|
|
a8ab4eaf23 | ||
|
|
3847837bd0 | ||
|
|
1747bbbbcb | ||
|
|
90e80477e9 | ||
|
|
4f90110a7c | ||
|
|
28c616d3ff | ||
|
|
f5126c664c | ||
|
|
0f340086c0 | ||
|
|
5c34f16ab4 | ||
|
|
03fe928e3f | ||
|
|
7fd119d0eb |
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
3
glfw/context.c
vendored
@@ -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
1
glfw/egl_context.c
vendored
@@ -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
3
glfw/egl_context.h
vendored
@@ -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
2
glfw/wl_platform.h
vendored
@@ -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
37
glfw/wl_text_input.c
vendored
@@ -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
87
glfw/wl_window.c
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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'''
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
10
kitty/glfw.c
10
kitty/glfw.c
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
'''
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user