Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ce04c3e4b | ||
|
|
3b5951eb2f | ||
|
|
aa8dd4ddf4 | ||
|
|
4cd14aee42 | ||
|
|
0b2726ec33 | ||
|
|
bc7f689165 | ||
|
|
6681652c21 | ||
|
|
ed4b5f179e | ||
|
|
e48e166584 | ||
|
|
e7675e8bab | ||
|
|
cf7eaea88c | ||
|
|
3e0855ff45 | ||
|
|
822c9367c6 | ||
|
|
0e46e2a6a7 | ||
|
|
ade7ef19e7 | ||
|
|
e82a6dedd9 | ||
|
|
2fd6bf7442 | ||
|
|
d10681f27f | ||
|
|
e96ff19a7a | ||
|
|
09e75ea329 | ||
|
|
84b03e9f77 | ||
|
|
55597dcea5 | ||
|
|
3290bf3c35 | ||
|
|
6771429911 | ||
|
|
e428858ad6 | ||
|
|
3e73f860d1 | ||
|
|
4cf5bd8d70 | ||
|
|
bffe0f4a6c | ||
|
|
3b8be26cc7 | ||
|
|
9e95939c47 | ||
|
|
0af7af95cf | ||
|
|
2b3e2ea6f8 | ||
|
|
19870983ca | ||
|
|
6e83b4c1bc | ||
|
|
14f5e10fd6 | ||
|
|
ed41e3cd25 | ||
|
|
4cc58e8535 | ||
|
|
e1d9686eb5 | ||
|
|
c5cb4f9e69 | ||
|
|
4a60413e0b | ||
|
|
245f5d60b5 | ||
|
|
23420adfa6 | ||
|
|
c9828dfece | ||
|
|
f52c843fa2 | ||
|
|
b5e704a934 | ||
|
|
0173959e64 | ||
|
|
b00cd5cbc3 | ||
|
|
00aba7c646 | ||
|
|
34db18ea0a | ||
|
|
457c292d3c | ||
|
|
f481c17732 | ||
|
|
4a049b14ca | ||
|
|
ed2dae5884 | ||
|
|
df61ee4ac3 | ||
|
|
850bc12929 | ||
|
|
559e17eb13 | ||
|
|
4c4f6983d1 | ||
|
|
2dbeb151f3 | ||
|
|
82c137878c | ||
|
|
8845bd6ed3 | ||
|
|
c89d861c0e | ||
|
|
3ed536ace1 | ||
|
|
d13ad4ec4b | ||
|
|
cb5ac1fcf8 | ||
|
|
53797b9ead | ||
|
|
cb164e5cac | ||
|
|
c46ebebbb3 | ||
|
|
ff96036f62 | ||
|
|
6409786f8d | ||
|
|
05eb07caf5 | ||
|
|
f5b2a60549 | ||
|
|
620eaf1ada | ||
|
|
9193a20b44 | ||
|
|
bdcac9aed3 | ||
|
|
e9a28550cc | ||
|
|
f3cd3e74ae | ||
|
|
b853fc30c0 | ||
|
|
63e32a94c0 | ||
|
|
ef596f6a8b | ||
|
|
c830097511 | ||
|
|
d8790a4628 | ||
|
|
e06ef732d8 | ||
|
|
792a3e7db8 | ||
|
|
9b32842f66 | ||
|
|
9816979169 | ||
|
|
a74679dd95 | ||
|
|
a40059729e | ||
|
|
6bab967586 | ||
|
|
bb88b33469 | ||
|
|
c440470276 | ||
|
|
ef6164eecb | ||
|
|
7ad360d872 | ||
|
|
0870a33956 | ||
|
|
3ccc2f538d | ||
|
|
17375f0be2 | ||
|
|
5a987a162e | ||
|
|
5058fb9919 | ||
|
|
3335f8602b | ||
|
|
87ab1bbe54 | ||
|
|
f8d52b33f4 | ||
|
|
bc2dcdf9db | ||
|
|
93d1aacfb6 | ||
|
|
3ddaa4aff9 | ||
|
|
75488d568a | ||
|
|
3df80545bd | ||
|
|
c605fd9783 | ||
|
|
60719e7bff | ||
|
|
e9faa3277a | ||
|
|
35f7388725 | ||
|
|
ef1486a779 | ||
|
|
95c44165e9 | ||
|
|
7b816bb96f | ||
|
|
d2ac51aa4a | ||
|
|
26974b1f3a | ||
|
|
3960c6678d | ||
|
|
30b8991a73 | ||
|
|
75a94bcd96 | ||
|
|
e160cbf32b | ||
|
|
2da2e63245 | ||
|
|
f160ea6012 | ||
|
|
8057bc639d | ||
|
|
6f5ec5aa03 | ||
|
|
e343c9445c | ||
|
|
c73e374f1a | ||
|
|
d9b292f808 | ||
|
|
46ef2f313b | ||
|
|
92a705c79e | ||
|
|
12ebdd2574 | ||
|
|
ed2e951032 | ||
|
|
01a34a8846 | ||
|
|
b13174cb9c | ||
|
|
03ac6cab99 | ||
|
|
532eebaefb | ||
|
|
f825345b14 | ||
|
|
1409477ed7 | ||
|
|
d8e4294432 | ||
|
|
5d13908776 | ||
|
|
2832a73285 | ||
|
|
4152ed5809 | ||
|
|
8a90ed70ee | ||
|
|
e2646c08a5 | ||
|
|
7122ba820f | ||
|
|
efca2658ea | ||
|
|
91a714947c | ||
|
|
5d3ac649ba | ||
|
|
6443e726a6 | ||
|
|
6a755bda45 | ||
|
|
dab555ea3b | ||
|
|
a3bbad0060 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -11,3 +11,6 @@ kitty/parse-graphics-command.h linguist-generated=true
|
|||||||
glfw/*.c linguist-vendored=true
|
glfw/*.c linguist-vendored=true
|
||||||
glfw/*.h linguist-vendored=true
|
glfw/*.h linguist-vendored=true
|
||||||
kittens/unicode_input/names.h linguist-generated=true
|
kittens/unicode_input/names.h linguist-generated=true
|
||||||
|
|
||||||
|
*.py text diff=python
|
||||||
|
*.m text diff=objc
|
||||||
|
|||||||
3
.github/workflows/ci.py
vendored
3
.github/workflows/ci.py
vendored
@@ -40,6 +40,9 @@ def install_deps():
|
|||||||
if is_bundle:
|
if is_bundle:
|
||||||
install_bundle()
|
install_bundle()
|
||||||
else:
|
else:
|
||||||
|
if is_macos:
|
||||||
|
# needed for zlib for pillow, should not be needed after pillow 8.0
|
||||||
|
os.environ['PKG_CONFIG_PATH'] = '/usr/local/opt/zlib/lib/pkgconfig'
|
||||||
run('pip3 install Pillow pygments')
|
run('pip3 install Pillow pygments')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
Brewfile
1
Brewfile
@@ -1,4 +1,5 @@
|
|||||||
pkg-config
|
pkg-config
|
||||||
|
zlib
|
||||||
python
|
python
|
||||||
imagemagick
|
imagemagick
|
||||||
harfbuzz
|
harfbuzz
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ def run_kitten(args: List[str]) -> None:
|
|||||||
rk(kitten)
|
rk(kitten)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_config_file(args: List[str]) -> None:
|
||||||
|
from kitty.utils import edit_config_file as f
|
||||||
|
f()
|
||||||
|
|
||||||
|
|
||||||
def namespaced(args: List[str]) -> None:
|
def namespaced(args: List[str]) -> None:
|
||||||
func = namespaced_entry_points[args[1]]
|
func = namespaced_entry_points[args[1]]
|
||||||
func(args[1:])
|
func(args[1:])
|
||||||
@@ -77,6 +82,7 @@ entry_points = {
|
|||||||
'runpy': runpy,
|
'runpy': runpy,
|
||||||
'launch': launch,
|
'launch': launch,
|
||||||
'kitten': run_kitten,
|
'kitten': run_kitten,
|
||||||
|
'edit-config': edit_config_file,
|
||||||
|
|
||||||
'@': remote_control,
|
'@': remote_control,
|
||||||
'+': namespaced,
|
'+': namespaced,
|
||||||
|
|||||||
4
docs/_static/custom.css
vendored
4
docs/_static/custom.css
vendored
@@ -21,6 +21,10 @@ div.body p, div.body dd, div.body li, div.body blockquote {
|
|||||||
text-align: justify;
|
text-align: justify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.body {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Build-time dependencies:
|
|||||||
* gcc or clang
|
* gcc or clang
|
||||||
* pkg-config
|
* pkg-config
|
||||||
* For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for:
|
* For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for:
|
||||||
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev``, and ``libpython-dev``,
|
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev``, and ``libpython3-dev``,
|
||||||
if they are not already installed by your distro.
|
if they are not already installed by your distro.
|
||||||
|
|
||||||
Install and run from source
|
Install and run from source
|
||||||
@@ -78,6 +78,19 @@ you might have to rebuild the app.
|
|||||||
a self signed certificate, see for example, `here
|
a self signed certificate, see for example, `here
|
||||||
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
|
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
|
||||||
|
|
||||||
|
Build and run from source with Nix
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
On NixOS or any other Linux or macOS system with the Nix package manager
|
||||||
|
installed, execute `nix-shell
|
||||||
|
<https://nixos.org/guides/nix-pills/developing-with-nix-shell.html>`_ to create
|
||||||
|
the correct environment to build kitty or use ``nix-shell --pure`` instead to
|
||||||
|
eliminate most of the influence of the outside system, e.g. globally installed
|
||||||
|
packages. ``nix-shell`` will automatically fetch all required dependencies and
|
||||||
|
make them available in the newly spawned shell.
|
||||||
|
|
||||||
|
Then proceed with ``make`` or ``make app`` according to the platform specific instructions above.
|
||||||
|
|
||||||
|
|
||||||
Note for Linux/macOS packagers
|
Note for Linux/macOS packagers
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|||||||
@@ -4,6 +4,111 @@ Changelog
|
|||||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||||
|
|
||||||
|
0.19.3 [2020-12-19]
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- Happy holidays to all kitty users!
|
||||||
|
|
||||||
|
- A new :doc:`broadcast <kittens/broadcast>` kitten to type in all kitty windows
|
||||||
|
simultaneously (:iss:`1569`)
|
||||||
|
|
||||||
|
- Add a new mappable `select_tab` action to choose a tab to switch to even
|
||||||
|
when the tab bar is hidden (:iss:`3115`)
|
||||||
|
|
||||||
|
- Allow specifying text formatting in :opt:`tab_title_template` (:iss:`3146`)
|
||||||
|
|
||||||
|
- Linux: Read :opt:`font_features` from the FontConfig database as well, so
|
||||||
|
that they can be configured in a single, central location (:pull:`3174`)
|
||||||
|
|
||||||
|
- Graphics protocol: Add support for giving individual image placements their
|
||||||
|
own ids and for asking the terminal emulator to assign ids for images. Also
|
||||||
|
allow suppressing responses from the terminal to commands.
|
||||||
|
These are backwards compatible protocol extensions. (:iss:`3133`,
|
||||||
|
:iss:`3163`)
|
||||||
|
|
||||||
|
- Distribute extra pixels among all eight-blocks rather than adding them
|
||||||
|
all to the last block (:iss:`3097`)
|
||||||
|
|
||||||
|
- Fix drawing of a few sextant characters incorrect (:pull:`3105`)
|
||||||
|
|
||||||
|
- macOS: Fix minimize not working for chromeless windows (:iss:`3112`)
|
||||||
|
|
||||||
|
- Preserve lines in the scrollback if a scrolling region is defined that
|
||||||
|
is contiguous with the top of the screen (:iss:`3113`)
|
||||||
|
|
||||||
|
- Wayland: Fix key repeat being stopped by the release of an unrelated key
|
||||||
|
(:iss:`2191`)
|
||||||
|
|
||||||
|
- Add an option, :opt:`detect_urls` to control whether kitty will detect URLs
|
||||||
|
when the mouse moves over them (:pull:`3118`)
|
||||||
|
|
||||||
|
- Graphics protocol: Dont return filename in the error message when opening file
|
||||||
|
fails, since filenames can contain control characters (:iss:`3128`)
|
||||||
|
|
||||||
|
- macOS: Partial fix for traditional fullscreen not working on Big Sur
|
||||||
|
(:iss:`3100`)
|
||||||
|
|
||||||
|
- Fix one ANSI formatting escape code not being removed from the pager history
|
||||||
|
buffer when piping it as plain text (:iss:`3132`)
|
||||||
|
|
||||||
|
- Match the save/restore cursor behavior of other terminals, for the sake of
|
||||||
|
interoperability. This means that doing a DECRC without a prior DECSC is now
|
||||||
|
undefined (:iss:`1264`)
|
||||||
|
|
||||||
|
- Fix mapping ``remote_control send-text`` not working (:iss:`3147`)
|
||||||
|
|
||||||
|
- Add a ``right`` option for :opt:`tab_switch_strategy` (:pull:`3155`)
|
||||||
|
|
||||||
|
- Fix a regression in 0.19.0 that caused a rare crash when using the optional
|
||||||
|
:opt:`scrollback_pager_history_size` (:iss:`3049`)
|
||||||
|
|
||||||
|
- Full screen kittens: Fix incorrect cursor position after kitten quits
|
||||||
|
(:iss:`3176`)
|
||||||
|
|
||||||
|
|
||||||
|
0.19.2 [2020-11-13]
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- A new :doc:`kittens/query_terminal` kitten to easily query the running kitty
|
||||||
|
via escape codes to detect its version, and the values of
|
||||||
|
configuration options that enable or disable terminal features.
|
||||||
|
|
||||||
|
- Options to control mouse pointer shape, :opt:`default_pointer_shape`, and
|
||||||
|
:opt:`pointer_shape_when_dragging` (:pull:`3041`)
|
||||||
|
|
||||||
|
- Font independent rendering for braille characters, which ensures they are properly
|
||||||
|
aligned at all font sizes.
|
||||||
|
|
||||||
|
- Fix a regression in 0.19.0 that caused borders not to be drawn when setting
|
||||||
|
:opt:`window_margin_width` and keeping :opt:`draw_minimal_borders` on
|
||||||
|
(:iss:`3017`)
|
||||||
|
|
||||||
|
- Fix a regression in 0.19.0 that broke rendering of one-eight bar unicode
|
||||||
|
characters at very small font sizes (:iss:`3025`)
|
||||||
|
|
||||||
|
- Wayland: Fix a crash under GNOME when using multiple OS windows
|
||||||
|
(:pull:`3066`)
|
||||||
|
|
||||||
|
- Fix selections created by dragging upwards not being auto-cleared when
|
||||||
|
screen contents change (:pull:`3028`)
|
||||||
|
|
||||||
|
- macOS: Fix kitty not being added to PATH automatically when using pre-built
|
||||||
|
binaries (:iss:`3063`)
|
||||||
|
|
||||||
|
- Allow adding MIME definitions to kitty by placing a ``mime.types`` file in
|
||||||
|
the kitty config directory (:iss:`3056`)
|
||||||
|
|
||||||
|
- Dont ignore :option:`--title` when using a session file that defines no
|
||||||
|
windows (:iss:`3055`)
|
||||||
|
|
||||||
|
- Fix the send_text action not working in URL handlers (:iss:`3081`)
|
||||||
|
|
||||||
|
- Fix last character of URL not being detected if it is the only character on a
|
||||||
|
new line (:iss:`3088`)
|
||||||
|
|
||||||
|
- Don't restrict the ICH,DCH,REP control codes to only the current scroll region (:iss:`3090`, :iss:`3096`)
|
||||||
|
|
||||||
|
|
||||||
0.19.1 [2020-10-06]
|
0.19.1 [2020-10-06]
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from pygments.lexer import RegexLexer, bygroups # type: ignore
|
|||||||
from pygments.token import ( # type: ignore
|
from pygments.token import ( # type: ignore
|
||||||
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
Comment, Keyword, Literal, Name, Number, String, Whitespace
|
||||||
)
|
)
|
||||||
from sphinx import addnodes # type: ignore
|
from sphinx import addnodes, version_info # type: ignore
|
||||||
from sphinx.environment.adapters.toctree import TocTree # type: ignore
|
from sphinx.environment.adapters.toctree import TocTree # type: ignore
|
||||||
from sphinx.util.logging import getLogger # type: ignore
|
from sphinx.util.logging import getLogger # type: ignore
|
||||||
|
|
||||||
@@ -555,7 +555,7 @@ def process_shortcut_link(env: Any, refnode: Any, has_explicit_title: bool, titl
|
|||||||
|
|
||||||
|
|
||||||
def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
def write_conf_docs(app: Any, all_kitten_names: Iterable[str]) -> None:
|
||||||
app.add_lexer('conf', ConfLexer)
|
app.add_lexer('conf', ConfLexer() if version_info[0] < 3 else ConfLexer)
|
||||||
app.add_object_type(
|
app.add_object_type(
|
||||||
'opt', 'opt',
|
'opt', 'opt',
|
||||||
indextemplate="pair: %s; Config Setting",
|
indextemplate="pair: %s; Config Setting",
|
||||||
@@ -604,7 +604,7 @@ def setup(app: Any) -> None:
|
|||||||
write_cli_docs(kn)
|
write_cli_docs(kn)
|
||||||
write_remote_control_protocol_docs()
|
write_remote_control_protocol_docs()
|
||||||
write_conf_docs(app, kn)
|
write_conf_docs(app, kn)
|
||||||
app.add_lexer('session', SessionLexer)
|
app.add_lexer('session', SessionLexer() if version_info[0] < 3 else SessionLexer)
|
||||||
app.add_role('link', link_role)
|
app.add_role('link', link_role)
|
||||||
app.add_role('iss', partial(num_role, 'issues'))
|
app.add_role('iss', partial(num_role, 'issues'))
|
||||||
app.add_role('pull', partial(num_role, 'pull'))
|
app.add_role('pull', partial(num_role, 'pull'))
|
||||||
|
|||||||
27
docs/faq.rst
27
docs/faq.rst
@@ -47,6 +47,10 @@ terminfo files to the server::
|
|||||||
|
|
||||||
kitty +kitten ssh myserver
|
kitty +kitten ssh myserver
|
||||||
|
|
||||||
|
This ssh kitten takes all the same command line arguments
|
||||||
|
as ssh, you can alias it to ssh in your shell's rc files to avoid having to
|
||||||
|
type it each time.
|
||||||
|
|
||||||
If for some reason that does not work (typically because the server is using a
|
If for some reason that does not work (typically because the server is using a
|
||||||
non POSIX compliant shell), you can use the following one-liner instead (it
|
non POSIX compliant shell), you can use the following one-liner instead (it
|
||||||
is slower as it needs to ssh into the server twice, but will work with most
|
is slower as it needs to ssh into the server twice, but will work with most
|
||||||
@@ -165,9 +169,12 @@ kitty is not able to use my favorite font?
|
|||||||
|kitty| achieves its stellar performance by caching alpha masks of each rendered
|
|kitty| achieves its stellar performance by caching alpha masks of each rendered
|
||||||
character on the GPU, so that every character needs to be rendered only once.
|
character on the GPU, so that every character needs to be rendered only once.
|
||||||
This means it is a strictly character cell based display. As such it can use
|
This means it is a strictly character cell based display. As such it can use
|
||||||
only monospace fonts, since every cell in the grid has to be the same size. If
|
only monospace fonts, since every cell in the grid has to be the same size.
|
||||||
your font is not listed in ``kitty list-fonts`` it means that it is not
|
Furthermore, it needs fonts to be freely resizable, so it does not support
|
||||||
monospace. On Linux you can list all monospace fonts with::
|
bitmapped fonts.
|
||||||
|
|
||||||
|
If your font is not listed in ``kitty list-fonts`` it means that it is not
|
||||||
|
monospace or is a bitmapped font. On Linux you can list all monospace fonts with::
|
||||||
|
|
||||||
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
||||||
|
|
||||||
@@ -179,7 +186,7 @@ following :file:`~/.config/fontconfig/fonts.conf`::
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||||
<fontconfig>
|
<fontconfig>
|
||||||
<match target="font">
|
<match target="scan">
|
||||||
<test name="family">
|
<test name="family">
|
||||||
<string>Your Font Family Name</string>
|
<string>Your Font Family Name</string>
|
||||||
</test>
|
</test>
|
||||||
@@ -233,6 +240,18 @@ available. The manual way to figure it out is:
|
|||||||
|
|
||||||
3. Use ``\x(hexval)`` in your ``send_text`` command in kitty. So in this example, ``\x13``
|
3. Use ``\x(hexval)`` in your ``send_text`` command in kitty. So in this example, ``\x13``
|
||||||
|
|
||||||
|
How do I open a new window or tab with the same working directory as the current window?
|
||||||
|
--------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
In :file:`kitty.conf` add the following::
|
||||||
|
|
||||||
|
map f1 launch --cwd=current
|
||||||
|
map f2 launch --cwd=current --type=tab
|
||||||
|
|
||||||
|
Pressing :kbd:`F1` will open a new kitty window with the same working directory
|
||||||
|
as the current window. The :doc:`launch command <launch>` is very powerful,
|
||||||
|
explore :doc:`its documentation <launch>`.
|
||||||
|
|
||||||
|
|
||||||
I am using tmux and have a problem
|
I am using tmux and have a problem
|
||||||
--------------------------------------
|
--------------------------------------
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ Some programs that use the kitty graphics protocol:
|
|||||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
||||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||||
information tool
|
information tool
|
||||||
|
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
||||||
|
* `glkitty <https://github.com/michaeljclark/glkitty>`_ - C library to draw OpenGL shaders in the terminal with a glgears demo
|
||||||
|
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
@@ -313,7 +315,7 @@ terminal emulators handle input in a FIFO manner, anyway.
|
|||||||
|
|
||||||
So for example, you could send::
|
So for example, you could send::
|
||||||
|
|
||||||
<ESC>_Gi=31,s=1,v=1,a=q,t=d,f=24;<NUL><NUL><NUL><ESC>\<ESC>[c
|
<ESC>_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA<ESC>\<ESC>[c
|
||||||
|
|
||||||
If you get back a response to the graphics query, the terminal emulator supports
|
If you get back a response to the graphics query, the terminal emulator supports
|
||||||
the protocol, if you get back a response to the device attributes query without
|
the protocol, if you get back a response to the device attributes query without
|
||||||
@@ -325,11 +327,12 @@ Display images on screen
|
|||||||
|
|
||||||
Every transmitted image can be displayed an arbitrary number of times on the
|
Every transmitted image can be displayed an arbitrary number of times on the
|
||||||
screen, in different locations, using different parts of the source image, as
|
screen, in different locations, using different parts of the source image, as
|
||||||
needed. You can either simultaneously transmit and display an image using the
|
needed. Each such display of an image is called a *placement*. You can either
|
||||||
action ``a=T``, or first transmit the image with a id, such as ``i=10`` and then display
|
simultaneously transmit and display an image using the action ``a=T``, or first
|
||||||
it with ``a=p,i=10`` which will display the previously transmitted image at the current
|
transmit the image with a id, such as ``i=10`` and then display it with
|
||||||
cursor position. When specifying an image id, the terminal emulator will reply with an
|
``a=p,i=10`` which will display the previously transmitted image at the current
|
||||||
acknowledgement code, which will be either::
|
cursor position. When specifying an image id, the terminal emulator will reply
|
||||||
|
to the placement request with an acknowledgement code, which will be either::
|
||||||
|
|
||||||
<ESC>_Gi=<id>;OK<ESC>\
|
<ESC>_Gi=<id>;OK<ESC>\
|
||||||
|
|
||||||
@@ -342,6 +345,24 @@ scheme described above for querying available transmission media, except that
|
|||||||
here we are querying if the image with the specified id is available or needs to
|
here we are querying if the image with the specified id is available or needs to
|
||||||
be re-transmitted.
|
be re-transmitted.
|
||||||
|
|
||||||
|
Since there can be many placements per image, you can also give placements an
|
||||||
|
id. To do so add the ``p`` key with a number between ``1`` and ``4294967295``.
|
||||||
|
When you specify a placement id, it will be added to the acknowledgement code
|
||||||
|
above. Every placement is uniquely identified by the pair of the ``image id``
|
||||||
|
and the ``placement id``. If you specify a placement id for an image that does
|
||||||
|
not have an id, it will be ignored. An example response::
|
||||||
|
|
||||||
|
<ESC>_Gi=<image id>,p=<placement id>;OK<ESC>\
|
||||||
|
|
||||||
|
If you send two placements with the same ``image id`` and ``placement id`` the
|
||||||
|
second one will replace the first. This can be used to resize or move
|
||||||
|
placements around the screen, without flicker.
|
||||||
|
|
||||||
|
|
||||||
|
.. versionadded:: 0.19.3
|
||||||
|
Support for specifying placement ids (see :doc:`kittens/query_terminal` to query kitty version)
|
||||||
|
|
||||||
|
|
||||||
Controlling displayed image layout
|
Controlling displayed image layout
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
@@ -368,6 +389,13 @@ allows rendering of text on top of images. Negative z-index values below
|
|||||||
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
|
INT32_MIN/2 (-1,073,741,824) will be drawn under cells with non-default background
|
||||||
colors.
|
colors.
|
||||||
|
|
||||||
|
.. note:: After placing an image on the screen the cursor must be moved to the
|
||||||
|
right by the number of cols in the image placement rectangle and down by the
|
||||||
|
number of rows in the image placement rectangle. If either of these cause
|
||||||
|
the cursor to leave either the screen or the scroll area, the exact
|
||||||
|
positioning of the cursor is undefined, and up to implementations.
|
||||||
|
|
||||||
|
|
||||||
Deleting images
|
Deleting images
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
@@ -384,24 +412,77 @@ scrollback buffer. The values of the ``x`` and ``y`` keys are the same as cursor
|
|||||||
================= ============
|
================= ============
|
||||||
Value of ``d`` Meaning
|
Value of ``d`` Meaning
|
||||||
================= ============
|
================= ============
|
||||||
``a`` or ``A`` Delete all images visible on screen
|
``a`` or ``A`` Delete all placements visible on screen
|
||||||
``i`` or ``I`` Delete all images with the specified id, specified using the ``i`` key.
|
``i`` or ``I`` Delete all images with the specified id, specified using the ``i`` key. If you specify a ``p`` key for the placement id as well, then only the placement with the specified image id and placement id will be deleted.
|
||||||
``c`` or ``C`` Delete all images that intersect with the current cursor position.
|
``n`` or ``N`` Delete newest image with the specified number, specified using the ``I`` key. If you specify a ``p`` key for the
|
||||||
``p`` or ``P`` Delete all images that intersect a specific cell, the cell is specified using the ``x`` and ``y`` keys
|
placement id as well, then only the placement with the specified number and placement id will be deleted.
|
||||||
``q`` or ``Q`` Delete all images that intersect a specific cell having a specific z-index. The cell and z-index is specified using the ``x``, ``y`` and ``z`` keys.
|
``c`` or ``C`` Delete all placements that intersect with the current cursor position.
|
||||||
``x`` or ``X`` Delete all images that intersect the specified column, specified using the ``x`` key.
|
``c`` or ``C`` Delete all placements that intersect with the current cursor position.
|
||||||
``y`` or ``Y`` Delete all images that intersect the specified row, specified using the ``y`` key.
|
``p`` or ``P`` Delete all placements that intersect a specific cell, the cell is specified using the ``x`` and ``y`` keys
|
||||||
``z`` or ``Z`` Delete all images that have the specified z-index, specified using the ``z`` key.
|
``q`` or ``Q`` Delete all placements that intersect a specific cell having a specific z-index. The cell and z-index is specified using the ``x``, ``y`` and ``z`` keys.
|
||||||
|
``x`` or ``X`` Delete all placements that intersect the specified column, specified using the ``x`` key.
|
||||||
|
``y`` or ``Y`` Delete all placements that intersect the specified row, specified using the ``y`` key.
|
||||||
|
``z`` or ``Z`` Delete all placements that have the specified z-index, specified using the ``z`` key.
|
||||||
================= ============
|
================= ============
|
||||||
|
|
||||||
|
|
||||||
|
Note when all placements for an image have been deleted, the image is also
|
||||||
|
deleted, if the capital letter form above is specified. Also, when the terminal
|
||||||
|
is running out of quota space for image, images without placements will be
|
||||||
|
preferentially deleted.
|
||||||
|
|
||||||
Some examples::
|
Some examples::
|
||||||
|
|
||||||
<ESC>_Ga=d<ESC>\ # delete all visible images
|
<ESC>_Ga=d<ESC>\ # delete all visible placements
|
||||||
<ESC>_Ga=d,d=i,i=10<ESC>\ # delete the image with id=10, without freeing data
|
<ESC>_Ga=d,d=i,i=10<ESC>\ # delete the image with id=10, without freeing data
|
||||||
<ESC>_Ga=d,d=Z,z=-1<ESC>\ # delete the images with z-index -1, also freeing up image data
|
<ESC>_Ga=d,d=i,i=10,p=7<ESC>\ # delete the image with id=10 and placement id=7, without freeing data
|
||||||
<ESC>_Ga=d,d=p,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4), without freeing data
|
<ESC>_Ga=d,d=Z,z=-1<ESC>\ # delete the placements with z-index -1, also freeing up image data
|
||||||
|
<ESC>_Ga=d,d=p,x=3,y=4<ESC>\ # delete all placements that intersect the cell at (3, 4), without freeing data
|
||||||
|
|
||||||
|
|
||||||
|
Suppressing responses from the terminal
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you are using the graphics protocol from a limited client, such as a shell
|
||||||
|
script, it might be useful to avoid having to process responses from the
|
||||||
|
terminal. For this, you can use the ``q`` key. Set it to ``1`` to suppress
|
||||||
|
``OK`` responses and to ``2`` to suppress failure responses.
|
||||||
|
|
||||||
|
.. versionadded:: 0.19.3
|
||||||
|
The ability to suppress responses (see :doc:`kittens/query_terminal` to query kitty version)
|
||||||
|
|
||||||
|
|
||||||
|
Requesting image ids from the terminal
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
If you are writing a program that is going to share the screen with other
|
||||||
|
programs and you still want to use image ids, it is not possible to know
|
||||||
|
what image ids are free to use. In this case, instead of using the ``i``
|
||||||
|
key to specify and image id use the ``I`` key to specify and image number
|
||||||
|
instead. These numbers are not unique.
|
||||||
|
When creating a new image, even if an existing image has the same number a new
|
||||||
|
one is created. And the terminal will reply with the id of the newly created
|
||||||
|
image. For example, when creating an image with ``I=13``, the terminal will
|
||||||
|
send the response::
|
||||||
|
|
||||||
|
<ESC>_Gi=99,I=13;OK<ESC>\
|
||||||
|
|
||||||
|
Here, the value of ``i`` is the id for the newly created image and the value of
|
||||||
|
``I`` is the same as was sent in the creation command.
|
||||||
|
|
||||||
|
All future commands that refer to images using the image number, such as
|
||||||
|
creating placements or deleting images, will act on only the newest image with
|
||||||
|
that number. This allows the client program to send a bunch of commands dealing
|
||||||
|
with an image by image number without waiting for a response from the terminal
|
||||||
|
with the image id. Once such a response is received, the client program should
|
||||||
|
use the ``i`` key with the image id for all future communication.
|
||||||
|
|
||||||
|
.. note:: Specifying both ``i`` and ``I`` keys in any command is an error. The
|
||||||
|
terminal must reply with an EINVAL error message, unless silenced.
|
||||||
|
|
||||||
|
.. versionadded:: 0.19.3
|
||||||
|
The ability to use image numbers (see :doc:`kittens/query_terminal` to query kitty version)
|
||||||
|
|
||||||
|
|
||||||
Image persistence and storage quotas
|
Image persistence and storage quotas
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@@ -424,6 +505,9 @@ Key Value Default Description
|
|||||||
======= ==================== ========= =================
|
======= ==================== ========= =================
|
||||||
``a`` Single character. ``t`` The overall action this graphics command is performing.
|
``a`` Single character. ``t`` The overall action this graphics command is performing.
|
||||||
``(t, T, q, p, d)``
|
``(t, T, q, p, d)``
|
||||||
|
|
||||||
|
``q`` ``0, 1, 2`` ``0`` Suppress responses from the terminal to this graphics command.
|
||||||
|
|
||||||
**Keys for image transmission**
|
**Keys for image transmission**
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
``f`` Positive integer. ``32`` The format in which the image data is sent.
|
``f`` Positive integer. ``32`` The format in which the image data is sent.
|
||||||
@@ -436,6 +520,8 @@ Key Value Default Description
|
|||||||
``O`` Positive integer. ``0`` The offset from which to read data from a file.
|
``O`` Positive integer. ``0`` The offset from which to read data from a file.
|
||||||
``i`` Positive integer.
|
``i`` Positive integer.
|
||||||
``(0 - 4294967295)`` ``0`` The image id
|
``(0 - 4294967295)`` ``0`` The image id
|
||||||
|
``p`` Positive integer.
|
||||||
|
``(0 - 4294967295)`` ``0`` The placement id
|
||||||
``o`` Single character. ``null`` The type of data compression.
|
``o`` Single character. ``null`` The type of data compression.
|
||||||
``only z``
|
``only z``
|
||||||
``m`` zero or one ``0`` Whether there is more chunked data available.
|
``m`` zero or one ``0`` Whether there is more chunked data available.
|
||||||
@@ -453,9 +539,9 @@ Key Value Default Description
|
|||||||
**Keys for deleting images**
|
**Keys for deleting images**
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
``d`` Single character. ``a`` What to delete.
|
``d`` Single character. ``a`` What to delete.
|
||||||
``(a, A, c, C, i,
|
``(a, A, c, C, n, N,
|
||||||
I, p, P, q, Q, x, X,
|
i, I, p, P, q, Q, x,
|
||||||
y, Y, z, Z)``.
|
X, y, Y, z, Z)``.
|
||||||
======= ==================== ========= =================
|
======= ==================== ========= =================
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ images and other types of documents directly in your terminal, even over SSH.
|
|||||||
`koneko <https://github.com/twenty5151/koneko>`_
|
`koneko <https://github.com/twenty5151/koneko>`_
|
||||||
browse images from the pixiv artist community directly in kitty
|
browse images from the pixiv artist community directly in kitty
|
||||||
|
|
||||||
|
`viu <https://github.com/atanunq/viu>`_
|
||||||
|
view images in the terminal, similar to kitty's icat, however also supports
|
||||||
|
displaying animated GIFs
|
||||||
|
|
||||||
|
|
||||||
System and data visualisation tools
|
System and data visualisation tools
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
@@ -63,6 +67,12 @@ System and data visualisation tools
|
|||||||
|
|
||||||
iplot 'sin(x*3)*exp(x*.2)'
|
iplot 'sin(x*3)*exp(x*.2)'
|
||||||
|
|
||||||
|
`onefetch <https://github.com/o2sh/onefetch>`_
|
||||||
|
a tool to fetch information about your git repositories
|
||||||
|
|
||||||
|
`wttr <https://github.com/chubin/wttr.in>`_
|
||||||
|
a tool to display weather information in your terminal with curl
|
||||||
|
|
||||||
|
|
||||||
Editor integration
|
Editor integration
|
||||||
-----------------------
|
-----------------------
|
||||||
@@ -116,3 +126,6 @@ Miscellaneous
|
|||||||
`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
|
`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
|
||||||
URL hints kitten for WeeChat that works without having to use WeeChat's
|
URL hints kitten for WeeChat that works without having to use WeeChat's
|
||||||
raw-mode.
|
raw-mode.
|
||||||
|
|
||||||
|
`glkitty <https://github.com/michaeljclark/glkitty>`_
|
||||||
|
C library to draw OpenGL shaders in the terminal with a glgears demo
|
||||||
|
|||||||
23
docs/kittens/broadcast.rst
Normal file
23
docs/kittens/broadcast.rst
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
broadcast - type text in all kitty windows
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
The ``broadcast`` kitten can be used to type text simultaneously in
|
||||||
|
all kitty windows (or a subset as desired).
|
||||||
|
|
||||||
|
To use it, simply create a mapping in :file:`kitty.conf` such as::
|
||||||
|
|
||||||
|
map F1 launch --allow-remote-control kitty +kitten broadcast
|
||||||
|
|
||||||
|
Then press the :kbd:`F1` key and whatever you type in the newly created widow
|
||||||
|
will be sent to all kitty windows.
|
||||||
|
|
||||||
|
You can use the options described below to control which windows
|
||||||
|
are selected.
|
||||||
|
|
||||||
|
.. program:: kitty +kitten broadcast
|
||||||
|
|
||||||
|
|
||||||
|
Command Line Interface
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
.. include:: /generated/cli-kitten-broadcast.rst
|
||||||
@@ -20,6 +20,9 @@ your machine).
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
from kitty.boss import Boss
|
||||||
|
|
||||||
def main(args: List[str]) -> str:
|
def main(args: List[str]) -> str:
|
||||||
# this is the main entry point of the kitten, it will be executed in
|
# this is the main entry point of the kitten, it will be executed in
|
||||||
# the overlay window when the kitten is launched
|
# the overlay window when the kitten is launched
|
||||||
@@ -28,7 +31,7 @@ your machine).
|
|||||||
# handle_result() function
|
# handle_result() function
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
def handle_result(args: List[str], answer: str, target_window_id: int, boss: kitty.boss.Boss) -> None:
|
def handle_result(args: List[str], answer: str, target_window_id: int, boss: Boss) -> None:
|
||||||
# get the kitty window into which to paste answer
|
# get the kitty window into which to paste answer
|
||||||
w = boss.window_id_map.get(target_window_id)
|
w = boss.window_id_map.get(target_window_id)
|
||||||
if w is not None:
|
if w is not None:
|
||||||
|
|||||||
@@ -28,11 +28,17 @@ to be used can be modified using options passed to the kitten. For example::
|
|||||||
will open the selected file in a new tab inside neovim when you press
|
will open the selected file in a new tab inside neovim when you press
|
||||||
:kbd:`ctrl+g`.
|
:kbd:`ctrl+g`.
|
||||||
|
|
||||||
|
Pressing :sc:`open_selected_hyperlink` will open hyperlinks, i.e. a URL
|
||||||
|
that has been marked as such by the program running in the terminal,
|
||||||
|
for example, by ``ls --hyperlink=auto``. You can also :doc:`customize what actions are
|
||||||
|
taken for different types of URLs <../open_actions>`.
|
||||||
|
|
||||||
The hints kitten is very powerful to see more detailed help on its various
|
The hints kitten is very powerful to see more detailed help on its various
|
||||||
options and modes of operation, see below. You can use these options to
|
options and modes of operation, see below. You can use these options to
|
||||||
create mappings in :file:`kitty.conf` to select various different text
|
create mappings in :file:`kitty.conf` to select various different text
|
||||||
snippets. See :sc:`insert_selected_path` for examples.
|
snippets. See :sc:`insert_selected_path` for examples.
|
||||||
|
|
||||||
|
|
||||||
Completely customizing the matching and actions of the kitten
|
Completely customizing the matching and actions of the kitten
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
21
docs/kittens/query_terminal.rst
Normal file
21
docs/kittens/query_terminal.rst
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Query terminal
|
||||||
|
=================
|
||||||
|
|
||||||
|
Used to query kitty from terminal programs about version, values of various
|
||||||
|
runtime options controlling its features, etc.
|
||||||
|
|
||||||
|
The querying is done using the (*semi*) standard XTGETTCAP escape sequence
|
||||||
|
pioneered by XTerm, so it works over SSH as well. The downside is that it
|
||||||
|
is slow, since it requires a roundtrip to the terminal emulator and back.
|
||||||
|
|
||||||
|
If you want to do some of the same querying in your terminal program without
|
||||||
|
depending on the kitten, you can do so, by processing the same escape codes.
|
||||||
|
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`_
|
||||||
|
for *XTGETTCAP* to see the syntax for the escape code and read the source
|
||||||
|
of this kitten to find the values of the keys for the various queries.
|
||||||
|
|
||||||
|
|
||||||
|
Command Line Interface
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. include:: ../generated/cli-kitten-query_terminal.rst
|
||||||
@@ -81,7 +81,11 @@ lines. The various available criteria are:
|
|||||||
|
|
||||||
``mime``
|
``mime``
|
||||||
A comma separated list of MIME types, for example: ``text/*, image/*,
|
A comma separated list of MIME types, for example: ``text/*, image/*,
|
||||||
application/pdf``
|
application/pdf``. You can add MIME types to kitty by creating the
|
||||||
|
:file:`mime.types` in the kitty configuration directory. Useful if your
|
||||||
|
system MIME database does not have definitions you need. This file is
|
||||||
|
in the standard format of one definition per line, like: ``text/plain rst
|
||||||
|
md``.
|
||||||
|
|
||||||
``ext``
|
``ext``
|
||||||
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
||||||
|
|||||||
@@ -115,8 +115,10 @@ shell. Run ``kitty @`` with no arguments and you will be dropped into the |kitty
|
|||||||
shell with completion for |kitty| command names and options.
|
shell with completion for |kitty| command names and options.
|
||||||
|
|
||||||
You can even open the |kitty| shell inside a running |kitty| using a simple
|
You can even open the |kitty| shell inside a running |kitty| using a simple
|
||||||
keyboard shortcut (:sc:`kitty_shell` by default). This has the added
|
keyboard shortcut (:sc:`kitty_shell` by default).
|
||||||
advantage that you don't need to use ``allow_remote_control`` to make it work.
|
|
||||||
|
.. note:: This has the added advantage that you don't need to use
|
||||||
|
``allow_remote_control`` to make it work.
|
||||||
|
|
||||||
|
|
||||||
Allowing only some windows to control kitty
|
Allowing only some windows to control kitty
|
||||||
@@ -134,6 +136,12 @@ still write to the pipes of any other program on the same computer and
|
|||||||
therefore can control |kitty|. It can, however, be useful to block programs
|
therefore can control |kitty|. It can, however, be useful to block programs
|
||||||
running on other computers (for example, over ssh) or as other users.
|
running on other computers (for example, over ssh) or as other users.
|
||||||
|
|
||||||
|
.. note:: You dont need ``allow_remote_control`` to make this work as it is
|
||||||
|
limited to only programs running in that specific window. Be careful with
|
||||||
|
what programs you run in such windows, since they can effectively control
|
||||||
|
kitty, as if you were running with ``allow_remote_control`` turned on.
|
||||||
|
|
||||||
|
|
||||||
Mapping key presses to remote control commands
|
Mapping key presses to remote control commands
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
|
||||||
@@ -146,6 +154,23 @@ Then pressing the :kbd:`F1` key will set the active window margins to 30.
|
|||||||
The syntax for what follows :code:`remote_control` is exactly the same
|
The syntax for what follows :code:`remote_control` is exactly the same
|
||||||
as the syntax for what follows :code:`kitty @` above.
|
as the syntax for what follows :code:`kitty @` above.
|
||||||
|
|
||||||
|
.. note:: You do not need ``allow_remote_control`` to use these mappings,
|
||||||
|
as they are not actual remote programs, but are simply a way to resuse
|
||||||
|
the remote control infrastructure via keybings.
|
||||||
|
|
||||||
|
|
||||||
|
Broadcasting what you type to all kitty windows
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
As a simple illustration of the power of remote control, lets
|
||||||
|
have what we type sent to all open kitty windows. To do that define the
|
||||||
|
following mapping in :file:`kitty.conf`::
|
||||||
|
|
||||||
|
map F1 launch --allow-remote-control kitty +kitten broadcast
|
||||||
|
|
||||||
|
Now press, F1 and start typing, what you type will be sent to all windows,
|
||||||
|
live, as you type it.
|
||||||
|
|
||||||
|
|
||||||
Documentation for the remote control protocol
|
Documentation for the remote control protocol
|
||||||
-----------------------------------------------
|
-----------------------------------------------
|
||||||
|
|||||||
@@ -251,12 +251,15 @@ def graphics_parser() -> None:
|
|||||||
flag = frozenset
|
flag = frozenset
|
||||||
keymap: KeymapType = {
|
keymap: KeymapType = {
|
||||||
'a': ('action', flag('tTqpd')),
|
'a': ('action', flag('tTqpd')),
|
||||||
'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')),
|
'd': ('delete_action', flag('aAiIcCnNpPqQxXyYzZ')),
|
||||||
't': ('transmission_type', flag('dfts')),
|
't': ('transmission_type', flag('dfts')),
|
||||||
'o': ('compressed', flag('z')),
|
'o': ('compressed', flag('z')),
|
||||||
'f': ('format', 'uint'),
|
'f': ('format', 'uint'),
|
||||||
'm': ('more', 'uint'),
|
'm': ('more', 'uint'),
|
||||||
'i': ('id', 'uint'),
|
'i': ('id', 'uint'),
|
||||||
|
'I': ('image_number', 'uint'),
|
||||||
|
'p': ('placement_id', 'uint'),
|
||||||
|
'q': ('quiet', 'uint'),
|
||||||
'w': ('width', 'uint'),
|
'w': ('width', 'uint'),
|
||||||
'h': ('height', 'uint'),
|
'h': ('height', 'uint'),
|
||||||
'x': ('x_offset', 'uint'),
|
'x': ('x_offset', 'uint'),
|
||||||
|
|||||||
3
glfw/cocoa_platform.h
vendored
3
glfw/cocoa_platform.h
vendored
@@ -128,6 +128,9 @@ typedef struct _GLFWwindowNS
|
|||||||
|
|
||||||
bool maximized;
|
bool maximized;
|
||||||
bool retina;
|
bool retina;
|
||||||
|
bool in_traditional_fullscreen;
|
||||||
|
bool titlebar_hidden;
|
||||||
|
unsigned long pre_full_screen_style_mask;
|
||||||
|
|
||||||
// Cached window properties to filter out duplicate events
|
// Cached window properties to filter out duplicate events
|
||||||
int width, height;
|
int width, height;
|
||||||
|
|||||||
@@ -1314,6 +1314,16 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c,
|
|||||||
glfw_window = NULL;
|
glfw_window = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)validateMenuItem:(NSMenuItem *)item {
|
||||||
|
if (item.action == @selector(performMiniaturize:)) return YES;
|
||||||
|
return [super validateMenuItem:item];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)performMiniaturize:(id)sender
|
||||||
|
{
|
||||||
|
if (glfw_window && (!glfw_window->decorated || glfw_window->ns.titlebar_hidden)) [self miniaturize:self];
|
||||||
|
else [super performMiniaturize:sender];
|
||||||
|
}
|
||||||
|
|
||||||
- (BOOL)canBecomeKeyWindow
|
- (BOOL)canBecomeKeyWindow
|
||||||
{
|
{
|
||||||
@@ -1994,8 +2004,10 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
||||||
bytesPerRow:src->width * 4
|
bytesPerRow:src->width * 4
|
||||||
bitsPerPixel:32];
|
bitsPerPixel:32];
|
||||||
if (rep == nil)
|
if (rep == nil) {
|
||||||
|
[native release];
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
||||||
[native addRepresentation:rep];
|
[native addRepresentation:rep];
|
||||||
@@ -2060,18 +2072,38 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
|||||||
bool made_fullscreen = true;
|
bool made_fullscreen = true;
|
||||||
bool traditional = !(flags & 1);
|
bool traditional = !(flags & 1);
|
||||||
NSWindowStyleMask sm = [window styleMask];
|
NSWindowStyleMask sm = [window styleMask];
|
||||||
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
|
||||||
if (traditional) {
|
if (traditional) {
|
||||||
if (!(in_fullscreen)) {
|
if (@available(macOS 10.16, *)) {
|
||||||
sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen;
|
// As of Big Turd NSWindowStyleMaskFullScreen is no longer useable
|
||||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
|
if (!w->ns.in_traditional_fullscreen) {
|
||||||
|
w->ns.pre_full_screen_style_mask = sm;
|
||||||
|
[window setStyleMask: NSWindowStyleMaskBorderless];
|
||||||
|
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
|
||||||
|
[window setFrame:[window.screen frame] display:YES];
|
||||||
|
w->ns.in_traditional_fullscreen = true;
|
||||||
|
} else {
|
||||||
|
made_fullscreen = false;
|
||||||
|
[window setStyleMask: w->ns.pre_full_screen_style_mask];
|
||||||
|
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
|
||||||
|
w->ns.in_traditional_fullscreen = false;
|
||||||
|
}
|
||||||
|
// for some reason despite calling this selector, the window doesnt actually get keyboard focus till you click on it.
|
||||||
|
// presumably a bug with NSWindowStyleMaskBorderless windows
|
||||||
|
[window performSelector:@selector(makeKeyAndOrderFront:) withObject:nil afterDelay:0];
|
||||||
} else {
|
} else {
|
||||||
made_fullscreen = false;
|
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
||||||
sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen);
|
if (!(in_fullscreen)) {
|
||||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
|
sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen;
|
||||||
|
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
|
||||||
|
} else {
|
||||||
|
made_fullscreen = false;
|
||||||
|
sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen);
|
||||||
|
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
|
||||||
|
}
|
||||||
|
[window setStyleMask: sm];
|
||||||
}
|
}
|
||||||
[window setStyleMask: sm];
|
|
||||||
} else {
|
} else {
|
||||||
|
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
||||||
if (in_fullscreen) made_fullscreen = false;
|
if (in_fullscreen) made_fullscreen = false;
|
||||||
[window toggleFullScreen: nil];
|
[window toggleFullScreen: nil];
|
||||||
}
|
}
|
||||||
@@ -2271,6 +2303,29 @@ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
|
|||||||
return window->ns.object;
|
return window->ns.object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLFWAPI void glfwHideCocoaTitlebar(GLFWwindow* handle, bool yes) {
|
||||||
|
@autoreleasepool {
|
||||||
|
_GLFWwindow* w = (_GLFWwindow*) handle;
|
||||||
|
NSWindow *window = w->ns.object;
|
||||||
|
w->ns.titlebar_hidden = yes;
|
||||||
|
NSButton *button;
|
||||||
|
button = [window standardWindowButton: NSWindowCloseButton];
|
||||||
|
if (button) button.hidden = yes;
|
||||||
|
button = [window standardWindowButton: NSWindowMiniaturizeButton];
|
||||||
|
if (button) button.hidden = yes;
|
||||||
|
button = [window standardWindowButton: NSWindowZoomButton];
|
||||||
|
[window setTitlebarAppearsTransparent:yes];
|
||||||
|
if (button) button.hidden = yes;
|
||||||
|
if (yes) {
|
||||||
|
[window setTitleVisibility:NSWindowTitleHidden];
|
||||||
|
[window setStyleMask: [window styleMask] | NSWindowStyleMaskFullSizeContentView];
|
||||||
|
} else {
|
||||||
|
[window setTitleVisibility:NSWindowTitleVisible];
|
||||||
|
[window setStyleMask: [window styleMask] & ~NSWindowStyleMaskFullSizeContentView];
|
||||||
|
}
|
||||||
|
} // autoreleasepool
|
||||||
|
}
|
||||||
|
|
||||||
GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *handle, GLFWcocoatextinputfilterfun callback) {
|
GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *handle, GLFWcocoatextinputfilterfun callback) {
|
||||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||||
@@ -2446,7 +2501,7 @@ START_ALLOW_CASE_RANGE
|
|||||||
END_ALLOW_CASE_RANGE
|
END_ALLOW_CASE_RANGE
|
||||||
}
|
}
|
||||||
if (utf_16_key != 0) {
|
if (utf_16_key != 0) {
|
||||||
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
|
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
|
||||||
} else {
|
} else {
|
||||||
unsigned str_pos = 0;
|
unsigned str_pos = 0;
|
||||||
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {
|
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ def generate_wrappers(glfw_header: str) -> None:
|
|||||||
functions.append(Function(decl))
|
functions.append(Function(decl))
|
||||||
for line in '''\
|
for line in '''\
|
||||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
void* glfwGetCocoaWindow(GLFWwindow* window)
|
||||||
|
void glfwHideCocoaTitlebar(GLFWwindow* window, bool yes)
|
||||||
void* glfwGetNSGLContext(GLFWwindow *window)
|
void* glfwGetNSGLContext(GLFWwindow *window)
|
||||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||||
|
|||||||
3
glfw/memfd.h
vendored
3
glfw/memfd.h
vendored
@@ -39,8 +39,7 @@ static inline int memfd_create(const char *name, unsigned int flags) {
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
static inline int
|
static inline int createTmpfileCloexec(char* tmpname)
|
||||||
createTmpfileCloexec(char* tmpname)
|
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
|
|||||||
24
glfw/wl_init.c
vendored
24
glfw/wl_init.c
vendored
@@ -445,19 +445,17 @@ static void keyboardHandleKey(void* data UNUSED,
|
|||||||
|
|
||||||
_glfw.wl.serial = serial;
|
_glfw.wl.serial = serial;
|
||||||
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
||||||
bool repeatable = false;
|
|
||||||
_glfw.wl.keyRepeatInfo.key = 0;
|
|
||||||
|
|
||||||
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
||||||
{
|
{
|
||||||
_glfw.wl.keyRepeatInfo.key = key;
|
_glfw.wl.keyRepeatInfo.key = key;
|
||||||
repeatable = true;
|
|
||||||
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
||||||
}
|
|
||||||
if (repeatable) {
|
|
||||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, _glfw.wl.keyboardRepeatDelay);
|
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, _glfw.wl.keyboardRepeatDelay);
|
||||||
|
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
|
||||||
|
} else if (action == GLFW_RELEASE && key == _glfw.wl.keyRepeatInfo.key) {
|
||||||
|
_glfw.wl.keyRepeatInfo.key = 0;
|
||||||
|
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 0);
|
||||||
}
|
}
|
||||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void keyboardHandleModifiers(void* data UNUSED,
|
static void keyboardHandleModifiers(void* data UNUSED,
|
||||||
@@ -717,6 +715,9 @@ glfwWaylandCheckForServerSideDecorations(void) {
|
|||||||
|
|
||||||
int _glfwPlatformInit(void)
|
int _glfwPlatformInit(void)
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
|
_GLFWmonitor* monitor;
|
||||||
|
|
||||||
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
|
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
|
||||||
if (!_glfw.wl.cursor.handle)
|
if (!_glfw.wl.cursor.handle)
|
||||||
{
|
{
|
||||||
@@ -768,6 +769,17 @@ int _glfwPlatformInit(void)
|
|||||||
// Sync so we got all initial output events
|
// Sync so we got all initial output events
|
||||||
wl_display_roundtrip(_glfw.wl.display);
|
wl_display_roundtrip(_glfw.wl.display);
|
||||||
|
|
||||||
|
for (i = 0; i < _glfw.monitorCount; ++i)
|
||||||
|
{
|
||||||
|
monitor = _glfw.monitors[i];
|
||||||
|
if (monitor->widthMM <= 0 || monitor->heightMM <= 0)
|
||||||
|
{
|
||||||
|
// If Wayland does not provide a physical size, assume the default 96 DPI
|
||||||
|
monitor->widthMM = (int) (monitor->modes[monitor->wl.currentMode].width * 25.4f / 96.f);
|
||||||
|
monitor->heightMM = (int) (monitor->modes[monitor->wl.currentMode].height * 25.4f / 96.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!_glfw.wl.wmBase)
|
if (!_glfw.wl.wmBase)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
|||||||
8
glfw/wl_platform.h
vendored
8
glfw/wl_platform.h
vendored
@@ -204,14 +204,14 @@ typedef struct _GLFWlibraryWayland
|
|||||||
struct wl_seat* seat;
|
struct wl_seat* seat;
|
||||||
struct wl_pointer* pointer;
|
struct wl_pointer* pointer;
|
||||||
struct wl_keyboard* keyboard;
|
struct wl_keyboard* keyboard;
|
||||||
|
struct wl_data_device_manager* dataDeviceManager;
|
||||||
|
struct wl_data_device* dataDevice;
|
||||||
struct xdg_wm_base* wmBase;
|
struct xdg_wm_base* wmBase;
|
||||||
struct zxdg_decoration_manager_v1* decorationManager;
|
struct zxdg_decoration_manager_v1* decorationManager;
|
||||||
struct wp_viewporter* viewporter;
|
struct wp_viewporter* viewporter;
|
||||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||||
struct wl_data_device_manager* dataDeviceManager;
|
|
||||||
struct wl_data_device* dataDevice;
|
|
||||||
struct wl_data_source* dataSourceForClipboard;
|
struct wl_data_source* dataSourceForClipboard;
|
||||||
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||||
@@ -226,6 +226,7 @@ typedef struct _GLFWlibraryWayland
|
|||||||
|
|
||||||
int32_t keyboardRepeatRate;
|
int32_t keyboardRepeatRate;
|
||||||
monotonic_t keyboardRepeatDelay;
|
monotonic_t keyboardRepeatDelay;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
id_type keyRepeatTimer;
|
id_type keyRepeatTimer;
|
||||||
@@ -275,7 +276,8 @@ typedef struct _GLFWmonitorWayland
|
|||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
int scale;
|
int scale;
|
||||||
} _GLFWmonitorWayland;
|
|
||||||
|
} _GLFWmonitorWayland;
|
||||||
|
|
||||||
// Wayland-specific per-cursor data
|
// Wayland-specific per-cursor data
|
||||||
//
|
//
|
||||||
|
|||||||
23
glfw/wl_window.c
vendored
23
glfw/wl_window.c
vendored
@@ -42,8 +42,7 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void setCursorImage(_GLFWwindow* window)
|
||||||
setCursorImage(_GLFWwindow* window)
|
|
||||||
{
|
{
|
||||||
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
|
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
|
||||||
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
|
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
|
||||||
@@ -247,8 +246,7 @@ static void dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, in
|
|||||||
* is set to ENOSPC. If posix_fallocate() is not supported, program may
|
* is set to ENOSPC. If posix_fallocate() is not supported, program may
|
||||||
* receive SIGBUS on accessing mmap()'ed file contents instead.
|
* receive SIGBUS on accessing mmap()'ed file contents instead.
|
||||||
*/
|
*/
|
||||||
static int
|
static int createAnonymousFile(off_t size)
|
||||||
createAnonymousFile(off_t size)
|
|
||||||
{
|
{
|
||||||
int ret, fd = -1, shm_anon = 0;
|
int ret, fd = -1, shm_anon = 0;
|
||||||
#ifdef HAS_MEMFD_CREATE
|
#ifdef HAS_MEMFD_CREATE
|
||||||
@@ -415,10 +413,10 @@ static void createDecorations(_GLFWwindow* window)
|
|||||||
|
|
||||||
static void destroyDecoration(_GLFWdecorationWayland* decoration)
|
static void destroyDecoration(_GLFWdecorationWayland* decoration)
|
||||||
{
|
{
|
||||||
if (decoration->surface)
|
|
||||||
wl_surface_destroy(decoration->surface);
|
|
||||||
if (decoration->subsurface)
|
if (decoration->subsurface)
|
||||||
wl_subsurface_destroy(decoration->subsurface);
|
wl_subsurface_destroy(decoration->subsurface);
|
||||||
|
if (decoration->surface)
|
||||||
|
wl_surface_destroy(decoration->surface);
|
||||||
if (decoration->viewport)
|
if (decoration->viewport)
|
||||||
wp_viewport_destroy(decoration->viewport);
|
wp_viewport_destroy(decoration->viewport);
|
||||||
decoration->surface = NULL;
|
decoration->surface = NULL;
|
||||||
@@ -439,7 +437,9 @@ static void xdgDecorationHandleConfigure(void* data,
|
|||||||
uint32_t mode)
|
uint32_t mode)
|
||||||
{
|
{
|
||||||
_GLFWwindow* window = data;
|
_GLFWwindow* window = data;
|
||||||
|
|
||||||
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||||
|
|
||||||
if (!window->wl.decorations.serverSide)
|
if (!window->wl.decorations.serverSide)
|
||||||
createDecorations(window);
|
createDecorations(window);
|
||||||
}
|
}
|
||||||
@@ -547,8 +547,7 @@ static bool createSurface(_GLFWwindow* window,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
|
||||||
setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
|
|
||||||
{
|
{
|
||||||
if (window->wl.xdg.toplevel)
|
if (window->wl.xdg.toplevel)
|
||||||
{
|
{
|
||||||
@@ -739,8 +738,7 @@ static bool createXdgSurface(_GLFWwindow* window)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void incrementCursorImage(_GLFWwindow* window)
|
||||||
incrementCursorImage(_GLFWwindow* window)
|
|
||||||
{
|
{
|
||||||
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
|
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
|
||||||
_GLFWcursor* cursor = window->wl.currentCursor;
|
_GLFWcursor* cursor = window->wl.currentCursor;
|
||||||
@@ -788,8 +786,7 @@ wayland_read_events(int poll_result, int events, void *data UNUSED) {
|
|||||||
else wl_display_cancel_read(_glfw.wl.display);
|
else wl_display_cancel_read(_glfw.wl.display);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void handleEvents(monotonic_t timeout)
|
||||||
handleEvents(monotonic_t timeout)
|
|
||||||
{
|
{
|
||||||
struct wl_display* display = _glfw.wl.display;
|
struct wl_display* display = _glfw.wl.display;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
@@ -958,7 +955,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
|||||||
window->context.destroy(window);
|
window->context.destroy(window);
|
||||||
|
|
||||||
destroyDecorations(window);
|
destroyDecorations(window);
|
||||||
|
|
||||||
if (window->wl.xdg.decoration)
|
if (window->wl.xdg.decoration)
|
||||||
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
|
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
|
||||||
|
|
||||||
@@ -1362,6 +1358,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
cursor->wl.buffer = createShmBuffer(image);
|
cursor->wl.buffer = createShmBuffer(image);
|
||||||
if (!cursor->wl.buffer)
|
if (!cursor->wl.buffer)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
cursor->wl.width = image->width;
|
cursor->wl.width = image->width;
|
||||||
cursor->wl.height = image->height;
|
cursor->wl.height = image->height;
|
||||||
cursor->wl.xhot = xhot;
|
cursor->wl.xhot = xhot;
|
||||||
|
|||||||
6
glfw/x11_window.c
vendored
6
glfw/x11_window.c
vendored
@@ -423,14 +423,16 @@ static char* convertLatin1toUTF8(const char* source)
|
|||||||
const char* sp;
|
const char* sp;
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
for (sp = source; *sp; sp++) size += (*sp & 0x80) ? 2 : 1;
|
for (sp = source; *sp; sp++)
|
||||||
|
size += (*sp & 0x80) ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* target = calloc(size, 1);
|
char* target = calloc(size, 1);
|
||||||
char* tp = target;
|
char* tp = target;
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
for (sp = source; *sp; sp++) tp += encodeUTF8(tp, *sp);
|
for (sp = source; *sp; sp++)
|
||||||
|
tp += encodeUTF8(tp, *sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
|
|||||||
0
kittens/broadcast/__init__.py
Normal file
0
kittens/broadcast/__init__.py
Normal file
114
kittens/broadcast/main.py
Normal file
114
kittens/broadcast/main.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from base64 import standard_b64encode
|
||||||
|
from gettext import gettext as _
|
||||||
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from kitty.cli import parse_args
|
||||||
|
from kitty.cli_stub import BroadcastCLIOptions
|
||||||
|
from kitty.key_encoding import RELEASE, encode_key_event, key_defs as K
|
||||||
|
from kitty.rc.base import MATCH_TAB_OPTION, MATCH_WINDOW_OPTION
|
||||||
|
from kitty.remote_control import create_basic_command, encode_send
|
||||||
|
from kitty.typing import KeyEventType, ScreenSize
|
||||||
|
|
||||||
|
from ..tui.handler import Handler
|
||||||
|
from ..tui.line_edit import LineEdit
|
||||||
|
from ..tui.loop import Loop
|
||||||
|
from ..tui.operations import RESTORE_CURSOR, SAVE_CURSOR, styled
|
||||||
|
|
||||||
|
|
||||||
|
class Broadcast(Handler):
|
||||||
|
|
||||||
|
def __init__(self, opts: BroadcastCLIOptions, initial_strings: List[str]) -> None:
|
||||||
|
self.opts = opts
|
||||||
|
self.initial_strings = initial_strings
|
||||||
|
self.payload = {'exclude_active': True, 'data': '', 'match': opts.match_tab, 'match_tab': opts.match_tab}
|
||||||
|
self.line_edit = LineEdit()
|
||||||
|
if not opts.match and not opts.match_tab:
|
||||||
|
self.payload['all'] = True
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
|
self.print('Type the text to broadcast below, press', styled('Ctrl+c', fg='yellow'), 'to quit:')
|
||||||
|
for x in self.initial_strings:
|
||||||
|
self.write_broadcast_text(x)
|
||||||
|
self.write(SAVE_CURSOR)
|
||||||
|
|
||||||
|
def commit_line(self) -> None:
|
||||||
|
self.write(RESTORE_CURSOR + SAVE_CURSOR)
|
||||||
|
self.cmd.clear_to_end_of_screen()
|
||||||
|
self.line_edit.write(self.write, screen_cols=self.screen_size.cols)
|
||||||
|
|
||||||
|
def on_resize(self, screen_size: ScreenSize) -> None:
|
||||||
|
super().on_resize(screen_size)
|
||||||
|
self.commit_line()
|
||||||
|
|
||||||
|
def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:
|
||||||
|
self.write_broadcast_text(text)
|
||||||
|
self.line_edit.on_text(text, in_bracketed_paste)
|
||||||
|
self.commit_line()
|
||||||
|
|
||||||
|
def on_interrupt(self) -> None:
|
||||||
|
self.quit_loop(0)
|
||||||
|
|
||||||
|
def on_eot(self) -> None:
|
||||||
|
self.write_broadcast_text('\x04')
|
||||||
|
|
||||||
|
def on_key(self, key_event: KeyEventType) -> None:
|
||||||
|
if self.line_edit.on_key(key_event):
|
||||||
|
self.commit_line()
|
||||||
|
if key_event.type is not RELEASE and not key_event.mods:
|
||||||
|
if key_event.key is K['ENTER']:
|
||||||
|
self.write_broadcast_text('\r')
|
||||||
|
self.print('')
|
||||||
|
self.line_edit.clear()
|
||||||
|
self.write(SAVE_CURSOR)
|
||||||
|
return
|
||||||
|
|
||||||
|
ek = encode_key_event(key_event)
|
||||||
|
self.write_broadcast_data('kitty-key:' + ek)
|
||||||
|
|
||||||
|
def write_broadcast_text(self, text: str) -> None:
|
||||||
|
self.write_broadcast_data('base64:' + standard_b64encode(text.encode('utf-8')).decode('ascii'))
|
||||||
|
|
||||||
|
def write_broadcast_data(self, data: str) -> None:
|
||||||
|
payload = self.payload.copy()
|
||||||
|
payload['data'] = data
|
||||||
|
send = create_basic_command('send-text', payload, no_response=True)
|
||||||
|
self.write(encode_send(send))
|
||||||
|
|
||||||
|
|
||||||
|
OPTIONS = (MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t')).format
|
||||||
|
help_text = 'Broadcast typed text to all kitty windows. By default text is sent to all windows, unless one of the matching options is specified'
|
||||||
|
usage = '[initial text to send ...]'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_broadcast_args(args: List[str]) -> Tuple[BroadcastCLIOptions, List[str]]:
|
||||||
|
return parse_args(args, OPTIONS, usage, help_text, 'kitty +kitten broadcast', result_class=BroadcastCLIOptions)
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: List[str]) -> Optional[Dict[str, Any]]:
|
||||||
|
try:
|
||||||
|
opts, items = parse_broadcast_args(args[1:])
|
||||||
|
except SystemExit as e:
|
||||||
|
if e.code != 0:
|
||||||
|
print(e.args[0], file=sys.stderr)
|
||||||
|
input(_('Press Enter to quit'))
|
||||||
|
return None
|
||||||
|
|
||||||
|
print('Type text to be broadcast below, Ctrl-C to quit:', end='\r\n')
|
||||||
|
sys.stdout.flush()
|
||||||
|
loop = Loop()
|
||||||
|
handler = Broadcast(opts, items)
|
||||||
|
loop.loop(handler)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main(sys.argv)
|
||||||
|
elif __name__ == '__doc__':
|
||||||
|
cd = sys.cli_docs # type: ignore
|
||||||
|
cd['usage'] = usage
|
||||||
|
cd['options'] = OPTIONS
|
||||||
|
cd['help_text'] = help_text
|
||||||
@@ -7,7 +7,7 @@ import re
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from mimetypes import guess_type
|
from kitty.guess_mime_type import guess_type
|
||||||
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
|
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -68,7 +68,10 @@ class Collection:
|
|||||||
self.removed_count += len(lines_for_path(left_path))
|
self.removed_count += len(lines_for_path(left_path))
|
||||||
|
|
||||||
def finalize(self) -> None:
|
def finalize(self) -> None:
|
||||||
self.all_paths.sort(key=path_name_map.get)
|
def key(x: str) -> str:
|
||||||
|
return path_name_map.get(x, '')
|
||||||
|
|
||||||
|
self.all_paths.sort(key=key)
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]:
|
def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]:
|
||||||
for path in self.all_paths:
|
for path in self.all_paths:
|
||||||
@@ -133,7 +136,7 @@ def sanitize(text: str) -> str:
|
|||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
def mime_type_for_path(path: str) -> str:
|
def mime_type_for_path(path: str) -> str:
|
||||||
return guess_type(path)[0] or 'application/octet-stream'
|
return guess_type(path) or 'application/octet-stream'
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ from collections import defaultdict
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from typing import DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
|
from typing import (
|
||||||
|
Any, DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
)
|
||||||
|
|
||||||
from kitty.cli import CONFIG_HELP, parse_args
|
from kitty.cli import CONFIG_HELP, parse_args
|
||||||
from kitty.cli_stub import DiffCLIOptions
|
from kitty.cli_stub import DiffCLIOptions
|
||||||
@@ -24,6 +26,11 @@ from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
|
|||||||
from kitty.options_stub import DiffOptions
|
from kitty.options_stub import DiffOptions
|
||||||
from kitty.utils import ScreenSize
|
from kitty.utils import ScreenSize
|
||||||
|
|
||||||
|
from ..tui.handler import Handler
|
||||||
|
from ..tui.images import ImageManager, Placement
|
||||||
|
from ..tui.line_edit import LineEdit
|
||||||
|
from ..tui.loop import Loop
|
||||||
|
from ..tui.operations import styled
|
||||||
from . import global_data
|
from . import global_data
|
||||||
from .collect import (
|
from .collect import (
|
||||||
Collection, create_collection, data_for_path, lines_for_path, sanitize,
|
Collection, create_collection, data_for_path, lines_for_path, sanitize,
|
||||||
@@ -35,14 +42,11 @@ from .render import (
|
|||||||
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
||||||
)
|
)
|
||||||
from .search import BadRegex, Search
|
from .search import BadRegex, Search
|
||||||
from ..tui.handler import Handler
|
|
||||||
from ..tui.images import ImageManager, Placement
|
|
||||||
from ..tui.line_edit import LineEdit
|
|
||||||
from ..tui.loop import Loop
|
|
||||||
from ..tui.operations import styled
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .highlight import initialize_highlighter, highlight_collection, DiffHighlight
|
from .highlight import (
|
||||||
|
DiffHighlight, highlight_collection, initialize_highlighter
|
||||||
|
)
|
||||||
has_highlighter = True
|
has_highlighter = True
|
||||||
DiffHighlight
|
DiffHighlight
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -334,8 +338,8 @@ class DiffHandler(Handler):
|
|||||||
if candidate.image.image_id == image_id:
|
if candidate.image.image_id == image_id:
|
||||||
q = self.xpos_for_image(row, candidate, is_left)
|
q = self.xpos_for_image(row, candidate, is_left)
|
||||||
if q is not None:
|
if q is not None:
|
||||||
pl['x'] = q[0]
|
pl.x = q[0]
|
||||||
pl['y'] = row
|
pl.y = row
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -566,8 +570,8 @@ class ShowWarning:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.warnings: List[str] = []
|
self.warnings: List[str] = []
|
||||||
|
|
||||||
def __call__(self, message: str, category: object, filename: str, lineno: int, file: object = None, line: object = None) -> None:
|
def __call__(self, message: Any, category: Any, filename: str, lineno: int, file: object = None, line: object = None) -> None:
|
||||||
if category is ImageSupportWarning:
|
if category is ImageSupportWarning and isinstance(message, str):
|
||||||
showwarning.warnings.append(message)
|
showwarning.warnings.append(message)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ class Hints(Handler):
|
|||||||
self.all_marks = all_marks
|
self.all_marks = all_marks
|
||||||
self.ignore_mark_indices: Set[int] = set()
|
self.ignore_mark_indices: Set[int] = set()
|
||||||
self.args = args
|
self.args = args
|
||||||
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
|
self.window_title = args.window_title or (_('Choose URL') if args.type == 'url' else _('Choose text'))
|
||||||
self.multiple = args.multiple
|
self.multiple = args.multiple
|
||||||
self.match_suffix = self.get_match_suffix(args)
|
self.match_suffix = self.get_match_suffix(args)
|
||||||
self.chosen: List[Mark] = []
|
self.chosen: List[Mark] = []
|
||||||
@@ -519,7 +519,7 @@ The type of text to search for. A value of :code:`linenum` is special, it looks
|
|||||||
for error messages using the pattern specified with :option:`--regex`, which
|
for error messages using the pattern specified with :option:`--regex`, which
|
||||||
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
||||||
will look for :code:`path:line`. The :option:`--linenum-action` option
|
will look for :code:`path:line`. The :option:`--linenum-action` option
|
||||||
controls what to do with the selected error message, other options are ignored.
|
controls where to display the selected error message, other options are ignored.
|
||||||
|
|
||||||
|
|
||||||
--regex
|
--regex
|
||||||
@@ -539,13 +539,13 @@ the form key=value.
|
|||||||
default=self
|
default=self
|
||||||
type=choice
|
type=choice
|
||||||
choices=self,window,tab,os_window,background
|
choices=self,window,tab,os_window,background
|
||||||
|
Where to perform the action on matched errors. :code:`self` means the current
|
||||||
|
window, :code:`window` a new kitty window, :code:`tab` a new tab,
|
||||||
|
:code:`os_window` a new OS window and :code:`background` run in the background.
|
||||||
The action to perform on the matched errors. The actual action is whatever
|
The action to perform on the matched errors. The actual action is whatever
|
||||||
arguments are provided to the kitten, for example:
|
arguments are provided to the kitten, for example: :code:`kitty + kitten hints
|
||||||
:code:`kitty + kitten hints --type=linenum vim +{line} {path}`
|
--type=linenum --linenum-action=tab vim +{line} {path}` will open the matched
|
||||||
will open the matched path at the matched line number in vim. This option
|
path at the matched line number in vim in a new kitty tab.
|
||||||
controls where the action is executed: :code:`self` means the current window,
|
|
||||||
:code:`window` a new kitty window, :code:`tab` a new tab, :code:`os_window`
|
|
||||||
a new OS window and :code:`background` run in the background.
|
|
||||||
|
|
||||||
|
|
||||||
--url-prefixes
|
--url-prefixes
|
||||||
@@ -634,6 +634,9 @@ on selected matches. See https://sw.kovidgoyal.net/kitty/kittens/hints.html
|
|||||||
for details. You can also specify absolute paths to load the script from elsewhere.
|
for details. You can also specify absolute paths to load the script from elsewhere.
|
||||||
|
|
||||||
|
|
||||||
|
--window-title
|
||||||
|
The window title for the hints window, default title is selected based on
|
||||||
|
the type of text being hinted.
|
||||||
'''.format(
|
'''.format(
|
||||||
default_regex=DEFAULT_REGEX,
|
default_regex=DEFAULT_REGEX,
|
||||||
line='{{line}}', path='{{path}}'
|
line='{{line}}', path='{{path}}'
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import mimetypes
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
@@ -18,6 +17,7 @@ from typing import (
|
|||||||
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
|
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from kitty.guess_mime_type import guess_type
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import IcatCLIOptions
|
from kitty.cli_stub import IcatCLIOptions
|
||||||
from kitty.constants import appname
|
from kitty.constants import appname
|
||||||
@@ -273,7 +273,7 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
|
|||||||
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
|
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
|
||||||
for dirpath, dirnames, filenames in os.walk(d):
|
for dirpath, dirnames, filenames in os.walk(d):
|
||||||
for f in filenames:
|
for f in filenames:
|
||||||
mt = mimetypes.guess_type(f)[0]
|
mt = guess_type(f)
|
||||||
if mt and mt.startswith('image/'):
|
if mt and mt.startswith('image/'):
|
||||||
yield os.path.join(dirpath, f), mt
|
yield os.path.join(dirpath, f), mt
|
||||||
|
|
||||||
|
|||||||
0
kittens/query_terminal/__init__.py
Normal file
0
kittens/query_terminal/__init__.py
Normal file
162
kittens/query_terminal/main.py
Normal file
162
kittens/query_terminal/main.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from binascii import unhexlify, hexlify
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Dict, Iterable, List, Type
|
||||||
|
|
||||||
|
from kitty.cli import parse_args
|
||||||
|
from kitty.cli_stub import QueryTerminalCLIOptions
|
||||||
|
from kitty.constants import appname
|
||||||
|
from kitty.utils import TTYIO
|
||||||
|
from kitty.terminfo import names
|
||||||
|
|
||||||
|
|
||||||
|
class Query:
|
||||||
|
name: str = ''
|
||||||
|
ans: str = ''
|
||||||
|
query_name: str = ''
|
||||||
|
help_text: str = ''
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.encoded_query_name = hexlify(self.query_name.encode('utf-8')).decode('ascii')
|
||||||
|
self.pat = re.compile('\x1bP([01])\\+r{}(.*?)\x1b\\\\'.format(self.encoded_query_name).encode('ascii'))
|
||||||
|
|
||||||
|
def query_code(self) -> str:
|
||||||
|
return "\x1bP+q{}\x1b\\".format(self.encoded_query_name)
|
||||||
|
|
||||||
|
def decode_response(self, res: bytes) -> str:
|
||||||
|
return unhexlify(res).decode('utf-8')
|
||||||
|
|
||||||
|
def more_needed(self, buffer: bytes) -> bool:
|
||||||
|
m = self.pat.search(buffer)
|
||||||
|
if m is None:
|
||||||
|
return True
|
||||||
|
if m.group(1) == b'1':
|
||||||
|
q = m.group(2)
|
||||||
|
if q.startswith(b'='):
|
||||||
|
with suppress(Exception):
|
||||||
|
self.ans = self.decode_response(memoryview(q)[1:])
|
||||||
|
return False
|
||||||
|
|
||||||
|
def output_line(self) -> str:
|
||||||
|
return self.ans
|
||||||
|
|
||||||
|
|
||||||
|
all_queries: Dict[str, Type[Query]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def query(cls: Type[Query]) -> Type[Query]:
|
||||||
|
all_queries[cls.name] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
@query
|
||||||
|
class TerminalName(Query):
|
||||||
|
name: str = 'name'
|
||||||
|
query_name: str = 'TN'
|
||||||
|
help_text: str = f'Terminal name ({names[0]})'
|
||||||
|
|
||||||
|
|
||||||
|
@query
|
||||||
|
class TerminalVersion(Query):
|
||||||
|
name: str = 'version'
|
||||||
|
query_name: str = 'kitty-query-version'
|
||||||
|
help_text: str = 'Terminal version, for e.g.: 0.19.2'
|
||||||
|
|
||||||
|
|
||||||
|
@query
|
||||||
|
class AllowHyperlinks(Query):
|
||||||
|
name: str = 'allow_hyperlinks'
|
||||||
|
query_name: str = 'kitty-query-allow_hyperlinks'
|
||||||
|
help_text: str = 'yes, no or ask'
|
||||||
|
|
||||||
|
|
||||||
|
def do_queries(queries: Iterable, cli_opts: QueryTerminalCLIOptions) -> Dict[str, str]:
|
||||||
|
actions = tuple(all_queries[x]() for x in queries)
|
||||||
|
qstring = ''.join(a.query_code() for a in actions)
|
||||||
|
received = b''
|
||||||
|
|
||||||
|
def more_needed(data: bytes) -> bool:
|
||||||
|
nonlocal received
|
||||||
|
received += data
|
||||||
|
for a in actions:
|
||||||
|
if a.more_needed(received):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
with TTYIO() as ttyio:
|
||||||
|
ttyio.send(qstring)
|
||||||
|
ttyio.recv(more_needed, timeout=cli_opts.wait_for)
|
||||||
|
|
||||||
|
return {a.name: a.output_line() for a in actions}
|
||||||
|
|
||||||
|
|
||||||
|
def options_spec() -> str:
|
||||||
|
return '''\
|
||||||
|
--wait-for
|
||||||
|
type=float
|
||||||
|
default=10
|
||||||
|
The amount of time (in seconds) to wait for a response from the terminal, after
|
||||||
|
querying it.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
help_text = '''\
|
||||||
|
Query the terminal this kitten is run in for various
|
||||||
|
capabilities. This sends escape codes to the terminal
|
||||||
|
and based on its response prints out data about supported
|
||||||
|
capabilities. Note that this is a blocking operation, since
|
||||||
|
it has to wait for a response from the terminal. You can control
|
||||||
|
the maximum wait time via the ``--wait-for`` option.
|
||||||
|
|
||||||
|
The output is lines of the form::
|
||||||
|
|
||||||
|
query: data
|
||||||
|
|
||||||
|
If a particular query is unsupported by the running kitty version,
|
||||||
|
the data will be blank.
|
||||||
|
|
||||||
|
Note that when calling this from another program, be very
|
||||||
|
careful not to perform any I/O on the terminal device
|
||||||
|
until the kitten exits.
|
||||||
|
|
||||||
|
Available queries are::
|
||||||
|
|
||||||
|
{}
|
||||||
|
'''.format(' ' + '\n '.join(
|
||||||
|
f'{name}: {c.help_text}' for name, c in all_queries.items()))
|
||||||
|
usage = '[query1 query2 ...]'
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: List[str] = sys.argv) -> None:
|
||||||
|
cli_opts, items_ = parse_args(
|
||||||
|
args[1:],
|
||||||
|
options_spec,
|
||||||
|
usage,
|
||||||
|
help_text,
|
||||||
|
'{} +kitten query_terminal'.format(appname),
|
||||||
|
result_class=QueryTerminalCLIOptions
|
||||||
|
)
|
||||||
|
queries: List[str] = list(items_)
|
||||||
|
if 'all' in queries or not queries:
|
||||||
|
queries = sorted(all_queries)
|
||||||
|
else:
|
||||||
|
extra = frozenset(queries) - frozenset(all_queries)
|
||||||
|
if extra:
|
||||||
|
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
|
||||||
|
|
||||||
|
for key, val in do_queries(queries, cli_opts).items():
|
||||||
|
print(key + ':', val)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
elif __name__ == '__doc__':
|
||||||
|
cd = sys.cli_docs # type: ignore
|
||||||
|
cd['usage'] = usage
|
||||||
|
cd['options'] = options_spec
|
||||||
|
cd['help_text'] = help_text
|
||||||
@@ -118,15 +118,14 @@ def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
|
|||||||
if arg in boolean_ssh_args:
|
if arg in boolean_ssh_args:
|
||||||
ssh_args.append(arg)
|
ssh_args.append(arg)
|
||||||
continue
|
continue
|
||||||
if arg.startswith('-p') and arg[2:].isdigit():
|
|
||||||
ssh_args.append(arg)
|
|
||||||
continue
|
|
||||||
if arg in other_ssh_args:
|
if arg in other_ssh_args:
|
||||||
if i != len(all_args) - 1:
|
|
||||||
raise SystemExit('Option {} cannot occur in the middle'.format(arg))
|
|
||||||
ssh_args.append(arg)
|
ssh_args.append(arg)
|
||||||
expecting_option_val = True
|
rest = all_args[i+1:]
|
||||||
continue
|
if rest:
|
||||||
|
ssh_args.append(rest)
|
||||||
|
else:
|
||||||
|
expecting_option_val = True
|
||||||
|
break
|
||||||
raise SystemExit('Unknown option: {}'.format(arg))
|
raise SystemExit('Unknown option: {}'.format(arg))
|
||||||
continue
|
continue
|
||||||
if expecting_option_val:
|
if expecting_option_val:
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ from typing import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from kitty.typing import (
|
from kitty.typing import (
|
||||||
CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType,
|
CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType
|
||||||
TypedDict
|
|
||||||
)
|
)
|
||||||
from kitty.utils import ScreenSize, fit_image
|
from kitty.utils import ScreenSize, fit_image
|
||||||
|
|
||||||
@@ -136,6 +135,7 @@ class GraphicsCommand:
|
|||||||
S: int = 0 # size of data to read from file
|
S: int = 0 # size of data to read from file
|
||||||
O: int = 0 # offset of data to read from file
|
O: int = 0 # offset of data to read from file
|
||||||
i: int = 0 # image id
|
i: int = 0 # image id
|
||||||
|
p: int = 0 # placement id
|
||||||
o: Optional[GRT_o] = None # type of compression
|
o: Optional[GRT_o] = None # type of compression
|
||||||
m: GRT_m = 0 # 0 or 1 whether there is more chunked data
|
m: GRT_m = 0 # 0 or 1 whether there is more chunked data
|
||||||
x: int = 0 # left edge of image area to display
|
x: int = 0 # left edge of image area to display
|
||||||
@@ -173,10 +173,15 @@ class GraphicsCommand:
|
|||||||
setattr(self, k, defval)
|
setattr(self, k, defval)
|
||||||
|
|
||||||
|
|
||||||
class Placement(TypedDict):
|
class Placement:
|
||||||
cmd: GraphicsCommand
|
cmd: GraphicsCommand
|
||||||
x: int
|
x: int = 0
|
||||||
y: int
|
y: int = 0
|
||||||
|
|
||||||
|
def __init__(self, cmd: GraphicsCommand, x: int = 0, y: int = 0):
|
||||||
|
self.cmd = cmd
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
|
||||||
class ImageManager:
|
class ImageManager:
|
||||||
@@ -263,8 +268,8 @@ class ImageManager:
|
|||||||
skey = self.image_id_to_converted_data[image_id]
|
skey = self.image_id_to_converted_data[image_id]
|
||||||
self.transmit_image(image_data, image_id, *skey)
|
self.transmit_image(image_data, image_id, *skey)
|
||||||
with cursor(self.handler.write):
|
with cursor(self.handler.write):
|
||||||
self.handler.cmd.set_cursor_position(pl['x'], pl['y'])
|
self.handler.cmd.set_cursor_position(pl.x, pl.y)
|
||||||
self.handler.cmd.gr_command(pl['cmd'])
|
self.handler.cmd.gr_command(pl.cmd)
|
||||||
|
|
||||||
def send_image(self, path: str, max_cols: Optional[int] = None, max_rows: Optional[int] = None, scale_up: bool = False) -> SentImageKey:
|
def send_image(self, path: str, max_cols: Optional[int] = None, max_rows: Optional[int] = None, scale_up: bool = False) -> SentImageKey:
|
||||||
path = os.path.abspath(path)
|
path = os.path.abspath(path)
|
||||||
@@ -316,7 +321,7 @@ class ImageManager:
|
|||||||
gc.i = image_id
|
gc.i = image_id
|
||||||
if src_rect is not None:
|
if src_rect is not None:
|
||||||
gc.x, gc.y, gc.w, gc.h = map(int, src_rect)
|
gc.x, gc.y, gc.w, gc.h = map(int, src_rect)
|
||||||
self.placements_in_flight[image_id].append({'cmd': gc, 'x': x, 'y': y})
|
self.placements_in_flight[image_id].append(Placement(gc, x, y))
|
||||||
with cursor(self.handler.write):
|
with cursor(self.handler.write):
|
||||||
self.handler.cmd.set_cursor_position(x, y)
|
self.handler.cmd.set_cursor_position(x, y)
|
||||||
self.handler.cmd.gr_command(gc)
|
self.handler.cmd.gr_command(gc)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from typing import Callable, Tuple
|
|||||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||||
from kitty.key_encoding import RELEASE, KeyEvent, key_defs as K
|
from kitty.key_encoding import RELEASE, KeyEvent, key_defs as K
|
||||||
|
|
||||||
|
from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by
|
||||||
|
|
||||||
HOME = K['HOME']
|
HOME = K['HOME']
|
||||||
END = K['END']
|
END = K['END']
|
||||||
BACKSPACE = K['BACKSPACE']
|
BACKSPACE = K['BACKSPACE']
|
||||||
@@ -31,13 +33,28 @@ class LineEdit:
|
|||||||
before, after = self.current_input[:x], self.current_input[x:]
|
before, after = self.current_input[:x], self.current_input[x:]
|
||||||
return before, after
|
return before, after
|
||||||
|
|
||||||
def write(self, write: Callable[[str], None], prompt: str = '') -> None:
|
def write(self, write: Callable[[str], None], prompt: str = '', screen_cols: int = 0) -> None:
|
||||||
if self.pending_bell:
|
if self.pending_bell:
|
||||||
write('\a')
|
write('\a')
|
||||||
self.pending_bell = False
|
self.pending_bell = False
|
||||||
write(prompt)
|
text = prompt + self.current_input
|
||||||
write(self.current_input)
|
cursor_pos = self.cursor_pos + wcswidth(prompt)
|
||||||
write('\r\x1b[{}C'.format(self.cursor_pos + wcswidth(prompt)))
|
if screen_cols:
|
||||||
|
write(SAVE_CURSOR + text + RESTORE_CURSOR)
|
||||||
|
used_lines, last_line_cursor_pos = divmod(cursor_pos, screen_cols)
|
||||||
|
if used_lines == 0:
|
||||||
|
if last_line_cursor_pos:
|
||||||
|
write(move_cursor_by(last_line_cursor_pos, 'right'))
|
||||||
|
else:
|
||||||
|
if used_lines:
|
||||||
|
write(move_cursor_by(used_lines, 'down'))
|
||||||
|
if last_line_cursor_pos:
|
||||||
|
write(move_cursor_by(last_line_cursor_pos, 'right'))
|
||||||
|
else:
|
||||||
|
write(text)
|
||||||
|
write('\r')
|
||||||
|
if cursor_pos:
|
||||||
|
write(move_cursor_by(cursor_pos, 'right'))
|
||||||
|
|
||||||
def add_text(self, text: str) -> None:
|
def add_text(self, text: str) -> None:
|
||||||
if self.current_input:
|
if self.current_input:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import re
|
|||||||
import selectors
|
import selectors
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import termios
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
|
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
|
||||||
@@ -61,8 +62,9 @@ debug = Debug()
|
|||||||
|
|
||||||
class TermManager:
|
class TermManager:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, optional_actions: int = termios.TCSANOW) -> None:
|
||||||
self.extra_finalize: Optional[str] = None
|
self.extra_finalize: Optional[str] = None
|
||||||
|
self.optional_actions = optional_actions
|
||||||
|
|
||||||
def set_state_for_loop(self, set_raw: bool = True) -> None:
|
def set_state_for_loop(self, set_raw: bool = True) -> None:
|
||||||
if set_raw:
|
if set_raw:
|
||||||
@@ -82,7 +84,7 @@ class TermManager:
|
|||||||
self.set_state_for_loop()
|
self.set_state_for_loop()
|
||||||
|
|
||||||
def __enter__(self) -> 'TermManager':
|
def __enter__(self) -> 'TermManager':
|
||||||
self.tty_fd, self.original_termios = open_tty()
|
self.tty_fd, self.original_termios = open_tty(False, self.optional_actions)
|
||||||
self.set_state_for_loop(set_raw=False)
|
self.set_state_for_loop(set_raw=False)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -179,11 +181,15 @@ class SignalManager:
|
|||||||
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM)))
|
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM)))
|
||||||
|
|
||||||
|
|
||||||
|
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'
|
||||||
|
|
||||||
|
|
||||||
class Loop:
|
class Loop:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'
|
sanitize_bracketed_paste: str = sanitize_bracketed_paste,
|
||||||
|
optional_actions: int = termios.TCSADRAIN
|
||||||
):
|
):
|
||||||
if is_macos:
|
if is_macos:
|
||||||
# On macOS PTY devices are not supported by the KqueueSelector and
|
# On macOS PTY devices are not supported by the KqueueSelector and
|
||||||
@@ -193,6 +199,7 @@ class Loop:
|
|||||||
else:
|
else:
|
||||||
self.asycio_loop = asyncio.get_event_loop()
|
self.asycio_loop = asyncio.get_event_loop()
|
||||||
self.return_code = 0
|
self.return_code = 0
|
||||||
|
self.optional_actions = optional_actions
|
||||||
self.read_buf = ''
|
self.read_buf = ''
|
||||||
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||||
try:
|
try:
|
||||||
@@ -295,7 +302,7 @@ class Loop:
|
|||||||
def _on_apc(self, apc: str) -> None:
|
def _on_apc(self, apc: str) -> None:
|
||||||
if apc.startswith('K'):
|
if apc.startswith('K'):
|
||||||
try:
|
try:
|
||||||
k = decode_key_event(apc)
|
k = decode_key_event(apc[1:])
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -317,13 +324,17 @@ class Loop:
|
|||||||
self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:])
|
self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:])
|
||||||
del self.write_buf[self.iov_limit:]
|
del self.write_buf[self.iov_limit:]
|
||||||
sizes = tuple(map(len, self.write_buf))
|
sizes = tuple(map(len, self.write_buf))
|
||||||
try:
|
total_size = sum(sizes)
|
||||||
written = os.writev(fd, self.write_buf)
|
if total_size:
|
||||||
except BlockingIOError:
|
try:
|
||||||
return
|
written = os.writev(fd, self.write_buf)
|
||||||
if not written:
|
except BlockingIOError:
|
||||||
raise EOFError('The output stream is closed')
|
return
|
||||||
if written >= sum(sizes):
|
if not written:
|
||||||
|
raise EOFError('The output stream is closed')
|
||||||
|
else:
|
||||||
|
written = 0
|
||||||
|
if written >= total_size:
|
||||||
self.write_buf: List[bytes] = []
|
self.write_buf: List[bytes] = []
|
||||||
self.asycio_loop.remove_writer(fd)
|
self.asycio_loop.remove_writer(fd)
|
||||||
self.waiting_for_writes = False
|
self.waiting_for_writes = False
|
||||||
@@ -388,7 +399,7 @@ class Loop:
|
|||||||
handler.on_resize(handler.screen_size)
|
handler.on_resize(handler.screen_size)
|
||||||
|
|
||||||
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
|
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
|
||||||
with TermManager() as term_manager, signal_manager:
|
with TermManager(self.optional_actions) as term_manager, signal_manager:
|
||||||
self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
|
self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
|
||||||
image_manager = None
|
image_manager = None
|
||||||
if handler.image_manager_class is not None:
|
if handler.image_manager_class is not None:
|
||||||
|
|||||||
@@ -68,6 +68,11 @@ def clear_screen() -> str:
|
|||||||
return '\033[H\033[2J'
|
return '\033[H\033[2J'
|
||||||
|
|
||||||
|
|
||||||
|
@cmd
|
||||||
|
def clear_to_end_of_screen() -> str:
|
||||||
|
return '\033[J'
|
||||||
|
|
||||||
|
|
||||||
@cmd
|
@cmd
|
||||||
def clear_to_eol() -> str:
|
def clear_to_eol() -> str:
|
||||||
return '\033[K'
|
return '\033[K'
|
||||||
@@ -108,6 +113,12 @@ def set_cursor_position(x: int, y: int) -> str: # (0, 0) is top left
|
|||||||
return '\033[{};{}H'.format(y + 1, x + 1)
|
return '\033[{};{}H'.format(y + 1, x + 1)
|
||||||
|
|
||||||
|
|
||||||
|
@cmd
|
||||||
|
def move_cursor_by(amt: int, direction: str) -> str:
|
||||||
|
suffix = {'up': 'A', 'down': 'B', 'right': 'C', 'left': 'D'}[direction]
|
||||||
|
return f'\033[{amt}{suffix}'
|
||||||
|
|
||||||
|
|
||||||
@cmd
|
@cmd
|
||||||
def set_cursor_shape(shape: str = 'block', blink: bool = True) -> str:
|
def set_cursor_shape(shape: str = 'block', blink: bool = True) -> str:
|
||||||
val = {'block': 1, 'underline': 3, 'bar': 5}.get(shape, 1)
|
val = {'block': 1, 'underline': 3, 'bar': 5}.get(shape, 1)
|
||||||
@@ -253,7 +264,7 @@ def init_state(alternate_screen: bool = True) -> str:
|
|||||||
ans = (
|
ans = (
|
||||||
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
|
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
|
||||||
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
|
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
|
||||||
set_mode('DECARM') + reset_mode('DECOM') + set_mode('DECAWM') +
|
set_mode('DECARM') + set_mode('DECAWM') +
|
||||||
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
|
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
|
||||||
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
|
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
|
||||||
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
|
reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
|
||||||
@@ -263,7 +274,7 @@ def init_state(alternate_screen: bool = True) -> str:
|
|||||||
'\033[*x' # reset DECSACE to default region select
|
'\033[*x' # reset DECSACE to default region select
|
||||||
)
|
)
|
||||||
if alternate_screen:
|
if alternate_screen:
|
||||||
ans += set_mode('ALTERNATE_SCREEN')
|
ans += set_mode('ALTERNATE_SCREEN') + reset_mode('DECOM')
|
||||||
ans += clear_screen()
|
ans += clear_screen()
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Borders:
|
|||||||
self.os_window_id = os_window_id
|
self.os_window_id = os_window_id
|
||||||
self.tab_id = tab_id
|
self.tab_id = tab_id
|
||||||
self.draw_active_borders = opts.active_border_color is not None
|
self.draw_active_borders = opts.active_border_color is not None
|
||||||
self.draw_minimal_borders = opts.draw_minimal_borders
|
self.draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ from contextlib import suppress
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from typing import (
|
from typing import (
|
||||||
Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union
|
Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple, Union,
|
||||||
|
cast
|
||||||
)
|
)
|
||||||
from weakref import WeakValueDictionary
|
from weakref import WeakValueDictionary
|
||||||
|
|
||||||
@@ -363,9 +364,17 @@ class Boss:
|
|||||||
c = command_for_name(cmd)
|
c = command_for_name(cmd)
|
||||||
opts, items = parse_subcommand_cli(c, items)
|
opts, items = parse_subcommand_cli(c, items)
|
||||||
payload = c.message_to_kitty(global_opts, opts, items)
|
payload = c.message_to_kitty(global_opts, opts, items)
|
||||||
c.response_from_kitty(self, self.active_window, PayloadGetter(c, payload if isinstance(payload, dict) else {}))
|
import types
|
||||||
except (Exception, SystemExit) as e:
|
if isinstance(cast(types.GeneratorType, payload), types.GeneratorType):
|
||||||
self.show_error(_('remote_control mapping failed'), str(e))
|
payloads = cast(types.GeneratorType, payload)
|
||||||
|
for x in payloads:
|
||||||
|
c.response_from_kitty(self, self.active_window, PayloadGetter(c, x if isinstance(x, dict) else {}))
|
||||||
|
else:
|
||||||
|
c.response_from_kitty(self, self.active_window, PayloadGetter(c, payload if isinstance(payload, dict) else {}))
|
||||||
|
except (Exception, SystemExit):
|
||||||
|
import traceback
|
||||||
|
tb = traceback.format_exc()
|
||||||
|
self.show_error(_('remote_control mapping failed'), tb)
|
||||||
|
|
||||||
def peer_message_received(self, msg_bytes: bytes) -> Optional[bytes]:
|
def peer_message_received(self, msg_bytes: bytes) -> Optional[bytes]:
|
||||||
cmd_prefix = b'\x1bP@kitty-cmd'
|
cmd_prefix = b'\x1bP@kitty-cmd'
|
||||||
@@ -1459,15 +1468,45 @@ class Boss:
|
|||||||
self._cleanup_tab_after_window_removal(tab)
|
self._cleanup_tab_after_window_removal(tab)
|
||||||
target_tab.make_active()
|
target_tab.make_active()
|
||||||
|
|
||||||
|
def select_tab(self) -> None:
|
||||||
|
title = 'Choose a tab to switch to'
|
||||||
|
lines = [title, '']
|
||||||
|
fmt = ': {1}'
|
||||||
|
tab_id_map: Dict[int, Optional[Union[str, int]]] = {}
|
||||||
|
current_tab = self.active_tab
|
||||||
|
done_tab_id: Optional[Union[str, int]] = None
|
||||||
|
|
||||||
|
for i, tab in enumerate(self.all_tabs):
|
||||||
|
if tab is not current_tab:
|
||||||
|
tab_id_map[len(tab_id_map)] = tab.id
|
||||||
|
lines.append(fmt.format(i + 1, tab.title))
|
||||||
|
|
||||||
|
def done(data: Dict[str, Any], target_window_id: int, self: Boss) -> None:
|
||||||
|
nonlocal done_tab_id
|
||||||
|
done_tab_id = tab_id_map[int(data['groupdicts'][0]['index'])]
|
||||||
|
|
||||||
|
def done2(target_window_id: int, self: Boss) -> None:
|
||||||
|
tab_id = done_tab_id
|
||||||
|
if tab_id is not None:
|
||||||
|
for i, tab in enumerate(self.all_tabs):
|
||||||
|
if tab.id == tab_id:
|
||||||
|
self.set_active_tab(tab)
|
||||||
|
break
|
||||||
|
|
||||||
|
self._run_kitten(
|
||||||
|
'hints', args=(
|
||||||
|
'--ascending', '--customize-processing=::import::kitty.choose_entry',
|
||||||
|
r'--regex=(?m)^:\s+.+$', '--window-title', title,
|
||||||
|
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
|
||||||
|
)
|
||||||
|
|
||||||
def detach_window(self, *args: str) -> None:
|
def detach_window(self, *args: str) -> None:
|
||||||
if not args or args[0] == 'new':
|
if not args or args[0] == 'new':
|
||||||
return self._move_window_to(target_os_window_id='new')
|
return self._move_window_to(target_os_window_id='new')
|
||||||
if args[0] == 'new-tab':
|
if args[0] == 'new-tab':
|
||||||
return self._move_window_to(target_tab_id='new')
|
return self._move_window_to(target_tab_id='new')
|
||||||
lines = [
|
title = 'Choose a tab to move the window to'
|
||||||
'Choose a tab to move the window to',
|
lines = [title, '']
|
||||||
''
|
|
||||||
]
|
|
||||||
fmt = ': {1}'
|
fmt = ': {1}'
|
||||||
tab_id_map: Dict[int, Optional[Union[str, int]]] = {}
|
tab_id_map: Dict[int, Optional[Union[str, int]]] = {}
|
||||||
current_tab = self.active_tab
|
current_tab = self.active_tab
|
||||||
@@ -1503,7 +1542,7 @@ class Boss:
|
|||||||
self._run_kitten(
|
self._run_kitten(
|
||||||
'hints', args=(
|
'hints', args=(
|
||||||
'--ascending', '--customize-processing=::import::kitty.choose_entry',
|
'--ascending', '--customize-processing=::import::kitty.choose_entry',
|
||||||
r'--regex=(?m)^:\s+.+$',
|
r'--regex=(?m)^:\s+.+$', '--window-title', title,
|
||||||
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
|
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1511,10 +1550,8 @@ class Boss:
|
|||||||
if not args or args[0] == 'new':
|
if not args or args[0] == 'new':
|
||||||
return self._move_tab_to()
|
return self._move_tab_to()
|
||||||
|
|
||||||
lines = [
|
title = 'Choose an OS window to move the tab to'
|
||||||
'Choose an OS window to move the tab to',
|
lines = [title, '']
|
||||||
''
|
|
||||||
]
|
|
||||||
fmt = ': {1}'
|
fmt = ': {1}'
|
||||||
os_window_id_map: Dict[int, Optional[int]] = {}
|
os_window_id_map: Dict[int, Optional[int]] = {}
|
||||||
current_os_window = getattr(self.active_tab, 'os_window_id', 0)
|
current_os_window = getattr(self.active_tab, 'os_window_id', 0)
|
||||||
@@ -1551,7 +1588,7 @@ class Boss:
|
|||||||
self._run_kitten(
|
self._run_kitten(
|
||||||
'hints', args=(
|
'hints', args=(
|
||||||
'--ascending', '--customize-processing=::import::kitty.choose_entry',
|
'--ascending', '--customize-processing=::import::kitty.choose_entry',
|
||||||
r'--regex=(?m)^:\s+.+$',
|
r'--regex=(?m)^:\s+.+$', '--window-title', title,
|
||||||
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
|
), input_data='\r\n'.join(lines).encode('utf-8'), custom_callback=done, action_on_removal=done2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -156,9 +156,12 @@ def default_env() -> Dict[str, str]:
|
|||||||
|
|
||||||
def set_default_env(val: Optional[Dict[str, str]] = None) -> None:
|
def set_default_env(val: Optional[Dict[str, str]] = None) -> None:
|
||||||
env = process_env().copy()
|
env = process_env().copy()
|
||||||
|
has_lctype = False
|
||||||
if val:
|
if val:
|
||||||
|
has_lctype = 'LC_CTYPE' in val
|
||||||
env.update(val)
|
env.update(val)
|
||||||
setattr(default_env, 'env', env)
|
setattr(default_env, 'env', env)
|
||||||
|
setattr(default_env, 'lc_ctype_set_by_user', has_lctype)
|
||||||
|
|
||||||
|
|
||||||
def openpty() -> Tuple[int, int]:
|
def openpty() -> Tuple[int, int]:
|
||||||
@@ -210,7 +213,8 @@ class Child:
|
|||||||
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
|
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
|
||||||
if env is None:
|
if env is None:
|
||||||
env = self._final_env = default_env().copy()
|
env = self._final_env = default_env().copy()
|
||||||
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not sys._xoptions.get('lc_ctype_before_python'):
|
if is_macos and env.get('LC_CTYPE') == 'UTF-8' and not sys._xoptions.get(
|
||||||
|
'lc_ctype_before_python') and not getattr(default_env, 'lc_ctype_set_by_user', False):
|
||||||
del env['LC_CTYPE']
|
del env['LC_CTYPE']
|
||||||
env.update(self.env)
|
env.update(self.env)
|
||||||
env['TERM'] = self.opts.term
|
env['TERM'] = self.opts.term
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class CLIOptions:
|
|||||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
||||||
|
QueryTerminalCLIOptions = BroadcastCLIOptions = CLIOptions
|
||||||
|
|
||||||
|
|
||||||
def generate_stub() -> None:
|
def generate_stub() -> None:
|
||||||
@@ -47,9 +48,15 @@ def generate_stub() -> None:
|
|||||||
from kittens.hints.main import OPTIONS
|
from kittens.hints.main import OPTIONS
|
||||||
do(OPTIONS(), 'HintsCLIOptions')
|
do(OPTIONS(), 'HintsCLIOptions')
|
||||||
|
|
||||||
|
from kittens.broadcast.main import OPTIONS
|
||||||
|
do(OPTIONS(), 'BroadcastCLIOptions')
|
||||||
|
|
||||||
from kittens.icat.main import options_spec
|
from kittens.icat.main import options_spec
|
||||||
do(options_spec(), 'IcatCLIOptions')
|
do(options_spec(), 'IcatCLIOptions')
|
||||||
|
|
||||||
|
from kittens.query_terminal.main import options_spec
|
||||||
|
do(options_spec(), 'QueryTerminalCLIOptions')
|
||||||
|
|
||||||
from kittens.panel.main import OPTIONS
|
from kittens.panel.main import OPTIONS
|
||||||
do(OPTIONS(), 'PanelCLIOptions')
|
do(OPTIONS(), 'PanelCLIOptions')
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
|
||||||
CSI = '\033['
|
CSI = '\033['
|
||||||
@@ -50,10 +51,26 @@ def screen_cursor_forward(amt: int) -> None:
|
|||||||
write(CSI + '%sC' % amt)
|
write(CSI + '%sC' % amt)
|
||||||
|
|
||||||
|
|
||||||
|
def screen_save_cursor() -> None:
|
||||||
|
write('\x1b7')
|
||||||
|
|
||||||
|
|
||||||
|
def screen_restore_cursor() -> None:
|
||||||
|
write('\x1b8')
|
||||||
|
|
||||||
|
|
||||||
def screen_cursor_back1(amt: int) -> None:
|
def screen_cursor_back1(amt: int) -> None:
|
||||||
write(CSI + '%sD' % amt)
|
write(CSI + '%sD' % amt)
|
||||||
|
|
||||||
|
|
||||||
|
def screen_save_modes() -> None:
|
||||||
|
write(CSI + '?s')
|
||||||
|
|
||||||
|
|
||||||
|
def screen_restore_modes() -> None:
|
||||||
|
write(CSI + '?r')
|
||||||
|
|
||||||
|
|
||||||
def screen_designate_charset(which: int, to: int) -> None:
|
def screen_designate_charset(which: int, to: int) -> None:
|
||||||
w = '()'[int(which)]
|
w = '()'[int(which)]
|
||||||
t = chr(int(to))
|
t = chr(int(to))
|
||||||
@@ -161,6 +178,14 @@ def report_device_attributes(mode: int, char: int) -> None:
|
|||||||
write(CSI + x + 'c')
|
write(CSI + x + 'c')
|
||||||
|
|
||||||
|
|
||||||
|
def screen_decsace(mode: int) -> None:
|
||||||
|
write(CSI + str(mode) + '*x')
|
||||||
|
|
||||||
|
|
||||||
|
def screen_set_8bit_controls(mode: int) -> None:
|
||||||
|
write('\x1b ' + ('G' if mode else 'F'))
|
||||||
|
|
||||||
|
|
||||||
def write_osc(code: int, string: str = '') -> None:
|
def write_osc(code: int, string: str = '') -> None:
|
||||||
if string:
|
if string:
|
||||||
string = ';' + string
|
string = ';' + string
|
||||||
@@ -168,6 +193,8 @@ def write_osc(code: int, string: str = '') -> None:
|
|||||||
|
|
||||||
|
|
||||||
set_dynamic_color = set_color_table_color = write_osc
|
set_dynamic_color = set_color_table_color = write_osc
|
||||||
|
screen_push_dynamic_colors = partial(write_osc, 30001)
|
||||||
|
screen_pop_dynamic_colors = partial(write_osc, 30101)
|
||||||
|
|
||||||
|
|
||||||
def replay(raw: str) -> None:
|
def replay(raw: str) -> None:
|
||||||
|
|||||||
@@ -182,14 +182,9 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
|
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
|
||||||
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
||||||
NSUserNotification *n = [NSUserNotification new];
|
NSUserNotification *n = [NSUserNotification new];
|
||||||
#define SET(x) { \
|
if (title) n.title = @(title);
|
||||||
if (x) { \
|
if (subtitle) n.subtitle = @(subtitle);
|
||||||
NSString *t = @(x); \
|
if (informativeText) n.informativeText = @(informativeText);
|
||||||
n.x = t; \
|
|
||||||
[t release]; \
|
|
||||||
}}
|
|
||||||
SET(title); SET(subtitle); SET(informativeText);
|
|
||||||
#undef SET
|
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
n.userInfo = @{@"user_id": @(identifier)};
|
n.userInfo = @{@"user_id": @(identifier)};
|
||||||
}
|
}
|
||||||
@@ -307,17 +302,17 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
|
|
||||||
@implementation ServiceProvider
|
@implementation ServiceProvider
|
||||||
|
|
||||||
- (void)openTab:(NSPasteboard*)pasteboard
|
- (BOOL)openTab:(NSPasteboard*)pasteboard
|
||||||
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
||||||
[self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD];
|
return [self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)openOSWindow:(NSPasteboard*)pasteboard
|
- (BOOL)openOSWindow:(NSPasteboard*)pasteboard
|
||||||
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
||||||
[self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD];
|
return [self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
|
- (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
|
||||||
NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES };
|
NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES };
|
||||||
NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
|
NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
|
||||||
for (NSURL *url in filePathArray) {
|
for (NSURL *url in filePathArray) {
|
||||||
@@ -330,6 +325,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
set_cocoa_pending_action(type, [path UTF8String]);
|
set_cocoa_pending_action(type, [path UTF8String]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -619,30 +615,6 @@ cocoa_hide_window_title(void *w)
|
|||||||
} // autoreleasepool
|
} // autoreleasepool
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
|
||||||
cocoa_hide_titlebar(void *w)
|
|
||||||
{
|
|
||||||
@autoreleasepool {
|
|
||||||
|
|
||||||
cocoa_hide_window_title(w);
|
|
||||||
|
|
||||||
NSWindow *window = (NSWindow*)w;
|
|
||||||
NSButton *button;
|
|
||||||
|
|
||||||
button = [window standardWindowButton: NSWindowCloseButton];
|
|
||||||
if (button) button.hidden = true;
|
|
||||||
button = [window standardWindowButton: NSWindowMiniaturizeButton];
|
|
||||||
if (button) button.hidden = true;
|
|
||||||
button = [window standardWindowButton: NSWindowZoomButton];
|
|
||||||
if (button) button.hidden = true;
|
|
||||||
|
|
||||||
[window setTitlebarAppearsTransparent:YES];
|
|
||||||
[window setStyleMask:
|
|
||||||
[window styleMask] | NSWindowStyleMaskFullSizeContentView];
|
|
||||||
|
|
||||||
} // autoreleasepool
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
cocoa_system_beep(void) {
|
cocoa_system_beep(void) {
|
||||||
NSBeep();
|
NSBeep();
|
||||||
|
|||||||
@@ -394,10 +394,10 @@ def complete_files_and_dirs(
|
|||||||
|
|
||||||
|
|
||||||
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
|
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
|
||||||
from mimetypes import guess_type
|
from .guess_mime_type import guess_type
|
||||||
|
|
||||||
def icat_file_predicate(filename: str) -> bool:
|
def icat_file_predicate(filename: str) -> bool:
|
||||||
mt = guess_type(filename)[0]
|
mt = guess_type(filename)
|
||||||
if mt and mt.startswith('image/'):
|
if mt and mt.startswith('image/'):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from .conf.utils import (
|
|||||||
)
|
)
|
||||||
from .config_data import all_options, parse_mods, type_convert
|
from .config_data import all_options, parse_mods, type_convert
|
||||||
from .constants import cache_dir, defconf, is_macos
|
from .constants import cache_dir, defconf, is_macos
|
||||||
|
from .fonts import FontFeature
|
||||||
from .key_names import get_key_name_lookup, key_name_aliases
|
from .key_names import get_key_name_lookup, key_name_aliases
|
||||||
from .options_stub import Options as OptionsStub
|
from .options_stub import Options as OptionsStub
|
||||||
from .typing import TypedDict
|
from .typing import TypedDict
|
||||||
@@ -524,14 +525,6 @@ def handle_symbol_map(key: str, val: str, ans: Dict[str, Any]) -> None:
|
|||||||
ans['symbol_map'].update(parse_symbol_map(val))
|
ans['symbol_map'].update(parse_symbol_map(val))
|
||||||
|
|
||||||
|
|
||||||
class FontFeature(str):
|
|
||||||
|
|
||||||
def __new__(cls, name: str, parsed: bytes) -> 'FontFeature':
|
|
||||||
ans: FontFeature = str.__new__(cls, name) # type: ignore
|
|
||||||
ans.parsed = parsed # type: ignore
|
|
||||||
return ans
|
|
||||||
|
|
||||||
|
|
||||||
@special_handler
|
@special_handler
|
||||||
def handle_font_features(key: str, val: str, ans: Dict[str, Any]) -> None:
|
def handle_font_features(key: str, val: str, ans: Dict[str, Any]) -> None:
|
||||||
if val != 'none':
|
if val != 'none':
|
||||||
|
|||||||
@@ -325,6 +325,9 @@ Note that this code is indexed by PostScript name, and not the font
|
|||||||
family. This allows you to define very precise feature settings; e.g. you can
|
family. This allows you to define very precise feature settings; e.g. you can
|
||||||
disable a feature in the italic font but not in the regular font.
|
disable a feature in the italic font but not in the regular font.
|
||||||
|
|
||||||
|
On Linux, these are read from the FontConfig database first and then this,
|
||||||
|
setting is applied, so they can be configured in a single, central place.
|
||||||
|
|
||||||
To get the PostScript name for a font, use :code:`kitty + list-fonts --psnames`:
|
To get the PostScript name for a font, use :code:`kitty + list-fonts --psnames`:
|
||||||
|
|
||||||
.. code-block:: sh
|
.. code-block:: sh
|
||||||
@@ -520,6 +523,11 @@ def url_prefixes(x: str) -> Tuple[str, ...]:
|
|||||||
o('url_prefixes', 'http https file ftp', option_type=url_prefixes, long_text=_('''
|
o('url_prefixes', 'http https file ftp', option_type=url_prefixes, long_text=_('''
|
||||||
The set of URL prefixes to look for when detecting a URL under the mouse cursor.'''))
|
The set of URL prefixes to look for when detecting a URL under the mouse cursor.'''))
|
||||||
|
|
||||||
|
o('detect_urls', True, long_text=_('''
|
||||||
|
Detect URLs under the mouse. Detected URLs are highlighted
|
||||||
|
with an underline and the mouse cursor becomes a hand over them.
|
||||||
|
Even if this option is disabled, URLs are still clickable.'''))
|
||||||
|
|
||||||
|
|
||||||
def copy_on_select(raw: str) -> str:
|
def copy_on_select(raw: str) -> str:
|
||||||
q = raw.lower()
|
q = raw.lower()
|
||||||
@@ -576,6 +584,16 @@ The shape of the mouse pointer when the program running in the terminal grabs th
|
|||||||
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
o('default_pointer_shape', 'beam', option_type=choices('arrow', 'beam', 'hand'), long_text=('''
|
||||||
|
The default shape of the mouse pointer.
|
||||||
|
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
||||||
|
'''))
|
||||||
|
|
||||||
|
o('pointer_shape_when_dragging', 'beam', option_type=choices('arrow', 'beam', 'hand'), long_text=('''
|
||||||
|
The default shape of the mouse pointer when dragging across text.
|
||||||
|
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
||||||
|
'''))
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
g('performance') # {{{
|
g('performance') # {{{
|
||||||
@@ -852,6 +870,8 @@ o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'powerline',
|
|||||||
The tab bar style, can be one of: :code:`fade`, :code:`separator`, :code:`powerline`, or :code:`hidden`.
|
The tab bar style, can be one of: :code:`fade`, :code:`separator`, :code:`powerline`, or :code:`hidden`.
|
||||||
In the fade style, each tab's edges fade into the background color, in the separator style, tabs are
|
In the fade style, each tab's edges fade into the background color, in the separator style, tabs are
|
||||||
separated by a configurable separator, and the powerline shows the tabs as a continuous line.
|
separated by a configurable separator, and the powerline shows the tabs as a continuous line.
|
||||||
|
If you use the hidden style, you might want to create a mapping for the :code:`select_tab` action which
|
||||||
|
presents you with a list of tabs and allows for easy switching to a tab.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
|
||||||
@@ -863,11 +883,12 @@ o('tab_bar_min_tabs', 2, option_type=tab_bar_min_tabs, long_text=_('''
|
|||||||
The minimum number of tabs that must exist before the tab bar is shown
|
The minimum number of tabs that must exist before the tab bar is shown
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
o('tab_switch_strategy', 'previous', option_type=choices('previous', 'left', 'last'), long_text=_('''
|
o('tab_switch_strategy', 'previous', option_type=choices('previous', 'left', 'right', 'last'), long_text=_('''
|
||||||
The algorithm to use when switching to a tab when the current tab is closed.
|
The algorithm to use when switching to a tab when the current tab is closed.
|
||||||
The default of :code:`previous` will switch to the last used tab. A value of
|
The default of :code:`previous` will switch to the last used tab. A value of
|
||||||
:code:`left` will switch to the tab to the left of the closed tab. A value
|
:code:`left` will switch to the tab to the left of the closed tab. A value
|
||||||
of :code:`last` will switch to the right-most tab.
|
of :code:`right` will switch to the tab to the right of the closed tab.
|
||||||
|
A value of :code:`last` will switch to the right-most tab.
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
|
||||||
@@ -922,6 +943,10 @@ layout name and :code:`{num_windows}` for the number of windows
|
|||||||
in the tab. Note that formatting is done by Python's string formatting
|
in the tab. Note that formatting is done by Python's string formatting
|
||||||
machinery, so you can use, for instance, :code:`{layout_name[:2].upper()}` to
|
machinery, so you can use, for instance, :code:`{layout_name[:2].upper()}` to
|
||||||
show only the first two letters of the layout name, upper-cased.
|
show only the first two letters of the layout name, upper-cased.
|
||||||
|
If you want to style the text, you can use styling directives, for example:
|
||||||
|
:code:`{fmt.fg.red}red{fmt.fg.default}normal{fmt.bg._00FF00}green bg{fmt.bg.normal}`.
|
||||||
|
Similarly, for bold and italic:
|
||||||
|
:code:`{fmt.bold}bold{fmt.nobold}normal{fmt.italic}italic{fmt.noitalic}`.
|
||||||
'''))
|
'''))
|
||||||
o('active_tab_title_template', 'none', option_type=active_tab_title_template, long_text=_('''
|
o('active_tab_title_template', 'none', option_type=active_tab_title_template, long_text=_('''
|
||||||
Template to use for active tabs, if not specified falls back
|
Template to use for active tabs, if not specified falls back
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
appname: str = 'kitty'
|
appname: str = 'kitty'
|
||||||
version: Version = Version(0, 19, 1)
|
version: Version = Version(0, 19, 3)
|
||||||
str_version: str = '.'.join(map(str, version))
|
str_version: str = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos: bool = 'darwin' in _plat
|
is_macos: bool = 'darwin' in _plat
|
||||||
|
|||||||
@@ -161,12 +161,13 @@ coretext_all_fonts(PyObject UNUSED *_self) {
|
|||||||
CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
|
CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
|
||||||
const CFIndex count = CFArrayGetCount(matches);
|
const CFIndex count = CFArrayGetCount(matches);
|
||||||
PyObject *ans = PyTuple_New(count), *temp;
|
PyObject *ans = PyTuple_New(count), *temp;
|
||||||
if (ans == NULL) return PyErr_NoMemory();
|
if (ans == NULL) { CFRelease(matches); return PyErr_NoMemory(); }
|
||||||
for (CFIndex i = 0; i < count; i++) {
|
for (CFIndex i = 0; i < count; i++) {
|
||||||
temp = font_descriptor_to_python((CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i));
|
temp = font_descriptor_to_python((CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i));
|
||||||
if (temp == NULL) { Py_DECREF(ans); return NULL; }
|
if (temp == NULL) { CFRelease(matches); Py_DECREF(ans); return NULL; }
|
||||||
PyTuple_SET_ITEM(ans, i, temp); temp = NULL;
|
PyTuple_SET_ITEM(ans, i, temp); temp = NULL;
|
||||||
}
|
}
|
||||||
|
CFRelease(matches);
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +213,7 @@ manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
|
|||||||
CFRelease(new_font);
|
CFRelease(new_font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CFRelease(fonts);
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout) {
|
put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) {
|
||||||
struct termios raw_termios = *termios_p;
|
struct termios raw_termios = *termios_p;
|
||||||
cfmakeraw(&raw_termios);
|
cfmakeraw(&raw_termios);
|
||||||
if (read_with_timeout) {
|
if (read_with_timeout) {
|
||||||
@@ -99,14 +99,14 @@ put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_time
|
|||||||
} else {
|
} else {
|
||||||
raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0;
|
raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0;
|
||||||
}
|
}
|
||||||
if (tcsetattr(fd, TCSAFLUSH, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; }
|
if (tcsetattr(fd, optional_actions, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
open_tty(PyObject *self UNUSED, PyObject *args) {
|
open_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
int read_with_timeout = 0;
|
int read_with_timeout = 0, optional_actions = TCSAFLUSH;
|
||||||
if (!PyArg_ParseTuple(args, "|p", &read_with_timeout)) return NULL;
|
if (!PyArg_ParseTuple(args, "|pi", &read_with_timeout, &optional_actions)) return NULL;
|
||||||
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
|
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
|
||||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||||
static char ctty[L_ctermid+1];
|
static char ctty[L_ctermid+1];
|
||||||
@@ -115,26 +115,26 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
|
|||||||
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
||||||
if (!termios_p) return PyErr_NoMemory();
|
if (!termios_p) return PyErr_NoMemory();
|
||||||
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||||
if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0)) { free(termios_p); return NULL; }
|
if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0, optional_actions)) { free(termios_p); return NULL; }
|
||||||
return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p));
|
return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TTY_ARGS \
|
#define TTY_ARGS \
|
||||||
PyObject *tp; int fd; \
|
PyObject *tp; int fd; int optional_actions = TCSAFLUSH; \
|
||||||
if (!PyArg_ParseTuple(args, "iO!", &fd, &PyLong_Type, &tp)) return NULL; \
|
if (!PyArg_ParseTuple(args, "iO!|i", &fd, &PyLong_Type, &tp, &optional_actions)) return NULL; \
|
||||||
struct termios *termios_p = PyLong_AsVoidPtr(tp);
|
struct termios *termios_p = PyLong_AsVoidPtr(tp);
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
normal_tty(PyObject *self UNUSED, PyObject *args) {
|
normal_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
TTY_ARGS
|
TTY_ARGS
|
||||||
if (tcsetattr(fd, TCSAFLUSH, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
if (tcsetattr(fd, optional_actions, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
raw_tty(PyObject *self UNUSED, PyObject *args) {
|
raw_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
TTY_ARGS
|
TTY_ARGS
|
||||||
if (!put_tty_in_raw_mode(fd, termios_p, false)) return NULL;
|
if (!put_tty_in_raw_mode(fd, termios_p, false, optional_actions)) return NULL;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ raw_tty(PyObject *self UNUSED, PyObject *args) {
|
|||||||
static PyObject*
|
static PyObject*
|
||||||
close_tty(PyObject *self UNUSED, PyObject *args) {
|
close_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
TTY_ARGS
|
TTY_ARGS
|
||||||
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure
|
tcsetattr(fd, optional_actions, termios_p); // deliberately ignore failure
|
||||||
free(termios_p);
|
free(termios_p);
|
||||||
safe_close(fd, __FILE__, __LINE__);
|
safe_close(fd, __FILE__, __LINE__);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|||||||
@@ -193,9 +193,8 @@ typedef struct {
|
|||||||
} HistoryBufSegment;
|
} HistoryBufSegment;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t *buffer;
|
void *ringbuf;
|
||||||
size_t buffer_size, max_sz;
|
size_t maximum_size;
|
||||||
size_t start, length;
|
|
||||||
bool rewrap_needed;
|
bool rewrap_needed;
|
||||||
} PagerHistoryBuf;
|
} PagerHistoryBuf;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from typing import (
|
|||||||
Union
|
Union
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import termios
|
||||||
from kitty.boss import Boss
|
from kitty.boss import Boss
|
||||||
from kitty.fonts.render import FontObject
|
from kitty.fonts.render import FontObject
|
||||||
from kitty.options_stub import Options
|
from kitty.options_stub import Options
|
||||||
@@ -409,6 +410,7 @@ class FontConfigPattern(TypedDict):
|
|||||||
postscript_name: str
|
postscript_name: str
|
||||||
style: str
|
style: str
|
||||||
spacing: str
|
spacing: str
|
||||||
|
fontfeatures: List[str]
|
||||||
weight: int
|
weight: int
|
||||||
width: int
|
width: int
|
||||||
slant: int
|
slant: int
|
||||||
@@ -440,6 +442,12 @@ def fc_match(
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def fc_match_postscript_name(
|
||||||
|
postscript_name: str
|
||||||
|
) -> FontConfigPattern:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class CoreTextFont(TypedDict):
|
class CoreTextFont(TypedDict):
|
||||||
path: str
|
path: str
|
||||||
postscript_name: str
|
postscript_name: str
|
||||||
@@ -865,19 +873,19 @@ def viewport_for_window(
|
|||||||
TermiosPtr = NewType('TermiosPtr', int)
|
TermiosPtr = NewType('TermiosPtr', int)
|
||||||
|
|
||||||
|
|
||||||
def raw_tty(fd: int, termios_ptr: TermiosPtr) -> None:
|
def raw_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def close_tty(fd: int, termios_ptr: TermiosPtr) -> None:
|
def close_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def normal_tty(fd: int, termios_ptr: TermiosPtr) -> None:
|
def normal_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def open_tty(read_with_timeout: bool = False) -> Tuple[int, TermiosPtr]:
|
def open_tty(read_with_timeout: bool = False, optional_actions: int = termios.TCSAFLUSH) -> Tuple[int, TermiosPtr]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -1001,6 +1009,9 @@ class Screen:
|
|||||||
def draw(self, text: str) -> None:
|
def draw(self, text: str) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def apply_sgr(self, text: str) -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
def copy_colors_from(self, other: 'Screen') -> None:
|
def copy_colors_from(self, other: 'Screen') -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -27,17 +27,33 @@ pyspacing(int val) {
|
|||||||
|
|
||||||
static inline PyObject*
|
static inline PyObject*
|
||||||
pattern_as_dict(FcPattern *pat) {
|
pattern_as_dict(FcPattern *pat) {
|
||||||
PyObject *ans = PyDict_New();
|
PyObject *ans = PyDict_New(), *p = NULL, *list = NULL;
|
||||||
if (ans == NULL) return NULL;
|
if (ans == NULL) return NULL;
|
||||||
|
|
||||||
#define PS(x) PyUnicode_Decode((const char*)x, strlen((const char*)x), "UTF-8", "replace")
|
#define PS(x) PyUnicode_Decode((const char*)x, strlen((const char*)x), "UTF-8", "replace")
|
||||||
|
|
||||||
#define G(type, get, which, conv, name) { \
|
#define G(type, get, which, conv, name) { \
|
||||||
type out; PyObject *p; \
|
type out; \
|
||||||
if (get(pat, which, 0, &out) == FcResultMatch) { \
|
if (get(pat, which, 0, &out) == FcResultMatch) { \
|
||||||
p = conv(out); if (p == NULL) { Py_CLEAR(ans); return NULL; } \
|
p = conv(out); if (p == NULL) goto exit; \
|
||||||
if (PyDict_SetItemString(ans, #name, p) != 0) { Py_CLEAR(p); Py_CLEAR(ans); return NULL; } \
|
if (PyDict_SetItemString(ans, #name, p) != 0) goto exit; \
|
||||||
Py_CLEAR(p); \
|
Py_CLEAR(p); \
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
#define L(type, get, which, conv, name) { \
|
||||||
|
type out; int n = 0; \
|
||||||
|
list = PyList_New(0); \
|
||||||
|
if (!list) goto exit; \
|
||||||
|
while (get(pat, which, n++, &out) == FcResultMatch) { \
|
||||||
|
p = conv(out); if (p == NULL) goto exit; \
|
||||||
|
if (PyList_Append(list, p) != 0) goto exit; \
|
||||||
|
Py_CLEAR(p); \
|
||||||
|
} \
|
||||||
|
if (PyDict_SetItemString(ans, #name, list) != 0) goto exit; \
|
||||||
|
Py_CLEAR(list); \
|
||||||
|
}
|
||||||
#define S(which, key) G(FcChar8*, FcPatternGetString, which, PS, key)
|
#define S(which, key) G(FcChar8*, FcPatternGetString, which, PS, key)
|
||||||
|
#define LS(which, key) L(FcChar8*, FcPatternGetString, which, PS, key)
|
||||||
#define I(which, key) G(int, FcPatternGetInteger, which, PyLong_FromLong, key)
|
#define I(which, key) G(int, FcPatternGetInteger, which, PyLong_FromLong, key)
|
||||||
#define B(which, key) G(int, FcPatternGetBool, which, pybool, key)
|
#define B(which, key) G(int, FcPatternGetBool, which, pybool, key)
|
||||||
#define E(which, key, conv) G(int, FcPatternGetInteger, which, conv, key)
|
#define E(which, key, conv) G(int, FcPatternGetInteger, which, conv, key)
|
||||||
@@ -46,6 +62,7 @@ pattern_as_dict(FcPattern *pat) {
|
|||||||
S(FC_STYLE, style);
|
S(FC_STYLE, style);
|
||||||
S(FC_FULLNAME, full_name);
|
S(FC_FULLNAME, full_name);
|
||||||
S(FC_POSTSCRIPT_NAME, postscript_name);
|
S(FC_POSTSCRIPT_NAME, postscript_name);
|
||||||
|
LS(FC_FONT_FEATURES, fontfeatures);
|
||||||
I(FC_WEIGHT, weight);
|
I(FC_WEIGHT, weight);
|
||||||
I(FC_WIDTH, width)
|
I(FC_WIDTH, width)
|
||||||
I(FC_SLANT, slant);
|
I(FC_SLANT, slant);
|
||||||
@@ -58,6 +75,10 @@ pattern_as_dict(FcPattern *pat) {
|
|||||||
B(FC_OUTLINE, outline);
|
B(FC_OUTLINE, outline);
|
||||||
B(FC_COLOR, color);
|
B(FC_COLOR, color);
|
||||||
E(FC_SPACING, spacing, pyspacing);
|
E(FC_SPACING, spacing, pyspacing);
|
||||||
|
exit:
|
||||||
|
if (PyErr_Occurred()) Py_CLEAR(ans);
|
||||||
|
Py_CLEAR(p);
|
||||||
|
Py_CLEAR(list);
|
||||||
|
|
||||||
return ans;
|
return ans;
|
||||||
#undef PS
|
#undef PS
|
||||||
@@ -66,6 +87,8 @@ pattern_as_dict(FcPattern *pat) {
|
|||||||
#undef B
|
#undef B
|
||||||
#undef E
|
#undef E
|
||||||
#undef G
|
#undef G
|
||||||
|
#undef L
|
||||||
|
#undef LS
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline PyObject*
|
static inline PyObject*
|
||||||
@@ -179,6 +202,27 @@ end:
|
|||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
fc_match_postscript_name(PyObject UNUSED *self, PyObject *args) {
|
||||||
|
const char *postscript_name = NULL;
|
||||||
|
FcPattern *pat = NULL;
|
||||||
|
PyObject *ans = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "s", &postscript_name)) return NULL;
|
||||||
|
if (!postscript_name || !postscript_name[0]) { PyErr_SetString(PyExc_KeyError, "postscript_name must not be empty"); return NULL; }
|
||||||
|
|
||||||
|
pat = FcPatternCreate();
|
||||||
|
if (pat == NULL) return PyErr_NoMemory();
|
||||||
|
|
||||||
|
AP(FcPatternAddString, FC_POSTSCRIPT_NAME, (const FcChar8*)postscript_name, "postscript_name");
|
||||||
|
|
||||||
|
ans = _fc_match(pat);
|
||||||
|
|
||||||
|
end:
|
||||||
|
if (pat != NULL) FcPatternDestroy(pat);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
PyObject*
|
PyObject*
|
||||||
specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE fg) {
|
specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE fg) {
|
||||||
PyObject *p = PyDict_GetItemString(base_descriptor, "path"), *ans = NULL;
|
PyObject *p = PyDict_GetItemString(base_descriptor, "path"), *ans = NULL;
|
||||||
@@ -224,6 +268,7 @@ end:
|
|||||||
static PyMethodDef module_methods[] = {
|
static PyMethodDef module_methods[] = {
|
||||||
METHODB(fc_list, METH_VARARGS),
|
METHODB(fc_list, METH_VARARGS),
|
||||||
METHODB(fc_match, METH_VARARGS),
|
METHODB(fc_match, METH_VARARGS),
|
||||||
|
METHODB(fc_match_postscript_name, METH_VARARGS),
|
||||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -596,6 +596,7 @@ START_ALLOW_CASE_RANGE
|
|||||||
case 0x2500 ... 0x2573:
|
case 0x2500 ... 0x2573:
|
||||||
case 0x2574 ... 0x259f:
|
case 0x2574 ... 0x259f:
|
||||||
case 0xe0b0 ... 0xe0b4:
|
case 0xe0b0 ... 0xe0b4:
|
||||||
|
case 0x2800 ... 0x28ff:
|
||||||
case 0xe0b6:
|
case 0xe0b6:
|
||||||
case 0xe0b8: //
|
case 0xe0b8: //
|
||||||
case 0xe0ba: //
|
case 0xe0ba: //
|
||||||
@@ -642,8 +643,10 @@ START_ALLOW_CASE_RANGE
|
|||||||
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
|
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
|
||||||
case 0x1fb00 ... 0x1fb8b:
|
case 0x1fb00 ... 0x1fb8b:
|
||||||
return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150
|
return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150
|
||||||
case 0x1fba0 ... 0x1fbae:
|
case 0x1fba0 ... 0x1fbae: // IDs from 0x151 to 0x15f
|
||||||
return 0x151 + ch - 0x1fba0;
|
return 0x151 + ch - 0x1fba0;
|
||||||
|
case 0x2800 ... 0x28ff:
|
||||||
|
return 0x160 + ch - 0x2800;
|
||||||
default:
|
default:
|
||||||
return 0xffff;
|
return 0xffff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,11 @@ class ListedFont(TypedDict):
|
|||||||
full_name: str
|
full_name: str
|
||||||
postscript_name: str
|
postscript_name: str
|
||||||
is_monospace: bool
|
is_monospace: bool
|
||||||
|
|
||||||
|
|
||||||
|
class FontFeature(str):
|
||||||
|
|
||||||
|
def __new__(cls, name: str, parsed: bytes) -> 'FontFeature':
|
||||||
|
ans: FontFeature = str.__new__(cls, name) # type: ignore
|
||||||
|
ans.parsed = parsed # type: ignore
|
||||||
|
return ans
|
||||||
|
|||||||
@@ -663,17 +663,32 @@ def smooth_mosaic(
|
|||||||
buf[offset + x] = 255
|
buf[offset + x] = 255
|
||||||
|
|
||||||
|
|
||||||
|
def eight_range(size: int, which: int) -> range:
|
||||||
|
thickness = max(1, size // 8)
|
||||||
|
block = thickness * 8
|
||||||
|
if block == size:
|
||||||
|
return range(thickness * which, thickness * (which + 1))
|
||||||
|
if block > size:
|
||||||
|
start = min(which * thickness, size - thickness)
|
||||||
|
return range(start, start + thickness)
|
||||||
|
extra = size - block
|
||||||
|
thicknesses = list(repeat(thickness, 8))
|
||||||
|
for i in (3, 4, 2, 5, 6, 1, 7, 0): # ensures the thickness of first and last are least likely to be changed
|
||||||
|
if not extra:
|
||||||
|
break
|
||||||
|
extra -= 1
|
||||||
|
thicknesses[i] += 1
|
||||||
|
pos = sum(thicknesses[:which])
|
||||||
|
return range(pos, pos + thicknesses[which])
|
||||||
|
|
||||||
|
|
||||||
def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int = 0, horizontal: bool = False) -> None:
|
def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int = 0, horizontal: bool = False) -> None:
|
||||||
if horizontal:
|
if horizontal:
|
||||||
x_range = range(0, width)
|
x_range = range(0, width)
|
||||||
thickness = height // 8
|
y_range = eight_range(height, which)
|
||||||
y_start = min(which * thickness, height - 2)
|
|
||||||
y_range = range(y_start, height if which == 7 else min(y_start + thickness, height))
|
|
||||||
else:
|
else:
|
||||||
y_range = range(0, height)
|
y_range = range(0, height)
|
||||||
thickness = width // 8
|
x_range = eight_range(width, which)
|
||||||
x_start = min(which * thickness, width - 2)
|
|
||||||
x_range = range(x_start, width if which == 7 else min(x_start + thickness, width))
|
|
||||||
for y in y_range:
|
for y in y_range:
|
||||||
offset = y * width
|
offset = y * width
|
||||||
for x in x_range:
|
for x in x_range:
|
||||||
@@ -685,6 +700,32 @@ def eight_block(buf: BufType, width: int, height: int, level: int = 1, which: Tu
|
|||||||
eight_bar(buf, width, height, level, x, horizontal)
|
eight_bar(buf, width, height, level, x, horizontal)
|
||||||
|
|
||||||
|
|
||||||
|
def braille_dot(buf: BufType, width: int, height: int, col: int, row: int) -> None:
|
||||||
|
dot_height = max(1, height // 8)
|
||||||
|
dot_width = max(1, width // 4)
|
||||||
|
top_margin = (height - 7 * dot_height) // 2
|
||||||
|
left_margin = (width - 3 * dot_width) // 2
|
||||||
|
x_start = left_margin + (col * 2 * dot_width)
|
||||||
|
y_start = top_margin + (row * 2 * dot_height)
|
||||||
|
if y_start < height:
|
||||||
|
for y in range(y_start, min(height, y_start + dot_height)):
|
||||||
|
if x_start < width:
|
||||||
|
offset = y * width
|
||||||
|
for x in range(x_start, min(width, x_start + dot_width)):
|
||||||
|
buf[offset + x] = 255
|
||||||
|
|
||||||
|
|
||||||
|
def braille(buf: BufType, width: int, height: int, which: int = 0) -> None:
|
||||||
|
if not which:
|
||||||
|
return
|
||||||
|
for i, x in enumerate(reversed(bin(which)[2:])):
|
||||||
|
if x == '1':
|
||||||
|
q = i + 1
|
||||||
|
col = 0 if q in (1, 2, 3, 7) else 1
|
||||||
|
row = 0 if q in (1, 4) else 1 if q in (2, 5) else 2 if q in (3, 6) else 3
|
||||||
|
braille_dot(buf, width, height, col, row)
|
||||||
|
|
||||||
|
|
||||||
box_chars: Dict[str, List[Callable]] = {
|
box_chars: Dict[str, List[Callable]] = {
|
||||||
'─': [hline],
|
'─': [hline],
|
||||||
'━': [p(hline, level=3)],
|
'━': [p(hline, level=3)],
|
||||||
@@ -908,13 +949,15 @@ for chars, func_ in (('╒╕╘╛', dvcorner), ('╓╖╙╜', dhcorner), ('
|
|||||||
for ch in chars:
|
for ch in chars:
|
||||||
box_chars[ch] = [p(cast(Callable, func_), which=ch)]
|
box_chars[ch] = [p(cast(Callable, func_), which=ch)]
|
||||||
|
|
||||||
|
for i in range(256):
|
||||||
|
box_chars[chr(0x2800 + i)] = [p(braille, which=i)]
|
||||||
|
|
||||||
|
|
||||||
c = 0x1fb00
|
c = 0x1fb00
|
||||||
for i in range(1, 63):
|
for i in range(1, 63):
|
||||||
if i in (20, 40):
|
if i not in (21, 42):
|
||||||
continue
|
box_chars[chr(c)] = [p(sextant, which=i)]
|
||||||
box_chars[chr(c)] = [p(sextant, which=i)]
|
c += 1
|
||||||
c += 1
|
|
||||||
|
|
||||||
for i in range(1, 7):
|
for i in range(1, 7):
|
||||||
box_chars[chr(0x1fb6f + i)] = [p(eight_bar, which=i)]
|
box_chars[chr(0x1fb6f + i)] = [p(eight_bar, which=i)]
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ def list_fonts() -> Generator[ListedFont, None, None]:
|
|||||||
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono}
|
yield {'family': f, 'full_name': fn, 'postscript_name': fd['postscript_name'] or '', 'is_monospace': is_mono}
|
||||||
|
|
||||||
|
|
||||||
|
def find_font_features(postscript_name: str) -> Tuple[str, ...]:
|
||||||
|
"""Not Implemented"""
|
||||||
|
return ()
|
||||||
|
|
||||||
|
|
||||||
def find_best_match(family: str, bold: bool = False, italic: bool = False) -> CoreTextFont:
|
def find_best_match(family: str, bold: bool = False, italic: bool = False) -> CoreTextFont:
|
||||||
q = re.sub(r'\s+', ' ', family.lower())
|
q = re.sub(r'\s+', ' ', family.lower())
|
||||||
font_map = all_fonts_map()
|
font_map = all_fonts_map()
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ from typing import Dict, Generator, List, Optional, Tuple, cast
|
|||||||
|
|
||||||
from kitty.fast_data_types import (
|
from kitty.fast_data_types import (
|
||||||
FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD,
|
FC_DUAL, FC_MONO, FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD,
|
||||||
FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, fc_list, fc_match as fc_match_impl
|
FC_WEIGHT_REGULAR, FC_WIDTH_NORMAL, fc_list, fc_match as fc_match_impl,
|
||||||
|
fc_match_postscript_name, parse_font_feature
|
||||||
)
|
)
|
||||||
from kitty.options_stub import Options
|
from kitty.options_stub import Options
|
||||||
from kitty.typing import FontConfigPattern
|
from kitty.typing import FontConfigPattern
|
||||||
|
from kitty.utils import log_error
|
||||||
|
|
||||||
from . import ListedFont
|
from . import ListedFont, FontFeature
|
||||||
|
|
||||||
attr_map = {(False, False): 'font_family',
|
attr_map = {(False, False): 'font_family',
|
||||||
(True, False): 'bold_font',
|
(True, False): 'bold_font',
|
||||||
@@ -69,6 +71,24 @@ def fc_match(family: str, bold: bool, italic: bool, spacing: int = FC_MONO) -> F
|
|||||||
return fc_match_impl(family, bold, italic, spacing)
|
return fc_match_impl(family, bold, italic, spacing)
|
||||||
|
|
||||||
|
|
||||||
|
def find_font_features(postscript_name: str) -> Tuple[str, ...]:
|
||||||
|
pat = fc_match_postscript_name(postscript_name)
|
||||||
|
|
||||||
|
if pat.get('postscript_name') != postscript_name or 'fontfeatures' not in pat:
|
||||||
|
return ()
|
||||||
|
|
||||||
|
features = []
|
||||||
|
for feat in pat['fontfeatures']:
|
||||||
|
try:
|
||||||
|
parsed = parse_font_feature(feat)
|
||||||
|
except ValueError:
|
||||||
|
log_error('Ignoring invalid font feature: {}'.format(feat))
|
||||||
|
else:
|
||||||
|
features.append(FontFeature(feat, parsed))
|
||||||
|
|
||||||
|
return tuple(features)
|
||||||
|
|
||||||
|
|
||||||
def find_best_match(family: str, bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern:
|
def find_best_match(family: str, bold: bool = False, italic: bool = False, monospaced: bool = True) -> FontConfigPattern:
|
||||||
q = family_name_to_key(family)
|
q = family_name_to_key(family)
|
||||||
font_map = all_fonts_map(monospaced)
|
font_map = all_fonts_map(monospaced)
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ from kitty.typing import CoreTextFont, FontConfigPattern
|
|||||||
from kitty.utils import log_error
|
from kitty.utils import log_error
|
||||||
|
|
||||||
if is_macos:
|
if is_macos:
|
||||||
from .core_text import get_font_files as get_font_files_coretext, font_for_family as font_for_family_macos
|
from .core_text import get_font_files as get_font_files_coretext, font_for_family as font_for_family_macos, find_font_features
|
||||||
else:
|
else:
|
||||||
from .fontconfig import get_font_files as get_font_files_fontconfig, font_for_family as font_for_family_fontconfig
|
from .fontconfig import get_font_files as get_font_files_fontconfig, font_for_family as font_for_family_fontconfig, find_font_features
|
||||||
|
|
||||||
FontObject = Union[CoreTextFont, FontConfigPattern]
|
FontObject = Union[CoreTextFont, FontConfigPattern]
|
||||||
current_faces: List[Tuple[FontObject, bool, bool]] = []
|
current_faces: List[Tuple[FontObject, bool, bool]] = []
|
||||||
@@ -189,12 +189,16 @@ def set_font_family(opts: Optional[OptionsStub] = None, override_font_size: Opti
|
|||||||
before = len(current_faces)
|
before = len(current_faces)
|
||||||
sm = create_symbol_map(opts)
|
sm = create_symbol_map(opts)
|
||||||
num_symbol_fonts = len(current_faces) - before
|
num_symbol_fonts = len(current_faces) - before
|
||||||
|
font_features = {}
|
||||||
|
for face, _, _ in current_faces:
|
||||||
|
font_features[face['postscript_name']] = find_font_features(face['postscript_name'])
|
||||||
|
font_features.update(opts.font_features)
|
||||||
if debug_font_matching:
|
if debug_font_matching:
|
||||||
dump_faces(ftypes, indices)
|
dump_faces(ftypes, indices)
|
||||||
set_font_data(
|
set_font_data(
|
||||||
render_box_drawing, prerender_function, descriptor_for_idx,
|
render_box_drawing, prerender_function, descriptor_for_idx,
|
||||||
indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts,
|
indices['bold'], indices['italic'], indices['bi'], num_symbol_fonts,
|
||||||
sm, sz, opts.font_features
|
sm, sz, font_features
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
kitty/glfw-wrapper.c
generated
2
kitty/glfw-wrapper.c
generated
@@ -380,6 +380,8 @@ load_glfw(const char* path) {
|
|||||||
|
|
||||||
*(void **) (&glfwGetCocoaWindow_impl) = dlsym(handle, "glfwGetCocoaWindow");
|
*(void **) (&glfwGetCocoaWindow_impl) = dlsym(handle, "glfwGetCocoaWindow");
|
||||||
|
|
||||||
|
*(void **) (&glfwHideCocoaTitlebar_impl) = dlsym(handle, "glfwHideCocoaTitlebar");
|
||||||
|
|
||||||
*(void **) (&glfwGetNSGLContext_impl) = dlsym(handle, "glfwGetNSGLContext");
|
*(void **) (&glfwGetNSGLContext_impl) = dlsym(handle, "glfwGetNSGLContext");
|
||||||
|
|
||||||
*(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor");
|
*(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor");
|
||||||
|
|||||||
4
kitty/glfw-wrapper.h
generated
4
kitty/glfw-wrapper.h
generated
@@ -2147,6 +2147,10 @@ typedef void* (*glfwGetCocoaWindow_func)(GLFWwindow*);
|
|||||||
GFW_EXTERN glfwGetCocoaWindow_func glfwGetCocoaWindow_impl;
|
GFW_EXTERN glfwGetCocoaWindow_func glfwGetCocoaWindow_impl;
|
||||||
#define glfwGetCocoaWindow glfwGetCocoaWindow_impl
|
#define glfwGetCocoaWindow glfwGetCocoaWindow_impl
|
||||||
|
|
||||||
|
typedef void (*glfwHideCocoaTitlebar_func)(GLFWwindow*, bool);
|
||||||
|
GFW_EXTERN glfwHideCocoaTitlebar_func glfwHideCocoaTitlebar_impl;
|
||||||
|
#define glfwHideCocoaTitlebar glfwHideCocoaTitlebar_impl
|
||||||
|
|
||||||
typedef void* (*glfwGetNSGLContext_func)(GLFWwindow*);
|
typedef void* (*glfwGetNSGLContext_func)(GLFWwindow*);
|
||||||
GFW_EXTERN glfwGetNSGLContext_func glfwGetNSGLContext_impl;
|
GFW_EXTERN glfwGetNSGLContext_func glfwGetNSGLContext_impl;
|
||||||
#define glfwGetNSGLContext glfwGetNSGLContext_impl
|
#define glfwGetNSGLContext glfwGetNSGLContext_impl
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ extern void cocoa_focus_window(void *w);
|
|||||||
extern long cocoa_window_number(void *w);
|
extern long cocoa_window_number(void *w);
|
||||||
extern void cocoa_create_global_menu(void);
|
extern void cocoa_create_global_menu(void);
|
||||||
extern void cocoa_hide_window_title(void *w);
|
extern void cocoa_hide_window_title(void *w);
|
||||||
extern void cocoa_hide_titlebar(void *w);
|
|
||||||
extern void cocoa_system_beep(void);
|
extern void cocoa_system_beep(void);
|
||||||
extern void cocoa_set_activation_policy(bool);
|
extern void cocoa_set_activation_policy(bool);
|
||||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
||||||
@@ -421,6 +420,7 @@ make_os_window_context_current(OSWindow *w) {
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
|
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
|
||||||
|
*xscale = 1; *yscale = 1;
|
||||||
if (w) glfwGetWindowContentScale(w, xscale, yscale);
|
if (w) glfwGetWindowContentScale(w, xscale, yscale);
|
||||||
else {
|
else {
|
||||||
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
||||||
@@ -440,7 +440,7 @@ get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xd
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
||||||
float xscale = 1, yscale = 1;
|
float xscale, yscale;
|
||||||
get_window_content_scale(w, &xscale, &yscale, x, y);
|
get_window_content_scale(w, &xscale, &yscale, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -694,7 +694,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
|||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
if (glfwGetCocoaWindow) {
|
if (glfwGetCocoaWindow) {
|
||||||
if (OPT(hide_window_decorations) & 2) {
|
if (OPT(hide_window_decorations) & 2) {
|
||||||
cocoa_hide_titlebar(glfwGetCocoaWindow(glfw_window));
|
glfwHideCocoaTitlebar(glfw_window, true);
|
||||||
} else if (!(OPT(macos_show_window_title_in) & WINDOW)) {
|
} else if (!(OPT(macos_show_window_title_in) & WINDOW)) {
|
||||||
cocoa_hide_window_title(glfwGetCocoaWindow(glfw_window));
|
cocoa_hide_window_title(glfwGetCocoaWindow(glfw_window));
|
||||||
}
|
}
|
||||||
|
|||||||
176
kitty/graphics.c
176
kitty/graphics.c
@@ -92,6 +92,16 @@ img_by_client_id(GraphicsManager *self, uint32_t id) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline Image*
|
||||||
|
img_by_client_number(GraphicsManager *self, uint32_t number) {
|
||||||
|
// get the newest image with the specified number
|
||||||
|
for (size_t i = self->image_count; i-- > 0; ) {
|
||||||
|
if (self->images[i].client_number == number) return self->images + i;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
remove_image(GraphicsManager *self, size_t idx) {
|
remove_image(GraphicsManager *self, size_t idx) {
|
||||||
free_image(self, self->images + idx);
|
free_image(self, self->images + idx);
|
||||||
@@ -137,22 +147,20 @@ apply_storage_quota(GraphicsManager *self, size_t storage_limit, id_type current
|
|||||||
if (!self->image_count) self->used_storage = 0; // sanity check
|
if (!self->image_count) self->used_storage = 0; // sanity check
|
||||||
}
|
}
|
||||||
|
|
||||||
static char add_response[512] = {0};
|
static char command_response[512] = {0};
|
||||||
static bool has_add_respose = false;
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
set_add_response(const char *code, const char *fmt, ...) {
|
set_command_failed_response(const char *code, const char *fmt, ...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
size_t sz = sizeof(add_response)/sizeof(add_response[0]);
|
const size_t sz = sizeof(command_response)/sizeof(command_response[0]);
|
||||||
int num = snprintf(add_response, sz, "%s:", code);
|
const int num = snprintf(command_response, sz, "%s:", code);
|
||||||
vsnprintf(add_response + num, sz - num, fmt, args);
|
vsnprintf(command_response + num, sz - num, fmt, args);
|
||||||
va_end(args);
|
va_end(args);
|
||||||
has_add_respose = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode formats {{{
|
// Decode formats {{{
|
||||||
#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); goto err; }
|
#define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); goto err; }
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
mmap_img_file(GraphicsManager UNUSED *self, Image *img, int fd, size_t sz, off_t offset) {
|
mmap_img_file(GraphicsManager UNUSED *self, Image *img, int fd, size_t sz, off_t offset) {
|
||||||
@@ -220,7 +228,7 @@ err:
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
png_error_handler(const char *code, const char *msg) {
|
png_error_handler(const char *code, const char *msg) {
|
||||||
set_add_response(code, "%s", msg);
|
set_command_failed_response(code, "%s", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@@ -308,12 +316,38 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
|
|||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
cmp_client_ids(const void* a, const void* b) {
|
||||||
|
const uint32_t *x = a, *y = b;
|
||||||
|
return *x - *y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t
|
||||||
|
get_free_client_id(const GraphicsManager *self) {
|
||||||
|
if (!self->image_count) return 1;
|
||||||
|
uint32_t *client_ids = malloc(sizeof(uint32_t) * self->image_count);
|
||||||
|
size_t count = 0;
|
||||||
|
for (size_t i = 0; i < self->image_count; i++) {
|
||||||
|
Image *q = self->images + i;
|
||||||
|
if (q->client_id) client_ids[count++] = q->client_id;
|
||||||
|
}
|
||||||
|
if (!count) { free(client_ids); return 1; }
|
||||||
|
qsort(client_ids, count, sizeof(uint32_t), cmp_client_ids);
|
||||||
|
uint32_t prev_id = 0, ans = 1;
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
if (client_ids[i] == prev_id) continue;
|
||||||
|
prev_id = client_ids[i];
|
||||||
|
if (client_ids[i] != ans) break;
|
||||||
|
ans = client_ids[i] + 1;
|
||||||
|
}
|
||||||
|
free(client_ids);
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
static Image*
|
static Image*
|
||||||
handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid) {
|
handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, bool *is_dirty, uint32_t iid) {
|
||||||
#define ABRT(code, ...) { set_add_response(#code, __VA_ARGS__); self->loading_image = 0; if (img) img->data_loaded = false; return NULL; }
|
#define ABRT(code, ...) { set_command_failed_response(#code, __VA_ARGS__); self->loading_image = 0; if (img) img->data_loaded = false; return NULL; }
|
||||||
#define MAX_DATA_SZ (4u * 100000000u)
|
#define MAX_DATA_SZ (4u * 100000000u)
|
||||||
has_add_respose = false;
|
|
||||||
bool existing, init_img = true;
|
bool existing, init_img = true;
|
||||||
Image *img = NULL;
|
Image *img = NULL;
|
||||||
unsigned char tt = g->transmission_type ? g->transmission_type : 'd';
|
unsigned char tt = g->transmission_type ? g->transmission_type : 'd';
|
||||||
@@ -336,6 +370,11 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
|||||||
} else {
|
} else {
|
||||||
img->internal_id = internal_id_counter++;
|
img->internal_id = internal_id_counter++;
|
||||||
img->client_id = iid;
|
img->client_id = iid;
|
||||||
|
img->client_number = g->image_number;
|
||||||
|
if (!img->client_id && img->client_number) {
|
||||||
|
img->client_id = get_free_client_id(self);
|
||||||
|
self->last_init_graphics_command.id = img->client_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
img->atime = monotonic(); img->used_storage = 0;
|
img->atime = monotonic(); img->used_storage = 0;
|
||||||
img->width = g->data_width; img->height = g->data_height;
|
img->width = g->data_width; img->height = g->data_height;
|
||||||
@@ -402,7 +441,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
|||||||
snprintf(fname, sizeof(fname)/sizeof(fname[0]), "%.*s", (int)g->payload_sz, payload);
|
snprintf(fname, sizeof(fname)/sizeof(fname[0]), "%.*s", (int)g->payload_sz, payload);
|
||||||
if (tt == 's') fd = shm_open(fname, O_RDONLY, 0);
|
if (tt == 's') fd = shm_open(fname, O_RDONLY, 0);
|
||||||
else fd = open(fname, O_CLOEXEC | O_RDONLY);
|
else fd = open(fname, O_CLOEXEC | O_RDONLY);
|
||||||
if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno));
|
if (fd == -1) ABRT(EBADF, "Failed to open file for graphics transmission with error: [%d] %s", errno, strerror(errno));
|
||||||
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
|
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
|
||||||
safe_close(fd, __FILE__, __LINE__);
|
safe_close(fd, __FILE__, __LINE__);
|
||||||
if (tt == 't') {
|
if (tt == 't') {
|
||||||
@@ -475,15 +514,26 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
|||||||
}
|
}
|
||||||
|
|
||||||
static inline const char*
|
static inline const char*
|
||||||
create_add_response(GraphicsManager UNUSED *self, bool data_loaded, uint32_t iid) {
|
finish_command_response(const GraphicsCommand *g, bool data_loaded, uint32_t iid, uint32_t placement_id, uint32_t image_number) {
|
||||||
static char rbuf[sizeof(add_response)/sizeof(add_response[0]) + 64];
|
static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128];
|
||||||
if (iid) {
|
bool is_ok_response = !command_response[0];
|
||||||
if (!has_add_respose) {
|
if (g->quiet) {
|
||||||
|
if (is_ok_response || g->quiet > 1) return NULL;
|
||||||
|
}
|
||||||
|
if (iid || image_number) {
|
||||||
|
if (is_ok_response) {
|
||||||
if (!data_loaded) return NULL;
|
if (!data_loaded) return NULL;
|
||||||
snprintf(add_response, 10, "OK");
|
snprintf(command_response, 10, "OK");
|
||||||
}
|
}
|
||||||
snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u;%s", iid, add_response);
|
size_t pos = 0;
|
||||||
|
rbuf[pos++] = 'G';
|
||||||
|
#define print(fmt, ...) pos += snprintf(rbuf + pos, arraysz(rbuf) - 1 - pos, fmt, __VA_ARGS__)
|
||||||
|
if (iid) print("i=%u", iid);
|
||||||
|
if (image_number) print(",I=%u", image_number);
|
||||||
|
if (placement_id) print(",p=%u", placement_id);
|
||||||
|
print(";%s", command_response);
|
||||||
return rbuf;
|
return rbuf;
|
||||||
|
#undef print
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@@ -519,20 +569,24 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows, CellPixelS
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void
|
static uint32_t
|
||||||
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
|
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img, CellPixelSize cell) {
|
||||||
has_add_respose = false;
|
if (img == NULL) {
|
||||||
if (img == NULL) img = img_by_client_id(self, g->id);
|
if (g->id) img = img_by_client_id(self, g->id);
|
||||||
if (img == NULL) { set_add_response("ENOENT", "Put command refers to non-existent image with id: %u", g->id); return; }
|
else if (g->image_number) img = img_by_client_number(self, g->image_number);
|
||||||
if (!img->data_loaded) { set_add_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return; }
|
if (img == NULL) { set_command_failed_response("ENOENT", "Put command refers to non-existent image with id: %u and number: %u", g->id, g->image_number); return g->id; }
|
||||||
|
}
|
||||||
|
if (!img->data_loaded) { set_command_failed_response("ENOENT", "Put command refers to image with id: %u that could not load its data", g->id); return img->client_id; }
|
||||||
ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 16, true);
|
ensure_space_for(img, refs, ImageRef, img->refcnt + 1, refcap, 16, true);
|
||||||
*is_dirty = true;
|
*is_dirty = true;
|
||||||
self->layers_dirty = true;
|
self->layers_dirty = true;
|
||||||
ImageRef *ref = NULL;
|
ImageRef *ref = NULL;
|
||||||
for (size_t i=0; i < img->refcnt; i++) {
|
if (g->placement_id && img->client_id) {
|
||||||
if ((unsigned)img->refs[i].start_row == c->x && (unsigned)img->refs[i].start_column == c->y) {
|
for (size_t i=0; i < img->refcnt; i++) {
|
||||||
ref = img->refs + i;
|
if (img->refs[i].client_id == g->placement_id) {
|
||||||
break;
|
ref = img->refs + i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ref == NULL) {
|
if (ref == NULL) {
|
||||||
@@ -548,10 +602,12 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
|
|||||||
ref->cell_x_offset = MIN(g->cell_x_offset, cell.width - 1);
|
ref->cell_x_offset = MIN(g->cell_x_offset, cell.width - 1);
|
||||||
ref->cell_y_offset = MIN(g->cell_y_offset, cell.height - 1);
|
ref->cell_y_offset = MIN(g->cell_y_offset, cell.height - 1);
|
||||||
ref->num_cols = g->num_cells; ref->num_rows = g->num_lines;
|
ref->num_cols = g->num_cells; ref->num_rows = g->num_lines;
|
||||||
|
if (img->client_id) ref->client_id = g->placement_id;
|
||||||
update_src_rect(ref, img);
|
update_src_rect(ref, img);
|
||||||
update_dest_rect(ref, g->num_cells, g->num_lines, cell);
|
update_dest_rect(ref, g->num_cells, g->num_lines, cell);
|
||||||
// Move the cursor, the screen will take care of ensuring it is in bounds
|
// Move the cursor, the screen will take care of ensuring it is in bounds
|
||||||
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
|
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
|
||||||
|
return img->client_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
@@ -645,7 +701,8 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
|||||||
// Image lifetime/scrolling {{{
|
// Image lifetime/scrolling {{{
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(const ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell, bool only_first_image) {
|
||||||
|
bool matched = false;
|
||||||
for (size_t i = self->image_count; i-- > 0;) {
|
for (size_t i = self->image_count; i-- > 0;) {
|
||||||
Image *img = self->images + i;
|
Image *img = self->images + i;
|
||||||
for (size_t j = img->refcnt; j-- > 0;) {
|
for (size_t j = img->refcnt; j-- > 0;) {
|
||||||
@@ -653,12 +710,15 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi
|
|||||||
if (filter_func(ref, img, data, cell)) {
|
if (filter_func(ref, img, data, cell)) {
|
||||||
remove_i_from_array(img->refs, j, img->refcnt);
|
remove_i_from_array(img->refs, j, img->refcnt);
|
||||||
self->layers_dirty = true;
|
self->layers_dirty = true;
|
||||||
|
matched = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
|
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
|
||||||
|
if (only_first_image && matched) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
modify_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
modify_refs(GraphicsManager *self, const void* data, bool free_images, bool (*filter_func)(ImageRef*, Image*, const void*, CellPixelSize), CellPixelSize cell) {
|
||||||
for (size_t i = self->image_count; i-- > 0;) {
|
for (size_t i = self->image_count; i-- > 0;) {
|
||||||
@@ -739,15 +799,24 @@ clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void
|
|||||||
|
|
||||||
void
|
void
|
||||||
grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) {
|
grman_clear(GraphicsManager *self, bool all, CellPixelSize cell) {
|
||||||
filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell);
|
filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func, cell, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
id_filter_func(const ImageRef UNUSED *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
uint32_t iid = *(uint32_t*)data;
|
const GraphicsCommand *g = data;
|
||||||
return img->client_id == iid;
|
if (g->id && img->client_id == g->id) return !g->placement_id || ref->client_id == g->placement_id;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline bool
|
||||||
|
number_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
|
const GraphicsCommand *g = data;
|
||||||
|
if (g->image_number && img->client_number == g->image_number) return !g->placement_id || ref->client_id == g->placement_id;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||||
const GraphicsCommand *g = data;
|
const GraphicsCommand *g = data;
|
||||||
@@ -780,14 +849,15 @@ point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixel
|
|||||||
|
|
||||||
static void
|
static void
|
||||||
handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, CellPixelSize cell) {
|
handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, CellPixelSize cell) {
|
||||||
static GraphicsCommand d;
|
GraphicsCommand d;
|
||||||
|
bool only_first_image = false;
|
||||||
switch (g->delete_action) {
|
switch (g->delete_action) {
|
||||||
#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func, cell); *is_dirty = true; break
|
#define I(u, data, func) filter_refs(self, data, g->delete_action == u, func, cell, only_first_image); *is_dirty = true; break
|
||||||
#define D(l, u, data, func) case l: case u: I(u, data, func)
|
#define D(l, u, data, func) case l: case u: I(u, data, func)
|
||||||
#define G(l, u, func) D(l, u, g, func)
|
#define G(l, u, func) D(l, u, g, func)
|
||||||
case 0:
|
case 0:
|
||||||
D('a', 'A', NULL, clear_filter_func);
|
D('a', 'A', NULL, clear_filter_func);
|
||||||
D('i', 'I', &g->id, id_filter_func);
|
G('i', 'I', id_filter_func);
|
||||||
G('p', 'P', point_filter_func);
|
G('p', 'P', point_filter_func);
|
||||||
G('q', 'Q', point3d_filter_func);
|
G('q', 'Q', point3d_filter_func);
|
||||||
G('x', 'X', x_filter_func);
|
G('x', 'X', x_filter_func);
|
||||||
@@ -797,6 +867,10 @@ handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c
|
|||||||
case 'C':
|
case 'C':
|
||||||
d.x_offset = c->x + 1; d.y_offset = c->y + 1;
|
d.x_offset = c->x + 1; d.y_offset = c->y + 1;
|
||||||
I('C', &d, point_filter_func);
|
I('C', &d, point_filter_func);
|
||||||
|
case 'n':
|
||||||
|
case 'N':
|
||||||
|
only_first_image = true;
|
||||||
|
I('N', g, number_filter_func);
|
||||||
default:
|
default:
|
||||||
REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action);
|
REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action);
|
||||||
break;
|
break;
|
||||||
@@ -832,6 +906,12 @@ grman_rescale(GraphicsManager *self, CellPixelSize cell) {
|
|||||||
const char*
|
const char*
|
||||||
grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) {
|
grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) {
|
||||||
const char *ret = NULL;
|
const char *ret = NULL;
|
||||||
|
command_response[0] = 0;
|
||||||
|
|
||||||
|
if (g->id && g->image_number) {
|
||||||
|
set_command_failed_response("EINVAL", "Must not specify both image id and image number");
|
||||||
|
return finish_command_response(g, false, g->id, g->placement_id, g->image_number);
|
||||||
|
}
|
||||||
|
|
||||||
switch(g->action) {
|
switch(g->action) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -839,9 +919,11 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
|||||||
case 'T':
|
case 'T':
|
||||||
case 'q': {
|
case 'q': {
|
||||||
uint32_t iid = g->id, q_iid = iid;
|
uint32_t iid = g->id, q_iid = iid;
|
||||||
if (g->action == 'q') { iid = 0; if (!q_iid) { REPORT_ERROR("Query graphics command without image id"); break; } }
|
bool is_query = g->action == 'q';
|
||||||
|
if (is_query) { iid = 0; if (!q_iid) { REPORT_ERROR("Query graphics command without image id"); break; } }
|
||||||
Image *image = handle_add_command(self, g, payload, is_dirty, iid);
|
Image *image = handle_add_command(self, g, payload, is_dirty, iid);
|
||||||
ret = create_add_response(self, image != NULL, g->action == 'q' ? q_iid: self->last_init_graphics_command.id);
|
if (is_query) ret = finish_command_response(g, image != NULL, q_iid, 0, 0);
|
||||||
|
else ret = finish_command_response(g, image != NULL, self->last_init_graphics_command.id, self->last_init_graphics_command.placement_id, self->last_init_graphics_command.image_number);
|
||||||
if (self->last_init_graphics_command.action == 'T' && image && image->data_loaded) handle_put_command(self, &self->last_init_graphics_command, c, is_dirty, image, cell);
|
if (self->last_init_graphics_command.action == 'T' && image && image->data_loaded) handle_put_command(self, &self->last_init_graphics_command, c, is_dirty, image, cell);
|
||||||
id_type added_image_id = image ? image->internal_id : 0;
|
id_type added_image_id = image ? image->internal_id : 0;
|
||||||
if (g->action == 'q') remove_images(self, add_trim_predicate, 0);
|
if (g->action == 'q') remove_images(self, add_trim_predicate, 0);
|
||||||
@@ -849,12 +931,12 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'p':
|
case 'p':
|
||||||
if (!g->id) {
|
if (!g->id && !g->image_number) {
|
||||||
REPORT_ERROR("Put graphics command without image id");
|
REPORT_ERROR("Put graphics command without image id or number");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
handle_put_command(self, g, c, is_dirty, NULL, cell);
|
uint32_t image_id = handle_put_command(self, g, c, is_dirty, NULL, cell);
|
||||||
ret = create_add_response(self, true, g->id);
|
ret = finish_command_response(g, true, image_id, g->placement_id, g->image_number);
|
||||||
break;
|
break;
|
||||||
case 'd':
|
case 'd':
|
||||||
handle_delete_command(self, g, c, is_dirty, cell);
|
handle_delete_command(self, g, c, is_dirty, cell);
|
||||||
@@ -878,8 +960,8 @@ new(PyTypeObject UNUSED *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
|||||||
static inline PyObject*
|
static inline PyObject*
|
||||||
image_as_dict(Image *img) {
|
image_as_dict(Image *img) {
|
||||||
#define U(x) #x, img->x
|
#define U(x) #x, img->x
|
||||||
return Py_BuildValue("{sI sI sI sI sK sI sO sO sN}",
|
return Py_BuildValue("{sI sI sI sI sK sI sI sO sO sN}",
|
||||||
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt),
|
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt), U(client_number),
|
||||||
"data_loaded", img->data_loaded ? Py_True : Py_False,
|
"data_loaded", img->data_loaded ? Py_True : Py_False,
|
||||||
"is_4byte_aligned", img->load_data.is_4byte_aligned ? Py_True : Py_False,
|
"is_4byte_aligned", img->load_data.is_4byte_aligned ? Py_True : Py_False,
|
||||||
"data", Py_BuildValue("y#", img->load_data.data, img->load_data.data_sz)
|
"data", Py_BuildValue("y#", img->load_data.data, img->load_data.data_sz)
|
||||||
@@ -899,6 +981,13 @@ W(image_for_client_id) {
|
|||||||
return image_as_dict(img);
|
return image_as_dict(img);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
W(image_for_client_number) {
|
||||||
|
unsigned long num = PyLong_AsUnsignedLong(args);
|
||||||
|
Image *img = img_by_client_number(self, num);
|
||||||
|
if (!img) Py_RETURN_NONE;
|
||||||
|
return image_as_dict(img);
|
||||||
|
}
|
||||||
|
|
||||||
W(shm_write) {
|
W(shm_write) {
|
||||||
const char *name, *data;
|
const char *name, *data;
|
||||||
Py_ssize_t sz;
|
Py_ssize_t sz;
|
||||||
@@ -949,6 +1038,7 @@ W(update_layers) {
|
|||||||
|
|
||||||
static PyMethodDef methods[] = {
|
static PyMethodDef methods[] = {
|
||||||
M(image_for_client_id, METH_O),
|
M(image_for_client_id, METH_O),
|
||||||
|
M(image_for_client_number, METH_O),
|
||||||
M(update_layers, METH_VARARGS),
|
M(update_layers, METH_VARARGS),
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned char action, transmission_type, compressed, delete_action;
|
unsigned char action, transmission_type, compressed, delete_action;
|
||||||
uint32_t format, more, id, data_sz, data_offset;
|
uint32_t format, more, id, image_number, data_sz, data_offset, placement_id, quiet;
|
||||||
uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines, cell_x_offset, cell_y_offset;
|
uint32_t width, height, x_offset, y_offset, data_height, data_width, num_cells, num_lines, cell_x_offset, cell_y_offset;
|
||||||
int32_t z_index;
|
int32_t z_index;
|
||||||
size_t payload_sz;
|
size_t payload_sz;
|
||||||
@@ -38,12 +38,13 @@ typedef struct {
|
|||||||
uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows, effective_num_cols;
|
uint32_t cell_x_offset, cell_y_offset, num_cols, num_rows, effective_num_rows, effective_num_cols;
|
||||||
int32_t z_index;
|
int32_t z_index;
|
||||||
int32_t start_row, start_column;
|
int32_t start_row, start_column;
|
||||||
|
uint32_t client_id;
|
||||||
ImageRect src_rect;
|
ImageRect src_rect;
|
||||||
} ImageRef;
|
} ImageRef;
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t texture_id, client_id, width, height;
|
uint32_t texture_id, client_id, client_number, width, height;
|
||||||
id_type internal_id;
|
id_type internal_id;
|
||||||
|
|
||||||
bool data_loaded;
|
bool data_loaded;
|
||||||
|
|||||||
57
kitty/guess_mime_type.py
Normal file
57
kitty/guess_mime_type.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
known_extensions = {
|
||||||
|
'asciidoc': 'text/asciidoctor',
|
||||||
|
'conf': 'text/config',
|
||||||
|
'md': 'text/markdown',
|
||||||
|
'pyj': 'text/rapydscript-ng',
|
||||||
|
'recipe': 'text/python',
|
||||||
|
'rst': 'text/restructured-text',
|
||||||
|
'toml': 'text/toml',
|
||||||
|
'vim': 'text/vim',
|
||||||
|
'yaml': 'text/yaml',
|
||||||
|
'js': 'text/javascript',
|
||||||
|
'json': 'text/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
text_mimes = ('application/javascript', 'application/x-sh', 'application/json')
|
||||||
|
|
||||||
|
|
||||||
|
def is_rc_file(path: str) -> bool:
|
||||||
|
name = os.path.basename(path)
|
||||||
|
return '.' not in name and name.endswith('rc')
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_mime_database() -> None:
|
||||||
|
if hasattr(initialize_mime_database, 'inited'):
|
||||||
|
return
|
||||||
|
setattr(initialize_mime_database, 'inited', True)
|
||||||
|
from mimetypes import init
|
||||||
|
init(None)
|
||||||
|
from kitty.constants import config_dir
|
||||||
|
local_defs = os.path.join(config_dir, 'mime.types')
|
||||||
|
if os.path.exists(local_defs):
|
||||||
|
init((local_defs,))
|
||||||
|
|
||||||
|
|
||||||
|
def guess_type(path: str) -> Optional[str]:
|
||||||
|
from mimetypes import guess_type as stdlib_guess_type
|
||||||
|
initialize_mime_database()
|
||||||
|
mt = None
|
||||||
|
with suppress(Exception):
|
||||||
|
mt = stdlib_guess_type(path)[0]
|
||||||
|
if not mt:
|
||||||
|
ext = path.rpartition('.')[-1].lower()
|
||||||
|
mt = known_extensions.get(ext)
|
||||||
|
if mt in text_mimes:
|
||||||
|
mt = 'text/' + mt.split('/', 1)[-1]
|
||||||
|
if not mt and is_rc_file(path):
|
||||||
|
mt = 'text/plain'
|
||||||
|
return mt
|
||||||
110
kitty/history.c
110
kitty/history.c
@@ -9,6 +9,7 @@
|
|||||||
#include "lineops.h"
|
#include "lineops.h"
|
||||||
#include "charsets.h"
|
#include "charsets.h"
|
||||||
#include <structmember.h>
|
#include <structmember.h>
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
extern PyTypeObject Line_Type;
|
extern PyTypeObject Line_Type;
|
||||||
#define SEGMENT_SIZE 2048
|
#define SEGMENT_SIZE 2048
|
||||||
@@ -61,42 +62,37 @@ alloc_pagerhist(size_t pagerhist_sz) {
|
|||||||
if (!pagerhist_sz) return NULL;
|
if (!pagerhist_sz) return NULL;
|
||||||
ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf));
|
ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf));
|
||||||
if (!ph) return NULL;
|
if (!ph) return NULL;
|
||||||
ph->max_sz = pagerhist_sz;
|
size_t sz = MIN(1024u * 1024u, pagerhist_sz);
|
||||||
ph->buffer_size = MIN(1024u*1024u, ph->max_sz);
|
ph->ringbuf = ringbuf_new(sz);
|
||||||
ph->buffer = PyMem_RawMalloc(ph->buffer_size);
|
if (!ph->ringbuf) { PyMem_Free(ph); return NULL; }
|
||||||
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
|
ph->maximum_size = pagerhist_sz;
|
||||||
return ph;
|
return ph;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
free_pagerhist(HistoryBuf *self) {
|
free_pagerhist(HistoryBuf *self) {
|
||||||
if (self->pagerhist) PyMem_Free(self->pagerhist->buffer);
|
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_free((ringbuf_t*)&self->pagerhist->ringbuf);
|
||||||
PyMem_Free(self->pagerhist);
|
PyMem_Free(self->pagerhist);
|
||||||
self->pagerhist = NULL;
|
self->pagerhist = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
||||||
if (ph->buffer_size >= ph->max_sz) return false;
|
size_t buffer_size = ringbuf_capacity(ph->ringbuf);
|
||||||
size_t newsz = MIN(ph->max_sz, ph->buffer_size + MAX(1024u * 1024u, minsz));
|
if (buffer_size >= ph->maximum_size) return false;
|
||||||
uint8_t *newbuf = PyMem_Malloc(newsz);
|
size_t newsz = MIN(ph->maximum_size, buffer_size + MAX(1024u * 1024u, minsz));
|
||||||
|
ringbuf_t newbuf = ringbuf_new(newsz);
|
||||||
if (!newbuf) return false;
|
if (!newbuf) return false;
|
||||||
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
|
size_t count = ringbuf_bytes_used(ph->ringbuf);
|
||||||
if (copied) memcpy(newbuf, ph->buffer + ph->start, copied);
|
if (count) ringbuf_copy(newbuf, ph->ringbuf, count);
|
||||||
if (copied < ph->length) memcpy(newbuf + copied, ph->buffer, (ph->length - copied));
|
ringbuf_free((ringbuf_t*)&ph->ringbuf);
|
||||||
PyMem_Free(ph->buffer);
|
ph->ringbuf = newbuf;
|
||||||
ph->start = 0;
|
|
||||||
ph->buffer = newbuf;
|
|
||||||
ph->buffer_size = newsz;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
pagerhist_clear(HistoryBuf *self) {
|
pagerhist_clear(HistoryBuf *self) {
|
||||||
if (!self->pagerhist || !self->pagerhist->max_sz) return;
|
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_reset(self->pagerhist->ringbuf);
|
||||||
index_type pagerhist_sz = self->pagerhist->max_sz;
|
|
||||||
free_pagerhist(self);
|
|
||||||
self->pagerhist = alloc_pagerhist(pagerhist_sz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static HistoryBuf*
|
static HistoryBuf*
|
||||||
@@ -188,36 +184,28 @@ historybuf_clear(HistoryBuf *self) {
|
|||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) {
|
pagerhist_write_bytes(PagerHistoryBuf *ph, const uint8_t *buf, size_t sz) {
|
||||||
if (sz > ph->max_sz) return false;
|
if (sz > ph->maximum_size) return false;
|
||||||
if (!sz) return true;
|
if (!sz) return true;
|
||||||
if (sz > ph->buffer_size - ph->length) pagerhist_extend(ph, sz);
|
size_t space_in_ringbuf = ringbuf_bytes_free(ph->ringbuf);
|
||||||
if (sz > ph->buffer_size) return false;
|
if (sz > space_in_ringbuf) pagerhist_extend(ph, sz);
|
||||||
size_t start_writing_at = (ph->start + ph->length) % ph->buffer_size;
|
ringbuf_memcpy_into(ph->ringbuf, buf, sz);
|
||||||
size_t available_space = ph->buffer_size - ph->length;
|
|
||||||
size_t overlap = available_space < sz ? sz - available_space : 0;
|
|
||||||
size_t copied = MIN(sz, ph->buffer_size - start_writing_at);
|
|
||||||
ph->length += sz - overlap;
|
|
||||||
ph->start = (ph->start + overlap) % ph->buffer_size;
|
|
||||||
if (copied) memcpy(ph->buffer + start_writing_at, buf, copied);
|
|
||||||
if (copied < sz) memcpy(ph->buffer, buf + copied, (sz - copied));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) {
|
pagerhist_ensure_start_is_valid_utf8(PagerHistoryBuf *ph) {
|
||||||
|
uint8_t scratch[8];
|
||||||
|
size_t num = ringbuf_memcpy_from(scratch, ph->ringbuf, arraysz(scratch));
|
||||||
uint32_t state = UTF8_ACCEPT, codep;
|
uint32_t state = UTF8_ACCEPT, codep;
|
||||||
size_t pos = ph->start, count = 0;
|
size_t count = 0;
|
||||||
size_t last_reject_at = 0;
|
size_t last_reject_at = 0;
|
||||||
while (count < ph->length) {
|
while (count < num) {
|
||||||
decode_utf8(&state, &codep, ph->buffer[pos]);
|
decode_utf8(&state, &codep, scratch[count++]);
|
||||||
count++;
|
|
||||||
if (state == UTF8_ACCEPT) break;
|
if (state == UTF8_ACCEPT) break;
|
||||||
if (state == UTF8_REJECT) { state = UTF8_ACCEPT; last_reject_at = count; }
|
if (state == UTF8_REJECT) { state = UTF8_ACCEPT; last_reject_at = count; }
|
||||||
pos = pos == ph->buffer_size - 1 ? 0: pos + 1;
|
|
||||||
}
|
}
|
||||||
if (last_reject_at) {
|
if (last_reject_at) {
|
||||||
ph->start = (ph->start + last_reject_at) % ph->buffer_size;
|
ringbuf_memmove_from(scratch, ph->ringbuf, last_reject_at);
|
||||||
ph->length -= last_reject_at;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -241,7 +229,7 @@ pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
|
|||||||
Line l = {.xnum=self->xnum};
|
Line l = {.xnum=self->xnum};
|
||||||
init_line(self, self->start_of_data, &l);
|
init_line(self, self->start_of_data, &l);
|
||||||
line_as_ansi(&l, as_ansi_buf, &prev_cell);
|
line_as_ansi(&l, as_ansi_buf, &prev_cell);
|
||||||
if (ph->length != 0 && !l.continued) pagerhist_write_bytes(ph, (const uint8_t*)"\n", 1);
|
if (ringbuf_bytes_used(ph->ringbuf) && !l.continued) pagerhist_write_bytes(ph, (const uint8_t*)"\n", 1);
|
||||||
pagerhist_write_bytes(ph, (const uint8_t*)"\x1b[m", 3);
|
pagerhist_write_bytes(ph, (const uint8_t*)"\x1b[m", 3);
|
||||||
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1);
|
if (pagerhist_write_ucs4(ph, as_ansi_buf->buf, as_ansi_buf->len)) pagerhist_write_bytes(ph, (const uint8_t*)"\r", 1);
|
||||||
}
|
}
|
||||||
@@ -335,15 +323,16 @@ static inline Line*
|
|||||||
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
|
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
|
||||||
|
|
||||||
static inline char_type
|
static inline char_type
|
||||||
pagerhist_read_char(PagerHistoryBuf *ph, size_t pos, unsigned *count, uint8_t record[8]) {
|
pagerhist_remove_char(PagerHistoryBuf *ph, unsigned *count, uint8_t record[8]) {
|
||||||
uint32_t codep, state = UTF8_ACCEPT;
|
uint32_t codep, state = UTF8_ACCEPT;
|
||||||
*count = 0;
|
*count = 0;
|
||||||
while (true) {
|
size_t num = ringbuf_bytes_used(ph->ringbuf);
|
||||||
decode_utf8(&state, &codep, ph->buffer[pos]);
|
while (num--) {
|
||||||
record[(*count)++] = ph->buffer[pos];
|
record[*count] = ringbuf_move_char(ph->ringbuf);
|
||||||
|
decode_utf8(&state, &codep, record[*count]);
|
||||||
|
*count += 1;
|
||||||
if (state == UTF8_REJECT) { codep = 0; break; }
|
if (state == UTF8_REJECT) { codep = 0; break; }
|
||||||
if (state == UTF8_ACCEPT) break;
|
if (state == UTF8_ACCEPT) break;
|
||||||
pos = pos == ph->buffer_size - 1 ? 0 : (pos + 1);
|
|
||||||
}
|
}
|
||||||
return codep;
|
return codep;
|
||||||
}
|
}
|
||||||
@@ -351,14 +340,12 @@ pagerhist_read_char(PagerHistoryBuf *ph, size_t pos, unsigned *count, uint8_t re
|
|||||||
static void
|
static void
|
||||||
pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
||||||
PagerHistoryBuf *ph = self->pagerhist;
|
PagerHistoryBuf *ph = self->pagerhist;
|
||||||
if (!ph->length) return;
|
if (!ph->ringbuf || !ringbuf_bytes_used(ph->ringbuf)) return;
|
||||||
PagerHistoryBuf *nph = PyMem_Calloc(sizeof(PagerHistoryBuf), 1);
|
PagerHistoryBuf *nph = PyMem_Calloc(sizeof(PagerHistoryBuf), 1);
|
||||||
if (!nph) return;
|
if (!nph) return;
|
||||||
nph->buffer_size = ph->buffer_size;
|
nph->maximum_size = ph->maximum_size;
|
||||||
nph->max_sz = ph->max_sz;
|
nph->ringbuf = ringbuf_new(MIN(ph->maximum_size, ringbuf_capacity(ph->ringbuf) + 4096));
|
||||||
nph->buffer = PyMem_Malloc(nph->buffer_size);
|
if (!nph->ringbuf) { PyMem_Free(nph); return ; }
|
||||||
if (!nph->buffer) { PyMem_Free(nph); return ; }
|
|
||||||
size_t i = 0, pos;
|
|
||||||
ssize_t ch_width = 0;
|
ssize_t ch_width = 0;
|
||||||
unsigned count;
|
unsigned count;
|
||||||
uint8_t record[8];
|
uint8_t record[8];
|
||||||
@@ -367,11 +354,6 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
|||||||
WCSState wcs_state;
|
WCSState wcs_state;
|
||||||
initialize_wcs_state(&wcs_state);
|
initialize_wcs_state(&wcs_state);
|
||||||
|
|
||||||
#define READ_CHAR(ch) { \
|
|
||||||
ch = pagerhist_read_char(ph, pos, &count, record); \
|
|
||||||
i += count; pos += count; \
|
|
||||||
if (pos >= ph->buffer_size) pos = pos - ph->buffer_size; \
|
|
||||||
}
|
|
||||||
#define WRITE_CHAR() { \
|
#define WRITE_CHAR() { \
|
||||||
if (num_in_current_line + ch_width > cells_in_line) { \
|
if (num_in_current_line + ch_width > cells_in_line) { \
|
||||||
pagerhist_write_bytes(nph, (const uint8_t*)"\r", 1); \
|
pagerhist_write_bytes(nph, (const uint8_t*)"\r", 1); \
|
||||||
@@ -381,10 +363,8 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
|||||||
pagerhist_write_bytes(nph, record, count); \
|
pagerhist_write_bytes(nph, record, count); \
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < ph->length;) {
|
while (ringbuf_bytes_used(ph->ringbuf)) {
|
||||||
pos = ph->start + i;
|
ch = pagerhist_remove_char(ph, &count, record);
|
||||||
if (pos >= ph->buffer_size) pos = pos - ph->buffer_size;
|
|
||||||
READ_CHAR(ch);
|
|
||||||
if (ch == '\n') {
|
if (ch == '\n') {
|
||||||
initialize_wcs_state(&wcs_state);
|
initialize_wcs_state(&wcs_state);
|
||||||
ch_width = 1;
|
ch_width = 1;
|
||||||
@@ -397,12 +377,12 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
|||||||
}
|
}
|
||||||
free_pagerhist(self);
|
free_pagerhist(self);
|
||||||
self->pagerhist = nph;
|
self->pagerhist = nph;
|
||||||
#undef READ_CHAR
|
#undef WRITE_CHAR
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
pagerhist_write(HistoryBuf *self, PyObject *what) {
|
pagerhist_write(HistoryBuf *self, PyObject *what) {
|
||||||
if (self->pagerhist && self->pagerhist->max_sz) {
|
if (self->pagerhist && self->pagerhist->maximum_size) {
|
||||||
if (PyBytes_Check(what)) pagerhist_write_bytes(self->pagerhist, (const uint8_t*)PyBytes_AS_STRING(what), PyBytes_GET_SIZE(what));
|
if (PyBytes_Check(what)) pagerhist_write_bytes(self->pagerhist, (const uint8_t*)PyBytes_AS_STRING(what), PyBytes_GET_SIZE(what));
|
||||||
else if (PyUnicode_Check(what) && PyUnicode_READY(what) == 0) {
|
else if (PyUnicode_Check(what) && PyUnicode_READY(what) == 0) {
|
||||||
Py_UCS4 *buf = PyUnicode_AsUCS4Copy(what);
|
Py_UCS4 *buf = PyUnicode_AsUCS4Copy(what);
|
||||||
@@ -418,19 +398,17 @@ pagerhist_write(HistoryBuf *self, PyObject *what) {
|
|||||||
static PyObject*
|
static PyObject*
|
||||||
pagerhist_as_bytes(HistoryBuf *self, PyObject *args UNUSED) {
|
pagerhist_as_bytes(HistoryBuf *self, PyObject *args UNUSED) {
|
||||||
PagerHistoryBuf *ph = self->pagerhist;
|
PagerHistoryBuf *ph = self->pagerhist;
|
||||||
if (!ph || !ph->length) return PyBytes_FromStringAndSize("", 0);
|
if (!ph || !ringbuf_bytes_used(ph->ringbuf)) return PyBytes_FromStringAndSize("", 0);
|
||||||
pagerhist_ensure_start_is_valid_utf8(ph);
|
pagerhist_ensure_start_is_valid_utf8(ph);
|
||||||
if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum);
|
if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum);
|
||||||
|
|
||||||
Line l = {.xnum=self->xnum}; get_line(self, 0, &l);
|
Line l = {.xnum=self->xnum}; get_line(self, 0, &l);
|
||||||
size_t sz = ph->length;
|
size_t sz = ringbuf_bytes_used(ph->ringbuf);
|
||||||
if (!l.continued) sz += 1;
|
if (!l.continued) sz += 1;
|
||||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, sz);
|
PyObject *ans = PyBytes_FromStringAndSize(NULL, sz);
|
||||||
if (!ans) return NULL;
|
if (!ans) return NULL;
|
||||||
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
|
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
|
||||||
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
|
ringbuf_memcpy_from(buf, ph->ringbuf, sz);
|
||||||
if (copied) memcpy(buf, ph->buffer + ph->start, copied);
|
|
||||||
if (copied < ph->length) memcpy(buf + copied, ph->buffer, (ph->length - copied));
|
|
||||||
if (!l.continued) buf[sz-1] = '\n';
|
if (!l.continued) buf[sz-1] = '\n';
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
@@ -561,7 +539,7 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other, ANSIBuf *as_ansi_buf
|
|||||||
other->count = self->count; other->start_of_data = self->start_of_data;
|
other->count = self->count; other->start_of_data = self->start_of_data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (other->pagerhist && other->xnum != self->xnum && other->pagerhist->length)
|
if (other->pagerhist && other->xnum != self->xnum && ringbuf_bytes_used(other->pagerhist->ringbuf))
|
||||||
other->pagerhist->rewrap_needed = true;
|
other->pagerhist->rewrap_needed = true;
|
||||||
other->count = 0; other->start_of_data = 0;
|
other->count = 0; other->start_of_data = 0;
|
||||||
index_type x = 0, y = 0;
|
index_type x = 0, y = 0;
|
||||||
|
|||||||
41
kitty/key_encoding.py
generated
41
kitty/key_encoding.py
generated
@@ -417,6 +417,10 @@ def symbolic_name(glfw_name: str) -> str:
|
|||||||
return glfw_name[9:].replace('_', ' ')
|
return glfw_name[9:].replace('_', ' ')
|
||||||
|
|
||||||
|
|
||||||
|
def glfw_key_name(symbolic_name: str) -> str:
|
||||||
|
return 'GLFW_KEY_' + symbolic_name.replace(' ', '_')
|
||||||
|
|
||||||
|
|
||||||
def update_encoding() -> None:
|
def update_encoding() -> None:
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -424,7 +428,7 @@ def update_encoding() -> None:
|
|||||||
ans = ENCODING
|
ans = ENCODING
|
||||||
key_map = {}
|
key_map = {}
|
||||||
i = len(ans)
|
i = len(ans)
|
||||||
for k in sorted(keys, key=lambda k: getattr(defines, k)):
|
for k in sorted(keys, key=lambda k: int(getattr(defines, k))):
|
||||||
if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'):
|
if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'):
|
||||||
continue
|
continue
|
||||||
val = getattr(defines, k)
|
val = getattr(defines, k)
|
||||||
@@ -492,9 +496,9 @@ del key_name, enc
|
|||||||
|
|
||||||
|
|
||||||
def decode_key_event(text: str) -> KeyEvent:
|
def decode_key_event(text: str) -> KeyEvent:
|
||||||
typ = type_map[text[1]]
|
typ = type_map[text[0]]
|
||||||
mods = mod_map[text[2]]
|
mods = mod_map[text[1]]
|
||||||
key = key_rmap[text[3:5]]
|
key = key_rmap[text[2:4]]
|
||||||
return KeyEvent(typ, mods, key)
|
return KeyEvent(typ, mods, key)
|
||||||
|
|
||||||
|
|
||||||
@@ -503,3 +507,32 @@ def encode_key_event(key_event: KeyEvent) -> str:
|
|||||||
mods = rmod_map[key_event.mods]
|
mods = rmod_map[key_event.mods]
|
||||||
key = ENCODING[key_event.key.replace('_', ' ')]
|
key = ENCODING[key_event.key.replace('_', ' ')]
|
||||||
return typ + mods + key
|
return typ + mods + key
|
||||||
|
|
||||||
|
|
||||||
|
class WindowSystemKeyEvent(NamedTuple):
|
||||||
|
code: int
|
||||||
|
mods: int
|
||||||
|
action: int
|
||||||
|
|
||||||
|
|
||||||
|
def decode_key_event_as_window_system_key(text: str) -> Optional[WindowSystemKeyEvent]:
|
||||||
|
k = decode_key_event(text)
|
||||||
|
glfw_name = glfw_key_name(k.key)
|
||||||
|
glfw_code = getattr(defines, glfw_name, None)
|
||||||
|
if glfw_code is None:
|
||||||
|
return None
|
||||||
|
action = defines.GLFW_PRESS
|
||||||
|
if k.type is RELEASE:
|
||||||
|
action = defines.GLFW_RELEASE
|
||||||
|
elif k.type is REPEAT:
|
||||||
|
action = defines.GLFW_REPEAT
|
||||||
|
mods = 0
|
||||||
|
if k.mods & CTRL:
|
||||||
|
mods |= defines.GLFW_MOD_CONTROL
|
||||||
|
if k.mods & ALT:
|
||||||
|
mods |= defines.GLFW_MOD_ALT
|
||||||
|
if k.mods & SUPER:
|
||||||
|
mods |= defines.GLFW_MOD_SUPER
|
||||||
|
if k.mods & SHIFT:
|
||||||
|
mods |= defines.GLFW_MOD_SHIFT
|
||||||
|
return WindowSystemKeyEvent(glfw_code, mods, action)
|
||||||
|
|||||||
@@ -245,10 +245,8 @@ empty:
|
|||||||
|
|
||||||
error:
|
error:
|
||||||
Py_XDECREF(py_ret);
|
Py_XDECREF(py_ret);
|
||||||
if (procargs != NULL)
|
free(procargs);
|
||||||
free(procargs);
|
free(procenv);
|
||||||
if (procenv != NULL)
|
|
||||||
free(procargs);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,6 @@ def expand_listen_on(listen_on: str, from_config_file: bool) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def setup_environment(opts: OptionsStub, cli_opts: CLIOptions) -> None:
|
def setup_environment(opts: OptionsStub, cli_opts: CLIOptions) -> None:
|
||||||
extra_env = opts.env.copy()
|
|
||||||
if opts.editor == '.':
|
if opts.editor == '.':
|
||||||
editor = get_editor_from_env(os.environ)
|
editor = get_editor_from_env(os.environ)
|
||||||
if not editor:
|
if not editor:
|
||||||
@@ -266,7 +265,7 @@ def setup_environment(opts: OptionsStub, cli_opts: CLIOptions) -> None:
|
|||||||
if cli_opts.listen_on and opts.allow_remote_control != 'n':
|
if cli_opts.listen_on and opts.allow_remote_control != 'n':
|
||||||
cli_opts.listen_on = expand_listen_on(cli_opts.listen_on, from_config_file)
|
cli_opts.listen_on = expand_listen_on(cli_opts.listen_on, from_config_file)
|
||||||
os.environ['KITTY_LISTEN_ON'] = cli_opts.listen_on
|
os.environ['KITTY_LISTEN_ON'] = cli_opts.listen_on
|
||||||
set_default_env(extra_env)
|
set_default_env(opts.env.copy())
|
||||||
|
|
||||||
|
|
||||||
def set_locale() -> None:
|
def set_locale() -> None:
|
||||||
|
|||||||
@@ -195,6 +195,14 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_
|
|||||||
|
|
||||||
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
|
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
set_mouse_cursor_when_dragging(void) {
|
||||||
|
if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) {
|
||||||
|
mouse_cursor_shape = OPT(pointer_shape_when_dragging);
|
||||||
|
set_mouse_cursor(mouse_cursor_shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
||||||
Screen *screen = w->render_data.screen;
|
Screen *screen = w->render_data.screen;
|
||||||
@@ -213,6 +221,7 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
|||||||
} else if (screen->selections.in_progress) {
|
} else if (screen->selections.in_progress) {
|
||||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false);
|
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false);
|
||||||
}
|
}
|
||||||
|
set_mouse_cursor_when_dragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@@ -250,6 +259,7 @@ extend_selection(Window *w, bool ended) {
|
|||||||
if (screen_has_selection(screen)) {
|
if (screen_has_selection(screen)) {
|
||||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false);
|
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false);
|
||||||
}
|
}
|
||||||
|
set_mouse_cursor_when_dragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -265,7 +275,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
|
|||||||
// we deliberately allow non-continued lines as some programs, like
|
// we deliberately allow non-continued lines as some programs, like
|
||||||
// mutt split URLs with newlines at line boundaries
|
// mutt split URLs with newlines at line boundaries
|
||||||
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars);
|
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars);
|
||||||
if (!new_x) break;
|
if (!new_x && !line_startswith_url_chars(line)) break;
|
||||||
*y += 1; *x = new_x;
|
*y += 1; *x = new_x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,7 +305,7 @@ get_url_sentinel(Line *line, index_type url_start) {
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
set_mouse_cursor_for_screen(Screen *screen) {
|
set_mouse_cursor_for_screen(Screen *screen) {
|
||||||
mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? BEAM : OPT(pointer_shape_when_grabbed);
|
mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? OPT(default_pointer_shape): OPT(pointer_shape_when_grabbed);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -358,7 +368,7 @@ HANDLER(handle_move_event) {
|
|||||||
bool in_left_half_of_cell = false;
|
bool in_left_half_of_cell = false;
|
||||||
if (!cell_for_pos(w, &x, &y, &in_left_half_of_cell, global_state.callback_os_window)) return;
|
if (!cell_for_pos(w, &x, &y, &in_left_half_of_cell, global_state.callback_os_window)) return;
|
||||||
Screen *screen = w->render_data.screen;
|
Screen *screen = w->render_data.screen;
|
||||||
detect_url(screen, x, y);
|
if(OPT(detect_urls)) detect_url(screen, x, y);
|
||||||
bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y;
|
bool mouse_cell_changed = x != w->mouse_pos.cell_x || y != w->mouse_pos.cell_y;
|
||||||
bool cell_half_changed = in_left_half_of_cell != w->mouse_pos.in_left_half_of_cell;
|
bool cell_half_changed = in_left_half_of_cell != w->mouse_pos.in_left_half_of_cell;
|
||||||
w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y;
|
w->mouse_pos.cell_x = x; w->mouse_pos.cell_y = y;
|
||||||
@@ -397,6 +407,7 @@ multi_click(Window *w, unsigned int count) {
|
|||||||
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode);
|
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode);
|
||||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true);
|
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true);
|
||||||
}
|
}
|
||||||
|
set_mouse_cursor_when_dragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline double
|
static inline double
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from .config import KeyAction, parse_key_action
|
|||||||
from .constants import config_dir
|
from .constants import config_dir
|
||||||
from .typing import MatchType
|
from .typing import MatchType
|
||||||
from .utils import expandvars, log_error
|
from .utils import expandvars, log_error
|
||||||
|
from .guess_mime_type import guess_type
|
||||||
|
|
||||||
|
|
||||||
class MatchCriteria(NamedTuple):
|
class MatchCriteria(NamedTuple):
|
||||||
@@ -75,12 +76,8 @@ def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc:
|
|||||||
|
|
||||||
if mc.type == 'mime':
|
if mc.type == 'mime':
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from mimetypes import guess_type
|
mt = guess_type(unquoted_path)
|
||||||
try:
|
if not mt:
|
||||||
mt = guess_type(unquoted_path)[0]
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
if mt is None:
|
|
||||||
return False
|
return False
|
||||||
mt = mt.lower()
|
mt = mt.lower()
|
||||||
for mpat in mc.value.split(','):
|
for mpat in mc.value.split(','):
|
||||||
@@ -161,8 +158,14 @@ def actions_for_url_from_list(url: str, actions: Iterable[OpenAction]) -> Genera
|
|||||||
}
|
}
|
||||||
|
|
||||||
def expand(x: Any) -> Any:
|
def expand(x: Any) -> Any:
|
||||||
|
as_bytes = isinstance(x, bytes)
|
||||||
|
if as_bytes:
|
||||||
|
x = x.decode('utf-8')
|
||||||
if isinstance(x, str):
|
if isinstance(x, str):
|
||||||
return expandvars(x, env, fallback_to_os_env=False)
|
ans = expandvars(x, env, fallback_to_os_env=False)
|
||||||
|
if as_bytes:
|
||||||
|
return ans.encode('utf-8')
|
||||||
|
return ans
|
||||||
return x
|
return x
|
||||||
|
|
||||||
for action in actions:
|
for action in actions:
|
||||||
|
|||||||
46
kitty/parse-graphics-command.h
generated
46
kitty/parse-graphics-command.h
generated
@@ -1,4 +1,5 @@
|
|||||||
// This file is generated by ./gen-apc-parsers.py do not edit!
|
// This file is generated by /home/kovid/work/kitty/./gen-apc-parsers.py do not
|
||||||
|
// edit!
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@@ -23,6 +24,9 @@ static inline void parse_graphics_code(Screen *screen,
|
|||||||
format = 'f',
|
format = 'f',
|
||||||
more = 'm',
|
more = 'm',
|
||||||
id = 'i',
|
id = 'i',
|
||||||
|
image_number = 'I',
|
||||||
|
placement_id = 'p',
|
||||||
|
quiet = 'q',
|
||||||
width = 'w',
|
width = 'w',
|
||||||
height = 'h',
|
height = 'h',
|
||||||
x_offset = 'x',
|
x_offset = 'x',
|
||||||
@@ -69,6 +73,15 @@ static inline void parse_graphics_code(Screen *screen,
|
|||||||
case id:
|
case id:
|
||||||
value_state = UINT;
|
value_state = UINT;
|
||||||
break;
|
break;
|
||||||
|
case image_number:
|
||||||
|
value_state = UINT;
|
||||||
|
break;
|
||||||
|
case placement_id:
|
||||||
|
value_state = UINT;
|
||||||
|
break;
|
||||||
|
case quiet:
|
||||||
|
value_state = UINT;
|
||||||
|
break;
|
||||||
case width:
|
case width:
|
||||||
value_state = UINT;
|
value_state = UINT;
|
||||||
break;
|
break;
|
||||||
@@ -131,8 +144,8 @@ static inline void parse_graphics_code(Screen *screen,
|
|||||||
|
|
||||||
case action: {
|
case action: {
|
||||||
g.action = screen->parser_buf[pos++] & 0xff;
|
g.action = screen->parser_buf[pos++] & 0xff;
|
||||||
if (g.action != 't' && g.action != 'd' && g.action != 'p' &&
|
if (g.action != 'T' && g.action != 'q' && g.action != 'd' &&
|
||||||
g.action != 'q' && g.action != 'T') {
|
g.action != 'p' && g.action != 't') {
|
||||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||||
"value for action: 0x%x",
|
"value for action: 0x%x",
|
||||||
g.action);
|
g.action);
|
||||||
@@ -142,14 +155,15 @@ static inline void parse_graphics_code(Screen *screen,
|
|||||||
|
|
||||||
case delete_action: {
|
case delete_action: {
|
||||||
g.delete_action = screen->parser_buf[pos++] & 0xff;
|
g.delete_action = screen->parser_buf[pos++] & 0xff;
|
||||||
if (g.delete_action != 'X' && g.delete_action != 'y' &&
|
if (g.delete_action != 'C' && g.delete_action != 'z' &&
|
||||||
g.delete_action != 'i' && g.delete_action != 'I' &&
|
g.delete_action != 'y' && g.delete_action != 'X' &&
|
||||||
g.delete_action != 'A' && g.delete_action != 'p' &&
|
|
||||||
g.delete_action != 'Y' && g.delete_action != 'z' &&
|
|
||||||
g.delete_action != 'a' && g.delete_action != 'P' &&
|
g.delete_action != 'a' && g.delete_action != 'P' &&
|
||||||
g.delete_action != 'x' && g.delete_action != 'q' &&
|
g.delete_action != 'i' && g.delete_action != 'c' &&
|
||||||
g.delete_action != 'Z' && g.delete_action != 'Q' &&
|
g.delete_action != 'N' && g.delete_action != 'I' &&
|
||||||
g.delete_action != 'c' && g.delete_action != 'C') {
|
g.delete_action != 'p' && g.delete_action != 'q' &&
|
||||||
|
g.delete_action != 'Q' && g.delete_action != 'Z' &&
|
||||||
|
g.delete_action != 'A' && g.delete_action != 'n' &&
|
||||||
|
g.delete_action != 'x' && g.delete_action != 'Y') {
|
||||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||||
"value for delete_action: 0x%x",
|
"value for delete_action: 0x%x",
|
||||||
g.delete_action);
|
g.delete_action);
|
||||||
@@ -159,7 +173,7 @@ static inline void parse_graphics_code(Screen *screen,
|
|||||||
|
|
||||||
case transmission_type: {
|
case transmission_type: {
|
||||||
g.transmission_type = screen->parser_buf[pos++] & 0xff;
|
g.transmission_type = screen->parser_buf[pos++] & 0xff;
|
||||||
if (g.transmission_type != 'f' && g.transmission_type != 'd' &&
|
if (g.transmission_type != 'd' && g.transmission_type != 'f' &&
|
||||||
g.transmission_type != 's' && g.transmission_type != 't') {
|
g.transmission_type != 's' && g.transmission_type != 't') {
|
||||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||||
"value for transmission_type: 0x%x",
|
"value for transmission_type: 0x%x",
|
||||||
@@ -233,6 +247,9 @@ static inline void parse_graphics_code(Screen *screen,
|
|||||||
U(format);
|
U(format);
|
||||||
U(more);
|
U(more);
|
||||||
U(id);
|
U(id);
|
||||||
|
U(image_number);
|
||||||
|
U(placement_id);
|
||||||
|
U(quiet);
|
||||||
U(width);
|
U(width);
|
||||||
U(height);
|
U(height);
|
||||||
U(x_offset);
|
U(x_offset);
|
||||||
@@ -303,11 +320,14 @@ static inline void parse_graphics_code(Screen *screen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
REPORT_VA_COMMAND(
|
REPORT_VA_COMMAND(
|
||||||
"s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si sI} y#",
|
"s {sc sc sc sc sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI sI si "
|
||||||
|
"sI} y#",
|
||||||
"graphics_command", "action", g.action, "delete_action", g.delete_action,
|
"graphics_command", "action", g.action, "delete_action", g.delete_action,
|
||||||
"transmission_type", g.transmission_type, "compressed", g.compressed,
|
"transmission_type", g.transmission_type, "compressed", g.compressed,
|
||||||
"format", (unsigned int)g.format, "more", (unsigned int)g.more, "id",
|
"format", (unsigned int)g.format, "more", (unsigned int)g.more, "id",
|
||||||
(unsigned int)g.id, "width", (unsigned int)g.width, "height",
|
(unsigned int)g.id, "image_number", (unsigned int)g.image_number,
|
||||||
|
"placement_id", (unsigned int)g.placement_id, "quiet",
|
||||||
|
(unsigned int)g.quiet, "width", (unsigned int)g.width, "height",
|
||||||
(unsigned int)g.height, "x_offset", (unsigned int)g.x_offset, "y_offset",
|
(unsigned int)g.height, "x_offset", (unsigned int)g.x_offset, "y_offset",
|
||||||
(unsigned int)g.y_offset, "data_height", (unsigned int)g.data_height,
|
(unsigned int)g.y_offset, "data_height", (unsigned int)g.data_height,
|
||||||
"data_width", (unsigned int)g.data_width, "data_sz",
|
"data_width", (unsigned int)g.data_width, "data_sz",
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ utf8(char_type codepoint) {
|
|||||||
// }}}
|
// }}}
|
||||||
|
|
||||||
// Macros {{{
|
// Macros {{{
|
||||||
#define MAX_PARAMS 256
|
|
||||||
#define IS_DIGIT \
|
#define IS_DIGIT \
|
||||||
case '0': \
|
case '0': \
|
||||||
case '1': \
|
case '1': \
|
||||||
@@ -465,7 +464,10 @@ repr_csi_params(unsigned int *params, unsigned int num_params) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
#ifdef DUMP_COMMANDS
|
||||||
|
static
|
||||||
|
#endif
|
||||||
|
void
|
||||||
parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject DUMP_UNUSED *dump_callback, const char *report_name DUMP_UNUSED, Region *region) {
|
parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject DUMP_UNUSED *dump_callback, const char *report_name DUMP_UNUSED, Region *region) {
|
||||||
enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 };
|
enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 };
|
||||||
enum State state = START;
|
enum State state = START;
|
||||||
|
|||||||
@@ -5,9 +5,13 @@
|
|||||||
import base64
|
import base64
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import TYPE_CHECKING, Dict, Generator, Optional
|
from typing import TYPE_CHECKING, Dict, Generator, List, Optional
|
||||||
|
|
||||||
from kitty.config import parse_send_text_bytes
|
from kitty.config import parse_send_text_bytes
|
||||||
|
from kitty.key_encoding import (
|
||||||
|
WindowSystemKeyEvent, decode_key_event_as_window_system_key
|
||||||
|
)
|
||||||
|
from kitty.keys import interpret_key_event
|
||||||
|
|
||||||
from .base import (
|
from .base import (
|
||||||
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError,
|
MATCH_TAB_OPTION, MATCH_WINDOW_OPTION, ArgsType, Boss, MatchError,
|
||||||
@@ -24,6 +28,8 @@ class SendText(RemoteCommand):
|
|||||||
data+: The data being sent. Can be either: text: followed by text or base64: followed by standard base64 encoded bytes
|
data+: The data being sent. Can be either: text: followed by text or base64: followed by standard base64 encoded bytes
|
||||||
match: A string indicating the window to send text to
|
match: A string indicating the window to send text to
|
||||||
match_tab: A string indicating the tab to send text to
|
match_tab: A string indicating the tab to send text to
|
||||||
|
all: A boolean indicating all windows should be matched.
|
||||||
|
exclude_active: A boolean that prevents sending text to the active window
|
||||||
'''
|
'''
|
||||||
short_desc = 'Send arbitrary text to specified windows'
|
short_desc = 'Send arbitrary text to specified windows'
|
||||||
desc = (
|
desc = (
|
||||||
@@ -34,6 +40,11 @@ class SendText(RemoteCommand):
|
|||||||
' only the currently active window.'
|
' only the currently active window.'
|
||||||
)
|
)
|
||||||
options_spec = MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n
|
options_spec = MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n
|
||||||
|
--all
|
||||||
|
type=bool-set
|
||||||
|
Match all windows.
|
||||||
|
|
||||||
|
|
||||||
--stdin
|
--stdin
|
||||||
type=bool-set
|
type=bool-set
|
||||||
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
|
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
|
||||||
@@ -43,16 +54,22 @@ not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end
|
|||||||
--from-file
|
--from-file
|
||||||
Path to a file whose contents you wish to send. Note that in this case the file contents
|
Path to a file whose contents you wish to send. Note that in this case the file contents
|
||||||
are sent as is, not interpreted for escapes.
|
are sent as is, not interpreted for escapes.
|
||||||
|
|
||||||
|
|
||||||
|
--exclude-active
|
||||||
|
type=bool-set
|
||||||
|
Do not send text to the active window, even if it is one of the matched windows.
|
||||||
'''
|
'''
|
||||||
no_response = True
|
no_response = True
|
||||||
argspec = '[TEXT TO SEND]'
|
argspec = '[TEXT TO SEND]'
|
||||||
|
|
||||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||||
limit = 1024
|
limit = 1024
|
||||||
ret = {'match': opts.match, 'data': '', 'match_tab': opts.match_tab}
|
ret = {'match': opts.match, 'data': '', 'match_tab': opts.match_tab, 'all': opts.all, 'exclude_active': opts.exclude_active}
|
||||||
|
|
||||||
def pipe() -> Generator[Dict, None, None]:
|
def pipe() -> Generator[Dict, None, None]:
|
||||||
if sys.stdin.isatty():
|
if sys.stdin.isatty():
|
||||||
|
ret['exclude_active'] = True
|
||||||
import select
|
import select
|
||||||
fd = sys.stdin.fileno()
|
fd = sys.stdin.fileno()
|
||||||
keep_going = True
|
keep_going = True
|
||||||
@@ -109,28 +126,40 @@ are sent as is, not interpreted for escapes.
|
|||||||
return chain()
|
return chain()
|
||||||
|
|
||||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||||
windows = [boss.active_window]
|
if payload_get('all'):
|
||||||
match = payload_get('match')
|
windows: List[Optional[Window]] = list(boss.all_windows)
|
||||||
if match:
|
else:
|
||||||
windows = list(boss.match_windows(match))
|
windows = [boss.active_window]
|
||||||
mt = payload_get('match_tab')
|
match = payload_get('match')
|
||||||
if mt:
|
if match:
|
||||||
windows = []
|
windows = list(boss.match_windows(match))
|
||||||
tabs = tuple(boss.match_tabs(mt))
|
mt = payload_get('match_tab')
|
||||||
if not tabs:
|
if mt:
|
||||||
raise MatchError(payload_get('match_tab'), 'tabs')
|
windows = []
|
||||||
for tab in tabs:
|
tabs = tuple(boss.match_tabs(mt))
|
||||||
windows += tuple(tab)
|
if not tabs:
|
||||||
|
raise MatchError(payload_get('match_tab'), 'tabs')
|
||||||
|
for tab in tabs:
|
||||||
|
windows += tuple(tab)
|
||||||
encoding, _, q = payload_get('data').partition(':')
|
encoding, _, q = payload_get('data').partition(':')
|
||||||
if encoding == 'text':
|
if encoding == 'text':
|
||||||
data = q.encode('utf-8')
|
data = q.encode('utf-8')
|
||||||
elif encoding == 'base64':
|
elif encoding == 'base64':
|
||||||
data = base64.standard_b64decode(q)
|
data = base64.standard_b64decode(q)
|
||||||
|
elif encoding == 'kitty-key':
|
||||||
|
data = decode_key_event_as_window_system_key(q)
|
||||||
else:
|
else:
|
||||||
raise TypeError(f'Invalid encoding for send-text data: {encoding}')
|
raise TypeError(f'Invalid encoding for send-text data: {encoding}')
|
||||||
|
exclude_active = payload_get('exclude_active')
|
||||||
for window in windows:
|
for window in windows:
|
||||||
if window is not None:
|
if window is not None:
|
||||||
window.write_to_child(data)
|
if not exclude_active or window is not boss.active_window:
|
||||||
|
if isinstance(data, WindowSystemKeyEvent):
|
||||||
|
kdata = interpret_key_event(data.code, 0, data.mods, window, data.action)
|
||||||
|
if kdata:
|
||||||
|
window.write_to_child(kdata)
|
||||||
|
else:
|
||||||
|
window.write_to_child(data)
|
||||||
|
|
||||||
|
|
||||||
send_text = SendText()
|
send_text = SendText()
|
||||||
|
|||||||
@@ -155,6 +155,13 @@ def parse_rc_args(args: List[str]) -> Tuple[RCOptions, List[str]]:
|
|||||||
return parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname), result_class=RCOptions)
|
return parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname), result_class=RCOptions)
|
||||||
|
|
||||||
|
|
||||||
|
def create_basic_command(name: str, payload: Any = None, no_response: bool = False) -> Dict[str, Any]:
|
||||||
|
ans = {'cmd': name, 'version': version, 'no_response': no_response}
|
||||||
|
if payload is not None:
|
||||||
|
ans['payload'] = payload
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
def main(args: List[str]) -> None:
|
def main(args: List[str]) -> None:
|
||||||
global_opts, items = parse_rc_args(args)
|
global_opts, items = parse_rc_args(args)
|
||||||
global_opts.no_command_response = None
|
global_opts.no_command_response = None
|
||||||
@@ -171,17 +178,11 @@ def main(args: List[str]) -> None:
|
|||||||
emph(cmd), ', '.join(x.replace('_', '-') for x in all_command_names())))
|
emph(cmd), ', '.join(x.replace('_', '-') for x in all_command_names())))
|
||||||
opts, items = parse_subcommand_cli(c, items)
|
opts, items = parse_subcommand_cli(c, items)
|
||||||
payload = c.message_to_kitty(global_opts, opts, items)
|
payload = c.message_to_kitty(global_opts, opts, items)
|
||||||
send = {
|
|
||||||
'cmd': cmd,
|
|
||||||
'version': version,
|
|
||||||
}
|
|
||||||
if payload is not None:
|
|
||||||
send['payload'] = payload
|
|
||||||
if global_opts.no_command_response is not None:
|
if global_opts.no_command_response is not None:
|
||||||
no_response = global_opts.no_command_response # type: ignore
|
no_response = global_opts.no_command_response # type: ignore
|
||||||
else:
|
else:
|
||||||
no_response = c.no_response
|
no_response = c.no_response
|
||||||
send['no_response'] = no_response
|
send = create_basic_command(cmd, payload=payload, no_response=no_response)
|
||||||
if not global_opts.to and 'KITTY_LISTEN_ON' in os.environ:
|
if not global_opts.to and 'KITTY_LISTEN_ON' in os.environ:
|
||||||
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
||||||
response = do_io(global_opts.to, send, no_response)
|
response = do_io(global_opts.to, send, no_response)
|
||||||
|
|||||||
394
kitty/ringbuf.c
Normal file
394
kitty/ringbuf.c
Normal file
@@ -0,0 +1,394 @@
|
|||||||
|
/*
|
||||||
|
* ringbuf.c - C ring buffer (FIFO) implementation.
|
||||||
|
*
|
||||||
|
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
|
||||||
|
*
|
||||||
|
* To the extent possible under law, the author(s) have dedicated all
|
||||||
|
* copyright and related and neighboring rights to this software to
|
||||||
|
* the public domain worldwide. This software is distributed without
|
||||||
|
* any warranty.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
* along with this software. If not, see
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef KITTY_DEBUG_BUILD
|
||||||
|
#define NDEBUG 1
|
||||||
|
#endif
|
||||||
|
#include "ringbuf.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
static inline size_t
|
||||||
|
size_t_min(size_t x, size_t y) {
|
||||||
|
return x > y ? y : x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The code is written for clarity, not cleverness or performance, and
|
||||||
|
* contains many assert()s to enforce invariant assumptions and catch
|
||||||
|
* bugs. Feel free to optimize the code and to remove asserts for use
|
||||||
|
* in your own projects, once you're comfortable that it functions as
|
||||||
|
* intended.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct ringbuf_t
|
||||||
|
{
|
||||||
|
uint8_t *buf;
|
||||||
|
uint8_t *head, *tail;
|
||||||
|
size_t size;
|
||||||
|
};
|
||||||
|
|
||||||
|
ringbuf_t
|
||||||
|
ringbuf_new(size_t capacity)
|
||||||
|
{
|
||||||
|
ringbuf_t rb = malloc(sizeof(struct ringbuf_t));
|
||||||
|
if (rb) {
|
||||||
|
|
||||||
|
/* One byte is used for detecting the full condition. */
|
||||||
|
rb->size = capacity + 1;
|
||||||
|
rb->buf = malloc(rb->size);
|
||||||
|
if (rb->buf)
|
||||||
|
ringbuf_reset(rb);
|
||||||
|
else {
|
||||||
|
free(rb);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rb;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ringbuf_buffer_size(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return rb->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ringbuf_reset(ringbuf_t rb)
|
||||||
|
{
|
||||||
|
rb->head = rb->tail = rb->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
ringbuf_free(ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
assert(rb && *rb);
|
||||||
|
free((*rb)->buf);
|
||||||
|
free(*rb);
|
||||||
|
*rb = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ringbuf_capacity(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return ringbuf_buffer_size(rb) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return a pointer to one-past-the-end of the ring buffer's
|
||||||
|
* contiguous buffer. You shouldn't normally need to use this function
|
||||||
|
* unless you're writing a new ringbuf_* function.
|
||||||
|
*/
|
||||||
|
static const uint8_t *
|
||||||
|
ringbuf_end(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return rb->buf + ringbuf_buffer_size(rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ringbuf_bytes_free(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
if (rb->head >= rb->tail)
|
||||||
|
return ringbuf_capacity(rb) - (rb->head - rb->tail);
|
||||||
|
else
|
||||||
|
return rb->tail - rb->head - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ringbuf_bytes_used(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return ringbuf_capacity(rb) - ringbuf_bytes_free(rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ringbuf_is_full(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return ringbuf_bytes_free(rb) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
ringbuf_is_empty(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return ringbuf_bytes_free(rb) == ringbuf_capacity(rb);
|
||||||
|
}
|
||||||
|
|
||||||
|
const void *
|
||||||
|
ringbuf_tail(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return rb->tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
const void *
|
||||||
|
ringbuf_head(const struct ringbuf_t *rb)
|
||||||
|
{
|
||||||
|
return rb->head;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a ring buffer rb and a pointer to a location within its
|
||||||
|
* contiguous buffer, return the a pointer to the next logical
|
||||||
|
* location in the ring buffer.
|
||||||
|
*/
|
||||||
|
static uint8_t *
|
||||||
|
ringbuf_nextp(ringbuf_t rb, const uint8_t *p)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* The assert guarantees the expression (++p - rb->buf) is
|
||||||
|
* non-negative; therefore, the modulus operation is safe and
|
||||||
|
* portable.
|
||||||
|
*/
|
||||||
|
assert((p >= rb->buf) && (p < ringbuf_end(rb)));
|
||||||
|
return rb->buf + ((++p - rb->buf) % ringbuf_buffer_size(rb));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset)
|
||||||
|
{
|
||||||
|
const uint8_t *bufend = ringbuf_end(rb);
|
||||||
|
size_t bytes_used = ringbuf_bytes_used(rb);
|
||||||
|
if (offset >= bytes_used)
|
||||||
|
return bytes_used;
|
||||||
|
|
||||||
|
const uint8_t *start = rb->buf +
|
||||||
|
(((rb->tail - rb->buf) + offset) % ringbuf_buffer_size(rb));
|
||||||
|
assert(bufend > start);
|
||||||
|
size_t n = size_t_min(bufend - start, bytes_used - offset);
|
||||||
|
const uint8_t *found = memchr(start, c, n);
|
||||||
|
if (found)
|
||||||
|
return offset + (found - start);
|
||||||
|
else
|
||||||
|
return ringbuf_findchr(rb, c, offset + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ringbuf_memset(ringbuf_t dst, int c, size_t len)
|
||||||
|
{
|
||||||
|
const uint8_t *bufend = ringbuf_end(dst);
|
||||||
|
size_t nwritten = 0;
|
||||||
|
size_t count = size_t_min(len, ringbuf_buffer_size(dst));
|
||||||
|
int overflow = count > ringbuf_bytes_free(dst);
|
||||||
|
|
||||||
|
while (nwritten != count) {
|
||||||
|
|
||||||
|
/* don't copy beyond the end of the buffer */
|
||||||
|
assert(bufend > dst->head);
|
||||||
|
size_t n = size_t_min(bufend - dst->head, count - nwritten);
|
||||||
|
memset(dst->head, c, n);
|
||||||
|
dst->head += n;
|
||||||
|
nwritten += n;
|
||||||
|
|
||||||
|
/* wrap? */
|
||||||
|
if (dst->head == bufend)
|
||||||
|
dst->head = dst->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
dst->tail = ringbuf_nextp(dst, dst->head);
|
||||||
|
assert(ringbuf_is_full(dst));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nwritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count)
|
||||||
|
{
|
||||||
|
const uint8_t *u8src = src;
|
||||||
|
const uint8_t *bufend = ringbuf_end(dst);
|
||||||
|
int overflow = count > ringbuf_bytes_free(dst);
|
||||||
|
size_t nread = 0;
|
||||||
|
|
||||||
|
while (nread != count) {
|
||||||
|
/* don't copy beyond the end of the buffer */
|
||||||
|
assert(bufend > dst->head);
|
||||||
|
size_t n = size_t_min(bufend - dst->head, count - nread);
|
||||||
|
memcpy(dst->head, u8src + nread, n);
|
||||||
|
dst->head += n;
|
||||||
|
nread += n;
|
||||||
|
|
||||||
|
/* wrap? */
|
||||||
|
if (dst->head == bufend)
|
||||||
|
dst->head = dst->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
dst->tail = ringbuf_nextp(dst, dst->head);
|
||||||
|
assert(ringbuf_is_full(dst));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst->head;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
ringbuf_read(int fd, ringbuf_t rb, size_t count)
|
||||||
|
{
|
||||||
|
const uint8_t *bufend = ringbuf_end(rb);
|
||||||
|
size_t nfree = ringbuf_bytes_free(rb);
|
||||||
|
|
||||||
|
/* don't write beyond the end of the buffer */
|
||||||
|
assert(bufend > rb->head);
|
||||||
|
count = size_t_min(bufend - rb->head, count);
|
||||||
|
ssize_t n = read(fd, rb->head, count);
|
||||||
|
if (n > 0) {
|
||||||
|
assert(rb->head + n <= bufend);
|
||||||
|
rb->head += n;
|
||||||
|
|
||||||
|
/* wrap? */
|
||||||
|
if (rb->head == bufend)
|
||||||
|
rb->head = rb->buf;
|
||||||
|
|
||||||
|
/* fix up the tail pointer if an overflow occurred */
|
||||||
|
if ((size_t)n > nfree) {
|
||||||
|
rb->tail = ringbuf_nextp(rb, rb->head);
|
||||||
|
assert(ringbuf_is_full(rb));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
ringbuf_memmove_from(void *dst, ringbuf_t src, size_t count)
|
||||||
|
{
|
||||||
|
size_t bytes_used = ringbuf_bytes_used(src);
|
||||||
|
if (count > bytes_used)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint8_t *u8dst = dst;
|
||||||
|
const uint8_t *bufend = ringbuf_end(src);
|
||||||
|
size_t nwritten = 0;
|
||||||
|
while (nwritten != count) {
|
||||||
|
assert(bufend > src->tail);
|
||||||
|
size_t n = size_t_min(bufend - src->tail, count - nwritten);
|
||||||
|
memcpy(u8dst + nwritten, src->tail, n);
|
||||||
|
src->tail += n;
|
||||||
|
nwritten += n;
|
||||||
|
|
||||||
|
/* wrap ? */
|
||||||
|
if (src->tail == bufend)
|
||||||
|
src->tail = src->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(count + ringbuf_bytes_used(src) == bytes_used);
|
||||||
|
return src->tail;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char
|
||||||
|
ringbuf_move_char(ringbuf_t src) {
|
||||||
|
assert(!ringbuf_is_empty(src));
|
||||||
|
const uint8_t *bufend = ringbuf_end(src);
|
||||||
|
assert(bufend > src->tail);
|
||||||
|
uint8_t ans = *src->tail;
|
||||||
|
src->tail += 1;
|
||||||
|
if (src->tail == bufend)
|
||||||
|
src->tail = src->buf;
|
||||||
|
return ans;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
ringbuf_memcpy_from(void *dst, const ringbuf_t src, size_t count)
|
||||||
|
{
|
||||||
|
size_t bytes_used = ringbuf_bytes_used(src);
|
||||||
|
if (count > bytes_used) count = bytes_used;
|
||||||
|
|
||||||
|
uint8_t *u8dst = dst;
|
||||||
|
const uint8_t *bufend = ringbuf_end(src);
|
||||||
|
size_t nwritten = 0;
|
||||||
|
const uint8_t* tail = src->tail;
|
||||||
|
while (nwritten != count) {
|
||||||
|
assert(bufend > tail);
|
||||||
|
size_t n = size_t_min(bufend - tail, count - nwritten);
|
||||||
|
memcpy(u8dst + nwritten, tail, n);
|
||||||
|
tail += n;
|
||||||
|
nwritten += n;
|
||||||
|
|
||||||
|
/* wrap ? */
|
||||||
|
if (tail == bufend)
|
||||||
|
tail = src->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(ringbuf_bytes_used(src) == bytes_used);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ssize_t
|
||||||
|
ringbuf_write(int fd, ringbuf_t rb, size_t count)
|
||||||
|
{
|
||||||
|
size_t bytes_used = ringbuf_bytes_used(rb);
|
||||||
|
if (count > bytes_used)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const uint8_t *bufend = ringbuf_end(rb);
|
||||||
|
assert(bufend > rb->head);
|
||||||
|
count = size_t_min(bufend - rb->tail, count);
|
||||||
|
ssize_t n = write(fd, rb->tail, count);
|
||||||
|
if (n > 0) {
|
||||||
|
assert(rb->tail + n <= bufend);
|
||||||
|
rb->tail += n;
|
||||||
|
|
||||||
|
/* wrap? */
|
||||||
|
if (rb->tail == bufend)
|
||||||
|
rb->tail = rb->buf;
|
||||||
|
|
||||||
|
assert(n + ringbuf_bytes_used(rb) == bytes_used);
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count)
|
||||||
|
{
|
||||||
|
size_t src_bytes_used = ringbuf_bytes_used(src);
|
||||||
|
if (count > src_bytes_used)
|
||||||
|
return 0;
|
||||||
|
int overflow = count > ringbuf_bytes_free(dst);
|
||||||
|
|
||||||
|
const uint8_t *src_bufend = ringbuf_end(src);
|
||||||
|
const uint8_t *dst_bufend = ringbuf_end(dst);
|
||||||
|
size_t ncopied = 0;
|
||||||
|
while (ncopied != count) {
|
||||||
|
assert(src_bufend > src->tail);
|
||||||
|
size_t nsrc = size_t_min(src_bufend - src->tail, count - ncopied);
|
||||||
|
assert(dst_bufend > dst->head);
|
||||||
|
size_t n = size_t_min(dst_bufend - dst->head, nsrc);
|
||||||
|
memcpy(dst->head, src->tail, n);
|
||||||
|
src->tail += n;
|
||||||
|
dst->head += n;
|
||||||
|
ncopied += n;
|
||||||
|
|
||||||
|
/* wrap ? */
|
||||||
|
if (src->tail == src_bufend)
|
||||||
|
src->tail = src->buf;
|
||||||
|
if (dst->head == dst_bufend)
|
||||||
|
dst->head = dst->buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(count + ringbuf_bytes_used(src) == src_bytes_used);
|
||||||
|
|
||||||
|
if (overflow) {
|
||||||
|
dst->tail = ringbuf_nextp(dst, dst->head);
|
||||||
|
assert(ringbuf_is_full(dst));
|
||||||
|
}
|
||||||
|
|
||||||
|
return dst->head;
|
||||||
|
}
|
||||||
252
kitty/ringbuf.h
Normal file
252
kitty/ringbuf.h
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
#pragma once
|
||||||
|
/*
|
||||||
|
* ringbuf.h - C ring buffer (FIFO) interface.
|
||||||
|
*
|
||||||
|
* Written in 2011 by Drew Hess <dhess-src@bothan.net>.
|
||||||
|
*
|
||||||
|
* To the extent possible under law, the author(s) have dedicated all
|
||||||
|
* copyright and related and neighboring rights to this software to
|
||||||
|
* the public domain worldwide. This software is distributed without
|
||||||
|
* any warranty.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the CC0 Public Domain Dedication
|
||||||
|
* along with this software. If not, see
|
||||||
|
* <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A byte-addressable ring buffer FIFO implementation.
|
||||||
|
*
|
||||||
|
* The ring buffer's head pointer points to the starting location
|
||||||
|
* where data should be written when copying data *into* the buffer
|
||||||
|
* (e.g., with ringbuf_read). The ring buffer's tail pointer points to
|
||||||
|
* the starting location where data should be read when copying data
|
||||||
|
* *from* the buffer (e.g., with ringbuf_write).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
typedef struct ringbuf_t *ringbuf_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a new ring buffer with the given capacity (usable
|
||||||
|
* bytes). Note that the actual internal buffer size may be one or
|
||||||
|
* more bytes larger than the usable capacity, for bookkeeping.
|
||||||
|
*
|
||||||
|
* Returns the new ring buffer object, or 0 if there's not enough
|
||||||
|
* memory to fulfill the request for the given capacity.
|
||||||
|
*/
|
||||||
|
ringbuf_t
|
||||||
|
ringbuf_new(size_t capacity);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The size of the internal buffer, in bytes. One or more bytes may be
|
||||||
|
* unusable in order to distinguish the "buffer full" state from the
|
||||||
|
* "buffer empty" state.
|
||||||
|
*
|
||||||
|
* For the usable capacity of the ring buffer, use the
|
||||||
|
* ringbuf_capacity function.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
ringbuf_buffer_size(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Deallocate a ring buffer, and, as a side effect, set the pointer to
|
||||||
|
* 0.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ringbuf_free(ringbuf_t *rb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reset a ring buffer to its initial state (empty).
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
ringbuf_reset(ringbuf_t rb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The usable capacity of the ring buffer, in bytes. Note that this
|
||||||
|
* value may be less than the ring buffer's internal buffer size, as
|
||||||
|
* returned by ringbuf_buffer_size.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
ringbuf_capacity(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The number of free/available bytes in the ring buffer. This value
|
||||||
|
* is never larger than the ring buffer's usable capacity.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
ringbuf_bytes_free(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The number of bytes currently being used in the ring buffer. This
|
||||||
|
* value is never larger than the ring buffer's usable capacity.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
ringbuf_bytes_used(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
int
|
||||||
|
ringbuf_is_full(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
int
|
||||||
|
ringbuf_is_empty(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Const access to the head and tail pointers of the ring buffer.
|
||||||
|
*/
|
||||||
|
const void *
|
||||||
|
ringbuf_tail(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
const void *
|
||||||
|
ringbuf_head(const struct ringbuf_t *rb);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Locate the first occurrence of character c (converted to an
|
||||||
|
* unsigned char) in ring buffer rb, beginning the search at offset
|
||||||
|
* bytes from the ring buffer's tail pointer. The function returns the
|
||||||
|
* offset of the character from the ring buffer's tail pointer, if
|
||||||
|
* found. If c does not occur in the ring buffer, the function returns
|
||||||
|
* the number of bytes used in the ring buffer.
|
||||||
|
*
|
||||||
|
* Note that the offset parameter and the returned offset are logical
|
||||||
|
* offsets from the tail pointer, not necessarily linear offsets.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
ringbuf_findchr(const struct ringbuf_t *rb, int c, size_t offset);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Beginning at ring buffer dst's head pointer, fill the ring buffer
|
||||||
|
* with a repeating sequence of len bytes, each of value c (converted
|
||||||
|
* to an unsigned char). len can be as large as you like, but the
|
||||||
|
* function will never write more than ringbuf_buffer_size(dst) bytes
|
||||||
|
* in a single invocation, since that size will cause all bytes in the
|
||||||
|
* ring buffer to be written exactly once each.
|
||||||
|
*
|
||||||
|
* Note that if len is greater than the number of free bytes in dst,
|
||||||
|
* the ring buffer will overflow. When an overflow occurs, the state
|
||||||
|
* of the ring buffer is guaranteed to be consistent, including the
|
||||||
|
* head and tail pointers; old data will simply be overwritten in FIFO
|
||||||
|
* fashion, as needed. However, note that, if calling the function
|
||||||
|
* results in an overflow, the value of the ring buffer's tail pointer
|
||||||
|
* may be different than it was before the function was called.
|
||||||
|
*
|
||||||
|
* Returns the actual number of bytes written to dst: len, if
|
||||||
|
* len < ringbuf_buffer_size(dst), else ringbuf_buffer_size(dst).
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
ringbuf_memset(ringbuf_t dst, int c, size_t len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy n bytes from a contiguous memory area src into the ring buffer
|
||||||
|
* dst. Returns the ring buffer's new head pointer.
|
||||||
|
*
|
||||||
|
* It is possible to copy more data from src than is available in the
|
||||||
|
* buffer; i.e., it's possible to overflow the ring buffer using this
|
||||||
|
* function. When an overflow occurs, the state of the ring buffer is
|
||||||
|
* guaranteed to be consistent, including the head and tail pointers;
|
||||||
|
* old data will simply be overwritten in FIFO fashion, as
|
||||||
|
* needed. However, note that, if calling the function results in an
|
||||||
|
* overflow, the value of the ring buffer's tail pointer may be
|
||||||
|
* different than it was before the function was called.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
ringbuf_memcpy_into(ringbuf_t dst, const void *src, size_t count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This convenience function calls read(2) on the file descriptor fd,
|
||||||
|
* using the ring buffer rb as the destination buffer for the read,
|
||||||
|
* and returns the value returned by read(2). It will only call
|
||||||
|
* read(2) once, and may return a short count.
|
||||||
|
*
|
||||||
|
* It is possible to read more data from the file descriptor than is
|
||||||
|
* available in the buffer; i.e., it's possible to overflow the ring
|
||||||
|
* buffer using this function. When an overflow occurs, the state of
|
||||||
|
* the ring buffer is guaranteed to be consistent, including the head
|
||||||
|
* and tail pointers: old data will simply be overwritten in FIFO
|
||||||
|
* fashion, as needed. However, note that, if calling the function
|
||||||
|
* results in an overflow, the value of the ring buffer's tail pointer
|
||||||
|
* may be different than it was before the function was called.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
ringbuf_read(int fd, ringbuf_t rb, size_t count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy n bytes from the ring buffer src, starting from its tail
|
||||||
|
* pointer, into a contiguous memory area dst. Returns the value of
|
||||||
|
* src's tail pointer after the copy is finished.
|
||||||
|
*
|
||||||
|
* Note that this copy is destructive with respect to the ring buffer:
|
||||||
|
* the n bytes copied from the ring buffer are no longer available in
|
||||||
|
* the ring buffer after the copy is complete, and the ring buffer
|
||||||
|
* will have n more free bytes than it did before the function was
|
||||||
|
* called.
|
||||||
|
*
|
||||||
|
* This function will *not* allow the ring buffer to underflow. If
|
||||||
|
* count is greater than the number of bytes used in the ring buffer,
|
||||||
|
* no bytes are copied, and the function will return 0.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
ringbuf_memmove_from(void *dst, ringbuf_t src, size_t count);
|
||||||
|
|
||||||
|
/* ringbuf_memmove_from() optimized for a single character.
|
||||||
|
* Must only be called if the ringbuf is not empty */
|
||||||
|
unsigned char
|
||||||
|
ringbuf_move_char(ringbuf_t src);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Same as ringbuf_memmove_from() except that it does not change the ringbuffer
|
||||||
|
* and returns the actual number of bytes copied, which is the minimum of ringbuf_bytes_used
|
||||||
|
* and count.
|
||||||
|
*/
|
||||||
|
size_t
|
||||||
|
ringbuf_memcpy_from(void *dst, const ringbuf_t src, size_t count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This convenience function calls write(2) on the file descriptor fd,
|
||||||
|
* using the ring buffer rb as the source buffer for writing (starting
|
||||||
|
* at the ring buffer's tail pointer), and returns the value returned
|
||||||
|
* by write(2). It will only call write(2) once, and may return a
|
||||||
|
* short count.
|
||||||
|
*
|
||||||
|
* Note that this copy is destructive with respect to the ring buffer:
|
||||||
|
* any bytes written from the ring buffer to the file descriptor are
|
||||||
|
* no longer available in the ring buffer after the copy is complete,
|
||||||
|
* and the ring buffer will have N more free bytes than it did before
|
||||||
|
* the function was called, where N is the value returned by the
|
||||||
|
* function (unless N is < 0, in which case an error occurred and no
|
||||||
|
* bytes were written).
|
||||||
|
*
|
||||||
|
* This function will *not* allow the ring buffer to underflow. If
|
||||||
|
* count is greater than the number of bytes used in the ring buffer,
|
||||||
|
* no bytes are written to the file descriptor, and the function will
|
||||||
|
* return 0.
|
||||||
|
*/
|
||||||
|
ssize_t
|
||||||
|
ringbuf_write(int fd, ringbuf_t rb, size_t count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copy count bytes from ring buffer src, starting from its tail
|
||||||
|
* pointer, into ring buffer dst. Returns dst's new head pointer after
|
||||||
|
* the copy is finished.
|
||||||
|
*
|
||||||
|
* Note that this copy is destructive with respect to the ring buffer
|
||||||
|
* src: any bytes copied from src into dst are no longer available in
|
||||||
|
* src after the copy is complete, and src will have 'count' more free
|
||||||
|
* bytes than it did before the function was called.
|
||||||
|
*
|
||||||
|
* It is possible to copy more data from src than is available in dst;
|
||||||
|
* i.e., it's possible to overflow dst using this function. When an
|
||||||
|
* overflow occurs, the state of dst is guaranteed to be consistent,
|
||||||
|
* including the head and tail pointers; old data will simply be
|
||||||
|
* overwritten in FIFO fashion, as needed. However, note that, if
|
||||||
|
* calling the function results in an overflow, the value dst's tail
|
||||||
|
* pointer may be different than it was before the function was
|
||||||
|
* called.
|
||||||
|
*
|
||||||
|
* It is *not* possible to underflow src; if count is greater than the
|
||||||
|
* number of bytes used in src, no bytes are copied, and the function
|
||||||
|
* returns 0.
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
ringbuf_copy(ringbuf_t dst, ringbuf_t src, size_t count);
|
||||||
@@ -149,6 +149,8 @@ void
|
|||||||
screen_reset(Screen *self) {
|
screen_reset(Screen *self) {
|
||||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
|
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
|
||||||
if (self->overlay_line.is_active) deactivate_overlay_line(self);
|
if (self->overlay_line.is_active) deactivate_overlay_line(self);
|
||||||
|
self->main_savepoint.is_valid = false;
|
||||||
|
self->alt_savepoint.is_valid = false;
|
||||||
linebuf_clear(self->linebuf, BLANK_CHAR);
|
linebuf_clear(self->linebuf, BLANK_CHAR);
|
||||||
historybuf_clear(self->historybuf);
|
historybuf_clear(self->historybuf);
|
||||||
clear_hyperlink_pool(self->hyperlink_pool);
|
clear_hyperlink_pool(self->hyperlink_pool);
|
||||||
@@ -373,8 +375,10 @@ selection_has_screen_line(const Selections *selections, const int y) {
|
|||||||
for (size_t i = 0; i < selections->count; i++) {
|
for (size_t i = 0; i < selections->count; i++) {
|
||||||
const Selection *s = selections->items + i;
|
const Selection *s = selections->items + i;
|
||||||
if (!is_selection_empty(s)) {
|
if (!is_selection_empty(s)) {
|
||||||
int top = (int)s->start.y - s->start_scrolled_by;
|
int start = (int)s->start.y - s->start_scrolled_by;
|
||||||
int bottom = (int)s->end.y - s->end_scrolled_by;
|
int end = (int)s->end.y - s->end_scrolled_by;
|
||||||
|
int top = MIN(start, end);
|
||||||
|
int bottom = MAX(start, end);
|
||||||
if (top <= y && y <= bottom) return true;
|
if (top <= y && y <= bottom) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -997,8 +1001,8 @@ index_selection(const Screen *self, Selections *selections, bool up) {
|
|||||||
if (self->overlay_line.is_active) deactivate_overlay_line(self); \
|
if (self->overlay_line.is_active) deactivate_overlay_line(self); \
|
||||||
linebuf_index(self->linebuf, top, bottom); \
|
linebuf_index(self->linebuf, top, bottom); \
|
||||||
INDEX_GRAPHICS(-1) \
|
INDEX_GRAPHICS(-1) \
|
||||||
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \
|
if (self->linebuf == self->main_linebuf && self->margin_top == 0) { \
|
||||||
/* Only add to history when no page margins have been set */ \
|
/* Only add to history when no top margin has been set */ \
|
||||||
linebuf_init_line(self->linebuf, bottom); \
|
linebuf_init_line(self->linebuf, bottom); \
|
||||||
historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \
|
historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \
|
||||||
self->history_line_added_count++; \
|
self->history_line_added_count++; \
|
||||||
@@ -1094,14 +1098,13 @@ screen_linefeed(Screen *self) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_save_cursor(Screen *self) {
|
screen_save_cursor(Screen *self) {
|
||||||
SavepointBuffer *pts = self->linebuf == self->main_linebuf ? &self->main_savepoints : &self->alt_savepoints;
|
Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint;
|
||||||
Savepoint *sp;
|
|
||||||
buffer_push(pts, sp);
|
|
||||||
cursor_copy_to(self->cursor, &(sp->cursor));
|
cursor_copy_to(self->cursor, &(sp->cursor));
|
||||||
sp->mDECOM = self->modes.mDECOM;
|
sp->mDECOM = self->modes.mDECOM;
|
||||||
sp->mDECAWM = self->modes.mDECAWM;
|
sp->mDECAWM = self->modes.mDECAWM;
|
||||||
sp->mDECSCNM = self->modes.mDECSCNM;
|
sp->mDECSCNM = self->modes.mDECSCNM;
|
||||||
COPY_CHARSETS(self, sp);
|
COPY_CHARSETS(self, sp);
|
||||||
|
sp->is_valid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -1113,10 +1116,8 @@ screen_save_modes(Screen *self) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_restore_cursor(Screen *self) {
|
screen_restore_cursor(Screen *self) {
|
||||||
SavepointBuffer *pts = self->linebuf == self->main_linebuf ? &self->main_savepoints : &self->alt_savepoints;
|
Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint;
|
||||||
Savepoint *sp;
|
if (!sp->is_valid) {
|
||||||
buffer_pop(pts, sp);
|
|
||||||
if (sp == NULL) {
|
|
||||||
screen_cursor_position(self, 1, 1);
|
screen_cursor_position(self, 1, 1);
|
||||||
screen_reset_mode(self, DECOM);
|
screen_reset_mode(self, DECOM);
|
||||||
RESET_CHARSETS;
|
RESET_CHARSETS;
|
||||||
@@ -1311,7 +1312,7 @@ screen_delete_lines(Screen *self, unsigned int count) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_insert_characters(Screen *self, unsigned int count) {
|
screen_insert_characters(Screen *self, unsigned int count) {
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
|
||||||
if (count == 0) count = 1;
|
if (count == 0) count = 1;
|
||||||
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||||
unsigned int x = self->cursor->x;
|
unsigned int x = self->cursor->x;
|
||||||
@@ -1327,7 +1328,7 @@ screen_insert_characters(Screen *self, unsigned int count) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_repeat_character(Screen *self, unsigned int count) {
|
screen_repeat_character(Screen *self, unsigned int count) {
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
|
||||||
unsigned int x = self->cursor->x;
|
unsigned int x = self->cursor->x;
|
||||||
if (count == 0) count = 1;
|
if (count == 0) count = 1;
|
||||||
if (x > self->columns) return;
|
if (x > self->columns) return;
|
||||||
@@ -1349,7 +1350,7 @@ screen_repeat_character(Screen *self, unsigned int count) {
|
|||||||
void
|
void
|
||||||
screen_delete_characters(Screen *self, unsigned int count) {
|
screen_delete_characters(Screen *self, unsigned int count) {
|
||||||
// Delete characters, later characters are moved left
|
// Delete characters, later characters are moved left
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
|
||||||
if (count == 0) count = 1;
|
if (count == 0) count = 1;
|
||||||
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||||
unsigned int x = self->cursor->x;
|
unsigned int x = self->cursor->x;
|
||||||
@@ -2136,6 +2137,20 @@ draw(Screen *self, PyObject *src) {
|
|||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern void
|
||||||
|
parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject *dump_callback, const char *report_name, Region *region);
|
||||||
|
|
||||||
|
static PyObject*
|
||||||
|
apply_sgr(Screen *self, PyObject *src) {
|
||||||
|
if (!PyUnicode_Check(src)) { PyErr_SetString(PyExc_TypeError, "A unicode string is required"); return NULL; }
|
||||||
|
if (PyUnicode_READY(src) != 0) { return PyErr_NoMemory(); }
|
||||||
|
Py_UCS4 *buf = PyUnicode_AsUCS4Copy(src);
|
||||||
|
if (!buf) return NULL;
|
||||||
|
unsigned int params[MAX_PARAMS] = {0};
|
||||||
|
parse_sgr(self, buf, PyUnicode_GET_LENGTH(src), params, NULL, "parse_sgr", NULL);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
reset_mode(Screen *self, PyObject *args) {
|
reset_mode(Screen *self, PyObject *args) {
|
||||||
int private = false;
|
int private = false;
|
||||||
@@ -2782,6 +2797,7 @@ static PyMethodDef methods[] = {
|
|||||||
MND(visual_line, METH_VARARGS)
|
MND(visual_line, METH_VARARGS)
|
||||||
MND(current_url_text, METH_NOARGS)
|
MND(current_url_text, METH_NOARGS)
|
||||||
MND(draw, METH_O)
|
MND(draw, METH_O)
|
||||||
|
MND(apply_sgr, METH_O)
|
||||||
MND(cursor_position, METH_VARARGS)
|
MND(cursor_position, METH_VARARGS)
|
||||||
MND(set_mode, METH_VARARGS)
|
MND(set_mode, METH_VARARGS)
|
||||||
MND(reset_mode, METH_VARARGS)
|
MND(reset_mode, METH_VARARGS)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include "graphics.h"
|
#include "graphics.h"
|
||||||
#include "monotonic.h"
|
#include "monotonic.h"
|
||||||
|
#define MAX_PARAMS 256
|
||||||
|
|
||||||
typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType;
|
typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType;
|
||||||
|
|
||||||
@@ -58,16 +59,10 @@ typedef struct {
|
|||||||
bool use_latin1;
|
bool use_latin1;
|
||||||
Cursor cursor;
|
Cursor cursor;
|
||||||
bool mDECOM, mDECAWM, mDECSCNM;
|
bool mDECOM, mDECAWM, mDECSCNM;
|
||||||
|
bool is_valid;
|
||||||
} Savepoint;
|
} Savepoint;
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
Savepoint buf[SAVEPOINTS_SZ];
|
|
||||||
index_type start_of_data, count;
|
|
||||||
} SavepointBuffer;
|
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ScreenModes buf[SAVEPOINTS_SZ];
|
ScreenModes buf[SAVEPOINTS_SZ];
|
||||||
index_type start_of_data, count;
|
index_type start_of_data, count;
|
||||||
@@ -97,7 +92,7 @@ typedef struct {
|
|||||||
} last_rendered;
|
} last_rendered;
|
||||||
bool use_latin1, is_dirty, scroll_changed, reload_all_gpu_data;
|
bool use_latin1, is_dirty, scroll_changed, reload_all_gpu_data;
|
||||||
Cursor *cursor;
|
Cursor *cursor;
|
||||||
SavepointBuffer main_savepoints, alt_savepoints;
|
Savepoint main_savepoint, alt_savepoint;
|
||||||
SavemodesBuffer modes_savepoints;
|
SavemodesBuffer modes_savepoints;
|
||||||
PyObject *callbacks, *test_child;
|
PyObject *callbacks, *test_child;
|
||||||
LineBuf *linebuf, *main_linebuf, *alt_linebuf;
|
LineBuf *linebuf, *main_linebuf, *alt_linebuf;
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
|
|||||||
w: Optional[Watchers] = None
|
w: Optional[Watchers] = None
|
||||||
if t.watchers.has_watchers:
|
if t.watchers.has_watchers:
|
||||||
w = t.watchers.copy()
|
w = t.watchers.copy()
|
||||||
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w))
|
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w, override_title=default_title))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
ans = Session(default_title)
|
ans = Session(default_title)
|
||||||
|
|||||||
@@ -703,6 +703,9 @@ PYWRAP1(set_options) {
|
|||||||
S(resize_in_steps, PyObject_IsTrue);
|
S(resize_in_steps, PyObject_IsTrue);
|
||||||
S(allow_hyperlinks, PyObject_IsTrue);
|
S(allow_hyperlinks, PyObject_IsTrue);
|
||||||
S(pointer_shape_when_grabbed, pointer_shape);
|
S(pointer_shape_when_grabbed, pointer_shape);
|
||||||
|
S(default_pointer_shape, pointer_shape);
|
||||||
|
S(pointer_shape_when_dragging, pointer_shape);
|
||||||
|
S(detect_urls, PyObject_IsTrue);
|
||||||
|
|
||||||
GA(tab_bar_style);
|
GA(tab_bar_style);
|
||||||
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
|
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
|
||||||
|
|||||||
@@ -66,10 +66,13 @@ typedef struct {
|
|||||||
bool allow_hyperlinks;
|
bool allow_hyperlinks;
|
||||||
monotonic_t resize_debounce_time;
|
monotonic_t resize_debounce_time;
|
||||||
MouseShape pointer_shape_when_grabbed;
|
MouseShape pointer_shape_when_grabbed;
|
||||||
|
MouseShape default_pointer_shape;
|
||||||
|
MouseShape pointer_shape_when_dragging;
|
||||||
struct {
|
struct {
|
||||||
UrlPrefix *values;
|
UrlPrefix *values;
|
||||||
size_t num, max_prefix_len;
|
size_t num, max_prefix_len;
|
||||||
} url_prefixes;
|
} url_prefixes;
|
||||||
|
bool detect_urls;
|
||||||
} Options;
|
} Options;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from .fast_data_types import (
|
|||||||
)
|
)
|
||||||
from .layout.base import Rect
|
from .layout.base import Rect
|
||||||
from .options_stub import Options
|
from .options_stub import Options
|
||||||
from .rgb import Color, alpha_blend, color_from_int
|
from .rgb import Color, alpha_blend, color_as_sgr, color_from_int, to_color
|
||||||
from .utils import color_as_int, log_error
|
from .utils import color_as_int, log_error
|
||||||
from .window import calculate_gl_geometry
|
from .window import calculate_gl_geometry
|
||||||
|
|
||||||
@@ -61,6 +61,35 @@ def compile_template(template: str) -> Any:
|
|||||||
report_template_failure(template, str(e))
|
report_template_failure(template, str(e))
|
||||||
|
|
||||||
|
|
||||||
|
class ColorFormatter:
|
||||||
|
|
||||||
|
def __init__(self, which: str):
|
||||||
|
self.which = which
|
||||||
|
|
||||||
|
def __getattr__(self, name: str) -> str:
|
||||||
|
q = name
|
||||||
|
if q == 'default':
|
||||||
|
ans = '9'
|
||||||
|
else:
|
||||||
|
if name.startswith('_'):
|
||||||
|
q = '#' + name[1:]
|
||||||
|
c = to_color(q)
|
||||||
|
if c is None:
|
||||||
|
raise AttributeError(f'{name} is not a valid color')
|
||||||
|
ans = '8' + color_as_sgr(c)
|
||||||
|
return f'\x1b[{self.which}{ans}m'
|
||||||
|
|
||||||
|
|
||||||
|
class Formatter:
|
||||||
|
reset = '\x1b[0m'
|
||||||
|
fg = ColorFormatter('3')
|
||||||
|
bg = ColorFormatter('4')
|
||||||
|
bold = '\x1b[1m'
|
||||||
|
nobold = '\x1b[22m'
|
||||||
|
italic = '\x1b[3m'
|
||||||
|
noitalic = '\x1b[23m'
|
||||||
|
|
||||||
|
|
||||||
def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int) -> None:
|
def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int) -> None:
|
||||||
if tab.needs_attention and draw_data.bell_on_tab:
|
if tab.needs_attention and draw_data.bell_on_tab:
|
||||||
fg = screen.cursor.fg
|
fg = screen.cursor.fg
|
||||||
@@ -81,13 +110,22 @@ def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int)
|
|||||||
'index': index,
|
'index': index,
|
||||||
'layout_name': tab.layout_name,
|
'layout_name': tab.layout_name,
|
||||||
'num_windows': tab.num_windows,
|
'num_windows': tab.num_windows,
|
||||||
'title': tab.title
|
'title': tab.title,
|
||||||
|
'fmt': Formatter,
|
||||||
}
|
}
|
||||||
title = eval(compile_template(template), {'__builtins__': {}}, eval_locals)
|
title = eval(compile_template(template), {'__builtins__': {}}, eval_locals)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
report_template_failure(template, str(e))
|
report_template_failure(template, str(e))
|
||||||
title = tab.title
|
title = tab.title
|
||||||
screen.draw(title)
|
if '\x1b' in title:
|
||||||
|
import re
|
||||||
|
for x in re.split('(\x1b\\[[^m]*m)', title):
|
||||||
|
if x.startswith('\x1b') and x.endswith('m'):
|
||||||
|
screen.apply_sgr(x[2:-1])
|
||||||
|
else:
|
||||||
|
screen.draw(x)
|
||||||
|
else:
|
||||||
|
screen.draw(title)
|
||||||
|
|
||||||
|
|
||||||
def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int:
|
def draw_tab_with_separator(draw_data: DrawData, screen: Screen, tab: TabBarData, before: int, max_title_length: int, index: int, is_last: bool) -> int:
|
||||||
|
|||||||
@@ -721,6 +721,8 @@ class TabManager: # {{{
|
|||||||
break
|
break
|
||||||
elif self.opts.tab_switch_strategy == 'left':
|
elif self.opts.tab_switch_strategy == 'left':
|
||||||
next_active_tab = max(0, self.active_tab_idx - 1)
|
next_active_tab = max(0, self.active_tab_idx - 1)
|
||||||
|
elif self.opts.tab_switch_strategy == 'right':
|
||||||
|
next_active_tab = min(self.active_tab_idx, len(self.tabs) - 1)
|
||||||
|
|
||||||
if next_active_tab < 0:
|
if next_active_tab < 0:
|
||||||
next_active_tab = max(0, min(self.active_tab_idx, len(self.tabs) - 1))
|
next_active_tab = max(0, min(self.active_tab_idx, len(self.tabs) - 1))
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from typing import cast, Dict
|
from typing import TYPE_CHECKING, Dict, Generator, Optional, cast
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .options_stub import Options
|
||||||
|
|
||||||
|
|
||||||
def modify_key_bytes(keybytes: bytes, amt: int) -> bytes:
|
def modify_key_bytes(keybytes: bytes, amt: int) -> bytes:
|
||||||
@@ -449,28 +452,42 @@ def key_as_bytes(name: str) -> bytes:
|
|||||||
return ans.encode('ascii')
|
return ans.encode('ascii')
|
||||||
|
|
||||||
|
|
||||||
def get_capabilities(query_string: str) -> str:
|
def get_capabilities(query_string: str, opts: 'Options') -> Generator[str, None, None]:
|
||||||
from .fast_data_types import ERROR_PREFIX
|
from .fast_data_types import ERROR_PREFIX
|
||||||
ans = []
|
|
||||||
try:
|
def result(encoded_query_name: str, x: Optional[str] = None) -> str:
|
||||||
for q in query_string.split(';'):
|
if x is None:
|
||||||
name = qname = unhexlify(q).decode('utf-8')
|
return '0+r' + encoded_query_name
|
||||||
if name in ('TN', 'name'):
|
return '1+r' + encoded_query_name + '=' + hexlify(str(x).encode('utf-8')).decode('ascii')
|
||||||
val = names[0]
|
|
||||||
|
for encoded_query_name in query_string.split(';'):
|
||||||
|
name = qname = unhexlify(encoded_query_name).decode('utf-8')
|
||||||
|
if name in ('TN', 'name'):
|
||||||
|
yield result(encoded_query_name, names[0])
|
||||||
|
elif name.startswith('kitty-query-'):
|
||||||
|
name = name[len('kitty-query-'):]
|
||||||
|
if name == 'version':
|
||||||
|
from .constants import str_version
|
||||||
|
yield result(encoded_query_name, str_version)
|
||||||
|
elif name == 'allow_hyperlinks':
|
||||||
|
yield result(encoded_query_name,
|
||||||
|
'ask' if opts.allow_hyperlinks == 0b11 else ('yes' if opts.allow_hyperlinks else 'no'))
|
||||||
else:
|
else:
|
||||||
|
from .utils import log_error
|
||||||
|
log_error('Unknown kitty terminfo query:', name)
|
||||||
|
yield result(encoded_query_name)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
val = queryable_capabilities[name]
|
||||||
|
except KeyError:
|
||||||
try:
|
try:
|
||||||
val = queryable_capabilities[name]
|
qname = termcap_aliases[name]
|
||||||
except KeyError:
|
val = queryable_capabilities[qname]
|
||||||
try:
|
except Exception:
|
||||||
qname = termcap_aliases[name]
|
from .utils import log_error
|
||||||
val = queryable_capabilities[qname]
|
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
||||||
except Exception:
|
yield result(encoded_query_name)
|
||||||
from .utils import log_error
|
continue
|
||||||
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
if qname in string_capabilities and '%' not in val:
|
||||||
raise
|
val = key_as_bytes(qname).decode('ascii')
|
||||||
if qname in string_capabilities and '%' not in val:
|
yield result(encoded_query_name, val)
|
||||||
val = key_as_bytes(qname).decode('ascii')
|
|
||||||
ans.append(q + '=' + hexlify(str(val).encode('utf-8')).decode('ascii'))
|
|
||||||
return '1+r' + ';'.join(ans)
|
|
||||||
except Exception:
|
|
||||||
return '0+r' + query_string
|
|
||||||
|
|||||||
@@ -580,6 +580,13 @@ def parse_uri_list(text: str) -> Generator[str, None, None]:
|
|||||||
yield unquote(purl.path)
|
yield unquote(purl.path)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_config_file() -> None:
|
||||||
|
from kitty.config import prepare_config_file_for_editing
|
||||||
|
p = prepare_config_file_for_editing()
|
||||||
|
editor = get_editor()
|
||||||
|
os.execvp(editor[0], editor + [p])
|
||||||
|
|
||||||
|
|
||||||
class SSHConnectionData(NamedTuple):
|
class SSHConnectionData(NamedTuple):
|
||||||
binary: str
|
binary: str
|
||||||
hostname: str
|
hostname: str
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ def text_sanitizer(as_ansi: bool, add_wrap_markers: bool) -> Callable[[str], str
|
|||||||
pat = getattr(text_sanitizer, 'pat', None)
|
pat = getattr(text_sanitizer, 'pat', None)
|
||||||
if pat is None:
|
if pat is None:
|
||||||
import re
|
import re
|
||||||
pat = re.compile(r'\033\[.+?m')
|
pat = re.compile('\033\\[.*?m')
|
||||||
setattr(text_sanitizer, 'pat', pat)
|
setattr(text_sanitizer, 'pat', pat)
|
||||||
|
|
||||||
ansi, wrap_markers = not as_ansi, not add_wrap_markers
|
ansi, wrap_markers = not as_ansi, not add_wrap_markers
|
||||||
@@ -704,7 +704,8 @@ class Window:
|
|||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def request_capabilities(self, q: str) -> None:
|
def request_capabilities(self, q: str) -> None:
|
||||||
self.screen.send_escape_code_to_child(DCS, get_capabilities(q))
|
for result in get_capabilities(q, self.opts):
|
||||||
|
self.screen.send_escape_code_to_child(DCS, result)
|
||||||
|
|
||||||
def handle_remote_cmd(self, cmd: str) -> None:
|
def handle_remote_cmd(self, cmd: str) -> None:
|
||||||
get_boss().handle_remote_cmd(cmd, self)
|
get_boss().handle_remote_cmd(cmd, self)
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ class Callbacks:
|
|||||||
|
|
||||||
def request_capabilities(self, q):
|
def request_capabilities(self, q):
|
||||||
from kitty.terminfo import get_capabilities
|
from kitty.terminfo import get_capabilities
|
||||||
c = get_capabilities(q)
|
for c in get_capabilities(q, None):
|
||||||
self.write(c.encode('ascii'))
|
self.write(c.encode('ascii'))
|
||||||
|
|
||||||
def use_utf8(self, on):
|
def use_utf8(self, on):
|
||||||
self.iutf8 = on
|
self.iutf8 = on
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ def parse_response(res):
|
|||||||
return res.decode('ascii').partition(';')[2].partition('\033')[0]
|
return res.decode('ascii').partition(';')[2].partition('\033')[0]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_response_with_ids(res):
|
||||||
|
if not res:
|
||||||
|
return
|
||||||
|
a, b = res.decode('ascii').split(';', 1)
|
||||||
|
code = b.partition('\033')[0].split(':', 1)[0]
|
||||||
|
a = a.split('G', 1)[1]
|
||||||
|
return code, a
|
||||||
|
|
||||||
|
|
||||||
all_bytes = bytes(bytearray(range(256)))
|
all_bytes = bytes(bytearray(range(256)))
|
||||||
|
|
||||||
|
|
||||||
@@ -90,20 +99,23 @@ def put_helpers(self, cw, ch):
|
|||||||
s = self.create_screen(10, 5, cell_width=cw, cell_height=ch)
|
s = self.create_screen(10, 5, cell_width=cw, cell_height=ch)
|
||||||
return s, 2 / s.columns, 2 / s.lines
|
return s, 2 / s.columns, 2 / s.lines
|
||||||
|
|
||||||
def put_cmd(z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, height=0, cell_x_off=0, cell_y_off=0):
|
def put_cmd(z=0, num_cols=0, num_lines=0, x_off=0, y_off=0, width=0, height=0, cell_x_off=0, cell_y_off=0, placement_id=0):
|
||||||
return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d' % (z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off, cell_y_off)
|
return 'z=%d,c=%d,r=%d,x=%d,y=%d,w=%d,h=%d,X=%d,Y=%d,p=%d' % (
|
||||||
|
z, num_cols, num_lines, x_off, y_off, width, height, cell_x_off, cell_y_off, placement_id)
|
||||||
|
|
||||||
def put_image(screen, w, h, **kw):
|
def put_image(screen, w, h, **kw):
|
||||||
nonlocal iid
|
nonlocal iid
|
||||||
iid += 1
|
iid += 1
|
||||||
cmd = 'a=T,f=24,i=%d,s=%d,v=%d,%s' % (iid, w, h, put_cmd(**kw))
|
imgid = kw.pop('id', None) or iid
|
||||||
|
cmd = 'a=T,f=24,i=%d,s=%d,v=%d,%s' % (imgid, w, h, put_cmd(**kw))
|
||||||
data = b'x' * w * h * 3
|
data = b'x' * w * h * 3
|
||||||
res = send_command(screen, cmd, data)
|
res = send_command(screen, cmd, data)
|
||||||
return iid, parse_response(res)
|
return imgid, parse_response(res)
|
||||||
|
|
||||||
def put_ref(screen, **kw):
|
def put_ref(screen, **kw):
|
||||||
cmd = 'a=p,i=%d,%s' % (iid, put_cmd(**kw))
|
imgid = kw.pop('id', None) or iid
|
||||||
send_command(screen, cmd)
|
cmd = 'a=p,i=%d,%s' % (imgid, put_cmd(**kw))
|
||||||
|
return imgid, parse_response_with_ids(send_command(screen, cmd))
|
||||||
|
|
||||||
def layers(screen, scrolled_by=0, xstart=-1, ystart=1):
|
def layers(screen, scrolled_by=0, xstart=-1, ystart=1):
|
||||||
return screen.grman.update_layers(scrolled_by, xstart, ystart, dx, dy, screen.columns, screen.lines, cw, ch)
|
return screen.grman.update_layers(scrolled_by, xstart, ystart, dx, dy, screen.columns, screen.lines, cw, ch)
|
||||||
@@ -125,6 +137,7 @@ class TestGraphics(BaseTest):
|
|||||||
|
|
||||||
# Test load query
|
# Test load query
|
||||||
self.ae(l('abcd', s=1, v=1, a='q'), 'OK')
|
self.ae(l('abcd', s=1, v=1, a='q'), 'OK')
|
||||||
|
self.assertIsNone(l('abcd', s=1, v=1, a='q', q=1))
|
||||||
self.ae(g.image_count, 0)
|
self.ae(g.image_count, 0)
|
||||||
|
|
||||||
# Test simple load
|
# Test simple load
|
||||||
@@ -140,6 +153,8 @@ class TestGraphics(BaseTest):
|
|||||||
self.ae(l('mnop', m=0), 'OK')
|
self.ae(l('mnop', m=0), 'OK')
|
||||||
img = g.image_for_client_id(1)
|
img = g.image_for_client_id(1)
|
||||||
self.ae(img['data'], b'abcdefghijklmnop')
|
self.ae(img['data'], b'abcdefghijklmnop')
|
||||||
|
self.ae(l('abcd', s=10, v=10, q=1), 'ENODATA:Insufficient image data: 4 < 400')
|
||||||
|
self.ae(l('abcd', s=10, v=10, q=2), None)
|
||||||
|
|
||||||
# Test compression
|
# Test compression
|
||||||
random_data = byte_block(3 * 1024)
|
random_data = byte_block(3 * 1024)
|
||||||
@@ -216,6 +231,73 @@ class TestGraphics(BaseTest):
|
|||||||
# test error handling for loading bad png data
|
# test error handling for loading bad png data
|
||||||
self.assertRaisesRegex(ValueError, '[EBADPNG]', load_png_data, b'dsfsdfsfsfd')
|
self.assertRaisesRegex(ValueError, '[EBADPNG]', load_png_data, b'dsfsdfsfsfd')
|
||||||
|
|
||||||
|
def test_gr_operations_with_numbers(self):
|
||||||
|
s = self.create_screen()
|
||||||
|
g = s.grman
|
||||||
|
|
||||||
|
def li(payload, **kw):
|
||||||
|
cmd = ','.join('%s=%s' % (k, v) for k, v in kw.items())
|
||||||
|
res = send_command(s, cmd, payload)
|
||||||
|
return parse_response_with_ids(res)
|
||||||
|
|
||||||
|
code, ids = li('abc', s=1, v=1, f=24, I=1, i=3)
|
||||||
|
self.ae(code, 'EINVAL')
|
||||||
|
|
||||||
|
code, ids = li('abc', s=1, v=1, f=24, I=1)
|
||||||
|
self.ae((code, ids), ('OK', 'i=1,I=1'))
|
||||||
|
img = g.image_for_client_number(1)
|
||||||
|
self.ae(img['client_number'], 1)
|
||||||
|
self.ae(img['client_id'], 1)
|
||||||
|
code, ids = li('abc', s=1, v=1, f=24, I=1)
|
||||||
|
self.ae((code, ids), ('OK', 'i=2,I=1'))
|
||||||
|
img = g.image_for_client_number(1)
|
||||||
|
self.ae(img['client_number'], 1)
|
||||||
|
self.ae(img['client_id'], 2)
|
||||||
|
code, ids = li('abc', s=1, v=1, f=24, I=1)
|
||||||
|
self.ae((code, ids), ('OK', 'i=3,I=1'))
|
||||||
|
code, ids = li('abc', s=1, v=1, f=24, i=5)
|
||||||
|
self.ae((code, ids), ('OK', 'i=5'))
|
||||||
|
code, ids = li('abc', s=1, v=1, f=24, I=3)
|
||||||
|
self.ae((code, ids), ('OK', 'i=4,I=3'))
|
||||||
|
|
||||||
|
# Test chunked load with number
|
||||||
|
self.assertIsNone(li('abcd', s=2, v=2, m=1, I=93))
|
||||||
|
self.assertIsNone(li('efgh', m=1))
|
||||||
|
self.assertIsNone(li('ijkx', m=1))
|
||||||
|
self.ae(li('mnop', m=0), ('OK', 'i=6,I=93'))
|
||||||
|
img = g.image_for_client_number(93)
|
||||||
|
self.ae(img['data'], b'abcdefghijkxmnop')
|
||||||
|
self.ae(img['client_id'], 6)
|
||||||
|
|
||||||
|
# test put with number
|
||||||
|
def put(**kw):
|
||||||
|
cmd = ','.join('%s=%s' % (k, v) for k, v in kw.items())
|
||||||
|
cmd = 'a=p,' + cmd
|
||||||
|
return parse_response_with_ids(send_command(s, cmd))
|
||||||
|
|
||||||
|
code, idstr = put(c=2, r=2, I=93)
|
||||||
|
self.ae((code, idstr), ('OK', 'i=6,I=93'))
|
||||||
|
code, idstr = put(c=2, r=2, I=94)
|
||||||
|
self.ae(code, 'ENOENT')
|
||||||
|
|
||||||
|
# test delete with number
|
||||||
|
def delete(ac='N', **kw):
|
||||||
|
cmd = 'a=d'
|
||||||
|
if ac:
|
||||||
|
cmd += ',d={}'.format(ac)
|
||||||
|
if kw:
|
||||||
|
cmd += ',' + ','.join('{}={}'.format(k, v) for k, v in kw.items())
|
||||||
|
send_command(s, cmd)
|
||||||
|
|
||||||
|
count = s.grman.image_count
|
||||||
|
put(i=1), put(i=2), put(i=3), put(i=4), put(i=5)
|
||||||
|
delete(I=94)
|
||||||
|
self.ae(s.grman.image_count, count)
|
||||||
|
delete(I=93)
|
||||||
|
self.ae(s.grman.image_count, count - 1)
|
||||||
|
delete(I=1)
|
||||||
|
self.ae(s.grman.image_count, count - 2)
|
||||||
|
|
||||||
def test_image_put(self):
|
def test_image_put(self):
|
||||||
cw, ch = 10, 20
|
cw, ch = 10, 20
|
||||||
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
|
s, dx, dy, put_image, put_ref, layers, rect_eq = put_helpers(self, cw, ch)
|
||||||
@@ -226,7 +308,8 @@ class TestGraphics(BaseTest):
|
|||||||
rect_eq(l0[0]['dest_rect'], -1, 1, -1 + dx, 1 - dy)
|
rect_eq(l0[0]['dest_rect'], -1, 1, -1 + dx, 1 - dy)
|
||||||
self.ae(l0[0]['group_count'], 1)
|
self.ae(l0[0]['group_count'], 1)
|
||||||
self.ae(s.cursor.x, 1), self.ae(s.cursor.y, 0)
|
self.ae(s.cursor.x, 1), self.ae(s.cursor.y, 0)
|
||||||
put_ref(s, num_cols=s.columns, x_off=2, y_off=1, width=3, height=5, cell_x_off=3, cell_y_off=1, z=-1)
|
iid, (code, idstr) = put_ref(s, num_cols=s.columns, x_off=2, y_off=1, width=3, height=5, cell_x_off=3, cell_y_off=1, z=-1, placement_id=17)
|
||||||
|
self.ae(idstr, f'i={iid},p=17')
|
||||||
l2 = layers(s)
|
l2 = layers(s)
|
||||||
self.ae(len(l2), 2)
|
self.ae(len(l2), 2)
|
||||||
rect_eq(l2[0]['src_rect'], 2 / 10, 1 / 20, (2 + 3) / 10, (1 + 5)/20)
|
rect_eq(l2[0]['src_rect'], 2 / 10, 1 / 20, (2 + 3) / 10, (1 + 5)/20)
|
||||||
@@ -320,8 +403,13 @@ class TestGraphics(BaseTest):
|
|||||||
delete('A')
|
delete('A')
|
||||||
self.ae(s.grman.image_count, 0)
|
self.ae(s.grman.image_count, 0)
|
||||||
iid = put_image(s, cw, ch)[0]
|
iid = put_image(s, cw, ch)[0]
|
||||||
|
delete('I', i=iid, p=7)
|
||||||
|
self.ae(s.grman.image_count, 1)
|
||||||
delete('I', i=iid)
|
delete('I', i=iid)
|
||||||
self.ae(s.grman.image_count, 0)
|
self.ae(s.grman.image_count, 0)
|
||||||
|
iid = put_image(s, cw, ch, placement_id=9)[0]
|
||||||
|
delete('I', i=iid, p=9)
|
||||||
|
self.ae(s.grman.image_count, 0)
|
||||||
s.reset()
|
s.reset()
|
||||||
put_image(s, cw, ch)
|
put_image(s, cw, ch)
|
||||||
put_image(s, cw, ch)
|
put_image(s, cw, ch)
|
||||||
@@ -335,3 +423,14 @@ class TestGraphics(BaseTest):
|
|||||||
put_image(s, cw, ch, z=9)
|
put_image(s, cw, ch, z=9)
|
||||||
delete('Z', z=9)
|
delete('Z', z=9)
|
||||||
self.ae(s.grman.image_count, 0)
|
self.ae(s.grman.image_count, 0)
|
||||||
|
|
||||||
|
# test put + delete + put
|
||||||
|
iid = 999999
|
||||||
|
self.ae(put_image(s, cw, ch, id=iid), (iid, 'OK'))
|
||||||
|
self.ae(put_ref(s, id=iid), (iid, ('OK', f'i={iid}')))
|
||||||
|
delete('i', i=iid)
|
||||||
|
self.ae(s.grman.image_count, 1)
|
||||||
|
self.ae(put_ref(s, id=iid), (iid, ('OK', f'i={iid}')))
|
||||||
|
delete('I', i=iid)
|
||||||
|
self.ae(put_ref(s, id=iid), (iid, ('ENOENT', f'i={iid}')))
|
||||||
|
self.ae(s.grman.image_count, 0)
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ class TestParser(BaseTest):
|
|||||||
for f in 'action delete_action transmission_type compressed'.split():
|
for f in 'action delete_action transmission_type compressed'.split():
|
||||||
k.setdefault(f, b'\0')
|
k.setdefault(f, b'\0')
|
||||||
for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width'
|
for f in ('format more id data_sz data_offset width height x_offset y_offset data_height data_width'
|
||||||
' num_cells num_lines cell_x_offset cell_y_offset z_index').split():
|
' num_cells num_lines cell_x_offset cell_y_offset z_index placement_id image_number quiet').split():
|
||||||
k.setdefault(f, 0)
|
k.setdefault(f, 0)
|
||||||
p = k.pop('payload', '').encode('utf-8')
|
p = k.pop('payload', '').encode('utf-8')
|
||||||
k['payload_sz'] = len(p)
|
k['payload_sz'] = len(p)
|
||||||
@@ -395,11 +395,12 @@ class TestParser(BaseTest):
|
|||||||
pb = partial(self.parse_bytes_dump, s)
|
pb = partial(self.parse_bytes_dump, s)
|
||||||
uint32_max = 2**32 - 1
|
uint32_max = 2**32 - 1
|
||||||
t('i=%d' % uint32_max, id=uint32_max)
|
t('i=%d' % uint32_max, id=uint32_max)
|
||||||
|
t('i=3,p=4', id=3, placement_id=4)
|
||||||
e('i=%d' % (uint32_max + 1), 'Malformed GraphicsCommand control block, number is too large')
|
e('i=%d' % (uint32_max + 1), 'Malformed GraphicsCommand control block, number is too large')
|
||||||
pb('\033_Gi=12\033\\', c(id=12))
|
pb('\033_Gi=12\033\\', c(id=12))
|
||||||
t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9, payload_sz=1)
|
t('a=t,t=d,s=100,z=-9', payload='X', action='t', transmission_type='d', data_width=100, z_index=-9, payload_sz=1)
|
||||||
t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9, payload_sz=7)
|
t('a=t,t=d,s=100,z=9', payload='payload', action='t', transmission_type='d', data_width=100, z_index=9, payload_sz=7)
|
||||||
t('a=t,t=d,s=100,z=9', action='t', transmission_type='d', data_width=100, z_index=9)
|
t('a=t,t=d,s=100,z=9,q=2', action='t', transmission_type='d', data_width=100, z_index=9, quiet=2)
|
||||||
e(',s=1', 'Malformed GraphicsCommand control block, invalid key character: 0x2c')
|
e(',s=1', 'Malformed GraphicsCommand control block, invalid key character: 0x2c')
|
||||||
e('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57')
|
e('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57')
|
||||||
e('1=1', 'Malformed GraphicsCommand control block, invalid key character: 0x31')
|
e('1=1', 'Malformed GraphicsCommand control block, invalid key character: 0x31')
|
||||||
|
|||||||
@@ -142,6 +142,13 @@ class TestScreen(BaseTest):
|
|||||||
self.ae(str(s.line(0)), 'ade')
|
self.ae(str(s.line(0)), 'ade')
|
||||||
self.assertTrue(s.line(0).cursor_from(4).bold)
|
self.assertTrue(s.line(0).cursor_from(4).bold)
|
||||||
self.assertFalse(s.line(0).cursor_from(2).bold)
|
self.assertFalse(s.line(0).cursor_from(2).bold)
|
||||||
|
s = self.create_screen()
|
||||||
|
s.set_margins(1, 2)
|
||||||
|
s.cursor.y = 3
|
||||||
|
s.draw('abcde')
|
||||||
|
s.cursor.x = 0
|
||||||
|
s.delete_characters(2)
|
||||||
|
self.ae('cde', str(s.line(s.cursor.y)))
|
||||||
|
|
||||||
init()
|
init()
|
||||||
s.erase_characters(2)
|
s.erase_characters(2)
|
||||||
@@ -676,3 +683,36 @@ class TestScreen(BaseTest):
|
|||||||
self.ae(s.current_url_text(), '123abcxyz')
|
self.ae(s.current_url_text(), '123abcxyz')
|
||||||
self.ae('2', s.hyperlink_at(1, 3))
|
self.ae('2', s.hyperlink_at(1, 3))
|
||||||
self.ae(s.current_url_text(), 'Z Z')
|
self.ae(s.current_url_text(), 'Z Z')
|
||||||
|
|
||||||
|
def test_bottom_margin(self):
|
||||||
|
s = self.create_screen(cols=80, lines=6, scrollback=4)
|
||||||
|
s.set_margins(0, 5)
|
||||||
|
for i in range(8):
|
||||||
|
s.draw(str(i))
|
||||||
|
s.linefeed()
|
||||||
|
s.carriage_return()
|
||||||
|
|
||||||
|
self.ae(str(s.linebuf), '4\n5\n6\n7\n\n')
|
||||||
|
self.ae(str(s.historybuf), '3\n2\n1\n0')
|
||||||
|
|
||||||
|
def test_top_margin(self):
|
||||||
|
s = self.create_screen(cols=80, lines=6, scrollback=4)
|
||||||
|
s.set_margins(2, 6)
|
||||||
|
for i in range(8):
|
||||||
|
s.draw(str(i))
|
||||||
|
s.linefeed()
|
||||||
|
s.carriage_return()
|
||||||
|
|
||||||
|
self.ae(str(s.linebuf), '0\n4\n5\n6\n7\n')
|
||||||
|
self.ae(str(s.historybuf), '')
|
||||||
|
|
||||||
|
def test_top_and_bottom_margin(self):
|
||||||
|
s = self.create_screen(cols=80, lines=6, scrollback=4)
|
||||||
|
s.set_margins(2, 5)
|
||||||
|
for i in range(8):
|
||||||
|
s.draw(str(i))
|
||||||
|
s.linefeed()
|
||||||
|
s.carriage_return()
|
||||||
|
|
||||||
|
self.ae(str(s.linebuf), '0\n5\n6\n7\n\n')
|
||||||
|
self.ae(str(s.historybuf), '')
|
||||||
|
|||||||
13
launcher.c
13
launcher.c
@@ -42,10 +42,11 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
set_xoptions(const wchar_t *exe_dir, const char *lc_ctype) {
|
set_xoptions(const wchar_t *exe_dir, const char *lc_ctype, bool from_source) {
|
||||||
wchar_t buf[PATH_MAX+1] = {0};
|
wchar_t buf[PATH_MAX+1] = {0};
|
||||||
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
|
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
|
||||||
PySys_AddXOption(buf);
|
PySys_AddXOption(buf);
|
||||||
|
if (from_source) PySys_AddXOption(L"kitty_from_source=1");
|
||||||
if (lc_ctype) {
|
if (lc_ctype) {
|
||||||
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
|
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
|
||||||
PySys_AddXOption(buf);
|
PySys_AddXOption(buf);
|
||||||
@@ -66,7 +67,6 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
|||||||
int ret = 1;
|
int ret = 1;
|
||||||
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
||||||
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; }
|
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; }
|
||||||
set_xoptions(exe_dir, lc_ctype);
|
|
||||||
wchar_t stdlib[PATH_MAX+1] = {0};
|
wchar_t stdlib[PATH_MAX+1] = {0};
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
const char *python_relpath = "../Resources/Python/lib";
|
const char *python_relpath = "../Resources/Python/lib";
|
||||||
@@ -80,9 +80,9 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
|||||||
);
|
);
|
||||||
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; }
|
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; }
|
||||||
Py_SetPath(stdlib);
|
Py_SetPath(stdlib);
|
||||||
PyMem_RawFree(exe_dir);
|
|
||||||
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; }
|
|
||||||
Py_Initialize();
|
Py_Initialize();
|
||||||
|
set_xoptions(exe_dir, lc_ctype, false);
|
||||||
|
PyMem_RawFree(exe_dir);
|
||||||
PySys_SetArgvEx(argc - 1, argv + 1, 0);
|
PySys_SetArgvEx(argc - 1, argv + 1, 0);
|
||||||
PySys_SetObject("frozen", Py_True);
|
PySys_SetObject("frozen", Py_True);
|
||||||
PyObject *kitty = PyUnicode_FromString(libpath);
|
PyObject *kitty = PyUnicode_FromString(libpath);
|
||||||
@@ -104,10 +104,11 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
|||||||
(void)libpath;
|
(void)libpath;
|
||||||
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
||||||
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; }
|
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; }
|
||||||
set_xoptions(exe_dir, lc_ctype);
|
bool from_source = false;
|
||||||
#ifdef FROM_SOURCE
|
#ifdef FROM_SOURCE
|
||||||
PySys_AddXOption(L"kitty_from_source=1");
|
from_source = true;
|
||||||
#endif
|
#endif
|
||||||
|
set_xoptions(exe_dir, lc_ctype, from_source);
|
||||||
PyMem_RawFree(exe_dir);
|
PyMem_RawFree(exe_dir);
|
||||||
return Py_Main(argc, argv);
|
return Py_Main(argc, argv);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class ReadFileWithProgressReporting(io.FileIO): # {{{
|
|||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return self._total
|
return self._total
|
||||||
|
|
||||||
def read(self, size: int = -1) -> Optional[bytes]:
|
def read(self, size: int = -1) -> bytes:
|
||||||
data = io.FileIO.read(self, size)
|
data = io.FileIO.read(self, size)
|
||||||
if data:
|
if data:
|
||||||
self.report_progress(len(data))
|
self.report_progress(len(data))
|
||||||
|
|||||||
32
setup.py
32
setup.py
@@ -13,6 +13,7 @@ import shutil
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import sysconfig
|
import sysconfig
|
||||||
|
import platform
|
||||||
import time
|
import time
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
@@ -47,6 +48,7 @@ version = tuple(
|
|||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos = 'darwin' in _plat
|
is_macos = 'darwin' in _plat
|
||||||
is_openbsd = 'openbsd' in _plat
|
is_openbsd = 'openbsd' in _plat
|
||||||
|
is_arm = platform.processor() == 'arm'
|
||||||
Env = glfw.Env
|
Env = glfw.Env
|
||||||
env = Env()
|
env = Env()
|
||||||
PKGCONFIG = os.environ.get('PKGCONFIG_EXE', 'pkg-config')
|
PKGCONFIG = os.environ.get('PKGCONFIG_EXE', 'pkg-config')
|
||||||
@@ -63,11 +65,11 @@ class Options(argparse.Namespace):
|
|||||||
for_freeze: bool = False
|
for_freeze: bool = False
|
||||||
libdir_name: str = 'lib'
|
libdir_name: str = 'lib'
|
||||||
extra_logging: List[str] = []
|
extra_logging: List[str] = []
|
||||||
link_time_optimization: bool = True
|
link_time_optimization: bool = 'KITTY_NO_LTO' not in os.environ
|
||||||
update_check_interval: float = 24
|
update_check_interval: float = 24
|
||||||
egl_library: Optional[str] = None
|
egl_library: Optional[str] = os.getenv('KITTY_EGL_LIBRARY')
|
||||||
startup_notification_library: Optional[str] = None
|
startup_notification_library: Optional[str] = os.getenv('KITTY_STARTUP_NOTIFICATION_LIBRARY')
|
||||||
canberra_library: Optional[str] = None
|
canberra_library: Optional[str] = os.getenv('KITTY_CANBERRA_LIBRARY')
|
||||||
|
|
||||||
|
|
||||||
class CompileKey(NamedTuple):
|
class CompileKey(NamedTuple):
|
||||||
@@ -211,11 +213,11 @@ def get_sanitize_args(cc: str, ccver: Tuple[int, int]) -> List[str]:
|
|||||||
return sanitize_args
|
return sanitize_args
|
||||||
|
|
||||||
|
|
||||||
def test_compile(cc: str, *cflags: str, src: Optional[str] = None) -> bool:
|
def test_compile(cc: str, *cflags: str, src: Optional[str] = None, lang: str = 'c') -> bool:
|
||||||
src = src or 'int main(void) { return 0; }'
|
src = src or 'int main(void) { return 0; }'
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
[cc] + list(cflags) + ['-x', 'c', '-o', os.devnull, '-'],
|
[cc] + list(cflags) + ['-x', lang, '-o', os.devnull, '-'],
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
stdin = p.stdin
|
stdin = p.stdin
|
||||||
assert stdin is not None
|
assert stdin is not None
|
||||||
@@ -227,9 +229,9 @@ def test_compile(cc: str, *cflags: str, src: Optional[str] = None) -> bool:
|
|||||||
return p.wait() == 0
|
return p.wait() == 0
|
||||||
|
|
||||||
|
|
||||||
def first_successful_compile(cc: str, *cflags: str, src: Optional[str] = None) -> str:
|
def first_successful_compile(cc: str, *cflags: str, src: Optional[str] = None, lang: str = 'c') -> str:
|
||||||
for x in cflags:
|
for x in cflags:
|
||||||
if test_compile(cc, *shlex.split(x), src=src):
|
if test_compile(cc, *shlex.split(x), src=src, lang=lang):
|
||||||
return x
|
return x
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@@ -246,6 +248,10 @@ def init_env(
|
|||||||
extra_logging: Iterable[str] = ()
|
extra_logging: Iterable[str] = ()
|
||||||
) -> Env:
|
) -> Env:
|
||||||
native_optimizations = native_optimizations and not sanitize and not debug
|
native_optimizations = native_optimizations and not sanitize and not debug
|
||||||
|
if native_optimizations and is_macos and is_arm:
|
||||||
|
# see https://github.com/kovidgoyal/kitty/issues/3126
|
||||||
|
# -march=native is not supported when targeting Apple Silicon
|
||||||
|
native_optimizations = False
|
||||||
cc, ccver = cc_version()
|
cc, ccver = cc_version()
|
||||||
print('CC:', cc, ccver)
|
print('CC:', cc, ccver)
|
||||||
stack_protector = first_successful_compile(cc, '-fstack-protector-strong', '-fstack-protector')
|
stack_protector = first_successful_compile(cc, '-fstack-protector-strong', '-fstack-protector')
|
||||||
@@ -298,6 +304,9 @@ def init_env(
|
|||||||
cflags.append('-flto')
|
cflags.append('-flto')
|
||||||
ldflags.append('-flto')
|
ldflags.append('-flto')
|
||||||
|
|
||||||
|
if debug:
|
||||||
|
cflags.append('-DKITTY_DEBUG_BUILD')
|
||||||
|
|
||||||
if profile:
|
if profile:
|
||||||
cppflags.append('-DWITH_PROFILER')
|
cppflags.append('-DWITH_PROFILER')
|
||||||
cflags.append('-g3')
|
cflags.append('-g3')
|
||||||
@@ -340,7 +349,10 @@ def kitty_env() -> Env:
|
|||||||
platform_libs = [
|
platform_libs = [
|
||||||
'-framework', 'CoreText', '-framework', 'CoreGraphics',
|
'-framework', 'CoreText', '-framework', 'CoreGraphics',
|
||||||
]
|
]
|
||||||
user_notifications_framework = first_successful_compile(ans.cc, '-framework UserNotifications')
|
test_program_src = '''#include <UserNotifications/UserNotifications.h>
|
||||||
|
int main(void) { return 0; }\n'''
|
||||||
|
user_notifications_framework = first_successful_compile(
|
||||||
|
ans.cc, '-framework UserNotifications', src=test_program_src, lang='objective-c')
|
||||||
if user_notifications_framework:
|
if user_notifications_framework:
|
||||||
platform_libs.extend(shlex.split(user_notifications_framework))
|
platform_libs.extend(shlex.split(user_notifications_framework))
|
||||||
else:
|
else:
|
||||||
|
|||||||
55
shell.nix
Normal file
55
shell.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
with pkgs;
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) optional optionals;
|
||||||
|
inherit (xorg) libX11 libXrandr libXinerama libXcursor libXi libXext;
|
||||||
|
inherit (darwin.apple_sdk.frameworks) Cocoa CoreGraphics Foundation IOKit Kernel OpenGL;
|
||||||
|
harfbuzzWithCoreText = harfbuzz.override { withCoreText = stdenv.isDarwin; };
|
||||||
|
in
|
||||||
|
mkShell rec {
|
||||||
|
buildInputs = [
|
||||||
|
harfbuzzWithCoreText
|
||||||
|
ncurses
|
||||||
|
lcms2
|
||||||
|
] ++ optionals stdenv.isDarwin [
|
||||||
|
Cocoa
|
||||||
|
CoreGraphics
|
||||||
|
Foundation
|
||||||
|
IOKit
|
||||||
|
Kernel
|
||||||
|
OpenGL
|
||||||
|
libpng
|
||||||
|
python3
|
||||||
|
zlib
|
||||||
|
] ++ optionals stdenv.isLinux [
|
||||||
|
fontconfig libunistring libcanberra libX11
|
||||||
|
libXrandr libXinerama libXcursor libxkbcommon libXi libXext
|
||||||
|
wayland-protocols wayland dbus
|
||||||
|
] ++ checkInputs;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgconfig python3Packages.sphinx ncurses
|
||||||
|
] ++ optionals stdenv.isDarwin [
|
||||||
|
imagemagick
|
||||||
|
libicns # For the png2icns tool.
|
||||||
|
installShellFiles
|
||||||
|
];
|
||||||
|
|
||||||
|
propagatedBuildInputs = optional stdenv.isLinux libGL;
|
||||||
|
|
||||||
|
checkInputs = [
|
||||||
|
python3Packages.pillow
|
||||||
|
];
|
||||||
|
|
||||||
|
# Causes build failure due to warning when using Clang
|
||||||
|
hardeningDisable = [ "strictoverflow" ];
|
||||||
|
|
||||||
|
shellHook = if stdenv.isDarwin then ''
|
||||||
|
export KITTY_NO_LTO=
|
||||||
|
'' else ''
|
||||||
|
export KITTY_EGL_LIBRARY='${stdenv.lib.getLib libGL}/lib/libEGL.so.1'
|
||||||
|
export KITTY_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
|
||||||
|
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
|
||||||
|
'';
|
||||||
|
}
|
||||||
2
test.py
2
test.py
@@ -97,7 +97,7 @@ def run_tests() -> None:
|
|||||||
|
|
||||||
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
|
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
|
||||||
r = unittest.TextTestRunner
|
r = unittest.TextTestRunner
|
||||||
r.resultclass = unittest.TextTestResult # type: ignore
|
r.resultclass = unittest.TextTestResult
|
||||||
init_env()
|
init_env()
|
||||||
runner = r(verbosity=verbosity)
|
runner = r(verbosity=verbosity)
|
||||||
runner.tb_locals = True # type: ignore
|
runner.tb_locals = True # type: ignore
|
||||||
|
|||||||
Reference in New Issue
Block a user