Compare commits
93 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e06ef732d8 | ||
|
|
792a3e7db8 | ||
|
|
9b32842f66 | ||
|
|
9816979169 | ||
|
|
a74679dd95 | ||
|
|
a40059729e | ||
|
|
6bab967586 | ||
|
|
bb88b33469 | ||
|
|
c440470276 | ||
|
|
ef6164eecb | ||
|
|
7ad360d872 | ||
|
|
0870a33956 | ||
|
|
3ccc2f538d | ||
|
|
17375f0be2 | ||
|
|
5a987a162e | ||
|
|
5058fb9919 | ||
|
|
3335f8602b | ||
|
|
87ab1bbe54 | ||
|
|
f8d52b33f4 | ||
|
|
bc2dcdf9db | ||
|
|
93d1aacfb6 | ||
|
|
3ddaa4aff9 | ||
|
|
75488d568a | ||
|
|
3df80545bd | ||
|
|
c605fd9783 | ||
|
|
60719e7bff | ||
|
|
e9faa3277a | ||
|
|
35f7388725 | ||
|
|
ef1486a779 | ||
|
|
95c44165e9 | ||
|
|
7b816bb96f | ||
|
|
d2ac51aa4a | ||
|
|
26974b1f3a | ||
|
|
3960c6678d | ||
|
|
30b8991a73 | ||
|
|
75a94bcd96 | ||
|
|
e160cbf32b | ||
|
|
2da2e63245 | ||
|
|
f160ea6012 | ||
|
|
8057bc639d | ||
|
|
6f5ec5aa03 | ||
|
|
e343c9445c | ||
|
|
c73e374f1a | ||
|
|
d9b292f808 | ||
|
|
46ef2f313b | ||
|
|
92a705c79e | ||
|
|
12ebdd2574 | ||
|
|
ed2e951032 | ||
|
|
01a34a8846 | ||
|
|
b13174cb9c | ||
|
|
03ac6cab99 | ||
|
|
532eebaefb | ||
|
|
f825345b14 | ||
|
|
1409477ed7 | ||
|
|
d8e4294432 | ||
|
|
5d13908776 | ||
|
|
2832a73285 | ||
|
|
4152ed5809 | ||
|
|
8a90ed70ee | ||
|
|
e2646c08a5 | ||
|
|
7122ba820f | ||
|
|
efca2658ea | ||
|
|
91a714947c | ||
|
|
5d3ac649ba | ||
|
|
6443e726a6 | ||
|
|
6a755bda45 | ||
|
|
dab555ea3b | ||
|
|
a3bbad0060 | ||
|
|
73db717a0c | ||
|
|
f1552c8354 | ||
|
|
fb87fc32f0 | ||
|
|
b04b0c670d | ||
|
|
b33845df8a | ||
|
|
12c9d8d8f8 | ||
|
|
fe97bbcbbf | ||
|
|
10533c3eba | ||
|
|
b4415c90f9 | ||
|
|
2970bbdf6f | ||
|
|
95a420534d | ||
|
|
be56a21574 | ||
|
|
3741a235fe | ||
|
|
523b6c4c2d | ||
|
|
c3b2300310 | ||
|
|
1c4a20d86f | ||
|
|
f5af475012 | ||
|
|
f149b74332 | ||
|
|
8cd51386cb | ||
|
|
fb2cca88df | ||
|
|
031d4dc85a | ||
|
|
bf66d1c0f5 | ||
|
|
0edfa88755 | ||
|
|
dba1f3bcbc | ||
|
|
e204cc8f12 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -11,3 +11,6 @@ kitty/parse-graphics-command.h linguist-generated=true
|
|||||||
glfw/*.c linguist-vendored=true
|
glfw/*.c linguist-vendored=true
|
||||||
glfw/*.h linguist-vendored=true
|
glfw/*.h linguist-vendored=true
|
||||||
kittens/unicode_input/names.h linguist-generated=true
|
kittens/unicode_input/names.h linguist-generated=true
|
||||||
|
|
||||||
|
*.py text diff=python
|
||||||
|
*.m text diff=objc
|
||||||
|
|||||||
5
.github/workflows/ci.py
vendored
5
.github/workflows/ci.py
vendored
@@ -40,7 +40,10 @@ def install_deps():
|
|||||||
if is_bundle:
|
if is_bundle:
|
||||||
install_bundle()
|
install_bundle()
|
||||||
else:
|
else:
|
||||||
run('pip install Pillow pygments')
|
if is_macos:
|
||||||
|
# needed for zlib for pillow, should not be needed after pillow 8.0
|
||||||
|
os.environ['PKG_CONFIG_PATH'] = '/usr/local/opt/zlib/lib/pkgconfig'
|
||||||
|
run('pip3 install Pillow pygments')
|
||||||
|
|
||||||
|
|
||||||
def build_kitty():
|
def build_kitty():
|
||||||
|
|||||||
1
Brewfile
1
Brewfile
@@ -1,4 +1,5 @@
|
|||||||
pkg-config
|
pkg-config
|
||||||
|
zlib
|
||||||
python
|
python
|
||||||
imagemagick
|
imagemagick
|
||||||
harfbuzz
|
harfbuzz
|
||||||
|
|||||||
@@ -65,6 +65,11 @@ def run_kitten(args: List[str]) -> None:
|
|||||||
rk(kitten)
|
rk(kitten)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_config_file(args: List[str]) -> None:
|
||||||
|
from kitty.utils import edit_config_file as f
|
||||||
|
f()
|
||||||
|
|
||||||
|
|
||||||
def namespaced(args: List[str]) -> None:
|
def namespaced(args: List[str]) -> None:
|
||||||
func = namespaced_entry_points[args[1]]
|
func = namespaced_entry_points[args[1]]
|
||||||
func(args[1:])
|
func(args[1:])
|
||||||
@@ -77,6 +82,7 @@ entry_points = {
|
|||||||
'runpy': runpy,
|
'runpy': runpy,
|
||||||
'launch': launch,
|
'launch': launch,
|
||||||
'kitten': run_kitten,
|
'kitten': run_kitten,
|
||||||
|
'edit-config': edit_config_file,
|
||||||
|
|
||||||
'@': remote_control,
|
'@': remote_control,
|
||||||
'+': namespaced,
|
'+': namespaced,
|
||||||
|
|||||||
@@ -78,6 +78,19 @@ you might have to rebuild the app.
|
|||||||
a self signed certificate, see for example, `here
|
a self signed certificate, see for example, `here
|
||||||
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
|
<https://stackoverflow.com/questions/27474751/how-can-i-codesign-an-app-without-being-in-the-mac-developer-program/27474942>`_.
|
||||||
|
|
||||||
|
Build and run from source with Nix
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
On NixOS or any other Linux or macOS system with the Nix package manager
|
||||||
|
installed, execute `nix-shell
|
||||||
|
<https://nixos.org/guides/nix-pills/developing-with-nix-shell.html>`_ to create
|
||||||
|
the correct environment to build kitty or use ``nix-shell --pure`` instead to
|
||||||
|
eliminate most of the influence of the outside system, e.g. globally installed
|
||||||
|
packages. ``nix-shell`` will automatically fetch all required dependencies and
|
||||||
|
make them available in the newly spawned shell.
|
||||||
|
|
||||||
|
Then proceed with ``make`` or ``make app`` according to the platform specific instructions above.
|
||||||
|
|
||||||
|
|
||||||
Note for Linux/macOS packagers
|
Note for Linux/macOS packagers
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|||||||
@@ -4,6 +4,61 @@ Changelog
|
|||||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||||
|
|
||||||
|
0.19.2 [2020-11-13]
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- A new :doc:`kittens/query_terminal` kitten to easily query the running kitty
|
||||||
|
via escape codes to detect its version, and the values of
|
||||||
|
configuration options that enable or disable terminal features.
|
||||||
|
|
||||||
|
- Options to control mouse pointer shape, :opt:`default_pointer_shape`, and
|
||||||
|
:opt:`pointer_shape_when_dragging` (:pull:`3041`)
|
||||||
|
|
||||||
|
- Font independent rendering for braille characters, which ensures they are properly
|
||||||
|
aligned at all font sizes.
|
||||||
|
|
||||||
|
- Fix a regression in 0.19.0 that caused borders not to be drawn when setting
|
||||||
|
:opt:`window_margin_width` and keeping :opt:`draw_minimal_borders` on
|
||||||
|
(:iss:`3017`)
|
||||||
|
|
||||||
|
- Fix a regression in 0.19.0 that broke rendering of one-eight bar unicode
|
||||||
|
characters at very small font sizes (:iss:`3025`)
|
||||||
|
|
||||||
|
- Wayland: Fix a crash under GNOME when using multiple OS windows
|
||||||
|
(:pull:`3066`)
|
||||||
|
|
||||||
|
- Fix selections created by dragging upwards not being auto-cleared when
|
||||||
|
screen contents change (:pull:`3028`)
|
||||||
|
|
||||||
|
- macOS: Fix kitty not being added to PATH automatically when using pre-built
|
||||||
|
binaries (:iss:`3063`)
|
||||||
|
|
||||||
|
- Allow adding MIME definitions to kitty by placing a ``mime.types`` file in
|
||||||
|
the kitty config directory (:iss:`3056`)
|
||||||
|
|
||||||
|
- Dont ignore :option:`--title` when using a session file that defines no
|
||||||
|
windows (:iss:`3055`)
|
||||||
|
|
||||||
|
- Fix the send_text action not working in URL handlers (:iss:`3081`)
|
||||||
|
|
||||||
|
- Fix last character of URL not being detected if it is the only character on a
|
||||||
|
new line (:iss:`3088`)
|
||||||
|
|
||||||
|
- Don't restrict the ICH,DCH,REP control codes to only the current scroll region (:iss:`3090`, :iss:`3096`)
|
||||||
|
|
||||||
|
|
||||||
|
0.19.1 [2020-10-06]
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- hints kitten: Add an ``ip`` type for easy selection of IP addresses
|
||||||
|
(:pull:`3009`)
|
||||||
|
|
||||||
|
- Fix a regression that caused a segfault when using
|
||||||
|
:opt:`scrollback_pager_history_size` and it needs to be expanded (:iss:`3011`)
|
||||||
|
|
||||||
|
- Fix update available notifications repeating (:pull:`3006`)
|
||||||
|
|
||||||
|
|
||||||
0.19.0 [2020-10-04]
|
0.19.0 [2020-10-04]
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|||||||
@@ -165,9 +165,12 @@ kitty is not able to use my favorite font?
|
|||||||
|kitty| achieves its stellar performance by caching alpha masks of each rendered
|
|kitty| achieves its stellar performance by caching alpha masks of each rendered
|
||||||
character on the GPU, so that every character needs to be rendered only once.
|
character on the GPU, so that every character needs to be rendered only once.
|
||||||
This means it is a strictly character cell based display. As such it can use
|
This means it is a strictly character cell based display. As such it can use
|
||||||
only monospace fonts, since every cell in the grid has to be the same size. If
|
only monospace fonts, since every cell in the grid has to be the same size.
|
||||||
your font is not listed in ``kitty list-fonts`` it means that it is not
|
Furthermore, it needs fonts to be freely resizable, so it does not support
|
||||||
monospace. On Linux you can list all monospace fonts with::
|
bitmapped fonts.
|
||||||
|
|
||||||
|
If your font is not listed in ``kitty list-fonts`` it means that it is not
|
||||||
|
monospace or is a bitmapped font. On Linux you can list all monospace fonts with::
|
||||||
|
|
||||||
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
fc-list : family spacing outline scalable | grep -e spacing=100 -e spacing=90 | grep -e outline=True | grep -e scalable=True
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ Some programs that use the kitty graphics protocol:
|
|||||||
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
* `pixcat <https://github.com/mirukana/pixcat>`_ - a third party CLI and python library that wraps the graphics protocol
|
||||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||||
information tool
|
information tool
|
||||||
|
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
||||||
|
|
||||||
|
|
||||||
.. contents::
|
.. contents::
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ images and other types of documents directly in your terminal, even over SSH.
|
|||||||
`koneko <https://github.com/twenty5151/koneko>`_
|
`koneko <https://github.com/twenty5151/koneko>`_
|
||||||
browse images from the pixiv artist community directly in kitty
|
browse images from the pixiv artist community directly in kitty
|
||||||
|
|
||||||
|
`viu <https://github.com/atanunq/viu>`_
|
||||||
|
view images in the terminal, similar to kitty's icat, however also supports
|
||||||
|
displaying animated GIFs
|
||||||
|
|
||||||
|
|
||||||
System and data visualisation tools
|
System and data visualisation tools
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
@@ -63,6 +67,9 @@ System and data visualisation tools
|
|||||||
|
|
||||||
iplot 'sin(x*3)*exp(x*.2)'
|
iplot 'sin(x*3)*exp(x*.2)'
|
||||||
|
|
||||||
|
`onefetch <https://github.com/o2sh/onefetch>`_
|
||||||
|
a tool to fetch information about your git repositories
|
||||||
|
|
||||||
|
|
||||||
Editor integration
|
Editor integration
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ you use some editor other than vim, you should adjust the
|
|||||||
|
|
||||||
Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``::
|
Finally, add an alias to your shell's rc files to invoke the kitten as ``hg``::
|
||||||
|
|
||||||
alias hg='kitty +kitten hyperlink_grep'
|
alias hg='kitty +kitten hyperlinked_grep'
|
||||||
|
|
||||||
|
|
||||||
You can now run searches with::
|
You can now run searches with::
|
||||||
@@ -42,10 +42,17 @@ You can now run searches with::
|
|||||||
hg some-search-term
|
hg some-search-term
|
||||||
|
|
||||||
If you want to enable completion, for the kitten, you can delegate completion
|
If you want to enable completion, for the kitten, you can delegate completion
|
||||||
to rg. For ZSH, you do that with::
|
to rg. For that, instead of using an alias create a simple wrapper script named
|
||||||
|
:file:`hg` somewhere in your ``PATH``:
|
||||||
|
|
||||||
compdef _rg kitty +kitten hyperlinked_grep
|
.. code-block:: sh
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
exec kitty +kitten hyperlinked_grep "$@"
|
||||||
|
|
||||||
|
Then, for example, for ZSH, add the following to :file:`.zshrc`::
|
||||||
|
|
||||||
|
compdef _rg hg
|
||||||
|
|
||||||
To learn more about kitty's powerful framework for customizing URL click
|
To learn more about kitty's powerful framework for customizing URL click
|
||||||
actions, :doc:`see here <../open_actions>`.
|
actions, :doc:`see here <../open_actions>`.
|
||||||
|
|||||||
21
docs/kittens/query_terminal.rst
Normal file
21
docs/kittens/query_terminal.rst
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
Query terminal
|
||||||
|
=================
|
||||||
|
|
||||||
|
Used to query kitty from terminal programs about version, values of various
|
||||||
|
runtime options controlling its features, etc.
|
||||||
|
|
||||||
|
The querying is done using the (*semi*) standard XTGETTCAP escape sequence
|
||||||
|
pioneered by XTerm, so it works over SSH as well. The downside is that it
|
||||||
|
is slow, since it requires a roundtrip to the terminal emulator and back.
|
||||||
|
|
||||||
|
If you want to do some of the same querying in your terminal program without
|
||||||
|
depending on the kitten, you can do so, by processing the same escape codes.
|
||||||
|
Search `this page <https://invisible-island.net/xterm/ctlseqs/ctlseqs.html>`_
|
||||||
|
for *XTGETTCAP* to see the syntax for the escape code and read the source
|
||||||
|
of this kitten to find the values of the keys for the various queries.
|
||||||
|
|
||||||
|
|
||||||
|
Command Line Interface
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
.. include:: ../generated/cli-kitten-query_terminal.rst
|
||||||
@@ -81,7 +81,11 @@ lines. The various available criteria are:
|
|||||||
|
|
||||||
``mime``
|
``mime``
|
||||||
A comma separated list of MIME types, for example: ``text/*, image/*,
|
A comma separated list of MIME types, for example: ``text/*, image/*,
|
||||||
application/pdf``
|
application/pdf``. You can add MIME types to kitty by creating the
|
||||||
|
:file:`mime.types` in the kitty configuration directory. Useful if your
|
||||||
|
system MIME database does not have definitions you need. This file is
|
||||||
|
in the standard format of one definition per line, like: ``text/plain rst
|
||||||
|
md``.
|
||||||
|
|
||||||
``ext``
|
``ext``
|
||||||
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
||||||
|
|||||||
@@ -250,12 +250,13 @@ The escape code has the form::
|
|||||||
|
|
||||||
<OSC> 99 ; metadata ; payload <terminator>
|
<OSC> 99 ; metadata ; payload <terminator>
|
||||||
|
|
||||||
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is :code:`<ESC><backslash>`. The
|
Here ``<OSC>`` is :code:`<ESC>]` and ``<terminator>`` is
|
||||||
metadata is a section of colon separated :code:`key=value` pairs. Every key
|
:code:`<ESC><backslash>`. The metadata is a section of colon separated
|
||||||
must be a single character from the set :code:`[a-zA-Z]` and every value must
|
:code:`key=value` pairs. Every key must be a single character from the set
|
||||||
be a character from the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The
|
:code:`a-zA-Z` and every value must be a word consisting of characters from
|
||||||
payload must be interpreted based on the metadata section. The two semi-colons
|
the set :code:`a-zA-Z0-9-_/\+.,(){}[]*&^%$#@!`~`. The payload must be
|
||||||
*must* always be present even when no metadata is present.
|
interpreted based on the metadata section. The two semi-colons *must* always be
|
||||||
|
present even when no metadata is present.
|
||||||
|
|
||||||
Before going into details, lets see how one can display a simple, single line
|
Before going into details, lets see how one can display a simple, single line
|
||||||
notification from a shell script::
|
notification from a shell script::
|
||||||
@@ -297,7 +298,7 @@ was activated. This is controlled by the ``a`` key which takes a comma
|
|||||||
separated set of values, ``report`` and ``focus``. The value ``focus`` means
|
separated set of values, ``report`` and ``focus``. The value ``focus`` means
|
||||||
focus the window from which the notification was issued and is the default.
|
focus the window from which the notification was issued and is the default.
|
||||||
``report`` means send an escape code back to the application. The format of the
|
``report`` means send an escape code back to the application. The format of the
|
||||||
escape code is::
|
returned escape code is::
|
||||||
|
|
||||||
<OSC> 99 ; i=identifier ; <terminator>
|
<OSC> 99 ; i=identifier ; <terminator>
|
||||||
|
|
||||||
@@ -322,7 +323,7 @@ to display it based on what it does understand.
|
|||||||
allow displaying custom icons on a notification, at all, it was decided to
|
allow displaying custom icons on a notification, at all, it was decided to
|
||||||
leave it out of the spec for the time being.
|
leave it out of the spec for the time being.
|
||||||
|
|
||||||
Similarly, features such a scheduled notifications could be added in future
|
Similarly, features such as scheduled notifications could be added in future
|
||||||
revisions.
|
revisions.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,8 @@ you can map it in :file:`kitty.conf`. For example::
|
|||||||
|
|
||||||
Then pressing the :kbd:`F1` key will set the active window margins to 30.
|
Then pressing the :kbd:`F1` key will set the active window margins to 30.
|
||||||
The syntax for what follows :code:`remote_control` is exactly the same
|
The syntax for what follows :code:`remote_control` is exactly the same
|
||||||
as the syntax for what follows :code:`kitty @` above.
|
as the syntax for what follows :code:`kitty @` above. You do not need
|
||||||
|
to enable remote control to use these mappings.
|
||||||
|
|
||||||
|
|
||||||
Documentation for the remote control protocol
|
Documentation for the remote control protocol
|
||||||
|
|||||||
@@ -529,7 +529,6 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
|
|||||||
*width = (int)frameRect.size.width;
|
*width = (int)frameRect.size.width;
|
||||||
if (height)
|
if (height)
|
||||||
*height = (int)frameRect.size.height;
|
*height = (int)frameRect.size.height;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count)
|
||||||
|
|||||||
@@ -1595,10 +1595,10 @@ void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
|||||||
|
|
||||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
|
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
|
||||||
{
|
{
|
||||||
if (numer != GLFW_DONT_CARE && denom != GLFW_DONT_CARE)
|
if (numer == GLFW_DONT_CARE || denom == GLFW_DONT_CARE)
|
||||||
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
|
|
||||||
else
|
|
||||||
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
[window->ns.object setResizeIncrements:NSMakeSize(1.0, 1.0)];
|
||||||
|
else
|
||||||
|
[window->ns.object setContentAspectRatio:NSMakeSize(numer, denom)];
|
||||||
}
|
}
|
||||||
|
|
||||||
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
|
void _glfwPlatformSetWindowSizeIncrements(_GLFWwindow* window, int widthincr, int heightincr)
|
||||||
@@ -1994,8 +1994,10 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
||||||
bytesPerRow:src->width * 4
|
bytesPerRow:src->width * 4
|
||||||
bitsPerPixel:32];
|
bitsPerPixel:32];
|
||||||
if (rep == nil)
|
if (rep == nil) {
|
||||||
|
[native release];
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
||||||
[native addRepresentation:rep];
|
[native addRepresentation:rep];
|
||||||
@@ -2446,7 +2448,7 @@ START_ALLOW_CASE_RANGE
|
|||||||
END_ALLOW_CASE_RANGE
|
END_ALLOW_CASE_RANGE
|
||||||
}
|
}
|
||||||
if (utf_16_key != 0) {
|
if (utf_16_key != 0) {
|
||||||
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
|
strncpy(cocoa_key, [[NSString stringWithCharacters:&utf_16_key length:1] UTF8String], key_sz - 1);
|
||||||
} else {
|
} else {
|
||||||
unsigned str_pos = 0;
|
unsigned str_pos = 0;
|
||||||
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {
|
for (unsigned i = 0; i < 4 && str_pos < key_sz - 1; i++) {
|
||||||
|
|||||||
3
glfw/memfd.h
vendored
3
glfw/memfd.h
vendored
@@ -39,8 +39,7 @@ static inline int memfd_create(const char *name, unsigned int flags) {
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
static inline int
|
static inline int createTmpfileCloexec(char* tmpname)
|
||||||
createTmpfileCloexec(char* tmpname)
|
|
||||||
{
|
{
|
||||||
int fd;
|
int fd;
|
||||||
|
|
||||||
|
|||||||
14
glfw/wl_init.c
vendored
14
glfw/wl_init.c
vendored
@@ -717,6 +717,9 @@ glfwWaylandCheckForServerSideDecorations(void) {
|
|||||||
|
|
||||||
int _glfwPlatformInit(void)
|
int _glfwPlatformInit(void)
|
||||||
{
|
{
|
||||||
|
int i;
|
||||||
|
_GLFWmonitor* monitor;
|
||||||
|
|
||||||
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
|
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
|
||||||
if (!_glfw.wl.cursor.handle)
|
if (!_glfw.wl.cursor.handle)
|
||||||
{
|
{
|
||||||
@@ -768,6 +771,17 @@ int _glfwPlatformInit(void)
|
|||||||
// Sync so we got all initial output events
|
// Sync so we got all initial output events
|
||||||
wl_display_roundtrip(_glfw.wl.display);
|
wl_display_roundtrip(_glfw.wl.display);
|
||||||
|
|
||||||
|
for (i = 0; i < _glfw.monitorCount; ++i)
|
||||||
|
{
|
||||||
|
monitor = _glfw.monitors[i];
|
||||||
|
if (monitor->widthMM <= 0 || monitor->heightMM <= 0)
|
||||||
|
{
|
||||||
|
// If Wayland does not provide a physical size, assume the default 96 DPI
|
||||||
|
monitor->widthMM = (int) (monitor->modes[monitor->wl.currentMode].width * 25.4f / 96.f);
|
||||||
|
monitor->heightMM = (int) (monitor->modes[monitor->wl.currentMode].height * 25.4f / 96.f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!_glfw.wl.wmBase)
|
if (!_glfw.wl.wmBase)
|
||||||
{
|
{
|
||||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||||
|
|||||||
8
glfw/wl_platform.h
vendored
8
glfw/wl_platform.h
vendored
@@ -204,14 +204,14 @@ typedef struct _GLFWlibraryWayland
|
|||||||
struct wl_seat* seat;
|
struct wl_seat* seat;
|
||||||
struct wl_pointer* pointer;
|
struct wl_pointer* pointer;
|
||||||
struct wl_keyboard* keyboard;
|
struct wl_keyboard* keyboard;
|
||||||
|
struct wl_data_device_manager* dataDeviceManager;
|
||||||
|
struct wl_data_device* dataDevice;
|
||||||
struct xdg_wm_base* wmBase;
|
struct xdg_wm_base* wmBase;
|
||||||
struct zxdg_decoration_manager_v1* decorationManager;
|
struct zxdg_decoration_manager_v1* decorationManager;
|
||||||
struct wp_viewporter* viewporter;
|
struct wp_viewporter* viewporter;
|
||||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||||
struct wl_data_device_manager* dataDeviceManager;
|
|
||||||
struct wl_data_device* dataDevice;
|
|
||||||
struct wl_data_source* dataSourceForClipboard;
|
struct wl_data_source* dataSourceForClipboard;
|
||||||
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||||
@@ -226,6 +226,7 @@ typedef struct _GLFWlibraryWayland
|
|||||||
|
|
||||||
int32_t keyboardRepeatRate;
|
int32_t keyboardRepeatRate;
|
||||||
monotonic_t keyboardRepeatDelay;
|
monotonic_t keyboardRepeatDelay;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
id_type keyRepeatTimer;
|
id_type keyRepeatTimer;
|
||||||
@@ -275,7 +276,8 @@ typedef struct _GLFWmonitorWayland
|
|||||||
int x;
|
int x;
|
||||||
int y;
|
int y;
|
||||||
int scale;
|
int scale;
|
||||||
} _GLFWmonitorWayland;
|
|
||||||
|
} _GLFWmonitorWayland;
|
||||||
|
|
||||||
// Wayland-specific per-cursor data
|
// Wayland-specific per-cursor data
|
||||||
//
|
//
|
||||||
|
|||||||
23
glfw/wl_window.c
vendored
23
glfw/wl_window.c
vendored
@@ -42,8 +42,7 @@
|
|||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void setCursorImage(_GLFWwindow* window)
|
||||||
setCursorImage(_GLFWwindow* window)
|
|
||||||
{
|
{
|
||||||
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
|
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
|
||||||
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
|
_GLFWcursorWayland* cursorWayland = window->cursor ? &window->cursor->wl : &defaultCursor;
|
||||||
@@ -247,8 +246,7 @@ static void dispatchChangesAfterConfigure(_GLFWwindow *window, int32_t width, in
|
|||||||
* is set to ENOSPC. If posix_fallocate() is not supported, program may
|
* is set to ENOSPC. If posix_fallocate() is not supported, program may
|
||||||
* receive SIGBUS on accessing mmap()'ed file contents instead.
|
* receive SIGBUS on accessing mmap()'ed file contents instead.
|
||||||
*/
|
*/
|
||||||
static int
|
static int createAnonymousFile(off_t size)
|
||||||
createAnonymousFile(off_t size)
|
|
||||||
{
|
{
|
||||||
int ret, fd = -1, shm_anon = 0;
|
int ret, fd = -1, shm_anon = 0;
|
||||||
#ifdef HAS_MEMFD_CREATE
|
#ifdef HAS_MEMFD_CREATE
|
||||||
@@ -415,10 +413,10 @@ static void createDecorations(_GLFWwindow* window)
|
|||||||
|
|
||||||
static void destroyDecoration(_GLFWdecorationWayland* decoration)
|
static void destroyDecoration(_GLFWdecorationWayland* decoration)
|
||||||
{
|
{
|
||||||
if (decoration->surface)
|
|
||||||
wl_surface_destroy(decoration->surface);
|
|
||||||
if (decoration->subsurface)
|
if (decoration->subsurface)
|
||||||
wl_subsurface_destroy(decoration->subsurface);
|
wl_subsurface_destroy(decoration->subsurface);
|
||||||
|
if (decoration->surface)
|
||||||
|
wl_surface_destroy(decoration->surface);
|
||||||
if (decoration->viewport)
|
if (decoration->viewport)
|
||||||
wp_viewport_destroy(decoration->viewport);
|
wp_viewport_destroy(decoration->viewport);
|
||||||
decoration->surface = NULL;
|
decoration->surface = NULL;
|
||||||
@@ -439,7 +437,9 @@ static void xdgDecorationHandleConfigure(void* data,
|
|||||||
uint32_t mode)
|
uint32_t mode)
|
||||||
{
|
{
|
||||||
_GLFWwindow* window = data;
|
_GLFWwindow* window = data;
|
||||||
|
|
||||||
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||||
|
|
||||||
if (!window->wl.decorations.serverSide)
|
if (!window->wl.decorations.serverSide)
|
||||||
createDecorations(window);
|
createDecorations(window);
|
||||||
}
|
}
|
||||||
@@ -547,8 +547,7 @@ static bool createSurface(_GLFWwindow* window,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
|
||||||
setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
|
|
||||||
{
|
{
|
||||||
if (window->wl.xdg.toplevel)
|
if (window->wl.xdg.toplevel)
|
||||||
{
|
{
|
||||||
@@ -739,8 +738,7 @@ static bool createXdgSurface(_GLFWwindow* window)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void incrementCursorImage(_GLFWwindow* window)
|
||||||
incrementCursorImage(_GLFWwindow* window)
|
|
||||||
{
|
{
|
||||||
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
|
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
|
||||||
_GLFWcursor* cursor = window->wl.currentCursor;
|
_GLFWcursor* cursor = window->wl.currentCursor;
|
||||||
@@ -788,8 +786,7 @@ wayland_read_events(int poll_result, int events, void *data UNUSED) {
|
|||||||
else wl_display_cancel_read(_glfw.wl.display);
|
else wl_display_cancel_read(_glfw.wl.display);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void handleEvents(monotonic_t timeout)
|
||||||
handleEvents(monotonic_t timeout)
|
|
||||||
{
|
{
|
||||||
struct wl_display* display = _glfw.wl.display;
|
struct wl_display* display = _glfw.wl.display;
|
||||||
errno = 0;
|
errno = 0;
|
||||||
@@ -958,7 +955,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
|||||||
window->context.destroy(window);
|
window->context.destroy(window);
|
||||||
|
|
||||||
destroyDecorations(window);
|
destroyDecorations(window);
|
||||||
|
|
||||||
if (window->wl.xdg.decoration)
|
if (window->wl.xdg.decoration)
|
||||||
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
|
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
|
||||||
|
|
||||||
@@ -1362,6 +1358,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
|||||||
cursor->wl.buffer = createShmBuffer(image);
|
cursor->wl.buffer = createShmBuffer(image);
|
||||||
if (!cursor->wl.buffer)
|
if (!cursor->wl.buffer)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
cursor->wl.width = image->width;
|
cursor->wl.width = image->width;
|
||||||
cursor->wl.height = image->height;
|
cursor->wl.height = image->height;
|
||||||
cursor->wl.xhot = xhot;
|
cursor->wl.xhot = xhot;
|
||||||
|
|||||||
6
glfw/x11_window.c
vendored
6
glfw/x11_window.c
vendored
@@ -423,14 +423,16 @@ static char* convertLatin1toUTF8(const char* source)
|
|||||||
const char* sp;
|
const char* sp;
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
for (sp = source; *sp; sp++) size += (*sp & 0x80) ? 2 : 1;
|
for (sp = source; *sp; sp++)
|
||||||
|
size += (*sp & 0x80) ? 2 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* target = calloc(size, 1);
|
char* target = calloc(size, 1);
|
||||||
char* tp = target;
|
char* tp = target;
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
for (sp = source; *sp; sp++) tp += encodeUTF8(tp, *sp);
|
for (sp = source; *sp; sp++)
|
||||||
|
tp += encodeUTF8(tp, *sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
return target;
|
return target;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
See https://sw.kovidgoyal.net/kitty/graphics-protocol.html
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
See https://sw.kovidgoyal.net/kitty/key-encoding.html
|
|
||||||
@@ -7,7 +7,7 @@ import re
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from mimetypes import guess_type
|
from kitty.guess_mime_type import guess_type
|
||||||
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
|
from typing import TYPE_CHECKING, Dict, List, Set, Optional, Iterator, Tuple, Union
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -68,7 +68,10 @@ class Collection:
|
|||||||
self.removed_count += len(lines_for_path(left_path))
|
self.removed_count += len(lines_for_path(left_path))
|
||||||
|
|
||||||
def finalize(self) -> None:
|
def finalize(self) -> None:
|
||||||
self.all_paths.sort(key=path_name_map.get)
|
def key(x: str) -> str:
|
||||||
|
return path_name_map.get(x, '')
|
||||||
|
|
||||||
|
self.all_paths.sort(key=key)
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]:
|
def __iter__(self) -> Iterator[Tuple[str, str, Optional[str]]]:
|
||||||
for path in self.all_paths:
|
for path in self.all_paths:
|
||||||
@@ -133,7 +136,7 @@ def sanitize(text: str) -> str:
|
|||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
def mime_type_for_path(path: str) -> str:
|
def mime_type_for_path(path: str) -> str:
|
||||||
return guess_type(path)[0] or 'application/octet-stream'
|
return guess_type(path) or 'application/octet-stream'
|
||||||
|
|
||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
@lru_cache(maxsize=1024)
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ from collections import defaultdict
|
|||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
from typing import DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
|
from typing import (
|
||||||
|
Any, DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
|
||||||
|
)
|
||||||
|
|
||||||
from kitty.cli import CONFIG_HELP, parse_args
|
from kitty.cli import CONFIG_HELP, parse_args
|
||||||
from kitty.cli_stub import DiffCLIOptions
|
from kitty.cli_stub import DiffCLIOptions
|
||||||
@@ -24,6 +26,11 @@ from kitty.key_encoding import RELEASE, KeyEvent, enter_key, key_defs as K
|
|||||||
from kitty.options_stub import DiffOptions
|
from kitty.options_stub import DiffOptions
|
||||||
from kitty.utils import ScreenSize
|
from kitty.utils import ScreenSize
|
||||||
|
|
||||||
|
from ..tui.handler import Handler
|
||||||
|
from ..tui.images import ImageManager, Placement
|
||||||
|
from ..tui.line_edit import LineEdit
|
||||||
|
from ..tui.loop import Loop
|
||||||
|
from ..tui.operations import styled
|
||||||
from . import global_data
|
from . import global_data
|
||||||
from .collect import (
|
from .collect import (
|
||||||
Collection, create_collection, data_for_path, lines_for_path, sanitize,
|
Collection, create_collection, data_for_path, lines_for_path, sanitize,
|
||||||
@@ -35,14 +42,11 @@ from .render import (
|
|||||||
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
||||||
)
|
)
|
||||||
from .search import BadRegex, Search
|
from .search import BadRegex, Search
|
||||||
from ..tui.handler import Handler
|
|
||||||
from ..tui.images import ImageManager, Placement
|
|
||||||
from ..tui.line_edit import LineEdit
|
|
||||||
from ..tui.loop import Loop
|
|
||||||
from ..tui.operations import styled
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .highlight import initialize_highlighter, highlight_collection, DiffHighlight
|
from .highlight import (
|
||||||
|
DiffHighlight, highlight_collection, initialize_highlighter
|
||||||
|
)
|
||||||
has_highlighter = True
|
has_highlighter = True
|
||||||
DiffHighlight
|
DiffHighlight
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -566,8 +570,8 @@ class ShowWarning:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.warnings: List[str] = []
|
self.warnings: List[str] = []
|
||||||
|
|
||||||
def __call__(self, message: str, category: object, filename: str, lineno: int, file: object = None, line: object = None) -> None:
|
def __call__(self, message: Any, category: Any, filename: str, lineno: int, file: object = None, line: object = None) -> None:
|
||||||
if category is ImageSupportWarning:
|
if category is ImageSupportWarning and isinstance(message, str):
|
||||||
showwarning.warnings.append(message)
|
showwarning.warnings.append(message)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -236,6 +236,11 @@ def postprocessor(func: PostprocessorFunc) -> PostprocessorFunc:
|
|||||||
return func
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidMatch(Exception):
|
||||||
|
"""Raised when a match turns out to be invalid."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@postprocessor
|
@postprocessor
|
||||||
def url(text: str, s: int, e: int) -> Tuple[int, int]:
|
def url(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||||
if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs
|
if s > 4 and text[s - 5:s] == 'link:': # asciidoc URLs
|
||||||
@@ -280,11 +285,29 @@ def quotes(text: str, s: int, e: int) -> Tuple[int, int]:
|
|||||||
return s, e
|
return s, e
|
||||||
|
|
||||||
|
|
||||||
|
@postprocessor
|
||||||
|
def ip(text: str, s: int, e: int) -> Tuple[int, int]:
|
||||||
|
from ipaddress import ip_address
|
||||||
|
# Check validity of IPs (or raise InvalidMatch)
|
||||||
|
ip = text[s:e]
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip_address(ip)
|
||||||
|
except Exception:
|
||||||
|
raise InvalidMatch("Invalid IP")
|
||||||
|
|
||||||
|
return s, e
|
||||||
|
|
||||||
|
|
||||||
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
|
def mark(pattern: str, post_processors: Iterable[PostprocessorFunc], text: str, args: HintsCLIOptions) -> Generator[Mark, None, None]:
|
||||||
pat = re.compile(pattern)
|
pat = re.compile(pattern)
|
||||||
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
|
for idx, (s, e, groupdict) in enumerate(regex_finditer(pat, args.minimum_match_length, text)):
|
||||||
for func in post_processors:
|
try:
|
||||||
s, e = func(text, s, e)
|
for func in post_processors:
|
||||||
|
s, e = func(text, s, e)
|
||||||
|
except InvalidMatch:
|
||||||
|
continue
|
||||||
|
|
||||||
mark_text = text[s:e].replace('\n', '').replace('\0', '')
|
mark_text = text[s:e].replace('\n', '').replace('\0', '')
|
||||||
yield Mark(idx, s, e, mark_text, groupdict)
|
yield Mark(idx, s, e, mark_text, groupdict)
|
||||||
|
|
||||||
@@ -325,6 +348,15 @@ def functions_for(args: HintsCLIOptions) -> Tuple[str, List[PostprocessorFunc]]:
|
|||||||
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
||||||
elif args.type == 'hash':
|
elif args.type == 'hash':
|
||||||
pattern = '[0-9a-f]{7,128}'
|
pattern = '[0-9a-f]{7,128}'
|
||||||
|
elif args.type == 'ip':
|
||||||
|
pattern = (
|
||||||
|
# # IPv4 with no validation
|
||||||
|
r"((?:\d{1,3}\.){3}\d{1,3}"
|
||||||
|
r"|"
|
||||||
|
# # IPv6 with no validation
|
||||||
|
r"(?:[a-fA-F0-9]{0,4}:){2,7}[a-fA-F0-9]{1,4})"
|
||||||
|
)
|
||||||
|
post_processors.append(ip)
|
||||||
elif args.type == 'word':
|
elif args.type == 'word':
|
||||||
chars = args.word_characters
|
chars = args.word_characters
|
||||||
if chars is None:
|
if chars is None:
|
||||||
@@ -402,7 +434,7 @@ def process_hyperlinks(text: str) -> Tuple[str, Tuple[Mark, ...]]:
|
|||||||
active_hyperlink_start_offset = 0
|
active_hyperlink_start_offset = 0
|
||||||
idx += 1
|
idx += 1
|
||||||
|
|
||||||
def process_hyperlink(m: re.Match) -> str:
|
def process_hyperlink(m: 're.Match') -> str:
|
||||||
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
nonlocal removed_size, active_hyperlink_url, active_hyperlink_id, active_hyperlink_start_offset
|
||||||
raw = m.group()
|
raw = m.group()
|
||||||
start = m.start() - removed_size
|
start = m.start() - removed_size
|
||||||
@@ -482,7 +514,7 @@ programs.
|
|||||||
|
|
||||||
--type
|
--type
|
||||||
default=url
|
default=url
|
||||||
choices=url,regex,path,line,hash,word,linenum,hyperlink
|
choices=url,regex,path,line,hash,word,linenum,hyperlink,ip
|
||||||
The type of text to search for. A value of :code:`linenum` is special, it looks
|
The type of text to search for. A value of :code:`linenum` is special, it looks
|
||||||
for error messages using the pattern specified with :option:`--regex`, which
|
for error messages using the pattern specified with :option:`--regex`, which
|
||||||
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
must have the named groups, :code:`path` and :code:`line`. If not specified,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import mimetypes
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
@@ -18,6 +17,7 @@ from typing import (
|
|||||||
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
|
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from kitty.guess_mime_type import guess_type
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.cli_stub import IcatCLIOptions
|
from kitty.cli_stub import IcatCLIOptions
|
||||||
from kitty.constants import appname
|
from kitty.constants import appname
|
||||||
@@ -273,7 +273,7 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil
|
|||||||
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
|
def scan(d: str) -> Generator[Tuple[str, str], None, None]:
|
||||||
for dirpath, dirnames, filenames in os.walk(d):
|
for dirpath, dirnames, filenames in os.walk(d):
|
||||||
for f in filenames:
|
for f in filenames:
|
||||||
mt = mimetypes.guess_type(f)[0]
|
mt = guess_type(f)
|
||||||
if mt and mt.startswith('image/'):
|
if mt and mt.startswith('image/'):
|
||||||
yield os.path.join(dirpath, f), mt
|
yield os.path.join(dirpath, f), mt
|
||||||
|
|
||||||
|
|||||||
0
kittens/query_terminal/__init__.py
Normal file
0
kittens/query_terminal/__init__.py
Normal file
162
kittens/query_terminal/main.py
Normal file
162
kittens/query_terminal/main.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from binascii import unhexlify, hexlify
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Dict, Iterable, List, Type
|
||||||
|
|
||||||
|
from kitty.cli import parse_args
|
||||||
|
from kitty.cli_stub import QueryTerminalCLIOptions
|
||||||
|
from kitty.constants import appname
|
||||||
|
from kitty.utils import TTYIO
|
||||||
|
from kitty.terminfo import names
|
||||||
|
|
||||||
|
|
||||||
|
class Query:
|
||||||
|
name: str = ''
|
||||||
|
ans: str = ''
|
||||||
|
query_name: str = ''
|
||||||
|
help_text: str = ''
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.encoded_query_name = hexlify(self.query_name.encode('utf-8')).decode('ascii')
|
||||||
|
self.pat = re.compile('\x1bP([01])\\+r{}(.*?)\x1b\\\\'.format(self.encoded_query_name).encode('ascii'))
|
||||||
|
|
||||||
|
def query_code(self) -> str:
|
||||||
|
return "\x1bP+q{}\x1b\\".format(self.encoded_query_name)
|
||||||
|
|
||||||
|
def decode_response(self, res: bytes) -> str:
|
||||||
|
return unhexlify(res).decode('utf-8')
|
||||||
|
|
||||||
|
def more_needed(self, buffer: bytes) -> bool:
|
||||||
|
m = self.pat.search(buffer)
|
||||||
|
if m is None:
|
||||||
|
return True
|
||||||
|
if m.group(1) == b'1':
|
||||||
|
q = m.group(2)
|
||||||
|
if q.startswith(b'='):
|
||||||
|
with suppress(Exception):
|
||||||
|
self.ans = self.decode_response(memoryview(q)[1:])
|
||||||
|
return False
|
||||||
|
|
||||||
|
def output_line(self) -> str:
|
||||||
|
return self.ans
|
||||||
|
|
||||||
|
|
||||||
|
all_queries: Dict[str, Type[Query]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def query(cls: Type[Query]) -> Type[Query]:
|
||||||
|
all_queries[cls.name] = cls
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
@query
|
||||||
|
class TerminalName(Query):
|
||||||
|
name: str = 'name'
|
||||||
|
query_name: str = 'TN'
|
||||||
|
help_text: str = f'Terminal name ({names[0]})'
|
||||||
|
|
||||||
|
|
||||||
|
@query
|
||||||
|
class TerminalVersion(Query):
|
||||||
|
name: str = 'version'
|
||||||
|
query_name: str = 'kitty-query-version'
|
||||||
|
help_text: str = 'Terminal version, for e.g.: 0.19.2'
|
||||||
|
|
||||||
|
|
||||||
|
@query
|
||||||
|
class AllowHyperlinks(Query):
|
||||||
|
name: str = 'allow_hyperlinks'
|
||||||
|
query_name: str = 'kitty-query-allow_hyperlinks'
|
||||||
|
help_text: str = 'yes, no or ask'
|
||||||
|
|
||||||
|
|
||||||
|
def do_queries(queries: Iterable, cli_opts: QueryTerminalCLIOptions) -> Dict[str, str]:
|
||||||
|
actions = tuple(all_queries[x]() for x in queries)
|
||||||
|
qstring = ''.join(a.query_code() for a in actions)
|
||||||
|
received = b''
|
||||||
|
|
||||||
|
def more_needed(data: bytes) -> bool:
|
||||||
|
nonlocal received
|
||||||
|
received += data
|
||||||
|
for a in actions:
|
||||||
|
if a.more_needed(received):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
with TTYIO() as ttyio:
|
||||||
|
ttyio.send(qstring)
|
||||||
|
ttyio.recv(more_needed, timeout=cli_opts.wait_for)
|
||||||
|
|
||||||
|
return {a.name: a.output_line() for a in actions}
|
||||||
|
|
||||||
|
|
||||||
|
def options_spec() -> str:
|
||||||
|
return '''\
|
||||||
|
--wait-for
|
||||||
|
type=float
|
||||||
|
default=10
|
||||||
|
The amount of time (in seconds) to wait for a response from the terminal, after
|
||||||
|
querying it.
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
help_text = '''\
|
||||||
|
Query the terminal this kitten is run in for various
|
||||||
|
capabilities. This sends escape codes to the terminal
|
||||||
|
and based on its response prints out data about supported
|
||||||
|
capabilities. Note that this is a blocking operation, since
|
||||||
|
it has to wait for a response from the terminal. You can control
|
||||||
|
the maximum wait time via the ``--wait-for`` option.
|
||||||
|
|
||||||
|
The output is lines of the form::
|
||||||
|
|
||||||
|
query: data
|
||||||
|
|
||||||
|
If a particular query is unsupported by the running kitty version,
|
||||||
|
the data will be blank.
|
||||||
|
|
||||||
|
Note that when calling this from another program, be very
|
||||||
|
careful not to perform any I/O on the terminal device
|
||||||
|
until the kitten exits.
|
||||||
|
|
||||||
|
Available queries are::
|
||||||
|
|
||||||
|
{}
|
||||||
|
'''.format(' ' + '\n '.join(
|
||||||
|
f'{name}: {c.help_text}' for name, c in all_queries.items()))
|
||||||
|
usage = '[query1 query2 ...]'
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: List[str] = sys.argv) -> None:
|
||||||
|
cli_opts, items_ = parse_args(
|
||||||
|
args[1:],
|
||||||
|
options_spec,
|
||||||
|
usage,
|
||||||
|
help_text,
|
||||||
|
'{} +kitten query_terminal'.format(appname),
|
||||||
|
result_class=QueryTerminalCLIOptions
|
||||||
|
)
|
||||||
|
queries: List[str] = list(items_)
|
||||||
|
if 'all' in queries or not queries:
|
||||||
|
queries = sorted(all_queries)
|
||||||
|
else:
|
||||||
|
extra = frozenset(queries) - frozenset(all_queries)
|
||||||
|
if extra:
|
||||||
|
raise SystemExit(f'Unknown queries: {", ".join(extra)}')
|
||||||
|
|
||||||
|
for key, val in do_queries(queries, cli_opts).items():
|
||||||
|
print(key + ':', val)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
elif __name__ == '__doc__':
|
||||||
|
cd = sys.cli_docs # type: ignore
|
||||||
|
cd['usage'] = usage
|
||||||
|
cd['options'] = options_spec
|
||||||
|
cd['help_text'] = help_text
|
||||||
@@ -118,15 +118,14 @@ def parse_ssh_args(args: List[str]) -> Tuple[List[str], List[str], bool]:
|
|||||||
if arg in boolean_ssh_args:
|
if arg in boolean_ssh_args:
|
||||||
ssh_args.append(arg)
|
ssh_args.append(arg)
|
||||||
continue
|
continue
|
||||||
if arg.startswith('-p') and arg[2:].isdigit():
|
|
||||||
ssh_args.append(arg)
|
|
||||||
continue
|
|
||||||
if arg in other_ssh_args:
|
if arg in other_ssh_args:
|
||||||
if i != len(all_args) - 1:
|
|
||||||
raise SystemExit('Option {} cannot occur in the middle'.format(arg))
|
|
||||||
ssh_args.append(arg)
|
ssh_args.append(arg)
|
||||||
expecting_option_val = True
|
rest = all_args[i+1:]
|
||||||
continue
|
if rest:
|
||||||
|
ssh_args.append(rest)
|
||||||
|
else:
|
||||||
|
expecting_option_val = True
|
||||||
|
break
|
||||||
raise SystemExit('Unknown option: {}'.format(arg))
|
raise SystemExit('Unknown option: {}'.format(arg))
|
||||||
continue
|
continue
|
||||||
if expecting_option_val:
|
if expecting_option_val:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import re
|
|||||||
import selectors
|
import selectors
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
import termios
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
|
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
|
||||||
@@ -61,8 +62,9 @@ debug = Debug()
|
|||||||
|
|
||||||
class TermManager:
|
class TermManager:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, optional_actions: int = termios.TCSANOW) -> None:
|
||||||
self.extra_finalize: Optional[str] = None
|
self.extra_finalize: Optional[str] = None
|
||||||
|
self.optional_actions = optional_actions
|
||||||
|
|
||||||
def set_state_for_loop(self, set_raw: bool = True) -> None:
|
def set_state_for_loop(self, set_raw: bool = True) -> None:
|
||||||
if set_raw:
|
if set_raw:
|
||||||
@@ -82,7 +84,7 @@ class TermManager:
|
|||||||
self.set_state_for_loop()
|
self.set_state_for_loop()
|
||||||
|
|
||||||
def __enter__(self) -> 'TermManager':
|
def __enter__(self) -> 'TermManager':
|
||||||
self.tty_fd, self.original_termios = open_tty()
|
self.tty_fd, self.original_termios = open_tty(False, self.optional_actions)
|
||||||
self.set_state_for_loop(set_raw=False)
|
self.set_state_for_loop(set_raw=False)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -183,7 +185,8 @@ class Loop:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'
|
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]',
|
||||||
|
optional_actions: int = termios.TCSADRAIN
|
||||||
):
|
):
|
||||||
if is_macos:
|
if is_macos:
|
||||||
# On macOS PTY devices are not supported by the KqueueSelector and
|
# On macOS PTY devices are not supported by the KqueueSelector and
|
||||||
@@ -193,6 +196,7 @@ class Loop:
|
|||||||
else:
|
else:
|
||||||
self.asycio_loop = asyncio.get_event_loop()
|
self.asycio_loop = asyncio.get_event_loop()
|
||||||
self.return_code = 0
|
self.return_code = 0
|
||||||
|
self.optional_actions = optional_actions
|
||||||
self.read_buf = ''
|
self.read_buf = ''
|
||||||
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||||
try:
|
try:
|
||||||
@@ -388,7 +392,7 @@ class Loop:
|
|||||||
handler.on_resize(handler.screen_size)
|
handler.on_resize(handler.screen_size)
|
||||||
|
|
||||||
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
|
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
|
||||||
with TermManager() as term_manager, signal_manager:
|
with TermManager(self.optional_actions) as term_manager, signal_manager:
|
||||||
self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
|
self._get_screen_size: ScreenSizeGetter = screen_size_function(term_manager.tty_fd)
|
||||||
image_manager = None
|
image_manager = None
|
||||||
if handler.image_manager_class is not None:
|
if handler.image_manager_class is not None:
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class Borders:
|
|||||||
self.os_window_id = os_window_id
|
self.os_window_id = os_window_id
|
||||||
self.tab_id = tab_id
|
self.tab_id = tab_id
|
||||||
self.draw_active_borders = opts.active_border_color is not None
|
self.draw_active_borders = opts.active_border_color is not None
|
||||||
self.draw_minimal_borders = opts.draw_minimal_borders
|
self.draw_minimal_borders = opts.draw_minimal_borders and max(opts.window_margin_width) < 1
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -1563,6 +1563,7 @@ class Boss:
|
|||||||
for os_window_id in os_windows:
|
for os_window_id in os_windows:
|
||||||
self.default_bg_changed_for(os_window_id)
|
self.default_bg_changed_for(os_window_id)
|
||||||
|
|
||||||
|
# Can be called with kitty -o "map f1 send_test_notification"
|
||||||
def send_test_notification(self) -> None:
|
def send_test_notification(self) -> None:
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class CLIOptions:
|
|||||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
||||||
|
QueryTerminalCLIOptions = CLIOptions
|
||||||
|
|
||||||
|
|
||||||
def generate_stub() -> None:
|
def generate_stub() -> None:
|
||||||
@@ -50,6 +51,9 @@ def generate_stub() -> None:
|
|||||||
from kittens.icat.main import options_spec
|
from kittens.icat.main import options_spec
|
||||||
do(options_spec(), 'IcatCLIOptions')
|
do(options_spec(), 'IcatCLIOptions')
|
||||||
|
|
||||||
|
from kittens.query_terminal.main import options_spec
|
||||||
|
do(options_spec(), 'QueryTerminalCLIOptions')
|
||||||
|
|
||||||
from kittens.panel.main import OPTIONS
|
from kittens.panel.main import OPTIONS
|
||||||
do(OPTIONS(), 'PanelCLIOptions')
|
do(OPTIONS(), 'PanelCLIOptions')
|
||||||
|
|
||||||
|
|||||||
@@ -182,14 +182,9 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
|
if (!center) {PyErr_SetString(PyExc_RuntimeError, "Failed to get the user notification center"); return NULL; }
|
||||||
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
if (!center.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
||||||
NSUserNotification *n = [NSUserNotification new];
|
NSUserNotification *n = [NSUserNotification new];
|
||||||
#define SET(x) { \
|
if (title) n.title = @(title);
|
||||||
if (x) { \
|
if (subtitle) n.subtitle = @(subtitle);
|
||||||
NSString *t = @(x); \
|
if (informativeText) n.informativeText = @(informativeText);
|
||||||
n.x = t; \
|
|
||||||
[t release]; \
|
|
||||||
}}
|
|
||||||
SET(title); SET(subtitle); SET(informativeText);
|
|
||||||
#undef SET
|
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
n.userInfo = @{@"user_id": @(identifier)};
|
n.userInfo = @{@"user_id": @(identifier)};
|
||||||
}
|
}
|
||||||
@@ -307,17 +302,17 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
|
|
||||||
@implementation ServiceProvider
|
@implementation ServiceProvider
|
||||||
|
|
||||||
- (void)openTab:(NSPasteboard*)pasteboard
|
- (BOOL)openTab:(NSPasteboard*)pasteboard
|
||||||
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
||||||
[self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD];
|
return [self openFilesFromPasteboard:pasteboard type:NEW_TAB_WITH_WD];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)openOSWindow:(NSPasteboard*)pasteboard
|
- (BOOL)openOSWindow:(NSPasteboard*)pasteboard
|
||||||
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
userData:(NSString *) UNUSED userData error:(NSError **) UNUSED error {
|
||||||
[self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD];
|
return [self openFilesFromPasteboard:pasteboard type:NEW_OS_WINDOW_WITH_WD];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
|
- (BOOL)openFilesFromPasteboard:(NSPasteboard *)pasteboard type:(int)type {
|
||||||
NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES };
|
NSDictionary *options = @{ NSPasteboardURLReadingFileURLsOnlyKey: @YES };
|
||||||
NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
|
NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
|
||||||
for (NSURL *url in filePathArray) {
|
for (NSURL *url in filePathArray) {
|
||||||
@@ -330,6 +325,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
|||||||
set_cocoa_pending_action(type, [path UTF8String]);
|
set_cocoa_pending_action(type, [path UTF8String]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -394,10 +394,10 @@ def complete_files_and_dirs(
|
|||||||
|
|
||||||
|
|
||||||
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
|
def complete_icat_args(ans: Completions, opt: Optional[OptionDict], prefix: str, unknown_args: Delegate) -> None:
|
||||||
from mimetypes import guess_type
|
from .guess_mime_type import guess_type
|
||||||
|
|
||||||
def icat_file_predicate(filename: str) -> bool:
|
def icat_file_predicate(filename: str) -> bool:
|
||||||
mt = guess_type(filename)[0]
|
mt = guess_type(filename)
|
||||||
if mt and mt.startswith('image/'):
|
if mt and mt.startswith('image/'):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -576,6 +576,16 @@ The shape of the mouse pointer when the program running in the terminal grabs th
|
|||||||
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
||||||
'''))
|
'''))
|
||||||
|
|
||||||
|
o('default_pointer_shape', 'beam', option_type=choices('arrow', 'beam', 'hand'), long_text=('''
|
||||||
|
The default shape of the mouse pointer.
|
||||||
|
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
||||||
|
'''))
|
||||||
|
|
||||||
|
o('pointer_shape_when_dragging', 'beam', option_type=choices('arrow', 'beam', 'hand'), long_text=('''
|
||||||
|
The default shape of the mouse pointer when dragging across text.
|
||||||
|
Valid values are: :code:`arrow`, :code:`beam` and :code:`hand`
|
||||||
|
'''))
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
g('performance') # {{{
|
g('performance') # {{{
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
|||||||
|
|
||||||
|
|
||||||
appname: str = 'kitty'
|
appname: str = 'kitty'
|
||||||
version: Version = Version(0, 19, 0)
|
version: Version = Version(0, 19, 2)
|
||||||
str_version: str = '.'.join(map(str, version))
|
str_version: str = '.'.join(map(str, version))
|
||||||
_plat = sys.platform.lower()
|
_plat = sys.platform.lower()
|
||||||
is_macos: bool = 'darwin' in _plat
|
is_macos: bool = 'darwin' in _plat
|
||||||
|
|||||||
@@ -161,12 +161,13 @@ coretext_all_fonts(PyObject UNUSED *_self) {
|
|||||||
CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
|
CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
|
||||||
const CFIndex count = CFArrayGetCount(matches);
|
const CFIndex count = CFArrayGetCount(matches);
|
||||||
PyObject *ans = PyTuple_New(count), *temp;
|
PyObject *ans = PyTuple_New(count), *temp;
|
||||||
if (ans == NULL) return PyErr_NoMemory();
|
if (ans == NULL) { CFRelease(matches); return PyErr_NoMemory(); }
|
||||||
for (CFIndex i = 0; i < count; i++) {
|
for (CFIndex i = 0; i < count; i++) {
|
||||||
temp = font_descriptor_to_python((CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i));
|
temp = font_descriptor_to_python((CTFontDescriptorRef) CFArrayGetValueAtIndex(matches, i));
|
||||||
if (temp == NULL) { Py_DECREF(ans); return NULL; }
|
if (temp == NULL) { CFRelease(matches); Py_DECREF(ans); return NULL; }
|
||||||
PyTuple_SET_ITEM(ans, i, temp); temp = NULL;
|
PyTuple_SET_ITEM(ans, i, temp); temp = NULL;
|
||||||
}
|
}
|
||||||
|
CFRelease(matches);
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,6 +213,7 @@ manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
|
|||||||
CFRelease(new_font);
|
CFRelease(new_font);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
CFRelease(fonts);
|
||||||
return ans;
|
return ans;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout) {
|
put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_timeout, int optional_actions) {
|
||||||
struct termios raw_termios = *termios_p;
|
struct termios raw_termios = *termios_p;
|
||||||
cfmakeraw(&raw_termios);
|
cfmakeraw(&raw_termios);
|
||||||
if (read_with_timeout) {
|
if (read_with_timeout) {
|
||||||
@@ -99,14 +99,14 @@ put_tty_in_raw_mode(int fd, const struct termios* termios_p, bool read_with_time
|
|||||||
} else {
|
} else {
|
||||||
raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0;
|
raw_termios.c_cc[VMIN] = 1; raw_termios.c_cc[VTIME] = 0;
|
||||||
}
|
}
|
||||||
if (tcsetattr(fd, TCSAFLUSH, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; }
|
if (tcsetattr(fd, optional_actions, &raw_termios) != 0) { PyErr_SetFromErrno(PyExc_OSError); return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
open_tty(PyObject *self UNUSED, PyObject *args) {
|
open_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
int read_with_timeout = 0;
|
int read_with_timeout = 0, optional_actions = TCSAFLUSH;
|
||||||
if (!PyArg_ParseTuple(args, "|p", &read_with_timeout)) return NULL;
|
if (!PyArg_ParseTuple(args, "|pi", &read_with_timeout, &optional_actions)) return NULL;
|
||||||
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
|
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
|
||||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||||
static char ctty[L_ctermid+1];
|
static char ctty[L_ctermid+1];
|
||||||
@@ -115,26 +115,26 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
|
|||||||
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
||||||
if (!termios_p) return PyErr_NoMemory();
|
if (!termios_p) return PyErr_NoMemory();
|
||||||
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||||
if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0)) { free(termios_p); return NULL; }
|
if (!put_tty_in_raw_mode(fd, termios_p, read_with_timeout != 0, optional_actions)) { free(termios_p); return NULL; }
|
||||||
return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p));
|
return Py_BuildValue("iN", fd, PyLong_FromVoidPtr(termios_p));
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TTY_ARGS \
|
#define TTY_ARGS \
|
||||||
PyObject *tp; int fd; \
|
PyObject *tp; int fd; int optional_actions = TCSAFLUSH; \
|
||||||
if (!PyArg_ParseTuple(args, "iO!", &fd, &PyLong_Type, &tp)) return NULL; \
|
if (!PyArg_ParseTuple(args, "iO!|i", &fd, &PyLong_Type, &tp, &optional_actions)) return NULL; \
|
||||||
struct termios *termios_p = PyLong_AsVoidPtr(tp);
|
struct termios *termios_p = PyLong_AsVoidPtr(tp);
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
normal_tty(PyObject *self UNUSED, PyObject *args) {
|
normal_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
TTY_ARGS
|
TTY_ARGS
|
||||||
if (tcsetattr(fd, TCSAFLUSH, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
if (tcsetattr(fd, optional_actions, termios_p) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static PyObject*
|
static PyObject*
|
||||||
raw_tty(PyObject *self UNUSED, PyObject *args) {
|
raw_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
TTY_ARGS
|
TTY_ARGS
|
||||||
if (!put_tty_in_raw_mode(fd, termios_p, false)) return NULL;
|
if (!put_tty_in_raw_mode(fd, termios_p, false, optional_actions)) return NULL;
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ raw_tty(PyObject *self UNUSED, PyObject *args) {
|
|||||||
static PyObject*
|
static PyObject*
|
||||||
close_tty(PyObject *self UNUSED, PyObject *args) {
|
close_tty(PyObject *self UNUSED, PyObject *args) {
|
||||||
TTY_ARGS
|
TTY_ARGS
|
||||||
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure
|
tcsetattr(fd, optional_actions, termios_p); // deliberately ignore failure
|
||||||
free(termios_p);
|
free(termios_p);
|
||||||
safe_close(fd, __FILE__, __LINE__);
|
safe_close(fd, __FILE__, __LINE__);
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from typing import (
|
|||||||
Union
|
Union
|
||||||
)
|
)
|
||||||
|
|
||||||
|
import termios
|
||||||
from kitty.boss import Boss
|
from kitty.boss import Boss
|
||||||
from kitty.fonts.render import FontObject
|
from kitty.fonts.render import FontObject
|
||||||
from kitty.options_stub import Options
|
from kitty.options_stub import Options
|
||||||
@@ -865,19 +866,19 @@ def viewport_for_window(
|
|||||||
TermiosPtr = NewType('TermiosPtr', int)
|
TermiosPtr = NewType('TermiosPtr', int)
|
||||||
|
|
||||||
|
|
||||||
def raw_tty(fd: int, termios_ptr: TermiosPtr) -> None:
|
def raw_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def close_tty(fd: int, termios_ptr: TermiosPtr) -> None:
|
def close_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def normal_tty(fd: int, termios_ptr: TermiosPtr) -> None:
|
def normal_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def open_tty(read_with_timeout: bool = False) -> Tuple[int, TermiosPtr]:
|
def open_tty(read_with_timeout: bool = False, optional_actions: int = termios.TCSAFLUSH) -> Tuple[int, TermiosPtr]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -596,6 +596,7 @@ START_ALLOW_CASE_RANGE
|
|||||||
case 0x2500 ... 0x2573:
|
case 0x2500 ... 0x2573:
|
||||||
case 0x2574 ... 0x259f:
|
case 0x2574 ... 0x259f:
|
||||||
case 0xe0b0 ... 0xe0b4:
|
case 0xe0b0 ... 0xe0b4:
|
||||||
|
case 0x2800 ... 0x28ff:
|
||||||
case 0xe0b6:
|
case 0xe0b6:
|
||||||
case 0xe0b8: //
|
case 0xe0b8: //
|
||||||
case 0xe0ba: //
|
case 0xe0ba: //
|
||||||
@@ -642,8 +643,10 @@ START_ALLOW_CASE_RANGE
|
|||||||
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
|
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
|
||||||
case 0x1fb00 ... 0x1fb8b:
|
case 0x1fb00 ... 0x1fb8b:
|
||||||
return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150
|
return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150
|
||||||
case 0x1fba0 ... 0x1fbae:
|
case 0x1fba0 ... 0x1fbae: // IDs from 0x151 to 0x15f
|
||||||
return 0x151 + ch - 0x1fba0;
|
return 0x151 + ch - 0x1fba0;
|
||||||
|
case 0x2800 ... 0x28ff:
|
||||||
|
return 0x160 + ch - 0x2800;
|
||||||
default:
|
default:
|
||||||
return 0xffff;
|
return 0xffff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -666,12 +666,12 @@ def smooth_mosaic(
|
|||||||
def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int = 0, horizontal: bool = False) -> None:
|
def eight_bar(buf: BufType, width: int, height: int, level: int = 1, which: int = 0, horizontal: bool = False) -> None:
|
||||||
if horizontal:
|
if horizontal:
|
||||||
x_range = range(0, width)
|
x_range = range(0, width)
|
||||||
thickness = height // 8
|
thickness = max(1, height // 8)
|
||||||
y_start = min(which * thickness, height - 2)
|
y_start = min(which * thickness, height - 2)
|
||||||
y_range = range(y_start, height if which == 7 else min(y_start + thickness, height))
|
y_range = range(y_start, height if which == 7 else min(y_start + thickness, height))
|
||||||
else:
|
else:
|
||||||
y_range = range(0, height)
|
y_range = range(0, height)
|
||||||
thickness = width // 8
|
thickness = max(1, width // 8)
|
||||||
x_start = min(which * thickness, width - 2)
|
x_start = min(which * thickness, width - 2)
|
||||||
x_range = range(x_start, width if which == 7 else min(x_start + thickness, width))
|
x_range = range(x_start, width if which == 7 else min(x_start + thickness, width))
|
||||||
for y in y_range:
|
for y in y_range:
|
||||||
@@ -685,6 +685,32 @@ def eight_block(buf: BufType, width: int, height: int, level: int = 1, which: Tu
|
|||||||
eight_bar(buf, width, height, level, x, horizontal)
|
eight_bar(buf, width, height, level, x, horizontal)
|
||||||
|
|
||||||
|
|
||||||
|
def braille_dot(buf: BufType, width: int, height: int, col: int, row: int) -> None:
|
||||||
|
dot_height = max(1, height // 8)
|
||||||
|
dot_width = max(1, width // 4)
|
||||||
|
top_margin = (height - 7 * dot_height) // 2
|
||||||
|
left_margin = (width - 3 * dot_width) // 2
|
||||||
|
x_start = left_margin + (col * 2 * dot_width)
|
||||||
|
y_start = top_margin + (row * 2 * dot_height)
|
||||||
|
if y_start < height:
|
||||||
|
for y in range(y_start, min(height, y_start + dot_height)):
|
||||||
|
if x_start < width:
|
||||||
|
offset = y * width
|
||||||
|
for x in range(x_start, min(width, x_start + dot_width)):
|
||||||
|
buf[offset + x] = 255
|
||||||
|
|
||||||
|
|
||||||
|
def braille(buf: BufType, width: int, height: int, which: int = 0) -> None:
|
||||||
|
if not which:
|
||||||
|
return
|
||||||
|
for i, x in enumerate(reversed(bin(which)[2:])):
|
||||||
|
if x == '1':
|
||||||
|
q = i + 1
|
||||||
|
col = 0 if q in (1, 2, 3, 7) else 1
|
||||||
|
row = 0 if q in (1, 4) else 1 if q in (2, 5) else 2 if q in (3, 6) else 3
|
||||||
|
braille_dot(buf, width, height, col, row)
|
||||||
|
|
||||||
|
|
||||||
box_chars: Dict[str, List[Callable]] = {
|
box_chars: Dict[str, List[Callable]] = {
|
||||||
'─': [hline],
|
'─': [hline],
|
||||||
'━': [p(hline, level=3)],
|
'━': [p(hline, level=3)],
|
||||||
@@ -908,6 +934,9 @@ for chars, func_ in (('╒╕╘╛', dvcorner), ('╓╖╙╜', dhcorner), ('
|
|||||||
for ch in chars:
|
for ch in chars:
|
||||||
box_chars[ch] = [p(cast(Callable, func_), which=ch)]
|
box_chars[ch] = [p(cast(Callable, func_), which=ch)]
|
||||||
|
|
||||||
|
for i in range(256):
|
||||||
|
box_chars[chr(0x2800 + i)] = [p(braille, which=i)]
|
||||||
|
|
||||||
|
|
||||||
c = 0x1fb00
|
c = 0x1fb00
|
||||||
for i in range(1, 63):
|
for i in range(1, 63):
|
||||||
|
|||||||
@@ -421,6 +421,7 @@ make_os_window_context_current(OSWindow *w) {
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
|
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
|
||||||
|
*xscale = 1; *yscale = 1;
|
||||||
if (w) glfwGetWindowContentScale(w, xscale, yscale);
|
if (w) glfwGetWindowContentScale(w, xscale, yscale);
|
||||||
else {
|
else {
|
||||||
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
||||||
@@ -440,7 +441,7 @@ get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xd
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
||||||
float xscale = 1, yscale = 1;
|
float xscale, yscale;
|
||||||
get_window_content_scale(w, &xscale, &yscale, x, y);
|
get_window_content_scale(w, &xscale, &yscale, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
57
kitty/guess_mime_type.py
Normal file
57
kitty/guess_mime_type.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
import os
|
||||||
|
from contextlib import suppress
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
known_extensions = {
|
||||||
|
'asciidoc': 'text/asciidoctor',
|
||||||
|
'conf': 'text/config',
|
||||||
|
'md': 'text/markdown',
|
||||||
|
'pyj': 'text/rapydscript-ng',
|
||||||
|
'recipe': 'text/python',
|
||||||
|
'rst': 'text/restructured-text',
|
||||||
|
'toml': 'text/toml',
|
||||||
|
'vim': 'text/vim',
|
||||||
|
'yaml': 'text/yaml',
|
||||||
|
'js': 'text/javascript',
|
||||||
|
'json': 'text/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
text_mimes = ('application/javascript', 'application/x-sh', 'application/json')
|
||||||
|
|
||||||
|
|
||||||
|
def is_rc_file(path: str) -> bool:
|
||||||
|
name = os.path.basename(path)
|
||||||
|
return '.' not in name and name.endswith('rc')
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_mime_database() -> None:
|
||||||
|
if hasattr(initialize_mime_database, 'inited'):
|
||||||
|
return
|
||||||
|
setattr(initialize_mime_database, 'inited', True)
|
||||||
|
from mimetypes import init
|
||||||
|
init(None)
|
||||||
|
from kitty.constants import config_dir
|
||||||
|
local_defs = os.path.join(config_dir, 'mime.types')
|
||||||
|
if os.path.exists(local_defs):
|
||||||
|
init((local_defs,))
|
||||||
|
|
||||||
|
|
||||||
|
def guess_type(path: str) -> Optional[str]:
|
||||||
|
from mimetypes import guess_type as stdlib_guess_type
|
||||||
|
initialize_mime_database()
|
||||||
|
mt = None
|
||||||
|
with suppress(Exception):
|
||||||
|
mt = stdlib_guess_type(path)[0]
|
||||||
|
if not mt:
|
||||||
|
ext = path.rpartition('.')[-1].lower()
|
||||||
|
mt = known_extensions.get(ext)
|
||||||
|
if mt in text_mimes:
|
||||||
|
mt = 'text/' + mt.split('/', 1)[-1]
|
||||||
|
if not mt and is_rc_file(path):
|
||||||
|
mt = 'text/plain'
|
||||||
|
return mt
|
||||||
@@ -63,7 +63,7 @@ alloc_pagerhist(size_t pagerhist_sz) {
|
|||||||
if (!ph) return NULL;
|
if (!ph) return NULL;
|
||||||
ph->max_sz = pagerhist_sz;
|
ph->max_sz = pagerhist_sz;
|
||||||
ph->buffer_size = MIN(1024u*1024u, ph->max_sz);
|
ph->buffer_size = MIN(1024u*1024u, ph->max_sz);
|
||||||
ph->buffer = PyMem_RawMalloc(ph->buffer_size);
|
ph->buffer = PyMem_Malloc(ph->buffer_size);
|
||||||
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
|
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
|
||||||
return ph;
|
return ph;
|
||||||
}
|
}
|
||||||
@@ -78,8 +78,8 @@ free_pagerhist(HistoryBuf *self) {
|
|||||||
static inline bool
|
static inline bool
|
||||||
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
||||||
if (ph->buffer_size >= ph->max_sz) return false;
|
if (ph->buffer_size >= ph->max_sz) return false;
|
||||||
size_t newsz = ph->buffer_size + MAX(1024u * 1024u, minsz);
|
size_t newsz = MIN(ph->max_sz, ph->buffer_size + MAX(1024u * 1024u, minsz));
|
||||||
uint8_t *newbuf = PyMem_Malloc(MIN(ph->buffer_size + minsz, ph->max_sz));
|
uint8_t *newbuf = PyMem_Malloc(newsz);
|
||||||
if (!newbuf) return false;
|
if (!newbuf) return false;
|
||||||
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
|
size_t copied = MIN(ph->length, ph->buffer_size - ph->start);
|
||||||
if (copied) memcpy(newbuf, ph->buffer + ph->start, copied);
|
if (copied) memcpy(newbuf, ph->buffer + ph->start, copied);
|
||||||
|
|||||||
2
kitty/key_encoding.py
generated
2
kitty/key_encoding.py
generated
@@ -424,7 +424,7 @@ def update_encoding() -> None:
|
|||||||
ans = ENCODING
|
ans = ENCODING
|
||||||
key_map = {}
|
key_map = {}
|
||||||
i = len(ans)
|
i = len(ans)
|
||||||
for k in sorted(keys, key=lambda k: getattr(defines, k)):
|
for k in sorted(keys, key=lambda k: int(getattr(defines, k))):
|
||||||
if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'):
|
if k in ('GLFW_KEY_LAST', 'GLFW_KEY_LAST_PRINTABLE'):
|
||||||
continue
|
continue
|
||||||
val = getattr(defines, k)
|
val = getattr(defines, k)
|
||||||
|
|||||||
@@ -245,10 +245,8 @@ empty:
|
|||||||
|
|
||||||
error:
|
error:
|
||||||
Py_XDECREF(py_ret);
|
Py_XDECREF(py_ret);
|
||||||
if (procargs != NULL)
|
free(procargs);
|
||||||
free(procargs);
|
free(procenv);
|
||||||
if (procenv != NULL)
|
|
||||||
free(procargs);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,6 +195,14 @@ cell_for_pos(Window *w, unsigned int *x, unsigned int *y, bool *in_left_half_of_
|
|||||||
|
|
||||||
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
|
#define HANDLER(name) static inline void name(Window UNUSED *w, int UNUSED button, int UNUSED modifiers, unsigned int UNUSED window_idx)
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
set_mouse_cursor_when_dragging(void) {
|
||||||
|
if (mouse_cursor_shape != OPT(pointer_shape_when_dragging)) {
|
||||||
|
mouse_cursor_shape = OPT(pointer_shape_when_dragging);
|
||||||
|
set_mouse_cursor(mouse_cursor_shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
||||||
Screen *screen = w->render_data.screen;
|
Screen *screen = w->render_data.screen;
|
||||||
@@ -213,6 +221,7 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
|||||||
} else if (screen->selections.in_progress) {
|
} else if (screen->selections.in_progress) {
|
||||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false);
|
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, false);
|
||||||
}
|
}
|
||||||
|
set_mouse_cursor_when_dragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool
|
static inline bool
|
||||||
@@ -250,6 +259,7 @@ extend_selection(Window *w, bool ended) {
|
|||||||
if (screen_has_selection(screen)) {
|
if (screen_has_selection(screen)) {
|
||||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false);
|
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, ended, false);
|
||||||
}
|
}
|
||||||
|
set_mouse_cursor_when_dragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -265,7 +275,7 @@ extend_url(Screen *screen, Line *line, index_type *x, index_type *y, char_type s
|
|||||||
// we deliberately allow non-continued lines as some programs, like
|
// we deliberately allow non-continued lines as some programs, like
|
||||||
// mutt split URLs with newlines at line boundaries
|
// mutt split URLs with newlines at line boundaries
|
||||||
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars);
|
index_type new_x = line_url_end_at(line, 0, false, sentinel, next_line_starts_with_url_chars);
|
||||||
if (!new_x) break;
|
if (!new_x && !line_startswith_url_chars(line)) break;
|
||||||
*y += 1; *x = new_x;
|
*y += 1; *x = new_x;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,7 +305,7 @@ get_url_sentinel(Line *line, index_type url_start) {
|
|||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
set_mouse_cursor_for_screen(Screen *screen) {
|
set_mouse_cursor_for_screen(Screen *screen) {
|
||||||
mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? BEAM : OPT(pointer_shape_when_grabbed);
|
mouse_cursor_shape = screen->modes.mouse_tracking_mode == NO_TRACKING ? OPT(default_pointer_shape): OPT(pointer_shape_when_grabbed);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
@@ -397,6 +407,7 @@ multi_click(Window *w, unsigned int count) {
|
|||||||
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode);
|
screen_start_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, mode);
|
||||||
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true);
|
screen_update_selection(screen, w->mouse_pos.cell_x, w->mouse_pos.cell_y, w->mouse_pos.in_left_half_of_cell, false, true);
|
||||||
}
|
}
|
||||||
|
set_mouse_cursor_when_dragging();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline double
|
static inline double
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from .config import KeyAction, parse_key_action
|
|||||||
from .constants import config_dir
|
from .constants import config_dir
|
||||||
from .typing import MatchType
|
from .typing import MatchType
|
||||||
from .utils import expandvars, log_error
|
from .utils import expandvars, log_error
|
||||||
|
from .guess_mime_type import guess_type
|
||||||
|
|
||||||
|
|
||||||
class MatchCriteria(NamedTuple):
|
class MatchCriteria(NamedTuple):
|
||||||
@@ -75,12 +76,8 @@ def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc:
|
|||||||
|
|
||||||
if mc.type == 'mime':
|
if mc.type == 'mime':
|
||||||
import fnmatch
|
import fnmatch
|
||||||
from mimetypes import guess_type
|
mt = guess_type(unquoted_path)
|
||||||
try:
|
if not mt:
|
||||||
mt = guess_type(unquoted_path)[0]
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
if mt is None:
|
|
||||||
return False
|
return False
|
||||||
mt = mt.lower()
|
mt = mt.lower()
|
||||||
for mpat in mc.value.split(','):
|
for mpat in mc.value.split(','):
|
||||||
@@ -161,8 +158,14 @@ def actions_for_url_from_list(url: str, actions: Iterable[OpenAction]) -> Genera
|
|||||||
}
|
}
|
||||||
|
|
||||||
def expand(x: Any) -> Any:
|
def expand(x: Any) -> Any:
|
||||||
|
as_bytes = isinstance(x, bytes)
|
||||||
|
if as_bytes:
|
||||||
|
x = x.decode('utf-8')
|
||||||
if isinstance(x, str):
|
if isinstance(x, str):
|
||||||
return expandvars(x, env, fallback_to_os_env=False)
|
ans = expandvars(x, env, fallback_to_os_env=False)
|
||||||
|
if as_bytes:
|
||||||
|
return ans.encode('utf-8')
|
||||||
|
return ans
|
||||||
return x
|
return x
|
||||||
|
|
||||||
for action in actions:
|
for action in actions:
|
||||||
|
|||||||
@@ -373,8 +373,10 @@ selection_has_screen_line(const Selections *selections, const int y) {
|
|||||||
for (size_t i = 0; i < selections->count; i++) {
|
for (size_t i = 0; i < selections->count; i++) {
|
||||||
const Selection *s = selections->items + i;
|
const Selection *s = selections->items + i;
|
||||||
if (!is_selection_empty(s)) {
|
if (!is_selection_empty(s)) {
|
||||||
int top = (int)s->start.y - s->start_scrolled_by;
|
int start = (int)s->start.y - s->start_scrolled_by;
|
||||||
int bottom = (int)s->end.y - s->end_scrolled_by;
|
int end = (int)s->end.y - s->end_scrolled_by;
|
||||||
|
int top = MIN(start, end);
|
||||||
|
int bottom = MAX(start, end);
|
||||||
if (top <= y && y <= bottom) return true;
|
if (top <= y && y <= bottom) return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1311,7 +1313,7 @@ screen_delete_lines(Screen *self, unsigned int count) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_insert_characters(Screen *self, unsigned int count) {
|
screen_insert_characters(Screen *self, unsigned int count) {
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
|
||||||
if (count == 0) count = 1;
|
if (count == 0) count = 1;
|
||||||
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||||
unsigned int x = self->cursor->x;
|
unsigned int x = self->cursor->x;
|
||||||
@@ -1327,7 +1329,7 @@ screen_insert_characters(Screen *self, unsigned int count) {
|
|||||||
|
|
||||||
void
|
void
|
||||||
screen_repeat_character(Screen *self, unsigned int count) {
|
screen_repeat_character(Screen *self, unsigned int count) {
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
|
||||||
unsigned int x = self->cursor->x;
|
unsigned int x = self->cursor->x;
|
||||||
if (count == 0) count = 1;
|
if (count == 0) count = 1;
|
||||||
if (x > self->columns) return;
|
if (x > self->columns) return;
|
||||||
@@ -1349,7 +1351,7 @@ screen_repeat_character(Screen *self, unsigned int count) {
|
|||||||
void
|
void
|
||||||
screen_delete_characters(Screen *self, unsigned int count) {
|
screen_delete_characters(Screen *self, unsigned int count) {
|
||||||
// Delete characters, later characters are moved left
|
// Delete characters, later characters are moved left
|
||||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
const unsigned int top = 0, bottom = self->lines ? self->lines - 1 : 0;
|
||||||
if (count == 0) count = 1;
|
if (count == 0) count = 1;
|
||||||
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
if (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||||
unsigned int x = self->cursor->x;
|
unsigned int x = self->cursor->x;
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ def parse_session(raw: str, opts: Options, default_title: Optional[str] = None)
|
|||||||
w: Optional[Watchers] = None
|
w: Optional[Watchers] = None
|
||||||
if t.watchers.has_watchers:
|
if t.watchers.has_watchers:
|
||||||
w = t.watchers.copy()
|
w = t.watchers.copy()
|
||||||
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w))
|
t.windows.append(SpecialWindow(cmd=resolved_shell(opts), watchers=w, override_title=default_title))
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
ans = Session(default_title)
|
ans = Session(default_title)
|
||||||
|
|||||||
@@ -703,6 +703,8 @@ PYWRAP1(set_options) {
|
|||||||
S(resize_in_steps, PyObject_IsTrue);
|
S(resize_in_steps, PyObject_IsTrue);
|
||||||
S(allow_hyperlinks, PyObject_IsTrue);
|
S(allow_hyperlinks, PyObject_IsTrue);
|
||||||
S(pointer_shape_when_grabbed, pointer_shape);
|
S(pointer_shape_when_grabbed, pointer_shape);
|
||||||
|
S(default_pointer_shape, pointer_shape);
|
||||||
|
S(pointer_shape_when_dragging, pointer_shape);
|
||||||
|
|
||||||
GA(tab_bar_style);
|
GA(tab_bar_style);
|
||||||
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
|
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ typedef struct {
|
|||||||
bool allow_hyperlinks;
|
bool allow_hyperlinks;
|
||||||
monotonic_t resize_debounce_time;
|
monotonic_t resize_debounce_time;
|
||||||
MouseShape pointer_shape_when_grabbed;
|
MouseShape pointer_shape_when_grabbed;
|
||||||
|
MouseShape default_pointer_shape;
|
||||||
|
MouseShape pointer_shape_when_dragging;
|
||||||
struct {
|
struct {
|
||||||
UrlPrefix *values;
|
UrlPrefix *values;
|
||||||
size_t num, max_prefix_len;
|
size_t num, max_prefix_len;
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from typing import cast, Dict
|
from typing import TYPE_CHECKING, Dict, Generator, Optional, cast
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .options_stub import Options
|
||||||
|
|
||||||
|
|
||||||
def modify_key_bytes(keybytes: bytes, amt: int) -> bytes:
|
def modify_key_bytes(keybytes: bytes, amt: int) -> bytes:
|
||||||
@@ -449,28 +452,42 @@ def key_as_bytes(name: str) -> bytes:
|
|||||||
return ans.encode('ascii')
|
return ans.encode('ascii')
|
||||||
|
|
||||||
|
|
||||||
def get_capabilities(query_string: str) -> str:
|
def get_capabilities(query_string: str, opts: 'Options') -> Generator[str, None, None]:
|
||||||
from .fast_data_types import ERROR_PREFIX
|
from .fast_data_types import ERROR_PREFIX
|
||||||
ans = []
|
|
||||||
try:
|
def result(encoded_query_name: str, x: Optional[str] = None) -> str:
|
||||||
for q in query_string.split(';'):
|
if x is None:
|
||||||
name = qname = unhexlify(q).decode('utf-8')
|
return '0+r' + encoded_query_name
|
||||||
if name in ('TN', 'name'):
|
return '1+r' + encoded_query_name + '=' + hexlify(str(x).encode('utf-8')).decode('ascii')
|
||||||
val = names[0]
|
|
||||||
|
for encoded_query_name in query_string.split(';'):
|
||||||
|
name = qname = unhexlify(encoded_query_name).decode('utf-8')
|
||||||
|
if name in ('TN', 'name'):
|
||||||
|
yield result(encoded_query_name, names[0])
|
||||||
|
elif name.startswith('kitty-query-'):
|
||||||
|
name = name[len('kitty-query-'):]
|
||||||
|
if name == 'version':
|
||||||
|
from .constants import str_version
|
||||||
|
yield result(encoded_query_name, str_version)
|
||||||
|
elif name == 'allow_hyperlinks':
|
||||||
|
yield result(encoded_query_name,
|
||||||
|
'ask' if opts.allow_hyperlinks == 0b11 else ('yes' if opts.allow_hyperlinks else 'no'))
|
||||||
else:
|
else:
|
||||||
|
from .utils import log_error
|
||||||
|
log_error('Unknown kitty terminfo query:', name)
|
||||||
|
yield result(encoded_query_name)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
val = queryable_capabilities[name]
|
||||||
|
except KeyError:
|
||||||
try:
|
try:
|
||||||
val = queryable_capabilities[name]
|
qname = termcap_aliases[name]
|
||||||
except KeyError:
|
val = queryable_capabilities[qname]
|
||||||
try:
|
except Exception:
|
||||||
qname = termcap_aliases[name]
|
from .utils import log_error
|
||||||
val = queryable_capabilities[qname]
|
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
||||||
except Exception:
|
yield result(encoded_query_name)
|
||||||
from .utils import log_error
|
continue
|
||||||
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
if qname in string_capabilities and '%' not in val:
|
||||||
raise
|
val = key_as_bytes(qname).decode('ascii')
|
||||||
if qname in string_capabilities and '%' not in val:
|
yield result(encoded_query_name, val)
|
||||||
val = key_as_bytes(qname).decode('ascii')
|
|
||||||
ans.append(q + '=' + hexlify(str(val).encode('utf-8')).decode('ascii'))
|
|
||||||
return '1+r' + ';'.join(ans)
|
|
||||||
except Exception:
|
|
||||||
return '0+r' + query_string
|
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ def save_notification(version: Version) -> None:
|
|||||||
for version in sorted(notified_versions):
|
for version in sorted(notified_versions):
|
||||||
n = notified_versions[version]
|
n = notified_versions[version]
|
||||||
lines.append('{},{},{}'.format(
|
lines.append('{},{},{}'.format(
|
||||||
'.'.join(map(str, n.version)), n.time_of_last_notification, n.count))
|
'.'.join(map(str, n.version)),
|
||||||
|
n.time_of_last_notification,
|
||||||
|
n.notification_count))
|
||||||
atomic_save('\n'.join(lines).encode('utf-8'), version_notification_log())
|
atomic_save('\n'.join(lines).encode('utf-8'), version_notification_log())
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -580,6 +580,13 @@ def parse_uri_list(text: str) -> Generator[str, None, None]:
|
|||||||
yield unquote(purl.path)
|
yield unquote(purl.path)
|
||||||
|
|
||||||
|
|
||||||
|
def edit_config_file() -> None:
|
||||||
|
from kitty.config import prepare_config_file_for_editing
|
||||||
|
p = prepare_config_file_for_editing()
|
||||||
|
editor = get_editor()
|
||||||
|
os.execvp(editor[0], editor + [p])
|
||||||
|
|
||||||
|
|
||||||
class SSHConnectionData(NamedTuple):
|
class SSHConnectionData(NamedTuple):
|
||||||
binary: str
|
binary: str
|
||||||
hostname: str
|
hostname: str
|
||||||
|
|||||||
@@ -704,7 +704,8 @@ class Window:
|
|||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def request_capabilities(self, q: str) -> None:
|
def request_capabilities(self, q: str) -> None:
|
||||||
self.screen.send_escape_code_to_child(DCS, get_capabilities(q))
|
for result in get_capabilities(q, self.opts):
|
||||||
|
self.screen.send_escape_code_to_child(DCS, result)
|
||||||
|
|
||||||
def handle_remote_cmd(self, cmd: str) -> None:
|
def handle_remote_cmd(self, cmd: str) -> None:
|
||||||
get_boss().handle_remote_cmd(cmd, self)
|
get_boss().handle_remote_cmd(cmd, self)
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ class Callbacks:
|
|||||||
|
|
||||||
def request_capabilities(self, q):
|
def request_capabilities(self, q):
|
||||||
from kitty.terminfo import get_capabilities
|
from kitty.terminfo import get_capabilities
|
||||||
c = get_capabilities(q)
|
for c in get_capabilities(q, None):
|
||||||
self.write(c.encode('ascii'))
|
self.write(c.encode('ascii'))
|
||||||
|
|
||||||
def use_utf8(self, on):
|
def use_utf8(self, on):
|
||||||
self.iutf8 = on
|
self.iutf8 = on
|
||||||
|
|||||||
@@ -30,3 +30,31 @@ class TestHints(BaseTest):
|
|||||||
t('link:{}[xxx]'.format(u), u)
|
t('link:{}[xxx]'.format(u), u)
|
||||||
t('`xyz <{}>`_.'.format(u), u)
|
t('`xyz <{}>`_.'.format(u), u)
|
||||||
t('<a href="{}">moo'.format(u), u)
|
t('<a href="{}">moo'.format(u), u)
|
||||||
|
|
||||||
|
def test_ip_hints(self):
|
||||||
|
from kittens.hints.main import parse_hints_args, functions_for, mark, convert_text
|
||||||
|
args = parse_hints_args(['--type', 'ip'])[0]
|
||||||
|
pattern, post_processors = functions_for(args)
|
||||||
|
|
||||||
|
def create_marks(text, cols=60):
|
||||||
|
text = convert_text(text, cols)
|
||||||
|
return tuple(mark(pattern, post_processors, text, args))
|
||||||
|
|
||||||
|
testcases = (
|
||||||
|
('100.64.0.0', ['100.64.0.0']),
|
||||||
|
('2001:0db8:0000:0000:0000:ff00:0042:8329', ['2001:0db8:0000:0000:0000:ff00:0042:8329']),
|
||||||
|
('2001:db8:0:0:0:ff00:42:8329', ['2001:db8:0:0:0:ff00:42:8329']),
|
||||||
|
('2001:db8::ff00:42:8329', ['2001:db8::ff00:42:8329']),
|
||||||
|
('2001:DB8::FF00:42:8329', ['2001:DB8::FF00:42:8329']),
|
||||||
|
('0000:0000:0000:0000:0000:0000:0000:0001', ['0000:0000:0000:0000:0000:0000:0000:0001']),
|
||||||
|
('::1', ['::1']),
|
||||||
|
# Invalid IPs won't match
|
||||||
|
('255.255.255.256', []),
|
||||||
|
(':1', []),
|
||||||
|
)
|
||||||
|
|
||||||
|
for testcase, expected in testcases:
|
||||||
|
with self.subTest(testcase=testcase, expected=expected):
|
||||||
|
marks = create_marks(testcase)
|
||||||
|
ips = [m.text for m in marks]
|
||||||
|
self.ae(ips, expected)
|
||||||
|
|||||||
@@ -142,6 +142,13 @@ class TestScreen(BaseTest):
|
|||||||
self.ae(str(s.line(0)), 'ade')
|
self.ae(str(s.line(0)), 'ade')
|
||||||
self.assertTrue(s.line(0).cursor_from(4).bold)
|
self.assertTrue(s.line(0).cursor_from(4).bold)
|
||||||
self.assertFalse(s.line(0).cursor_from(2).bold)
|
self.assertFalse(s.line(0).cursor_from(2).bold)
|
||||||
|
s = self.create_screen()
|
||||||
|
s.set_margins(1, 2)
|
||||||
|
s.cursor.y = 3
|
||||||
|
s.draw('abcde')
|
||||||
|
s.cursor.x = 0
|
||||||
|
s.delete_characters(2)
|
||||||
|
self.ae('cde', str(s.line(s.cursor.y)))
|
||||||
|
|
||||||
init()
|
init()
|
||||||
s.erase_characters(2)
|
s.erase_characters(2)
|
||||||
|
|||||||
13
launcher.c
13
launcher.c
@@ -42,10 +42,11 @@ safe_realpath(const char* src, char *buf, size_t buf_sz) {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
set_xoptions(const wchar_t *exe_dir, const char *lc_ctype) {
|
set_xoptions(const wchar_t *exe_dir, const char *lc_ctype, bool from_source) {
|
||||||
wchar_t buf[PATH_MAX+1] = {0};
|
wchar_t buf[PATH_MAX+1] = {0};
|
||||||
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
|
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
|
||||||
PySys_AddXOption(buf);
|
PySys_AddXOption(buf);
|
||||||
|
if (from_source) PySys_AddXOption(L"kitty_from_source=1");
|
||||||
if (lc_ctype) {
|
if (lc_ctype) {
|
||||||
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
|
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
|
||||||
PySys_AddXOption(buf);
|
PySys_AddXOption(buf);
|
||||||
@@ -66,7 +67,6 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
|||||||
int ret = 1;
|
int ret = 1;
|
||||||
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
||||||
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; }
|
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir\n"); return 1; }
|
||||||
set_xoptions(exe_dir, lc_ctype);
|
|
||||||
wchar_t stdlib[PATH_MAX+1] = {0};
|
wchar_t stdlib[PATH_MAX+1] = {0};
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
const char *python_relpath = "../Resources/Python/lib";
|
const char *python_relpath = "../Resources/Python/lib";
|
||||||
@@ -80,9 +80,9 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
|||||||
);
|
);
|
||||||
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; }
|
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to python stdlib\n"); return 1; }
|
||||||
Py_SetPath(stdlib);
|
Py_SetPath(stdlib);
|
||||||
PyMem_RawFree(exe_dir);
|
|
||||||
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; }
|
|
||||||
Py_Initialize();
|
Py_Initialize();
|
||||||
|
set_xoptions(exe_dir, lc_ctype, false);
|
||||||
|
PyMem_RawFree(exe_dir);
|
||||||
PySys_SetArgvEx(argc - 1, argv + 1, 0);
|
PySys_SetArgvEx(argc - 1, argv + 1, 0);
|
||||||
PySys_SetObject("frozen", Py_True);
|
PySys_SetObject("frozen", Py_True);
|
||||||
PyObject *kitty = PyUnicode_FromString(libpath);
|
PyObject *kitty = PyUnicode_FromString(libpath);
|
||||||
@@ -104,10 +104,11 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
|||||||
(void)libpath;
|
(void)libpath;
|
||||||
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
||||||
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; }
|
if (exe_dir == NULL) { fprintf(stderr, "Fatal error: cannot decode exe_dir: %s\n", exe_dir_); return 1; }
|
||||||
set_xoptions(exe_dir, lc_ctype);
|
bool from_source = false;
|
||||||
#ifdef FROM_SOURCE
|
#ifdef FROM_SOURCE
|
||||||
PySys_AddXOption(L"kitty_from_source=1");
|
from_source = true;
|
||||||
#endif
|
#endif
|
||||||
|
set_xoptions(exe_dir, lc_ctype, from_source);
|
||||||
PyMem_RawFree(exe_dir);
|
PyMem_RawFree(exe_dir);
|
||||||
return Py_Main(argc, argv);
|
return Py_Main(argc, argv);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
See https://sw.kovidgoyal.net/kitty/protocol-extensions.html
|
|
||||||
21
publish.py
21
publish.py
@@ -68,14 +68,15 @@ def run_html(args: Any) -> None:
|
|||||||
|
|
||||||
def add_analytics() -> None:
|
def add_analytics() -> None:
|
||||||
analytics = '''
|
analytics = '''
|
||||||
<!-- Google Analytics -->
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-20736318-2"></script>
|
||||||
<script>
|
<script>
|
||||||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
window.dataLayer = window.dataLayer || [];
|
||||||
ga('create', 'UA-20736318-2', 'auto');
|
function gtag(){dataLayer.push(arguments);}
|
||||||
ga('send', 'pageview');
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', 'UA-20736318-2');
|
||||||
</script>
|
</script>
|
||||||
<script async="async" src='https://www.google-analytics.com/analytics.js'></script>
|
|
||||||
<!-- End Google Analytics -->\
|
|
||||||
'''
|
'''
|
||||||
for dirpath, firnames, filenames in os.walk(publish_dir):
|
for dirpath, firnames, filenames in os.walk(publish_dir):
|
||||||
for fname in filenames:
|
for fname in filenames:
|
||||||
@@ -87,6 +88,10 @@ ga('send', 'pageview');
|
|||||||
f.write(html.encode('utf-8'))
|
f.write(html.encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
|
def run_docs(args: Any) -> None:
|
||||||
|
subprocess.check_call(['make', 'docs'])
|
||||||
|
|
||||||
|
|
||||||
def run_website(args: Any) -> None:
|
def run_website(args: Any) -> None:
|
||||||
if os.path.exists(publish_dir):
|
if os.path.exists(publish_dir):
|
||||||
shutil.rmtree(publish_dir)
|
shutil.rmtree(publish_dir)
|
||||||
@@ -132,7 +137,7 @@ class ReadFileWithProgressReporting(io.FileIO): # {{{
|
|||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return self._total
|
return self._total
|
||||||
|
|
||||||
def read(self, size: int = -1) -> Optional[bytes]:
|
def read(self, size: int = -1) -> bytes:
|
||||||
data = io.FileIO.read(self, size)
|
data = io.FileIO.read(self, size)
|
||||||
if data:
|
if data:
|
||||||
self.report_progress(len(data))
|
self.report_progress(len(data))
|
||||||
@@ -384,6 +389,8 @@ def main() -> None:
|
|||||||
ans = 'n'
|
ans = 'n'
|
||||||
if ans.lower() != 'y':
|
if ans.lower() != 'y':
|
||||||
return
|
return
|
||||||
|
if actions == ['website']:
|
||||||
|
actions.insert(0, 'docs')
|
||||||
for action in actions:
|
for action in actions:
|
||||||
print('Running', action)
|
print('Running', action)
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
See https://sw.kovidgoyal.net/kitty/remote-control.html
|
|
||||||
25
setup.py
25
setup.py
@@ -63,11 +63,11 @@ class Options(argparse.Namespace):
|
|||||||
for_freeze: bool = False
|
for_freeze: bool = False
|
||||||
libdir_name: str = 'lib'
|
libdir_name: str = 'lib'
|
||||||
extra_logging: List[str] = []
|
extra_logging: List[str] = []
|
||||||
link_time_optimization: bool = True
|
link_time_optimization: bool = 'KITTY_NO_LTO' not in os.environ
|
||||||
update_check_interval: float = 24
|
update_check_interval: float = 24
|
||||||
egl_library: Optional[str] = None
|
egl_library: Optional[str] = os.getenv('KITTY_EGL_LIBRARY')
|
||||||
startup_notification_library: Optional[str] = None
|
startup_notification_library: Optional[str] = os.getenv('KITTY_STARTUP_NOTIFICATION_LIBRARY')
|
||||||
canberra_library: Optional[str] = None
|
canberra_library: Optional[str] = os.getenv('KITTY_CANBERRA_LIBRARY')
|
||||||
|
|
||||||
|
|
||||||
class CompileKey(NamedTuple):
|
class CompileKey(NamedTuple):
|
||||||
@@ -211,11 +211,11 @@ def get_sanitize_args(cc: str, ccver: Tuple[int, int]) -> List[str]:
|
|||||||
return sanitize_args
|
return sanitize_args
|
||||||
|
|
||||||
|
|
||||||
def test_compile(cc: str, *cflags: str, src: Optional[str] = None) -> bool:
|
def test_compile(cc: str, *cflags: str, src: Optional[str] = None, lang: str = 'c') -> bool:
|
||||||
src = src or 'int main(void) { return 0; }'
|
src = src or 'int main(void) { return 0; }'
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
[cc] + list(cflags) + ['-x', 'c', '-o', os.devnull, '-'],
|
[cc] + list(cflags) + ['-x', lang, '-o', os.devnull, '-'],
|
||||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
stdin = p.stdin
|
stdin = p.stdin
|
||||||
assert stdin is not None
|
assert stdin is not None
|
||||||
@@ -227,9 +227,9 @@ def test_compile(cc: str, *cflags: str, src: Optional[str] = None) -> bool:
|
|||||||
return p.wait() == 0
|
return p.wait() == 0
|
||||||
|
|
||||||
|
|
||||||
def first_successful_compile(cc: str, *cflags: str, src: Optional[str] = None) -> str:
|
def first_successful_compile(cc: str, *cflags: str, src: Optional[str] = None, lang: str = 'c') -> str:
|
||||||
for x in cflags:
|
for x in cflags:
|
||||||
if test_compile(cc, *shlex.split(x), src=src):
|
if test_compile(cc, *shlex.split(x), src=src, lang=lang):
|
||||||
return x
|
return x
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
@@ -340,7 +340,10 @@ def kitty_env() -> Env:
|
|||||||
platform_libs = [
|
platform_libs = [
|
||||||
'-framework', 'CoreText', '-framework', 'CoreGraphics',
|
'-framework', 'CoreText', '-framework', 'CoreGraphics',
|
||||||
]
|
]
|
||||||
user_notifications_framework = first_successful_compile(ans.cc, '-framework UserNotifications')
|
test_program_src = '''#include <UserNotifications/UserNotifications.h>
|
||||||
|
int main(void) { return 0; }\n'''
|
||||||
|
user_notifications_framework = first_successful_compile(
|
||||||
|
ans.cc, '-framework UserNotifications', src=test_program_src, lang='objective-c')
|
||||||
if user_notifications_framework:
|
if user_notifications_framework:
|
||||||
platform_libs.extend(shlex.split(user_notifications_framework))
|
platform_libs.extend(shlex.split(user_notifications_framework))
|
||||||
else:
|
else:
|
||||||
@@ -1176,8 +1179,6 @@ def option_parser() -> argparse.ArgumentParser: # {{{
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
global verbose
|
global verbose
|
||||||
if sys.version_info < (3, 5):
|
|
||||||
raise SystemExit('python >= 3.5 required')
|
|
||||||
args = option_parser().parse_args(namespace=Options())
|
args = option_parser().parse_args(namespace=Options())
|
||||||
verbose = args.verbose > 0
|
verbose = args.verbose > 0
|
||||||
args.prefix = os.path.abspath(args.prefix)
|
args.prefix = os.path.abspath(args.prefix)
|
||||||
|
|||||||
55
shell.nix
Normal file
55
shell.nix
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
{ pkgs ? import <nixpkgs> { } }:
|
||||||
|
with pkgs;
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (lib) optional optionals;
|
||||||
|
inherit (xorg) libX11 libXrandr libXinerama libXcursor libXi libXext;
|
||||||
|
inherit (darwin.apple_sdk.frameworks) Cocoa CoreGraphics Foundation IOKit Kernel OpenGL;
|
||||||
|
harfbuzzWithCoreText = harfbuzz.override { withCoreText = stdenv.isDarwin; };
|
||||||
|
in
|
||||||
|
mkShell rec {
|
||||||
|
buildInputs = [
|
||||||
|
harfbuzzWithCoreText
|
||||||
|
ncurses
|
||||||
|
lcms2
|
||||||
|
] ++ optionals stdenv.isDarwin [
|
||||||
|
Cocoa
|
||||||
|
CoreGraphics
|
||||||
|
Foundation
|
||||||
|
IOKit
|
||||||
|
Kernel
|
||||||
|
OpenGL
|
||||||
|
libpng
|
||||||
|
python3
|
||||||
|
zlib
|
||||||
|
] ++ optionals stdenv.isLinux [
|
||||||
|
fontconfig libunistring libcanberra libX11
|
||||||
|
libXrandr libXinerama libXcursor libxkbcommon libXi libXext
|
||||||
|
wayland-protocols wayland dbus
|
||||||
|
] ++ checkInputs;
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
pkgconfig python3Packages.sphinx ncurses
|
||||||
|
] ++ optionals stdenv.isDarwin [
|
||||||
|
imagemagick
|
||||||
|
libicns # For the png2icns tool.
|
||||||
|
installShellFiles
|
||||||
|
];
|
||||||
|
|
||||||
|
propagatedBuildInputs = optional stdenv.isLinux libGL;
|
||||||
|
|
||||||
|
checkInputs = [
|
||||||
|
python3Packages.pillow
|
||||||
|
];
|
||||||
|
|
||||||
|
# Causes build failure due to warning when using Clang
|
||||||
|
hardeningDisable = [ "strictoverflow" ];
|
||||||
|
|
||||||
|
shellHook = if stdenv.isDarwin then ''
|
||||||
|
export KITTY_NO_LTO=
|
||||||
|
'' else ''
|
||||||
|
export KITTY_EGL_LIBRARY='${stdenv.lib.getLib libGL}/lib/libEGL.so.1'
|
||||||
|
export KITTY_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
|
||||||
|
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
|
||||||
|
'';
|
||||||
|
}
|
||||||
2
test.py
2
test.py
@@ -97,7 +97,7 @@ def run_tests() -> None:
|
|||||||
|
|
||||||
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
|
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
|
||||||
r = unittest.TextTestRunner
|
r = unittest.TextTestRunner
|
||||||
r.resultclass = unittest.TextTestResult # type: ignore
|
r.resultclass = unittest.TextTestResult
|
||||||
init_env()
|
init_env()
|
||||||
runner = r(verbosity=verbosity)
|
runner = r(verbosity=verbosity)
|
||||||
runner.tb_locals = True # type: ignore
|
runner.tb_locals = True # type: ignore
|
||||||
|
|||||||
Reference in New Issue
Block a user