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

View File

@@ -220,7 +220,7 @@ def create_tarfile(env, compression_level='9'):
print('Compressing archive...') print('Compressing archive...')
ans = f'{dist.rpartition(".")[0]}.txz' ans = f'{dist.rpartition(".")[0]}.txz'
start_time = time.time() 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 secs = time.time() - start_time
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60)) print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
os.rename(f'{dist}.xz', ans) 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 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] 0.26.4 [2022-10-17]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -277,7 +277,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
from kitty.rc.base import ( from kitty.rc.base import (
RemoteCommand, all_command_names, command_for_name 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: def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
p(name) 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+right move_window right
map shift+down move_window down 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 # Switch focus to the neighboring window in the indicated direction
map ctrl+left neighboring_window left map ctrl+left neighboring_window left
map ctrl+right neighboring_window right 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); window->context.swapBuffers(window);
#ifdef _GLFW_WAYLAND
_glfwWaylandAfterBufferSwap(window);
#endif
} }
GLFWAPI void glfwSwapInterval(int interval) 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.SwapBuffers, _glfw.egl.handle, "eglSwapBuffers");
glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval"); glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval");
glfw_dlsym(_glfw.egl.QueryString, _glfw.egl.handle, "eglQueryString"); 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"); glfw_dlsym(_glfw.egl.GetProcAddress, _glfw.egl.handle, "eglGetProcAddress");
if (!_glfw.egl.GetConfigAttrib || 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_eglSwapBuffers)(EGLDisplay,EGLSurface);
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint); typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint);
typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(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*); typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib #define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
#define eglGetConfigs _glfw.egl.GetConfigs #define eglGetConfigs _glfw.egl.GetConfigs
@@ -149,6 +150,7 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
#define eglSwapBuffers _glfw.egl.SwapBuffers #define eglSwapBuffers _glfw.egl.SwapBuffers
#define eglSwapInterval _glfw.egl.SwapInterval #define eglSwapInterval _glfw.egl.SwapInterval
#define eglQueryString _glfw.egl.QueryString #define eglQueryString _glfw.egl.QueryString
#define eglQuerySurface _glfw.egl.QuerySurface
#define eglGetProcAddress _glfw.egl.GetProcAddress #define eglGetProcAddress _glfw.egl.GetProcAddress
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*); typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
@@ -211,6 +213,7 @@ typedef struct _GLFWlibraryEGL
PFN_eglSwapBuffers SwapBuffers; PFN_eglSwapBuffers SwapBuffers;
PFN_eglSwapInterval SwapInterval; PFN_eglSwapInterval SwapInterval;
PFN_eglQueryString QueryString; PFN_eglQueryString QueryString;
PFN_eglQuerySurface QuerySurface;
PFN_eglGetProcAddress GetProcAddress; PFN_eglGetProcAddress GetProcAddress;
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT; PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;

2
glfw/wl_platform.h vendored
View File

@@ -152,6 +152,7 @@ typedef struct _GLFWwindowWayland
bool hovered; bool hovered;
bool transparent; bool transparent;
struct wl_surface* surface; struct wl_surface* surface;
bool waiting_for_swap_to_commit;
struct wl_egl_window* native; struct wl_egl_window* native;
struct wl_callback* callback; struct wl_callback* callback;
@@ -369,6 +370,7 @@ typedef struct _GLFWcursorWayland
void _glfwAddOutputWayland(uint32_t name, uint32_t version); void _glfwAddOutputWayland(uint32_t name, uint32_t version);
void _glfwWaylandAfterBufferSwap(_GLFWwindow *window);
void _glfwSetupWaylandDataDevice(void); void _glfwSetupWaylandDataDevice(void);
void _glfwSetupWaylandPrimarySelectionDevice(void); void _glfwSetupWaylandPrimarySelectionDevice(void);
void animateCursorImage(id_type timer_id, void *data); 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 "internal.h"
#include "wayland-text-input-unstable-v3-client-protocol.h" #include "wayland-text-input-unstable-v3-client-protocol.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__); #define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
static struct zwp_text_input_v3* text_input; static struct zwp_text_input_v3* text_input;
static struct zwp_text_input_manager_v3* text_input_manager; static struct zwp_text_input_manager_v3* text_input_manager;
static char *pending_pre_edit = NULL; static char *pending_pre_edit = NULL;
static char *current_pre_edit = NULL;
static char *pending_commit = 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; uint32_t commit_serial = 0;
static void commit(void) { 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); if (serial > commit_serial) _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
return; 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; free(pending_pre_edit); pending_pre_edit = NULL;
} else { } else {
// Clear pre-edit text free(current_pre_edit);
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT); 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) { if (pending_commit) {
send_text(pending_commit, GLFW_IME_COMMIT_TEXT); 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); if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
text_input = NULL; text_input_manager = NULL; text_input = NULL; text_input_manager = NULL;
free(pending_pre_edit); pending_pre_edit = NULL; free(pending_pre_edit); pending_pre_edit = NULL;
free(current_pre_edit); current_pre_edit = NULL;
free(pending_commit); pending_commit = 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_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); zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
} else { } else {
if (pending_pre_edit) { free(pending_pre_edit); pending_pre_edit = NULL;
if (current_pre_edit) {
// Clear pre-edit text // Clear pre-edit text
send_text(NULL, GLFW_IME_PREEDIT_CHANGED); 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) { if (pending_commit) {
free(pending_commit); pending_commit = NULL; free(pending_commit); pending_commit = NULL;
@@ -161,9 +174,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
case GLFW_IME_UPDATE_CURSOR_POSITION: { case GLFW_IME_UPDATE_CURSOR_POSITION: {
const int scale = w->wl.scale; 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; 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); if (left != last_cursor_left || top != last_cursor_top || width != last_cursor_width || height != last_cursor_height) {
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height); last_cursor_left = left;
commit(); 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; break;
} }

87
glfw/wl_window.c vendored
View File

@@ -251,6 +251,15 @@ static bool checkScaleChange(_GLFWwindow* window)
return false; 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. // Makes the surface considered as XRGB instead of ARGB.
static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface) 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_region_add(region, 0, 0, window->wl.width, window->wl.height);
wl_surface_set_opaque_region(window->wl.surface, region); 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); 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); 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); wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
if (!window->wl.transparent) setOpaqueRegion(window, false); if (!window->wl.transparent) setOpaqueRegion(window, false);
window->wl.waiting_for_swap_to_commit = true;
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight); _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* static const char*
clipboard_mime(void) { clipboard_mime(void) {
@@ -301,9 +321,9 @@ clipboard_mime(void) {
} }
static bool 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; bool size_changed = width != window->wl.width || height != window->wl.height;
*scale_changed = checkScaleChange(window); bool scale_changed = checkScaleChange(window);
if (size_changed) { if (size_changed) {
_glfwInputWindowSize(window, width, height); _glfwInputWindowSize(window, width, height);
@@ -311,7 +331,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
resizeFramebuffer(window); resizeFramebuffer(window);
} }
if (*scale_changed) { if (scale_changed) {
debug("Scale changed to %d in dispatchChangesAfterConfigure\n", window->wl.scale); debug("Scale changed to %d in dispatchChangesAfterConfigure\n", window->wl.scale);
if (!size_changed) resizeFramebuffer(window); if (!size_changed) resizeFramebuffer(window);
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale); _glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
@@ -319,7 +339,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
_glfwInputWindowDamage(window); _glfwInputWindowDamage(window);
return size_changed || *scale_changed; return size_changed || scale_changed;
} }
static void static void
@@ -568,6 +588,15 @@ static void xdgSurfaceHandleConfigure(void* data,
struct xdg_surface* surface, struct xdg_surface* surface,
uint32_t serial) 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; _GLFWwindow* window = data;
xdg_surface_ack_configure(surface, serial); xdg_surface_ack_configure(surface, serial);
if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) { if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) {
@@ -600,11 +629,10 @@ static void xdgSurfaceHandleConfigure(void* data,
} }
bool resized = false; bool resized = false;
bool scale_changed = false;
if (window->wl.pending_state) { if (window->wl.pending_state) {
int width = window->wl.pending.width, height = window->wl.pending.height; int width = window->wl.pending.width, height = window->wl.pending.height;
set_csd_window_geometry(window, &width, &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) { if (window->wl.decorations.serverSide) {
free_csd_surfaces(window); free_csd_surfaces(window);
} else { } else {
@@ -614,14 +642,7 @@ static void xdgSurfaceHandleConfigure(void* data,
} }
inform_compositor_of_window_geometry(window, "configure"); inform_compositor_of_window_geometry(window, "configure");
commit_window_surface_if_safe(window);
// 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);
window->wl.pending_state = 0; 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; window->wl.width = w; window->wl.height = h;
resizeFramebuffer(window); resizeFramebuffer(window);
ensure_csd_resources(window); ensure_csd_resources(window);
wl_surface_commit(window->wl.surface); commit_window_surface_if_safe(window);
inform_compositor_of_window_geometry(window, "SetWindowSize"); inform_compositor_of_window_geometry(window, "SetWindowSize");
} }
} }
@@ -1038,7 +1059,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
maxwidth = maxheight = 0; maxwidth = maxheight = 0;
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight); xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight); 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 else
wl_surface_set_input_region(window->wl.surface, 0); 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) 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( zwp_locked_pointer_v1_set_cursor_position_hint(
window->wl.pointerLock.lockedPointer, window->wl.pointerLock.lockedPointer,
wl_fixed_from_double(x), wl_fixed_from_double(y)); 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); 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 = { static const struct wl_data_source_listener data_source_listener = {
.send = _glfwSendClipboardText, .send = _glfwSendClipboardText,
.cancelled = data_source_canceled, .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 = { 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; _GLFWwindow* window = (_GLFWwindow*) handle;
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw }; 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); if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
window->wl.frameCallbackData.id = id; if (window->wl.waiting_for_swap_to_commit) {
window->wl.frameCallbackData.callback = callback; callback(id);
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface); window->wl.frameCallbackData.id = 0;
if (window->wl.frameCallbackData.current_wl_callback) { window->wl.frameCallbackData.callback = NULL;
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window); window->wl.frameCallbackData.current_wl_callback = NULL;
wl_surface_commit(window->wl.surface); } 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. 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 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 installed. Relative paths are resolved with respect to :code:`$HOME`.
paths that resolve to a location outside the HOME are not allowed.
''') ''')
opt('+copy', '', option_type='copy', add_to_default=False, long_text=f''' 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 # generated by gen-config.py DO NOT edit
import typing 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 from kitty.conf.utils import merge_dicts, to_bool
@@ -39,7 +39,7 @@ class Parser:
ans['login_shell'] = str(val) ans['login_shell'] = str(val)
def remote_dir(self, val: str, ans: typing.Dict[str, typing.Any]) -> None: 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: def remote_kitty(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
val = val.lower() val = val.lower()

View File

@@ -1,7 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
import posixpath
from typing import Any, Dict, Iterable, Optional, Tuple from typing import Any, Dict, Iterable, Optional, Tuple
from ..copy import CopyInstruction, parse_copy_instructions 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_' 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]]: def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]:
val = val.strip() val = val.strip()
if val: if val:

View File

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

View File

@@ -6,7 +6,7 @@ import os
import pwd import pwd
import sys import sys
from contextlib import suppress 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 from .types import run_once
@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty' appname: str = 'kitty'
kitty_face = '🐱' kitty_face = '🐱'
version: Version = Version(0, 26, 4) version: Version = Version(0, 26, 5)
str_version: str = '.'.join(map(str, version)) str_version: str = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos: bool = 'darwin' in _plat 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)) return bool(getattr(running_in_kitty, 'ans', False))
def list_kitty_resources(package: str = 'kitty') -> Iterable[str]: def list_kitty_resources(package: str = 'kitty') -> Iterator[str]:
from importlib.resources import contents try:
return contents(package) 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: def read_kitty_resource(name: str, package_name: str = 'kitty') -> bytes:
from importlib.resources import read_binary try:
if sys.version_info[:2] < (3, 10):
return read_binary(package_name, name) 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: 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 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: def cocoa_set_menubar_title(title: str) -> None:
pass 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(); window->live_resize.last_resize_event_at = monotonic();
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
request_tick_callback(); 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 static void
@@ -1539,8 +1531,8 @@ request_frame_render(OSWindow *w) {
// Some Wayland compositors are too fragile to handle multiple // Some Wayland compositors are too fragile to handle multiple
// render frame requests, see https://github.com/kovidgoyal/kitty/issues/2329 // render frame requests, see https://github.com/kovidgoyal/kitty/issues/2329
if (w->render_state != RENDER_FRAME_REQUESTED) { if (w->render_state != RENDER_FRAME_REQUESTED) {
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
w->render_state = RENDER_FRAME_REQUESTED; 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: def pairs_root(self, root: Pair) -> None:
self._pairs_root = root 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: def do_layout(self, all_windows: WindowList) -> None:
groups = tuple(all_windows.iter_all_layoutable_groups()) groups = tuple(all_windows.iter_all_layoutable_groups())
window_count = len(groups) window_count = len(groups)
@@ -421,15 +431,8 @@ class Splits(Layout):
all_present_window_ids = frozenset(w.id for w in groups) all_present_window_ids = frozenset(w.id for w in groups)
already_placed_window_ids = frozenset(root.all_window_ids()) already_placed_window_ids = frozenset(root.all_window_ids())
windows_to_remove = already_placed_window_ids - all_present_window_ids windows_to_remove = already_placed_window_ids - all_present_window_ids
if windows_to_remove: if windows_to_remove:
for pair in root.self_and_descendants(): self.remove_windows(*windows_to_remove)
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
id_window_map = {w.id: w for w in groups} id_window_map = {w.id: w for w in groups}
id_idx_map = {w.id: i for i, w in enumerate(groups)} id_idx_map = {w.id: i for i, w in enumerate(groups)}
windows_to_add = all_present_window_ids - already_placed_window_ids windows_to_add = all_present_window_ids - already_placed_window_ids
@@ -552,6 +555,23 @@ class Splits(Layout):
if swap: if swap:
pair.one, pair.two = pair.two, pair.one pair.one, pair.two = pair.two, pair.one
return True 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 return None
def layout_state(self) -> Dict[str, Any]: def layout_state(self) -> Dict[str, Any]:

View File

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

View File

@@ -38,7 +38,7 @@ if TYPE_CHECKING:
error_events = select.POLLERR | select.POLLNVAL | select.POLLHUP 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: 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: 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`. :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 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. 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: 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`. :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 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. 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++) { for (TrackCursor *t = track; !t->is_sentinel; t++) {
if (t->is_tracked_line && src_x <= t->x && t->x < src_x + num) { if (t->is_tracked_line && src_x <= t->x && t->x < src_x + num) {
t->y = dest_y; 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; 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; if (n == NULL) return false;
Py_CLEAR(self->main_linebuf); self->main_linebuf = n; Py_CLEAR(self->main_linebuf); self->main_linebuf = n;
if (is_main) setup_cursor(cursor); 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); setup_cursor(main_saved_cursor);
grman_resize(self->main_grman, self->lines, lines, self->columns, columns); 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->selections);
clear_selection(&self->url_ranges); clear_selection(&self->url_ranges);
self->last_visited_prompt.is_set = false; 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); #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->cursor, cursor);
S((&(self->main_savepoint.cursor)), main_saved_cursor); S((&(self->main_savepoint.cursor)), main_saved_cursor);

View File

@@ -12,6 +12,7 @@
#include "window_logo.h" #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 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 #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 }; 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 static void
draw_tint(bool premult, Screen *screen, const CellRenderData *crd) { 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); 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; 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 #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); glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
} else if (OPT(background_tint) > 0) { } else if (OPT(background_tint) > 0) {
BLEND_ONTO_OPAQUE;
draw_tint(false, screen, crd); draw_tint(false, screen, crd);
BLEND_ONTO_OPAQUE; 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) { 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) { if (OPT(background_tint) > 0.f) {
glEnable(GL_BLEND); glEnable(GL_BLEND);
BLEND_PREMULT;
draw_tint(true, screen, crd); draw_tint(true, screen, crd);
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
@@ -982,7 +981,7 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
BLEND_ONTO_OPAQUE; BLEND_ONTO_OPAQUE;
background_opacity = 1.0f; background_opacity = 1.0f;
tint_opacity = OPT(background_tint); tint_opacity = OPT(background_tint);
tint_premult = 1.0f; tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f;
} }
if (num_border_rects) { 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; 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)); glUniform3f(border_uniform_locations[BORDER_default_bg], CV3(default_bg));
#undef CV3 #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); glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects);
unbind_vertex_array(); unbind_vertex_array();
unbind_program(); unbind_program();

View File

@@ -96,7 +96,7 @@ pt_to_px(double pt, id_type os_window_id) {
OSWindow* OSWindow*
current_os_window() { current_os_window(void) {
if (global_state.callback_os_window) return global_state.callback_os_window; if (global_state.callback_os_window) return global_state.callback_os_window;
for (size_t i = 0; i < global_state.num_os_windows; i++) { 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; 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; 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* OSWindow*
os_window_for_kitty_window(id_type kitty_window_id) { os_window_for_kitty_window(id_type kitty_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) { 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); 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) { PYWRAP1(handle_for_window_id) {
id_type os_window_id; id_type os_window_id;
PA("K", &os_window_id); PA("K", &os_window_id);
@@ -1266,6 +1298,8 @@ KK5I(add_borders_rect)
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
MW(current_os_window, METH_NOARGS), MW(current_os_window, METH_NOARGS),
MW(next_window_id, 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(set_options, METH_VARARGS),
MW(get_options, METH_NOARGS), MW(get_options, METH_NOARGS),
MW(click_mouse_url, METH_VARARGS), MW(click_mouse_url, METH_VARARGS),

View File

@@ -2,6 +2,7 @@
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os import os
import re
from functools import lru_cache, partial, wraps from functools import lru_cache, partial, wraps
from string import Formatter as StringFormatter from string import Formatter as StringFormatter
from typing import ( from typing import (
@@ -189,7 +190,7 @@ class TabAccessor:
safe_builtins = { 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 operator import attrgetter
from time import monotonic from time import monotonic
from typing import ( from typing import (
Any, Deque, Dict, Generator, Iterable, Iterator, List, Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional,
NamedTuple, Optional, Sequence, Set, Tuple, Union Sequence, Set, Tuple, Union,
) )
from .borders import Border, Borders from .borders import Border, Borders
@@ -19,11 +19,11 @@ from .child import Child
from .cli_stub import CLIOptions from .cli_stub import CLIOptions
from .constants import appname, kitty_exe from .constants import appname, kitty_exe
from .fast_data_types import ( from .fast_data_types import (
GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, add_tab,
add_tab, attach_window, current_os_window, detach_window, get_boss, attach_window, current_focused_os_window_id, detach_window, get_boss,
get_click_interval, get_options, mark_tab_bar_dirty, next_window_id, get_click_interval, get_options, last_focused_os_window_id, mark_tab_bar_dirty,
remove_tab, remove_window, ring_bell, set_active_tab, set_active_window, next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
swap_tabs, sync_os_window_title set_active_window, swap_tabs, sync_os_window_title,
) )
from .layout.base import Layout from .layout.base import Layout
from .layout.interface import create_layout_object_for, evict_cached_layouts from .layout.interface import create_layout_object_for, evict_cached_layouts
@@ -46,6 +46,7 @@ class TabMouseEvent(NamedTuple):
class TabDict(TypedDict): class TabDict(TypedDict):
id: int id: int
is_focused: bool is_focused: bool
is_active_tab: bool
title: str title: str
layout: str layout: str
layout_state: Dict[str, Any] layout_state: Dict[str, Any]
@@ -698,9 +699,13 @@ class Tab: # {{{
def move_window_backward(self) -> None: def move_window_backward(self) -> None:
self.move_window(-1) 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: 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: def matches_query(self, field: str, query: str, active_tab_manager: Optional['TabManager'] = None) -> bool:
if field == 'title': if field == 'title':
@@ -724,9 +729,10 @@ class Tab: # {{{
return False return False
if field == 'state': if field == 'state':
if query == 'active': 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': 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': if query == 'needs_attention':
for w in self: for w in self:
if w.needs_attention: if w.needs_attention:
@@ -734,7 +740,7 @@ class Tab: # {{{
if query == 'parent_active': if query == 'parent_active':
return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager
if query == 'parent_focused': 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
return False return False
@@ -758,7 +764,7 @@ class Tab: # {{{
self.windows = WindowList(self) self.windows = WindowList(self)
def __repr__(self) -> str: 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: def make_active(self) -> None:
tm = self.tab_manager_ref() tm = self.tab_manager_ref()
@@ -934,17 +940,19 @@ class TabManager: # {{{
def __len__(self) -> int: def __len__(self) -> int:
return len(self.tabs) 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: for tab in self:
yield { yield {
'id': tab.id, '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, 'title': tab.name or tab.title,
'layout': str(tab.current_layout.name), 'layout': str(tab.current_layout.name),
'layout_state': tab.current_layout.layout_state(), 'layout_state': tab.current_layout.layout_state(),
'layout_opts': tab.current_layout.layout_opts.serialized(), 'layout_opts': tab.current_layout.layout_opts.serialized(),
'enabled_layouts': tab.enabled_layouts, '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), '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, NUM_UNDERLINE_STYLES, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
STRIKETHROUGH, TINT_PROGRAM, Color, KeyEvent, Screen, add_timer, add_window, STRIKETHROUGH, TINT_PROGRAM, Color, KeyEvent, Screen, add_timer, add_window,
cell_size_for_window, click_mouse_cmd_output, click_mouse_url, compile_program, 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, current_focused_os_window_id, encode_key_for_tty, get_boss, get_click_interval,
init_cell_program, mark_os_window_dirty, mouse_selection, get_options, init_cell_program, last_focused_os_window_id, mark_os_window_dirty,
move_cursor_to_mouse_if_in_prompt, pt_to_px, set_titlebar_color, set_window_logo, mouse_selection, move_cursor_to_mouse_if_in_prompt, pt_to_px, set_titlebar_color,
set_window_padding, set_window_render_data, update_ime_position_for_window, set_window_logo, set_window_padding, set_window_render_data,
update_window_title, update_window_visibility, wakeup_main_loop, update_ime_position_for_window, update_window_title, update_window_visibility,
wakeup_main_loop,
) )
from .keys import keyboard_mode_name, mod_mask from .keys import keyboard_mode_name, mod_mask
from .notify import ( from .notify import (
@@ -138,6 +139,7 @@ def compile_match_query(exp: str, is_simple: bool = True) -> MatchPatternType:
class WindowDict(TypedDict): class WindowDict(TypedDict):
id: int id: int
is_focused: bool is_focused: bool
is_active_window: bool
title: str title: str
pid: Optional[int] pid: Optional[int]
cwd: str cwd: str
@@ -614,10 +616,11 @@ class Window:
def __repr__(self) -> str: def __repr__(self) -> str:
return f'Window(title={self.title}, id={self.id})' 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( return dict(
id=self.id, id=self.id,
is_focused=is_focused, is_focused=is_focused,
is_active_window=is_active_window,
title=self.title, title=self.title,
pid=self.child.pid, pid=self.child.pid,
cwd=self.child.current_cwd or self.child.cwd, cwd=self.child.current_cwd or self.child.cwd,
@@ -705,15 +708,20 @@ class Window:
return False return False
if field == 'state': if field == 'state':
if query == 'active': 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': 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': if query == 'needs_attention':
return self.needs_attention return self.needs_attention
if query == 'parent_active': 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': 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 return False
pat = compile_match_query(query, field != 'env') pat = compile_match_query(query, field != 'env')
return self.matches(field, pat) return self.matches(field, pat)
@@ -919,7 +927,7 @@ class Window:
tab = self.tabref() tab = self.tabref()
if tab is not None: if tab is not None:
tab.relayout_borders() 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 # Cancel IME composition after loses focus
update_ime_position_for_window(self.id, False, -1) update_ime_position_for_window(self.id, False, -1)
@@ -1354,7 +1362,7 @@ class Window:
self.destroyed = True self.destroyed = True
del self.kitten_result_processors del self.kitten_result_processors
if hasattr(self, 'screen'): 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 # Cancel IME composition when window is destroyed
update_ime_position_for_window(self.id, False, -1) update_ime_position_for_window(self.id, False, -1)
# Remove cycles so that screen is de-allocated immediately # Remove cycles so that screen is de-allocated immediately

View File

@@ -4,9 +4,20 @@
import importlib import importlib
import os import os
import shutil import shutil
import sys
import unittest import unittest
from importlib.resources import contents from typing import Callable, Generator, Iterator, NoReturn, Sequence, Set
from typing import Callable, Generator, 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]: 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(), 'pid': os.getpid(),
'font_family': get_options().font_family, 'font_family': get_options().font_family,
'stdin': sys.stdin.read(), '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()) self.assertFalse(pty.screen_contents().strip())
p.mark_child_as_ready(child.child_id) p.mark_child_as_ready(child.child_id)
pty.wait_till(lambda: 'ALL_OUTPUT_PRESENT' in pty.screen_contents()) 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 # 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 # https://stackoverflow.com/questions/48487935/sigchld-is-sent-on-sigcont-on-linux-but-not-on-macos
p.stdin.close() p.stdin.close()
p.wait(1) p.wait(3)
for fd, event in poll.poll(0): for fd, event in poll.poll(0):
read_signals(signal_read_fd, lambda si: None) read_signals(signal_read_fd, lambda si: None)
finally: finally:

View File

@@ -332,6 +332,15 @@ class TestScreen(BaseTest):
s.resize(s.lines - 1, s.columns) s.resize(s.lines - 1, s.columns)
self.ae(x_before, s.cursor.x) 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 test_scrollback_fill_after_resize(self):
def prepare_screen(content=()): def prepare_screen(content=()):
ans = self.create_screen(options={'scrollback_fill_enlarged_window': True}) ans = self.create_screen(options={'scrollback_fill_enlarged_window': True})

View File

@@ -275,13 +275,13 @@ copy --exclude */w.* d1
if 'bzip2' in q: if 'bzip2' in q:
raise ValueError('Untarring failed with screen contents:\n' + q) raise ValueError('Untarring failed with screen contents:\n' + q)
return 'UNTAR_DONE' in 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'))) self.assertTrue(os.path.exists(os.path.join(home_dir, '.terminfo/kitty.terminfo')))
if SHELL_INTEGRATION_VALUE != 'enabled': 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) self.assertEqual(pty.screen.cursor.shape, 0)
else: 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 return pty
finally: finally:
with suppress(FileNotFoundError): with suppress(FileNotFoundError):

View File

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

View File

@@ -103,7 +103,10 @@ untar_and_read_env() {
. "$tdir/bootstrap-utils.sh" . "$tdir/bootstrap-utils.sh"
. "$tdir/data.sh" . "$tdir/data.sh"
[ -z "$KITTY_SSH_KITTEN_DATA_DIR" ] && die "Failed to read SSH data from tty" [ -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" shell_integration_dir="$data_dir/shell-integration"
unset KITTY_SSH_KITTEN_DATA_DIR unset KITTY_SSH_KITTEN_DATA_DIR
login_shell="$KITTY_LOGIN_SHELL" login_shell="$KITTY_LOGIN_SHELL"