Compare commits
68 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 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -11,3 +11,6 @@ kitty/parse-graphics-command.h linguist-generated=true
|
||||
glfw/*.c linguist-vendored=true
|
||||
glfw/*.h linguist-vendored=true
|
||||
kittens/unicode_input/names.h linguist-generated=true
|
||||
|
||||
*.py text diff=python
|
||||
*.m text diff=objc
|
||||
|
||||
3
.github/workflows/ci.py
vendored
3
.github/workflows/ci.py
vendored
@@ -40,6 +40,9 @@ def install_deps():
|
||||
if is_bundle:
|
||||
install_bundle()
|
||||
else:
|
||||
if is_macos:
|
||||
# needed for zlib for pillow, should not be needed after pillow 8.0
|
||||
os.environ['PKG_CONFIG_PATH'] = '/usr/local/opt/zlib/lib/pkgconfig'
|
||||
run('pip3 install Pillow pygments')
|
||||
|
||||
|
||||
|
||||
@@ -65,6 +65,11 @@ def run_kitten(args: List[str]) -> None:
|
||||
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:
|
||||
func = namespaced_entry_points[args[1]]
|
||||
func(args[1:])
|
||||
@@ -77,6 +82,7 @@ entry_points = {
|
||||
'runpy': runpy,
|
||||
'launch': launch,
|
||||
'kitten': run_kitten,
|
||||
'edit-config': edit_config_file,
|
||||
|
||||
'@': remote_control,
|
||||
'+': namespaced,
|
||||
|
||||
@@ -78,6 +78,19 @@ you might have to rebuild the app.
|
||||
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>`_.
|
||||
|
||||
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
|
||||
----------------------------------
|
||||
|
||||
@@ -4,6 +4,49 @@ Changelog
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
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]
|
||||
-------------------
|
||||
|
||||
|
||||
@@ -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
|
||||
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
|
||||
only monospace fonts, since every cell in the grid has to be the same size. If
|
||||
your font is not listed in ``kitty list-fonts`` it means that it is not
|
||||
monospace. On Linux you can list all monospace fonts with::
|
||||
only monospace fonts, since every cell in the grid has to be the same size.
|
||||
Furthermore, it needs fonts to be freely resizable, so it does not support
|
||||
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
|
||||
|
||||
|
||||
@@ -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
|
||||
* `neofetch <https://github.com/dylanaraps/neofetch>`_ - A command line system
|
||||
information tool
|
||||
* `viu <https://github.com/atanunq/viu>`_ - a terminal image viewer
|
||||
|
||||
|
||||
.. contents::
|
||||
|
||||
@@ -30,6 +30,10 @@ images and other types of documents directly in your terminal, even over SSH.
|
||||
`koneko <https://github.com/twenty5151/koneko>`_
|
||||
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
|
||||
---------------------------------------
|
||||
@@ -63,6 +67,9 @@ System and data visualisation tools
|
||||
|
||||
iplot 'sin(x*3)*exp(x*.2)'
|
||||
|
||||
`onefetch <https://github.com/o2sh/onefetch>`_
|
||||
a tool to fetch information about your git repositories
|
||||
|
||||
|
||||
Editor integration
|
||||
-----------------------
|
||||
|
||||
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``
|
||||
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``
|
||||
A comma separated list of file extensions, for example: ``jpeg, tar.gz``
|
||||
|
||||
@@ -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.
|
||||
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
|
||||
|
||||
@@ -1994,8 +1994,10 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
||||
bytesPerRow:src->width * 4
|
||||
bitsPerPixel:32];
|
||||
if (rep == nil)
|
||||
if (rep == nil) {
|
||||
[native release];
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
||||
[native addRepresentation:rep];
|
||||
@@ -2446,7 +2448,7 @@ START_ALLOW_CASE_RANGE
|
||||
END_ALLOW_CASE_RANGE
|
||||
}
|
||||
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 {
|
||||
unsigned str_pos = 0;
|
||||
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 <fcntl.h>
|
||||
|
||||
static inline int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
static inline int createTmpfileCloexec(char* tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
|
||||
14
glfw/wl_init.c
vendored
14
glfw/wl_init.c
vendored
@@ -717,6 +717,9 @@ glfwWaylandCheckForServerSideDecorations(void) {
|
||||
|
||||
int _glfwPlatformInit(void)
|
||||
{
|
||||
int i;
|
||||
_GLFWmonitor* monitor;
|
||||
|
||||
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
|
||||
if (!_glfw.wl.cursor.handle)
|
||||
{
|
||||
@@ -768,6 +771,17 @@ int _glfwPlatformInit(void)
|
||||
// Sync so we got all initial output events
|
||||
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)
|
||||
{
|
||||
_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_pointer* pointer;
|
||||
struct wl_keyboard* keyboard;
|
||||
struct wl_data_device_manager* dataDeviceManager;
|
||||
struct wl_data_device* dataDevice;
|
||||
struct xdg_wm_base* wmBase;
|
||||
struct zxdg_decoration_manager_v1* decorationManager;
|
||||
struct wp_viewporter* viewporter;
|
||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||
struct wl_data_device_manager* dataDeviceManager;
|
||||
struct wl_data_device* dataDevice;
|
||||
struct wl_data_source* dataSourceForClipboard;
|
||||
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||
@@ -226,6 +226,7 @@ typedef struct _GLFWlibraryWayland
|
||||
|
||||
int32_t keyboardRepeatRate;
|
||||
monotonic_t keyboardRepeatDelay;
|
||||
|
||||
struct {
|
||||
uint32_t key;
|
||||
id_type keyRepeatTimer;
|
||||
@@ -275,7 +276,8 @@ typedef struct _GLFWmonitorWayland
|
||||
int x;
|
||||
int y;
|
||||
int scale;
|
||||
} _GLFWmonitorWayland;
|
||||
|
||||
} _GLFWmonitorWayland;
|
||||
|
||||
// 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>
|
||||
|
||||
|
||||
static void
|
||||
setCursorImage(_GLFWwindow* window)
|
||||
static void setCursorImage(_GLFWwindow* window)
|
||||
{
|
||||
_GLFWcursorWayland defaultCursor = {.shape = GLFW_ARROW_CURSOR};
|
||||
_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
|
||||
* receive SIGBUS on accessing mmap()'ed file contents instead.
|
||||
*/
|
||||
static int
|
||||
createAnonymousFile(off_t size)
|
||||
static int createAnonymousFile(off_t size)
|
||||
{
|
||||
int ret, fd = -1, shm_anon = 0;
|
||||
#ifdef HAS_MEMFD_CREATE
|
||||
@@ -415,10 +413,10 @@ static void createDecorations(_GLFWwindow* window)
|
||||
|
||||
static void destroyDecoration(_GLFWdecorationWayland* decoration)
|
||||
{
|
||||
if (decoration->surface)
|
||||
wl_surface_destroy(decoration->surface);
|
||||
if (decoration->subsurface)
|
||||
wl_subsurface_destroy(decoration->subsurface);
|
||||
if (decoration->surface)
|
||||
wl_surface_destroy(decoration->surface);
|
||||
if (decoration->viewport)
|
||||
wp_viewport_destroy(decoration->viewport);
|
||||
decoration->surface = NULL;
|
||||
@@ -439,7 +437,9 @@ static void xdgDecorationHandleConfigure(void* data,
|
||||
uint32_t mode)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
|
||||
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
|
||||
if (!window->wl.decorations.serverSide)
|
||||
createDecorations(window);
|
||||
}
|
||||
@@ -547,8 +547,7 @@ static bool createSurface(_GLFWwindow* window,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
|
||||
static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, bool on)
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
{
|
||||
@@ -739,8 +738,7 @@ static bool createXdgSurface(_GLFWwindow* window)
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
incrementCursorImage(_GLFWwindow* window)
|
||||
static void incrementCursorImage(_GLFWwindow* window)
|
||||
{
|
||||
if (window && window->wl.decorations.focus == mainWindow && window->cursorMode != GLFW_CURSOR_HIDDEN) {
|
||||
_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);
|
||||
}
|
||||
|
||||
static void
|
||||
handleEvents(monotonic_t timeout)
|
||||
static void handleEvents(monotonic_t timeout)
|
||||
{
|
||||
struct wl_display* display = _glfw.wl.display;
|
||||
errno = 0;
|
||||
@@ -958,7 +955,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
window->context.destroy(window);
|
||||
|
||||
destroyDecorations(window);
|
||||
|
||||
if (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);
|
||||
if (!cursor->wl.buffer)
|
||||
return false;
|
||||
|
||||
cursor->wl.width = image->width;
|
||||
cursor->wl.height = image->height;
|
||||
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;
|
||||
|
||||
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* tp = target;
|
||||
|
||||
if (source) {
|
||||
for (sp = source; *sp; sp++) tp += encodeUTF8(tp, *sp);
|
||||
for (sp = source; *sp; sp++)
|
||||
tp += encodeUTF8(tp, *sp);
|
||||
}
|
||||
|
||||
return target;
|
||||
|
||||
@@ -7,7 +7,7 @@ import re
|
||||
from contextlib import suppress
|
||||
from functools import lru_cache
|
||||
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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -68,7 +68,10 @@ class Collection:
|
||||
self.removed_count += len(lines_for_path(left_path))
|
||||
|
||||
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]]]:
|
||||
for path in self.all_paths:
|
||||
@@ -133,7 +136,7 @@ def sanitize(text: str) -> str:
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
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)
|
||||
|
||||
@@ -13,7 +13,9 @@ from collections import defaultdict
|
||||
from contextlib import suppress
|
||||
from functools import partial
|
||||
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_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.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 .collect import (
|
||||
Collection, create_collection, data_for_path, lines_for_path, sanitize,
|
||||
@@ -35,14 +42,11 @@ from .render import (
|
||||
ImagePlacement, ImageSupportWarning, Line, LineRef, Reference, render_diff
|
||||
)
|
||||
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:
|
||||
from .highlight import initialize_highlighter, highlight_collection, DiffHighlight
|
||||
from .highlight import (
|
||||
DiffHighlight, highlight_collection, initialize_highlighter
|
||||
)
|
||||
has_highlighter = True
|
||||
DiffHighlight
|
||||
except ImportError:
|
||||
@@ -566,8 +570,8 @@ class ShowWarning:
|
||||
def __init__(self) -> None:
|
||||
self.warnings: List[str] = []
|
||||
|
||||
def __call__(self, message: str, category: object, filename: str, lineno: int, file: object = None, line: object = None) -> None:
|
||||
if category is ImageSupportWarning:
|
||||
def __call__(self, message: Any, category: Any, filename: str, lineno: int, file: object = None, line: object = None) -> None:
|
||||
if category is ImageSupportWarning and isinstance(message, str):
|
||||
showwarning.warnings.append(message)
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import contextlib
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
@@ -18,6 +17,7 @@ from typing import (
|
||||
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_stub import IcatCLIOptions
|
||||
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]:
|
||||
for dirpath, dirnames, filenames in os.walk(d):
|
||||
for f in filenames:
|
||||
mt = mimetypes.guess_type(f)[0]
|
||||
mt = guess_type(f)
|
||||
if mt and mt.startswith('image/'):
|
||||
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:
|
||||
ssh_args.append(arg)
|
||||
continue
|
||||
if arg.startswith('-p') and arg[2:].isdigit():
|
||||
ssh_args.append(arg)
|
||||
continue
|
||||
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)
|
||||
expecting_option_val = True
|
||||
continue
|
||||
rest = all_args[i+1:]
|
||||
if rest:
|
||||
ssh_args.append(rest)
|
||||
else:
|
||||
expecting_option_val = True
|
||||
break
|
||||
raise SystemExit('Unknown option: {}'.format(arg))
|
||||
continue
|
||||
if expecting_option_val:
|
||||
|
||||
@@ -10,6 +10,7 @@ import re
|
||||
import selectors
|
||||
import signal
|
||||
import sys
|
||||
import termios
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from typing import Any, Callable, Dict, Generator, List, NamedTuple, Optional
|
||||
@@ -61,8 +62,9 @@ debug = Debug()
|
||||
|
||||
class TermManager:
|
||||
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, optional_actions: int = termios.TCSANOW) -> None:
|
||||
self.extra_finalize: Optional[str] = None
|
||||
self.optional_actions = optional_actions
|
||||
|
||||
def set_state_for_loop(self, set_raw: bool = True) -> None:
|
||||
if set_raw:
|
||||
@@ -82,7 +84,7 @@ class TermManager:
|
||||
self.set_state_for_loop()
|
||||
|
||||
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)
|
||||
return self
|
||||
|
||||
@@ -183,7 +185,8 @@ class Loop:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'
|
||||
sanitize_bracketed_paste: str = '[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]',
|
||||
optional_actions: int = termios.TCSADRAIN
|
||||
):
|
||||
if is_macos:
|
||||
# On macOS PTY devices are not supported by the KqueueSelector and
|
||||
@@ -193,6 +196,7 @@ class Loop:
|
||||
else:
|
||||
self.asycio_loop = asyncio.get_event_loop()
|
||||
self.return_code = 0
|
||||
self.optional_actions = optional_actions
|
||||
self.read_buf = ''
|
||||
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||
try:
|
||||
@@ -388,7 +392,7 @@ class Loop:
|
||||
handler.on_resize(handler.screen_size)
|
||||
|
||||
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)
|
||||
image_manager = None
|
||||
if handler.image_manager_class is not None:
|
||||
|
||||
@@ -69,7 +69,7 @@ class Borders:
|
||||
self.os_window_id = os_window_id
|
||||
self.tab_id = tab_id
|
||||
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__(
|
||||
self,
|
||||
|
||||
@@ -13,6 +13,7 @@ class CLIOptions:
|
||||
LaunchCLIOptions = AskCLIOptions = ClipboardCLIOptions = DiffCLIOptions = CLIOptions
|
||||
HintsCLIOptions = IcatCLIOptions = PanelCLIOptions = ResizeCLIOptions = CLIOptions
|
||||
ErrorCLIOptions = UnicodeCLIOptions = RCOptions = RemoteFileCLIOptions = CLIOptions
|
||||
QueryTerminalCLIOptions = CLIOptions
|
||||
|
||||
|
||||
def generate_stub() -> None:
|
||||
@@ -50,6 +51,9 @@ def generate_stub() -> None:
|
||||
from kittens.icat.main import options_spec
|
||||
do(options_spec(), 'IcatCLIOptions')
|
||||
|
||||
from kittens.query_terminal.main import options_spec
|
||||
do(options_spec(), 'QueryTerminalCLIOptions')
|
||||
|
||||
from kittens.panel.main import OPTIONS
|
||||
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.delegate) center.delegate = [[NotificationDelegate alloc] init];
|
||||
NSUserNotification *n = [NSUserNotification new];
|
||||
#define SET(x) { \
|
||||
if (x) { \
|
||||
NSString *t = @(x); \
|
||||
n.x = t; \
|
||||
[t release]; \
|
||||
}}
|
||||
SET(title); SET(subtitle); SET(informativeText);
|
||||
#undef SET
|
||||
if (title) n.title = @(title);
|
||||
if (subtitle) n.subtitle = @(subtitle);
|
||||
if (informativeText) n.informativeText = @(informativeText);
|
||||
if (identifier) {
|
||||
n.userInfo = @{@"user_id": @(identifier)};
|
||||
}
|
||||
@@ -307,17 +302,17 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||
|
||||
@implementation ServiceProvider
|
||||
|
||||
- (void)openTab:(NSPasteboard*)pasteboard
|
||||
- (BOOL)openTab:(NSPasteboard*)pasteboard
|
||||
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 {
|
||||
[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 };
|
||||
NSArray *filePathArray = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:options];
|
||||
for (NSURL *url in filePathArray) {
|
||||
@@ -330,6 +325,7 @@ cocoa_send_notification(PyObject *self UNUSED, PyObject *args) {
|
||||
set_cocoa_pending_action(type, [path UTF8String]);
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
@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:
|
||||
from mimetypes import guess_type
|
||||
from .guess_mime_type import guess_type
|
||||
|
||||
def icat_file_predicate(filename: str) -> bool:
|
||||
mt = guess_type(filename)[0]
|
||||
mt = guess_type(filename)
|
||||
if mt and mt.startswith('image/'):
|
||||
return True
|
||||
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`
|
||||
'''))
|
||||
|
||||
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') # {{{
|
||||
|
||||
@@ -20,7 +20,7 @@ class Version(NamedTuple):
|
||||
|
||||
|
||||
appname: str = 'kitty'
|
||||
version: Version = Version(0, 19, 1)
|
||||
version: Version = Version(0, 19, 2)
|
||||
str_version: str = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos: bool = 'darwin' in _plat
|
||||
|
||||
@@ -161,12 +161,13 @@ coretext_all_fonts(PyObject UNUSED *_self) {
|
||||
CFArrayRef matches = CTFontCollectionCreateMatchingFontDescriptors(all_fonts_collection());
|
||||
const CFIndex count = CFArrayGetCount(matches);
|
||||
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++) {
|
||||
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;
|
||||
}
|
||||
CFRelease(matches);
|
||||
return ans;
|
||||
}
|
||||
|
||||
@@ -212,6 +213,7 @@ manually_search_fallback_fonts(CTFontRef current_font, CPUCell *cell) {
|
||||
CFRelease(new_font);
|
||||
}
|
||||
}
|
||||
CFRelease(fonts);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
|
||||
#endif
|
||||
|
||||
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;
|
||||
cfmakeraw(&raw_termios);
|
||||
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 {
|
||||
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;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
open_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
int read_with_timeout = 0;
|
||||
if (!PyArg_ParseTuple(args, "|p", &read_with_timeout)) return NULL;
|
||||
int read_with_timeout = 0, optional_actions = TCSAFLUSH;
|
||||
if (!PyArg_ParseTuple(args, "|pi", &read_with_timeout, &optional_actions)) return NULL;
|
||||
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
|
||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||
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));
|
||||
if (!termios_p) return PyErr_NoMemory();
|
||||
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));
|
||||
}
|
||||
|
||||
#define TTY_ARGS \
|
||||
PyObject *tp; int fd; \
|
||||
if (!PyArg_ParseTuple(args, "iO!", &fd, &PyLong_Type, &tp)) return NULL; \
|
||||
PyObject *tp; int fd; int optional_actions = TCSAFLUSH; \
|
||||
if (!PyArg_ParseTuple(args, "iO!|i", &fd, &PyLong_Type, &tp, &optional_actions)) return NULL; \
|
||||
struct termios *termios_p = PyLong_AsVoidPtr(tp);
|
||||
|
||||
static PyObject*
|
||||
normal_tty(PyObject *self UNUSED, PyObject *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;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
raw_tty(PyObject *self UNUSED, PyObject *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;
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ raw_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
static PyObject*
|
||||
close_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
TTY_ARGS
|
||||
tcsetattr(fd, TCSAFLUSH, termios_p); // deliberately ignore failure
|
||||
tcsetattr(fd, optional_actions, termios_p); // deliberately ignore failure
|
||||
free(termios_p);
|
||||
safe_close(fd, __FILE__, __LINE__);
|
||||
Py_RETURN_NONE;
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import (
|
||||
Union
|
||||
)
|
||||
|
||||
import termios
|
||||
from kitty.boss import Boss
|
||||
from kitty.fonts.render import FontObject
|
||||
from kitty.options_stub import Options
|
||||
@@ -865,19 +866,19 @@ def viewport_for_window(
|
||||
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
|
||||
|
||||
|
||||
def close_tty(fd: int, termios_ptr: TermiosPtr) -> None:
|
||||
def close_tty(fd: int, termios_ptr: TermiosPtr, optional_actions: int = termios.TCSAFLUSH) -> None:
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -596,6 +596,7 @@ START_ALLOW_CASE_RANGE
|
||||
case 0x2500 ... 0x2573:
|
||||
case 0x2574 ... 0x259f:
|
||||
case 0xe0b0 ... 0xe0b4:
|
||||
case 0x2800 ... 0x28ff:
|
||||
case 0xe0b6:
|
||||
case 0xe0b8: //
|
||||
case 0xe0ba: //
|
||||
@@ -642,8 +643,10 @@ START_ALLOW_CASE_RANGE
|
||||
return 0xa0 + ch - 0xe0b0; // IDs from 0xa0 to 0xc4
|
||||
case 0x1fb00 ... 0x1fb8b:
|
||||
return 0xc5 + ch - 0x1fb00; // IDs from 0xc5 to 0x150
|
||||
case 0x1fba0 ... 0x1fbae:
|
||||
case 0x1fba0 ... 0x1fbae: // IDs from 0x151 to 0x15f
|
||||
return 0x151 + ch - 0x1fba0;
|
||||
case 0x2800 ... 0x28ff:
|
||||
return 0x160 + ch - 0x2800;
|
||||
default:
|
||||
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:
|
||||
if horizontal:
|
||||
x_range = range(0, width)
|
||||
thickness = height // 8
|
||||
thickness = max(1, height // 8)
|
||||
y_start = min(which * thickness, height - 2)
|
||||
y_range = range(y_start, height if which == 7 else min(y_start + thickness, height))
|
||||
else:
|
||||
y_range = range(0, height)
|
||||
thickness = width // 8
|
||||
thickness = max(1, width // 8)
|
||||
x_start = min(which * thickness, width - 2)
|
||||
x_range = range(x_start, width if which == 7 else min(x_start + thickness, width))
|
||||
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)
|
||||
|
||||
|
||||
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]] = {
|
||||
'─': [hline],
|
||||
'━': [p(hline, level=3)],
|
||||
@@ -908,6 +934,9 @@ for chars, func_ in (('╒╕╘╛', dvcorner), ('╓╖╙╜', dhcorner), ('
|
||||
for ch in chars:
|
||||
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
|
||||
for i in range(1, 63):
|
||||
|
||||
@@ -421,6 +421,7 @@ make_os_window_context_current(OSWindow *w) {
|
||||
|
||||
static inline void
|
||||
get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xdpi, double *ydpi) {
|
||||
*xscale = 1; *yscale = 1;
|
||||
if (w) glfwGetWindowContentScale(w, xscale, yscale);
|
||||
else {
|
||||
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
|
||||
@@ -440,7 +441,7 @@ get_window_content_scale(GLFWwindow *w, float *xscale, float *yscale, double *xd
|
||||
|
||||
static inline void
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
ph->max_sz = pagerhist_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; }
|
||||
return ph;
|
||||
}
|
||||
|
||||
2
kitty/key_encoding.py
generated
2
kitty/key_encoding.py
generated
@@ -424,7 +424,7 @@ def update_encoding() -> None:
|
||||
ans = ENCODING
|
||||
key_map = {}
|
||||
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'):
|
||||
continue
|
||||
val = getattr(defines, k)
|
||||
|
||||
@@ -245,10 +245,8 @@ empty:
|
||||
|
||||
error:
|
||||
Py_XDECREF(py_ret);
|
||||
if (procargs != NULL)
|
||||
free(procargs);
|
||||
if (procenv != NULL)
|
||||
free(procargs);
|
||||
free(procargs);
|
||||
free(procenv);
|
||||
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)
|
||||
|
||||
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
|
||||
update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
|
||||
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) {
|
||||
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
|
||||
@@ -250,6 +259,7 @@ extend_selection(Window *w, bool ended) {
|
||||
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);
|
||||
}
|
||||
set_mouse_cursor_when_dragging();
|
||||
}
|
||||
|
||||
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
|
||||
// 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);
|
||||
if (!new_x) break;
|
||||
if (!new_x && !line_startswith_url_chars(line)) break;
|
||||
*y += 1; *x = new_x;
|
||||
}
|
||||
}
|
||||
@@ -295,7 +305,7 @@ get_url_sentinel(Line *line, index_type url_start) {
|
||||
|
||||
static inline void
|
||||
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
|
||||
@@ -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_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
|
||||
|
||||
@@ -17,6 +17,7 @@ from .config import KeyAction, parse_key_action
|
||||
from .constants import config_dir
|
||||
from .typing import MatchType
|
||||
from .utils import expandvars, log_error
|
||||
from .guess_mime_type import guess_type
|
||||
|
||||
|
||||
class MatchCriteria(NamedTuple):
|
||||
@@ -75,12 +76,8 @@ def url_matches_criterion(purl: 'ParseResult', url: str, unquoted_path: str, mc:
|
||||
|
||||
if mc.type == 'mime':
|
||||
import fnmatch
|
||||
from mimetypes import guess_type
|
||||
try:
|
||||
mt = guess_type(unquoted_path)[0]
|
||||
except Exception:
|
||||
return False
|
||||
if mt is None:
|
||||
mt = guess_type(unquoted_path)
|
||||
if not mt:
|
||||
return False
|
||||
mt = mt.lower()
|
||||
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:
|
||||
as_bytes = isinstance(x, bytes)
|
||||
if as_bytes:
|
||||
x = x.decode('utf-8')
|
||||
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
|
||||
|
||||
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++) {
|
||||
const Selection *s = selections->items + i;
|
||||
if (!is_selection_empty(s)) {
|
||||
int top = (int)s->start.y - s->start_scrolled_by;
|
||||
int bottom = (int)s->end.y - s->end_scrolled_by;
|
||||
int start = (int)s->start.y - s->start_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;
|
||||
}
|
||||
}
|
||||
@@ -1311,7 +1313,7 @@ screen_delete_lines(Screen *self, unsigned int count) {
|
||||
|
||||
void
|
||||
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 (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||
unsigned int x = self->cursor->x;
|
||||
@@ -1327,7 +1329,7 @@ screen_insert_characters(Screen *self, unsigned int count) {
|
||||
|
||||
void
|
||||
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;
|
||||
if (count == 0) count = 1;
|
||||
if (x > self->columns) return;
|
||||
@@ -1349,7 +1351,7 @@ screen_repeat_character(Screen *self, unsigned int count) {
|
||||
void
|
||||
screen_delete_characters(Screen *self, unsigned int count) {
|
||||
// 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 (top <= self->cursor->y && self->cursor->y <= bottom) {
|
||||
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
|
||||
if t.watchers.has_watchers:
|
||||
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
|
||||
|
||||
ans = Session(default_title)
|
||||
|
||||
@@ -703,6 +703,8 @@ PYWRAP1(set_options) {
|
||||
S(resize_in_steps, PyObject_IsTrue);
|
||||
S(allow_hyperlinks, PyObject_IsTrue);
|
||||
S(pointer_shape_when_grabbed, pointer_shape);
|
||||
S(default_pointer_shape, pointer_shape);
|
||||
S(pointer_shape_when_dragging, pointer_shape);
|
||||
|
||||
GA(tab_bar_style);
|
||||
global_state.tab_bar_hidden = PyUnicode_CompareWithASCIIString(ret, "hidden") == 0 ? true: false;
|
||||
|
||||
@@ -66,6 +66,8 @@ typedef struct {
|
||||
bool allow_hyperlinks;
|
||||
monotonic_t resize_debounce_time;
|
||||
MouseShape pointer_shape_when_grabbed;
|
||||
MouseShape default_pointer_shape;
|
||||
MouseShape pointer_shape_when_dragging;
|
||||
struct {
|
||||
UrlPrefix *values;
|
||||
size_t num, max_prefix_len;
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
import re
|
||||
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:
|
||||
@@ -449,28 +452,42 @@ def key_as_bytes(name: str) -> bytes:
|
||||
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
|
||||
ans = []
|
||||
try:
|
||||
for q in query_string.split(';'):
|
||||
name = qname = unhexlify(q).decode('utf-8')
|
||||
if name in ('TN', 'name'):
|
||||
val = names[0]
|
||||
|
||||
def result(encoded_query_name: str, x: Optional[str] = None) -> str:
|
||||
if x is None:
|
||||
return '0+r' + encoded_query_name
|
||||
return '1+r' + encoded_query_name + '=' + hexlify(str(x).encode('utf-8')).decode('ascii')
|
||||
|
||||
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:
|
||||
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:
|
||||
val = queryable_capabilities[name]
|
||||
except KeyError:
|
||||
try:
|
||||
qname = termcap_aliases[name]
|
||||
val = queryable_capabilities[qname]
|
||||
except Exception:
|
||||
from .utils import log_error
|
||||
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
||||
raise
|
||||
if qname in string_capabilities and '%' not in 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
|
||||
qname = termcap_aliases[name]
|
||||
val = queryable_capabilities[qname]
|
||||
except Exception:
|
||||
from .utils import log_error
|
||||
log_error(ERROR_PREFIX, 'Unknown terminfo property:', name)
|
||||
yield result(encoded_query_name)
|
||||
continue
|
||||
if qname in string_capabilities and '%' not in val:
|
||||
val = key_as_bytes(qname).decode('ascii')
|
||||
yield result(encoded_query_name, val)
|
||||
|
||||
@@ -580,6 +580,13 @@ def parse_uri_list(text: str) -> Generator[str, None, None]:
|
||||
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):
|
||||
binary: str
|
||||
hostname: str
|
||||
|
||||
@@ -704,7 +704,8 @@ class Window:
|
||||
self.refresh()
|
||||
|
||||
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:
|
||||
get_boss().handle_remote_cmd(cmd, self)
|
||||
|
||||
@@ -31,8 +31,8 @@ class Callbacks:
|
||||
|
||||
def request_capabilities(self, q):
|
||||
from kitty.terminfo import get_capabilities
|
||||
c = get_capabilities(q)
|
||||
self.write(c.encode('ascii'))
|
||||
for c in get_capabilities(q, None):
|
||||
self.write(c.encode('ascii'))
|
||||
|
||||
def use_utf8(self, on):
|
||||
self.iutf8 = on
|
||||
|
||||
@@ -142,6 +142,13 @@ class TestScreen(BaseTest):
|
||||
self.ae(str(s.line(0)), 'ade')
|
||||
self.assertTrue(s.line(0).cursor_from(4).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()
|
||||
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
|
||||
|
||||
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};
|
||||
swprintf(buf, PATH_MAX, L"bundle_exe_dir=%ls", exe_dir);
|
||||
PySys_AddXOption(buf);
|
||||
if (from_source) PySys_AddXOption(L"kitty_from_source=1");
|
||||
if (lc_ctype) {
|
||||
swprintf(buf, PATH_MAX, L"lc_ctype_before_python=%s", lc_ctype);
|
||||
PySys_AddXOption(buf);
|
||||
@@ -66,7 +67,6 @@ static int run_embedded(const char* exe_dir_, const char *libpath, int argc, wch
|
||||
int ret = 1;
|
||||
wchar_t *exe_dir = Py_DecodeLocale(exe_dir_, NULL);
|
||||
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};
|
||||
#ifdef __APPLE__
|
||||
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; }
|
||||
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();
|
||||
set_xoptions(exe_dir, lc_ctype, false);
|
||||
PyMem_RawFree(exe_dir);
|
||||
PySys_SetArgvEx(argc - 1, argv + 1, 0);
|
||||
PySys_SetObject("frozen", Py_True);
|
||||
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;
|
||||
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; }
|
||||
set_xoptions(exe_dir, lc_ctype);
|
||||
bool from_source = false;
|
||||
#ifdef FROM_SOURCE
|
||||
PySys_AddXOption(L"kitty_from_source=1");
|
||||
from_source = true;
|
||||
#endif
|
||||
set_xoptions(exe_dir, lc_ctype, from_source);
|
||||
PyMem_RawFree(exe_dir);
|
||||
return Py_Main(argc, argv);
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ class ReadFileWithProgressReporting(io.FileIO): # {{{
|
||||
def __len__(self) -> int:
|
||||
return self._total
|
||||
|
||||
def read(self, size: int = -1) -> Optional[bytes]:
|
||||
def read(self, size: int = -1) -> bytes:
|
||||
data = io.FileIO.read(self, size)
|
||||
if data:
|
||||
self.report_progress(len(data))
|
||||
|
||||
23
setup.py
23
setup.py
@@ -63,11 +63,11 @@ class Options(argparse.Namespace):
|
||||
for_freeze: bool = False
|
||||
libdir_name: str = 'lib'
|
||||
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
|
||||
egl_library: Optional[str] = None
|
||||
startup_notification_library: Optional[str] = None
|
||||
canberra_library: Optional[str] = None
|
||||
egl_library: Optional[str] = os.getenv('KITTY_EGL_LIBRARY')
|
||||
startup_notification_library: Optional[str] = os.getenv('KITTY_STARTUP_NOTIFICATION_LIBRARY')
|
||||
canberra_library: Optional[str] = os.getenv('KITTY_CANBERRA_LIBRARY')
|
||||
|
||||
|
||||
class CompileKey(NamedTuple):
|
||||
@@ -211,11 +211,11 @@ def get_sanitize_args(cc: str, ccver: Tuple[int, int]) -> List[str]:
|
||||
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; }'
|
||||
p = subprocess.Popen(
|
||||
[cc] + list(cflags) + ['-x', 'c', '-o', os.devnull, '-'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE
|
||||
[cc] + list(cflags) + ['-x', lang, '-o', os.devnull, '-'],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.PIPE,
|
||||
)
|
||||
stdin = p.stdin
|
||||
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
|
||||
|
||||
|
||||
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:
|
||||
if test_compile(cc, *shlex.split(x), src=src):
|
||||
if test_compile(cc, *shlex.split(x), src=src, lang=lang):
|
||||
return x
|
||||
return ''
|
||||
|
||||
@@ -340,7 +340,10 @@ def kitty_env() -> Env:
|
||||
platform_libs = [
|
||||
'-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:
|
||||
platform_libs.extend(shlex.split(user_notifications_framework))
|
||||
else:
|
||||
|
||||
55
shell.nix
Normal file
55
shell.nix
Normal file
@@ -0,0 +1,55 @@
|
||||
{ pkgs ? import <nixpkgs> { } }:
|
||||
with pkgs;
|
||||
|
||||
let
|
||||
inherit (lib) optional optionals;
|
||||
inherit (xorg) libX11 libXrandr libXinerama libXcursor libXi libXext;
|
||||
inherit (darwin.apple_sdk.frameworks) Cocoa CoreGraphics Foundation IOKit Kernel OpenGL;
|
||||
harfbuzzWithCoreText = harfbuzz.override { withCoreText = stdenv.isDarwin; };
|
||||
in
|
||||
mkShell rec {
|
||||
buildInputs = [
|
||||
harfbuzzWithCoreText
|
||||
ncurses
|
||||
lcms2
|
||||
] ++ optionals stdenv.isDarwin [
|
||||
Cocoa
|
||||
CoreGraphics
|
||||
Foundation
|
||||
IOKit
|
||||
Kernel
|
||||
OpenGL
|
||||
libpng
|
||||
python3
|
||||
zlib
|
||||
] ++ optionals stdenv.isLinux [
|
||||
fontconfig libunistring libcanberra libX11
|
||||
libXrandr libXinerama libXcursor libxkbcommon libXi libXext
|
||||
wayland-protocols wayland dbus
|
||||
] ++ checkInputs;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgconfig python3Packages.sphinx ncurses
|
||||
] ++ optionals stdenv.isDarwin [
|
||||
imagemagick
|
||||
libicns # For the png2icns tool.
|
||||
installShellFiles
|
||||
];
|
||||
|
||||
propagatedBuildInputs = optional stdenv.isLinux libGL;
|
||||
|
||||
checkInputs = [
|
||||
python3Packages.pillow
|
||||
];
|
||||
|
||||
# Causes build failure due to warning when using Clang
|
||||
hardeningDisable = [ "strictoverflow" ];
|
||||
|
||||
shellHook = if stdenv.isDarwin then ''
|
||||
export KITTY_NO_LTO=
|
||||
'' else ''
|
||||
export KITTY_EGL_LIBRARY='${stdenv.lib.getLib libGL}/lib/libEGL.so.1'
|
||||
export KITTY_STARTUP_NOTIFICATION_LIBRARY='${libstartup_notification}/lib/libstartup-notification-1.so'
|
||||
export KITTY_CANBERRA_LIBRARY='${libcanberra}/lib/libcanberra.so'
|
||||
'';
|
||||
}
|
||||
2
test.py
2
test.py
@@ -97,7 +97,7 @@ def run_tests() -> None:
|
||||
|
||||
def run_cli(suite: unittest.TestSuite, verbosity: int = 4) -> None:
|
||||
r = unittest.TextTestRunner
|
||||
r.resultclass = unittest.TextTestResult # type: ignore
|
||||
r.resultclass = unittest.TextTestResult
|
||||
init_env()
|
||||
runner = r(verbosity=verbosity)
|
||||
runner.tb_locals = True # type: ignore
|
||||
|
||||
Reference in New Issue
Block a user