Compare commits
81 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 |
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;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
pre {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ Build-time dependencies:
|
||||
* gcc or clang
|
||||
* pkg-config
|
||||
* 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.
|
||||
|
||||
Install and run from source
|
||||
|
||||
@@ -4,6 +4,68 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
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]
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from pygments.lexer import RegexLexer, bygroups # type: ignore
|
||||
from pygments.token import ( # type: ignore
|
||||
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.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:
|
||||
app.add_lexer('conf', ConfLexer)
|
||||
app.add_lexer('conf', ConfLexer() if version_info[0] < 3 else ConfLexer)
|
||||
app.add_object_type(
|
||||
'opt', 'opt',
|
||||
indextemplate="pair: %s; Config Setting",
|
||||
@@ -604,7 +604,7 @@ def setup(app: Any) -> None:
|
||||
write_cli_docs(kn)
|
||||
write_remote_control_protocol_docs()
|
||||
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('iss', partial(num_role, 'issues'))
|
||||
app.add_role('pull', partial(num_role, 'pull'))
|
||||
|
||||
18
docs/faq.rst
18
docs/faq.rst
@@ -47,6 +47,10 @@ terminfo files to the server::
|
||||
|
||||
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
|
||||
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
|
||||
@@ -182,7 +186,7 @@ following :file:`~/.config/fontconfig/fonts.conf`::
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||
<fontconfig>
|
||||
<match target="font">
|
||||
<match target="scan">
|
||||
<test name="family">
|
||||
<string>Your Font Family Name</string>
|
||||
</test>
|
||||
@@ -236,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``
|
||||
|
||||
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
|
||||
--------------------------------------
|
||||
|
||||
@@ -38,6 +38,7 @@ Some programs that use the kitty graphics protocol:
|
||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||
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::
|
||||
@@ -314,7 +315,7 @@ terminal emulators handle input in a FIFO manner, anyway.
|
||||
|
||||
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
|
||||
the protocol, if you get back a response to the device attributes query without
|
||||
@@ -326,11 +327,12 @@ Display images on screen
|
||||
|
||||
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
|
||||
needed. You can either simultaneously transmit and display an image using the
|
||||
action ``a=T``, or first transmit the image with a id, such as ``i=10`` and then display
|
||||
it with ``a=p,i=10`` which will display the previously transmitted image at the current
|
||||
cursor position. When specifying an image id, the terminal emulator will reply with an
|
||||
acknowledgement code, which will be either::
|
||||
needed. Each such display of an image is called a *placement*. You can either
|
||||
simultaneously transmit and display an image using the action ``a=T``, or first
|
||||
transmit the image with a id, such as ``i=10`` and then display it with
|
||||
``a=p,i=10`` which will display the previously transmitted image at the current
|
||||
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>\
|
||||
|
||||
@@ -343,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
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -369,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
|
||||
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
|
||||
---------------------
|
||||
|
||||
@@ -385,24 +412,77 @@ scrollback buffer. The values of the ``x`` and ``y`` keys are the same as cursor
|
||||
================= ============
|
||||
Value of ``d`` Meaning
|
||||
================= ============
|
||||
``a`` or ``A`` Delete all images visible on screen
|
||||
``i`` or ``I`` Delete all images with the specified id, specified using the ``i`` key.
|
||||
``c`` or ``C`` Delete all images that intersect with the current cursor position.
|
||||
``p`` or ``P`` Delete all images that intersect a specific cell, the cell is specified using the ``x`` and ``y`` keys
|
||||
``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.
|
||||
``x`` or ``X`` Delete all images that intersect the specified column, specified using the ``x`` key.
|
||||
``y`` or ``Y`` Delete all images that intersect the specified row, specified using the ``y`` key.
|
||||
``z`` or ``Z`` Delete all images that have the specified z-index, specified using the ``z`` key.
|
||||
``a`` or ``A`` Delete all placements visible on screen
|
||||
``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.
|
||||
``n`` or ``N`` Delete newest image with the specified number, specified using the ``I`` key. If you specify a ``p`` key for the
|
||||
placement id as well, then only the placement with the specified number and placement id will be deleted.
|
||||
``c`` or ``C`` Delete all placements that intersect with the current cursor position.
|
||||
``c`` or ``C`` Delete all placements that intersect with the current cursor position.
|
||||
``p`` or ``P`` Delete all placements that intersect a specific cell, the cell is specified using the ``x`` and ``y`` keys
|
||||
``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::
|
||||
|
||||
<ESC>_Ga=d<ESC>\ # delete all visible images
|
||||
<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=p,x=3,y=4<ESC>\ # delete all images that intersect the cell at (3, 4), without freeing data
|
||||
<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,p=7<ESC>\ # delete the image with id=10 and placement id=7, 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -425,6 +505,9 @@ Key Value Default Description
|
||||
======= ==================== ========= =================
|
||||
``a`` Single character. ``t`` The overall action this graphics command is performing.
|
||||
``(t, T, q, p, d)``
|
||||
|
||||
``q`` ``0, 1, 2`` ``0`` Suppress responses from the terminal to this graphics command.
|
||||
|
||||
**Keys for image transmission**
|
||||
-----------------------------------------------------------
|
||||
``f`` Positive integer. ``32`` The format in which the image data is sent.
|
||||
@@ -437,6 +520,8 @@ Key Value Default Description
|
||||
``O`` Positive integer. ``0`` The offset from which to read data from a file.
|
||||
``i`` Positive integer.
|
||||
``(0 - 4294967295)`` ``0`` The image id
|
||||
``p`` Positive integer.
|
||||
``(0 - 4294967295)`` ``0`` The placement id
|
||||
``o`` Single character. ``null`` The type of data compression.
|
||||
``only z``
|
||||
``m`` zero or one ``0`` Whether there is more chunked data available.
|
||||
@@ -454,9 +539,9 @@ Key Value Default Description
|
||||
**Keys for deleting images**
|
||||
-----------------------------------------------------------
|
||||
``d`` Single character. ``a`` What to delete.
|
||||
``(a, A, c, C, i,
|
||||
I, p, P, q, Q, x, X,
|
||||
y, Y, z, Z)``.
|
||||
``(a, A, c, C, n, N,
|
||||
i, I, p, P, q, Q, x,
|
||||
X, y, Y, z, Z)``.
|
||||
======= ==================== ========= =================
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ System and data visualisation tools
|
||||
`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
|
||||
-----------------------
|
||||
@@ -123,3 +126,6 @@ Miscellaneous
|
||||
`weechat-hints <https://github.com/GermainZ/kitty-weechat-hints>`_
|
||||
URL hints kitten for WeeChat that works without having to use WeeChat's
|
||||
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
|
||||
|
||||
from typing import List
|
||||
from kitty.boss import Boss
|
||||
|
||||
def main(args: List[str]) -> str:
|
||||
# this is the main entry point of the kitten, it will be executed in
|
||||
# the overlay window when the kitten is launched
|
||||
@@ -28,7 +31,7 @@ your machine).
|
||||
# handle_result() function
|
||||
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
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
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
|
||||
: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
|
||||
options and modes of operation, see below. You can use these options to
|
||||
create mappings in :file:`kitty.conf` to select various different text
|
||||
snippets. See :sc:`insert_selected_path` for examples.
|
||||
|
||||
|
||||
Completely customizing the matching and actions of the kitten
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
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
|
||||
advantage that you don't need to use ``allow_remote_control`` to make it work.
|
||||
keyboard shortcut (:sc:`kitty_shell` by default).
|
||||
|
||||
.. 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
|
||||
@@ -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
|
||||
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
|
||||
--------------------------------------------------
|
||||
|
||||
@@ -144,8 +152,24 @@ you can map it in :file:`kitty.conf`. For example::
|
||||
|
||||
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
|
||||
as the syntax for what follows :code:`kitty @` above. You do not need
|
||||
to enable remote control to use these mappings.
|
||||
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
|
||||
|
||||
@@ -251,12 +251,15 @@ def graphics_parser() -> None:
|
||||
flag = frozenset
|
||||
keymap: KeymapType = {
|
||||
'a': ('action', flag('tTqpd')),
|
||||
'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')),
|
||||
'd': ('delete_action', flag('aAiIcCnNpPqQxXyYzZ')),
|
||||
't': ('transmission_type', flag('dfts')),
|
||||
'o': ('compressed', flag('z')),
|
||||
'f': ('format', 'uint'),
|
||||
'm': ('more', 'uint'),
|
||||
'i': ('id', 'uint'),
|
||||
'I': ('image_number', 'uint'),
|
||||
'p': ('placement_id', 'uint'),
|
||||
'q': ('quiet', 'uint'),
|
||||
'w': ('width', 'uint'),
|
||||
'h': ('height', '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 retina;
|
||||
bool in_traditional_fullscreen;
|
||||
bool titlebar_hidden;
|
||||
unsigned long pre_full_screen_style_mask;
|
||||
|
||||
// Cached window properties to filter out duplicate events
|
||||
int width, height;
|
||||
|
||||
@@ -1314,6 +1314,16 @@ void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c,
|
||||
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
|
||||
{
|
||||
@@ -2062,18 +2072,38 @@ bool _glfwPlatformToggleFullscreen(_GLFWwindow* w, unsigned int flags) {
|
||||
bool made_fullscreen = true;
|
||||
bool traditional = !(flags & 1);
|
||||
NSWindowStyleMask sm = [window styleMask];
|
||||
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
||||
if (traditional) {
|
||||
if (!(in_fullscreen)) {
|
||||
sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen;
|
||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
|
||||
if (@available(macOS 10.16, *)) {
|
||||
// As of Big Turd NSWindowStyleMaskFullScreen is no longer useable
|
||||
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 {
|
||||
made_fullscreen = false;
|
||||
sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen);
|
||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
|
||||
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
||||
if (!(in_fullscreen)) {
|
||||
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 {
|
||||
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
||||
if (in_fullscreen) made_fullscreen = false;
|
||||
[window toggleFullScreen: nil];
|
||||
}
|
||||
@@ -2273,6 +2303,29 @@ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
|
||||
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) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
|
||||
@@ -199,6 +199,7 @@ def generate_wrappers(glfw_header: str) -> None:
|
||||
functions.append(Function(decl))
|
||||
for line in '''\
|
||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
||||
void glfwHideCocoaTitlebar(GLFWwindow* window, bool yes)
|
||||
void* glfwGetNSGLContext(GLFWwindow *window)
|
||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||
|
||||
10
glfw/wl_init.c
vendored
10
glfw/wl_init.c
vendored
@@ -445,19 +445,17 @@ static void keyboardHandleKey(void* data UNUSED,
|
||||
|
||||
_glfw.wl.serial = serial;
|
||||
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))
|
||||
{
|
||||
_glfw.wl.keyRepeatInfo.key = key;
|
||||
repeatable = true;
|
||||
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
||||
}
|
||||
if (repeatable) {
|
||||
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,
|
||||
|
||||
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
|
||||
@@ -338,8 +338,8 @@ class DiffHandler(Handler):
|
||||
if candidate.image.image_id == image_id:
|
||||
q = self.xpos_for_image(row, candidate, is_left)
|
||||
if q is not None:
|
||||
pl['x'] = q[0]
|
||||
pl['y'] = row
|
||||
pl.x = q[0]
|
||||
pl.y = row
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ class Hints(Handler):
|
||||
self.all_marks = all_marks
|
||||
self.ignore_mark_indices: Set[int] = set()
|
||||
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.match_suffix = self.get_match_suffix(args)
|
||||
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
|
||||
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
||||
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
|
||||
@@ -539,13 +539,13 @@ the form key=value.
|
||||
default=self
|
||||
type=choice
|
||||
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
|
||||
arguments are provided to the kitten, for example:
|
||||
:code:`kitty + kitten hints --type=linenum vim +{line} {path}`
|
||||
will open the matched path at the matched line number in vim. This option
|
||||
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.
|
||||
arguments are provided to the kitten, for example: :code:`kitty + kitten hints
|
||||
--type=linenum --linenum-action=tab vim +{line} {path}` will open the matched
|
||||
path at the matched line number in vim in a new kitty tab.
|
||||
|
||||
|
||||
--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.
|
||||
|
||||
|
||||
--window-title
|
||||
The window title for the hints window, default title is selected based on
|
||||
the type of text being hinted.
|
||||
'''.format(
|
||||
default_regex=DEFAULT_REGEX,
|
||||
line='{{line}}', path='{{path}}'
|
||||
|
||||
@@ -15,8 +15,7 @@ from typing import (
|
||||
)
|
||||
|
||||
from kitty.typing import (
|
||||
CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType,
|
||||
TypedDict
|
||||
CompletedProcess, GRT_a, GRT_d, GRT_f, GRT_m, GRT_o, GRT_t, HandlerType
|
||||
)
|
||||
from kitty.utils import ScreenSize, fit_image
|
||||
|
||||
@@ -136,6 +135,7 @@ class GraphicsCommand:
|
||||
S: int = 0 # size of data to read from file
|
||||
O: int = 0 # offset of data to read from file
|
||||
i: int = 0 # image id
|
||||
p: int = 0 # placement id
|
||||
o: Optional[GRT_o] = None # type of compression
|
||||
m: GRT_m = 0 # 0 or 1 whether there is more chunked data
|
||||
x: int = 0 # left edge of image area to display
|
||||
@@ -173,10 +173,15 @@ class GraphicsCommand:
|
||||
setattr(self, k, defval)
|
||||
|
||||
|
||||
class Placement(TypedDict):
|
||||
class Placement:
|
||||
cmd: GraphicsCommand
|
||||
x: int
|
||||
y: int
|
||||
x: int = 0
|
||||
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:
|
||||
@@ -263,8 +268,8 @@ class ImageManager:
|
||||
skey = self.image_id_to_converted_data[image_id]
|
||||
self.transmit_image(image_data, image_id, *skey)
|
||||
with cursor(self.handler.write):
|
||||
self.handler.cmd.set_cursor_position(pl['x'], pl['y'])
|
||||
self.handler.cmd.gr_command(pl['cmd'])
|
||||
self.handler.cmd.set_cursor_position(pl.x, pl.y)
|
||||
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:
|
||||
path = os.path.abspath(path)
|
||||
@@ -316,7 +321,7 @@ class ImageManager:
|
||||
gc.i = image_id
|
||||
if src_rect is not None:
|
||||
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):
|
||||
self.handler.cmd.set_cursor_position(x, y)
|
||||
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.key_encoding import RELEASE, KeyEvent, key_defs as K
|
||||
|
||||
from .operations import RESTORE_CURSOR, SAVE_CURSOR, move_cursor_by
|
||||
|
||||
HOME = K['HOME']
|
||||
END = K['END']
|
||||
BACKSPACE = K['BACKSPACE']
|
||||
@@ -31,13 +33,28 @@ class LineEdit:
|
||||
before, after = self.current_input[:x], self.current_input[x:]
|
||||
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:
|
||||
write('\a')
|
||||
self.pending_bell = False
|
||||
write(prompt)
|
||||
write(self.current_input)
|
||||
write('\r\x1b[{}C'.format(self.cursor_pos + wcswidth(prompt)))
|
||||
text = prompt + self.current_input
|
||||
cursor_pos = 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:
|
||||
if self.current_input:
|
||||
|
||||
@@ -181,11 +181,14 @@ class SignalManager:
|
||||
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:
|
||||
|
||||
def __init__(
|
||||
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:
|
||||
@@ -299,7 +302,7 @@ class Loop:
|
||||
def _on_apc(self, apc: str) -> None:
|
||||
if apc.startswith('K'):
|
||||
try:
|
||||
k = decode_key_event(apc)
|
||||
k = decode_key_event(apc[1:])
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
@@ -321,13 +324,17 @@ class Loop:
|
||||
self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:])
|
||||
del self.write_buf[self.iov_limit:]
|
||||
sizes = tuple(map(len, self.write_buf))
|
||||
try:
|
||||
written = os.writev(fd, self.write_buf)
|
||||
except BlockingIOError:
|
||||
return
|
||||
if not written:
|
||||
raise EOFError('The output stream is closed')
|
||||
if written >= sum(sizes):
|
||||
total_size = sum(sizes)
|
||||
if total_size:
|
||||
try:
|
||||
written = os.writev(fd, self.write_buf)
|
||||
except BlockingIOError:
|
||||
return
|
||||
if not written:
|
||||
raise EOFError('The output stream is closed')
|
||||
else:
|
||||
written = 0
|
||||
if written >= total_size:
|
||||
self.write_buf: List[bytes] = []
|
||||
self.asycio_loop.remove_writer(fd)
|
||||
self.waiting_for_writes = False
|
||||
|
||||
@@ -68,6 +68,11 @@ def clear_screen() -> str:
|
||||
return '\033[H\033[2J'
|
||||
|
||||
|
||||
@cmd
|
||||
def clear_to_end_of_screen() -> str:
|
||||
return '\033[J'
|
||||
|
||||
|
||||
@cmd
|
||||
def clear_to_eol() -> str:
|
||||
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)
|
||||
|
||||
|
||||
@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
|
||||
def set_cursor_shape(shape: str = 'block', blink: bool = True) -> str:
|
||||
val = {'block': 1, 'underline': 3, 'bar': 5}.get(shape, 1)
|
||||
@@ -253,7 +264,7 @@ def init_state(alternate_screen: bool = True) -> str:
|
||||
ans = (
|
||||
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
|
||||
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') +
|
||||
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING') +
|
||||
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
|
||||
)
|
||||
if alternate_screen:
|
||||
ans += set_mode('ALTERNATE_SCREEN')
|
||||
ans += set_mode('ALTERNATE_SCREEN') + reset_mode('DECOM')
|
||||
ans += clear_screen()
|
||||
return ans
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ from contextlib import suppress
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
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
|
||||
|
||||
@@ -363,9 +364,17 @@ class Boss:
|
||||
c = command_for_name(cmd)
|
||||
opts, items = parse_subcommand_cli(c, 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 {}))
|
||||
except (Exception, SystemExit) as e:
|
||||
self.show_error(_('remote_control mapping failed'), str(e))
|
||||
import types
|
||||
if isinstance(cast(types.GeneratorType, payload), types.GeneratorType):
|
||||
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]:
|
||||
cmd_prefix = b'\x1bP@kitty-cmd'
|
||||
@@ -1459,15 +1468,45 @@ class Boss:
|
||||
self._cleanup_tab_after_window_removal(tab)
|
||||
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:
|
||||
if not args or args[0] == 'new':
|
||||
return self._move_window_to(target_os_window_id='new')
|
||||
if args[0] == 'new-tab':
|
||||
return self._move_window_to(target_tab_id='new')
|
||||
lines = [
|
||||
'Choose a tab to move the window to',
|
||||
''
|
||||
]
|
||||
title = 'Choose a tab to move the window to'
|
||||
lines = [title, '']
|
||||
fmt = ': {1}'
|
||||
tab_id_map: Dict[int, Optional[Union[str, int]]] = {}
|
||||
current_tab = self.active_tab
|
||||
@@ -1503,7 +1542,7 @@ class Boss:
|
||||
self._run_kitten(
|
||||
'hints', args=(
|
||||
'--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
|
||||
)
|
||||
|
||||
@@ -1511,10 +1550,8 @@ class Boss:
|
||||
if not args or args[0] == 'new':
|
||||
return self._move_tab_to()
|
||||
|
||||
lines = [
|
||||
'Choose an OS window to move the tab to',
|
||||
''
|
||||
]
|
||||
title = 'Choose an OS window to move the tab to'
|
||||
lines = [title, '']
|
||||
fmt = ': {1}'
|
||||
os_window_id_map: Dict[int, Optional[int]] = {}
|
||||
current_os_window = getattr(self.active_tab, 'os_window_id', 0)
|
||||
@@ -1551,7 +1588,7 @@ class Boss:
|
||||
self._run_kitten(
|
||||
'hints', args=(
|
||||
'--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
|
||||
)
|
||||
|
||||
|
||||
@@ -156,9 +156,12 @@ def default_env() -> Dict[str, str]:
|
||||
|
||||
def set_default_env(val: Optional[Dict[str, str]] = None) -> None:
|
||||
env = process_env().copy()
|
||||
has_lctype = False
|
||||
if val:
|
||||
has_lctype = 'LC_CTYPE' in val
|
||||
env.update(val)
|
||||
setattr(default_env, 'env', env)
|
||||
setattr(default_env, 'lc_ctype_set_by_user', has_lctype)
|
||||
|
||||
|
||||
def openpty() -> Tuple[int, int]:
|
||||
@@ -210,7 +213,8 @@ class Child:
|
||||
env: Optional[Dict[str, str]] = getattr(self, '_final_env', None)
|
||||
if env is None:
|
||||
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']
|
||||
env.update(self.env)
|
||||
env['TERM'] = self.opts.term
|
||||
|
||||
@@ -13,7 +13,7 @@ class CLIOptions:
|
||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
||||
QueryTerminalCLIOptions = CLIOptions
|
||||
QueryTerminalCLIOptions = BroadcastCLIOptions = CLIOptions
|
||||
|
||||
|
||||
def generate_stub() -> None:
|
||||
@@ -48,6 +48,9 @@ def generate_stub() -> None:
|
||||
from kittens.hints.main import OPTIONS
|
||||
do(OPTIONS(), 'HintsCLIOptions')
|
||||
|
||||
from kittens.broadcast.main import OPTIONS
|
||||
do(OPTIONS(), 'BroadcastCLIOptions')
|
||||
|
||||
from kittens.icat.main import options_spec
|
||||
do(options_spec(), 'IcatCLIOptions')
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import sys
|
||||
from contextlib import suppress
|
||||
from typing import Any
|
||||
from functools import partial
|
||||
|
||||
|
||||
CSI = '\033['
|
||||
@@ -50,10 +51,26 @@ def screen_cursor_forward(amt: int) -> None:
|
||||
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:
|
||||
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:
|
||||
w = '()'[int(which)]
|
||||
t = chr(int(to))
|
||||
@@ -161,6 +178,14 @@ def report_device_attributes(mode: int, char: int) -> None:
|
||||
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:
|
||||
if string:
|
||||
string = ';' + string
|
||||
@@ -168,6 +193,8 @@ def write_osc(code: int, string: str = '') -> None:
|
||||
|
||||
|
||||
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:
|
||||
|
||||
@@ -615,30 +615,6 @@ cocoa_hide_window_title(void *w)
|
||||
} // 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
|
||||
cocoa_system_beep(void) {
|
||||
NSBeep();
|
||||
|
||||
@@ -21,6 +21,7 @@ from .conf.utils import (
|
||||
)
|
||||
from .config_data import all_options, parse_mods, type_convert
|
||||
from .constants import cache_dir, defconf, is_macos
|
||||
from .fonts import FontFeature
|
||||
from .key_names import get_key_name_lookup, key_name_aliases
|
||||
from .options_stub import Options as OptionsStub
|
||||
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))
|
||||
|
||||
|
||||
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
|
||||
def handle_font_features(key: str, val: str, ans: Dict[str, Any]) -> 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
|
||||
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`:
|
||||
|
||||
.. 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=_('''
|
||||
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:
|
||||
q = raw.lower()
|
||||
@@ -862,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`.
|
||||
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.
|
||||
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.
|
||||
'''))
|
||||
|
||||
|
||||
@@ -873,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
|
||||
'''))
|
||||
|
||||
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 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
|
||||
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.
|
||||
'''))
|
||||
|
||||
|
||||
@@ -932,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
|
||||
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.
|
||||
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=_('''
|
||||
Template to use for active tabs, if not specified falls back
|
||||
|
||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
||||
|
||||
|
||||
appname: str = 'kitty'
|
||||
version: Version = Version(0, 19, 2)
|
||||
version: Version = Version(0, 19, 3)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -193,9 +193,8 @@ typedef struct {
|
||||
} HistoryBufSegment;
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buffer;
|
||||
size_t buffer_size, max_sz;
|
||||
size_t start, length;
|
||||
void *ringbuf;
|
||||
size_t maximum_size;
|
||||
bool rewrap_needed;
|
||||
} PagerHistoryBuf;
|
||||
|
||||
|
||||
@@ -410,6 +410,7 @@ class FontConfigPattern(TypedDict):
|
||||
postscript_name: str
|
||||
style: str
|
||||
spacing: str
|
||||
fontfeatures: List[str]
|
||||
weight: int
|
||||
width: int
|
||||
slant: int
|
||||
@@ -441,6 +442,12 @@ def fc_match(
|
||||
pass
|
||||
|
||||
|
||||
def fc_match_postscript_name(
|
||||
postscript_name: str
|
||||
) -> FontConfigPattern:
|
||||
pass
|
||||
|
||||
|
||||
class CoreTextFont(TypedDict):
|
||||
path: str
|
||||
postscript_name: str
|
||||
@@ -1002,6 +1009,9 @@ class Screen:
|
||||
def draw(self, text: str) -> None:
|
||||
pass
|
||||
|
||||
def apply_sgr(self, text: str) -> None:
|
||||
pass
|
||||
|
||||
def copy_colors_from(self, other: 'Screen') -> None:
|
||||
pass
|
||||
|
||||
|
||||
@@ -27,17 +27,33 @@ pyspacing(int val) {
|
||||
|
||||
static inline PyObject*
|
||||
pattern_as_dict(FcPattern *pat) {
|
||||
PyObject *ans = PyDict_New();
|
||||
PyObject *ans = PyDict_New(), *p = NULL, *list = NULL;
|
||||
if (ans == NULL) return NULL;
|
||||
|
||||
#define PS(x) PyUnicode_Decode((const char*)x, strlen((const char*)x), "UTF-8", "replace")
|
||||
|
||||
#define G(type, get, which, conv, name) { \
|
||||
type out; PyObject *p; \
|
||||
type out; \
|
||||
if (get(pat, which, 0, &out) == FcResultMatch) { \
|
||||
p = conv(out); if (p == NULL) { Py_CLEAR(ans); return NULL; } \
|
||||
if (PyDict_SetItemString(ans, #name, p) != 0) { Py_CLEAR(p); Py_CLEAR(ans); return NULL; } \
|
||||
p = conv(out); if (p == NULL) goto exit; \
|
||||
if (PyDict_SetItemString(ans, #name, p) != 0) goto exit; \
|
||||
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 LS(which, key) L(FcChar8*, FcPatternGetString, which, PS, key)
|
||||
#define I(which, key) G(int, FcPatternGetInteger, which, PyLong_FromLong, key)
|
||||
#define B(which, key) G(int, FcPatternGetBool, which, pybool, 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_FULLNAME, full_name);
|
||||
S(FC_POSTSCRIPT_NAME, postscript_name);
|
||||
LS(FC_FONT_FEATURES, fontfeatures);
|
||||
I(FC_WEIGHT, weight);
|
||||
I(FC_WIDTH, width)
|
||||
I(FC_SLANT, slant);
|
||||
@@ -58,6 +75,10 @@ pattern_as_dict(FcPattern *pat) {
|
||||
B(FC_OUTLINE, outline);
|
||||
B(FC_COLOR, color);
|
||||
E(FC_SPACING, spacing, pyspacing);
|
||||
exit:
|
||||
if (PyErr_Occurred()) Py_CLEAR(ans);
|
||||
Py_CLEAR(p);
|
||||
Py_CLEAR(list);
|
||||
|
||||
return ans;
|
||||
#undef PS
|
||||
@@ -66,6 +87,8 @@ pattern_as_dict(FcPattern *pat) {
|
||||
#undef B
|
||||
#undef E
|
||||
#undef G
|
||||
#undef L
|
||||
#undef LS
|
||||
}
|
||||
|
||||
static inline PyObject*
|
||||
@@ -179,6 +202,27 @@ end:
|
||||
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*
|
||||
specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE fg) {
|
||||
PyObject *p = PyDict_GetItemString(base_descriptor, "path"), *ans = NULL;
|
||||
@@ -224,6 +268,7 @@ end:
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(fc_list, METH_VARARGS),
|
||||
METHODB(fc_match, METH_VARARGS),
|
||||
METHODB(fc_match_postscript_name, METH_VARARGS),
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@@ -9,3 +9,11 @@ class ListedFont(TypedDict):
|
||||
full_name: str
|
||||
postscript_name: str
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
if horizontal:
|
||||
x_range = range(0, width)
|
||||
thickness = max(1, height // 8)
|
||||
y_start = min(which * thickness, height - 2)
|
||||
y_range = range(y_start, height if which == 7 else min(y_start + thickness, height))
|
||||
y_range = eight_range(height, which)
|
||||
else:
|
||||
y_range = range(0, height)
|
||||
thickness = max(1, width // 8)
|
||||
x_start = min(which * thickness, width - 2)
|
||||
x_range = range(x_start, width if which == 7 else min(x_start + thickness, width))
|
||||
x_range = eight_range(width, which)
|
||||
for y in y_range:
|
||||
offset = y * width
|
||||
for x in x_range:
|
||||
@@ -940,10 +955,9 @@ for i in range(256):
|
||||
|
||||
c = 0x1fb00
|
||||
for i in range(1, 63):
|
||||
if i in (20, 40):
|
||||
continue
|
||||
box_chars[chr(c)] = [p(sextant, which=i)]
|
||||
c += 1
|
||||
if i not in (21, 42):
|
||||
box_chars[chr(c)] = [p(sextant, which=i)]
|
||||
c += 1
|
||||
|
||||
for i in range(1, 7):
|
||||
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}
|
||||
|
||||
|
||||
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:
|
||||
q = re.sub(r'\s+', ' ', family.lower())
|
||||
font_map = all_fonts_map()
|
||||
|
||||
@@ -8,12 +8,14 @@ from typing import Dict, Generator, List, Optional, Tuple, cast
|
||||
|
||||
from kitty.fast_data_types import (
|
||||
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.typing import FontConfigPattern
|
||||
from kitty.utils import log_error
|
||||
|
||||
from . import ListedFont
|
||||
from . import ListedFont, FontFeature
|
||||
|
||||
attr_map = {(False, False): 'font_family',
|
||||
(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)
|
||||
|
||||
|
||||
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:
|
||||
q = family_name_to_key(family)
|
||||
font_map = all_fonts_map(monospaced)
|
||||
|
||||
@@ -25,9 +25,9 @@ from kitty.typing import CoreTextFont, FontConfigPattern
|
||||
from kitty.utils import log_error
|
||||
|
||||
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:
|
||||
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]
|
||||
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)
|
||||
sm = create_symbol_map(opts)
|
||||
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:
|
||||
dump_faces(ftypes, indices)
|
||||
set_font_data(
|
||||
render_box_drawing, prerender_function, descriptor_for_idx,
|
||||
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 **) (&glfwHideCocoaTitlebar_impl) = dlsym(handle, "glfwHideCocoaTitlebar");
|
||||
|
||||
*(void **) (&glfwGetNSGLContext_impl) = dlsym(handle, "glfwGetNSGLContext");
|
||||
|
||||
*(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;
|
||||
#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*);
|
||||
GFW_EXTERN glfwGetNSGLContext_func 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 void cocoa_create_global_menu(void);
|
||||
extern void cocoa_hide_window_title(void *w);
|
||||
extern void cocoa_hide_titlebar(void *w);
|
||||
extern void cocoa_system_beep(void);
|
||||
extern void cocoa_set_activation_policy(bool);
|
||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
||||
@@ -695,7 +694,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
#ifdef __APPLE__
|
||||
if (glfwGetCocoaWindow) {
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
remove_image(GraphicsManager *self, size_t 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
|
||||
}
|
||||
|
||||
static char add_response[512] = {0};
|
||||
static bool has_add_respose = false;
|
||||
static char command_response[512] = {0};
|
||||
|
||||
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_start(args, fmt);
|
||||
size_t sz = sizeof(add_response)/sizeof(add_response[0]);
|
||||
int num = snprintf(add_response, sz, "%s:", code);
|
||||
vsnprintf(add_response + num, sz - num, fmt, args);
|
||||
const size_t sz = sizeof(command_response)/sizeof(command_response[0]);
|
||||
const int num = snprintf(command_response, sz, "%s:", code);
|
||||
vsnprintf(command_response + num, sz - num, fmt, args);
|
||||
va_end(args);
|
||||
has_add_respose = true;
|
||||
}
|
||||
|
||||
// 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
|
||||
mmap_img_file(GraphicsManager UNUSED *self, Image *img, int fd, size_t sz, off_t offset) {
|
||||
@@ -220,7 +228,7 @@ err:
|
||||
|
||||
static void
|
||||
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
|
||||
@@ -308,12 +316,38 @@ find_or_create_image(GraphicsManager *self, uint32_t id, bool *existing) {
|
||||
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*
|
||||
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)
|
||||
has_add_respose = false;
|
||||
bool existing, init_img = true;
|
||||
Image *img = NULL;
|
||||
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 {
|
||||
img->internal_id = internal_id_counter++;
|
||||
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->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);
|
||||
if (tt == 's') fd = shm_open(fname, O_RDONLY, 0);
|
||||
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);
|
||||
safe_close(fd, __FILE__, __LINE__);
|
||||
if (tt == 't') {
|
||||
@@ -475,15 +514,26 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
create_add_response(GraphicsManager UNUSED *self, bool data_loaded, uint32_t iid) {
|
||||
static char rbuf[sizeof(add_response)/sizeof(add_response[0]) + 64];
|
||||
if (iid) {
|
||||
if (!has_add_respose) {
|
||||
finish_command_response(const GraphicsCommand *g, bool data_loaded, uint32_t iid, uint32_t placement_id, uint32_t image_number) {
|
||||
static char rbuf[sizeof(command_response)/sizeof(command_response[0]) + 128];
|
||||
bool is_ok_response = !command_response[0];
|
||||
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;
|
||||
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;
|
||||
#undef print
|
||||
}
|
||||
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) {
|
||||
has_add_respose = false;
|
||||
if (img == NULL) 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; }
|
||||
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) {
|
||||
if (g->id) img = img_by_client_id(self, g->id);
|
||||
else if (g->image_number) img = img_by_client_number(self, g->image_number);
|
||||
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);
|
||||
*is_dirty = true;
|
||||
self->layers_dirty = true;
|
||||
ImageRef *ref = NULL;
|
||||
for (size_t i=0; i < img->refcnt; i++) {
|
||||
if ((unsigned)img->refs[i].start_row == c->x && (unsigned)img->refs[i].start_column == c->y) {
|
||||
ref = img->refs + i;
|
||||
break;
|
||||
if (g->placement_id && img->client_id) {
|
||||
for (size_t i=0; i < img->refcnt; i++) {
|
||||
if (img->refs[i].client_id == g->placement_id) {
|
||||
ref = img->refs + i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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_y_offset = MIN(g->cell_y_offset, cell.height - 1);
|
||||
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_dest_rect(ref, g->num_cells, g->num_lines, cell);
|
||||
// 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;
|
||||
return img->client_id;
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -645,7 +701,8 @@ grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float scree
|
||||
// Image lifetime/scrolling {{{
|
||||
|
||||
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;) {
|
||||
Image *img = self->images + i;
|
||||
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)) {
|
||||
remove_i_from_array(img->refs, j, img->refcnt);
|
||||
self->layers_dirty = true;
|
||||
matched = true;
|
||||
}
|
||||
}
|
||||
if (img->refcnt == 0 && (free_images || img->client_id == 0)) remove_image(self, i);
|
||||
if (only_first_image && matched) break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
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;) {
|
||||
@@ -739,15 +799,24 @@ clear_all_filter_func(const ImageRef *ref UNUSED, Image UNUSED *img, const void
|
||||
|
||||
void
|
||||
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
|
||||
id_filter_func(const ImageRef UNUSED *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
uint32_t iid = *(uint32_t*)data;
|
||||
return img->client_id == iid;
|
||||
id_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
const GraphicsCommand *g = data;
|
||||
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
|
||||
x_filter_func(const ImageRef *ref, Image UNUSED *img, const void *data, CellPixelSize cell UNUSED) {
|
||||
const GraphicsCommand *g = data;
|
||||
@@ -780,14 +849,15 @@ point3d_filter_func(const ImageRef *ref, Image *img, const void *data, CellPixel
|
||||
|
||||
static void
|
||||
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) {
|
||||
#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 G(l, u, func) D(l, u, g, func)
|
||||
case 0:
|
||||
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('q', 'Q', point3d_filter_func);
|
||||
G('x', 'X', x_filter_func);
|
||||
@@ -797,6 +867,10 @@ handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c
|
||||
case 'C':
|
||||
d.x_offset = c->x + 1; d.y_offset = c->y + 1;
|
||||
I('C', &d, point_filter_func);
|
||||
case 'n':
|
||||
case 'N':
|
||||
only_first_image = true;
|
||||
I('N', g, number_filter_func);
|
||||
default:
|
||||
REPORT_ERROR("Unknown graphics command delete action: %c", g->delete_action);
|
||||
break;
|
||||
@@ -832,6 +906,12 @@ grman_rescale(GraphicsManager *self, CellPixelSize cell) {
|
||||
const char*
|
||||
grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty, CellPixelSize cell) {
|
||||
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) {
|
||||
case 0:
|
||||
@@ -839,9 +919,11 @@ grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint
|
||||
case 'T':
|
||||
case 'q': {
|
||||
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);
|
||||
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);
|
||||
id_type added_image_id = image ? image->internal_id : 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;
|
||||
}
|
||||
case 'p':
|
||||
if (!g->id) {
|
||||
REPORT_ERROR("Put graphics command without image id");
|
||||
if (!g->id && !g->image_number) {
|
||||
REPORT_ERROR("Put graphics command without image id or number");
|
||||
break;
|
||||
}
|
||||
handle_put_command(self, g, c, is_dirty, NULL, cell);
|
||||
ret = create_add_response(self, true, g->id);
|
||||
uint32_t image_id = handle_put_command(self, g, c, is_dirty, NULL, cell);
|
||||
ret = finish_command_response(g, true, image_id, g->placement_id, g->image_number);
|
||||
break;
|
||||
case 'd':
|
||||
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*
|
||||
image_as_dict(Image *img) {
|
||||
#define U(x) #x, img->x
|
||||
return Py_BuildValue("{sI sI sI sI sK sI sO sO sN}",
|
||||
U(texture_id), U(client_id), U(width), U(height), U(internal_id), U(refcnt),
|
||||
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(client_number),
|
||||
"data_loaded", img->data_loaded ? 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)
|
||||
@@ -899,6 +981,13 @@ W(image_for_client_id) {
|
||||
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) {
|
||||
const char *name, *data;
|
||||
Py_ssize_t sz;
|
||||
@@ -949,6 +1038,7 @@ W(update_layers) {
|
||||
|
||||
static PyMethodDef methods[] = {
|
||||
M(image_for_client_id, METH_O),
|
||||
M(image_for_client_number, METH_O),
|
||||
M(update_layers, METH_VARARGS),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
typedef struct {
|
||||
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;
|
||||
int32_t z_index;
|
||||
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;
|
||||
int32_t z_index;
|
||||
int32_t start_row, start_column;
|
||||
uint32_t client_id;
|
||||
ImageRect src_rect;
|
||||
} ImageRef;
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint32_t texture_id, client_id, width, height;
|
||||
uint32_t texture_id, client_id, client_number, width, height;
|
||||
id_type internal_id;
|
||||
|
||||
bool data_loaded;
|
||||
|
||||
110
kitty/history.c
110
kitty/history.c
@@ -9,6 +9,7 @@
|
||||
#include "lineops.h"
|
||||
#include "charsets.h"
|
||||
#include <structmember.h>
|
||||
#include "ringbuf.h"
|
||||
|
||||
extern PyTypeObject Line_Type;
|
||||
#define SEGMENT_SIZE 2048
|
||||
@@ -61,42 +62,37 @@ alloc_pagerhist(size_t pagerhist_sz) {
|
||||
if (!pagerhist_sz) return NULL;
|
||||
ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf));
|
||||
if (!ph) return NULL;
|
||||
ph->max_sz = pagerhist_sz;
|
||||
ph->buffer_size = MIN(1024u*1024u, ph->max_sz);
|
||||
ph->buffer = PyMem_Malloc(ph->buffer_size);
|
||||
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
|
||||
size_t sz = MIN(1024u * 1024u, pagerhist_sz);
|
||||
ph->ringbuf = ringbuf_new(sz);
|
||||
if (!ph->ringbuf) { PyMem_Free(ph); return NULL; }
|
||||
ph->maximum_size = pagerhist_sz;
|
||||
return ph;
|
||||
}
|
||||
|
||||
static inline void
|
||||
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);
|
||||
self->pagerhist = NULL;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
||||
if (ph->buffer_size >= ph->max_sz) return false;
|
||||
size_t newsz = MIN(ph->max_sz, ph->buffer_size + MAX(1024u * 1024u, minsz));
|
||||
uint8_t *newbuf = PyMem_Malloc(newsz);
|
||||
size_t buffer_size = ringbuf_capacity(ph->ringbuf);
|
||||
if (buffer_size >= ph->maximum_size) return false;
|
||||
size_t newsz = MIN(ph->maximum_size, buffer_size + MAX(1024u * 1024u, minsz));
|
||||
ringbuf_t newbuf = ringbuf_new(newsz);
|
||||
if (!newbuf) return false;
|
||||
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
|
||||
if (copied) memcpy(newbuf, ph->buffer + ph->start, copied);
|
||||
if (copied < ph->length) memcpy(newbuf + copied, ph->buffer, (ph->length - copied));
|
||||
PyMem_Free(ph->buffer);
|
||||
ph->start = 0;
|
||||
ph->buffer = newbuf;
|
||||
ph->buffer_size = newsz;
|
||||
size_t count = ringbuf_bytes_used(ph->ringbuf);
|
||||
if (count) ringbuf_copy(newbuf, ph->ringbuf, count);
|
||||
ringbuf_free((ringbuf_t*)&ph->ringbuf);
|
||||
ph->ringbuf = newbuf;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void
|
||||
pagerhist_clear(HistoryBuf *self) {
|
||||
if (!self->pagerhist || !self->pagerhist->max_sz) return;
|
||||
index_type pagerhist_sz = self->pagerhist->max_sz;
|
||||
free_pagerhist(self);
|
||||
self->pagerhist = alloc_pagerhist(pagerhist_sz);
|
||||
if (self->pagerhist && self->pagerhist->ringbuf) ringbuf_reset(self->pagerhist->ringbuf);
|
||||
}
|
||||
|
||||
static HistoryBuf*
|
||||
@@ -188,36 +184,28 @@ historybuf_clear(HistoryBuf *self) {
|
||||
|
||||
static inline bool
|
||||
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 > ph->buffer_size - ph->length) pagerhist_extend(ph, sz);
|
||||
if (sz > ph->buffer_size) return false;
|
||||
size_t start_writing_at = (ph->start + ph->length) % ph->buffer_size;
|
||||
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));
|
||||
size_t space_in_ringbuf = ringbuf_bytes_free(ph->ringbuf);
|
||||
if (sz > space_in_ringbuf) pagerhist_extend(ph, sz);
|
||||
ringbuf_memcpy_into(ph->ringbuf, buf, sz);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
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;
|
||||
size_t pos = ph->start, count = 0;
|
||||
size_t count = 0;
|
||||
size_t last_reject_at = 0;
|
||||
while (count < ph->length) {
|
||||
decode_utf8(&state, &codep, ph->buffer[pos]);
|
||||
count++;
|
||||
while (count < num) {
|
||||
decode_utf8(&state, &codep, scratch[count++]);
|
||||
if (state == UTF8_ACCEPT) break;
|
||||
if (state == UTF8_REJECT) { state = UTF8_ACCEPT; last_reject_at = count; }
|
||||
pos = pos == ph->buffer_size - 1 ? 0: pos + 1;
|
||||
}
|
||||
if (last_reject_at) {
|
||||
ph->start = (ph->start + last_reject_at) % ph->buffer_size;
|
||||
ph->length -= last_reject_at;
|
||||
ringbuf_memmove_from(scratch, ph->ringbuf, last_reject_at);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -241,7 +229,7 @@ pagerhist_push(HistoryBuf *self, ANSIBuf *as_ansi_buf) {
|
||||
Line l = {.xnum=self->xnum};
|
||||
init_line(self, self->start_of_data, &l);
|
||||
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);
|
||||
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; }
|
||||
|
||||
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;
|
||||
*count = 0;
|
||||
while (true) {
|
||||
decode_utf8(&state, &codep, ph->buffer[pos]);
|
||||
record[(*count)++] = ph->buffer[pos];
|
||||
size_t num = ringbuf_bytes_used(ph->ringbuf);
|
||||
while (num--) {
|
||||
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_ACCEPT) break;
|
||||
pos = pos == ph->buffer_size - 1 ? 0 : (pos + 1);
|
||||
}
|
||||
return codep;
|
||||
}
|
||||
@@ -351,14 +340,12 @@ pagerhist_read_char(PagerHistoryBuf *ph, size_t pos, unsigned *count, uint8_t re
|
||||
static void
|
||||
pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
||||
PagerHistoryBuf *ph = self->pagerhist;
|
||||
if (!ph->length) return;
|
||||
if (!ph->ringbuf || !ringbuf_bytes_used(ph->ringbuf)) return;
|
||||
PagerHistoryBuf *nph = PyMem_Calloc(sizeof(PagerHistoryBuf), 1);
|
||||
if (!nph) return;
|
||||
nph->buffer_size = ph->buffer_size;
|
||||
nph->max_sz = ph->max_sz;
|
||||
nph->buffer = PyMem_Malloc(nph->buffer_size);
|
||||
if (!nph->buffer) { PyMem_Free(nph); return ; }
|
||||
size_t i = 0, pos;
|
||||
nph->maximum_size = ph->maximum_size;
|
||||
nph->ringbuf = ringbuf_new(MIN(ph->maximum_size, ringbuf_capacity(ph->ringbuf) + 4096));
|
||||
if (!nph->ringbuf) { PyMem_Free(nph); return ; }
|
||||
ssize_t ch_width = 0;
|
||||
unsigned count;
|
||||
uint8_t record[8];
|
||||
@@ -367,11 +354,6 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
||||
WCSState 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() { \
|
||||
if (num_in_current_line + ch_width > cells_in_line) { \
|
||||
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); \
|
||||
}
|
||||
|
||||
for (i = 0; i < ph->length;) {
|
||||
pos = ph->start + i;
|
||||
if (pos >= ph->buffer_size) pos = pos - ph->buffer_size;
|
||||
READ_CHAR(ch);
|
||||
while (ringbuf_bytes_used(ph->ringbuf)) {
|
||||
ch = pagerhist_remove_char(ph, &count, record);
|
||||
if (ch == '\n') {
|
||||
initialize_wcs_state(&wcs_state);
|
||||
ch_width = 1;
|
||||
@@ -397,12 +377,12 @@ pagerhist_rewrap_to(HistoryBuf *self, index_type cells_in_line) {
|
||||
}
|
||||
free_pagerhist(self);
|
||||
self->pagerhist = nph;
|
||||
#undef READ_CHAR
|
||||
#undef WRITE_CHAR
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
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));
|
||||
else if (PyUnicode_Check(what) && PyUnicode_READY(what) == 0) {
|
||||
Py_UCS4 *buf = PyUnicode_AsUCS4Copy(what);
|
||||
@@ -418,19 +398,17 @@ pagerhist_write(HistoryBuf *self, PyObject *what) {
|
||||
static PyObject*
|
||||
pagerhist_as_bytes(HistoryBuf *self, PyObject *args UNUSED) {
|
||||
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);
|
||||
if (ph->rewrap_needed) pagerhist_rewrap_to(self, self->xnum);
|
||||
|
||||
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;
|
||||
PyObject *ans = PyBytes_FromStringAndSize(NULL, sz);
|
||||
if (!ans) return NULL;
|
||||
uint8_t *buf = (uint8_t*)PyBytes_AS_STRING(ans);
|
||||
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
|
||||
if (copied) memcpy(buf, ph->buffer + ph->start, copied);
|
||||
if (copied < ph->length) memcpy(buf + copied, ph->buffer, (ph->length - copied));
|
||||
ringbuf_memcpy_from(buf, ph->ringbuf, sz);
|
||||
if (!l.continued) buf[sz-1] = '\n';
|
||||
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;
|
||||
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->count = 0; other->start_of_data = 0;
|
||||
index_type x = 0, y = 0;
|
||||
|
||||
39
kitty/key_encoding.py
generated
39
kitty/key_encoding.py
generated
@@ -417,6 +417,10 @@ def symbolic_name(glfw_name: str) -> str:
|
||||
return glfw_name[9:].replace('_', ' ')
|
||||
|
||||
|
||||
def glfw_key_name(symbolic_name: str) -> str:
|
||||
return 'GLFW_KEY_' + symbolic_name.replace(' ', '_')
|
||||
|
||||
|
||||
def update_encoding() -> None:
|
||||
import re
|
||||
import subprocess
|
||||
@@ -492,9 +496,9 @@ del key_name, enc
|
||||
|
||||
|
||||
def decode_key_event(text: str) -> KeyEvent:
|
||||
typ = type_map[text[1]]
|
||||
mods = mod_map[text[2]]
|
||||
key = key_rmap[text[3:5]]
|
||||
typ = type_map[text[0]]
|
||||
mods = mod_map[text[1]]
|
||||
key = key_rmap[text[2:4]]
|
||||
return KeyEvent(typ, mods, key)
|
||||
|
||||
|
||||
@@ -503,3 +507,32 @@ def encode_key_event(key_event: KeyEvent) -> str:
|
||||
mods = rmod_map[key_event.mods]
|
||||
key = ENCODING[key_event.key.replace('_', ' ')]
|
||||
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)
|
||||
|
||||
@@ -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:
|
||||
extra_env = opts.env.copy()
|
||||
if opts.editor == '.':
|
||||
editor = get_editor_from_env(os.environ)
|
||||
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':
|
||||
cli_opts.listen_on = expand_listen_on(cli_opts.listen_on, from_config_file)
|
||||
os.environ['KITTY_LISTEN_ON'] = cli_opts.listen_on
|
||||
set_default_env(extra_env)
|
||||
set_default_env(opts.env.copy())
|
||||
|
||||
|
||||
def set_locale() -> None:
|
||||
|
||||
@@ -368,7 +368,7 @@ HANDLER(handle_move_event) {
|
||||
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;
|
||||
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 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;
|
||||
|
||||
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
|
||||
|
||||
@@ -23,6 +24,9 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
format = 'f',
|
||||
more = 'm',
|
||||
id = 'i',
|
||||
image_number = 'I',
|
||||
placement_id = 'p',
|
||||
quiet = 'q',
|
||||
width = 'w',
|
||||
height = 'h',
|
||||
x_offset = 'x',
|
||||
@@ -69,6 +73,15 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
case id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case image_number:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case placement_id:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case quiet:
|
||||
value_state = UINT;
|
||||
break;
|
||||
case width:
|
||||
value_state = UINT;
|
||||
break;
|
||||
@@ -131,8 +144,8 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case action: {
|
||||
g.action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.action != 't' && g.action != 'd' && g.action != 'p' &&
|
||||
g.action != 'q' && g.action != 'T') {
|
||||
if (g.action != 'T' && g.action != 'q' && g.action != 'd' &&
|
||||
g.action != 'p' && g.action != 't') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for action: 0x%x",
|
||||
g.action);
|
||||
@@ -142,14 +155,15 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case delete_action: {
|
||||
g.delete_action = screen->parser_buf[pos++] & 0xff;
|
||||
if (g.delete_action != 'X' && g.delete_action != 'y' &&
|
||||
g.delete_action != 'i' && g.delete_action != 'I' &&
|
||||
g.delete_action != 'A' && g.delete_action != 'p' &&
|
||||
g.delete_action != 'Y' && g.delete_action != 'z' &&
|
||||
if (g.delete_action != 'C' && g.delete_action != 'z' &&
|
||||
g.delete_action != 'y' && g.delete_action != 'X' &&
|
||||
g.delete_action != 'a' && g.delete_action != 'P' &&
|
||||
g.delete_action != 'x' && g.delete_action != 'q' &&
|
||||
g.delete_action != 'Z' && g.delete_action != 'Q' &&
|
||||
g.delete_action != 'c' && g.delete_action != 'C') {
|
||||
g.delete_action != 'i' && g.delete_action != 'c' &&
|
||||
g.delete_action != 'N' && g.delete_action != 'I' &&
|
||||
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 "
|
||||
"value for delete_action: 0x%x",
|
||||
g.delete_action);
|
||||
@@ -159,7 +173,7 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
|
||||
case transmission_type: {
|
||||
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') {
|
||||
REPORT_ERROR("Malformed GraphicsCommand control block, unknown flag "
|
||||
"value for transmission_type: 0x%x",
|
||||
@@ -233,6 +247,9 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
U(format);
|
||||
U(more);
|
||||
U(id);
|
||||
U(image_number);
|
||||
U(placement_id);
|
||||
U(quiet);
|
||||
U(width);
|
||||
U(height);
|
||||
U(x_offset);
|
||||
@@ -303,11 +320,14 @@ static inline void parse_graphics_code(Screen *screen,
|
||||
}
|
||||
|
||||
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,
|
||||
"transmission_type", g.transmission_type, "compressed", g.compressed,
|
||||
"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.y_offset, "data_height", (unsigned int)g.data_height,
|
||||
"data_width", (unsigned int)g.data_width, "data_sz",
|
||||
|
||||
@@ -52,7 +52,6 @@ utf8(char_type codepoint) {
|
||||
// }}}
|
||||
|
||||
// Macros {{{
|
||||
#define MAX_PARAMS 256
|
||||
#define IS_DIGIT \
|
||||
case '0': \
|
||||
case '1': \
|
||||
@@ -465,7 +464,10 @@ repr_csi_params(unsigned int *params, unsigned int num_params) {
|
||||
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) {
|
||||
enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 };
|
||||
enum State state = START;
|
||||
|
||||
@@ -5,9 +5,13 @@
|
||||
import base64
|
||||
import os
|
||||
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.key_encoding import (
|
||||
WindowSystemKeyEvent, decode_key_event_as_window_system_key
|
||||
)
|
||||
from kitty.keys import interpret_key_event
|
||||
|
||||
from .base import (
|
||||
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
|
||||
match: A string indicating the window 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'
|
||||
desc = (
|
||||
@@ -34,6 +40,11 @@ class SendText(RemoteCommand):
|
||||
' only the currently active window.'
|
||||
)
|
||||
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
|
||||
type=bool-set
|
||||
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
|
||||
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.
|
||||
|
||||
|
||||
--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
|
||||
argspec = '[TEXT TO SEND]'
|
||||
|
||||
def message_to_kitty(self, global_opts: RCOptions, opts: 'CLIOptions', args: ArgsType) -> PayloadType:
|
||||
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]:
|
||||
if sys.stdin.isatty():
|
||||
ret['exclude_active'] = True
|
||||
import select
|
||||
fd = sys.stdin.fileno()
|
||||
keep_going = True
|
||||
@@ -109,28 +126,40 @@ are sent as is, not interpreted for escapes.
|
||||
return chain()
|
||||
|
||||
def response_from_kitty(self, boss: Boss, window: Optional[Window], payload_get: PayloadGetType) -> ResponseType:
|
||||
windows = [boss.active_window]
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = list(boss.match_windows(match))
|
||||
mt = payload_get('match_tab')
|
||||
if mt:
|
||||
windows = []
|
||||
tabs = tuple(boss.match_tabs(mt))
|
||||
if not tabs:
|
||||
raise MatchError(payload_get('match_tab'), 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
if payload_get('all'):
|
||||
windows: List[Optional[Window]] = list(boss.all_windows)
|
||||
else:
|
||||
windows = [boss.active_window]
|
||||
match = payload_get('match')
|
||||
if match:
|
||||
windows = list(boss.match_windows(match))
|
||||
mt = payload_get('match_tab')
|
||||
if mt:
|
||||
windows = []
|
||||
tabs = tuple(boss.match_tabs(mt))
|
||||
if not tabs:
|
||||
raise MatchError(payload_get('match_tab'), 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
encoding, _, q = payload_get('data').partition(':')
|
||||
if encoding == 'text':
|
||||
data = q.encode('utf-8')
|
||||
elif encoding == 'base64':
|
||||
data = base64.standard_b64decode(q)
|
||||
elif encoding == 'kitty-key':
|
||||
data = decode_key_event_as_window_system_key(q)
|
||||
else:
|
||||
raise TypeError(f'Invalid encoding for send-text data: {encoding}')
|
||||
exclude_active = payload_get('exclude_active')
|
||||
for window in windows:
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
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:
|
||||
global_opts, items = parse_rc_args(args)
|
||||
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())))
|
||||
opts, items = parse_subcommand_cli(c, 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:
|
||||
no_response = global_opts.no_command_response # type: ignore
|
||||
else:
|
||||
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:
|
||||
global_opts.to = os.environ['KITTY_LISTEN_ON']
|
||||
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) {
|
||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self, true, true);
|
||||
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);
|
||||
historybuf_clear(self->historybuf);
|
||||
clear_hyperlink_pool(self->hyperlink_pool);
|
||||
@@ -999,8 +1001,8 @@ index_selection(const Screen *self, Selections *selections, bool up) {
|
||||
if (self->overlay_line.is_active) deactivate_overlay_line(self); \
|
||||
linebuf_index(self->linebuf, top, bottom); \
|
||||
INDEX_GRAPHICS(-1) \
|
||||
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \
|
||||
/* Only add to history when no page margins have been set */ \
|
||||
if (self->linebuf == self->main_linebuf && self->margin_top == 0) { \
|
||||
/* Only add to history when no top margin has been set */ \
|
||||
linebuf_init_line(self->linebuf, bottom); \
|
||||
historybuf_add_line(self->historybuf, self->linebuf->line, &self->as_ansi_buf); \
|
||||
self->history_line_added_count++; \
|
||||
@@ -1096,14 +1098,13 @@ screen_linefeed(Screen *self) {
|
||||
|
||||
void
|
||||
screen_save_cursor(Screen *self) {
|
||||
SavepointBuffer *pts = self->linebuf == self->main_linebuf ? &self->main_savepoints : &self->alt_savepoints;
|
||||
Savepoint *sp;
|
||||
buffer_push(pts, sp);
|
||||
Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint;
|
||||
cursor_copy_to(self->cursor, &(sp->cursor));
|
||||
sp->mDECOM = self->modes.mDECOM;
|
||||
sp->mDECAWM = self->modes.mDECAWM;
|
||||
sp->mDECSCNM = self->modes.mDECSCNM;
|
||||
COPY_CHARSETS(self, sp);
|
||||
sp->is_valid = true;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1115,10 +1116,8 @@ screen_save_modes(Screen *self) {
|
||||
|
||||
void
|
||||
screen_restore_cursor(Screen *self) {
|
||||
SavepointBuffer *pts = self->linebuf == self->main_linebuf ? &self->main_savepoints : &self->alt_savepoints;
|
||||
Savepoint *sp;
|
||||
buffer_pop(pts, sp);
|
||||
if (sp == NULL) {
|
||||
Savepoint *sp = self->linebuf == self->main_linebuf ? &self->main_savepoint : &self->alt_savepoint;
|
||||
if (!sp->is_valid) {
|
||||
screen_cursor_position(self, 1, 1);
|
||||
screen_reset_mode(self, DECOM);
|
||||
RESET_CHARSETS;
|
||||
@@ -2138,6 +2137,20 @@ draw(Screen *self, PyObject *src) {
|
||||
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*
|
||||
reset_mode(Screen *self, PyObject *args) {
|
||||
int private = false;
|
||||
@@ -2784,6 +2797,7 @@ static PyMethodDef methods[] = {
|
||||
MND(visual_line, METH_VARARGS)
|
||||
MND(current_url_text, METH_NOARGS)
|
||||
MND(draw, METH_O)
|
||||
MND(apply_sgr, METH_O)
|
||||
MND(cursor_position, METH_VARARGS)
|
||||
MND(set_mode, METH_VARARGS)
|
||||
MND(reset_mode, METH_VARARGS)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "graphics.h"
|
||||
#include "monotonic.h"
|
||||
#define MAX_PARAMS 256
|
||||
|
||||
typedef enum ScrollTypes { SCROLL_LINE = -999999, SCROLL_PAGE, SCROLL_FULL } ScrollType;
|
||||
|
||||
@@ -58,16 +59,10 @@ typedef struct {
|
||||
bool use_latin1;
|
||||
Cursor cursor;
|
||||
bool mDECOM, mDECAWM, mDECSCNM;
|
||||
|
||||
bool is_valid;
|
||||
} Savepoint;
|
||||
|
||||
|
||||
typedef struct {
|
||||
Savepoint buf[SAVEPOINTS_SZ];
|
||||
index_type start_of_data, count;
|
||||
} SavepointBuffer;
|
||||
|
||||
|
||||
typedef struct {
|
||||
ScreenModes buf[SAVEPOINTS_SZ];
|
||||
index_type start_of_data, count;
|
||||
@@ -97,7 +92,7 @@ typedef struct {
|
||||
} last_rendered;
|
||||
bool use_latin1, is_dirty, scroll_changed, reload_all_gpu_data;
|
||||
Cursor *cursor;
|
||||
SavepointBuffer main_savepoints, alt_savepoints;
|
||||
Savepoint main_savepoint, alt_savepoint;
|
||||
SavemodesBuffer modes_savepoints;
|
||||
PyObject *callbacks, *test_child;
|
||||
LineBuf *linebuf, *main_linebuf, *alt_linebuf;
|
||||
|
||||
@@ -705,6 +705,7 @@ PYWRAP1(set_options) {
|
||||
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);
|
||||
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
|
||||
|
||||
@@ -72,6 +72,7 @@ typedef struct {
|
||||
UrlPrefix *values;
|
||||
size_t num, max_prefix_len;
|
||||
} url_prefixes;
|
||||
bool detect_urls;
|
||||
} Options;
|
||||
|
||||
typedef struct {
|
||||
|
||||
@@ -13,7 +13,7 @@ from .fast_data_types import (
|
||||
)
|
||||
from .layout.base import Rect
|
||||
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 .window import calculate_gl_geometry
|
||||
|
||||
@@ -61,6 +61,35 @@ def compile_template(template: str) -> Any:
|
||||
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:
|
||||
if tab.needs_attention and draw_data.bell_on_tab:
|
||||
fg = screen.cursor.fg
|
||||
@@ -81,13 +110,22 @@ def draw_title(draw_data: DrawData, screen: Screen, tab: TabBarData, index: int)
|
||||
'index': index,
|
||||
'layout_name': tab.layout_name,
|
||||
'num_windows': tab.num_windows,
|
||||
'title': tab.title
|
||||
'title': tab.title,
|
||||
'fmt': Formatter,
|
||||
}
|
||||
title = eval(compile_template(template), {'__builtins__': {}}, eval_locals)
|
||||
except Exception as e:
|
||||
report_template_failure(template, str(e))
|
||||
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:
|
||||
|
||||
@@ -721,6 +721,8 @@ class TabManager: # {{{
|
||||
break
|
||||
elif self.opts.tab_switch_strategy == 'left':
|
||||
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:
|
||||
next_active_tab = max(0, min(self.active_tab_idx, len(self.tabs) - 1))
|
||||
|
||||
@@ -241,7 +241,7 @@ def text_sanitizer(as_ansi: bool, add_wrap_markers: bool) -> Callable[[str], str
|
||||
pat = getattr(text_sanitizer, 'pat', None)
|
||||
if pat is None:
|
||||
import re
|
||||
pat = re.compile(r'\033\[.+?m')
|
||||
pat = re.compile('\033\\[.*?m')
|
||||
setattr(text_sanitizer, 'pat', pat)
|
||||
|
||||
ansi, wrap_markers = not as_ansi, not add_wrap_markers
|
||||
|
||||
@@ -48,6 +48,15 @@ def parse_response(res):
|
||||
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)))
|
||||
|
||||
|
||||
@@ -90,20 +99,23 @@ def put_helpers(self, cw, ch):
|
||||
s = self.create_screen(10, 5, cell_width=cw, cell_height=ch)
|
||||
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):
|
||||
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)
|
||||
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,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):
|
||||
nonlocal iid
|
||||
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
|
||||
res = send_command(screen, cmd, data)
|
||||
return iid, parse_response(res)
|
||||
return imgid, parse_response(res)
|
||||
|
||||
def put_ref(screen, **kw):
|
||||
cmd = 'a=p,i=%d,%s' % (iid, put_cmd(**kw))
|
||||
send_command(screen, cmd)
|
||||
imgid = kw.pop('id', None) or iid
|
||||
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):
|
||||
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
|
||||
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)
|
||||
|
||||
# Test simple load
|
||||
@@ -140,6 +153,8 @@ class TestGraphics(BaseTest):
|
||||
self.ae(l('mnop', m=0), 'OK')
|
||||
img = g.image_for_client_id(1)
|
||||
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
|
||||
random_data = byte_block(3 * 1024)
|
||||
@@ -216,6 +231,73 @@ class TestGraphics(BaseTest):
|
||||
# test error handling for loading bad png data
|
||||
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):
|
||||
cw, ch = 10, 20
|
||||
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)
|
||||
self.ae(l0[0]['group_count'], 1)
|
||||
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)
|
||||
self.ae(len(l2), 2)
|
||||
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')
|
||||
self.ae(s.grman.image_count, 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)
|
||||
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()
|
||||
put_image(s, cw, ch)
|
||||
put_image(s, cw, ch)
|
||||
@@ -335,3 +423,14 @@ class TestGraphics(BaseTest):
|
||||
put_image(s, cw, ch, z=9)
|
||||
delete('Z', z=9)
|
||||
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():
|
||||
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'
|
||||
' 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)
|
||||
p = k.pop('payload', '').encode('utf-8')
|
||||
k['payload_sz'] = len(p)
|
||||
@@ -395,11 +395,12 @@ class TestParser(BaseTest):
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
uint32_max = 2**32 - 1
|
||||
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')
|
||||
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='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('W=1', 'Malformed GraphicsCommand control block, invalid key character: 0x57')
|
||||
e('1=1', 'Malformed GraphicsCommand control block, invalid key character: 0x31')
|
||||
|
||||
@@ -683,3 +683,36 @@ class TestScreen(BaseTest):
|
||||
self.ae(s.current_url_text(), '123abcxyz')
|
||||
self.ae('2', s.hyperlink_at(1, 3))
|
||||
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), '')
|
||||
|
||||
9
setup.py
9
setup.py
@@ -13,6 +13,7 @@ import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
import platform
|
||||
import time
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
@@ -47,6 +48,7 @@ version = tuple(
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
is_openbsd = 'openbsd' in _plat
|
||||
is_arm = platform.processor() == 'arm'
|
||||
Env = glfw.Env
|
||||
env = Env()
|
||||
PKGCONFIG = os.environ.get('PKGCONFIG_EXE', 'pkg-config')
|
||||
@@ -246,6 +248,10 @@ def init_env(
|
||||
extra_logging: Iterable[str] = ()
|
||||
) -> Env:
|
||||
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()
|
||||
print('CC:', cc, ccver)
|
||||
stack_protector = first_successful_compile(cc, '-fstack-protector-strong', '-fstack-protector')
|
||||
@@ -298,6 +304,9 @@ def init_env(
|
||||
cflags.append('-flto')
|
||||
ldflags.append('-flto')
|
||||
|
||||
if debug:
|
||||
cflags.append('-DKITTY_DEBUG_BUILD')
|
||||
|
||||
if profile:
|
||||
cppflags.append('-DWITH_PROFILER')
|
||||
cflags.append('-g3')
|
||||
|
||||
Reference in New Issue
Block a user