Compare commits
85 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 | ||
|
|
55b53bcac6 | ||
|
|
57d044861f | ||
|
|
aab149366d | ||
|
|
896f93822d | ||
|
|
6890e265b6 | ||
|
|
48a4edc199 | ||
|
|
d63be90a7f | ||
|
|
6af020e4ef | ||
|
|
ca24e9ca67 | ||
|
|
fbfaefd72a | ||
|
|
94b3776066 | ||
|
|
15e4be9051 | ||
|
|
ae8f3de070 | ||
|
|
c8d18ffe26 | ||
|
|
4d80427908 | ||
|
|
0e69324469 | ||
|
|
cf22729dfa | ||
|
|
1402c5bbfa | ||
|
|
f0f0c8f4fe | ||
|
|
96faac95db | ||
|
|
08041415d1 | ||
|
|
647b18d345 | ||
|
|
a116e3cadd | ||
|
|
52c9b5f5b8 | ||
|
|
bbc9f588f1 | ||
|
|
13a3c6b5b2 | ||
|
|
fc15b20f24 | ||
|
|
cd8f2e5cc6 | ||
|
|
267798a277 | ||
|
|
921ac12e90 | ||
|
|
901e00cab1 | ||
|
|
d0769f3979 | ||
|
|
7e4dd8cf1e | ||
|
|
5ea0519f62 | ||
|
|
a1029418f8 | ||
|
|
98eacb2067 | ||
|
|
2cef9abbd3 | ||
|
|
0fa158c809 | ||
|
|
c54a6b398b | ||
|
|
27ea367123 | ||
|
|
42178a4570 | ||
|
|
e98ecc1c4f | ||
|
|
0d64246209 | ||
|
|
a0495219ab | ||
|
|
9d98aa3159 | ||
|
|
5c82f4beca | ||
|
|
13539bd8c6 | ||
|
|
26b8ab9adf | ||
|
|
7fd119d0eb |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -6,6 +6,9 @@ env:
|
||||
LC_ALL: en_US.UTF-8
|
||||
LANG: en_US.UTF-8
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
name: Linux (python=${{ matrix.pyver }} cc=${{ matrix.cc }} sanitize=${{ matrix.sanitize }})
|
||||
@@ -24,11 +27,11 @@ jobs:
|
||||
sanitize: 0
|
||||
|
||||
- python: b
|
||||
pyver: "3.9"
|
||||
pyver: "3.11"
|
||||
sanitize: 1
|
||||
|
||||
- python: c
|
||||
pyver: "3.10"
|
||||
pyver: "3.9"
|
||||
sanitize: 1
|
||||
|
||||
|
||||
@@ -73,7 +76,7 @@ jobs:
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Install build-only deps
|
||||
run: pip install -r docs/requirements.txt flake8 mypy types-requests types-docutils
|
||||
@@ -125,6 +128,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0 # needed for :commit: docs role
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
|
||||
- name: Build kitty
|
||||
run: python3 .github/workflows/ci.py build
|
||||
|
||||
|
||||
7
.github/workflows/codeql-analysis.yml
vendored
7
.github/workflows/codeql-analysis.yml
vendored
@@ -9,9 +9,16 @@ on:
|
||||
schedule:
|
||||
- cron: '0 22 * * 5'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
security-events: write # to upload SARIF results (github/codeql-action/analyze)
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
@@ -220,7 +220,7 @@ def create_tarfile(env, compression_level='9'):
|
||||
print('Compressing archive...')
|
||||
ans = f'{dist.rpartition(".")[0]}.txz'
|
||||
start_time = time.time()
|
||||
subprocess.check_call(['xz', '--threads=0', '-f', f'-{compression_level}', dist])
|
||||
subprocess.check_call(['xz', '--verbose', '--threads=0', '-f', f'-{compression_level}', dist])
|
||||
secs = time.time() - start_time
|
||||
print('Compressed in {} minutes {} seconds'.format(secs // 60, secs % 60))
|
||||
os.rename(f'{dist}.xz', ans)
|
||||
|
||||
2
docs/_static/custom.css
vendored
2
docs/_static/custom.css
vendored
@@ -10,7 +10,7 @@
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
max-height: 128px;
|
||||
height: 128px;
|
||||
}
|
||||
|
||||
.major-features li {
|
||||
|
||||
@@ -85,7 +85,7 @@ Customizing the installation
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
_kitty_install_cmd \\
|
||||
_kitty_install_cmd \
|
||||
installer=nightly
|
||||
|
||||
If you want to install it in parallel to the released kitty specify a
|
||||
@@ -93,14 +93,14 @@ Customizing the installation
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
_kitty_install_cmd \\
|
||||
_kitty_install_cmd \
|
||||
installer=nightly dest=/some/other/location
|
||||
|
||||
* You can specify a different install location, with ``dest``:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
_kitty_install_cmd \\
|
||||
_kitty_install_cmd \
|
||||
dest=/some/other/location
|
||||
|
||||
* You can tell the installer not to launch |kitty| after installing it with
|
||||
@@ -108,14 +108,14 @@ Customizing the installation
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
_kitty_install_cmd \\
|
||||
_kitty_install_cmd \
|
||||
launch=n
|
||||
|
||||
* You can use a previously downloaded dmg/tarball, with ``installer``:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
_kitty_install_cmd \\
|
||||
_kitty_install_cmd \
|
||||
installer=/path/to/dmg or tarball
|
||||
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ While |kitty| does use Python, it is not a traditional Python package, so please
|
||||
do not install it in site-packages.
|
||||
Instead run::
|
||||
|
||||
python3 setup.py linux-package
|
||||
make linux-package
|
||||
|
||||
This will install |kitty| into the directory :file:`linux-package`. You can run
|
||||
|kitty| with :file:`linux-package/bin/kitty`. All the files needed to run kitty
|
||||
|
||||
@@ -35,6 +35,49 @@ mouse anywhere in the current command to move the cursor there. See
|
||||
Detailed list of changes
|
||||
-------------------------------------
|
||||
|
||||
0.26.5 [2022-11-07]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- Splits layout: Add a new mappable action to move the active window to the screen edge (:iss:`5643`)
|
||||
|
||||
- ssh kitten: Allow using absolute paths for the location of transferred data (:iss:`5607`)
|
||||
|
||||
- Fix a regression in the previous release that caused a :opt:`resize_draw_strategy` of ``static`` to not work (:iss:`5601`)
|
||||
|
||||
- Wayland KDE: Fix abort when pasting into Firefox (:iss:`5603`)
|
||||
|
||||
- Wayland GNOME: Fix ghosting when using :opt:`background_tint` (:iss:`5605`)
|
||||
|
||||
- Fix cursor position at x=0 changing to x=1 on resize (:iss:`5635`)
|
||||
|
||||
- Wayland GNOME: Fix incorrect window size in some circumstances when switching between windows with window decorations disabled (:iss:`4802`)
|
||||
|
||||
- Wayland: Fix high CPU usage when using some input methods (:pull:`5369`)
|
||||
|
||||
- Remote control: When matching window by `state:focused` and no window currently has keyboard focus, match the window belonging to the OS window that was last focused (:iss:`5602`)
|
||||
|
||||
0.26.4 [2022-10-17]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- macOS: Allow changing the kitty icon by placing a custom icon in the kitty config folder (:pull:`5464`)
|
||||
|
||||
- Allow centering the :opt:`background_image` (:iss:`5525`)
|
||||
|
||||
- X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines (:iss:`5528`)
|
||||
|
||||
- Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab (:iss:`5548`)
|
||||
|
||||
- Fix :opt:`background_tint` not applying to window margins and padding (:iss:`3933`)
|
||||
|
||||
- Wayland: Fix background image scaling using tiled mode on high DPI screens
|
||||
|
||||
- Wayland: Fix an abort when changing background colors with :opt:`wayland_titlebar_color` set to ``background`` (:iss:`5562`)
|
||||
|
||||
- Update to Unicode 15.0 (:pull:`5542`)
|
||||
|
||||
- GNOME Wayland: Fix a memory leak in gnome-shell when using client side decorations
|
||||
|
||||
|
||||
0.26.3 [2022-09-22]
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
@@ -277,7 +277,7 @@ def write_remote_control_protocol_docs() -> None: # {{{
|
||||
from kitty.rc.base import (
|
||||
RemoteCommand, all_command_names, command_for_name
|
||||
)
|
||||
field_pat = re.compile(r'\s*([a-zA-Z0-9_+/]+)\s*:\s*(.+)')
|
||||
field_pat = re.compile(r'\s*([^:]+?)\s*:\s*(.+)')
|
||||
|
||||
def format_cmd(p: Callable[..., None], name: str, cmd: RemoteCommand) -> None:
|
||||
p(name)
|
||||
|
||||
35
docs/faq.rst
35
docs/faq.rst
@@ -205,13 +205,15 @@ fonts to be freely resizable, so it does not support bitmapped fonts.
|
||||
.. note::
|
||||
If you are trying to use a font patched with `Nerd Fonts
|
||||
<https://nerdfonts.com/>`__ symbols, don't do that as patching destroys
|
||||
fonts. There is no need, simply install the standalone ``Symbols Nerd Font``
|
||||
fonts. There is no need, simply install the standalone ``Symbols Nerd Font Mono``
|
||||
(the file :file:`NerdFontsSymbolsOnly.zip` from the `Nerd Fonts releases page
|
||||
<https://github.com/ryanoasis/nerd-fonts/releases>`__). kitty should pick up
|
||||
symbols from it automatically, and you can tell it to do so explicitly in
|
||||
case it doesn't with the :opt:`symbol_map` directive::
|
||||
|
||||
symbol_map U+23FB-U+23FE,U+2665,U+26A1,U+2B58,U+E000-U+E00A,U+E0A0-U+E0A3,U+E0B0-U+E0C8,U+E0CA,U+E0CC-U+E0D2,U+E0D4,U+E200-U+E2A9,U+E300-U+E3E3,U+E5FA-U+E62F,U+E700-U+E7C5,U+F000-U+F2E0,U+F300-U+F31C,U+F400-U+F4A9,U+F500-U+F8FF Symbols Nerd Font
|
||||
# Nerd Fonts v2.2.2
|
||||
|
||||
symbol_map U+23FB-U+23FE,U+2665,U+26A1,U+2B58,U+E000-U+E00A,U+E0A0-U+E0A3,U+E0B0-U+E0C8,U+E0CA,U+E0CC-U+E0D2,U+E0D4,U+E200-U+E2A9,U+E300-U+E3E3,U+E5FA-U+E634,U+E700-U+E7C5,U+EA60-U+EBEB,U+F000-U+F2E0,U+F300-U+F32F,U+F400-U+F4A9,U+F500-U+F8FF Symbols Nerd Font Mono
|
||||
|
||||
Those Unicode symbols beyond the ``E000-F8FF`` Unicode private use area are
|
||||
not included.
|
||||
@@ -287,13 +289,36 @@ homepage:
|
||||
:target: https://github.com/samholmes/whiskers
|
||||
:width: 256
|
||||
|
||||
On macOS you can change the icon by following the steps:
|
||||
On macOS you can put :file:`kitty.app.icns` or :file:`kitty.app.png` in the
|
||||
:ref:`kitty configuration directory <confloc>`, and this icon will be applied
|
||||
automatically at startup. Unfortunately, Apple's Dock does not change its
|
||||
cached icon so the custom icon will revert when kitty is quit. Run the
|
||||
following to force the Dock to update its cached icons:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
|
||||
|
||||
If you prefer not to keep a custom icon in the kitty config folder, you can
|
||||
also set it with the following command:
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# Set kitty.icns as the icon for currently running kitty
|
||||
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' kitty.icns
|
||||
|
||||
# Set the icon for app bundle specified by the path
|
||||
kitty +runpy 'from kitty.fast_data_types import cocoa_set_app_icon; import sys; cocoa_set_app_icon(*sys.argv[1:]); print("OK")' /path/to/icon.png /Applications/kitty.app
|
||||
|
||||
You can also change the icon manually by following the steps:
|
||||
|
||||
#. Find :file:`kitty.app` in the Applications folder, select it and press :kbd:`⌘+I`
|
||||
#. Drag :file:`kitty.icns` onto the application icon in the kitty info pane
|
||||
#. Delete the icon cache and restart Dock::
|
||||
#. Delete the icon cache and restart Dock:
|
||||
|
||||
$ rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
|
||||
.. code-block:: sh
|
||||
|
||||
rm /var/folders/*/*/*/com.apple.dock.iconcache; killall Dock
|
||||
|
||||
|
||||
How do I map key presses in kitty to different keys in the terminal program?
|
||||
|
||||
@@ -17,12 +17,12 @@ following contents:
|
||||
# by the hyperlink_grep kitten and nothing else so far.
|
||||
protocol file
|
||||
fragment_matches [0-9]+
|
||||
action launch --type=overlay vim +${FRAGMENT} ${FILE_PATH}
|
||||
action launch --type=overlay --cwd=current vim +${FRAGMENT} ${FILE_PATH}
|
||||
|
||||
# Open text files without fragments in the editor
|
||||
protocol file
|
||||
mime text/*
|
||||
action launch --type=overlay ${EDITOR} ${FILE_PATH}
|
||||
action launch --type=overlay --cwd=current ${EDITOR} ${FILE_PATH}
|
||||
|
||||
Now, run a search with::
|
||||
|
||||
|
||||
@@ -167,6 +167,12 @@ define a few extra key bindings in :file:`kitty.conf`::
|
||||
map shift+right move_window right
|
||||
map shift+down move_window down
|
||||
|
||||
# Move the active window to the indicated screen edge
|
||||
map ctrl+shift+up layout_action move_to_screen_edge top
|
||||
map ctrl+shift+left layout_action move_to_screen_edge left
|
||||
map ctrl+shift+right layout_action move_to_screen_edge right
|
||||
map ctrl+shift+down layout_action move_to_screen_edge bottom
|
||||
|
||||
# Switch focus to the neighboring window in the indicated direction
|
||||
map ctrl+left neighboring_window left
|
||||
map ctrl+right neighboring_window right
|
||||
|
||||
3
glfw/context.c
vendored
3
glfw/context.c
vendored
@@ -478,6 +478,9 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
|
||||
}
|
||||
|
||||
window->context.swapBuffers(window);
|
||||
#ifdef _GLFW_WAYLAND
|
||||
_glfwWaylandAfterBufferSwap(window);
|
||||
#endif
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSwapInterval(int interval)
|
||||
|
||||
1
glfw/egl_context.c
vendored
1
glfw/egl_context.c
vendored
@@ -326,6 +326,7 @@ bool _glfwInitEGL(void)
|
||||
glfw_dlsym(_glfw.egl.SwapBuffers, _glfw.egl.handle, "eglSwapBuffers");
|
||||
glfw_dlsym(_glfw.egl.SwapInterval, _glfw.egl.handle, "eglSwapInterval");
|
||||
glfw_dlsym(_glfw.egl.QueryString, _glfw.egl.handle, "eglQueryString");
|
||||
glfw_dlsym(_glfw.egl.QuerySurface, _glfw.egl.handle, "eglQuerySurface");
|
||||
glfw_dlsym(_glfw.egl.GetProcAddress, _glfw.egl.handle, "eglGetProcAddress");
|
||||
|
||||
if (!_glfw.egl.GetConfigAttrib ||
|
||||
|
||||
3
glfw/egl_context.h
vendored
3
glfw/egl_context.h
vendored
@@ -132,6 +132,7 @@ typedef EGLBoolean (EGLAPIENTRY * PFN_eglMakeCurrent)(EGLDisplay,EGLSurface,EGLS
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapBuffers)(EGLDisplay,EGLSurface);
|
||||
typedef EGLBoolean (EGLAPIENTRY * PFN_eglSwapInterval)(EGLDisplay,EGLint);
|
||||
typedef const char* (EGLAPIENTRY * PFN_eglQueryString)(EGLDisplay,EGLint);
|
||||
typedef const char* (EGLAPIENTRY * PFN_eglQuerySurface)(EGLDisplay,EGLSurface,EGLint,EGLint*);
|
||||
typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
||||
#define eglGetConfigAttrib _glfw.egl.GetConfigAttrib
|
||||
#define eglGetConfigs _glfw.egl.GetConfigs
|
||||
@@ -149,6 +150,7 @@ typedef GLFWglproc (EGLAPIENTRY * PFN_eglGetProcAddress)(const char*);
|
||||
#define eglSwapBuffers _glfw.egl.SwapBuffers
|
||||
#define eglSwapInterval _glfw.egl.SwapInterval
|
||||
#define eglQueryString _glfw.egl.QueryString
|
||||
#define eglQuerySurface _glfw.egl.QuerySurface
|
||||
#define eglGetProcAddress _glfw.egl.GetProcAddress
|
||||
|
||||
typedef EGLDisplay (EGLAPIENTRY * PFNEGLGETPLATFORMDISPLAYEXTPROC)(EGLenum,void*,const EGLint*);
|
||||
@@ -211,6 +213,7 @@ typedef struct _GLFWlibraryEGL
|
||||
PFN_eglSwapBuffers SwapBuffers;
|
||||
PFN_eglSwapInterval SwapInterval;
|
||||
PFN_eglQueryString QueryString;
|
||||
PFN_eglQuerySurface QuerySurface;
|
||||
PFN_eglGetProcAddress GetProcAddress;
|
||||
|
||||
PFNEGLGETPLATFORMDISPLAYEXTPROC GetPlatformDisplayEXT;
|
||||
|
||||
@@ -172,6 +172,8 @@ class Arg:
|
||||
while self.name.startswith('*'):
|
||||
self.name = self.name[1:]
|
||||
self.type = self.type + '*'
|
||||
if '[' in self.name:
|
||||
self.type += '[' + self.name.partition('[')[-1]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Arg({self.type}, {self.name})'
|
||||
@@ -252,6 +254,8 @@ def generate_wrappers(glfw_header: str) -> None:
|
||||
const char *action_text, int32_t timeout, GLFWDBusnotificationcreatedfun callback, void *data)
|
||||
void glfwDBusSetUserNotificationHandler(GLFWDBusnotificationactivatedfun handler)
|
||||
int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||
void glfwSetX11WindowAsDock(int32_t x11_window_id)
|
||||
void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12])
|
||||
'''.splitlines():
|
||||
if line:
|
||||
functions.append(Function(line.strip(), check_fail=False))
|
||||
|
||||
19
glfw/wl_client_side_decorations.c
vendored
19
glfw/wl_client_side_decorations.c
vendored
@@ -104,9 +104,10 @@ init_buffer_pair(_GLFWWaylandBufferPair *pair, size_t width, size_t height, unsi
|
||||
|
||||
static bool
|
||||
window_has_buffer(_GLFWwindow *window, struct wl_buffer *q) {
|
||||
#define Q(which) decs.which.buffer.a == q || decs.which.buffer.b == q
|
||||
return Q(left) || Q(top) || Q(right) || Q(bottom);
|
||||
#define Q(which) if (decs.which.buffer.a == q) { decs.which.buffer.a_needs_to_be_destroyed = false; return true; } if (decs.which.buffer.b == q) { decs.which.buffer.b_needs_to_be_destroyed = false; return true; }
|
||||
Q(left); Q(top); Q(right); Q(bottom);
|
||||
#undef Q
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -122,10 +123,12 @@ static void
|
||||
alloc_buffer_pair(uintptr_t window_id, _GLFWWaylandBufferPair *pair, struct wl_shm_pool *pool, uint8_t *data, size_t *offset) {
|
||||
pair->data.a = data + *offset;
|
||||
pair->a = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
|
||||
pair->a_needs_to_be_destroyed = true;
|
||||
wl_buffer_add_listener(pair->a, &handle_buffer_events, (void*)window_id);
|
||||
*offset += pair->size_in_bytes;
|
||||
pair->data.b = data + *offset;
|
||||
pair->b = wl_shm_pool_create_buffer(pool, *offset, pair->width, pair->height, pair->stride, WL_SHM_FORMAT_ARGB8888);
|
||||
pair->b_needs_to_be_destroyed = true;
|
||||
wl_buffer_add_listener(pair->b, &handle_buffer_events, (void*)window_id);
|
||||
*offset += pair->size_in_bytes;
|
||||
pair->front = pair->a; pair->back = pair->b;
|
||||
@@ -331,6 +334,8 @@ free_csd_surfaces(_GLFWwindow *window) {
|
||||
static void
|
||||
free_csd_buffers(_GLFWwindow *window) {
|
||||
#define d(which) { \
|
||||
if (decs.which.buffer.a_needs_to_be_destroyed && decs.which.buffer.a) wl_buffer_destroy(decs.which.buffer.a); \
|
||||
if (decs.which.buffer.b_needs_to_be_destroyed && decs.which.buffer.b) wl_buffer_destroy(decs.which.buffer.b); \
|
||||
memset(&decs.which.buffer, 0, sizeof(_GLFWWaylandBufferPair)); \
|
||||
}
|
||||
d(left); d(top); d(right); d(bottom);
|
||||
@@ -353,9 +358,10 @@ create_csd_surfaces(_GLFWwindow *window, _GLFWWaylandCSDEdge *s) {
|
||||
}
|
||||
|
||||
#define damage_csd(which, xbuffer) \
|
||||
wl_surface_attach(decs.which.surface, xbuffer, 0, 0); \
|
||||
wl_surface_attach(decs.which.surface, (xbuffer), 0, 0); \
|
||||
wl_surface_damage(decs.which.surface, 0, 0, decs.which.buffer.width, decs.which.buffer.height); \
|
||||
wl_surface_commit(decs.which.surface)
|
||||
wl_surface_commit(decs.which.surface); \
|
||||
if (decs.which.buffer.a == (xbuffer)) { decs.which.buffer.a_needs_to_be_destroyed = false; } else { decs.which.buffer.b_needs_to_be_destroyed = false; }
|
||||
|
||||
bool
|
||||
ensure_csd_resources(_GLFWwindow *window) {
|
||||
@@ -450,8 +456,5 @@ set_titlebar_color(_GLFWwindow *window, uint32_t color, bool use_system_color) {
|
||||
decs.use_custom_titlebar_color = use_custom_color;
|
||||
decs.titlebar_color = color;
|
||||
}
|
||||
if (window->decorated && decs.top.surface) {
|
||||
update_title_bar(window);
|
||||
damage_csd(top, decs.top.buffer.front);
|
||||
}
|
||||
change_csd_title(window);
|
||||
}
|
||||
|
||||
3
glfw/wl_platform.h
vendored
3
glfw/wl_platform.h
vendored
@@ -104,6 +104,7 @@ typedef struct _GLFWWaylandBufferPair {
|
||||
struct { uint8_t *a, *b, *front, *back; } data;
|
||||
bool has_pending_update;
|
||||
size_t size_in_bytes, width, height, stride;
|
||||
bool a_needs_to_be_destroyed, b_needs_to_be_destroyed;
|
||||
} _GLFWWaylandBufferPair;
|
||||
|
||||
typedef struct _GLFWWaylandCSDEdge {
|
||||
@@ -151,6 +152,7 @@ typedef struct _GLFWwindowWayland
|
||||
bool hovered;
|
||||
bool transparent;
|
||||
struct wl_surface* surface;
|
||||
bool waiting_for_swap_to_commit;
|
||||
struct wl_egl_window* native;
|
||||
struct wl_callback* callback;
|
||||
|
||||
@@ -368,6 +370,7 @@ typedef struct _GLFWcursorWayland
|
||||
|
||||
|
||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||
void _glfwWaylandAfterBufferSwap(_GLFWwindow *window);
|
||||
void _glfwSetupWaylandDataDevice(void);
|
||||
void _glfwSetupWaylandPrimarySelectionDevice(void);
|
||||
void animateCursorImage(id_type timer_id, void *data);
|
||||
|
||||
37
glfw/wl_text_input.c
vendored
37
glfw/wl_text_input.c
vendored
@@ -9,12 +9,15 @@
|
||||
#include "internal.h"
|
||||
#include "wayland-text-input-unstable-v3-client-protocol.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
|
||||
static struct zwp_text_input_v3* text_input;
|
||||
static struct zwp_text_input_manager_v3* text_input_manager;
|
||||
static char *pending_pre_edit = NULL;
|
||||
static char *current_pre_edit = NULL;
|
||||
static char *pending_commit = NULL;
|
||||
static int last_cursor_left = 0, last_cursor_top = 0, last_cursor_width = 0, last_cursor_height = 0;
|
||||
uint32_t commit_serial = 0;
|
||||
|
||||
static void commit(void) {
|
||||
@@ -90,12 +93,20 @@ text_input_done(void *data UNUSED, struct zwp_text_input_v3 *txt_input UNUSED, u
|
||||
if (serial > commit_serial) _glfwInputError(GLFW_PLATFORM_ERROR, "Wayland: text_input_done serial mismatch, expected=%u got=%u\n", commit_serial, serial);
|
||||
return;
|
||||
}
|
||||
if (pending_pre_edit) {
|
||||
send_text(pending_pre_edit, GLFW_IME_PREEDIT_CHANGED);
|
||||
|
||||
if ((pending_pre_edit == NULL && current_pre_edit == NULL) ||
|
||||
(pending_pre_edit && current_pre_edit && strcmp(pending_pre_edit, current_pre_edit) == 0)) {
|
||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||
} else {
|
||||
// Clear pre-edit text
|
||||
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
|
||||
free(current_pre_edit);
|
||||
current_pre_edit = pending_pre_edit;
|
||||
pending_pre_edit = NULL;
|
||||
if (current_pre_edit) {
|
||||
send_text(current_pre_edit, GLFW_IME_PREEDIT_CHANGED);
|
||||
} else {
|
||||
// Clear pre-edit text
|
||||
send_text(NULL, GLFW_IME_WAYLAND_DONE_EVENT);
|
||||
}
|
||||
}
|
||||
if (pending_commit) {
|
||||
send_text(pending_commit, GLFW_IME_COMMIT_TEXT);
|
||||
@@ -133,6 +144,7 @@ _glfwWaylandDestroyTextInput(void) {
|
||||
if (text_input_manager) zwp_text_input_manager_v3_destroy(text_input_manager);
|
||||
text_input = NULL; text_input_manager = NULL;
|
||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||
free(current_pre_edit); current_pre_edit = NULL;
|
||||
free(pending_commit); pending_commit = NULL;
|
||||
}
|
||||
|
||||
@@ -146,10 +158,11 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
zwp_text_input_v3_enable(text_input);
|
||||
zwp_text_input_v3_set_content_type(text_input, ZWP_TEXT_INPUT_V3_CONTENT_HINT_NONE, ZWP_TEXT_INPUT_V3_CONTENT_PURPOSE_TERMINAL);
|
||||
} else {
|
||||
if (pending_pre_edit) {
|
||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||
if (current_pre_edit) {
|
||||
// Clear pre-edit text
|
||||
send_text(NULL, GLFW_IME_PREEDIT_CHANGED);
|
||||
free(pending_pre_edit); pending_pre_edit = NULL;
|
||||
free(current_pre_edit); current_pre_edit = NULL;
|
||||
}
|
||||
if (pending_commit) {
|
||||
free(pending_commit); pending_commit = NULL;
|
||||
@@ -161,9 +174,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, const GLFWIMEUpdateEvent *ev) {
|
||||
case GLFW_IME_UPDATE_CURSOR_POSITION: {
|
||||
const int scale = w->wl.scale;
|
||||
const int left = ev->cursor.left / scale, top = ev->cursor.top / scale, width = ev->cursor.width / scale, height = ev->cursor.height / scale;
|
||||
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
|
||||
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
|
||||
commit();
|
||||
if (left != last_cursor_left || top != last_cursor_top || width != last_cursor_width || height != last_cursor_height) {
|
||||
last_cursor_left = left;
|
||||
last_cursor_top = top;
|
||||
last_cursor_width = width;
|
||||
last_cursor_height = height;
|
||||
debug("\ntext-input: updating cursor position: left=%d top=%d width=%d height=%d\n", left, top, width, height);
|
||||
zwp_text_input_v3_set_cursor_rectangle(text_input, left, top, width, height);
|
||||
commit();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
87
glfw/wl_window.c
vendored
87
glfw/wl_window.c
vendored
@@ -251,6 +251,15 @@ static bool checkScaleChange(_GLFWwindow* window)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void
|
||||
commit_window_surface_if_safe(_GLFWwindow *window) {
|
||||
// we only commit if the buffer attached to the surface is the correct size,
|
||||
// which means that at least one frame is drawn after resizeFramebuffer()
|
||||
if (!window->wl.waiting_for_swap_to_commit) {
|
||||
wl_surface_commit(window->wl.surface);
|
||||
}
|
||||
}
|
||||
|
||||
// Makes the surface considered as XRGB instead of ARGB.
|
||||
static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
|
||||
{
|
||||
@@ -262,7 +271,7 @@ static void setOpaqueRegion(_GLFWwindow* window, bool commit_surface)
|
||||
|
||||
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
|
||||
wl_surface_set_opaque_region(window->wl.surface, region);
|
||||
if (commit_surface) wl_surface_commit(window->wl.surface);
|
||||
if (commit_surface) commit_window_surface_if_safe(window);
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
|
||||
@@ -287,9 +296,20 @@ resizeFramebuffer(_GLFWwindow* window) {
|
||||
debug("Resizing framebuffer to: %dx%d at scale: %d\n", window->wl.width, window->wl.height, scale);
|
||||
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
|
||||
if (!window->wl.transparent) setOpaqueRegion(window, false);
|
||||
window->wl.waiting_for_swap_to_commit = true;
|
||||
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
|
||||
}
|
||||
|
||||
void
|
||||
_glfwWaylandAfterBufferSwap(_GLFWwindow* window) {
|
||||
if (window->wl.waiting_for_swap_to_commit) {
|
||||
debug("Waiting for swap to commit: swap has happened\n");
|
||||
window->wl.waiting_for_swap_to_commit = false;
|
||||
// this is not really needed, since I think eglSwapBuffers() calls wl_surface_commit()
|
||||
// but lets be safe. See https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/src/egl/drivers/dri2/platform_wayland.c#L1510
|
||||
wl_surface_commit(window->wl.surface);
|
||||
}
|
||||
}
|
||||
|
||||
static const char*
|
||||
clipboard_mime(void) {
|
||||
@@ -301,9 +321,9 @@ clipboard_mime(void) {
|
||||
}
|
||||
|
||||
static bool
|
||||
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height, bool *scale_changed) {
|
||||
dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height) {
|
||||
bool size_changed = width != window->wl.width || height != window->wl.height;
|
||||
*scale_changed = checkScaleChange(window);
|
||||
bool scale_changed = checkScaleChange(window);
|
||||
|
||||
if (size_changed) {
|
||||
_glfwInputWindowSize(window, width, height);
|
||||
@@ -311,7 +331,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
|
||||
resizeFramebuffer(window);
|
||||
}
|
||||
|
||||
if (*scale_changed) {
|
||||
if (scale_changed) {
|
||||
debug("Scale changed to %d in dispatchChangesAfterConfigure\n", window->wl.scale);
|
||||
if (!size_changed) resizeFramebuffer(window);
|
||||
_glfwInputWindowContentScale(window, window->wl.scale, window->wl.scale);
|
||||
@@ -319,7 +339,7 @@ dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, int32_t height
|
||||
|
||||
_glfwInputWindowDamage(window);
|
||||
|
||||
return size_changed || *scale_changed;
|
||||
return size_changed || scale_changed;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -568,6 +588,15 @@ static void xdgSurfaceHandleConfigure(void* data,
|
||||
struct xdg_surface* surface,
|
||||
uint32_t serial)
|
||||
{
|
||||
// The poorly documented pattern Wayland requires is:
|
||||
// 1) ack the configure,
|
||||
// 2) set the window geometry
|
||||
// 3) attach a new buffer of the correct size to the surface
|
||||
// 4) only then commit the surface.
|
||||
// buffer is attached only by eglSwapBuffers,
|
||||
// so we set a flag to not commit the surface till the next swapbuffers. Note that
|
||||
// wl_egl_window_resize() does not actually resize the buffer until the next draw call
|
||||
// or buffer state query.
|
||||
_GLFWwindow* window = data;
|
||||
xdg_surface_ack_configure(surface, serial);
|
||||
if (window->wl.pending_state & PENDING_STATE_TOPLEVEL) {
|
||||
@@ -600,11 +629,10 @@ static void xdgSurfaceHandleConfigure(void* data,
|
||||
}
|
||||
|
||||
bool resized = false;
|
||||
bool scale_changed = false;
|
||||
if (window->wl.pending_state) {
|
||||
int width = window->wl.pending.width, height = window->wl.pending.height;
|
||||
set_csd_window_geometry(window, &width, &height);
|
||||
resized = dispatchChangesAfterConfigure(window, width, height, &scale_changed);
|
||||
resized = dispatchChangesAfterConfigure(window, width, height);
|
||||
if (window->wl.decorations.serverSide) {
|
||||
free_csd_surfaces(window);
|
||||
} else {
|
||||
@@ -614,14 +642,7 @@ static void xdgSurfaceHandleConfigure(void* data,
|
||||
}
|
||||
|
||||
inform_compositor_of_window_geometry(window, "configure");
|
||||
|
||||
// we need to swap buffers here to ensure the buffer attached to the surface is a multiple
|
||||
// of the new scale. See https://github.com/kovidgoyal/kitty/issues/5467
|
||||
if (scale_changed) swap_buffers(window);
|
||||
|
||||
// if a resize happened there will be a commit at the next render frame so
|
||||
// dont commit here, GNOME doesnt like it and its not really needed anyway
|
||||
if (!resized) wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
window->wl.pending_state = 0;
|
||||
}
|
||||
|
||||
@@ -1021,7 +1042,7 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
window->wl.width = w; window->wl.height = h;
|
||||
resizeFramebuffer(window);
|
||||
ensure_csd_resources(window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
inform_compositor_of_window_geometry(window, "SetWindowSize");
|
||||
}
|
||||
}
|
||||
@@ -1038,7 +1059,7 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
maxwidth = maxheight = 0;
|
||||
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
|
||||
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1273,7 +1294,7 @@ void _glfwPlatformSetWindowMousePassthrough(_GLFWwindow* window, bool enabled)
|
||||
}
|
||||
else
|
||||
wl_surface_set_input_region(window->wl.surface, 0);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
}
|
||||
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window UNUSED)
|
||||
@@ -1337,7 +1358,7 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
||||
zwp_locked_pointer_v1_set_cursor_position_hint(
|
||||
window->wl.pointerLock.lockedPointer,
|
||||
wl_fixed_from_double(x), wl_fixed_from_double(y));
|
||||
wl_surface_commit(window->wl.surface);
|
||||
commit_window_surface_if_safe(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1691,9 +1712,18 @@ static void primary_selection_source_canceled(void *data UNUSED, struct zwp_prim
|
||||
zwp_primary_selection_source_v1_destroy(primary_selection_source);
|
||||
}
|
||||
|
||||
// KWin aborts if we don't define these even though they are not used for copy/paste
|
||||
static void dummy_data_source_target(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, const char* mime_type UNUSED) {
|
||||
}
|
||||
|
||||
static void dummy_data_source_action(void* data UNUSED, struct wl_data_source* wl_data_source UNUSED, uint dnd_action UNUSED) {
|
||||
}
|
||||
|
||||
static const struct wl_data_source_listener data_source_listener = {
|
||||
.send = _glfwSendClipboardText,
|
||||
.cancelled = data_source_canceled,
|
||||
.target = dummy_data_source_target,
|
||||
.action = dummy_data_source_action,
|
||||
};
|
||||
|
||||
static const struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
||||
@@ -2232,12 +2262,19 @@ GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
|
||||
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
|
||||
window->wl.frameCallbackData.id = id;
|
||||
window->wl.frameCallbackData.callback = callback;
|
||||
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
|
||||
if (window->wl.frameCallbackData.current_wl_callback) {
|
||||
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
if (window->wl.waiting_for_swap_to_commit) {
|
||||
callback(id);
|
||||
window->wl.frameCallbackData.id = 0;
|
||||
window->wl.frameCallbackData.callback = NULL;
|
||||
window->wl.frameCallbackData.current_wl_callback = NULL;
|
||||
} else {
|
||||
window->wl.frameCallbackData.id = id;
|
||||
window->wl.frameCallbackData.callback = callback;
|
||||
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
|
||||
if (window->wl.frameCallbackData.current_wl_callback) {
|
||||
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
|
||||
commit_window_surface_if_safe(window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
glfw/x11_init.c
vendored
4
glfw/x11_init.c
vendored
@@ -137,6 +137,8 @@ static void detectEWMH(void)
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
|
||||
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
|
||||
_glfw.x11.NET_WM_WINDOW_TYPE_DOCK =
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_DOCK");
|
||||
_glfw.x11.NET_WORKAREA =
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
|
||||
_glfw.x11.NET_CURRENT_DESKTOP =
|
||||
@@ -147,6 +149,8 @@ static void detectEWMH(void)
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
|
||||
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
|
||||
_glfw.x11.NET_WM_STRUT_PARTIAL =
|
||||
getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STRUT_PARTIAL");
|
||||
|
||||
XFree(supportedAtoms);
|
||||
}
|
||||
|
||||
2
glfw/x11_platform.h
vendored
2
glfw/x11_platform.h
vendored
@@ -253,6 +253,7 @@ typedef struct _GLFWlibraryX11
|
||||
Atom NET_WM_PING;
|
||||
Atom NET_WM_WINDOW_TYPE;
|
||||
Atom NET_WM_WINDOW_TYPE_NORMAL;
|
||||
Atom NET_WM_WINDOW_TYPE_DOCK;
|
||||
Atom NET_WM_STATE;
|
||||
Atom NET_WM_STATE_ABOVE;
|
||||
Atom NET_WM_STATE_FULLSCREEN;
|
||||
@@ -268,6 +269,7 @@ typedef struct _GLFWlibraryX11
|
||||
Atom NET_ACTIVE_WINDOW;
|
||||
Atom NET_FRAME_EXTENTS;
|
||||
Atom NET_REQUEST_FRAME_EXTENTS;
|
||||
Atom NET_WM_STRUT_PARTIAL;
|
||||
Atom MOTIF_WM_HINTS;
|
||||
|
||||
// Xdnd (drag and drop) atoms
|
||||
|
||||
27
glfw/x11_window.c
vendored
27
glfw/x11_window.c
vendored
@@ -793,7 +793,7 @@ static Atom writeTargetToProperty(const XSelectionRequestEvent* request)
|
||||
32,
|
||||
PropModeReplace,
|
||||
(unsigned char*) targets,
|
||||
sizeof(targets[0]) * (aa->sz + 2));
|
||||
aa->sz + 2);
|
||||
free(targets);
|
||||
return request->property;
|
||||
}
|
||||
@@ -2881,7 +2881,7 @@ void _glfwPlatformSetClipboard(GLFWClipboardType t) {
|
||||
}
|
||||
if (aa->capacity < cd->num_mime_types + 32) {
|
||||
aa->capacity = cd->num_mime_types + 32;
|
||||
aa->array = malloc(sizeof(aa->array[0]) * aa->capacity);
|
||||
aa->array = reallocarray(aa->array, aa->capacity, sizeof(aa->array[0]));
|
||||
}
|
||||
aa->sz = 0;
|
||||
for (size_t i = 0; i < cd->num_mime_types; i++) {
|
||||
@@ -2945,10 +2945,16 @@ _glfwPlatformGetClipboard(GLFWClipboardType clipboard_type, const char* mime_typ
|
||||
return;
|
||||
}
|
||||
size_t count = 0;
|
||||
atoms[count++] = atom_for_mime(mime_type).atom;
|
||||
if (strcmp(mime_type, "text/plain") == 0) {
|
||||
// we need to do this because GTK/GNOME is developed by morons
|
||||
// they convert text/plain to DOS line endings
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/issues/2307
|
||||
atoms[count++] = atom_for_mime("text/plain;charset=utf-8").atom;
|
||||
atoms[count++] = _glfw.x11.UTF8_STRING;
|
||||
atoms[count++] = atom_for_mime("text/plain").atom;
|
||||
atoms[count++] = XA_STRING;
|
||||
} else {
|
||||
atoms[count++] = atom_for_mime(mime_type).atom;
|
||||
}
|
||||
getSelectionString(which, atoms, count, write_data, object, true);
|
||||
}
|
||||
@@ -3191,3 +3197,18 @@ GLFWAPI int glfwSetX11LaunchCommand(GLFWwindow *handle, char **argv, int argc)
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
return XSetCommand(_glfw.x11.display, window->x11.handle, argv, argc);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetX11WindowAsDock(int32_t x11_window_id) {
|
||||
_GLFW_REQUIRE_INIT();
|
||||
Atom type = _glfw.x11.NET_WM_WINDOW_TYPE_DOCK;
|
||||
XChangeProperty(_glfw.x11.display, x11_window_id,
|
||||
_glfw.x11.NET_WM_WINDOW_TYPE, XA_ATOM, 32,
|
||||
PropModeReplace, (unsigned char*) &type, 1);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetX11WindowStrut(int32_t x11_window_id, uint32_t dimensions[12]) {
|
||||
_GLFW_REQUIRE_INIT();
|
||||
XChangeProperty(_glfw.x11.display, x11_window_id,
|
||||
_glfw.x11.NET_WM_STRUT_PARTIAL, XA_CARDINAL, 32,
|
||||
PropModeReplace, (unsigned char*) dimensions, 12);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# generated by gen-wcwidth.py, do not edit
|
||||
|
||||
url_delimiters = '\x00-\x09\x0b-\x0c\x0e-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u0890-\u0891\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U00013430-\U00013438\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
||||
url_delimiters = '\x00-\x09\x0b-\x0c\x0e-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u0890-\u0891\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U000110cd\U00013430-\U0001343f\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
||||
@@ -2,8 +2,6 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any, Callable, Dict, List, Tuple
|
||||
|
||||
@@ -11,6 +9,7 @@ from kitty.cli import parse_args
|
||||
from kitty.cli_stub import PanelCLIOptions
|
||||
from kitty.constants import appname, is_macos
|
||||
from kitty.os_window_size import WindowSizeData
|
||||
from kitty.fast_data_types import make_x11_window_a_dock_window
|
||||
|
||||
OPTIONS = r'''
|
||||
--lines
|
||||
@@ -65,14 +64,7 @@ def parse_panel_args(args: List[str]) -> Tuple[PanelCLIOptions, List[str]]:
|
||||
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten panel', result_class=PanelCLIOptions)
|
||||
|
||||
|
||||
def call_xprop(*cmd: str, silent: bool = False) -> None:
|
||||
cmd_ = ['xprop'] + list(cmd)
|
||||
try:
|
||||
cp = subprocess.run(cmd_, stdout=subprocess.DEVNULL if silent else None)
|
||||
except FileNotFoundError:
|
||||
raise SystemExit('You must have the xprop program installed')
|
||||
if cp.returncode != 0:
|
||||
raise SystemExit(cp.returncode)
|
||||
Strut = Tuple[int, int, int, int, int, int, int, int, int, int, int, int]
|
||||
|
||||
|
||||
def create_strut(
|
||||
@@ -80,43 +72,33 @@ def create_strut(
|
||||
left: int = 0, right: int = 0, top: int = 0, bottom: int = 0, left_start_y: int = 0, left_end_y: int = 0,
|
||||
right_start_y: int = 0, right_end_y: int = 0, top_start_x: int = 0, top_end_x: int = 0,
|
||||
bottom_start_x: int = 0, bottom_end_x: int = 0
|
||||
) -> None:
|
||||
call_xprop(
|
||||
'-id',
|
||||
str(int(win_id)), '-format', '_NET_WM_STRUT_PARTIAL', '32cccccccccccc',
|
||||
'-set', '_NET_WM_STRUT_PARTIAL',
|
||||
f'{left},{right},{top},{bottom},'
|
||||
f'{left_start_y},{left_end_y},{right_start_y},{right_end_y},'
|
||||
f'{top_start_x},{top_end_x},{bottom_start_x},{bottom_end_x}'
|
||||
)
|
||||
) -> Strut:
|
||||
return left, right, top, bottom, left_start_y, left_end_y, right_start_y, right_end_y, top_start_x, top_end_x, bottom_start_x, bottom_end_x
|
||||
|
||||
|
||||
def create_top_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, top=height, top_end_x=width)
|
||||
def create_top_strut(win_id: int, width: int, height: int) -> Strut:
|
||||
return create_strut(win_id, top=height, top_end_x=width)
|
||||
|
||||
|
||||
def create_bottom_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, bottom=height, bottom_end_x=width)
|
||||
def create_bottom_strut(win_id: int, width: int, height: int) -> Strut:
|
||||
return create_strut(win_id, bottom=height, bottom_end_x=width)
|
||||
|
||||
|
||||
def create_left_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, left=width, left_end_y=height)
|
||||
def create_left_strut(win_id: int, width: int, height: int) -> Strut:
|
||||
return create_strut(win_id, left=width, left_end_y=height)
|
||||
|
||||
|
||||
def create_right_strut(win_id: int, width: int, height: int) -> None:
|
||||
create_strut(win_id, right=width, right_end_y=height)
|
||||
def create_right_strut(win_id: int, width: int, height: int) -> Strut:
|
||||
return create_strut(win_id, right=width, right_end_y=height)
|
||||
|
||||
|
||||
window_width = window_height = 0
|
||||
|
||||
|
||||
def setup_x11_window(win_id: int) -> None:
|
||||
call_xprop(
|
||||
'-id', str(win_id), '-format', '_NET_WM_WINDOW_TYPE', '32a',
|
||||
'-set', '_NET_WM_WINDOW_TYPE', '_NET_WM_WINDOW_TYPE_DOCK'
|
||||
)
|
||||
func = globals()[f'create_{args.edge}_strut']
|
||||
func(win_id, window_width, window_height)
|
||||
strut = func(win_id, window_width, window_height)
|
||||
make_x11_window_a_dock_window(win_id, strut)
|
||||
|
||||
|
||||
def initial_window_size_func(opts: WindowSizeData, cached_values: Dict[str, Any]) -> Callable[[int, int, float, float, float, float], Tuple[int, int]]:
|
||||
@@ -140,8 +122,6 @@ def main(sys_args: List[str]) -> None:
|
||||
global args
|
||||
if is_macos or not os.environ.get('DISPLAY'):
|
||||
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
||||
if not shutil.which('xprop'):
|
||||
raise SystemExit('The xprop program is required for the panel kitten')
|
||||
args, items = parse_panel_args(sys_args[1:])
|
||||
if not items:
|
||||
raise SystemExit('You must specify the program to run')
|
||||
|
||||
@@ -40,10 +40,9 @@ shell or a :program:`python` executable. If the default :program:`sh` is not
|
||||
available or broken, using an alternate interpreter can be useful.
|
||||
''')
|
||||
|
||||
opt('remote_dir', '.local/share/kitty-ssh-kitten', option_type='relative_dir', long_text='''
|
||||
opt('remote_dir', '.local/share/kitty-ssh-kitten', long_text='''
|
||||
The location on the remote host where the files needed for this kitten are
|
||||
installed. The location is relative to the HOME directory. Absolute paths or
|
||||
paths that resolve to a location outside the HOME are not allowed.
|
||||
installed. Relative paths are resolved with respect to :code:`$HOME`.
|
||||
''')
|
||||
|
||||
opt('+copy', '', option_type='copy', add_to_default=False, long_text=f'''
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# generated by gen-config.py DO NOT edit
|
||||
|
||||
import typing
|
||||
from kittens.ssh.options.utils import copy, env, hostname, relative_dir
|
||||
from kittens.ssh.options.utils import copy, env, hostname
|
||||
from kitty.conf.utils import merge_dicts, to_bool
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class Parser:
|
||||
ans['login_shell'] = str(val)
|
||||
|
||||
def remote_dir(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['remote_dir'] = relative_dir(val)
|
||||
ans['remote_dir'] = str(val)
|
||||
|
||||
def remote_kitty(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
val = val.lower()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2022, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import posixpath
|
||||
from typing import Any, Dict, Iterable, Optional, Tuple
|
||||
|
||||
from ..copy import CopyInstruction, parse_copy_instructions
|
||||
@@ -9,16 +8,6 @@ from ..copy import CopyInstruction, parse_copy_instructions
|
||||
DELETE_ENV_VAR = '_delete_this_env_var_'
|
||||
|
||||
|
||||
def relative_dir(val: str) -> str:
|
||||
if posixpath.isabs(val):
|
||||
raise ValueError(f'Absolute paths not allowed. {val} is invalid.')
|
||||
base = '/ffjdg'
|
||||
q = posixpath.normpath(posixpath.join(base, val))
|
||||
if q == base or not q.startswith(base):
|
||||
raise ValueError(f'Paths that escape their parent dir are not allowed. {val} is not valid')
|
||||
return posixpath.normpath(val)
|
||||
|
||||
|
||||
def env(val: str, current_val: Dict[str, str]) -> Iterable[Tuple[str, str]]:
|
||||
val = val.strip()
|
||||
if val:
|
||||
|
||||
63207
kittens/unicode_input/names.h
generated
63207
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
@@ -1,8 +1,8 @@
|
||||
#version GLSL_VERSION
|
||||
#define left -1.0f
|
||||
#define top 1.0f
|
||||
#define right 1.0f
|
||||
#define bottom -1.0f
|
||||
#define left 0
|
||||
#define top 1
|
||||
#define right 2
|
||||
#define bottom 3
|
||||
#define tex_left 0
|
||||
#define tex_top 0
|
||||
#define tex_right 1
|
||||
@@ -14,15 +14,10 @@
|
||||
|
||||
uniform float tiled;
|
||||
uniform vec4 sizes; // [ window_width, window_height, image_width, image_height ]
|
||||
uniform vec4 positions; // [ left, top, right, bottom ]
|
||||
|
||||
out vec2 texcoord;
|
||||
|
||||
const vec2 pos_map[] = vec2[4](
|
||||
vec2(left, top),
|
||||
vec2(left, bottom),
|
||||
vec2(right, bottom),
|
||||
vec2(right, top)
|
||||
);
|
||||
const vec2 tex_map[] = vec2[4](
|
||||
vec2(tex_left, tex_top),
|
||||
vec2(tex_left, tex_bottom),
|
||||
@@ -39,6 +34,12 @@ float tiling_factor(int i) {
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 pos_map[] = vec2[4](
|
||||
vec2(positions[left], positions[top]),
|
||||
vec2(positions[left], positions[bottom]),
|
||||
vec2(positions[right], positions[bottom]),
|
||||
vec2(positions[right], positions[top])
|
||||
);
|
||||
vec2 tex_coords = tex_map[gl_VertexID];
|
||||
texcoord = vec2(
|
||||
tex_coords[x_axis] * tiling_factor(x_axis),
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
#version GLSL_VERSION
|
||||
uniform float background_opacity;
|
||||
in vec3 color;
|
||||
in vec4 color;
|
||||
out vec4 final_color;
|
||||
|
||||
void main() {
|
||||
final_color = vec4(color * background_opacity, background_opacity);
|
||||
final_color = color;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,11 @@ uniform vec3 inactive_border_color;
|
||||
uniform vec3 bell_border_color;
|
||||
uniform vec3 tab_bar_bg;
|
||||
uniform vec3 tab_bar_margin_color;
|
||||
uniform float background_opacity;
|
||||
uniform float tint_opacity, tint_premult;
|
||||
in vec4 rect; // left, top, right, bottom
|
||||
in uint rect_color;
|
||||
out vec3 color;
|
||||
out vec4 color;
|
||||
|
||||
// indices into the rect vector
|
||||
const int LEFT = 0;
|
||||
@@ -35,5 +37,9 @@ void main() {
|
||||
gl_Position = vec4(rect[pos.x], rect[pos.y], 0, 1);
|
||||
int rc = int(rect_color);
|
||||
vec3 window_bg = vec3(to_color(rect_color >> 24), to_color(rect_color >> 16), to_color(rect_color >> 8));
|
||||
color = W(0, default_bg) + W(1, active_border_color) + W(2, inactive_border_color) + W(3, window_bg) + W(4, bell_border_color) + W(5, tab_bar_bg) + W(6, tab_bar_margin_color);
|
||||
vec3 color3 = W(0, default_bg) + W(1, active_border_color) + W(2, inactive_border_color) + W(3, window_bg) + W(4, bell_border_color) + W(5, tab_bar_bg) + W(6, tab_bar_margin_color);
|
||||
float is_default_bg = float(rc & 1);
|
||||
float final_opacity = is_default_bg * tint_opacity + (1 - is_default_bg) * background_opacity;
|
||||
float final_premult_opacity = is_default_bg * tint_premult + (1 - is_default_bg) * background_opacity;
|
||||
color = vec4(color3 * final_premult_opacity, final_opacity);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class Borders:
|
||||
draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1
|
||||
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
|
||||
has_background_image = os_window_has_background_image(self.os_window_id)
|
||||
if not has_background_image:
|
||||
if not has_background_image or opts.background_tint > 0.0:
|
||||
for br in current_layout.blank_rects:
|
||||
add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg)
|
||||
for tbr in tab_bar_rects:
|
||||
|
||||
@@ -12,8 +12,8 @@ from functools import partial
|
||||
from gettext import gettext as _
|
||||
from time import monotonic
|
||||
from typing import (
|
||||
TYPE_CHECKING, Any, Callable, Container, Dict, Iterable, Iterator, List,
|
||||
Optional, Set, Tuple, Union
|
||||
TYPE_CHECKING, Any, Callable, Container, Dict, Iterable, Iterator, List, Optional,
|
||||
Set, Tuple, Union,
|
||||
)
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
@@ -21,15 +21,15 @@ from .child import cached_process_data, default_env, set_default_env
|
||||
from .cli import create_opts, parse_args
|
||||
from .cli_stub import CLIOptions
|
||||
from .clipboard import (
|
||||
Clipboard, get_clipboard_string, get_primary_selection,
|
||||
set_clipboard_string, set_primary_selection
|
||||
Clipboard, get_clipboard_string, get_primary_selection, set_clipboard_string,
|
||||
set_primary_selection,
|
||||
)
|
||||
from .conf.utils import BadLine, KeyAction, to_cmdline
|
||||
from .config import common_opts_as_dict, prepare_config_file_for_editing
|
||||
from .constants import (
|
||||
RC_ENCRYPTION_PROTOCOL_VERSION, appname, cache_dir, clear_handled_signals,
|
||||
config_dir, handled_signals, is_macos, is_wayland, kitty_exe,
|
||||
logo_png_file, supports_primary_selection, website_url
|
||||
config_dir, handled_signals, is_macos, is_wayland, kitty_exe, logo_png_file,
|
||||
supports_primary_selection, website_url,
|
||||
)
|
||||
from .fast_data_types import (
|
||||
CLOSE_BEING_CONFIRMED, GLFW_MOD_ALT, GLFW_MOD_CONTROL, GLFW_MOD_SHIFT,
|
||||
@@ -37,15 +37,15 @@ from .fast_data_types import (
|
||||
IMPERATIVE_CLOSE_REQUESTED, NO_CLOSE_REQUESTED, ChildMonitor, Color,
|
||||
EllipticCurveKey, KeyEvent, SingleKey, add_timer, apply_options_update,
|
||||
background_opacity_of, change_background_opacity, change_os_window_state,
|
||||
cocoa_set_menubar_title, create_os_window,
|
||||
current_application_quit_request, current_os_window, destroy_global_data,
|
||||
focus_os_window, get_boss, get_options, get_os_window_size,
|
||||
global_font_size, mark_os_window_for_close, os_window_font_size,
|
||||
patch_global_colors, redirect_mouse_handling, ring_bell,
|
||||
run_with_activation_token, safe_pipe, send_data_to_peer,
|
||||
set_application_quit_request, set_background_image, set_boss,
|
||||
set_in_sequence_mode, set_options, set_os_window_size, set_os_window_title,
|
||||
thread_write, toggle_fullscreen, toggle_maximized, toggle_secure_input
|
||||
cocoa_set_menubar_title, create_os_window, current_application_quit_request,
|
||||
current_focused_os_window_id, current_os_window, destroy_global_data,
|
||||
focus_os_window, get_boss, get_options, get_os_window_size, global_font_size,
|
||||
last_focused_os_window_id, mark_os_window_for_close, os_window_font_size,
|
||||
patch_global_colors, redirect_mouse_handling, ring_bell, run_with_activation_token,
|
||||
safe_pipe, send_data_to_peer, set_application_quit_request, set_background_image,
|
||||
set_boss, set_in_sequence_mode, set_options, set_os_window_size,
|
||||
set_os_window_title, thread_write, toggle_fullscreen, toggle_maximized,
|
||||
toggle_secure_input,
|
||||
)
|
||||
from .key_encoding import get_name_to_functional_number_map
|
||||
from .keys import get_shortcut, shortcut_matches
|
||||
@@ -57,16 +57,14 @@ from .os_window_size import initial_window_size_func
|
||||
from .prewarm import PrewarmProcess
|
||||
from .rgb import color_from_int
|
||||
from .session import Session, create_sessions, get_os_window_sizing_data
|
||||
from .tabs import (
|
||||
SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
|
||||
)
|
||||
from .tabs import SpecialWindow, SpecialWindowInstance, Tab, TabDict, TabManager
|
||||
from .types import _T, AsyncResponse, WindowSystemMouseEvent, ac
|
||||
from .typing import PopenType, TypedDict
|
||||
from .utils import (
|
||||
cleanup_ssh_control_masters, func_name, get_editor, get_new_os_window_size,
|
||||
is_path_in_temp_dir, less_version, log_error, macos_version, open_url,
|
||||
parse_address_spec, parse_uri_list, platform_window_id, remove_socket_file,
|
||||
safe_print, single_instance, startup_notification_handler, which
|
||||
safe_print, single_instance, startup_notification_handler, which,
|
||||
)
|
||||
from .window import CommandOutput, CwdRequest, Window
|
||||
|
||||
@@ -80,6 +78,8 @@ class OSWindowDict(TypedDict):
|
||||
id: int
|
||||
platform_window_id: Optional[int]
|
||||
is_focused: bool
|
||||
is_active: bool
|
||||
last_focused: bool
|
||||
tabs: List[TabDict]
|
||||
wm_class: str
|
||||
wm_name: str
|
||||
@@ -227,7 +227,7 @@ class VisualSelect:
|
||||
t = tm.tab_for_id(self.prev_tab_id)
|
||||
if t is not tm.active_tab and t is not None:
|
||||
tm.set_active_tab(t)
|
||||
if current_os_window() != self.prev_os_window_id and self.prev_os_window_id is not None:
|
||||
if current_focused_os_window_id() != self.prev_os_window_id and self.prev_os_window_id is not None:
|
||||
focus_os_window(self.prev_os_window_id, True)
|
||||
|
||||
|
||||
@@ -287,9 +287,7 @@ class Boss:
|
||||
self.mouse_handler: Optional[Callable[[WindowSystemMouseEvent], None]] = None
|
||||
self.update_keymap()
|
||||
if is_macos:
|
||||
from .fast_data_types import (
|
||||
cocoa_set_notification_activated_callback
|
||||
)
|
||||
from .fast_data_types import cocoa_set_notification_activated_callback
|
||||
cocoa_set_notification_activated_callback(notification_activated)
|
||||
|
||||
def update_keymap(self) -> None:
|
||||
@@ -345,14 +343,15 @@ class Boss:
|
||||
|
||||
def list_os_windows(self, self_window: Optional[Window] = None) -> Iterator[OSWindowDict]:
|
||||
with cached_process_data():
|
||||
active_tab, active_window = self.active_tab, self.active_window
|
||||
active_tab_manager = self.active_tab_manager
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'platform_window_id': platform_window_id(os_window_id),
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window, self_window)),
|
||||
'is_active': tm is active_tab_manager,
|
||||
'is_focused': current_focused_os_window_id() == os_window_id,
|
||||
'last_focused': os_window_id == last_focused_os_window_id(),
|
||||
'tabs': list(tm.list_tabs(self_window)),
|
||||
'wm_class': tm.wm_class,
|
||||
'wm_name': tm.wm_name
|
||||
}
|
||||
@@ -377,6 +376,10 @@ class Boss:
|
||||
return
|
||||
from .search_query_parser import search
|
||||
tab = self.active_tab
|
||||
if current_focused_os_window_id() <= 0:
|
||||
tm = self.os_window_map.get(last_focused_os_window_id())
|
||||
if tm is not None:
|
||||
tab = tm.active_tab
|
||||
|
||||
def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]:
|
||||
return {wid for wid in candidates if self.window_id_map[wid].matches_query(location, query, tab)}
|
||||
@@ -398,6 +401,8 @@ class Boss:
|
||||
return self.all_tabs
|
||||
from .search_query_parser import search
|
||||
tm = self.active_tab_manager
|
||||
if current_focused_os_window_id() <= 0:
|
||||
tm = self.os_window_map.get(last_focused_os_window_id()) or tm
|
||||
tim = {t.id: t for t in self.all_tabs}
|
||||
|
||||
def get_matches(location: str, query: str, candidates: Set[int]) -> Set[int]:
|
||||
@@ -424,7 +429,7 @@ class Boss:
|
||||
if tab is not self.active_tab:
|
||||
tm.set_active_tab(tab, for_keep_focus=window.tabref() if for_keep_focus else None)
|
||||
tab.set_active_window(w, for_keep_focus=window if for_keep_focus else None)
|
||||
if switch_os_window_if_needed and current_os_window() != os_window_id:
|
||||
if switch_os_window_if_needed and current_focused_os_window_id() != os_window_id:
|
||||
focus_os_window(os_window_id, True)
|
||||
return os_window_id
|
||||
return None
|
||||
@@ -534,9 +539,7 @@ class Boss:
|
||||
return True
|
||||
|
||||
def remote_cmd_permission_received(self, pcmd: Dict[str, Any], window_id: int, peer_id: int, choice: str) -> None:
|
||||
from .remote_control import (
|
||||
encode_response_for_peer, set_user_password_allowed
|
||||
)
|
||||
from .remote_control import encode_response_for_peer, set_user_password_allowed
|
||||
response: RCResponse = None
|
||||
window = self.window_id_map.get(window_id)
|
||||
choice = choice or 'r'
|
||||
@@ -587,9 +590,7 @@ class Boss:
|
||||
self.show_error(_('remote_control mapping failed'), tb)
|
||||
|
||||
def call_remote_control(self, active_window: Optional[Window], args: Tuple[str, ...]) -> 'ResponseType':
|
||||
from .rc.base import (
|
||||
PayloadGetter, command_for_name, parse_subcommand_cli
|
||||
)
|
||||
from .rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
|
||||
from .remote_control import parse_rc_args
|
||||
aa = list(args)
|
||||
silent = False
|
||||
@@ -1088,8 +1089,14 @@ class Boss:
|
||||
|
||||
@property
|
||||
def active_tab_manager(self) -> Optional[TabManager]:
|
||||
os_window_id = current_os_window()
|
||||
return None if os_window_id is None else self.os_window_map.get(os_window_id)
|
||||
os_window_id = current_focused_os_window_id()
|
||||
if os_window_id <= 0:
|
||||
os_window_id = last_focused_os_window_id()
|
||||
if os_window_id <= 0:
|
||||
q = current_os_window()
|
||||
if q is not None:
|
||||
os_window_id = q
|
||||
return self.os_window_map.get(os_window_id)
|
||||
|
||||
@property
|
||||
def active_tab(self) -> Optional[Tab]:
|
||||
@@ -1700,7 +1707,11 @@ class Boss:
|
||||
cmd = [kitty_exe(), '@']
|
||||
aw = self.active_window
|
||||
if aw is not None:
|
||||
kw['env'] = {'KITTY_SHELL_ACTIVE_WINDOW_ID': str(aw.id)}
|
||||
env = {'KITTY_SHELL_ACTIVE_WINDOW_ID': str(aw.id)}
|
||||
at = self.active_tab
|
||||
if at is not None:
|
||||
env['KITTY_SHELL_ACTIVE_TAB_ID'] = str(at.id)
|
||||
kw['env'] = env
|
||||
if window_type == 'tab':
|
||||
tab = self._new_tab(SpecialWindow(cmd, **kw))
|
||||
if tab is not None:
|
||||
@@ -2212,8 +2223,8 @@ class Boss:
|
||||
if bad_lines:
|
||||
self.show_bad_config_lines(bad_lines)
|
||||
self.apply_new_options(opts)
|
||||
from .open_actions import load_open_actions
|
||||
load_open_actions.clear_cached()
|
||||
from .open_actions import clear_caches
|
||||
clear_caches()
|
||||
|
||||
def safe_delete_temp_file(self, path: str) -> None:
|
||||
if is_path_in_temp_dir(path):
|
||||
@@ -2244,9 +2255,7 @@ class Boss:
|
||||
log_error(f'Failed to process update check data {raw!r}, with error: {e}')
|
||||
|
||||
def dbus_notification_callback(self, activated: bool, a: int, b: Union[int, str]) -> None:
|
||||
from .notify import (
|
||||
dbus_notification_activated, dbus_notification_created
|
||||
)
|
||||
from .notify import dbus_notification_activated, dbus_notification_created
|
||||
if activated:
|
||||
assert isinstance(b, str)
|
||||
dbus_notification_activated(a, b)
|
||||
@@ -2280,9 +2289,7 @@ class Boss:
|
||||
map f5 set_colors --configured /path/to/some/config/file/colors.conf
|
||||
''')
|
||||
def set_colors(self, *args: str) -> None:
|
||||
from kitty.rc.base import (
|
||||
PayloadGetter, command_for_name, parse_subcommand_cli
|
||||
)
|
||||
from kitty.rc.base import PayloadGetter, command_for_name, parse_subcommand_cli
|
||||
from kitty.remote_control import parse_rc_args
|
||||
c = command_for_name('set_colors')
|
||||
try:
|
||||
|
||||
@@ -687,16 +687,14 @@ render_os_window(OSWindow *os_window, unsigned int active_window_id, color_type
|
||||
if (os_window->clear_count++ < 3) blank_os_window(os_window);
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
BorderRects *br = &tab->border_rects;
|
||||
bool static_live_resize_in_progress = os_window->live_resize.in_progress && OPT(resize_draw_strategy) == RESIZE_DRAW_STATIC;
|
||||
float x_ratio = 1, y_ratio = 1;
|
||||
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, all_windows_have_same_bg, os_window);
|
||||
br->is_dirty = false;
|
||||
bool static_live_resize_in_progress = os_window->live_resize.in_progress && OPT(resize_draw_strategy) == RESIZE_DRAW_STATIC;
|
||||
if (static_live_resize_in_progress) {
|
||||
x_ratio = (float) os_window->viewport_width / (float) os_window->live_resize.width;
|
||||
y_ratio = (float) os_window->viewport_height / (float) os_window->live_resize.height;
|
||||
}
|
||||
if (!static_live_resize_in_progress) {
|
||||
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;
|
||||
}
|
||||
if (TD.screen && os_window->num_tabs >= OPT(tab_bar_min_tabs)) draw_cells(TD.vao_idx, 0, &TD, x_ratio, y_ratio, os_window, true, false, NULL);
|
||||
for (unsigned int i = 0; i < tab->num_windows; i++) {
|
||||
Window *w = tab->windows + i;
|
||||
|
||||
@@ -862,6 +862,65 @@ cocoa_set_url_handler(PyObject UNUSED *self, PyObject *args) {
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cocoa_set_app_icon(PyObject UNUSED *self, PyObject *args) {
|
||||
@autoreleasepool {
|
||||
|
||||
const char *icon_path = NULL, *app_path = NULL;
|
||||
if (!PyArg_ParseTuple(args, "s|z", &icon_path, &app_path)) return NULL;
|
||||
if (!icon_path || icon_path[0] == '\0') {
|
||||
PyErr_SetString(PyExc_TypeError, "Empty icon file path");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NSString *custom_icon_path = [NSString stringWithUTF8String:icon_path];
|
||||
NSString *bundle_path = @"";
|
||||
if (!app_path) {
|
||||
bundle_path = [[NSBundle mainBundle] bundlePath];
|
||||
if (!bundle_path || bundle_path.length == 0) bundle_path = @"/Applications/kitty.app";
|
||||
} else if (app_path[0] != '\0') {
|
||||
bundle_path = [NSString stringWithUTF8String:app_path];
|
||||
}
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:custom_icon_path]) {
|
||||
PyErr_Format(PyExc_FileNotFoundError, "Icon file not found: %s", [custom_icon_path UTF8String]);
|
||||
return NULL;
|
||||
}
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:bundle_path]) {
|
||||
PyErr_Format(PyExc_FileNotFoundError, "Application bundle not found: %s", [bundle_path UTF8String]);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NSImage *icon_image = [[NSImage alloc] initWithContentsOfFile:custom_icon_path];
|
||||
BOOL result = [[NSWorkspace sharedWorkspace] setIcon:icon_image forFile:bundle_path options:NSExcludeQuickDrawElementsIconCreationOption];
|
||||
[icon_image release];
|
||||
if (result) Py_RETURN_NONE;
|
||||
PyErr_Format(PyExc_OSError, "Failed to set custom icon %s for %s", [custom_icon_path UTF8String], [bundle_path UTF8String]);
|
||||
return NULL;
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cocoa_set_dock_icon(PyObject UNUSED *self, PyObject *args) {
|
||||
@autoreleasepool {
|
||||
|
||||
const char *icon_path = NULL;
|
||||
if (!PyArg_ParseTuple(args, "s", &icon_path)) return NULL;
|
||||
if (!icon_path || icon_path[0] == '\0') {
|
||||
PyErr_SetString(PyExc_TypeError, "Empty icon file path");
|
||||
return NULL;
|
||||
}
|
||||
NSString *custom_icon_path = [NSString stringWithUTF8String:icon_path];
|
||||
if ([[NSFileManager defaultManager] fileExistsAtPath:custom_icon_path]) {
|
||||
NSImage *icon_image = [[[NSImage alloc] initWithContentsOfFile:custom_icon_path] autorelease];
|
||||
[NSApplication sharedApplication].applicationIconImage = icon_image;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
return NULL;
|
||||
|
||||
} // autoreleasepool
|
||||
}
|
||||
|
||||
static NSSound *beep_sound = nil;
|
||||
|
||||
static void
|
||||
@@ -923,6 +982,8 @@ static PyMethodDef module_methods[] = {
|
||||
{"cocoa_send_notification", (PyCFunction)cocoa_send_notification, METH_VARARGS, ""},
|
||||
{"cocoa_set_notification_activated_callback", (PyCFunction)set_notification_activated_callback, METH_O, ""},
|
||||
{"cocoa_set_url_handler", (PyCFunction)cocoa_set_url_handler, METH_VARARGS, ""},
|
||||
{"cocoa_set_app_icon", (PyCFunction)cocoa_set_app_icon, METH_VARARGS, ""},
|
||||
{"cocoa_set_dock_icon", (PyCFunction)cocoa_set_dock_icon, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import os
|
||||
import pwd
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from typing import TYPE_CHECKING, Any, Iterable, NamedTuple, Optional, Set
|
||||
from typing import TYPE_CHECKING, Any, Iterator, NamedTuple, Optional, Set
|
||||
|
||||
from .types import run_once
|
||||
|
||||
@@ -22,7 +22,7 @@ class Version(NamedTuple):
|
||||
|
||||
appname: str = 'kitty'
|
||||
kitty_face = '🐱'
|
||||
version: Version = Version(0, 26, 3)
|
||||
version: Version = Version(0, 26, 5)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
@@ -219,15 +219,28 @@ def running_in_kitty(set_val: Optional[bool] = None) -> bool:
|
||||
return bool(getattr(running_in_kitty, 'ans', False))
|
||||
|
||||
|
||||
def list_kitty_resources(package: str = 'kitty') -> Iterable[str]:
|
||||
from importlib.resources import contents
|
||||
return contents(package)
|
||||
def list_kitty_resources(package: str = 'kitty') -> Iterator[str]:
|
||||
try:
|
||||
if sys.version_info[:2] < (3, 10):
|
||||
raise ImportError('importlib.resources.files() doesnt work with frozen builds on python 3.9')
|
||||
from importlib.resources import files
|
||||
except ImportError:
|
||||
from importlib.resources import contents
|
||||
return iter(contents(package))
|
||||
else:
|
||||
return (path.name for path in files(package).iterdir())
|
||||
|
||||
|
||||
def read_kitty_resource(name: str, package_name: str = 'kitty') -> bytes:
|
||||
from importlib.resources import read_binary
|
||||
|
||||
return read_binary(package_name, name)
|
||||
try:
|
||||
if sys.version_info[:2] < (3, 10):
|
||||
raise ImportError('importlib.resources.files() doesnt work with frozen builds on python 3.9')
|
||||
from importlib.resources import files
|
||||
except ImportError:
|
||||
from importlib.resources import read_binary
|
||||
return read_binary(package_name, name)
|
||||
else:
|
||||
return (files(package_name) / name).read_bytes()
|
||||
|
||||
|
||||
def website_url(doc_name: str = '', website: str = 'https://sw.kovidgoyal.net/kitty/') -> str:
|
||||
|
||||
@@ -65,7 +65,7 @@ typedef enum MouseTrackingModes { NO_TRACKING, BUTTON_MODE, MOTION_MODE, ANY_MOD
|
||||
typedef enum MouseTrackingProtocols { NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL, SGR_PIXEL_PROTOCOL} MouseTrackingProtocol;
|
||||
typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
|
||||
typedef enum { NONE, MENUBAR, WINDOW, ALL } WindowTitleIn;
|
||||
typedef enum { TILING, SCALED, MIRRORED, CLAMPED } BackgroundImageLayout;
|
||||
typedef enum { TILING, SCALED, MIRRORED, CLAMPED, CENTER_CLAMPED } BackgroundImageLayout;
|
||||
typedef struct ImageAnchorPosition {
|
||||
float canvas_x, canvas_y, image_x, image_y;
|
||||
} ImageAnchorPosition;
|
||||
|
||||
26
kitty/emoji.h
generated
26
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2022-01-05
|
||||
// unicode data, built from the unicode standard on: 2022-09-29
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
@@ -739,13 +739,13 @@ is_symbol(char_type code) {
|
||||
return true;
|
||||
case 0x1f300 ... 0x1f6d7:
|
||||
return true;
|
||||
case 0x1f6dd ... 0x1f6ec:
|
||||
case 0x1f6dc ... 0x1f6ec:
|
||||
return true;
|
||||
case 0x1f6f0 ... 0x1f6fc:
|
||||
return true;
|
||||
case 0x1f700 ... 0x1f773:
|
||||
case 0x1f700 ... 0x1f776:
|
||||
return true;
|
||||
case 0x1f780 ... 0x1f7d8:
|
||||
case 0x1f77b ... 0x1f7d9:
|
||||
return true;
|
||||
case 0x1f7e0 ... 0x1f7eb:
|
||||
return true;
|
||||
@@ -767,23 +767,19 @@ is_symbol(char_type code) {
|
||||
return true;
|
||||
case 0x1fa60 ... 0x1fa6d:
|
||||
return true;
|
||||
case 0x1fa70 ... 0x1fa74:
|
||||
case 0x1fa70 ... 0x1fa7c:
|
||||
return true;
|
||||
case 0x1fa78 ... 0x1fa7c:
|
||||
case 0x1fa80 ... 0x1fa88:
|
||||
return true;
|
||||
case 0x1fa80 ... 0x1fa86:
|
||||
case 0x1fa90 ... 0x1fabd:
|
||||
return true;
|
||||
case 0x1fa90 ... 0x1faac:
|
||||
case 0x1fabf ... 0x1fac5:
|
||||
return true;
|
||||
case 0x1fab0 ... 0x1faba:
|
||||
case 0x1face ... 0x1fadb:
|
||||
return true;
|
||||
case 0x1fac0 ... 0x1fac5:
|
||||
case 0x1fae0 ... 0x1fae8:
|
||||
return true;
|
||||
case 0x1fad0 ... 0x1fad9:
|
||||
return true;
|
||||
case 0x1fae0 ... 0x1fae7:
|
||||
return true;
|
||||
case 0x1faf0 ... 0x1faf6:
|
||||
case 0x1faf0 ... 0x1faf8:
|
||||
return true;
|
||||
case 0x1fb00 ... 0x1fb92:
|
||||
return true;
|
||||
|
||||
@@ -730,7 +730,15 @@ def cocoa_get_lang() -> Optional[str]:
|
||||
pass
|
||||
|
||||
|
||||
def cocoa_set_url_handler(url_scheme: str, bundle_id: Optional[str]) -> None:
|
||||
def cocoa_set_url_handler(url_scheme: str, bundle_id: Optional[str] = None) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def cocoa_set_app_icon(icon_path: str, app_path: Optional[str] = None) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def cocoa_set_dock_icon(icon_path: str) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@@ -778,6 +786,14 @@ def current_os_window() -> Optional[int]:
|
||||
pass
|
||||
|
||||
|
||||
def last_focused_os_window_id() -> int:
|
||||
pass
|
||||
|
||||
|
||||
def current_focused_os_window_id() -> int:
|
||||
pass
|
||||
|
||||
|
||||
def cocoa_set_menubar_title(title: str) -> None:
|
||||
pass
|
||||
|
||||
@@ -1475,3 +1491,4 @@ def clearenv() -> None: ...
|
||||
def set_clipboard_data_types(ct: int, mime_types: Tuple[str, ...]) -> None: ...
|
||||
def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes], None]) -> None: ...
|
||||
def run_with_activation_token(func: Callable[[str], None]) -> None: ...
|
||||
def make_x11_window_a_dock_window(x11_window_id: int, strut: Tuple[int, int, int, int, int, int, int, int, int, int, int, int]) -> None: ...
|
||||
|
||||
6
kitty/glfw-wrapper.c
generated
6
kitty/glfw-wrapper.c
generated
@@ -467,6 +467,12 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwSetX11LaunchCommand_impl) = dlsym(handle, "glfwSetX11LaunchCommand");
|
||||
if (glfwSetX11LaunchCommand_impl == NULL) dlerror(); // clear error indicator
|
||||
|
||||
*(void **) (&glfwSetX11WindowAsDock_impl) = dlsym(handle, "glfwSetX11WindowAsDock");
|
||||
if (glfwSetX11WindowAsDock_impl == NULL) dlerror(); // clear error indicator
|
||||
|
||||
*(void **) (&glfwSetX11WindowStrut_impl) = dlsym(handle, "glfwSetX11WindowStrut");
|
||||
if (glfwSetX11WindowStrut_impl == NULL) dlerror(); // clear error indicator
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
8
kitty/glfw-wrapper.h
generated
8
kitty/glfw-wrapper.h
generated
@@ -2230,4 +2230,12 @@ typedef int (*glfwSetX11LaunchCommand_func)(GLFWwindow*, char**, int);
|
||||
GFW_EXTERN glfwSetX11LaunchCommand_func glfwSetX11LaunchCommand_impl;
|
||||
#define glfwSetX11LaunchCommand glfwSetX11LaunchCommand_impl
|
||||
|
||||
typedef void (*glfwSetX11WindowAsDock_func)(int32_t);
|
||||
GFW_EXTERN glfwSetX11WindowAsDock_func glfwSetX11WindowAsDock_impl;
|
||||
#define glfwSetX11WindowAsDock glfwSetX11WindowAsDock_impl
|
||||
|
||||
typedef void (*glfwSetX11WindowStrut_func)(int32_t, uint32_t[12]);
|
||||
GFW_EXTERN glfwSetX11WindowStrut_func glfwSetX11WindowStrut_impl;
|
||||
#define glfwSetX11WindowStrut glfwSetX11WindowStrut_impl
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
|
||||
27
kitty/glfw.c
27
kitty/glfw.c
@@ -312,14 +312,6 @@ dpi_change_callback(GLFWwindow *w, float x_scale UNUSED, float y_scale UNUSED) {
|
||||
window->live_resize.last_resize_event_at = monotonic();
|
||||
global_state.callback_os_window = NULL;
|
||||
request_tick_callback();
|
||||
if (global_state.is_wayland) {
|
||||
// because of the stupidity of Wayland design, the GLFW wayland backend
|
||||
// will need to swap buffers immediately after a scale change to ensure
|
||||
// the buffer size is a multiple of the scale. So blank the new buffer to
|
||||
// ensure we dont leak any unintialized pixels to the screen. The OpenGL viewport
|
||||
// will already have been resized to its new size in framebuffer_size_callback
|
||||
blank_os_window(window);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1539,8 +1531,8 @@ request_frame_render(OSWindow *w) {
|
||||
// Some Wayland compositors are too fragile to handle multiple
|
||||
// render frame requests, see https://github.com/kovidgoyal/kitty/issues/2329
|
||||
if (w->render_state != RENDER_FRAME_REQUESTED) {
|
||||
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
|
||||
w->render_state = RENDER_FRAME_REQUESTED;
|
||||
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1686,6 +1678,22 @@ get_clipboard_mime(PyObject *self UNUSED, PyObject *args) {
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
make_x11_window_a_dock_window(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
int x11_window_id;
|
||||
PyObject *dims;
|
||||
if (!PyArg_ParseTuple(args, "iO!", &x11_window_id, &PyTuple_Type, &dims)) return NULL;
|
||||
if (PyTuple_GET_SIZE(dims) != 12 ) { PyErr_SetString(PyExc_TypeError, "dimesions must be a tuple of length 12"); return NULL; }
|
||||
if (!glfwSetX11WindowAsDock) { PyErr_SetString(PyExc_RuntimeError, "Failed to load glfwGetX11Window"); return NULL; }
|
||||
uint32_t dimensions[12];
|
||||
for (Py_ssize_t i = 0; i < 12; i++) dimensions[i] = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(dims, i));
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
glfwSetX11WindowAsDock(x11_window_id);
|
||||
glfwSetX11WindowStrut(x11_window_id, dimensions);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
@@ -1704,6 +1712,7 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(x11_display, METH_NOARGS),
|
||||
METHODB(get_click_interval, METH_NOARGS),
|
||||
METHODB(x11_window_id, METH_O),
|
||||
METHODB(make_x11_window_a_dock_window, METH_VARARGS),
|
||||
METHODB(strip_csi, METH_O),
|
||||
#ifndef __APPLE__
|
||||
METHODB(dbus_send_notification, METH_VARARGS),
|
||||
|
||||
@@ -414,6 +414,16 @@ class Splits(Layout):
|
||||
def pairs_root(self, root: Pair) -> None:
|
||||
self._pairs_root = root
|
||||
|
||||
def remove_windows(self, *windows_to_remove: int) -> None:
|
||||
root = self.pairs_root
|
||||
for pair in root.self_and_descendants():
|
||||
pair.remove_windows(windows_to_remove)
|
||||
root.collapse_redundant_pairs()
|
||||
if root.one is None or root.two is None:
|
||||
q = root.one or root.two
|
||||
if isinstance(q, Pair):
|
||||
self.pairs_root = q
|
||||
|
||||
def do_layout(self, all_windows: WindowList) -> None:
|
||||
groups = tuple(all_windows.iter_all_layoutable_groups())
|
||||
window_count = len(groups)
|
||||
@@ -421,15 +431,8 @@ class Splits(Layout):
|
||||
all_present_window_ids = frozenset(w.id for w in groups)
|
||||
already_placed_window_ids = frozenset(root.all_window_ids())
|
||||
windows_to_remove = already_placed_window_ids - all_present_window_ids
|
||||
|
||||
if windows_to_remove:
|
||||
for pair in root.self_and_descendants():
|
||||
pair.remove_windows(windows_to_remove)
|
||||
root.collapse_redundant_pairs()
|
||||
if root.one is None or root.two is None:
|
||||
q = root.one or root.two
|
||||
if isinstance(q, Pair):
|
||||
root = self.pairs_root = q
|
||||
self.remove_windows(*windows_to_remove)
|
||||
id_window_map = {w.id: w for w in groups}
|
||||
id_idx_map = {w.id: i for i, w in enumerate(groups)}
|
||||
windows_to_add = all_present_window_ids - already_placed_window_ids
|
||||
@@ -552,6 +555,23 @@ class Splits(Layout):
|
||||
if swap:
|
||||
pair.one, pair.two = pair.two, pair.one
|
||||
return True
|
||||
elif action_name == 'move_to_screen_edge':
|
||||
args = args or ('left',)
|
||||
which = args[0]
|
||||
horizontal = which in ('left', 'right')
|
||||
wg = all_windows.active_group
|
||||
if wg is not None:
|
||||
self.remove_windows(wg.id)
|
||||
new_root = Pair(horizontal)
|
||||
if which in ('left', 'top'):
|
||||
new_root.balanced_add(wg.id)
|
||||
new_root.two = self.pairs_root
|
||||
else:
|
||||
new_root.one = self.pairs_root
|
||||
new_root.two = wg.id
|
||||
self.pairs_root = new_root
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
def layout_state(self) -> Dict[str, Any]:
|
||||
@@ -566,7 +586,7 @@ class Splits(Layout):
|
||||
ans['one'] = p.one
|
||||
if isinstance(p.two, Pair):
|
||||
ans['two'] = add_pair(p.two)
|
||||
elif p.one is not None:
|
||||
elif p.two is not None:
|
||||
ans['two'] = p.two
|
||||
return ans
|
||||
|
||||
|
||||
@@ -16,14 +16,13 @@ from .cli_stub import CLIOptions
|
||||
from .conf.utils import BadLine
|
||||
from .config import cached_values_for
|
||||
from .constants import (
|
||||
appname, beam_cursor_data_file, clear_handled_signals, config_dir,
|
||||
glfw_path, is_macos, is_wayland, kitty_exe, logo_png_file,
|
||||
running_in_kitty, website_url
|
||||
appname, beam_cursor_data_file, clear_handled_signals, config_dir, glfw_path,
|
||||
is_macos, is_wayland, kitty_exe, logo_png_file, running_in_kitty, website_url,
|
||||
)
|
||||
from .fast_data_types import (
|
||||
GLFW_IBEAM_CURSOR, GLFW_MOD_ALT, GLFW_MOD_SHIFT, SingleKey,
|
||||
create_os_window, free_font_data, glfw_init, glfw_terminate, load_png_data,
|
||||
set_custom_cursor, set_default_window_icon, set_options
|
||||
GLFW_IBEAM_CURSOR, GLFW_MOD_ALT, GLFW_MOD_SHIFT, SingleKey, create_os_window,
|
||||
free_font_data, glfw_init, glfw_terminate, load_png_data, set_custom_cursor,
|
||||
set_default_window_icon, set_options,
|
||||
)
|
||||
from .fonts.box_drawing import set_scale
|
||||
from .fonts.render import set_font_family
|
||||
@@ -33,8 +32,8 @@ from .os_window_size import initial_window_size_func
|
||||
from .prewarm import PrewarmProcess, fork_prewarm_process
|
||||
from .session import create_sessions, get_os_window_sizing_data
|
||||
from .utils import (
|
||||
cleanup_ssh_control_masters, detach, expandvars, log_error,
|
||||
single_instance, startup_notification_handler, unix_socket_paths
|
||||
cleanup_ssh_control_masters, detach, expandvars, log_error, single_instance,
|
||||
startup_notification_handler, unix_socket_paths,
|
||||
)
|
||||
from .window import load_shader_programs
|
||||
|
||||
@@ -135,6 +134,32 @@ def get_macos_shortcut_for(
|
||||
return ans
|
||||
|
||||
|
||||
def safe_mtime(path: str) -> Optional[float]:
|
||||
with suppress(OSError):
|
||||
return os.path.getmtime(path)
|
||||
return None
|
||||
|
||||
|
||||
def set_macos_app_custom_icon() -> None:
|
||||
for name in ('kitty.app.icns', 'kitty.app.png'):
|
||||
icon_path = os.path.join(config_dir, name)
|
||||
custom_icon_mtime = safe_mtime(icon_path)
|
||||
if custom_icon_mtime is not None:
|
||||
from .fast_data_types import cocoa_set_app_icon, cocoa_set_dock_icon
|
||||
krd = getattr(sys, 'kitty_run_data')
|
||||
bundle_path = os.path.dirname(os.path.dirname(krd.get('bundle_exe_dir')))
|
||||
icon_sentinel = os.path.join(bundle_path, 'Icon\r')
|
||||
sentinel_mtime = safe_mtime(icon_sentinel)
|
||||
if sentinel_mtime is None or sentinel_mtime < custom_icon_mtime:
|
||||
cocoa_set_app_icon(icon_path)
|
||||
# macOS Dock does not reload icons until it is restarted, so we set
|
||||
# the application icon here. This will revert when kitty quits, but
|
||||
# cant be helped since there appears to be no way to get the dock
|
||||
# to reload short of killing it.
|
||||
cocoa_set_dock_icon(icon_path)
|
||||
break
|
||||
|
||||
|
||||
def set_x11_window_icon() -> None:
|
||||
# max icon size on X11 64bits is 128x128
|
||||
path, ext = os.path.splitext(logo_png_file)
|
||||
@@ -167,8 +192,11 @@ def _run_app(opts: Options, args: CLIOptions, prewarm: PrewarmProcess, bad_lines
|
||||
val = get_macos_shortcut_for(func_map, f'open_url {website_url()}', lookup_name='open_kitty_website')
|
||||
if val is not None:
|
||||
global_shortcuts['open_kitty_website'] = val
|
||||
if is_macos and opts.macos_custom_beam_cursor:
|
||||
set_custom_ibeam_cursor()
|
||||
|
||||
if opts.macos_custom_beam_cursor:
|
||||
set_custom_ibeam_cursor()
|
||||
set_macos_app_custom_icon()
|
||||
|
||||
if not is_wayland() and not is_macos: # no window icons on wayland
|
||||
set_x11_window_icon()
|
||||
with cached_values_for(run_app.cached_values_name) as cached_values:
|
||||
@@ -208,7 +236,7 @@ class AppRunner:
|
||||
free_font_data() # must free font data before glfw/freetype/fontconfig/opengl etc are finalized
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import (
|
||||
cocoa_set_notification_activated_callback
|
||||
cocoa_set_notification_activated_callback,
|
||||
)
|
||||
cocoa_set_notification_activated_callback(None)
|
||||
|
||||
|
||||
@@ -5,27 +5,44 @@
|
||||
# in kitty, when using the spawn launcher.
|
||||
|
||||
|
||||
import os
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from multiprocessing import util # type: ignore
|
||||
from multiprocessing import util
|
||||
from multiprocessing import context, get_all_start_methods, get_context, spawn
|
||||
from typing import Any, Callable, List, Optional, Tuple, Union
|
||||
from typing import Any, Callable, Sequence, Optional, Tuple, Union, TYPE_CHECKING
|
||||
|
||||
from .constants import kitty_exe
|
||||
|
||||
orig_spawn_passfds = util.spawnv_passfds
|
||||
orig_executable = spawn.get_executable()
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pickle import PickleBuffer
|
||||
from array import array
|
||||
from mmap import mmap
|
||||
from ctypes import _CData
|
||||
from typing import SupportsInt, SupportsIndex, Protocol
|
||||
|
||||
def spawnv_passfds(path: str, args: List[str], passfds: List[int]) -> Any:
|
||||
class SupportsTrunc(Protocol):
|
||||
def __trunc__(self) -> int: ...
|
||||
|
||||
ArgsType = Sequence[
|
||||
Union[str, Union[bytes, Union[bytearray, memoryview, array[Any], mmap, _CData, PickleBuffer]], SupportsInt, SupportsIndex, SupportsTrunc]
|
||||
]
|
||||
else:
|
||||
ArgsType = Sequence[str]
|
||||
|
||||
|
||||
def spawnv_passfds(path: bytes, args: ArgsType, passfds: Sequence[int]) -> int:
|
||||
if '-c' in args:
|
||||
idx = args.index('-c')
|
||||
patched_args = [spawn.get_executable(), '+runpy'] + args[idx + 1:]
|
||||
patched_args = [spawn.get_executable(), '+runpy'] + list(args)[idx + 1:]
|
||||
else:
|
||||
idx = args.index('--multiprocessing-fork')
|
||||
prog = 'from multiprocessing.spawn import spawn_main; spawn_main(%s)'
|
||||
prog %= ', '.join(item for item in args[idx+1:])
|
||||
prog %= ', '.join(str(item) for item in args[idx+1:])
|
||||
patched_args = [spawn.get_executable(), '+runpy', prog]
|
||||
return orig_spawn_passfds(kitty_exe(), patched_args, passfds)
|
||||
return orig_spawn_passfds(os.fsencode(kitty_exe()), patched_args, passfds)
|
||||
|
||||
|
||||
def monkey_patch_multiprocessing() -> None:
|
||||
@@ -64,7 +81,7 @@ def test_spawn() -> None:
|
||||
q = ctx.Queue()
|
||||
p = ctx.Process(target=q.put, args=('hello',))
|
||||
p.start()
|
||||
x = q.get(timeout=2)
|
||||
x = q.get(timeout=8)
|
||||
assert x == 'hello'
|
||||
p.join()
|
||||
finally:
|
||||
|
||||
@@ -221,6 +221,11 @@ def load_launch_actions() -> Tuple[OpenAction, ...]:
|
||||
return tuple(parse(f))
|
||||
|
||||
|
||||
def clear_caches() -> None:
|
||||
load_open_actions.clear_cached()
|
||||
load_launch_actions.clear_cached()
|
||||
|
||||
|
||||
@run_once
|
||||
def default_open_actions() -> Tuple[OpenAction, ...]:
|
||||
return tuple(parse('''\
|
||||
|
||||
@@ -402,9 +402,9 @@ opt('url_style', 'curly',
|
||||
opt('open_url_with', 'default',
|
||||
option_type='to_cmdline',
|
||||
long_text='''
|
||||
The program to open clicked URLs. The special value :code:`default` means to
|
||||
use the operating system's default URL handler (:program:`open` on macOS and
|
||||
:program:`xdg-open` on Linux).
|
||||
The program to open clicked URLs. The special value :code:`default` with first look for any URL handlers defined via
|
||||
the :doc:`open_actions` facility and if non are found, it will use the Operating System's
|
||||
default URL handler (:program:`open` on macOS and :program:`xdg-open` on Linux).
|
||||
'''
|
||||
)
|
||||
|
||||
@@ -1285,10 +1285,10 @@ opt('background_image', 'none',
|
||||
)
|
||||
|
||||
opt('background_image_layout', 'tiled',
|
||||
choices=('mirror-tiled', 'scaled', 'tiled', 'clamped'), ctype='bglayout',
|
||||
choices=('mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered'), ctype='bglayout',
|
||||
long_text='''
|
||||
Whether to tile, scale or clamp the background image. The value can be one of
|
||||
:code:`tiled`, :code:`mirror-tiled`, :code:`scaled`, :code:`clamped`.
|
||||
:code:`tiled`, :code:`mirror-tiled`, :code:`scaled`, :code:`clamped` or :code:`centered`.
|
||||
'''
|
||||
)
|
||||
|
||||
@@ -1310,11 +1310,10 @@ this option by reloading the config is not supported.
|
||||
opt('background_tint', '0.0',
|
||||
option_type='unit_float', ctype='float',
|
||||
long_text='''
|
||||
How much to tint the background image by the background color. The tint is
|
||||
applied only under the text area, not margin/borders. This option makes it
|
||||
easier to read the text. Tinting is done using the current background color for
|
||||
each window. This option applies only if :opt:`background_opacity` is set and
|
||||
transparent windows are supported or :opt:`background_image` is set.
|
||||
How much to tint the background image by the background color. This option
|
||||
makes it easier to read the text. Tinting is done using the current background
|
||||
color for each window. This option applies only if :opt:`background_opacity` is
|
||||
set and transparent windows are supported or :opt:`background_image` is set.
|
||||
'''
|
||||
)
|
||||
|
||||
|
||||
2
kitty/options/parse.py
generated
2
kitty/options/parse.py
generated
@@ -73,7 +73,7 @@ class Parser:
|
||||
raise ValueError(f"The value {val} is not a valid choice for background_image_layout")
|
||||
ans["background_image_layout"] = val
|
||||
|
||||
choices_for_background_image_layout = frozenset(('mirror-tiled', 'scaled', 'tiled', 'clamped'))
|
||||
choices_for_background_image_layout = frozenset(('mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered'))
|
||||
|
||||
def background_image_linear(self, val: str, ans: typing.Dict[str, typing.Any]) -> None:
|
||||
ans['background_image_linear'] = to_bool(val)
|
||||
|
||||
@@ -64,7 +64,9 @@ bglayout(PyObject *layout_name) {
|
||||
case 't': return TILING;
|
||||
case 'm': return MIRRORED;
|
||||
case 's': return SCALED;
|
||||
case 'c': return CLAMPED;
|
||||
case 'c': {
|
||||
return name[1] == 'l' ? CLAMPED : CENTER_CLAMPED;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
return TILING;
|
||||
|
||||
2
kitty/options/types.py
generated
2
kitty/options/types.py
generated
@@ -15,7 +15,7 @@ import kitty.types
|
||||
if typing.TYPE_CHECKING:
|
||||
choices_for_allow_cloning = typing.Literal['yes', 'y', 'true', 'no', 'n', 'false', 'ask']
|
||||
choices_for_allow_remote_control = typing.Literal['password', 'socket-only', 'socket', 'no', 'n', 'false', 'yes', 'y', 'true']
|
||||
choices_for_background_image_layout = typing.Literal['mirror-tiled', 'scaled', 'tiled', 'clamped']
|
||||
choices_for_background_image_layout = typing.Literal['mirror-tiled', 'scaled', 'tiled', 'clamped', 'centered']
|
||||
choices_for_default_pointer_shape = typing.Literal['arrow', 'beam', 'hand']
|
||||
choices_for_linux_display_server = typing.Literal['auto', 'wayland', 'x11']
|
||||
choices_for_macos_colorspace = typing.Literal['srgb', 'default', 'displayp3']
|
||||
|
||||
@@ -38,7 +38,7 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
error_events = select.POLLERR | select.POLLNVAL | select.POLLHUP
|
||||
TIMEOUT = 15.0 if os.environ.get('CI') == 'true' else 5.0
|
||||
TIMEOUT = 5.0
|
||||
|
||||
|
||||
def restore_python_signal_handlers() -> None:
|
||||
|
||||
@@ -100,7 +100,7 @@ or a name and value, for example, :code:`env:MY_ENV_VAR=2`.
|
||||
The field :code:`state` matches on the state of the window. Supported states are:
|
||||
:code:`active`, :code:`focused`, :code:`needs_attention`, :code:`parent_active` and :code:`parent_focused`.
|
||||
Active windows are the windows that are active in their parent tab. There is only one focused window and it is the
|
||||
window to which keyboard events are delivered.
|
||||
window to which keyboard events are delivered. If no window is focused, the last focused window is matched.
|
||||
|
||||
Note that you can use the :ref:`kitty @ ls <at-ls>` command to get a list of windows.
|
||||
'''
|
||||
@@ -134,7 +134,7 @@ variables are matched.
|
||||
The field :code:`state` matches on the state of the tab. Supported states are:
|
||||
:code:`active`, :code:`focused`, :code:`needs_attention`, :code:`parent_active` and :code:`parent_focused`.
|
||||
Active tabs are the tabs that are active in their parent OS window. There is only one focused tab
|
||||
and it is the tab to which keyboard events are delivered.
|
||||
and it is the tab to which keyboard events are delivered. If no tab is focused, the last focused tab is matched.
|
||||
|
||||
Note that you can use the :ref:`kitty @ ls <at-ls>` command to get a list of tabs.
|
||||
'''
|
||||
|
||||
@@ -17,6 +17,7 @@ class FocusTab(RemoteCommand):
|
||||
|
||||
'''
|
||||
match/str: The tab to focus
|
||||
no_response/bool: Boolean indicating whether to wait for a response
|
||||
'''
|
||||
|
||||
short_desc = 'Focus the specified tab'
|
||||
@@ -32,7 +33,7 @@ using this option means that you will not be notified of failures.
|
||||
argspec = ''
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match}
|
||||
return {'match': opts.match, 'no_response': opts.no_response}
|
||||
|
||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||
for tab in self.tabs_for_match_payload(boss, window, payload_get):
|
||||
|
||||
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
|
||||
class FocusWindow(RemoteCommand):
|
||||
'''
|
||||
match/str: The window to focus
|
||||
no_response/bool: Boolean indicating whether to wait for a response
|
||||
'''
|
||||
|
||||
short_desc = 'Focus the specified window'
|
||||
|
||||
@@ -17,6 +17,7 @@ class LastUsedLayout(RemoteCommand):
|
||||
'''
|
||||
match/str: Which tab to change the layout of
|
||||
all/bool: Boolean to match all tabs
|
||||
no_response/bool: Boolean indicating whether to wait for a response
|
||||
'''
|
||||
|
||||
short_desc = 'Switch to the last used layout'
|
||||
@@ -37,7 +38,7 @@ the command will exit with a success code.
|
||||
''' + '\n\n\n' + MATCH_TAB_OPTION
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
return {'match': opts.match, 'all': opts.all}
|
||||
return {'match': opts.match, 'all': opts.all, 'no_response': opts.no_response}
|
||||
|
||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||
for tab in self.tabs_for_match_payload(boss, window, payload_get):
|
||||
|
||||
@@ -81,7 +81,7 @@ rewrap_inner(BufType *src, BufType *dest, const index_type src_limit, HistoryBuf
|
||||
for (TrackCursor *t = track; !t->is_sentinel; t++) {
|
||||
if (t->is_tracked_line && src_x <= t->x && t->x < src_x + num) {
|
||||
t->y = dest_y;
|
||||
t->x = dest_x + (t->x - src_x + 1);
|
||||
t->x = dest_x + (t->x - src_x + (t->x > 0));
|
||||
}
|
||||
}
|
||||
src_x += num; dest_x += num;
|
||||
|
||||
@@ -368,6 +368,7 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
|
||||
if (n == NULL) return false;
|
||||
Py_CLEAR(self->main_linebuf); self->main_linebuf = n;
|
||||
if (is_main) setup_cursor(cursor);
|
||||
/* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor.after.x, cursor.after.y, cursor.is_beyond_content); */
|
||||
setup_cursor(main_saved_cursor);
|
||||
grman_resize(self->main_grman, self->lines, lines, self->columns, columns);
|
||||
|
||||
@@ -396,7 +397,6 @@ screen_resize(Screen *self, unsigned int lines, unsigned int columns) {
|
||||
clear_selection(&self->selections);
|
||||
clear_selection(&self->url_ranges);
|
||||
self->last_visited_prompt.is_set = false;
|
||||
/* printf("old_cursor: (%u, %u) new_cursor: (%u, %u) beyond_content: %d\n", self->cursor->x, self->cursor->y, cursor_x, cursor_y, cursor_is_beyond_content); */
|
||||
#define S(c, w) c->x = MIN(w.after.x, self->columns - 1); c->y = MIN(w.after.y, self->lines - 1);
|
||||
S(self->cursor, cursor);
|
||||
S((&(self->main_savepoint.cursor)), main_saved_cursor);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "window_logo.h"
|
||||
|
||||
#define BLEND_ONTO_OPAQUE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blending onto opaque colors
|
||||
#define BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // blending onto opaque colors with final color having alpha 1
|
||||
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
|
||||
|
||||
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BLIT_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, NUM_PROGRAMS };
|
||||
@@ -184,7 +185,7 @@ typedef struct {
|
||||
static CellProgramLayout cell_program_layouts[NUM_PROGRAMS];
|
||||
static ssize_t blit_vertex_array;
|
||||
typedef struct {
|
||||
GLint image_location, tiled_location, sizes_location, opacity_location, premult_location;
|
||||
GLint image_location, tiled_location, sizes_location, positions_location, opacity_location, premult_location;
|
||||
} BGImageProgramLayout;
|
||||
static BGImageProgramLayout bgimage_program_layout = {0};
|
||||
typedef struct {
|
||||
@@ -212,6 +213,7 @@ init_cell_program(void) {
|
||||
bgimage_program_layout.image_location = get_uniform_location(BGIMAGE_PROGRAM, "image");
|
||||
bgimage_program_layout.opacity_location = get_uniform_location(BGIMAGE_PROGRAM, "opacity");
|
||||
bgimage_program_layout.sizes_location = get_uniform_location(BGIMAGE_PROGRAM, "sizes");
|
||||
bgimage_program_layout.positions_location = get_uniform_location(BGIMAGE_PROGRAM, "positions");
|
||||
bgimage_program_layout.tiled_location = get_uniform_location(BGIMAGE_PROGRAM, "tiled");
|
||||
bgimage_program_layout.premult_location = get_uniform_location(BGIMAGE_PROGRAM, "premult");
|
||||
tint_program_layout.tint_color_location = get_uniform_location(TINT_PROGRAM, "tint_color");
|
||||
@@ -425,16 +427,29 @@ draw_bg(OSWindow *w) {
|
||||
glUniform1i(bgimage_program_layout.image_location, BGIMAGE_UNIT);
|
||||
glUniform1f(bgimage_program_layout.opacity_location, OPT(background_opacity));
|
||||
glUniform4f(bgimage_program_layout.sizes_location,
|
||||
(GLfloat)w->window_width, (GLfloat)w->window_height, (GLfloat)w->bgimage->width, (GLfloat)w->bgimage->height);
|
||||
(GLfloat)w->viewport_width, (GLfloat)w->viewport_height, (GLfloat)w->bgimage->width, (GLfloat)w->bgimage->height);
|
||||
glUniform1f(bgimage_program_layout.premult_location, w->is_semi_transparent ? 1.f : 0.f);
|
||||
GLfloat tiled = 0.f;;
|
||||
GLfloat left = -1.0, top = 1.0, right = 1.0, bottom = -1.0;
|
||||
switch (OPT(background_image_layout)) {
|
||||
case TILING: case MIRRORED: case CLAMPED:
|
||||
tiled = 1.f; break;
|
||||
case SCALED:
|
||||
tiled = 0.f; break;
|
||||
case CENTER_CLAMPED:
|
||||
tiled = 1.f;
|
||||
if (w->viewport_width > (int)w->bgimage->width) {
|
||||
GLfloat frac = (w->viewport_width - w->bgimage->width) / (GLfloat)w->viewport_width;
|
||||
left += frac; right += frac;
|
||||
}
|
||||
if (w->viewport_height > (int)w->bgimage->height) {
|
||||
GLfloat frac = (w->viewport_height - w->bgimage->height) / (GLfloat)w->viewport_height;
|
||||
top -= frac; bottom -= frac;
|
||||
}
|
||||
break;
|
||||
}
|
||||
glUniform1f(bgimage_program_layout.tiled_location, tiled);
|
||||
glUniform4f(bgimage_program_layout.positions_location, left, top, right, bottom);
|
||||
glActiveTexture(GL_TEXTURE0 + BGIMAGE_UNIT);
|
||||
glBindTexture(GL_TEXTURE_2D, w->bgimage->texture_id);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
@@ -517,12 +532,12 @@ has_bgimage(OSWindow *w) {
|
||||
|
||||
static void
|
||||
draw_tint(bool premult, Screen *screen, const CellRenderData *crd) {
|
||||
if (premult) { BLEND_PREMULT } else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT }
|
||||
bind_program(TINT_PROGRAM);
|
||||
color_type window_bg = colorprofile_to_color(screen->color_profile, screen->color_profile->overridden.default_bg, screen->color_profile->configured.default_bg).rgb;
|
||||
#define C(shift) ((((GLfloat)((window_bg >> shift) & 0xFF)) / 255.0f))
|
||||
float alpha = OPT(background_tint);
|
||||
if (premult) glUniform4f(tint_program_layout.tint_color_location, C(16) * alpha, C(8) * alpha, C(0) * alpha, alpha);
|
||||
else glUniform4f(tint_program_layout.tint_color_location, C(16), C(8), C(0), alpha);
|
||||
#define C(shift) ((((GLfloat)((window_bg >> shift) & 0xFF)) / 255.0f)) * premult_factor
|
||||
GLfloat premult_factor = premult ? OPT(background_tint) : 1.0f;
|
||||
glUniform4f(tint_program_layout.tint_color_location, C(16), C(8), C(0), OPT(background_tint));
|
||||
#undef C
|
||||
glUniform4f(tint_program_layout.edges_location, crd->gl.xstart, crd->gl.ystart - crd->gl.height, crd->gl.xstart + crd->gl.width, crd->gl.ystart);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
@@ -707,7 +722,6 @@ draw_cells_interleaved(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWind
|
||||
glUniform1ui(cell_program_layouts[CELL_BG_PROGRAM].draw_bg_bitfield_location, 3);
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, screen->lines * screen->columns);
|
||||
} else if (OPT(background_tint) > 0) {
|
||||
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE);
|
||||
draw_tint(false, screen, crd);
|
||||
BLEND_ONTO_OPAQUE;
|
||||
}
|
||||
@@ -744,7 +758,6 @@ static void
|
||||
draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen, OSWindow *os_window, const CellRenderData *crd, const WindowLogoRenderData *wl) {
|
||||
if (OPT(background_tint) > 0.f) {
|
||||
glEnable(GL_BLEND);
|
||||
BLEND_PREMULT;
|
||||
draw_tint(true, screen, crd);
|
||||
glDisable(GL_BLEND);
|
||||
}
|
||||
@@ -924,7 +937,7 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, const ScreenRenderData *srd, float
|
||||
// }}}
|
||||
|
||||
// Borders {{{
|
||||
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, BORDER_bell_border_color, BORDER_tab_bar_bg, BORDER_tab_bar_margin_color, NUM_BORDER_UNIFORMS };
|
||||
enum BorderUniforms { BORDER_viewport, BORDER_background_opacity, BORDER_tint_opacity, BORDER_tint_premult, BORDER_default_bg, BORDER_active_border_color, BORDER_inactive_border_color, BORDER_bell_border_color, BORDER_tab_bar_bg, BORDER_tab_bar_margin_color, NUM_BORDER_UNIFORMS };
|
||||
static GLint border_uniform_locations[NUM_BORDER_UNIFORMS] = {0};
|
||||
|
||||
static void
|
||||
@@ -932,6 +945,8 @@ init_borders_program(void) {
|
||||
#define SET_LOC(which) border_uniform_locations[BORDER_##which] = get_uniform_location(BORDERS_PROGRAM, #which);
|
||||
SET_LOC(viewport)
|
||||
SET_LOC(background_opacity)
|
||||
SET_LOC(tint_opacity)
|
||||
SET_LOC(tint_premult)
|
||||
SET_LOC(default_bg)
|
||||
SET_LOC(active_border_color)
|
||||
SET_LOC(inactive_border_color)
|
||||
@@ -956,12 +971,17 @@ create_border_vao(void) {
|
||||
|
||||
void
|
||||
draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height, color_type active_window_bg, unsigned int num_visible_windows, bool all_windows_have_same_bg, OSWindow *w) {
|
||||
|
||||
float background_opacity = w->is_semi_transparent ? w->background_opacity: 1.0f;
|
||||
float tint_opacity = background_opacity;
|
||||
float tint_premult = background_opacity;
|
||||
if (has_bgimage(w)) {
|
||||
glEnable(GL_BLEND);
|
||||
BLEND_ONTO_OPAQUE;
|
||||
draw_bg(w);
|
||||
BLEND_ONTO_OPAQUE;
|
||||
background_opacity = 1.0f;
|
||||
tint_opacity = OPT(background_tint);
|
||||
tint_premult = w->is_semi_transparent ? OPT(background_tint) : 1.0f;
|
||||
}
|
||||
|
||||
if (num_border_rects) {
|
||||
@@ -974,7 +994,9 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
|
||||
unmap_vao_buffer(vao_idx, 0);
|
||||
}
|
||||
#define CV3(x) (((float)((x >> 16) & 0xff))/255.f), (((float)((x >> 8) & 0xff))/255.f), (((float)(x & 0xff))/255.f)
|
||||
glUniform1f(border_uniform_locations[BORDER_background_opacity], w->is_semi_transparent ? w->background_opacity: 1.0f);
|
||||
glUniform1f(border_uniform_locations[BORDER_background_opacity], background_opacity);
|
||||
glUniform1f(border_uniform_locations[BORDER_tint_opacity], tint_opacity);
|
||||
glUniform1f(border_uniform_locations[BORDER_tint_premult], tint_premult);
|
||||
glUniform3f(border_uniform_locations[BORDER_active_border_color], CV3(OPT(active_border_color)));
|
||||
glUniform3f(border_uniform_locations[BORDER_inactive_border_color], CV3(OPT(inactive_border_color)));
|
||||
glUniform3f(border_uniform_locations[BORDER_bell_border_color], CV3(OPT(bell_border_color)));
|
||||
@@ -984,6 +1006,10 @@ draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_bu
|
||||
color_type default_bg = (num_visible_windows > 1 && !all_windows_have_same_bg) ? OPT(background) : active_window_bg;
|
||||
glUniform3f(border_uniform_locations[BORDER_default_bg], CV3(default_bg));
|
||||
#undef CV3
|
||||
if (has_bgimage(w)) {
|
||||
if (w->is_semi_transparent) { BLEND_PREMULT; }
|
||||
else { BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT; }
|
||||
}
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, num_border_rects);
|
||||
unbind_vertex_array();
|
||||
unbind_program();
|
||||
|
||||
@@ -185,7 +185,11 @@ def real_main(global_opts: RCOptions, encrypter: CommandEncrypter = NoEncryption
|
||||
print('Use {} for assistance or {} to quit'.format(green('help'), green('exit')))
|
||||
awid = os.environ.pop('KITTY_SHELL_ACTIVE_WINDOW_ID', None)
|
||||
if awid is not None:
|
||||
print(f'The ID of the previously active window is: {awid}')
|
||||
atid = os.environ.pop('KITTY_SHELL_ACTIVE_TAB_ID', None)
|
||||
am = f'Previously active window id: {awid}'
|
||||
if atid is not None:
|
||||
am += f' and tab id: {atid}'
|
||||
print(am)
|
||||
|
||||
pre_prompt = set_window_title('The kitty shell') + set_cursor_shape('bar')
|
||||
pre_prompt += f'\x1b]133;A;redraw={0 if is_libedit else 1}\x1b\\'
|
||||
|
||||
@@ -96,7 +96,7 @@ pt_to_px(double pt, id_type os_window_id) {
|
||||
|
||||
|
||||
OSWindow*
|
||||
current_os_window() {
|
||||
current_os_window(void) {
|
||||
if (global_state.callback_os_window) return global_state.callback_os_window;
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
if (global_state.os_windows[i].is_focused) return global_state.os_windows + i;
|
||||
@@ -104,6 +104,29 @@ current_os_window() {
|
||||
return global_state.os_windows;
|
||||
}
|
||||
|
||||
static id_type
|
||||
last_focused_os_window_id(void) {
|
||||
id_type ans = 0, max_fc_count = 0;
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = &global_state.os_windows[i];
|
||||
if (w->last_focused_counter > max_fc_count) {
|
||||
ans = w->id; max_fc_count = w->last_focused_counter;
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
static id_type
|
||||
current_focused_os_window_id(void) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = &global_state.os_windows[i];
|
||||
if (w->is_focused) { return w->id; }
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
OSWindow*
|
||||
os_window_for_kitty_window(id_type kitty_window_id) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
@@ -134,15 +157,14 @@ window_for_window_id(id_type kitty_window_id) {
|
||||
|
||||
static void
|
||||
send_bgimage_to_gpu(BackgroundImageLayout layout, BackgroundImage *bgimage) {
|
||||
RepeatStrategy r;
|
||||
RepeatStrategy r = REPEAT_DEFAULT;
|
||||
switch (layout) {
|
||||
case SCALED:
|
||||
case CLAMPED:
|
||||
case CLAMPED: case CENTER_CLAMPED:
|
||||
r = REPEAT_CLAMP; break;
|
||||
case MIRRORED:
|
||||
r = REPEAT_MIRROR; break;
|
||||
case TILING:
|
||||
default:
|
||||
r = REPEAT_DEFAULT; break;
|
||||
}
|
||||
bgimage->texture_id = 0;
|
||||
@@ -666,6 +688,15 @@ PYWRAP0(next_window_id) {
|
||||
return PyLong_FromUnsignedLongLong(global_state.window_id_counter + 1);
|
||||
}
|
||||
|
||||
PYWRAP0(last_focused_os_window_id) {
|
||||
return PyLong_FromUnsignedLongLong(last_focused_os_window_id());
|
||||
}
|
||||
|
||||
PYWRAP0(current_focused_os_window_id) {
|
||||
return PyLong_FromUnsignedLongLong(current_focused_os_window_id());
|
||||
}
|
||||
|
||||
|
||||
PYWRAP1(handle_for_window_id) {
|
||||
id_type os_window_id;
|
||||
PA("K", &os_window_id);
|
||||
@@ -1267,6 +1298,8 @@ KK5I(add_borders_rect)
|
||||
static PyMethodDef module_methods[] = {
|
||||
MW(current_os_window, METH_NOARGS),
|
||||
MW(next_window_id, METH_NOARGS),
|
||||
MW(last_focused_os_window_id, METH_NOARGS),
|
||||
MW(current_focused_os_window_id, METH_NOARGS),
|
||||
MW(set_options, METH_VARARGS),
|
||||
MW(get_options, METH_NOARGS),
|
||||
MW(click_mouse_url, METH_VARARGS),
|
||||
|
||||
116
kitty/tab_bar.py
116
kitty/tab_bar.py
@@ -2,10 +2,11 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
from functools import lru_cache, partial, wraps
|
||||
from string import Formatter as StringFormatter
|
||||
from typing import (
|
||||
Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union
|
||||
Any, Callable, Dict, List, NamedTuple, Optional, Sequence, Tuple, Union,
|
||||
)
|
||||
|
||||
from .borders import Border, BorderColor
|
||||
@@ -13,7 +14,7 @@ from .config import build_ansi_color_table
|
||||
from .constants import config_dir
|
||||
from .fast_data_types import (
|
||||
DECAWM, Color, Region, Screen, cell_size_for_window, get_boss, get_options,
|
||||
pt_to_px, set_tab_bar_render_data, viewport_for_window
|
||||
pt_to_px, set_tab_bar_render_data, viewport_for_window,
|
||||
)
|
||||
from .rgb import alpha_blend, color_as_sgr, color_from_int, to_color
|
||||
from .types import WindowGeometry, run_once
|
||||
@@ -151,6 +152,10 @@ class SupSub:
|
||||
class ExtraData:
|
||||
prev_tab: Optional[TabBarData] = None
|
||||
next_tab: Optional[TabBarData] = None
|
||||
# true if the draw_tab function is called just for layout. In such cases,
|
||||
# if drawing is expensive the draw_tab function should avoid drawing and
|
||||
# just move the cursor to its final position, as if drawing was performed.
|
||||
for_layout: bool = False
|
||||
|
||||
|
||||
def draw_attributed_string(title: str, screen: Screen) -> None:
|
||||
@@ -185,7 +190,7 @@ class TabAccessor:
|
||||
|
||||
|
||||
safe_builtins = {
|
||||
'max': max, 'min': min, 'str': str, 'repr': repr, 'abs': abs, 'len': len,
|
||||
'max': max, 'min': min, 'str': str, 'repr': repr, 'abs': abs, 'len': len, 're': re,
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +243,7 @@ DrawTabFunc = Callable[[DrawData, Screen, TabBarData, int, int, int, bool, Extra
|
||||
|
||||
def draw_tab_with_slant(
|
||||
draw_data: DrawData, screen: Screen, tab: TabBarData,
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
before: int, max_tab_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
orig_fg = screen.cursor.fg
|
||||
@@ -253,20 +258,20 @@ def draw_tab_with_slant(
|
||||
screen.cursor.bg = tab_bg
|
||||
screen.cursor.fg = orig_fg
|
||||
|
||||
max_title_length += 1
|
||||
if max_title_length <= 1:
|
||||
max_tab_length += 1
|
||||
if max_tab_length <= 1:
|
||||
screen.draw('…')
|
||||
elif max_title_length == 2:
|
||||
elif max_tab_length == 2:
|
||||
screen.draw('…|')
|
||||
elif max_title_length < 6:
|
||||
elif max_tab_length < 6:
|
||||
draw_sep(left_sep)
|
||||
screen.draw((' ' if max_title_length == 5 else '') + '…' + (' ' if max_title_length >= 4 else ''))
|
||||
screen.draw((' ' if max_tab_length == 5 else '') + '…' + (' ' if max_tab_length >= 4 else ''))
|
||||
draw_sep(right_sep)
|
||||
else:
|
||||
draw_sep(left_sep)
|
||||
screen.draw(' ')
|
||||
draw_title(draw_data, screen, tab, index, max_title_length)
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
draw_title(draw_data, screen, tab, index, max_tab_length)
|
||||
extra = screen.cursor.x - before - max_tab_length
|
||||
if extra >= 0:
|
||||
screen.cursor.x -= extra + 3
|
||||
screen.draw('…')
|
||||
@@ -281,15 +286,15 @@ def draw_tab_with_slant(
|
||||
|
||||
def draw_tab_with_separator(
|
||||
draw_data: DrawData, screen: Screen, tab: TabBarData,
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
before: int, max_tab_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
if draw_data.leading_spaces:
|
||||
screen.draw(' ' * draw_data.leading_spaces)
|
||||
draw_title(draw_data, screen, tab, index, max_title_length)
|
||||
trailing_spaces = min(max_title_length - 1, draw_data.trailing_spaces)
|
||||
max_title_length -= trailing_spaces
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
draw_title(draw_data, screen, tab, index, max_tab_length)
|
||||
trailing_spaces = min(max_tab_length - 1, draw_data.trailing_spaces)
|
||||
max_tab_length -= trailing_spaces
|
||||
extra = screen.cursor.x - before - max_tab_length
|
||||
if extra > 0:
|
||||
screen.cursor.x -= extra + 1
|
||||
screen.draw('…')
|
||||
@@ -307,7 +312,7 @@ def draw_tab_with_separator(
|
||||
|
||||
def draw_tab_with_fade(
|
||||
draw_data: DrawData, screen: Screen, tab: TabBarData,
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
before: int, max_tab_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
orig_bg = screen.cursor.bg
|
||||
@@ -317,12 +322,12 @@ def draw_tab_with_fade(
|
||||
screen.cursor.bg = bg
|
||||
screen.draw(' ')
|
||||
screen.cursor.bg = orig_bg
|
||||
draw_title(draw_data, screen, tab, index, max(0, max_title_length - 8))
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
draw_title(draw_data, screen, tab, index, max(0, max_tab_length - 8))
|
||||
extra = screen.cursor.x - before - max_tab_length
|
||||
if extra > 0:
|
||||
screen.cursor.x = before
|
||||
draw_title(draw_data, screen, tab, index, max(0, max_title_length - 4))
|
||||
extra = screen.cursor.x - before - max_title_length
|
||||
draw_title(draw_data, screen, tab, index, max(0, max_tab_length - 4))
|
||||
extra = screen.cursor.x - before - max_tab_length
|
||||
if extra > 0:
|
||||
screen.cursor.x -= extra + 1
|
||||
screen.draw('…')
|
||||
@@ -346,7 +351,7 @@ powerline_symbols: Dict[PowerlineStyle, Tuple[str, str]] = {
|
||||
|
||||
def draw_tab_with_powerline(
|
||||
draw_data: DrawData, screen: Screen, tab: TabBarData,
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
before: int, max_tab_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
tab_bg = screen.cursor.bg
|
||||
@@ -369,11 +374,11 @@ def draw_tab_with_powerline(
|
||||
start_draw = 1
|
||||
|
||||
screen.cursor.bg = tab_bg
|
||||
if min_title_length >= max_title_length:
|
||||
if min_title_length >= max_tab_length:
|
||||
screen.draw('…')
|
||||
else:
|
||||
draw_title(draw_data, screen, tab, index, max_title_length)
|
||||
extra = screen.cursor.x + start_draw - before - max_title_length
|
||||
draw_title(draw_data, screen, tab, index, max_tab_length)
|
||||
extra = screen.cursor.x + start_draw - before - max_tab_length
|
||||
if extra > 0 and extra + 1 < screen.cursor.x:
|
||||
screen.cursor.x -= extra + 1
|
||||
screen.draw('…')
|
||||
@@ -416,14 +421,14 @@ def load_custom_draw_tab() -> DrawTabFunc:
|
||||
@wraps(func)
|
||||
def draw_tab(
|
||||
draw_data: DrawData, screen: Screen, tab: TabBarData,
|
||||
before: int, max_title_length: int, index: int, is_last: bool,
|
||||
before: int, max_tab_length: int, index: int, is_last: bool,
|
||||
extra_data: ExtraData
|
||||
) -> int:
|
||||
try:
|
||||
return func(draw_data, screen, tab, before, max_title_length, index, is_last, extra_data)
|
||||
return func(draw_data, screen, tab, before, max_tab_length, index, is_last, extra_data)
|
||||
except Exception as e:
|
||||
log_error(f'Custom draw tab function failed with error: {e}')
|
||||
return draw_tab_with_fade(draw_data, screen, tab, before, max_title_length, index, is_last, extra_data)
|
||||
return draw_tab_with_fade(draw_data, screen, tab, before, max_tab_length, index, is_last, extra_data)
|
||||
|
||||
return draw_tab
|
||||
|
||||
@@ -435,6 +440,7 @@ class TabBar:
|
||||
self.num_tabs = 1
|
||||
self.data_buffer_size = 0
|
||||
self.blank_rects: Tuple[Border, ...] = ()
|
||||
self.cell_ranges: List[Tuple[int, int]] = []
|
||||
self.laid_out_once = False
|
||||
self.apply_options()
|
||||
|
||||
@@ -571,31 +577,67 @@ class TabBar:
|
||||
if not self.laid_out_once:
|
||||
return
|
||||
s = self.screen
|
||||
s.cursor.x = 0
|
||||
s.erase_in_line(2, False)
|
||||
max_title_length = max(1, (self.screen.columns // max(1, len(data))) - 1)
|
||||
cr = []
|
||||
last_tab = data[-1] if data else None
|
||||
ed = ExtraData()
|
||||
|
||||
for i, t in enumerate(data):
|
||||
def draw_tab(i: int, tab: TabBarData, cell_ranges: List[Tuple[int, int]], max_tab_length: int) -> None:
|
||||
ed.prev_tab = data[i - 1] if i > 0 else None
|
||||
ed.next_tab = data[i + 1] if i + 1 < len(data) else None
|
||||
s.cursor.bg = as_rgb(self.draw_data.tab_bg(t))
|
||||
s.cursor.fg = as_rgb(self.draw_data.tab_fg(t))
|
||||
s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style
|
||||
before = s.cursor.x
|
||||
end = self.draw_func(self.draw_data, s, t, before, max_title_length, i + 1, t is last_tab, ed)
|
||||
end = self.draw_func(self.draw_data, s, t, before, max_tab_length, i + 1, t is last_tab, ed)
|
||||
s.cursor.bg = s.cursor.fg = 0
|
||||
cr.append((before, end))
|
||||
if s.cursor.x > s.columns - max_title_length and t is not last_tab:
|
||||
cell_ranges.append((before, end))
|
||||
if not ed.for_layout and t is not last_tab and s.cursor.x > s.columns - max_tab_lengths[i+1]:
|
||||
# Stop if there is no space for next tab
|
||||
s.cursor.x = s.columns - 2
|
||||
s.cursor.bg = as_rgb(color_as_int(self.draw_data.default_bg))
|
||||
s.cursor.fg = as_rgb(0xff0000)
|
||||
s.draw(' …')
|
||||
raise StopIteration()
|
||||
|
||||
unconstrained_tab_length = max(1, s.columns - 2)
|
||||
ideal_tab_lengths = [i for i in range(len(data))]
|
||||
default_max_tab_length = max(1, (s.columns // max(1, len(data))) - 1)
|
||||
max_tab_lengths = [default_max_tab_length for _ in range(len(data))]
|
||||
active_idx = 0
|
||||
extra = 0
|
||||
ed.for_layout = True
|
||||
for i, t in enumerate(data):
|
||||
s.cursor.x = 0
|
||||
draw_tab(i, t, [], unconstrained_tab_length)
|
||||
ideal_tab_lengths[i] = tl = max(1, s.cursor.x)
|
||||
if t.is_active:
|
||||
active_idx = i
|
||||
if tl < default_max_tab_length:
|
||||
max_tab_lengths[i] = tl
|
||||
extra += default_max_tab_length - tl
|
||||
if extra > 0:
|
||||
if ideal_tab_lengths[active_idx] > max_tab_lengths[active_idx]:
|
||||
d = min(extra, ideal_tab_lengths[active_idx] - max_tab_lengths[active_idx])
|
||||
max_tab_lengths[active_idx] += d
|
||||
extra -= d
|
||||
if extra > 0:
|
||||
over_achievers = tuple(i for i in range(len(data)) if ideal_tab_lengths[i] > max_tab_lengths[i])
|
||||
if over_achievers:
|
||||
amt_per_over_achiever = extra // len(over_achievers)
|
||||
if amt_per_over_achiever > 0:
|
||||
for i in over_achievers:
|
||||
max_tab_lengths[i] += amt_per_over_achiever
|
||||
|
||||
s.cursor.x = 0
|
||||
s.erase_in_line(2, False)
|
||||
cr: List[Tuple[int, int]] = []
|
||||
ed.for_layout = False
|
||||
for i, t in enumerate(data):
|
||||
try:
|
||||
draw_tab(i, t, cr, max_tab_lengths[i])
|
||||
except StopIteration:
|
||||
break
|
||||
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
|
||||
self.cell_ranges = cr
|
||||
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
|
||||
self.align()
|
||||
|
||||
def align_with_factor(self, factor: int = 1) -> None:
|
||||
|
||||
@@ -10,8 +10,8 @@ from contextlib import suppress
|
||||
from operator import attrgetter
|
||||
from time import monotonic
|
||||
from typing import (
|
||||
Any, Deque, Dict, Generator, Iterable, Iterator, List,
|
||||
NamedTuple, Optional, Sequence, Set, Tuple, Union
|
||||
Any, Deque, Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional,
|
||||
Sequence, Set, Tuple, Union,
|
||||
)
|
||||
|
||||
from .borders import Border, Borders
|
||||
@@ -19,11 +19,11 @@ from .child import Child
|
||||
from .cli_stub import CLIOptions
|
||||
from .constants import appname, kitty_exe
|
||||
from .fast_data_types import (
|
||||
GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE,
|
||||
add_tab, attach_window, current_os_window, detach_window, get_boss,
|
||||
get_click_interval, get_options, mark_tab_bar_dirty, next_window_id,
|
||||
remove_tab, remove_window, ring_bell, set_active_tab, set_active_window,
|
||||
swap_tabs, sync_os_window_title
|
||||
GLFW_MOUSE_BUTTON_LEFT, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, GLFW_RELEASE, add_tab,
|
||||
attach_window, current_focused_os_window_id, detach_window, get_boss,
|
||||
get_click_interval, get_options, last_focused_os_window_id, mark_tab_bar_dirty,
|
||||
next_window_id, remove_tab, remove_window, ring_bell, set_active_tab,
|
||||
set_active_window, swap_tabs, sync_os_window_title,
|
||||
)
|
||||
from .layout.base import Layout
|
||||
from .layout.interface import create_layout_object_for, evict_cached_layouts
|
||||
@@ -46,6 +46,7 @@ class TabMouseEvent(NamedTuple):
|
||||
class TabDict(TypedDict):
|
||||
id: int
|
||||
is_focused: bool
|
||||
is_active_tab: bool
|
||||
title: str
|
||||
layout: str
|
||||
layout_state: Dict[str, Any]
|
||||
@@ -698,9 +699,13 @@ class Tab: # {{{
|
||||
def move_window_backward(self) -> None:
|
||||
self.move_window(-1)
|
||||
|
||||
def list_windows(self, active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:
|
||||
def list_windows(self, self_window: Optional[Window] = None) -> Generator[WindowDict, None, None]:
|
||||
active_window = self.active_window
|
||||
for w in self:
|
||||
yield w.as_dict(is_focused=w is active_window, is_self=w is self_window)
|
||||
yield w.as_dict(
|
||||
is_active_window=w is active_window,
|
||||
is_focused=w.os_window_id == current_focused_os_window_id() and w is active_window,
|
||||
is_self=w is self_window)
|
||||
|
||||
def matches_query(self, field: str, query: str, active_tab_manager: Optional['TabManager'] = None) -> bool:
|
||||
if field == 'title':
|
||||
@@ -724,9 +729,10 @@ class Tab: # {{{
|
||||
return False
|
||||
if field == 'state':
|
||||
if query == 'active':
|
||||
return active_tab_manager is not None and self is active_tab_manager.active_tab
|
||||
tm = self.tab_manager_ref()
|
||||
return tm is not None and self is tm.active_tab
|
||||
if query == 'focused':
|
||||
return active_tab_manager is not None and self is active_tab_manager.active_tab and self.os_window_id == current_os_window()
|
||||
return active_tab_manager is not None and self is active_tab_manager.active_tab and self.os_window_id == last_focused_os_window_id()
|
||||
if query == 'needs_attention':
|
||||
for w in self:
|
||||
if w.needs_attention:
|
||||
@@ -734,7 +740,7 @@ class Tab: # {{{
|
||||
if query == 'parent_active':
|
||||
return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager
|
||||
if query == 'parent_focused':
|
||||
return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager and self.os_window_id == current_os_window()
|
||||
return active_tab_manager is not None and self.tab_manager_ref() is active_tab_manager and self.os_window_id == last_focused_os_window_id()
|
||||
return False
|
||||
return False
|
||||
|
||||
@@ -758,7 +764,7 @@ class Tab: # {{{
|
||||
self.windows = WindowList(self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Tab(title={self.effective_title}, id={hex(id(self))})'
|
||||
return f'Tab(title={self.effective_title}, id={self.id})'
|
||||
|
||||
def make_active(self) -> None:
|
||||
tm = self.tab_manager_ref()
|
||||
@@ -934,17 +940,19 @@ class TabManager: # {{{
|
||||
def __len__(self) -> int:
|
||||
return len(self.tabs)
|
||||
|
||||
def list_tabs(self, active_tab: Optional[Tab], active_window: Optional[Window], self_window: Optional[Window] = None) -> Generator[TabDict, None, None]:
|
||||
def list_tabs(self, self_window: Optional[Window] = None) -> Generator[TabDict, None, None]:
|
||||
active_tab = self.active_tab
|
||||
for tab in self:
|
||||
yield {
|
||||
'id': tab.id,
|
||||
'is_focused': tab is active_tab,
|
||||
'is_focused': tab is active_tab and tab.os_window_id == current_focused_os_window_id(),
|
||||
'is_active_tab': tab is active_tab,
|
||||
'title': tab.name or tab.title,
|
||||
'layout': str(tab.current_layout.name),
|
||||
'layout_state': tab.current_layout.layout_state(),
|
||||
'layout_opts': tab.current_layout.layout_opts.serialized(),
|
||||
'enabled_layouts': tab.enabled_layouts,
|
||||
'windows': list(tab.list_windows(active_window, self_window)),
|
||||
'windows': list(tab.list_windows(self_window)),
|
||||
'active_window_history': list(tab.windows.active_window_history),
|
||||
}
|
||||
|
||||
|
||||
550
kitty/unicode-data.c
generated
550
kitty/unicode-data.c
generated
File diff suppressed because one or more lines are too long
@@ -2,7 +2,7 @@
|
||||
#include "data-types.h"
|
||||
#include "state.h"
|
||||
// START_KNOWN_MARKS
|
||||
static const combining_type VS15 = 1362, VS16 = 1363;
|
||||
static const combining_type VS15 = 1364, VS16 = 1365;
|
||||
// END_KNOWN_MARKS
|
||||
|
||||
bool is_combining_char(char_type ch);
|
||||
|
||||
@@ -1086,3 +1086,13 @@ def docs_url(which: str = '', local_docs_root: Optional[str] = '') -> str:
|
||||
if frag:
|
||||
url += '#' + frag
|
||||
return url
|
||||
|
||||
|
||||
def sanitize_for_bracketed_paste(text: bytes) -> bytes:
|
||||
pat = re.compile(b'(?:(?:\033\\\x5b)|(?:\x9b))201~')
|
||||
while True:
|
||||
new_text = pat.sub(b'', text)
|
||||
if new_text == text:
|
||||
break
|
||||
text = new_text
|
||||
return text
|
||||
|
||||
128
kitty/wcwidth-std.h
generated
128
kitty/wcwidth-std.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2022-01-05
|
||||
// unicode data, built from the unicode standard on: 2022-09-29
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
@@ -14,7 +14,7 @@ wcwidth_std(int32_t code) {
|
||||
return 2;
|
||||
// }}}
|
||||
|
||||
// Marks (6350 codepoints) {{{
|
||||
// Marks (6399 codepoints) {{{
|
||||
case 0x0:
|
||||
return 0;
|
||||
case 0xad:
|
||||
@@ -185,6 +185,8 @@ wcwidth_std(int32_t code) {
|
||||
return 0;
|
||||
case 0xce2 ... 0xce3:
|
||||
return 0;
|
||||
case 0xcf3:
|
||||
return 0;
|
||||
case 0xd00 ... 0xd03:
|
||||
return 0;
|
||||
case 0xd3b ... 0xd3c:
|
||||
@@ -221,7 +223,7 @@ wcwidth_std(int32_t code) {
|
||||
return 0;
|
||||
case 0xeb4 ... 0xebc:
|
||||
return 0;
|
||||
case 0xec8 ... 0xecd:
|
||||
case 0xec8 ... 0xece:
|
||||
return 0;
|
||||
case 0xf18 ... 0xf19:
|
||||
return 0;
|
||||
@@ -439,6 +441,8 @@ wcwidth_std(int32_t code) {
|
||||
return 0;
|
||||
case 0x10eab ... 0x10eac:
|
||||
return 0;
|
||||
case 0x10efd ... 0x10eff:
|
||||
return 0;
|
||||
case 0x10f46 ... 0x10f50:
|
||||
return 0;
|
||||
case 0x10f82 ... 0x10f85:
|
||||
@@ -481,6 +485,8 @@ wcwidth_std(int32_t code) {
|
||||
return 0;
|
||||
case 0x1123e:
|
||||
return 0;
|
||||
case 0x11241:
|
||||
return 0;
|
||||
case 0x112df ... 0x112ea:
|
||||
return 0;
|
||||
case 0x11300 ... 0x11303:
|
||||
@@ -575,7 +581,17 @@ wcwidth_std(int32_t code) {
|
||||
return 0;
|
||||
case 0x11ef3 ... 0x11ef6:
|
||||
return 0;
|
||||
case 0x13430 ... 0x13438:
|
||||
case 0x11f00 ... 0x11f01:
|
||||
return 0;
|
||||
case 0x11f03:
|
||||
return 0;
|
||||
case 0x11f34 ... 0x11f3a:
|
||||
return 0;
|
||||
case 0x11f3e ... 0x11f42:
|
||||
return 0;
|
||||
case 0x13430 ... 0x13440:
|
||||
return 0;
|
||||
case 0x13447 ... 0x13455:
|
||||
return 0;
|
||||
case 0x16af0 ... 0x16af4:
|
||||
return 0;
|
||||
@@ -631,12 +647,16 @@ wcwidth_std(int32_t code) {
|
||||
return 0;
|
||||
case 0x1e026 ... 0x1e02a:
|
||||
return 0;
|
||||
case 0x1e08f:
|
||||
return 0;
|
||||
case 0x1e130 ... 0x1e136:
|
||||
return 0;
|
||||
case 0x1e2ae:
|
||||
return 0;
|
||||
case 0x1e2ec ... 0x1e2ef:
|
||||
return 0;
|
||||
case 0x1e4ec ... 0x1e4ef:
|
||||
return 0;
|
||||
case 0x1e8d0 ... 0x1e8d6:
|
||||
return 0;
|
||||
case 0x1e944 ... 0x1e94a:
|
||||
@@ -1257,7 +1277,7 @@ wcwidth_std(int32_t code) {
|
||||
return -2;
|
||||
// }}}
|
||||
|
||||
// East Asian double width (182470 codepoints) {{{
|
||||
// East Asian double width (182492 codepoints) {{{
|
||||
case 0x1100 ... 0x115e:
|
||||
return 2;
|
||||
case 0x231a ... 0x231b:
|
||||
@@ -1402,8 +1422,12 @@ wcwidth_std(int32_t code) {
|
||||
return 2;
|
||||
case 0x1b000 ... 0x1b122:
|
||||
return 2;
|
||||
case 0x1b132:
|
||||
return 2;
|
||||
case 0x1b150 ... 0x1b152:
|
||||
return 2;
|
||||
case 0x1b155:
|
||||
return 2;
|
||||
case 0x1b164 ... 0x1b167:
|
||||
return 2;
|
||||
case 0x1b170 ... 0x1b2fb:
|
||||
@@ -1474,7 +1498,7 @@ wcwidth_std(int32_t code) {
|
||||
return 2;
|
||||
case 0x1f6d5 ... 0x1f6d7:
|
||||
return 2;
|
||||
case 0x1f6dd ... 0x1f6df:
|
||||
case 0x1f6dc ... 0x1f6df:
|
||||
return 2;
|
||||
case 0x1f6eb ... 0x1f6ec:
|
||||
return 2;
|
||||
@@ -1490,23 +1514,19 @@ wcwidth_std(int32_t code) {
|
||||
return 2;
|
||||
case 0x1f947 ... 0x1f9ff:
|
||||
return 2;
|
||||
case 0x1fa70 ... 0x1fa74:
|
||||
case 0x1fa70 ... 0x1fa7c:
|
||||
return 2;
|
||||
case 0x1fa78 ... 0x1fa7c:
|
||||
case 0x1fa80 ... 0x1fa88:
|
||||
return 2;
|
||||
case 0x1fa80 ... 0x1fa86:
|
||||
case 0x1fa90 ... 0x1fabd:
|
||||
return 2;
|
||||
case 0x1fa90 ... 0x1faac:
|
||||
case 0x1fabf ... 0x1fac5:
|
||||
return 2;
|
||||
case 0x1fab0 ... 0x1faba:
|
||||
case 0x1face ... 0x1fadb:
|
||||
return 2;
|
||||
case 0x1fac0 ... 0x1fac5:
|
||||
case 0x1fae0 ... 0x1fae8:
|
||||
return 2;
|
||||
case 0x1fad0 ... 0x1fad9:
|
||||
return 2;
|
||||
case 0x1fae0 ... 0x1fae7:
|
||||
return 2;
|
||||
case 0x1faf0 ... 0x1faf6:
|
||||
case 0x1faf0 ... 0x1faf8:
|
||||
return 2;
|
||||
case 0x20000 ... 0x2fffd:
|
||||
return 2;
|
||||
@@ -1517,7 +1537,7 @@ wcwidth_std(int32_t code) {
|
||||
// Emoji Presentation (0 codepoints) {{{
|
||||
// }}}
|
||||
|
||||
// Not assigned in the unicode character database (760767 codepoints) {{{
|
||||
// Not assigned in the unicode character database (760471 codepoints) {{{
|
||||
case 0x378 ... 0x379:
|
||||
return -4;
|
||||
case 0x380 ... 0x383:
|
||||
@@ -1760,7 +1780,7 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0xcf0:
|
||||
return -4;
|
||||
case 0xcf3 ... 0xcff:
|
||||
case 0xcf4 ... 0xcff:
|
||||
return -4;
|
||||
case 0xd0d:
|
||||
return -4;
|
||||
@@ -1820,7 +1840,7 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0xec7:
|
||||
return -4;
|
||||
case 0xece ... 0xecf:
|
||||
case 0xecf:
|
||||
return -4;
|
||||
case 0xeda ... 0xedb:
|
||||
return -4;
|
||||
@@ -2354,7 +2374,7 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x10eae ... 0x10eaf:
|
||||
return -4;
|
||||
case 0x10eb2 ... 0x10eff:
|
||||
case 0x10eb2 ... 0x10efc:
|
||||
return -4;
|
||||
case 0x10f28 ... 0x10f2f:
|
||||
return -4;
|
||||
@@ -2390,7 +2410,7 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x11212:
|
||||
return -4;
|
||||
case 0x1123f ... 0x1127f:
|
||||
case 0x11242 ... 0x1127f:
|
||||
return -4;
|
||||
case 0x11287:
|
||||
return -4;
|
||||
@@ -2494,7 +2514,9 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x11aa3 ... 0x11aaf:
|
||||
return -4;
|
||||
case 0x11af9 ... 0x11bff:
|
||||
case 0x11af9 ... 0x11aff:
|
||||
return -4;
|
||||
case 0x11b0a ... 0x11bff:
|
||||
return -4;
|
||||
case 0x11c09:
|
||||
return -4;
|
||||
@@ -2536,7 +2558,13 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x11daa ... 0x11edf:
|
||||
return -4;
|
||||
case 0x11ef9 ... 0x11faf:
|
||||
case 0x11ef9 ... 0x11eff:
|
||||
return -4;
|
||||
case 0x11f11:
|
||||
return -4;
|
||||
case 0x11f3b ... 0x11f3d:
|
||||
return -4;
|
||||
case 0x11f5a ... 0x11faf:
|
||||
return -4;
|
||||
case 0x11fb1 ... 0x11fbf:
|
||||
return -4;
|
||||
@@ -2552,9 +2580,7 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x12ff3 ... 0x12fff:
|
||||
return -4;
|
||||
case 0x1342f:
|
||||
return -4;
|
||||
case 0x13439 ... 0x143ff:
|
||||
case 0x13456 ... 0x143ff:
|
||||
return -4;
|
||||
case 0x14647 ... 0x167ff:
|
||||
return -4;
|
||||
@@ -2606,9 +2632,13 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x1afff:
|
||||
return -4;
|
||||
case 0x1b123 ... 0x1b14f:
|
||||
case 0x1b123 ... 0x1b131:
|
||||
return -4;
|
||||
case 0x1b153 ... 0x1b163:
|
||||
case 0x1b133 ... 0x1b14f:
|
||||
return -4;
|
||||
case 0x1b153 ... 0x1b154:
|
||||
return -4;
|
||||
case 0x1b156 ... 0x1b163:
|
||||
return -4;
|
||||
case 0x1b168 ... 0x1b16f:
|
||||
return -4;
|
||||
@@ -2636,7 +2666,9 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x1d1eb ... 0x1d1ff:
|
||||
return -4;
|
||||
case 0x1d246 ... 0x1d2df:
|
||||
case 0x1d246 ... 0x1d2bf:
|
||||
return -4;
|
||||
case 0x1d2d4 ... 0x1d2df:
|
||||
return -4;
|
||||
case 0x1d2f4 ... 0x1d2ff:
|
||||
return -4;
|
||||
@@ -2690,7 +2722,9 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x1dab0 ... 0x1deff:
|
||||
return -4;
|
||||
case 0x1df1f ... 0x1dfff:
|
||||
case 0x1df1f ... 0x1df24:
|
||||
return -4;
|
||||
case 0x1df2b ... 0x1dfff:
|
||||
return -4;
|
||||
case 0x1e007:
|
||||
return -4;
|
||||
@@ -2700,7 +2734,11 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x1e025:
|
||||
return -4;
|
||||
case 0x1e02b ... 0x1e0ff:
|
||||
case 0x1e02b ... 0x1e02f:
|
||||
return -4;
|
||||
case 0x1e06e ... 0x1e08e:
|
||||
return -4;
|
||||
case 0x1e090 ... 0x1e0ff:
|
||||
return -4;
|
||||
case 0x1e12d ... 0x1e12f:
|
||||
return -4;
|
||||
@@ -2714,7 +2752,9 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x1e2fa ... 0x1e2fe:
|
||||
return -4;
|
||||
case 0x1e300 ... 0x1e7df:
|
||||
case 0x1e300 ... 0x1e4cf:
|
||||
return -4;
|
||||
case 0x1e4fa ... 0x1e7df:
|
||||
return -4;
|
||||
case 0x1e7e7:
|
||||
return -4;
|
||||
@@ -2830,15 +2870,15 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x1f266 ... 0x1f2ff:
|
||||
return -4;
|
||||
case 0x1f6d8 ... 0x1f6dc:
|
||||
case 0x1f6d8 ... 0x1f6db:
|
||||
return -4;
|
||||
case 0x1f6ed ... 0x1f6ef:
|
||||
return -4;
|
||||
case 0x1f6fd ... 0x1f6ff:
|
||||
return -4;
|
||||
case 0x1f774 ... 0x1f77f:
|
||||
case 0x1f777 ... 0x1f77a:
|
||||
return -4;
|
||||
case 0x1f7d9 ... 0x1f7df:
|
||||
case 0x1f7da ... 0x1f7df:
|
||||
return -4;
|
||||
case 0x1f7ec ... 0x1f7ef:
|
||||
return -4;
|
||||
@@ -2860,23 +2900,19 @@ wcwidth_std(int32_t code) {
|
||||
return -4;
|
||||
case 0x1fa6e ... 0x1fa6f:
|
||||
return -4;
|
||||
case 0x1fa75 ... 0x1fa77:
|
||||
return -4;
|
||||
case 0x1fa7d ... 0x1fa7f:
|
||||
return -4;
|
||||
case 0x1fa87 ... 0x1fa8f:
|
||||
case 0x1fa89 ... 0x1fa8f:
|
||||
return -4;
|
||||
case 0x1faad ... 0x1faaf:
|
||||
case 0x1fabe:
|
||||
return -4;
|
||||
case 0x1fabb ... 0x1fabf:
|
||||
case 0x1fac6 ... 0x1facd:
|
||||
return -4;
|
||||
case 0x1fac6 ... 0x1facf:
|
||||
case 0x1fadc ... 0x1fadf:
|
||||
return -4;
|
||||
case 0x1fada ... 0x1fadf:
|
||||
case 0x1fae9 ... 0x1faef:
|
||||
return -4;
|
||||
case 0x1fae8 ... 0x1faef:
|
||||
return -4;
|
||||
case 0x1faf7 ... 0x1faff:
|
||||
case 0x1faf9 ... 0x1faff:
|
||||
return -4;
|
||||
case 0x1fb93:
|
||||
return -4;
|
||||
|
||||
@@ -14,39 +14,38 @@ from gettext import gettext as _
|
||||
from itertools import chain
|
||||
from time import monotonic
|
||||
from typing import (
|
||||
TYPE_CHECKING, Any, Callable, Deque, Dict, Iterable, List, NamedTuple,
|
||||
Optional, Pattern, Sequence, Tuple, Union
|
||||
TYPE_CHECKING, Any, Callable, Deque, Dict, Iterable, List, NamedTuple, Optional,
|
||||
Pattern, Sequence, Tuple, Union,
|
||||
)
|
||||
|
||||
from .child import ProcessDesc
|
||||
from .cli_stub import CLIOptions
|
||||
from .clipboard import (
|
||||
get_clipboard_string, get_primary_selection, set_clipboard_string,
|
||||
set_primary_selection
|
||||
set_primary_selection,
|
||||
)
|
||||
from .config import build_ansi_color_table
|
||||
from .constants import (
|
||||
appname, clear_handled_signals, config_dir, is_macos, wakeup_io_loop
|
||||
appname, clear_handled_signals, config_dir, is_macos, wakeup_io_loop,
|
||||
)
|
||||
from .fast_data_types import (
|
||||
BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM,
|
||||
CELL_PROGRAM, CELL_SPECIAL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK,
|
||||
CURSOR_UNDERLINE, DCS, DECORATION, DECORATION_MASK, DIM, GLFW_MOD_CONTROL,
|
||||
GRAPHICS_ALPHA_MASK_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM,
|
||||
MARK, MARK_MASK, NO_CURSOR_SHAPE, NUM_UNDERLINE_STYLES, OSC, REVERSE,
|
||||
SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE, STRIKETHROUGH, TINT_PROGRAM, Color,
|
||||
KeyEvent, Screen, add_timer, add_window, cell_size_for_window,
|
||||
click_mouse_cmd_output, click_mouse_url, compile_program,
|
||||
current_os_window, encode_key_for_tty, get_boss, get_click_interval,
|
||||
get_options, init_cell_program, mark_os_window_dirty, mouse_selection,
|
||||
move_cursor_to_mouse_if_in_prompt, pt_to_px, set_titlebar_color,
|
||||
BGIMAGE_PROGRAM, BLIT_PROGRAM, CELL_BG_PROGRAM, CELL_FG_PROGRAM, CELL_PROGRAM,
|
||||
CELL_SPECIAL_PROGRAM, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DCS, DECORATION,
|
||||
DECORATION_MASK, DIM, GLFW_MOD_CONTROL, GRAPHICS_ALPHA_MASK_PROGRAM,
|
||||
GRAPHICS_PREMULT_PROGRAM, GRAPHICS_PROGRAM, MARK, MARK_MASK, NO_CURSOR_SHAPE,
|
||||
NUM_UNDERLINE_STYLES, OSC, REVERSE, SCROLL_FULL, SCROLL_LINE, SCROLL_PAGE,
|
||||
STRIKETHROUGH, TINT_PROGRAM, Color, KeyEvent, Screen, add_timer, add_window,
|
||||
cell_size_for_window, click_mouse_cmd_output, click_mouse_url, compile_program,
|
||||
current_focused_os_window_id, encode_key_for_tty, get_boss, get_click_interval,
|
||||
get_options, init_cell_program, last_focused_os_window_id, mark_os_window_dirty,
|
||||
mouse_selection, move_cursor_to_mouse_if_in_prompt, pt_to_px, set_titlebar_color,
|
||||
set_window_logo, set_window_padding, set_window_render_data,
|
||||
update_ime_position_for_window, update_window_title,
|
||||
update_window_visibility, wakeup_main_loop
|
||||
update_ime_position_for_window, update_window_title, update_window_visibility,
|
||||
wakeup_main_loop,
|
||||
)
|
||||
from .keys import keyboard_mode_name, mod_mask
|
||||
from .notify import (
|
||||
NotificationCommand, handle_notification_cmd, sanitize_identifier_pat
|
||||
NotificationCommand, handle_notification_cmd, sanitize_identifier_pat,
|
||||
)
|
||||
from .options.types import Options
|
||||
from .rgb import to_color
|
||||
@@ -54,9 +53,9 @@ from .terminfo import get_capabilities
|
||||
from .types import MouseEvent, OverlayType, WindowGeometry, ac, run_once
|
||||
from .typing import BossType, ChildType, EdgeLiteral, TabType, TypedDict
|
||||
from .utils import (
|
||||
docs_url, kitty_ansi_sanitizer_pat, load_shaders, log_error, open_cmd,
|
||||
open_url, parse_color_set, path_from_osc7_url, resolve_custom_file,
|
||||
resolved_shell, sanitize_title
|
||||
docs_url, kitty_ansi_sanitizer_pat, load_shaders, log_error, open_cmd, open_url,
|
||||
parse_color_set, path_from_osc7_url, resolve_custom_file, resolved_shell,
|
||||
sanitize_for_bracketed_paste, sanitize_title,
|
||||
)
|
||||
|
||||
MatchPatternType = Union[Pattern[str], Tuple[Pattern[str], Optional[Pattern[str]]]]
|
||||
@@ -140,6 +139,7 @@ def compile_match_query(exp: str, is_simple: bool = True) -> MatchPatternType:
|
||||
class WindowDict(TypedDict):
|
||||
id: int
|
||||
is_focused: bool
|
||||
is_active_window: bool
|
||||
title: str
|
||||
pid: Optional[int]
|
||||
cwd: str
|
||||
@@ -616,10 +616,11 @@ class Window:
|
||||
def __repr__(self) -> str:
|
||||
return f'Window(title={self.title}, id={self.id})'
|
||||
|
||||
def as_dict(self, is_focused: bool = False, is_self: bool = False) -> WindowDict:
|
||||
def as_dict(self, is_focused: bool = False, is_self: bool = False, is_active_window: bool = False) -> WindowDict:
|
||||
return dict(
|
||||
id=self.id,
|
||||
is_focused=is_focused,
|
||||
is_active_window=is_active_window,
|
||||
title=self.title,
|
||||
pid=self.child.pid,
|
||||
cwd=self.child.current_cwd or self.child.cwd,
|
||||
@@ -707,15 +708,20 @@ class Window:
|
||||
return False
|
||||
if field == 'state':
|
||||
if query == 'active':
|
||||
return active_tab is not None and self is active_tab.active_window
|
||||
tab = self.tabref()
|
||||
return tab is not None and tab.active_window is self
|
||||
if query == 'focused':
|
||||
return active_tab is not None and self is active_tab.active_window and current_os_window() == self.os_window_id
|
||||
return active_tab is not None and self is active_tab.active_window and last_focused_os_window_id() == self.os_window_id
|
||||
if query == 'needs_attention':
|
||||
return self.needs_attention
|
||||
if query == 'parent_active':
|
||||
return active_tab is not None and self.tabref() is active_tab
|
||||
tab = self.tabref()
|
||||
if tab is not None:
|
||||
tm = tab.tab_manager_ref()
|
||||
return tm is not None and tm.active_tab is tab
|
||||
return False
|
||||
if query == 'parent_focused':
|
||||
return active_tab is not None and self.tabref() is active_tab and current_os_window() == self.os_window_id
|
||||
return active_tab is not None and self.tabref() is active_tab and last_focused_os_window_id() == self.os_window_id
|
||||
return False
|
||||
pat = compile_match_query(query, field != 'env')
|
||||
return self.matches(field, pat)
|
||||
@@ -921,7 +927,7 @@ class Window:
|
||||
tab = self.tabref()
|
||||
if tab is not None:
|
||||
tab.relayout_borders()
|
||||
elif self.os_window_id == current_os_window():
|
||||
elif self.os_window_id == current_focused_os_window_id():
|
||||
# Cancel IME composition after loses focus
|
||||
update_ime_position_for_window(self.id, False, -1)
|
||||
|
||||
@@ -1356,7 +1362,7 @@ class Window:
|
||||
self.destroyed = True
|
||||
del self.kitten_result_processors
|
||||
if hasattr(self, 'screen'):
|
||||
if self.is_active and self.os_window_id == current_os_window():
|
||||
if self.is_active and self.os_window_id == current_focused_os_window_id():
|
||||
# Cancel IME composition when window is destroyed
|
||||
update_ime_position_for_window(self.id, False, -1)
|
||||
# Remove cycles so that screen is de-allocated immediately
|
||||
@@ -1463,11 +1469,7 @@ class Window:
|
||||
if isinstance(text, str):
|
||||
text = text.encode('utf-8')
|
||||
if self.screen.in_bracketed_paste_mode:
|
||||
while True:
|
||||
new_text = text.replace(b'\033[201~', b'').replace(b'\x9b201~', b'')
|
||||
if len(text) == len(new_text):
|
||||
break
|
||||
text = new_text
|
||||
text = sanitize_for_bracketed_paste(text)
|
||||
else:
|
||||
# Workaround for broken editors like nano that cannot handle
|
||||
# newlines in pasted text see https://github.com/kovidgoyal/kitty/issues/994
|
||||
|
||||
@@ -559,3 +559,11 @@ class TestDataTypes(BaseTest):
|
||||
def test_notify_identifier_sanitization(self):
|
||||
from kitty.notify import sanitize_identifier_pat
|
||||
self.ae(sanitize_identifier_pat().sub('', '\x1b\nabc\n[*'), 'abc')
|
||||
|
||||
def test_bracketed_paste_sanitizer(self):
|
||||
from kitty.utils import sanitize_for_bracketed_paste
|
||||
for x in ('\x1b[201~ab\x9b201~cd', '\x1b[201\x1b[201~~ab'): # ]]]
|
||||
q = sanitize_for_bracketed_paste(x.encode('utf-8'))
|
||||
self.assertNotIn(b'\x1b[201~', q)
|
||||
self.assertNotIn('\x9b201~'.encode('utf-8'), q)
|
||||
self.assertIn(b'ab', q)
|
||||
|
||||
@@ -4,9 +4,20 @@
|
||||
import importlib
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import unittest
|
||||
from importlib.resources import contents
|
||||
from typing import Callable, Generator, NoReturn, Sequence, Set
|
||||
from typing import Callable, Generator, Iterator, NoReturn, Sequence, Set
|
||||
|
||||
|
||||
def contents(package: str) -> Iterator[str]:
|
||||
try:
|
||||
if sys.version_info[:2] < (3, 10):
|
||||
raise ImportError('importlib.resources.files() doesnt work with frozen builds on python 3.9')
|
||||
from importlib.resources import files
|
||||
except ImportError:
|
||||
from importlib.resources import contents
|
||||
return iter(contents(package))
|
||||
return (path.name for path in files(package).iterdir())
|
||||
|
||||
|
||||
def itertests(suite: unittest.TestSuite) -> Generator[unittest.TestCase, None, None]:
|
||||
@@ -66,7 +77,7 @@ def type_check() -> NoReturn:
|
||||
generate_stub()
|
||||
from kittens.tui.operations_stub import generate_stub # type: ignore
|
||||
generate_stub()
|
||||
py = shutil.which('python') or shutil.which('python3')
|
||||
py = os.environ.get('PYTHON_FOR_TYPE_CHECK') or shutil.which('python') or shutil.which('python3')
|
||||
os.execlp(py, py, '-m', 'mypy', '--pretty')
|
||||
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ import os, json; from kitty.utils import *; from kitty.fast_data_types import ge
|
||||
'pid': os.getpid(),
|
||||
'font_family': get_options().font_family,
|
||||
'stdin': sys.stdin.read(),
|
||||
}, indent=2), "ALL_OUTPUT_PRESENT", sep="")"""], cwd=cwd, env=env, stdin_data=stdin_data)
|
||||
}, indent=2), "ALL_OUTPUT_PRESENT", sep="")"""], cwd=cwd, env=env, stdin_data=stdin_data, timeout=15.0)
|
||||
self.assertFalse(pty.screen_contents().strip())
|
||||
p.mark_child_as_ready(child.child_id)
|
||||
pty.wait_till(lambda: 'ALL_OUTPUT_PRESENT' in pty.screen_contents())
|
||||
@@ -140,7 +140,7 @@ import os, json; from kitty.utils import *; from kitty.fast_data_types import ge
|
||||
# macOS does not send SIGCHLD when child is continued
|
||||
# https://stackoverflow.com/questions/48487935/sigchld-is-sent-on-sigcont-on-linux-but-not-on-macos
|
||||
p.stdin.close()
|
||||
p.wait(1)
|
||||
p.wait(3)
|
||||
for fd, event in poll.poll(0):
|
||||
read_signals(signal_read_fd, lambda si: None)
|
||||
finally:
|
||||
|
||||
@@ -332,6 +332,15 @@ class TestScreen(BaseTest):
|
||||
s.resize(s.lines - 1, s.columns)
|
||||
self.ae(x_before, s.cursor.x)
|
||||
|
||||
s = self.create_screen()
|
||||
s.draw('abc')
|
||||
b = s.cursor.x
|
||||
s.resize(7, s.columns)
|
||||
self.assertEqual(s.cursor.x, b)
|
||||
s.cursor.x = 0
|
||||
s.resize(5, s.columns)
|
||||
self.assertEqual(s.cursor.x, 0)
|
||||
|
||||
def test_scrollback_fill_after_resize(self):
|
||||
def prepare_screen(content=()):
|
||||
ans = self.create_screen(options={'scrollback_fill_enlarged_window': True})
|
||||
|
||||
@@ -275,13 +275,13 @@ copy --exclude */w.* d1
|
||||
if 'bzip2' in q:
|
||||
raise ValueError('Untarring failed with screen contents:\n' + q)
|
||||
return 'UNTAR_DONE' in q
|
||||
pty.wait_till(check_untar_or_fail)
|
||||
pty.wait_till(check_untar_or_fail, timeout=30)
|
||||
self.assertTrue(os.path.exists(os.path.join(home_dir, '.terminfo/kitty.terminfo')))
|
||||
if SHELL_INTEGRATION_VALUE != 'enabled':
|
||||
pty.wait_till(lambda: len(pty.screen_contents().splitlines()) > 1)
|
||||
pty.wait_till(lambda: len(pty.screen_contents().splitlines()) > 1, timeout=30)
|
||||
self.assertEqual(pty.screen.cursor.shape, 0)
|
||||
else:
|
||||
pty.wait_till(lambda: pty.screen.cursor.shape == CURSOR_BEAM)
|
||||
pty.wait_till(lambda: pty.screen.cursor.shape == CURSOR_BEAM, timeout=30)
|
||||
return pty
|
||||
finally:
|
||||
with suppress(FileNotFoundError):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -205,7 +205,10 @@ def get_data():
|
||||
with open(tdir + '/data.sh') as f:
|
||||
env_vars = f.read()
|
||||
apply_env_vars(env_vars)
|
||||
data_dir = os.path.join(HOME, os.environ.pop('KITTY_SSH_KITTEN_DATA_DIR'))
|
||||
data_dir = os.environ.pop('KITTY_SSH_KITTEN_DATA_DIR')
|
||||
if not os.path.isabs(data_dir):
|
||||
data_dir = os.path.join(HOME, data_dir)
|
||||
data_dir = os.path.abspath(data_dir)
|
||||
shell_integration_dir = os.path.join(data_dir, 'shell-integration')
|
||||
compile_terminfo(tdir + '/home')
|
||||
move(tdir + '/home', HOME)
|
||||
|
||||
@@ -103,7 +103,10 @@ untar_and_read_env() {
|
||||
. "$tdir/bootstrap-utils.sh"
|
||||
. "$tdir/data.sh"
|
||||
[ -z "$KITTY_SSH_KITTEN_DATA_DIR" ] && die "Failed to read SSH data from tty"
|
||||
data_dir="$HOME/$KITTY_SSH_KITTEN_DATA_DIR"
|
||||
case "$KITTY_SSH_KITTEN_DATA_DIR" in
|
||||
/*) data_dir="$KITTY_SSH_KITTEN_DATA_DIR" ;;
|
||||
*) data_dir="$HOME/$KITTY_SSH_KITTEN_DATA_DIR"
|
||||
esac
|
||||
shell_integration_dir="$data_dir/shell-integration"
|
||||
unset KITTY_SSH_KITTEN_DATA_DIR
|
||||
login_shell="$KITTY_LOGIN_SHELL"
|
||||
|
||||
4
test.py
4
test.py
@@ -3,6 +3,7 @@
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import shutil
|
||||
import warnings
|
||||
from contextlib import contextmanager
|
||||
from tempfile import TemporaryDirectory
|
||||
@@ -30,13 +31,14 @@ def main() -> None:
|
||||
path = os.pathsep.join(x for x in paths if not x.startswith(current_home))
|
||||
launcher_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty', 'launcher')
|
||||
path = f'{launcher_dir}{os.pathsep}{path}'
|
||||
PYTHON_FOR_TYPE_CHECK = shutil.which('python') or shutil.which('python3') or ''
|
||||
with TemporaryDirectory() as tdir, env_vars(
|
||||
PYTHONWARNINGS='error', HOME=tdir, USERPROFILE=tdir, PATH=path,
|
||||
XDG_CONFIG_HOME=os.path.join(tdir, '.config'),
|
||||
XDG_CONFIG_DIRS=os.path.join(tdir, '.config'),
|
||||
XDG_DATA_DIRS=os.path.join(tdir, '.local', 'xdg'),
|
||||
XDG_CACHE_HOME=os.path.join(tdir, '.cache'),
|
||||
KITTY_PREWARM_SOCKET='',
|
||||
PYTHON_FOR_TYPE_CHECK=PYTHON_FOR_TYPE_CHECK,
|
||||
):
|
||||
m = importlib.import_module('kitty_tests.main')
|
||||
getattr(m, 'run_tests')()
|
||||
|
||||
Reference in New Issue
Block a user