Compare commits

..

85 Commits

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

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

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

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

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

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

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

Fixes #5603
2022-10-19 11:28:33 +05:30
Kovid Goyal
0f340086c0 Fix resize_draw_strategy=static
The text is now static again but the background image is still scaled, cant be
bothered about that. Fixes #5601
2022-10-19 08:26:31 +05:30
Kovid Goyal
5c34f16ab4 Fix #5600 2022-10-19 07:45:27 +05:30
Kovid Goyal
03fe928e3f show progress while compressing release tarballs 2022-10-17 09:55:09 +05:30
Kovid Goyal
55b53bcac6 version 0.26.4 2022-10-17 08:18:51 +05:30
Kovid Goyal
57d044861f Merge branch 'patch-1' of https://github.com/ms-test/kitty 2022-10-16 13:28:06 +05:30
ms-test
aab149366d Check correct pair member
I haven't looked deeply, but this looked to be a simple typo unless it's checking one both times because of a type invariant. If that is the case, and the check is correct, then a comment and/or code refactoring to call attention to this would likely be a good idea.
2022-10-16 03:53:16 -04:00
Kovid Goyal
896f93822d kitty shell: Also output active tab id
Fixes #5590
2022-10-16 08:52:35 +05:30
Kovid Goyal
6890e265b6 GNOME Wayland: Fix a memory leak in gnome-shell when using client side decorations
destroy CSD buffers that were never attached explicitly, as mutter does
not send release events for these even when the pool is destroyed.
2022-10-12 17:52:47 +05:30
Kovid Goyal
48a4edc199 Merge branch 'action_cwd_doc' of https://github.com/cbosdo/kitty 2022-10-10 14:04:29 +05:30
Cédric Bosdonnat
d63be90a7f Recommend using --cwd=current in open-actions.conf
Editors like Vim are basing path-related commands on the current working
directory. Launching the action with the current CWD in hyperlink opens
makes life easier and would work with every editor.
2022-10-10 10:12:20 +02:00
Kovid Goyal
6af020e4ef Merge branch 'marksisson/simple-docs-change' of https://github.com/marksisson/kitty 2022-10-09 12:18:25 +05:30
Mark Sisson
ca24e9ca67 change linux_package build instructions to use make target 2022-10-09 00:02:38 -05:00
Kovid Goyal
fbfaefd72a Wayland: Fix an abort when changing background colors with wayland_titlebar_color set to background
Fixes #5562
2022-10-07 14:13:12 +05:30
Kovid Goyal
94b3776066 Merge branch 'master' of https://github.com/Diff-fusion/kitty 2022-10-05 09:09:23 +05:30
Felix
15e4be9051 Fix clipboard target properties and memory leak 2022-10-04 17:22:15 +02:00
Kovid Goyal
ae8f3de070 Allow centering the background image
Fixes #5525
2022-10-04 08:26:28 +05:30
Kovid Goyal
c8d18ffe26 Add a note about yet another gnome bug 2022-10-03 22:06:00 +05:30
Kovid Goyal
4d80427908 Wayland: Fix background image scaling using tiled mode on high DPI screens 2022-10-03 21:49:52 +05:30
Kovid Goyal
0e69324469 Fix resize draw strategy == static causing background image/borders to not be drawn 2022-10-03 21:40:00 +05:30
Kovid Goyal
cf22729dfa Fix background_tint not applying to window margins and padding
Fixes #3933
2022-10-03 21:39:57 +05:30
Kovid Goyal
1402c5bbfa Forgot to change check for space for next tab to use its max tab length rather than the current tabs max tab length, since they can now be different 2022-10-03 16:21:50 +05:30
Kovid Goyal
f0f0c8f4fe max_title_length -> max_tab_length 2022-10-03 16:15:24 +05:30
Kovid Goyal
96faac95db DRYer 2022-10-03 15:32:42 +05:30
Kovid Goyal
08041415d1 Document the purpose of for_layout 2022-10-03 13:26:55 +05:30
Kovid Goyal
647b18d345 Tab bar: Improve empty space management when some tabs have short titles, allocate the saved space to the active tab
Fixes #5548
2022-10-03 13:24:21 +05:30
Kovid Goyal
a116e3cadd Merge branch 'patch-1' of https://github.com/sorrycc/kitty 2022-09-30 07:46:32 +05:30
chencheng (云谦)
52c9b5f5b8 Update custom.css 2022-09-30 08:44:09 +08:00
Kovid Goyal
bbc9f588f1 Merge branch 'unicode-15' of https://github.com/page-down/kitty 2022-09-29 09:17:02 +05:30
pagedown
13a3c6b5b2 Update to Unicode 15.0 2022-09-29 10:13:21 +08:00
pagedown
fc15b20f24 Update to Nerd Fonts 2.2.2 2022-09-29 10:13:10 +08:00
Kovid Goyal
cd8f2e5cc6 another mypy update another round of breakage 2022-09-27 07:19:03 +05:30
Kovid Goyal
267798a277 Update the docs of open_url_with to indicate that open-actions.conf is read by the "default" handler 2022-09-26 20:21:44 +05:30
Kovid Goyal
921ac12e90 Clear both actions caches when config file is reloaded 2022-09-26 20:15:08 +05:30
Kovid Goyal
901e00cab1 Cleanup previous PR
1) Dont use deprecated code
2) Always set the dock icon on startup as the dock icon doesnt change
   till the dock is restarted
3) Update the app icon automatically if the mtime on the custom icon in
   the config dir is newer than the mtime of the sentinel file apple
   puts inside the application bundle to indicate it has a custom icon
2022-09-25 18:11:17 +05:30
Kovid Goyal
d0769f3979 ... 2022-09-25 17:36:32 +05:30
Kovid Goyal
7e4dd8cf1e Merge branch 'macos-set-icon' of https://github.com/page-down/kitty 2022-09-25 14:40:36 +05:30
Kovid Goyal
5ea0519f62 Pass through python used for type check to the test code from before the env is sanitized 2022-09-25 14:36:12 +05:30
pagedown
a1029418f8 macOS: Allow to set custom app icon automatically 2022-09-25 15:25:43 +08:00
Kovid Goyal
98eacb2067 panel kitten: Do not depend on xprop 2022-09-24 15:44:54 +05:30
Kovid Goyal
2cef9abbd3 Get rid of one call to xprop in the panel kitten 2022-09-24 15:17:11 +05:30
Kovid Goyal
0fa158c809 Merge branch 'docs' of https://github.com/page-down/kitty 2022-09-24 14:38:42 +05:30
pagedown
c54a6b398b Add some missing no_response for rc commands 2022-09-24 12:43:13 +08:00
pagedown
27ea367123 Docs: Remove the extra backslashes from the install commands 2022-09-24 12:39:05 +08:00
Kovid Goyal
42178a4570 X11: Fix a regression in the previous release that caused pasting from GTK based applications to have extra newlines
Fixes #5528

And let me just re-iterate, GNOME/GTK is developed by morons.
2022-09-24 08:56:50 +05:30
Kovid Goyal
e98ecc1c4f Another auto-indent fix 2022-09-24 08:34:07 +05:30
Kovid Goyal
0d64246209 fix vim autoindent 2022-09-24 08:31:14 +05:30
Kovid Goyal
a0495219ab ... 2022-09-24 08:30:40 +05:30
Kovid Goyal
9d98aa3159 Merge branch 'patch-2' of https://github.com/sashashura/kitty 2022-09-24 07:29:42 +05:30
Alex
5c82f4beca build: harden ci.yml permissions
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-09-23 22:14:57 +02:00
Alex
13539bd8c6 build: harden codeql-analysis.yml permissions
Signed-off-by: Alex <aleksandrosansan@gmail.com>
2022-09-23 22:14:16 +02:00
Kovid Goyal
26b8ab9adf Use a regex for bracketed paste sanitization 2022-09-23 22:18:03 +05:30
Weng Xuetian
7fd119d0eb Fix message recursion between wayland compositor (#5360)
1. Only update preedit if preedit changed.
2. Only send cursor update if cursor position is not changed.
2022-08-11 15:45:45 -07:00
76 changed files with 34296 additions and 32176 deletions

View File

@@ -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

View File

@@ -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:

View File

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

View File

@@ -10,7 +10,7 @@
}
.sidebar-logo {
max-height: 128px;
height: 128px;
}
.major-features li {

View File

@@ -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

View File

@@ -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

View File

@@ -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]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

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

View File

@@ -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?

View File

@@ -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::

View File

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

3
glfw/context.c vendored
View File

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

1
glfw/egl_context.c vendored
View File

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

3
glfw/egl_context.h vendored
View File

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

View File

@@ -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))

View File

@@ -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
View File

@@ -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
View File

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

87
glfw/wl_window.c vendored
View File

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

4
glfw/x11_init.c vendored
View File

@@ -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
View File

@@ -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
View File

@@ -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);
}

View File

@@ -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

View File

@@ -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')

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -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),

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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:

View File

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

View File

@@ -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;

View File

@@ -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 */
};

View File

@@ -6,7 +6,7 @@ import os
import pwd
import sys
from contextlib import suppress
from typing import TYPE_CHECKING, Any, Iterable, NamedTuple, Optional, Set
from typing import TYPE_CHECKING, Any, Iterator, NamedTuple, Optional, Set
from .types import run_once
@@ -22,7 +22,7 @@ class Version(NamedTuple):
appname: str = 'kitty'
kitty_face = '🐱'
version: Version = Version(0, 26, 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:

View File

@@ -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
View File

@@ -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;

View File

@@ -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
View File

@@ -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
View File

@@ -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);

View File

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

View File

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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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('''\

View File

@@ -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.
'''
)

View File

@@ -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)

View File

@@ -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;

View File

@@ -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']

View File

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

View File

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

View File

@@ -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):

View File

@@ -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'

View File

@@ -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):

View File

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

View File

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

View File

@@ -12,6 +12,7 @@
#include "window_logo.h"
#define BLEND_ONTO_OPAQUE glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blending onto opaque colors
#define BLEND_ONTO_OPAQUE_WITH_OPAQUE_OUTPUT glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ZERO, GL_ONE); // blending onto opaque colors with final color having alpha 1
#define BLEND_PREMULT glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // blending of pre-multiplied colors
enum { CELL_PROGRAM, CELL_BG_PROGRAM, CELL_SPECIAL_PROGRAM, CELL_FG_PROGRAM, BORDERS_PROGRAM, GRAPHICS_PROGRAM, GRAPHICS_PREMULT_PROGRAM, GRAPHICS_ALPHA_MASK_PROGRAM, BLIT_PROGRAM, BGIMAGE_PROGRAM, TINT_PROGRAM, NUM_PROGRAMS };
@@ -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();

View File

@@ -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\\'

View File

@@ -96,7 +96,7 @@ pt_to_px(double pt, id_type os_window_id) {
OSWindow*
current_os_window() {
current_os_window(void) {
if (global_state.callback_os_window) return global_state.callback_os_window;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
if (global_state.os_windows[i].is_focused) return global_state.os_windows + i;
@@ -104,6 +104,29 @@ current_os_window() {
return global_state.os_windows;
}
static id_type
last_focused_os_window_id(void) {
id_type ans = 0, max_fc_count = 0;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = &global_state.os_windows[i];
if (w->last_focused_counter > max_fc_count) {
ans = w->id; max_fc_count = w->last_focused_counter;
}
}
return ans;
}
static id_type
current_focused_os_window_id(void) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = &global_state.os_windows[i];
if (w->is_focused) { return w->id; }
}
return 0;
}
OSWindow*
os_window_for_kitty_window(id_type kitty_window_id) {
for (size_t i = 0; i < global_state.num_os_windows; i++) {
@@ -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),

View File

@@ -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:

View File

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

550
kitty/unicode-data.c generated

File diff suppressed because one or more lines are too long

View File

@@ -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);

View File

@@ -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
View File

@@ -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;

View File

@@ -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

View File

@@ -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)

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -205,7 +205,10 @@ def get_data():
with open(tdir + '/data.sh') as f:
env_vars = f.read()
apply_env_vars(env_vars)
data_dir = os.path.join(HOME, os.environ.pop('KITTY_SSH_KITTEN_DATA_DIR'))
data_dir = os.environ.pop('KITTY_SSH_KITTEN_DATA_DIR')
if not os.path.isabs(data_dir):
data_dir = os.path.join(HOME, data_dir)
data_dir = os.path.abspath(data_dir)
shell_integration_dir = os.path.join(data_dir, 'shell-integration')
compile_terminfo(tdir + '/home')
move(tdir + '/home', HOME)

View File

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

View File

@@ -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')()