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
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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]
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
3
glfw/context.c
vendored
@@ -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
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.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
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_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
2
glfw/wl_platform.h
vendored
@@ -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
37
glfw/wl_text_input.c
vendored
@@ -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
87
glfw/wl_window.c
vendored
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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'''
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
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();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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})
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user