Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6679ff7b5 | ||
|
|
de80003103 | ||
|
|
2b0596c7f9 | ||
|
|
3d15a1d786 | ||
|
|
1fcb865202 | ||
|
|
1c56de5605 | ||
|
|
adbce71fb4 | ||
|
|
00e3ea8c08 | ||
|
|
36432c8616 | ||
|
|
03e0b9de04 | ||
|
|
4364163ceb | ||
|
|
ffd9ec653d | ||
|
|
ee3c3e4cb4 | ||
|
|
277c46908d | ||
|
|
8e27c80e1f | ||
|
|
afa767c3a8 | ||
|
|
67ec04fba3 | ||
|
|
f35bf7f1ba | ||
|
|
4fdb55e260 | ||
|
|
be06669e8f | ||
|
|
313253cd95 | ||
|
|
8c16be2ccd | ||
|
|
e1b276786b | ||
|
|
05f5a05c20 | ||
|
|
7e79aac275 | ||
|
|
0a21819e16 | ||
|
|
fe3e51a00d | ||
|
|
21874339f1 | ||
|
|
4f22fcdaac | ||
|
|
0424d665d5 | ||
|
|
0bbea6812e | ||
|
|
101a50b0ff | ||
|
|
a9f3a698d2 | ||
|
|
5117a2c17a | ||
|
|
ebce1d7c61 | ||
|
|
e4668c1aff | ||
|
|
2519c49c02 | ||
|
|
9e512ff58a | ||
|
|
ed50595aca | ||
|
|
9d62d087e0 | ||
|
|
6b933e33f5 | ||
|
|
a86931f401 | ||
|
|
fc4e1595e8 | ||
|
|
b36c3f3425 | ||
|
|
e427fd1233 | ||
|
|
dd3af45043 | ||
|
|
304d42d4c2 | ||
|
|
abca9280e7 | ||
|
|
9a103f3979 | ||
|
|
270dde7020 | ||
|
|
11de18e737 | ||
|
|
62db44c71e | ||
|
|
3d5c65eaea | ||
|
|
ea298f95f2 | ||
|
|
2f21e0e341 | ||
|
|
ae62d36a4a | ||
|
|
76e3101d9b | ||
|
|
c3442545a8 | ||
|
|
3a9b0faa06 | ||
|
|
4989b1f8bb | ||
|
|
348fe4ada4 | ||
|
|
bbc6b2d86a | ||
|
|
b4d4ed718f | ||
|
|
836724709e | ||
|
|
96d2567815 | ||
|
|
419f43ceed | ||
|
|
47851ebb1b | ||
|
|
24a4fbd987 | ||
|
|
8047743882 | ||
|
|
e9b5963610 | ||
|
|
a1d4630a25 | ||
|
|
fafd710ce3 | ||
|
|
a79bb3add2 | ||
|
|
b3a718b1e4 | ||
|
|
952aa7ad4a | ||
|
|
149d606154 | ||
|
|
ad21c7ed0f | ||
|
|
38d2839206 | ||
|
|
24d0bb8bd5 | ||
|
|
08f336769f | ||
|
|
5525d4db49 | ||
|
|
448ba26257 | ||
|
|
1d1138ca31 | ||
|
|
357a415386 | ||
|
|
a65856ec98 | ||
|
|
83855e16ce | ||
|
|
ccf66fc621 | ||
|
|
c27b597951 | ||
|
|
85dbae1de4 | ||
|
|
cd1ba334c1 | ||
|
|
1cff4f9d29 | ||
|
|
d180601711 | ||
|
|
01d0e7474f | ||
|
|
bd75fa6ed3 | ||
|
|
14a66762a6 | ||
|
|
507edc95a8 | ||
|
|
9fdadfabcc | ||
|
|
b58ecde740 | ||
|
|
3b71a016a6 | ||
|
|
f0c546208d | ||
|
|
e5799321f8 | ||
|
|
6133fd581a | ||
|
|
0c8231d356 | ||
|
|
788f09e855 | ||
|
|
ba7a6e8106 | ||
|
|
59ac12570c | ||
|
|
69b187a743 | ||
|
|
800291c147 | ||
|
|
1b336bf864 | ||
|
|
2630668fa5 | ||
|
|
339dd9b412 | ||
|
|
3f272d102b | ||
|
|
29988c94fa | ||
|
|
35f2b3254a | ||
|
|
898a8075be | ||
|
|
f5d957e8ff | ||
|
|
2cc3cabd3f | ||
|
|
be5185ab1a | ||
|
|
01c289e440 | ||
|
|
db2d14d9ed | ||
|
|
c504b96b60 | ||
|
|
15c4b1961e | ||
|
|
2cc20e4b27 | ||
|
|
0923d6ecee | ||
|
|
defda41861 | ||
|
|
66c51798b8 | ||
|
|
ff966f667c | ||
|
|
96be8dcb2c | ||
|
|
892d3df6eb | ||
|
|
54e79a6901 | ||
|
|
7d0c05e20d | ||
|
|
421ae6d289 | ||
|
|
3af501b715 | ||
|
|
c73d6913da | ||
|
|
f7cb3e3f9e | ||
|
|
d96c7d71a7 | ||
|
|
7d8e172ce2 | ||
|
|
a671c7a184 | ||
|
|
72125701f5 | ||
|
|
a66d2b0890 | ||
|
|
211b771316 | ||
|
|
41ffad8e5c | ||
|
|
91144a8b63 | ||
|
|
0c408fa4af | ||
|
|
8b8186660b | ||
|
|
7d2332da21 | ||
|
|
2b4d0a4ac9 | ||
|
|
edab1aebaa | ||
|
|
924172f1ac | ||
|
|
d9563e52c8 | ||
|
|
ded9cf227a | ||
|
|
063d6652e0 | ||
|
|
0f8b83755a | ||
|
|
45334d6b35 | ||
|
|
96921e3a39 | ||
|
|
7080168bdd | ||
|
|
48a2a395c4 | ||
|
|
b6c0eb0909 | ||
|
|
b9a2524992 | ||
|
|
28f0dc1e51 | ||
|
|
cbe599735c | ||
|
|
e012e9459d | ||
|
|
eb71799b42 | ||
|
|
fe3f0932ea | ||
|
|
783717b8a4 | ||
|
|
bb37516d6f | ||
|
|
6c6f000229 | ||
|
|
a4715de5dc | ||
|
|
cbf0959fbf | ||
|
|
49c81da763 | ||
|
|
7e34807859 | ||
|
|
e2ac9ec118 | ||
|
|
c58be6ddf5 | ||
|
|
b9b15d41f4 | ||
|
|
dea60cdaf0 | ||
|
|
02ef3c6dc8 | ||
|
|
882a4f2ab3 | ||
|
|
85e05a447d | ||
|
|
1ff4e9703a | ||
|
|
0b2af7c33a | ||
|
|
68115b50a5 | ||
|
|
9aa1d74f83 | ||
|
|
4532194b01 | ||
|
|
ebacb16f67 | ||
|
|
585a01fff6 | ||
|
|
bb7edb5f8f | ||
|
|
142c883b0c | ||
|
|
bf3f6f6014 | ||
|
|
688d0f74c4 | ||
|
|
71b4ea5a60 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ build
|
||||
README.html
|
||||
linux-package
|
||||
logo/*.iconset
|
||||
test-launcher
|
||||
|
||||
52
.travis.yml
52
.travis.yml
@@ -6,13 +6,13 @@ matrix:
|
||||
group: beta
|
||||
sudo: false
|
||||
env:
|
||||
- CC=gcc ASANLIB=libasan.so.0 ASAN_ARG=--asan
|
||||
- CC=gcc SANITIZE_ARG=--sanitize
|
||||
language: python
|
||||
python: "3.5"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libfreetype6-dev
|
||||
- libfontconfig1-dev
|
||||
- libglew-dev
|
||||
- libxi-dev
|
||||
- libxrandr-dev
|
||||
@@ -24,13 +24,31 @@ matrix:
|
||||
group: beta
|
||||
sudo: false
|
||||
env:
|
||||
- CC=clang RUN_FLAKE=1 BUILD_PKG=1
|
||||
- CC=clang SANITIZE_ARG=--sanitize
|
||||
language: python
|
||||
python: "3.5"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libfreetype6-dev
|
||||
- libfontconfig1-dev
|
||||
- libglew-dev
|
||||
- libxi-dev
|
||||
- libxrandr-dev
|
||||
- libxinerama-dev
|
||||
- libxcursor-dev
|
||||
|
||||
- os: linux
|
||||
dist: trusty
|
||||
group: beta
|
||||
sudo: false
|
||||
env:
|
||||
- RUN_FLAKE=1 BUILD_PKG=1
|
||||
language: python
|
||||
python: "3.5"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libfontconfig1-dev
|
||||
- libglew-dev
|
||||
- libxi-dev
|
||||
- libxrandr-dev
|
||||
@@ -45,6 +63,13 @@ matrix:
|
||||
language: generic
|
||||
env: USE_BREW=1 BUILD_PKG=1
|
||||
|
||||
env:
|
||||
global:
|
||||
- PYTHON=python3
|
||||
- PKG_CONFIG_PATH=$HOME/glfw/lib/pkgconfig:$PKG_CONFIG_PATH
|
||||
- LD_LIBRARY_PATH=$HOME/glfw/lib:$LD_LIBRARY_PATH
|
||||
- ASAN_OPTIONS=leak_check_at_exit=0
|
||||
|
||||
install: |
|
||||
set -e
|
||||
if [[ "$RUN_FLAKE" == "1" ]]; then pip install flake8; fi
|
||||
@@ -59,7 +84,7 @@ install: |
|
||||
fi
|
||||
else
|
||||
wget -O glfw-3.2.1.zip https://github.com/glfw/glfw/archive/3.2.1.zip
|
||||
unzip glfw-3.2.1.zip
|
||||
unzip -q glfw-3.2.1.zip
|
||||
cd glfw-3.2.1
|
||||
cmake -DBUILD_SHARED_LIBS=ON -DGLFW_BUILD_EXAMPLES=OFF -DGLFW_BUILD_TESTS=OFF -DGLFW_BUILD_DOCS=OFF -DCMAKE_INSTALL_PREFIX=$HOME/glfw
|
||||
make
|
||||
@@ -67,17 +92,18 @@ install: |
|
||||
cd ..
|
||||
fi
|
||||
pkg-config --cflags glfw3
|
||||
if [[ "$TRAVIS_OS_NAME" != 'osx' ]]; then
|
||||
PLIB=$(ldd `which python` | grep libpython | cut -d ' ' -f 3)
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:`dirname $PLIB`
|
||||
fi
|
||||
set +e
|
||||
|
||||
env:
|
||||
global:
|
||||
- PKG_CONFIG_PATH=$HOME/glfw/lib/pkgconfig
|
||||
- LD_LIBRARY_PATH=$HOME/glfw/lib
|
||||
- ASAN_OPTIONS=leak_check_at_exit=0
|
||||
- PYTHON=python3
|
||||
before_script:
|
||||
- $PYTHON setup.py build --debug $ASAN_ARG;
|
||||
- echo $LD_LIBRARY_PATH
|
||||
- $PYTHON setup.py build --debug $SANITIZE_ARG;
|
||||
- if [[ "$TRAVIS_OS_NAME" != 'osx' ]]; then ldd ./test-launcher `which $PYTHON`; fi
|
||||
|
||||
script:
|
||||
- LD_PRELOAD=$ASANLIB $PYTHON setup.py test
|
||||
- ./test-launcher
|
||||
- if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi
|
||||
- if [[ "$BUILD_PKG" == "1" ]]; then $PYTHON setup.py linux-package; fi
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
:sc_next_window: pass:quotes[`ctrl+shift+]`]
|
||||
:sc_ninth_window: pass:quotes[`ctrl+shift+9`]
|
||||
:sc_paste_from_clipboard: pass:quotes[`ctrl+shift+v`]
|
||||
:sc_paste_from_selection: pass:quotes[`ctrl+shift+s`]
|
||||
:sc_paste_from_selection: pass:quotes[`ctrl+shift+s` or `shift+insert`]
|
||||
:sc_previous_tab: pass:quotes[`ctrl+shift+left`]
|
||||
:sc_previous_window: pass:quotes[`ctrl+shift+[`]
|
||||
:sc_scroll_end: pass:quotes[`ctrl+shift+end`]
|
||||
@@ -64,7 +64,7 @@ speed). Less than ten thousand lines of code.
|
||||
|
||||
* Cross-platform support: kitty currently works on Linux and macOS, but because it
|
||||
uses only OpenGL for rendering, it should be trivial to port to
|
||||
other platforms. See link:../../issues/5[5] for the status of the macOS port.
|
||||
other platforms.
|
||||
|
||||
image::screenshot.png?raw=true[Screenshot, showing three programs in the "Tall" layout]
|
||||
|
||||
@@ -81,8 +81,8 @@ the following dependencies are installed first.
|
||||
* python >= 3.5
|
||||
* glfw >= 3.2
|
||||
* glew >= 2.0 (not needed on macOS)
|
||||
* freetype, fontconfig (not needed on macOS)
|
||||
* xdpyinfo and xsel (only on X11 based systems)
|
||||
* fontconfig (not needed on macOS)
|
||||
* xrdb and xsel (only on X11 based systems)
|
||||
* gcc or clang (required only for building)
|
||||
* pkg-config (required only for building)
|
||||
|
||||
@@ -111,23 +111,24 @@ python3 /path/to/kitty/folder
|
||||
|
||||
=== macOS packages
|
||||
|
||||
Currently, no one has made macOS packages for kitty, but you can use the above
|
||||
install from source instructions to run kitty on macOS, after installing its
|
||||
two dependencies (python >= 3.5 and glfw >= 3.2 using http://brew.sh/[brew] or
|
||||
a similar package manager)
|
||||
kitty is available as a macOS `dmg` file for easy installation from the
|
||||
link:../../releases[releases page]. You can also run kitty directly from
|
||||
source using the above install from source instructions, after installing its
|
||||
two dependencies (`python >= 3.5` and `glfw >= 3.2` using http://brew.sh/[brew]
|
||||
or a similar package manager)
|
||||
|
||||
|
||||
== Design philosophy
|
||||
|
||||
kitty is designed for power keyboard users. To that end all its controls
|
||||
work with the keyboard (although it fully supports mouse interactions as
|
||||
well). It's configuration is a simple, human editable, single file for
|
||||
well). Its configuration is a simple, human editable, single file for
|
||||
easy reproducability (I like to store config files in source control).
|
||||
|
||||
The code in kitty is designed to be simple, modular and hackable. It is
|
||||
written in a mix of C (for performance sensitive parts) and Python (for
|
||||
easy hackability of the UI). It does not depend on any large and complex
|
||||
UI toolkit, using only OpenGL+FreeType for rendering everything.
|
||||
UI toolkit, using only OpenGL for rendering everything.
|
||||
|
||||
Finally, kitty is designed from the ground up to support all modern
|
||||
terminal features, such as unicode, true color, bold/italic fonts, text
|
||||
@@ -280,6 +281,17 @@ launch emacs
|
||||
kitty has a few extensions to the xterm protocol, to enable advanced features,
|
||||
see link:protocol-extensions.asciidoc[Protocol Extensions].
|
||||
|
||||
|
||||
== Font control
|
||||
|
||||
kitty has extremely flexible and powerful font selection features. You can
|
||||
specify individual families for the regular, bold, italic and bold+italic
|
||||
fonts. You can even specify specific font families for specific ranges of
|
||||
unicode characters. This allows precise control over text rendering. It can
|
||||
come in handy for applications like powerline, without the need to use patched
|
||||
fonts. See the various font related configuration directives in the
|
||||
link:kitty/kitty.conf[config file].
|
||||
|
||||
== Note for Linux/macOS packagers
|
||||
|
||||
While kitty does use python, it is not a traditional python package, so please do not install it in site-packages.
|
||||
@@ -305,6 +317,25 @@ without needing to install all of kitty.
|
||||
This applies to creating packages for kitty for macOS package managers such as
|
||||
brew or MacPorts as well.
|
||||
|
||||
== A tribute
|
||||
|
||||
While over the decades I am sure many people have contributed to the
|
||||
development of the terminal emulator space, there is one individual in
|
||||
particular I would like to thank. link:http://invisible-island.net[Thomas E.
|
||||
Dickey], the creator of xterm. xterm is the most comprehensive and
|
||||
feature-rich terminal emulator I have had the pleasure of using. As I worked on
|
||||
kitty, I ran headlong into more and more gnarly corners of terminal behavior.
|
||||
On all those occasions, either the excellent documentation at
|
||||
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html[xterm-ctlseqs] or
|
||||
investigating the behavior and code of xterm or vttest were invaluable tools to
|
||||
aid my understanding.
|
||||
|
||||
My achievements, if any, in developing kitty would not have been possible without
|
||||
his prior work and the generous sharing of knowledge accumulated over decades.
|
||||
|
||||
Thank you.
|
||||
|
||||
|
||||
== Resources on terminal behavior
|
||||
|
||||
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
cloc --exclude-list-file <(echo -e 'kitty/wcwidth9.h\nkitty/unicode-data.h\nkitty/gl.h\nkitty/glfw.c\nkitty/glfw.h\nkitty/charsets.c') kitty
|
||||
cloc --exclude-list-file <(echo -e 'kitty/wcwidth9.h\nkitty/unicode-data.h\nkitty/gl.h\nkitty/glfw.c\nkitty/glfw.h\nkitty/charsets.c\nkitty/key_encoding.py\nkitty/rgb.py') kitty
|
||||
|
||||
128
key_encoding.asciidoc
Normal file
128
key_encoding.asciidoc
Normal file
@@ -0,0 +1,128 @@
|
||||
= Key encoding for extended keyboard protocol
|
||||
|
||||
See link:protocol-extensions.asciidoc#keyboard-handling[Keyboard Handling protocol extension]
|
||||
|
||||
|===
|
||||
| Name | Encoded representation (base64)
|
||||
|
||||
| 0 | `G`
|
||||
| 1 | `H`
|
||||
| 2 | `I`
|
||||
| 3 | `J`
|
||||
| 4 | `K`
|
||||
| 5 | `L`
|
||||
| 6 | `M`
|
||||
| 7 | `N`
|
||||
| 8 | `O`
|
||||
| 9 | `P`
|
||||
| A | `S`
|
||||
| APOSTROPHE | `B`
|
||||
| B | `T`
|
||||
| BACKSLASH | `t`
|
||||
| BACKSPACE | `1`
|
||||
| C | `U`
|
||||
| CAPS LOCK | `:`
|
||||
| COMMA | `C`
|
||||
| D | `V`
|
||||
| DELETE | `3`
|
||||
| DOWN | `6`
|
||||
| E | `W`
|
||||
| END | `-`
|
||||
| ENTER | `z`
|
||||
| EQUAL | `R`
|
||||
| ESCAPE | `y`
|
||||
| F | `X`
|
||||
| F1 | `/`
|
||||
| F10 | `]`
|
||||
| F11 | `{`
|
||||
| F12 | `}`
|
||||
| F13 | `@`
|
||||
| F14 | `%`
|
||||
| F15 | `$`
|
||||
| F16 | `#`
|
||||
| F17 | `BA`
|
||||
| F18 | `BB`
|
||||
| F19 | `BC`
|
||||
| F2 | `*`
|
||||
| F20 | `BD`
|
||||
| F21 | `BE`
|
||||
| F22 | `BF`
|
||||
| F23 | `BG`
|
||||
| F24 | `BH`
|
||||
| F25 | `BI`
|
||||
| F3 | `?`
|
||||
| F4 | `&`
|
||||
| F5 | `<`
|
||||
| F6 | `>`
|
||||
| F7 | `(`
|
||||
| F8 | `)`
|
||||
| F9 | `[`
|
||||
| G | `Y`
|
||||
| GRAVE ACCENT | `v`
|
||||
| H | `Z`
|
||||
| HOME | `.`
|
||||
| I | `a`
|
||||
| INSERT | `2`
|
||||
| J | `b`
|
||||
| K | `c`
|
||||
| KP 0 | `BJ`
|
||||
| KP 1 | `BK`
|
||||
| KP 2 | `BL`
|
||||
| KP 3 | `BM`
|
||||
| KP 4 | `BN`
|
||||
| KP 5 | `BO`
|
||||
| KP 6 | `BP`
|
||||
| KP 7 | `BQ`
|
||||
| KP 8 | `BR`
|
||||
| KP 9 | `BS`
|
||||
| KP ADD | `BX`
|
||||
| KP DECIMAL | `BT`
|
||||
| KP DIVIDE | `BU`
|
||||
| KP ENTER | `BY`
|
||||
| KP EQUAL | `BZ`
|
||||
| KP MULTIPLY | `BV`
|
||||
| KP SUBTRACT | `BW`
|
||||
| L | `d`
|
||||
| LEFT | `5`
|
||||
| LEFT ALT | `Bc`
|
||||
| LEFT BRACKET | `s`
|
||||
| LEFT CONTROL | `Bb`
|
||||
| LEFT SHIFT | `Ba`
|
||||
| LEFT SUPER | `Bd`
|
||||
| M | `e`
|
||||
| MINUS | `D`
|
||||
| N | `f`
|
||||
| NUM LOCK | `=`
|
||||
| O | `g`
|
||||
| P | `h`
|
||||
| PAGE DOWN | `9`
|
||||
| PAGE UP | `8`
|
||||
| PAUSE | `!`
|
||||
| PERIOD | `E`
|
||||
| PRINT SCREEN | `^`
|
||||
| Q | `i`
|
||||
| R | `j`
|
||||
| RIGHT | `4`
|
||||
| RIGHT ALT | `Bg`
|
||||
| RIGHT BRACKET | `u`
|
||||
| RIGHT CONTROL | `Bf`
|
||||
| RIGHT SHIFT | `Be`
|
||||
| RIGHT SUPER | `Bh`
|
||||
| S | `k`
|
||||
| SCROLL LOCK | `+`
|
||||
| SEMICOLON | `Q`
|
||||
| SLASH | `F`
|
||||
| SPACE | `A`
|
||||
| T | `l`
|
||||
| TAB | `0`
|
||||
| U | `m`
|
||||
| UP | `7`
|
||||
| V | `n`
|
||||
| W | `o`
|
||||
| WORLD 1 | `w`
|
||||
| WORLD 2 | `x`
|
||||
| X | `p`
|
||||
| Y | `q`
|
||||
| Z | `r`
|
||||
|
||||
|===
|
||||
@@ -3,12 +3,16 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from ctypes import addressof
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
from threading import Lock
|
||||
|
||||
from .constants import viewport_size, GLfloat, GLint, GLuint
|
||||
from .fast_data_types import glUniform3fv, GL_TRIANGLE_FAN, glMultiDrawArrays
|
||||
from .utils import get_dpi
|
||||
from .constants import GLfloat, GLint, GLuint, viewport_size
|
||||
from .fast_data_types import (
|
||||
GL_STATIC_DRAW, GL_TRIANGLE_FAN, glMultiDrawArrays, glUniform3fv
|
||||
)
|
||||
from .shaders import ShaderProgram
|
||||
from .utils import pt_to_px
|
||||
|
||||
|
||||
def as_color(c):
|
||||
@@ -30,7 +34,8 @@ def as_rect(left, top, right, bottom, color=0):
|
||||
class BordersProgram(ShaderProgram):
|
||||
|
||||
def __init__(self):
|
||||
ShaderProgram.__init__(self, '''\
|
||||
ShaderProgram.__init__(
|
||||
self, '''\
|
||||
uniform vec3 colors[3];
|
||||
in vec3 rect;
|
||||
out vec3 color;
|
||||
@@ -47,43 +52,82 @@ void main() {
|
||||
final_color = vec4(color, 1);
|
||||
}
|
||||
''')
|
||||
self.add_vertex_array('rect')
|
||||
self.vao_id = self.add_vertex_arrays(self.vertex_array('rect'))
|
||||
|
||||
def send_data(self, data):
|
||||
self.send_vertex_data('rect', data)
|
||||
self.send_vertex_data(self.vao_id, data, usage=GL_STATIC_DRAW)
|
||||
|
||||
def set_colors(self, color_buf):
|
||||
glUniform3fv(self.uniform_location('colors'), 3, addressof(color_buf))
|
||||
|
||||
|
||||
def border_maker(rects):
|
||||
' Create a function that will add all the rectangles for drawing a border to rects '
|
||||
|
||||
def r(l, t, b, r, color):
|
||||
rects.extend(as_rect(l, t, b, r, color))
|
||||
|
||||
def vertical_edge(color, width, top, bottom, left):
|
||||
r(left, top, left + width, bottom, color)
|
||||
|
||||
def horizontal_edge(color, height, left, right, top):
|
||||
r(left, top, right, top + height, color)
|
||||
|
||||
def edge(func, color, sz, a, b):
|
||||
return partial(func, color, sz, a, b)
|
||||
|
||||
def border(color, sz, left, top, right, bottom):
|
||||
horz = edge(horizontal_edge, color, sz, left, right)
|
||||
horz(top), horz(bottom - sz) # top, bottom edges
|
||||
vert = edge(vertical_edge, color, sz, top, bottom)
|
||||
vert(left), vert(right - sz) # left, right edges
|
||||
|
||||
return border
|
||||
|
||||
|
||||
class Borders:
|
||||
|
||||
def __init__(self, opts):
|
||||
self.is_dirty = False
|
||||
self.lock = Lock()
|
||||
self.can_render = False
|
||||
dpix, dpiy = get_dpi()['logical']
|
||||
dpi = (dpix + dpiy) / 2
|
||||
self.border_width = round(opts.window_border_width * dpi / 72)
|
||||
self.border_width = pt_to_px(opts.window_border_width)
|
||||
self.padding_width = pt_to_px(opts.window_padding_width)
|
||||
self.color_buf = (GLfloat * 9)(
|
||||
*as_color(opts.background),
|
||||
*as_color(opts.active_border_color),
|
||||
*as_color(opts.inactive_border_color)
|
||||
)
|
||||
*as_color(opts.background), *as_color(opts.active_border_color),
|
||||
*as_color(opts.inactive_border_color))
|
||||
|
||||
def __call__(self, windows, active_window, current_layout, draw_window_borders=True):
|
||||
def __call__(
|
||||
self,
|
||||
windows,
|
||||
active_window,
|
||||
current_layout,
|
||||
extra_blank_rects,
|
||||
draw_window_borders=True
|
||||
):
|
||||
rects = []
|
||||
for br in current_layout.blank_rects:
|
||||
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
||||
rects.extend(as_rect(*br))
|
||||
if draw_window_borders and self.border_width > 0:
|
||||
bw = self.border_width
|
||||
bw, pw = self.border_width, self.padding_width
|
||||
fw = bw + pw
|
||||
border = border_maker(rects)
|
||||
|
||||
if fw > 0:
|
||||
for w in windows:
|
||||
g = w.geometry
|
||||
color = 1 if w is active_window else 2
|
||||
rects.extend(as_rect(g.left - bw, g.top - bw, g.left, g.bottom + bw, color))
|
||||
rects.extend(as_rect(g.left - bw, g.top - bw, g.right + bw, g.top, color))
|
||||
rects.extend(as_rect(g.right, g.top - bw, g.right + bw, g.bottom + bw, color))
|
||||
rects.extend(as_rect(g.left - bw, g.bottom, g.right + bw, g.bottom + bw, color))
|
||||
if bw > 0 and draw_window_borders:
|
||||
# Draw the border rectangles
|
||||
color = 1 if w is active_window else 2
|
||||
border(
|
||||
color, bw, g.left - fw, g.top - fw, g.right + fw,
|
||||
g.bottom + fw)
|
||||
if pw > 0:
|
||||
# Draw the background rectangles over the padding region
|
||||
color = 0
|
||||
border(
|
||||
color, pw, g.left - pw, g.top - pw, g.right + pw,
|
||||
g.bottom + pw)
|
||||
|
||||
with self.lock:
|
||||
self.num_of_rects = len(rects) // 12
|
||||
self.rects = (GLfloat * len(rects))()
|
||||
@@ -107,4 +151,8 @@ class Borders:
|
||||
program.send_data(self.rects)
|
||||
program.set_colors(self.color_buf)
|
||||
self.is_dirty = False
|
||||
glMultiDrawArrays(GL_TRIANGLE_FAN, addressof(self.starts), addressof(self.counts), self.num_of_rects)
|
||||
with program.bound_vertex_array(program.vao_id):
|
||||
glMultiDrawArrays(
|
||||
GL_TRIANGLE_FAN,
|
||||
addressof(self.starts),
|
||||
addressof(self.counts), self.num_of_rects)
|
||||
|
||||
180
kitty/boss.py
180
kitty/boss.py
@@ -2,37 +2,42 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import inspect
|
||||
import io
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import struct
|
||||
import inspect
|
||||
from functools import wraps
|
||||
from gettext import gettext as _
|
||||
from queue import Empty, Queue
|
||||
from threading import Thread, current_thread
|
||||
from time import monotonic
|
||||
from queue import Queue, Empty
|
||||
from gettext import gettext as _
|
||||
|
||||
from .borders import BordersProgram
|
||||
from .char_grid import load_shader_programs
|
||||
from .config import MINIMUM_FONT_SIZE
|
||||
from .constants import (
|
||||
viewport_size, set_boss, wakeup, cell_size, MODIFIER_KEYS,
|
||||
main_thread, mouse_button_pressed, mouse_cursor_pos
|
||||
MODIFIER_KEYS, cell_size, is_key_pressed, isosx, main_thread,
|
||||
mouse_button_pressed, mouse_cursor_pos, set_boss, viewport_size, wakeup
|
||||
)
|
||||
from .fast_data_types import (
|
||||
glViewport, glBlendFunc, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GLFW_PRESS,
|
||||
GLFW_REPEAT, GLFW_MOUSE_BUTTON_1, glfw_post_empty_event,
|
||||
GLFW_CURSOR_NORMAL, GLFW_CURSOR, GLFW_CURSOR_HIDDEN, drain_read
|
||||
GL_ONE_MINUS_SRC_ALPHA, GL_SRC_ALPHA, GLFW_CURSOR, GLFW_CURSOR_HIDDEN,
|
||||
GLFW_CURSOR_NORMAL, GLFW_MOUSE_BUTTON_1, GLFW_PRESS, GLFW_REPEAT,
|
||||
drain_read, glBlendFunc, glfw_post_empty_event, glViewport
|
||||
)
|
||||
from .fonts.render import set_font_family
|
||||
from .borders import BordersProgram
|
||||
from .char_grid import cursor_shader, cell_shader
|
||||
from .constants import is_key_pressed
|
||||
from .keys import interpret_text_event, interpret_key_event, get_shortcut
|
||||
from .keys import (
|
||||
get_sent_data, get_shortcut, interpret_key_event, interpret_text_event
|
||||
)
|
||||
from .session import create_session
|
||||
from .shaders import Sprites, ShaderProgram
|
||||
from .tabs import TabManager, SpecialWindow
|
||||
from .shaders import Sprites
|
||||
from .tabs import SpecialWindow, TabManager
|
||||
from .timers import Timers
|
||||
from .utils import handle_unix_signals, safe_print, pipe2
|
||||
from .utils import handle_unix_signals, pipe2, safe_print
|
||||
|
||||
if isosx:
|
||||
from .fast_data_types import cocoa_update_title
|
||||
|
||||
|
||||
def conditional_run(w, i):
|
||||
@@ -58,6 +63,7 @@ def callback(func):
|
||||
pass
|
||||
else:
|
||||
self.queue_action(conditional_run, w, i)
|
||||
|
||||
return f
|
||||
|
||||
|
||||
@@ -79,13 +85,16 @@ class Boss(Thread):
|
||||
self.screen_update_delay = opts.repaint_delay / 1000.0
|
||||
self.signal_fd = handle_unix_signals()
|
||||
self.read_wakeup_fd, self.write_wakeup_fd = pipe2()
|
||||
self.read_dispatch_map = {self.signal_fd: self.signal_received, self.read_wakeup_fd: self.on_wakeup}
|
||||
self.read_dispatch_map = {
|
||||
self.signal_fd: self.signal_received,
|
||||
self.read_wakeup_fd: self.on_wakeup}
|
||||
self.all_writers = []
|
||||
self.timers = Timers()
|
||||
self.ui_timers = Timers()
|
||||
self.pending_ui_thread_calls = Queue()
|
||||
self.write_dispatch_map = {}
|
||||
set_boss(self)
|
||||
self.current_font_size = opts.font_size
|
||||
cell_size.width, cell_size.height = set_font_family(opts)
|
||||
self.opts, self.args = opts, args
|
||||
self.glfw_window = glfw_window
|
||||
@@ -98,8 +107,7 @@ class Boss(Thread):
|
||||
glfw_window.window_focus_callback = self.on_focus
|
||||
self.tab_manager = TabManager(opts, args, startup_session)
|
||||
self.sprites = Sprites()
|
||||
self.cell_program = ShaderProgram(*cell_shader)
|
||||
self.cursor_program = ShaderProgram(*cursor_shader)
|
||||
self.cell_program, self.cursor_program = load_shader_programs()
|
||||
self.borders_program = BordersProgram()
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||
@@ -203,8 +211,10 @@ class Boss(Thread):
|
||||
def loop(self):
|
||||
while not self.shutting_down:
|
||||
all_readers = list(self.read_dispatch_map)
|
||||
all_writers = [w.child_fd for w in self.iterwindows() if w.write_buf]
|
||||
readers, writers, _ = select.select(all_readers, all_writers, [], self.timers.timeout())
|
||||
all_writers = [
|
||||
w.child_fd for w in self.iterwindows() if w.write_buf]
|
||||
readers, writers, _ = select.select(
|
||||
all_readers, all_writers, [], self.timers.timeout())
|
||||
for r in readers:
|
||||
self.read_dispatch_map[r]()
|
||||
for w in writers:
|
||||
@@ -212,7 +222,8 @@ class Boss(Thread):
|
||||
self.timers()
|
||||
for w in self.iterwindows():
|
||||
if w.screen.is_dirty():
|
||||
self.timers.add_if_missing(self.screen_update_delay, w.update_screen)
|
||||
self.timers.add_if_missing(
|
||||
self.screen_update_delay, w.update_screen)
|
||||
|
||||
@callback
|
||||
def on_window_resize(self, window, w, h):
|
||||
@@ -228,6 +239,34 @@ class Boss(Thread):
|
||||
self.pending_resize = False
|
||||
glfw_post_empty_event()
|
||||
|
||||
def increase_font_size(self):
|
||||
self.change_font_size(
|
||||
min(
|
||||
self.opts.font_size * 5, self.current_font_size +
|
||||
self.opts.font_size_delta))
|
||||
|
||||
def decrease_font_size(self):
|
||||
self.change_font_size(
|
||||
max(
|
||||
MINIMUM_FONT_SIZE, self.current_font_size -
|
||||
self.opts.font_size_delta))
|
||||
|
||||
def restore_font_size(self):
|
||||
self.change_font_size(self.opts.font_size)
|
||||
|
||||
def change_font_size(self, new_size):
|
||||
if new_size == self.current_font_size:
|
||||
return
|
||||
self.current_font_size = new_size
|
||||
cell_size.width, cell_size.height = set_font_family(
|
||||
self.opts, override_font_size=self.current_font_size)
|
||||
self.sprites.do_layout(cell_size.width, cell_size.height)
|
||||
self.queue_action(self.resize_windows_after_font_size_change)
|
||||
|
||||
def resize_windows_after_font_size_change(self):
|
||||
self.tab_manager.resize()
|
||||
glfw_post_empty_event()
|
||||
|
||||
def tabbar_visibility_changed(self):
|
||||
self.tab_manager.resize(only_tabs=True)
|
||||
glfw_post_empty_event()
|
||||
@@ -247,18 +286,20 @@ class Boss(Thread):
|
||||
|
||||
@callback
|
||||
def on_text_input(self, window, codepoint, mods):
|
||||
data = interpret_text_event(codepoint, mods)
|
||||
if data:
|
||||
w = self.active_window
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
yield w
|
||||
if w is not None:
|
||||
yield w
|
||||
w.write_to_child(data)
|
||||
data = interpret_text_event(codepoint, mods, w)
|
||||
if data:
|
||||
w.write_to_child(data)
|
||||
|
||||
@callback
|
||||
def on_key(self, window, key, scancode, action, mods):
|
||||
is_key_pressed[key] = action == GLFW_PRESS
|
||||
self.start_cursor_blink()
|
||||
self.cursor_blink_zero_time = monotonic()
|
||||
func = None
|
||||
if action == GLFW_PRESS or action == GLFW_REPEAT:
|
||||
func = get_shortcut(self.opts.keymap, mods, key, scancode)
|
||||
if func is not None:
|
||||
@@ -267,24 +308,26 @@ class Boss(Thread):
|
||||
passthrough = f()
|
||||
if not passthrough:
|
||||
return
|
||||
tab = self.active_tab
|
||||
if tab is None:
|
||||
return
|
||||
window = self.active_window
|
||||
if window is not None:
|
||||
yield window
|
||||
if func is not None:
|
||||
f = getattr(tab, func, getattr(window, func, None))
|
||||
if f is not None:
|
||||
passthrough = f()
|
||||
if not passthrough:
|
||||
return
|
||||
if window.screen.auto_repeat_enabled or action == GLFW_PRESS:
|
||||
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS:
|
||||
window.scroll_end()
|
||||
data = interpret_key_event(key, scancode, mods)
|
||||
if data:
|
||||
window.write_to_child(data)
|
||||
tab = self.active_tab
|
||||
if tab is None:
|
||||
return
|
||||
window = self.active_window
|
||||
if window is None:
|
||||
return
|
||||
yield window
|
||||
if func is not None:
|
||||
f = getattr(tab, func, getattr(window, func, None))
|
||||
if f is not None:
|
||||
passthrough = f()
|
||||
if not passthrough:
|
||||
return
|
||||
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
|
||||
window.scroll_end()
|
||||
data = get_sent_data(
|
||||
self.opts.send_text_map, key, scancode, mods, window, action
|
||||
) or interpret_key_event(key, scancode, mods, window, action)
|
||||
if data:
|
||||
window.write_to_child(data)
|
||||
|
||||
@callback
|
||||
def on_focus(self, window, focused):
|
||||
@@ -300,7 +343,9 @@ class Boss(Thread):
|
||||
else:
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
tab.new_special_window(SpecialWindow(self.opts.scrollback_pager, data, _('History')))
|
||||
tab.new_special_window(
|
||||
SpecialWindow(
|
||||
self.opts.scrollback_pager, data, _('History')))
|
||||
|
||||
def window_for_pos(self, x, y):
|
||||
tab = self.active_tab
|
||||
@@ -339,7 +384,8 @@ class Boss(Thread):
|
||||
|
||||
@callback
|
||||
def on_mouse_move(self, window, xpos, ypos):
|
||||
mouse_cursor_pos[:2] = int(xpos * viewport_size.x_ratio), int(ypos * viewport_size.y_ratio)
|
||||
mouse_cursor_pos[:2] = xpos, ypos = int(
|
||||
xpos * viewport_size.x_ratio), int(ypos * viewport_size.y_ratio)
|
||||
self.show_mouse_cursor()
|
||||
w = self.window_for_pos(xpos, ypos)
|
||||
if w is not None:
|
||||
@@ -361,7 +407,8 @@ class Boss(Thread):
|
||||
def show_mouse_cursor(self):
|
||||
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_NORMAL)
|
||||
if self.opts.mouse_hide_wait > 0:
|
||||
self.ui_timers.add(self.opts.mouse_hide_wait, self.hide_mouse_cursor)
|
||||
self.ui_timers.add(
|
||||
self.opts.mouse_hide_wait, self.hide_mouse_cursor)
|
||||
|
||||
def hide_mouse_cursor(self):
|
||||
self.glfw_window.set_input_mode(GLFW_CURSOR, GLFW_CURSOR_HIDDEN)
|
||||
@@ -369,10 +416,18 @@ class Boss(Thread):
|
||||
def change_mouse_cursor(self, click=False):
|
||||
self.glfw_window.set_click_cursor(click)
|
||||
|
||||
def request_attention(self):
|
||||
try:
|
||||
self.glfw_window.request_window_attention()
|
||||
except AttributeError:
|
||||
pass # needs glfw 3.3
|
||||
|
||||
def start_cursor_blink(self):
|
||||
self.cursor_blinking = True
|
||||
if self.opts.cursor_stop_blinking_after > 0:
|
||||
self.ui_timers.add(self.opts.cursor_stop_blinking_after, self.stop_cursor_blinking)
|
||||
self.ui_timers.add(
|
||||
self.opts.cursor_stop_blinking_after,
|
||||
self.stop_cursor_blinking)
|
||||
|
||||
def stop_cursor_blinking(self):
|
||||
self.cursor_blinking = False
|
||||
@@ -388,15 +443,22 @@ class Boss(Thread):
|
||||
if tab.title != self.glfw_window_title:
|
||||
self.glfw_window_title = tab.title
|
||||
self.glfw_window.set_title(self.glfw_window_title)
|
||||
if isosx:
|
||||
cocoa_update_title(self.glfw_window_title)
|
||||
with self.sprites:
|
||||
self.sprites.render_dirty_cells()
|
||||
tab.render()
|
||||
render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows() if not window.needs_layout}
|
||||
render_data = {
|
||||
window:
|
||||
window.char_grid.prepare_for_render(self.cell_program)
|
||||
for window in tab.visible_windows()
|
||||
if not window.needs_layout}
|
||||
with self.cell_program:
|
||||
self.tab_manager.render(self.cell_program, self.sprites)
|
||||
for window, rd in render_data.items():
|
||||
if rd is not None:
|
||||
window.char_grid.render_cells(rd, self.cell_program, self.sprites)
|
||||
window.render_cells(
|
||||
rd, self.cell_program, self.sprites)
|
||||
active = self.active_window
|
||||
rd = render_data.get(active)
|
||||
if rd is not None:
|
||||
@@ -407,15 +469,16 @@ class Boss(Thread):
|
||||
d = int(self.opts.cursor_blink_interval * 1000)
|
||||
n = t // d
|
||||
draw_cursor = n % 2 == 0
|
||||
self.ui_timers.add_if_missing(((n + 1) * d / 1000) - now, glfw_post_empty_event)
|
||||
self.ui_timers.add_if_missing(
|
||||
((n + 1) * d / 1000) - now, glfw_post_empty_event)
|
||||
if draw_cursor:
|
||||
with self.cursor_program:
|
||||
active.char_grid.render_cursor(rd, self.cursor_program, self.window_is_focused)
|
||||
active.char_grid.render_cursor(
|
||||
rd, self.cursor_program,
|
||||
self.window_is_focused)
|
||||
|
||||
def gui_close_window(self, window):
|
||||
if window.char_grid.buffer_id is not None:
|
||||
self.sprites.destroy_sprite_map(window.char_grid.buffer_id)
|
||||
window.char_grid.buffer_id = None
|
||||
window.char_grid.destroy(self.cell_program)
|
||||
for tab in self.tab_manager:
|
||||
if window in tab:
|
||||
break
|
||||
@@ -467,5 +530,8 @@ class Boss(Thread):
|
||||
self.queue_action(self.tab_manager.move_tab, -1)
|
||||
|
||||
def display_scrollback_in_new_tab(self, data):
|
||||
self.tab_manager.new_tab(special_window=SpecialWindow(self.opts.scrollback_pager, data, _('History')))
|
||||
self.tab_manager.new_tab(
|
||||
special_window=SpecialWindow(
|
||||
self.opts.scrollback_pager, data, _('History')))
|
||||
|
||||
# }}}
|
||||
|
||||
26
kitty/cell_fragment.glsl
Normal file
26
kitty/cell_fragment.glsl
Normal file
@@ -0,0 +1,26 @@
|
||||
uniform sampler2DArray sprites;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
in vec3 strike_pos;
|
||||
in vec3 foreground;
|
||||
in vec3 background;
|
||||
in vec3 decoration_fg;
|
||||
out vec4 final_color;
|
||||
|
||||
vec3 blend(float alpha, vec3 over, vec3 under) {
|
||||
return over + (1 - alpha) * under;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float text_alpha = texture(sprites, sprite_pos).r;
|
||||
float underline_alpha = texture(sprites, underline_pos).r;
|
||||
float strike_alpha = texture(sprites, strike_pos).r;
|
||||
vec3 underline = underline_alpha * decoration_fg;
|
||||
vec3 strike = strike_alpha * foreground;
|
||||
vec3 fg = text_alpha * foreground;
|
||||
vec3 decoration = blend(underline_alpha, underline, strike);
|
||||
vec3 combined_fg = blend(text_alpha, fg, decoration);
|
||||
float combined_alpha = max(max(underline_alpha, strike_alpha), text_alpha);
|
||||
final_color = vec4(blend(combined_alpha, combined_fg, background), 1);
|
||||
}
|
||||
|
||||
59
kitty/cell_vertex.glsl
Normal file
59
kitty/cell_vertex.glsl
Normal file
@@ -0,0 +1,59 @@
|
||||
uniform uvec2 dimensions; // xnum, ynum
|
||||
uniform vec4 steps; // xstart, ystart, dx, dy
|
||||
uniform vec2 sprite_layout; // dx, dy
|
||||
uniform ivec2 color_indices; // which color to use as fg and which as bg
|
||||
in uvec3 sprite_coords;
|
||||
in uvec3 colors;
|
||||
out vec3 sprite_pos;
|
||||
out vec3 underline_pos;
|
||||
out vec3 strike_pos;
|
||||
out vec3 foreground;
|
||||
out vec3 background;
|
||||
out vec3 decoration_fg;
|
||||
|
||||
const uvec2 pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
uvec2(0, 1), // left, bottom
|
||||
uvec2(0, 0) // left, top
|
||||
);
|
||||
|
||||
const uint BYTE_MASK = uint(255);
|
||||
const uint ZERO = uint(0);
|
||||
const uint SMASK = uint(3);
|
||||
|
||||
vec3 to_color(uint c) {
|
||||
uint r, g, b;
|
||||
r = (c >> 16) & BYTE_MASK;
|
||||
g = (c >> 8) & BYTE_MASK;
|
||||
b = c & BYTE_MASK;
|
||||
return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0);
|
||||
}
|
||||
|
||||
vec3 to_sprite_pos(uvec2 pos, uint x, uint y, uint z) {
|
||||
vec2 s_xpos = vec2(x, float(x) + 1.0) * sprite_layout.x;
|
||||
vec2 s_ypos = vec2(y, float(y) + 1.0) * sprite_layout.y;
|
||||
return vec3(s_xpos[pos.x], s_ypos[pos.y], z);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uint instance_id = uint(gl_InstanceID);
|
||||
uint r = instance_id / dimensions[0];
|
||||
uint c = instance_id - r * dimensions[0];
|
||||
float left = steps[0] + c * steps[2];
|
||||
float top = steps[1] - r * steps[3];
|
||||
vec2 xpos = vec2(left, left + steps[2]);
|
||||
vec2 ypos = vec2(top, top - steps[3]);
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1);
|
||||
|
||||
sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z);
|
||||
uint fg = colors[color_indices[0]];
|
||||
uint bg = colors[color_indices[1]];
|
||||
uint decoration = colors[2];
|
||||
foreground = to_color(fg);
|
||||
background = to_color(bg);
|
||||
decoration_fg = to_color(decoration);
|
||||
underline_pos = to_sprite_pos(pos, (decoration >> 24) & SMASK, ZERO, ZERO);
|
||||
strike_pos = to_sprite_pos(pos, (decoration >> 26) & SMASK, ZERO, ZERO);
|
||||
}
|
||||
@@ -6,145 +6,49 @@ import re
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from ctypes import addressof, memmove, sizeof
|
||||
from enum import Enum
|
||||
from threading import Lock
|
||||
|
||||
from .config import build_ansi_color_table, defaults
|
||||
from .constants import get_boss, viewport_size, cell_size, ScreenGeometry, GLuint
|
||||
from .utils import get_logical_dpi, to_color, set_primary_selection, open_url, color_as_int, safe_print
|
||||
from .constants import (
|
||||
GLuint, ScreenGeometry, cell_size, get_boss, viewport_size
|
||||
)
|
||||
from .fast_data_types import (
|
||||
glUniform2ui, glUniform4f, glUniform1i, glUniform2f, glDrawArraysInstanced,
|
||||
GL_TRIANGLE_FAN, glEnable, glDisable, GL_BLEND, glDrawArrays, ColorProfile,
|
||||
CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_LINE_LOOP
|
||||
CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_BLEND,
|
||||
GL_LINE_LOOP, GL_TRIANGLE_FAN, GL_UNSIGNED_INT, ColorProfile, glDisable,
|
||||
glDrawArrays, glDrawArraysInstanced, glEnable, glUniform1i, glUniform2f,
|
||||
glUniform2i, glUniform2ui, glUniform4f
|
||||
)
|
||||
from .rgb import to_color
|
||||
from .shaders import ShaderProgram, load_shaders
|
||||
from .utils import (
|
||||
color_as_int, color_from_int, get_logical_dpi, open_url, safe_print,
|
||||
set_primary_selection
|
||||
)
|
||||
|
||||
Cursor = namedtuple('Cursor', 'x y shape color blink')
|
||||
|
||||
if DATA_CELL_SIZE % 3:
|
||||
raise ValueError('Incorrect data cell size, must be a multiple of 3')
|
||||
Cursor = namedtuple('Cursor', 'x y shape blink')
|
||||
|
||||
|
||||
# cell shader {{{
|
||||
class DynamicColor(Enum):
|
||||
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6)
|
||||
|
||||
cell_shader = (
|
||||
'''\
|
||||
uniform uvec2 dimensions; // xnum, ynum
|
||||
uniform vec4 steps; // xstart, ystart, dx, dy
|
||||
uniform vec2 sprite_layout; // dx, dy
|
||||
uniform usamplerBuffer sprite_map; // gl_InstanceID -> x, y, z
|
||||
out vec3 sprite_pos;
|
||||
out vec3 underline_pos;
|
||||
out vec3 strike_pos;
|
||||
out vec3 foreground;
|
||||
out vec3 background;
|
||||
out vec3 decoration_fg;
|
||||
|
||||
const uvec2 pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
uvec2(0, 1), // left, bottom
|
||||
uvec2(0, 0) // left, top
|
||||
);
|
||||
class CellProgram(ShaderProgram):
|
||||
|
||||
const uint BYTE_MASK = uint(255);
|
||||
const uint ZERO = uint(0);
|
||||
const uint SMASK = uint(3);
|
||||
def create_sprite_map(self):
|
||||
stride = DATA_CELL_SIZE * sizeof(GLuint)
|
||||
size = DATA_CELL_SIZE // 2
|
||||
return self.add_vertex_arrays(
|
||||
self.vertex_array('sprite_coords', size=size, dtype=GL_UNSIGNED_INT, stride=stride, divisor=1),
|
||||
self.vertex_array('colors', size=size, dtype=GL_UNSIGNED_INT, stride=stride, offset=stride // 2, divisor=1),
|
||||
)
|
||||
|
||||
vec3 to_color(uint c) {
|
||||
uint r, g, b;
|
||||
r = (c >> 16) & BYTE_MASK;
|
||||
g = (c >> 8) & BYTE_MASK;
|
||||
b = c & BYTE_MASK;
|
||||
return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0);
|
||||
}
|
||||
|
||||
vec3 to_sprite_pos(uvec2 pos, uint x, uint y, uint z) {
|
||||
vec2 s_xpos = vec2(x, float(x) + 1.0) * sprite_layout[0];
|
||||
vec2 s_ypos = vec2(y, float(y) + 1.0) * sprite_layout[1];
|
||||
return vec3(s_xpos[pos[0]], s_ypos[pos[1]], z);
|
||||
}
|
||||
|
||||
void main() {
|
||||
uint instance_id = uint(gl_InstanceID);
|
||||
uint r = instance_id / dimensions[0];
|
||||
uint c = instance_id - r * dimensions[0];
|
||||
float left = steps[0] + c * steps[2];
|
||||
float top = steps[1] - r * steps[3];
|
||||
vec2 xpos = vec2(left, left + steps[2]);
|
||||
vec2 ypos = vec2(top, top - steps[3]);
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
|
||||
|
||||
int sprite_id = gl_InstanceID * STRIDE;
|
||||
uvec4 spos = texelFetch(sprite_map, sprite_id);
|
||||
uvec4 colors = texelFetch(sprite_map, sprite_id + 1);
|
||||
sprite_pos = to_sprite_pos(pos, spos[0], spos[1], spos[2]);
|
||||
foreground = to_color(colors[0]);
|
||||
background = to_color(colors[1]);
|
||||
uint decoration = colors[2];
|
||||
decoration_fg = to_color(decoration);
|
||||
underline_pos = to_sprite_pos(pos, (decoration >> 24) & SMASK, ZERO, ZERO);
|
||||
strike_pos = to_sprite_pos(pos, (decoration >> 26) & SMASK, ZERO, ZERO);
|
||||
}
|
||||
'''.replace('STRIDE', str(DATA_CELL_SIZE // 3)),
|
||||
|
||||
'''\
|
||||
uniform sampler2DArray sprites;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
in vec3 strike_pos;
|
||||
in vec3 foreground;
|
||||
in vec3 background;
|
||||
in vec3 decoration_fg;
|
||||
out vec4 final_color;
|
||||
|
||||
vec3 blend(float alpha, vec3 over, vec3 under) {
|
||||
return over + (1 - alpha) * under;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float text_alpha = texture(sprites, sprite_pos).r;
|
||||
float underline_alpha = texture(sprites, underline_pos).r;
|
||||
float strike_alpha = texture(sprites, strike_pos).r;
|
||||
vec3 underline = underline_alpha * decoration_fg;
|
||||
vec3 strike = strike_alpha * foreground;
|
||||
vec3 fg = text_alpha * foreground;
|
||||
vec3 decoration = blend(underline_alpha, underline, strike);
|
||||
vec3 combined_fg = blend(text_alpha, fg, decoration);
|
||||
float combined_alpha = max(max(underline_alpha, strike_alpha), text_alpha);
|
||||
final_color = vec4(blend(combined_alpha, combined_fg, background), 1);
|
||||
}
|
||||
''')
|
||||
# }}}
|
||||
|
||||
# cursor shader {{{
|
||||
|
||||
cursor_shader = (
|
||||
'''\
|
||||
uniform vec2 xpos;
|
||||
uniform vec2 ypos;
|
||||
|
||||
const uvec2 pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
uvec2(0, 1), // left, bottom
|
||||
uvec2(0, 0) // left, top
|
||||
);
|
||||
|
||||
void main() {
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
|
||||
}
|
||||
''',
|
||||
|
||||
'''\
|
||||
uniform vec4 color;
|
||||
out vec4 final_color;
|
||||
|
||||
void main() {
|
||||
final_color = color;
|
||||
}
|
||||
''')
|
||||
# }}}
|
||||
def load_shader_programs():
|
||||
cell = CellProgram(*load_shaders('cell'))
|
||||
cursor = ShaderProgram(*load_shaders('cursor'))
|
||||
cursor.vao_id = cursor.add_vertex_arrays()
|
||||
return cell, cursor
|
||||
|
||||
|
||||
class Selection: # {{{
|
||||
@@ -195,12 +99,15 @@ class Selection: # {{{
|
||||
if y == b[0]:
|
||||
endx = max(0, min(b[1], endx))
|
||||
l = line(y)
|
||||
is_continued = l.is_continued()
|
||||
if endx - startx >= linebuf.xnum - 1:
|
||||
l = str(l).rstrip(' ')
|
||||
else:
|
||||
l = ''.join(l[x] for x in range(startx, endx + 1))
|
||||
if not is_continued and startx == 0 and len(lines) > 0:
|
||||
l = '\n' + l
|
||||
lines.append(l)
|
||||
return '\n'.join(lines)
|
||||
return ''.join(lines)
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -213,15 +120,15 @@ def calculate_gl_geometry(window_geometry):
|
||||
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
|
||||
|
||||
|
||||
def render_cells(buffer_id, sg, cell_program, sprites):
|
||||
sprites.bind_sprite_map(buffer_id)
|
||||
def render_cells(vao_id, sg, cell_program, sprites, invert_colors=False):
|
||||
ul = cell_program.uniform_location
|
||||
glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum)
|
||||
glUniform2i(ul('color_indices'), 1 if invert_colors else 0, 0 if invert_colors else 1)
|
||||
glUniform4f(ul('steps'), sg.xstart, sg.ystart, sg.dx, sg.dy)
|
||||
glUniform1i(ul('sprites'), sprites.sampler_num)
|
||||
glUniform1i(ul('sprite_map'), sprites.buffer_sampler_num)
|
||||
glUniform2f(ul('sprite_layout'), *(sprites.layout))
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum)
|
||||
with cell_program.bound_vertex_array(vao_id):
|
||||
glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum)
|
||||
|
||||
|
||||
class CharGrid:
|
||||
@@ -230,7 +137,7 @@ class CharGrid:
|
||||
|
||||
def __init__(self, screen, opts):
|
||||
self.buffer_lock = Lock()
|
||||
self.buffer_id = None
|
||||
self.vao_id = None
|
||||
self.current_selection = Selection()
|
||||
self.last_rendered_selection = self.current_selection.limits(0, screen.lines, screen.columns)
|
||||
self.render_buf_is_dirty = True
|
||||
@@ -240,17 +147,13 @@ class CharGrid:
|
||||
self.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
|
||||
self.screen = screen
|
||||
self.opts = opts
|
||||
self.original_bg = opts.background
|
||||
self.original_fg = opts.foreground
|
||||
self.default_bg = color_as_int(self.original_bg)
|
||||
self.default_fg = color_as_int(self.original_fg)
|
||||
self.default_bg = color_as_int(opts.background)
|
||||
self.default_fg = color_as_int(opts.foreground)
|
||||
self.dpix, self.dpiy = get_logical_dpi()
|
||||
self.opts = opts
|
||||
self.default_cursor = self.current_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor, opts.cursor_blink_interval > 0)
|
||||
self.default_cursor = self.current_cursor = Cursor(0, 0, opts.cursor_shape, opts.cursor_blink_interval > 0)
|
||||
self.opts = opts
|
||||
self.original_bg = opts.background
|
||||
self.original_fg = opts.foreground
|
||||
self.selection_foreground, self.selection_background = map(color_as_int, (opts.selection_foreground, opts.selection_background))
|
||||
self.highlight_fg, self.highlight_bg = map(color_as_int, (opts.selection_foreground, opts.selection_background))
|
||||
self.sprite_map_type = self.main_sprite_map = self.scroll_sprite_map = self.render_buf = None
|
||||
|
||||
def escape(chars):
|
||||
@@ -262,6 +165,11 @@ class CharGrid:
|
||||
safe_print('Invalid characters in select_by_word_characters, ignoring', file=sys.stderr)
|
||||
self.word_pat = re.compile(r'[\w{}]'.format(escape(defaults.select_by_word_characters)), re.UNICODE)
|
||||
|
||||
def destroy(self, cell_program):
|
||||
if self.vao_id is not None:
|
||||
cell_program.remove_vertex_array(self.vao_id)
|
||||
self.vao_id = None
|
||||
|
||||
def update_position(self, window_geometry):
|
||||
self.screen_geometry = calculate_gl_geometry(window_geometry)
|
||||
|
||||
@@ -278,16 +186,19 @@ class CharGrid:
|
||||
|
||||
def change_colors(self, changes):
|
||||
dirtied = False
|
||||
|
||||
def item(raw):
|
||||
if raw is None:
|
||||
return 0
|
||||
val = to_color(raw)
|
||||
return None if val is None else (color_as_int(val) << 8) | 2
|
||||
|
||||
for which, val in changes.items():
|
||||
if which in ('fg', 'bg'):
|
||||
if not val:
|
||||
setattr(self, 'default_' + which, color_as_int(getattr(self, 'original_' + which)))
|
||||
dirtied = True
|
||||
else:
|
||||
val = to_color(val)
|
||||
if val is not None:
|
||||
setattr(self, 'default_' + which, color_as_int(val))
|
||||
dirtied = True
|
||||
val = item(val)
|
||||
if val is None:
|
||||
continue
|
||||
dirtied = True
|
||||
setattr(self.screen, which.name, val)
|
||||
if dirtied:
|
||||
self.screen.mark_as_dirty()
|
||||
|
||||
@@ -305,12 +216,15 @@ class CharGrid:
|
||||
sprites = get_boss().sprites
|
||||
is_dirty = self.screen.is_dirty()
|
||||
with sprites.lock:
|
||||
fg, bg = self.screen.default_fg, self.screen.default_bg
|
||||
fg = fg >> 8 if fg & 2 else self.default_fg
|
||||
bg = bg >> 8 if bg & 2 else self.default_bg
|
||||
cursor_changed, history_line_added_count = self.screen.update_cell_data(
|
||||
sprites.backend, self.color_profile, addressof(self.main_sprite_map), self.default_fg, self.default_bg, force_full_refresh)
|
||||
sprites.backend, self.color_profile, addressof(self.main_sprite_map), fg, bg, force_full_refresh)
|
||||
if self.scrolled_by:
|
||||
self.scrolled_by = min(self.scrolled_by + history_line_added_count, self.screen.historybuf.count)
|
||||
self.screen.set_scroll_cell_data(
|
||||
sprites.backend, self.color_profile, addressof(self.main_sprite_map), self.default_fg, self.default_bg,
|
||||
sprites.backend, self.color_profile, addressof(self.main_sprite_map), fg, bg,
|
||||
self.scrolled_by, addressof(self.scroll_sprite_map))
|
||||
|
||||
data = self.scroll_sprite_map if self.scrolled_by else self.main_sprite_map
|
||||
@@ -322,7 +236,7 @@ class CharGrid:
|
||||
self.render_buf_is_dirty = True
|
||||
if cursor_changed:
|
||||
c = self.screen.cursor
|
||||
self.current_cursor = Cursor(c.x, c.y, c.shape, c.color, c.blink)
|
||||
self.current_cursor = Cursor(c.x, c.y, c.shape, c.blink)
|
||||
|
||||
def cell_for_pos(self, x, y):
|
||||
x, y = int(x // cell_size.width), int(y // cell_size.height)
|
||||
@@ -372,6 +286,10 @@ class CharGrid:
|
||||
for m in self.url_pat.finditer(text):
|
||||
if m.start() <= x < m.end():
|
||||
url = ''.join(l[i] for i in range(*m.span())).rstrip('.')
|
||||
# Remove trailing "] and similar
|
||||
url = re.sub(r'''["'][)}\]]$''', '', url)
|
||||
# Remove closing trailing character if it is matched by it's
|
||||
# corresponding opening character before the url
|
||||
if m.start() > 0:
|
||||
before = l[m.start() - 1]
|
||||
closing = {'(': ')', '[': ']', '{': '}', '<': '>', '"': '"', "'": "'", '`': '`', '|': '|', ':': ':'}.get(before)
|
||||
@@ -436,28 +354,32 @@ class CharGrid:
|
||||
s = sel or self.current_selection
|
||||
return s.text(self.screen.linebuf, self.screen.historybuf)
|
||||
|
||||
def prepare_for_render(self, sprites):
|
||||
def prepare_for_render(self, cell_program):
|
||||
with self.buffer_lock:
|
||||
sg = self.render_data
|
||||
if sg is None:
|
||||
return
|
||||
if self.buffer_id is None:
|
||||
self.buffer_id = sprites.add_sprite_map()
|
||||
if self.vao_id is None:
|
||||
self.vao_id = cell_program.create_sprite_map()
|
||||
buf = self.render_buf
|
||||
start, end = sel = self.current_selection.limits(self.scrolled_by, self.screen.lines, self.screen.columns)
|
||||
if start != end:
|
||||
buf = self.selection_buf
|
||||
if self.render_buf_is_dirty or sel != self.last_rendered_selection:
|
||||
memmove(buf, self.render_buf, sizeof(type(buf)))
|
||||
self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], self.selection_foreground, self.selection_background)
|
||||
fg = self.screen.highlight_fg
|
||||
fg = fg >> 8 if fg & 2 else self.highlight_fg
|
||||
bg = self.screen.highlight_bg
|
||||
bg = bg >> 8 if bg & 2 else self.highlight_bg
|
||||
self.screen.apply_selection(addressof(buf), start[0], start[1], end[0], end[1], fg, bg)
|
||||
if self.render_buf_is_dirty or self.last_rendered_selection != sel:
|
||||
sprites.set_sprite_map(self.buffer_id, buf)
|
||||
cell_program.send_vertex_data(self.vao_id, buf)
|
||||
self.render_buf_is_dirty = False
|
||||
self.last_rendered_selection = sel
|
||||
return sg
|
||||
|
||||
def render_cells(self, sg, cell_program, sprites):
|
||||
render_cells(self.buffer_id, sg, cell_program, sprites)
|
||||
def render_cells(self, sg, cell_program, sprites, invert_colors=False):
|
||||
render_cells(self.vao_id, sg, cell_program, sprites, invert_colors=invert_colors)
|
||||
|
||||
def render_cursor(self, sg, cursor_program, is_focused):
|
||||
cursor = self.current_cursor
|
||||
@@ -473,7 +395,8 @@ class CharGrid:
|
||||
ul = cursor_program.uniform_location
|
||||
left = sg.xstart + cursor.x * sg.dx
|
||||
top = sg.ystart - cursor.y * sg.dy
|
||||
col = cursor.color or self.default_cursor.color
|
||||
cc = self.screen.cursor_color
|
||||
col = color_from_int(cc >> 8) if cc & 2 else self.opts.cursor
|
||||
shape = cursor.shape or self.default_cursor.shape
|
||||
alpha = self.opts.cursor_opacity
|
||||
if alpha < 1.0 and shape == CURSOR_BLOCK:
|
||||
@@ -483,8 +406,9 @@ class CharGrid:
|
||||
bottom = top - sg.dy
|
||||
if shape == CURSOR_UNDERLINE:
|
||||
top = bottom + width(vert=False)
|
||||
glUniform4f(ul('color'), col[0], col[1], col[2], alpha)
|
||||
glUniform4f(ul('color'), col[0] / 255.0, col[1] / 255.0, col[2] / 255.0, alpha)
|
||||
glUniform2f(ul('xpos'), left, right)
|
||||
glUniform2f(ul('ypos'), top, bottom)
|
||||
glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4)
|
||||
with cursor_program.bound_vertex_array(cursor_program.vao_id):
|
||||
glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4)
|
||||
glDisable(GL_BLEND)
|
||||
|
||||
@@ -10,6 +10,7 @@ import signal
|
||||
from threading import Thread
|
||||
|
||||
from .constants import terminfo_dir
|
||||
import kitty.fast_data_types as fast_data_types
|
||||
|
||||
|
||||
def remove_cloexec(fd):
|
||||
@@ -33,6 +34,7 @@ class Child:
|
||||
self.forked = True
|
||||
master, slave = os.openpty() # Note that master and slave are in blocking mode
|
||||
remove_cloexec(slave)
|
||||
self.set_iutf8(fd=master)
|
||||
stdin, self.stdin = self.stdin, None
|
||||
if stdin is not None:
|
||||
stdin_read_fd, stdin_write_fd = os.pipe()
|
||||
@@ -79,6 +81,16 @@ class Child:
|
||||
if self.child_fd is not None:
|
||||
fcntl.ioctl(self.child_fd, termios.TIOCSWINSZ, struct.pack('4H', h, w, ww, wh))
|
||||
|
||||
def set_iutf8(self, on=True, fd=None):
|
||||
fd = fd or self.child_fd
|
||||
if fd is not None and hasattr(fast_data_types, 'IUTF8'):
|
||||
attrs = termios.tcgetattr(fd)
|
||||
if on:
|
||||
attrs[0] |= fast_data_types.IUTF8
|
||||
else:
|
||||
attrs[0] &= ~fast_data_types.IUTF8
|
||||
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
||||
|
||||
def hangup(self):
|
||||
if self.pid is not None:
|
||||
pid, self.pid = self.pid, None
|
||||
|
||||
@@ -12,6 +12,7 @@ import sys
|
||||
|
||||
|
||||
CSI = '\033['
|
||||
OSC = '\033]'
|
||||
|
||||
|
||||
def write(x):
|
||||
@@ -19,10 +20,30 @@ def write(x):
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def set_title(*args):
|
||||
pass
|
||||
|
||||
|
||||
def set_icon(*args):
|
||||
pass
|
||||
|
||||
|
||||
def screen_bell():
|
||||
pass
|
||||
|
||||
|
||||
def screen_cursor_position(y, x):
|
||||
write(CSI + '%s;%sH' % (y, x))
|
||||
|
||||
|
||||
def screen_cursor_forward(amt):
|
||||
write(CSI + '%sC' % amt)
|
||||
|
||||
|
||||
def screen_cursor_back1(amt):
|
||||
write(CSI + '%sD' % amt)
|
||||
|
||||
|
||||
def screen_designate_charset(which, to):
|
||||
which = '()'[int(which)]
|
||||
to = chr(int(to))
|
||||
@@ -53,18 +74,34 @@ def screen_set_margins(t, b):
|
||||
write(CSI + '%d;%dr' % (t, b))
|
||||
|
||||
|
||||
def screen_indexn(n):
|
||||
write(CSI + '%dS' % n)
|
||||
|
||||
|
||||
def screen_erase_in_display(how, private):
|
||||
write(CSI + ('?' if private else '') + str(how) + 'J')
|
||||
|
||||
|
||||
def screen_erase_in_line(how, private):
|
||||
write(CSI + ('?' if private else '') + str(how) + 'K')
|
||||
|
||||
|
||||
def screen_cursor_up2(count):
|
||||
write(CSI + '%dA' % count)
|
||||
|
||||
|
||||
def screen_cursor_down(count):
|
||||
write(CSI + '%dB' % count)
|
||||
|
||||
|
||||
def screen_carriage_return():
|
||||
write('\r')
|
||||
|
||||
|
||||
def screen_linefeed():
|
||||
write('\n')
|
||||
|
||||
|
||||
def screen_backspace():
|
||||
write('\x08')
|
||||
|
||||
@@ -73,12 +110,30 @@ def draw(*a):
|
||||
write(' '.join(a))
|
||||
|
||||
|
||||
def report_device_attributes(mode, char):
|
||||
x = CSI
|
||||
if char:
|
||||
x += ord(char)
|
||||
if mode:
|
||||
x += str(mode)
|
||||
write(CSI + x + 'c')
|
||||
|
||||
|
||||
def write_osc(code, string=''):
|
||||
if string:
|
||||
string = ';' + string
|
||||
write(OSC + str(code) + string + '\x07')
|
||||
|
||||
|
||||
set_dynamic_color = set_color_table_color = write_osc
|
||||
|
||||
|
||||
def replay(raw):
|
||||
for line in raw.splitlines():
|
||||
if line.strip():
|
||||
cmd, rest = line.partition(' ')[::2]
|
||||
if cmd == 'draw':
|
||||
draw(rest)
|
||||
if cmd in {'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color'}:
|
||||
globals()[cmd](rest)
|
||||
else:
|
||||
rest = map(int, rest.split()) if rest else ()
|
||||
globals()[cmd](*rest)
|
||||
@@ -87,4 +142,7 @@ def replay(raw):
|
||||
def main(path):
|
||||
raw = open(path).read()
|
||||
replay(raw)
|
||||
input()
|
||||
try:
|
||||
input()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
pass
|
||||
|
||||
175
kitty/cocoa_window.m
Normal file
175
kitty/cocoa_window.m
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* cocoa_window.m
|
||||
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
|
||||
#include "data-types.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
// Needed for _NSGetProgname
|
||||
#include <crt_externs.h>
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||
#endif
|
||||
|
||||
@interface MenuDispatcher : NSObject
|
||||
@end
|
||||
|
||||
@implementation MenuDispatcher
|
||||
@end
|
||||
|
||||
static NSObject* menu_dispatcher = NULL;
|
||||
static NSMenuItem* title_menu = NULL;
|
||||
|
||||
static NSString*
|
||||
find_app_name(void) {
|
||||
size_t i;
|
||||
NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
|
||||
|
||||
// Keys to search for as potential application names
|
||||
NSString* name_keys[] =
|
||||
{
|
||||
@"CFBundleDisplayName",
|
||||
@"CFBundleName",
|
||||
@"CFBundleExecutable",
|
||||
};
|
||||
|
||||
for (i = 0; i < sizeof(name_keys) / sizeof(name_keys[0]); i++)
|
||||
{
|
||||
id name = [infoDictionary objectForKey:name_keys[i]];
|
||||
if (name &&
|
||||
[name isKindOfClass:[NSString class]] &&
|
||||
![name isEqualToString:@""])
|
||||
{
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
char** progname = _NSGetProgname();
|
||||
if (progname && *progname)
|
||||
return [NSString stringWithUTF8String:*progname];
|
||||
|
||||
// Really shouldn't get here
|
||||
return @"kitty";
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
cocoa_create_global_menu(PyObject UNUSED *_self) {
|
||||
if (menu_dispatcher != NULL) { Py_RETURN_NONE; }
|
||||
NSString* app_name = find_app_name();
|
||||
menu_dispatcher = [[MenuDispatcher alloc] init];
|
||||
NSMenu* bar = [[NSMenu alloc] init];
|
||||
[NSApp setMainMenu:bar];
|
||||
|
||||
NSMenuItem* appMenuItem =
|
||||
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
|
||||
NSMenu* appMenu = [[NSMenu alloc] init];
|
||||
[appMenuItem setSubmenu:appMenu];
|
||||
|
||||
[appMenu addItemWithTitle:[NSString stringWithFormat:@"About %@", app_name]
|
||||
action:@selector(orderFrontStandardAboutPanel:)
|
||||
keyEquivalent:@""];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"];
|
||||
[[appMenu addItemWithTitle:@"Hide Others"
|
||||
action:@selector(hideOtherApplications:)
|
||||
keyEquivalent:@"h"]
|
||||
setKeyEquivalentModifierMask:NSAlternateKeyMask | NSCommandKeyMask];
|
||||
[appMenu addItemWithTitle:@"Show All"
|
||||
action:@selector(unhideAllApplications:)
|
||||
keyEquivalent:@""];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
NSMenu* servicesMenu = [[NSMenu alloc] init];
|
||||
[NSApp setServicesMenu:servicesMenu];
|
||||
[[appMenu addItemWithTitle:@"Services"
|
||||
action:NULL
|
||||
keyEquivalent:@""] setSubmenu:servicesMenu];
|
||||
[servicesMenu release];
|
||||
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
|
||||
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Quit %@", app_name]
|
||||
action:@selector(terminate:)
|
||||
keyEquivalent:@"q"];
|
||||
[appMenu release];
|
||||
|
||||
NSMenuItem* windowMenuItem =
|
||||
[bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
|
||||
NSMenu* windowMenu = [[NSMenu alloc] initWithTitle:@"Window"];
|
||||
[NSApp setWindowsMenu:windowMenu];
|
||||
[windowMenuItem setSubmenu:windowMenu];
|
||||
|
||||
[windowMenu addItemWithTitle:@"Minimize"
|
||||
action:@selector(performMiniaturize:)
|
||||
keyEquivalent:@"m"];
|
||||
[windowMenu addItemWithTitle:@"Zoom"
|
||||
action:@selector(performZoom:)
|
||||
keyEquivalent:@""];
|
||||
[windowMenu addItem:[NSMenuItem separatorItem]];
|
||||
[windowMenu addItemWithTitle:@"Bring All to Front"
|
||||
action:@selector(arrangeInFront:)
|
||||
keyEquivalent:@""];
|
||||
|
||||
[windowMenu addItem:[NSMenuItem separatorItem]];
|
||||
[[windowMenu addItemWithTitle:@"Enter Full Screen"
|
||||
action:@selector(toggleFullScreen:)
|
||||
keyEquivalent:@"f"]
|
||||
setKeyEquivalentModifierMask:NSControlKeyMask | NSCommandKeyMask];
|
||||
[windowMenu release];
|
||||
|
||||
|
||||
[bar release];
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
cocoa_update_title(PyObject UNUSED *self, PyObject *pytitle) {
|
||||
NSString *title = [[NSString alloc] initWithUTF8String:PyUnicode_AsUTF8(pytitle)];
|
||||
NSMenu *bar = [NSApp mainMenu];
|
||||
if (title_menu != NULL) {
|
||||
[bar removeItem:title_menu];
|
||||
}
|
||||
title_menu = [bar addItemWithTitle:@"" action:NULL keyEquivalent:@""];
|
||||
NSMenu *m = [[NSMenu alloc] initWithTitle:[NSString stringWithFormat:@" :: %@", title]];
|
||||
[title_menu setSubmenu:m];
|
||||
[m release];
|
||||
[title release];
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id) {
|
||||
NSWindow *window = (NSWindow*)PyLong_AsVoidPtr(window_id);
|
||||
|
||||
@try {
|
||||
[window setStyleMask:
|
||||
[window styleMask] | NSWindowStyleMaskResizable];
|
||||
} @catch (NSException *e) {
|
||||
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
cocoa_get_lang(PyObject UNUSED *self) {
|
||||
NSString* locale = nil;
|
||||
NSString* lang_code = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode];
|
||||
NSString* country_code = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode];
|
||||
if (lang_code && country_code) {
|
||||
locale = [NSString stringWithFormat:@"%@_%@", lang_code, country_code];
|
||||
} else {
|
||||
locale = [[NSLocale currentLocale] localeIdentifier];
|
||||
}
|
||||
if (!locale) { Py_RETURN_NONE; }
|
||||
return Py_BuildValue("s", [locale UTF8String]);
|
||||
}
|
||||
@@ -99,6 +99,7 @@ as_color(ColorProfile *self, PyObject *val) {
|
||||
break;
|
||||
case 2:
|
||||
col = entry >> 8;
|
||||
break;
|
||||
default:
|
||||
ans = Py_None; Py_INCREF(Py_None);
|
||||
}
|
||||
|
||||
292
kitty/config.py
292
kitty/config.py
@@ -2,37 +2,45 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import shlex
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
from collections import namedtuple
|
||||
|
||||
from .fast_data_types import (
|
||||
CURSOR_BLOCK, CURSOR_BEAM, CURSOR_UNDERLINE
|
||||
)
|
||||
import kitty.fast_data_types as defines
|
||||
from .utils import to_color, safe_print
|
||||
from .layout import all_layouts
|
||||
from . import fast_data_types as defines
|
||||
from .constants import config_dir
|
||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||
from .layout import all_layouts
|
||||
from .rgb import to_color
|
||||
from .utils import safe_print
|
||||
|
||||
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
|
||||
MINIMUM_FONT_SIZE = 6
|
||||
|
||||
|
||||
def to_font_size(x):
|
||||
return max(6, float(x))
|
||||
return max(MINIMUM_FONT_SIZE, float(x))
|
||||
|
||||
|
||||
cshapes = {'block': CURSOR_BLOCK, 'beam': CURSOR_BEAM, 'underline': CURSOR_UNDERLINE}
|
||||
cshapes = {
|
||||
'block': CURSOR_BLOCK,
|
||||
'beam': CURSOR_BEAM,
|
||||
'underline': CURSOR_UNDERLINE
|
||||
}
|
||||
|
||||
|
||||
def to_cursor_shape(x):
|
||||
try:
|
||||
return cshapes[x.lower()]
|
||||
except KeyError:
|
||||
raise ValueError('Invalid cursor shape: {} allowed values are {}'.format(x, ', '.join(cshapes)))
|
||||
raise ValueError(
|
||||
'Invalid cursor shape: {} allowed values are {}'.
|
||||
format(x, ', '.join(cshapes))
|
||||
)
|
||||
|
||||
|
||||
def to_bool(x):
|
||||
@@ -53,33 +61,122 @@ def parse_mods(parts):
|
||||
try:
|
||||
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
|
||||
except AttributeError:
|
||||
safe_print('Shortcut: {} has an unknown modifier, ignoring'.format(parts.join('+')), file=sys.stderr)
|
||||
safe_print(
|
||||
'Shortcut: {} has an unknown modifier, ignoring'.
|
||||
format(parts.join('+')),
|
||||
file=sys.stderr
|
||||
)
|
||||
return
|
||||
|
||||
return mods
|
||||
|
||||
|
||||
named_keys = {"'": 'APOSTROPHE', ',': 'COMMA', '-': 'MINUS', '.': 'PERIOD',
|
||||
'/': 'SLASH', ';': 'SEMICOLON', '=': 'EQUAL', '[': 'LEFT_BRACKET',
|
||||
']': 'RIGHT_BRACKET', '`': 'GRAVE_ACCENT'}
|
||||
named_keys = {
|
||||
"'": 'APOSTROPHE',
|
||||
',': 'COMMA',
|
||||
'-': 'MINUS',
|
||||
'.': 'PERIOD',
|
||||
'/': 'SLASH',
|
||||
';': 'SEMICOLON',
|
||||
'=': 'EQUAL',
|
||||
'[': 'LEFT_BRACKET',
|
||||
']': 'RIGHT_BRACKET',
|
||||
'`': 'GRAVE_ACCENT'
|
||||
}
|
||||
|
||||
|
||||
def parse_key(val, keymap):
|
||||
sc, action = val.partition(' ')[::2]
|
||||
action = action.strip()
|
||||
sc = sc.strip()
|
||||
if not sc or not action:
|
||||
return
|
||||
def parse_shortcut(sc):
|
||||
parts = sc.split('+')
|
||||
mods = parse_mods(parts[:-1])
|
||||
key = parts[-1].upper()
|
||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||
if key is not None:
|
||||
return mods, key
|
||||
return None, None
|
||||
|
||||
|
||||
def parse_key(val, keymap):
|
||||
sc, action = val.partition(' ')[::2]
|
||||
sc, action = sc.strip(), action.strip()
|
||||
if not sc or not action:
|
||||
return
|
||||
mods, key = parse_shortcut(sc)
|
||||
if key is None:
|
||||
safe_print('Shortcut: {} has an unknown key, ignoring'.format(val), file=sys.stderr)
|
||||
safe_print(
|
||||
'Shortcut: {} has an unknown key, ignoring'.format(val),
|
||||
file=sys.stderr
|
||||
)
|
||||
return
|
||||
keymap[(mods, key)] = action
|
||||
|
||||
|
||||
def parse_symbol_map(val):
|
||||
parts = val.split(' ')
|
||||
symbol_map = {}
|
||||
|
||||
def abort():
|
||||
safe_print(
|
||||
'Symbol map: {} is invalid, ignoring'.format(val), file=sys.stderr
|
||||
)
|
||||
return {}
|
||||
|
||||
if len(parts) < 2:
|
||||
return abort()
|
||||
family = ' '.join(parts[1:])
|
||||
|
||||
def to_chr(x):
|
||||
if not x.startswith('U+'):
|
||||
raise ValueError()
|
||||
x = int(x[2:], 16)
|
||||
return x
|
||||
|
||||
for x in parts[0].split(','):
|
||||
a, b = x.partition('-')[::2]
|
||||
b = b or a
|
||||
try:
|
||||
a, b = map(to_chr, (a, b))
|
||||
except Exception:
|
||||
return abort()
|
||||
if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1:
|
||||
return abort()
|
||||
for y in range(a, b + 1):
|
||||
symbol_map[chr(y)] = family
|
||||
return symbol_map
|
||||
|
||||
|
||||
def parse_send_text(val):
|
||||
parts = val.split(' ')
|
||||
|
||||
def abort(msg):
|
||||
safe_print(
|
||||
'Send text: {} is invalid ({}), ignoring'.format(val, msg), file=sys.stderr
|
||||
)
|
||||
return {}
|
||||
|
||||
if len(parts) < 3:
|
||||
return abort('Incomplete')
|
||||
|
||||
text = ' '.join(parts[2:])
|
||||
mode, sc = parts[:2]
|
||||
mods, key = parse_shortcut(sc.strip())
|
||||
if key is None:
|
||||
return abort('Invalid shortcut')
|
||||
text = ast.literal_eval("'''" + text + "'''").encode('utf-8')
|
||||
if not text:
|
||||
return abort('Empty text')
|
||||
|
||||
if mode in ('all', '*'):
|
||||
modes = parse_send_text.all_modes
|
||||
else:
|
||||
modes = frozenset(mode.split(',')).intersection(parse_send_text.all_modes)
|
||||
if not modes:
|
||||
return abort('Invalid keyboard modes')
|
||||
return {mode: {(mods, key): text} for mode in modes}
|
||||
|
||||
|
||||
parse_send_text.all_modes = frozenset({'normal', 'application', 'kitty'})
|
||||
|
||||
|
||||
def to_open_url_modifiers(val):
|
||||
return parse_mods(val.split('+'))
|
||||
|
||||
@@ -93,29 +190,46 @@ def to_layout_names(raw):
|
||||
raise ValueError('The window layout {} is unknown'.format(p))
|
||||
|
||||
|
||||
def positive_int(x):
|
||||
return max(0, int(x))
|
||||
|
||||
|
||||
def positive_float(x):
|
||||
return max(0, float(x))
|
||||
|
||||
|
||||
type_map = {
|
||||
'scrollback_lines': int,
|
||||
'scrollback_lines': positive_int,
|
||||
'scrollback_pager': shlex.split,
|
||||
'scrollback_in_new_tab': to_bool,
|
||||
'font_size': to_font_size,
|
||||
'font_size_delta': positive_float,
|
||||
'cursor_shape': to_cursor_shape,
|
||||
'cursor_opacity': to_opacity,
|
||||
'open_url_modifiers': to_open_url_modifiers,
|
||||
'repaint_delay': int,
|
||||
'window_border_width': float,
|
||||
'repaint_delay': positive_int,
|
||||
'window_border_width': positive_float,
|
||||
'window_margin_width': positive_float,
|
||||
'window_padding_width': positive_float,
|
||||
'wheel_scroll_multiplier': float,
|
||||
'click_interval': float,
|
||||
'mouse_hide_wait': float,
|
||||
'cursor_blink_interval': float,
|
||||
'cursor_stop_blinking_after': float,
|
||||
'visual_bell_duration': positive_float,
|
||||
'enable_audio_bell': to_bool,
|
||||
'click_interval': positive_float,
|
||||
'mouse_hide_wait': positive_float,
|
||||
'cursor_blink_interval': positive_float,
|
||||
'cursor_stop_blinking_after': positive_float,
|
||||
'enabled_layouts': to_layout_names,
|
||||
'remember_window_size': to_bool,
|
||||
'initial_window_width': int,
|
||||
'initial_window_height': int,
|
||||
'initial_window_width': positive_int,
|
||||
'initial_window_height': positive_int,
|
||||
'use_system_wcwidth': to_bool,
|
||||
'macos_hide_titlebar': to_bool,
|
||||
}
|
||||
|
||||
for name in 'foreground background cursor active_border_color inactive_border_color selection_foreground selection_background'.split():
|
||||
for name in (
|
||||
'foreground background cursor active_border_color inactive_border_color'
|
||||
' selection_foreground selection_background'
|
||||
).split():
|
||||
type_map[name] = lambda x: to_color(x, validate=True)
|
||||
for i in range(16):
|
||||
type_map['color%d' % i] = lambda x: to_color(x, validate=True)
|
||||
@@ -124,8 +238,10 @@ for a in ('active', 'inactive'):
|
||||
type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True)
|
||||
|
||||
|
||||
def parse_config(lines):
|
||||
ans = {'keymap': {}}
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {'keymap': {}, 'symbol_map': {}, 'send_text_map': {'kitty': {}, 'normal': {}, 'application': {}}}
|
||||
if check_keys:
|
||||
all_keys = defaults._asdict()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
@@ -136,6 +252,18 @@ def parse_config(lines):
|
||||
if key == 'map':
|
||||
parse_key(val, ans['keymap'])
|
||||
continue
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
continue
|
||||
if key == 'send_text':
|
||||
stvals = parse_send_text(val)
|
||||
for k, v in ans['send_text_map'].items():
|
||||
v.update(stvals.get(k, {}))
|
||||
continue
|
||||
if check_keys:
|
||||
if key not in all_keys:
|
||||
safe_print('Ignoring unknown config key: {}'.format(key), file=sys.stderr)
|
||||
continue
|
||||
tm = type_map.get(key)
|
||||
if tm is not None:
|
||||
val = tm(val)
|
||||
@@ -143,46 +271,74 @@ def parse_config(lines):
|
||||
return ans
|
||||
|
||||
|
||||
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')) as f:
|
||||
defaults = parse_config(f.readlines())
|
||||
with open(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')
|
||||
) as f:
|
||||
defaults = parse_config(f.readlines(), check_keys=False)
|
||||
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
||||
defaults = Options(**defaults)
|
||||
actions = frozenset(defaults.keymap.values())
|
||||
|
||||
|
||||
def update_dict(a, b):
|
||||
a.update(b)
|
||||
return a
|
||||
def merge_keymaps(defaults, newvals):
|
||||
ans = defaults.copy()
|
||||
for k, v in newvals.items():
|
||||
if v in {'noop', 'no-op', 'no_op'}:
|
||||
ans.pop(k, None)
|
||||
continue
|
||||
if v in actions:
|
||||
ans[k] = v
|
||||
return ans
|
||||
|
||||
|
||||
def merge_dicts(vals, defaults):
|
||||
return {k: update_dict(v, vals.get(k, {})) if isinstance(v, dict) else vals.get(k, v) for k, v in defaults.items()}
|
||||
def merge_dicts(defaults, newvals):
|
||||
ans = defaults.copy()
|
||||
ans.update(newvals)
|
||||
return ans
|
||||
|
||||
|
||||
def load_config(path: str) -> Options:
|
||||
if not path:
|
||||
return defaults
|
||||
try:
|
||||
f = open(path)
|
||||
except FileNotFoundError:
|
||||
return defaults
|
||||
def merge_configs(defaults, vals):
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
newvals = vals.get(k, {})
|
||||
if k == 'keymap':
|
||||
ans['keymap'] = merge_keymaps(v, newvals)
|
||||
elif k == 'send_text_map':
|
||||
ans[k] = {m: merge_dicts(mm, newvals.get(m, {})) for m, mm in v.items()}
|
||||
else:
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
else:
|
||||
ans[k] = vals.get(k, v)
|
||||
return ans
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None) -> Options:
|
||||
ans = defaults._asdict()
|
||||
actions = frozenset(defaults.keymap.values())
|
||||
with f:
|
||||
vals = parse_config(f)
|
||||
remove_keys = {k for k, v in vals.get('keymap', {}).items() if v in ('noop', 'no-op', 'no_op')}
|
||||
vals['keymap'] = {k: v for k, v in vals.get('keymap', {}).items() if v in actions}
|
||||
ans = merge_dicts(vals, ans)
|
||||
for k in remove_keys:
|
||||
ans['keymap'].pop(k, None)
|
||||
for path in paths:
|
||||
if not path:
|
||||
continue
|
||||
try:
|
||||
f = open(path)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
with f:
|
||||
vals = parse_config(f)
|
||||
ans = merge_configs(ans, vals)
|
||||
if overrides is not None:
|
||||
vals = parse_config(overrides)
|
||||
ans = merge_configs(ans, vals)
|
||||
return Options(**ans)
|
||||
|
||||
|
||||
def build_ansi_color_table(opts: Options=defaults):
|
||||
|
||||
def as_int(x):
|
||||
return (x[0] << 16) | (x[1] << 8) | x[2]
|
||||
|
||||
def col(i):
|
||||
return as_int(getattr(opts, 'color{}'.format(i)))
|
||||
|
||||
return list(map(col, range(16)))
|
||||
|
||||
|
||||
@@ -198,21 +354,33 @@ def load_cached_values():
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as err:
|
||||
safe_print('Failed to load cached values with error: {}'.format(err), file=sys.stderr)
|
||||
safe_print(
|
||||
'Failed to load cached values with error: {}'.format(err),
|
||||
file=sys.stderr
|
||||
)
|
||||
|
||||
|
||||
def save_cached_values():
|
||||
fd, p = tempfile.mkstemp(dir=os.path.dirname(cached_path), suffix='cached.json.tmp')
|
||||
fd, p = tempfile.mkstemp(
|
||||
dir=os.path.dirname(cached_path), suffix='cached.json.tmp'
|
||||
)
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
f.write(json.dumps(cached_values).encode('utf-8'))
|
||||
os.rename(p, cached_path)
|
||||
except Exception as err:
|
||||
safe_print('Failed to save cached values with error: {}'.format(err), file=sys.stderr)
|
||||
safe_print(
|
||||
'Failed to save cached values with error: {}'.format(err),
|
||||
file=sys.stderr
|
||||
)
|
||||
finally:
|
||||
try:
|
||||
os.remove(p)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as err:
|
||||
safe_print('Failed to delete temp file for saved cached values with error: {}'.format(err), file=sys.stderr)
|
||||
safe_print(
|
||||
'Failed to delete temp file for saved cached values with error: {}'.
|
||||
format(err),
|
||||
file=sys.stderr
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ from .fast_data_types import (
|
||||
GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER)
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 1, 0)
|
||||
version = (0, 3, 0)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
isosx = 'darwin' in _plat
|
||||
@@ -32,10 +32,7 @@ def _get_config_dir():
|
||||
|
||||
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if isosx else '~/.config')))
|
||||
ans = os.path.join(candidate, appname)
|
||||
try:
|
||||
os.makedirs(ans)
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(ans, exist_ok=True)
|
||||
return ans
|
||||
|
||||
|
||||
|
||||
@@ -24,15 +24,15 @@ dealloc(Cursor* self) {
|
||||
|
||||
#define EQ(x) (a->x == b->x)
|
||||
static int __eq__(Cursor *a, Cursor *b) {
|
||||
return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink) && EQ(color);
|
||||
return EQ(bold) && EQ(italic) && EQ(strikethrough) && EQ(reverse) && EQ(decoration) && EQ(fg) && EQ(bg) && EQ(decoration_fg) && EQ(x) && EQ(y) && EQ(shape) && EQ(blink);
|
||||
}
|
||||
|
||||
#define BOOL(x) ((x) ? Py_True : Py_False)
|
||||
static PyObject *
|
||||
repr(Cursor *self) {
|
||||
return PyUnicode_FromFormat(
|
||||
"Cursor(x=%u, y=%u, shape=%d, blink=%R, color=#%08x, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)",
|
||||
self->x, self->y, self->shape, BOOL(self->blink), self->color, self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg
|
||||
"Cursor(x=%u, y=%u, shape=%d, blink=%R, fg=#%08x, bg=#%08x, bold=%R, italic=%R, reverse=%R, strikethrough=%R, decoration=%d, decoration_fg=#%08x)",
|
||||
self->x, self->y, self->shape, BOOL(self->blink), self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,12 +52,11 @@ void cursor_reset(Cursor *self) {
|
||||
cursor_reset_display_attrs(self);
|
||||
self->x = 0; self->y = 0;
|
||||
self->shape = 0; self->blink = false;
|
||||
self->color = 0;
|
||||
}
|
||||
|
||||
void cursor_copy_to(Cursor *src, Cursor *dest) {
|
||||
#define CCY(x) dest->x = src->x;
|
||||
CCY(x); CCY(y); CCY(shape); CCY(blink); CCY(color);
|
||||
CCY(x); CCY(y); CCY(shape); CCY(blink);
|
||||
CCY(bold); CCY(italic); CCY(strikethrough); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg);
|
||||
}
|
||||
|
||||
@@ -65,11 +64,6 @@ static PyObject*
|
||||
copy(Cursor *self);
|
||||
#define copy_doc "Create a clone of this cursor"
|
||||
|
||||
static PyObject* color_get(Cursor *self, void UNUSED *closure) {
|
||||
if (!(self->color & 0xFF)) { Py_RETURN_NONE; }
|
||||
return Py_BuildValue("BBB", (self->color >> 24) & 0xFF, (self->color >> 16) & 0xFF, (self->color >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
BOOL_GETSET(Cursor, bold)
|
||||
@@ -82,7 +76,6 @@ static PyMemberDef members[] = {
|
||||
{"x", T_UINT, offsetof(Cursor, x), 0, "x"},
|
||||
{"y", T_UINT, offsetof(Cursor, y), 0, "y"},
|
||||
{"shape", T_UBYTE, offsetof(Cursor, shape), 0, "shape"},
|
||||
{"color", T_ULONG, offsetof(Cursor, color), 0, "color"},
|
||||
{"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"},
|
||||
{"fg", T_ULONG, offsetof(Cursor, fg), 0, "fg"},
|
||||
{"bg", T_ULONG, offsetof(Cursor, bg), 0, "bg"},
|
||||
@@ -96,7 +89,6 @@ static PyGetSetDef getseters[] = {
|
||||
GETSET(reverse)
|
||||
GETSET(strikethrough)
|
||||
GETSET(blink)
|
||||
{"color", (getter) color_get, NULL, "color", NULL},
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
6
kitty/cursor_fragment.glsl
Normal file
6
kitty/cursor_fragment.glsl
Normal file
@@ -0,0 +1,6 @@
|
||||
uniform vec4 color;
|
||||
out vec4 final_color;
|
||||
|
||||
void main() {
|
||||
final_color = color;
|
||||
}
|
||||
16
kitty/cursor_vertex.glsl
Normal file
16
kitty/cursor_vertex.glsl
Normal file
@@ -0,0 +1,16 @@
|
||||
uniform vec2 xpos;
|
||||
uniform vec2 ypos;
|
||||
|
||||
const uvec2 pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
uvec2(0, 1), // left, bottom
|
||||
uvec2(0, 0) // left, top
|
||||
);
|
||||
|
||||
void main() {
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,17 @@ change_wcwidth_wrap(PyObject UNUSED *self, PyObject *use9) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
|
||||
char *devnull = NULL;
|
||||
if (!PyArg_ParseTuple(args, "s", &devnull)) return NULL;
|
||||
if (freopen(devnull, "r", stdin) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||
if (freopen(devnull, "w", stdout) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||
if (freopen(devnull, "w", stderr) == NULL) return PyErr_SetFromErrno(PyExc_EnvironmentError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
GL_METHODS
|
||||
{"drain_read", (PyCFunction)drain_read, METH_O, ""},
|
||||
@@ -38,8 +49,12 @@ static PyMethodDef module_methods[] = {
|
||||
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
|
||||
{"read_bytes", (PyCFunction)read_bytes, METH_VARARGS, ""},
|
||||
{"read_bytes_dump", (PyCFunction)read_bytes_dump, METH_VARARGS, ""},
|
||||
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
||||
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
|
||||
#ifndef __APPLE__
|
||||
{"get_fontconfig_font", (PyCFunction)get_fontconfig_font, METH_VARARGS, ""},
|
||||
#endif
|
||||
GLFW_FUNC_WRAPPERS
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
@@ -53,7 +68,9 @@ static struct PyModuleDef module = {
|
||||
.m_methods = module_methods
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
#include <termios.h>
|
||||
|
||||
EXPORTED PyMODINIT_FUNC
|
||||
PyInit_fast_data_types(void) {
|
||||
PyObject *m;
|
||||
|
||||
@@ -77,6 +94,7 @@ PyInit_fast_data_types(void) {
|
||||
#else
|
||||
if (!init_Face(m)) return NULL;
|
||||
if (!init_freetype_library(m)) return NULL;
|
||||
if (!init_fontconfig_library(m)) return NULL;
|
||||
#endif
|
||||
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
|
||||
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
|
||||
@@ -101,6 +119,9 @@ PyInit_fast_data_types(void) {
|
||||
PyModule_AddIntMacro(m, NORMAL_PROTOCOL);
|
||||
PyModule_AddIntMacro(m, URXVT_PROTOCOL);
|
||||
PyModule_AddIntMacro(m, UTF8_PROTOCOL);
|
||||
#ifdef IUTF8
|
||||
PyModule_AddIntMacro(m, IUTF8);
|
||||
#endif
|
||||
}
|
||||
|
||||
return m;
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include <Python.h>
|
||||
#define UNUSED __attribute__ ((unused))
|
||||
#define EXPORTED __attribute__ ((visibility ("default")))
|
||||
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
|
||||
#define MIN(x, y) (((x) > (y)) ? (y) : (x))
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
|
||||
typedef Py_UCS4 char_type;
|
||||
typedef uint64_t color_type;
|
||||
typedef uint32_t decoration_type;
|
||||
typedef uint32_t color_type;
|
||||
typedef uint32_t combining_type;
|
||||
typedef unsigned int index_type;
|
||||
|
||||
@@ -31,9 +33,9 @@ typedef unsigned int index_type;
|
||||
#define SGR_PROTOCOL 2
|
||||
#define URXVT_PROTOCOL 3
|
||||
|
||||
#define CELL_SIZE (sizeof(char_type) + sizeof(color_type) + sizeof(decoration_type) + sizeof(combining_type))
|
||||
// The data cell size must be a multiple of 3
|
||||
#define DATA_CELL_SIZE 2 * 3
|
||||
#define CELL_FIELD_COUNT 5
|
||||
#define CELL_SIZE (CELL_FIELD_COUNT * 4)
|
||||
#define DATA_CELL_SIZE 6
|
||||
|
||||
#define CHAR_MASK 0xFFFFFF
|
||||
#define ATTRS_SHIFT 24
|
||||
@@ -46,8 +48,6 @@ typedef unsigned int index_type;
|
||||
#define REVERSE_SHIFT 6
|
||||
#define STRIKE_SHIFT 7
|
||||
#define COL_MASK 0xFFFFFFFF
|
||||
#define COL_SHIFT 32
|
||||
#define HAS_BG_MASK (0xFF << COL_SHIFT)
|
||||
#define CC_MASK 0xFFFF
|
||||
#define CC_SHIFT 16
|
||||
#define UTF8_ACCEPT 0
|
||||
@@ -77,7 +77,8 @@ typedef unsigned int index_type;
|
||||
|
||||
#define COPY_CELL(src, s, dest, d) \
|
||||
(dest)->chars[d] = (src)->chars[s]; \
|
||||
(dest)->colors[d] = (src)->colors[s]; \
|
||||
(dest)->fg_colors[d] = (src)->fg_colors[s]; \
|
||||
(dest)->bg_colors[d] = (src)->bg_colors[s]; \
|
||||
(dest)->decoration_fg[d] = (src)->decoration_fg[s]; \
|
||||
(dest)->combining_chars[d] = (src)->combining_chars[s];
|
||||
|
||||
@@ -85,18 +86,17 @@ typedef unsigned int index_type;
|
||||
|
||||
#define COPY_LINE(src, dest) \
|
||||
memcpy((dest)->chars, (src)->chars, sizeof(char_type) * MIN((src)->xnum, (dest)->xnum)); \
|
||||
memcpy((dest)->colors, (src)->colors, sizeof(color_type) * MIN((src)->xnum, (dest)->xnum)); \
|
||||
memcpy((dest)->decoration_fg, (src)->decoration_fg, sizeof(decoration_type) * MIN((src)->xnum, (dest)->xnum)); \
|
||||
memcpy((dest)->fg_colors, (src)->fg_colors, sizeof(color_type) * MIN((src)->xnum, (dest)->xnum)); \
|
||||
memcpy((dest)->bg_colors, (src)->bg_colors, sizeof(color_type) * MIN((src)->xnum, (dest)->xnum)); \
|
||||
memcpy((dest)->decoration_fg, (src)->decoration_fg, sizeof(color_type) * MIN((src)->xnum, (dest)->xnum)); \
|
||||
memcpy((dest)->combining_chars, (src)->combining_chars, sizeof(combining_type) * MIN((src)->xnum, (dest)->xnum));
|
||||
|
||||
#define CLEAR_LINE(l, at, num) \
|
||||
for (index_type i = (at); i < (num); i++) (l)->chars[i] = (1 << ATTRS_SHIFT) | 32; \
|
||||
memset((l)->colors, (at), (num) * sizeof(color_type)); \
|
||||
memset((l)->decoration_fg, (at), (num) * sizeof(decoration_type)); \
|
||||
memset((l)->combining_chars, (at), (num) * sizeof(combining_type));
|
||||
|
||||
#define COLORS_TO_CURSOR(col, c) \
|
||||
c->fg = col & COL_MASK; c->bg = (col >> COL_SHIFT)
|
||||
#define CLEAR_LINE(l, num) \
|
||||
for (index_type i = 0; i < (num); i++) (l)->chars[i] = (1 << ATTRS_SHIFT) | 32; \
|
||||
memset((l)->fg_colors, 0, (num) * sizeof(color_type)); \
|
||||
memset((l)->bg_colors, 0, (num) * sizeof(color_type)); \
|
||||
memset((l)->decoration_fg, 0, (num) * sizeof(color_type)); \
|
||||
memset((l)->combining_chars, 0, (num) * sizeof(combining_type));
|
||||
|
||||
#define METHOD(name, arg_type) {#name, (PyCFunction)name, arg_type, name##_doc},
|
||||
|
||||
@@ -145,8 +145,9 @@ typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
char_type *chars;
|
||||
color_type *colors;
|
||||
decoration_type *decoration_fg;
|
||||
color_type *fg_colors;
|
||||
color_type *bg_colors;
|
||||
color_type *decoration_fg;
|
||||
combining_type *combining_chars;
|
||||
index_type xnum, ynum;
|
||||
bool continued;
|
||||
@@ -166,8 +167,9 @@ typedef struct {
|
||||
|
||||
// Pointers into buf
|
||||
char_type *chars;
|
||||
color_type *colors;
|
||||
decoration_type *decoration_fg;
|
||||
color_type *fg_colors;
|
||||
color_type *bg_colors;
|
||||
color_type *decoration_fg;
|
||||
combining_type *combining_chars;
|
||||
} LineBuf;
|
||||
PyTypeObject LineBuf_Type;
|
||||
@@ -175,7 +177,7 @@ PyTypeObject LineBuf_Type;
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
uint8_t *buf;
|
||||
uint32_t *buf;
|
||||
index_type xnum, ynum;
|
||||
Line *line;
|
||||
index_type start_of_data, count;
|
||||
@@ -188,7 +190,7 @@ typedef struct {
|
||||
bool bold, italic, reverse, strikethrough, blink;
|
||||
unsigned int x, y;
|
||||
uint8_t decoration, shape;
|
||||
unsigned long fg, bg, decoration_fg, color;
|
||||
unsigned long fg, bg, decoration_fg;
|
||||
|
||||
} Cursor;
|
||||
PyTypeObject Cursor_Type;
|
||||
@@ -244,8 +246,8 @@ PyTypeObject ChangeTracker_Type;
|
||||
|
||||
|
||||
typedef struct {
|
||||
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM,
|
||||
mBRACKETED_PASTE, mFOCUS_TRACKING;
|
||||
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM,
|
||||
mBRACKETED_PASTE, mFOCUS_TRACKING, mEXTENDED_KEYBOARD;
|
||||
unsigned long mouse_tracking_mode, mouse_tracking_protocol;
|
||||
} ScreenModes;
|
||||
PyTypeObject ScreenModes_Type;
|
||||
@@ -253,7 +255,7 @@ PyTypeObject ScreenModes_Type;
|
||||
#define SAVEPOINTS_SZ 256
|
||||
|
||||
typedef struct {
|
||||
uint32_t utf8_state, *g0_charset, *g1_charset, *g_charset;
|
||||
uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset;
|
||||
bool use_latin1;
|
||||
Cursor cursor;
|
||||
bool mDECOM, mDECAWM, mDECSCNM;
|
||||
@@ -274,7 +276,7 @@ typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
unsigned int columns, lines, margin_top, margin_bottom, charset;
|
||||
uint32_t utf8_state, *g0_charset, *g1_charset, *g_charset;
|
||||
uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset;
|
||||
bool use_latin1;
|
||||
Cursor *cursor;
|
||||
SavepointBuffer main_savepoints, alt_savepoints;
|
||||
@@ -289,6 +291,7 @@ typedef struct {
|
||||
unsigned int parser_state, parser_text_start, parser_buf_pos;
|
||||
bool parser_has_pending_text;
|
||||
uint8_t read_buf[READ_BUF_SZ];
|
||||
uint32_t default_fg, default_bg, highlight_fg, highlight_bg, cursor_color;
|
||||
|
||||
} Screen;
|
||||
PyTypeObject Screen_Type;
|
||||
@@ -373,6 +376,8 @@ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col)
|
||||
void screen_alignment_display(Screen *self);
|
||||
void screen_reverse_index(Screen *self);
|
||||
void screen_index(Screen *self);
|
||||
void screen_scroll(Screen *self, unsigned int count);
|
||||
void screen_reverse_scroll(Screen *self, unsigned int count);
|
||||
void screen_reset(Screen *self);
|
||||
void screen_set_tab_stop(Screen *self);
|
||||
void screen_tab(Screen *self);
|
||||
@@ -396,6 +401,7 @@ void screen_erase_characters(Screen *self, unsigned int count);
|
||||
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
|
||||
void screen_change_charset(Screen *, uint32_t to);
|
||||
void screen_designate_charset(Screen *, uint32_t which, uint32_t as);
|
||||
void screen_use_latin1(Screen *, bool);
|
||||
void set_title(Screen *self, PyObject*);
|
||||
void set_icon(Screen *self, PyObject*);
|
||||
void set_dynamic_color(Screen *self, unsigned int code, PyObject*);
|
||||
@@ -403,9 +409,10 @@ void set_color_table_color(Screen *self, unsigned int code, PyObject*);
|
||||
uint32_t* translation_table(uint32_t which);
|
||||
uint32_t *latin1_charset;
|
||||
void screen_request_capabilities(Screen *, PyObject *);
|
||||
void report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary);
|
||||
void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start_modifier);
|
||||
void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count);
|
||||
void report_device_status(Screen *self, unsigned int which, bool UNUSED);
|
||||
void report_mode_status(Screen *self, unsigned int which, bool);
|
||||
#define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
|
||||
DECLARE_CH_SCREEN_HANDLER(bell)
|
||||
DECLARE_CH_SCREEN_HANDLER(backspace)
|
||||
@@ -414,3 +421,5 @@ DECLARE_CH_SCREEN_HANDLER(linefeed)
|
||||
DECLARE_CH_SCREEN_HANDLER(carriage_return)
|
||||
|
||||
bool init_freetype_library(PyObject*);
|
||||
bool init_fontconfig_library(PyObject*);
|
||||
PyObject *get_fontconfig_font(PyObject *self, PyObject *args);
|
||||
|
||||
84
kitty/fontconfig.c
Normal file
84
kitty/fontconfig.c
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* fontconfig.c
|
||||
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
PyObject*
|
||||
get_fontconfig_font(PyObject UNUSED *self, PyObject *args) {
|
||||
char *family;
|
||||
int bold, italic, allow_bitmapped_fonts, index = 0, hint_style=0, weight=0, slant=0;
|
||||
double size_in_pts, dpi;
|
||||
unsigned int character;
|
||||
FcBool hinting, scalable, outline;
|
||||
FcChar8 *path = NULL;
|
||||
FcPattern *pat = NULL, *match = NULL;
|
||||
FcResult result;
|
||||
FcCharSet *charset = NULL;
|
||||
PyObject *ans = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "spppdId", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &character, &dpi)) return NULL;
|
||||
pat = FcPatternCreate();
|
||||
if (pat == NULL) return PyErr_NoMemory();
|
||||
|
||||
#define AP(func, which, in, desc) if (!func(pat, which, in)) { PyErr_Format(PyExc_RuntimeError, "Failed to add %s to fontconfig patter", desc, NULL); goto end; }
|
||||
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family");
|
||||
if (!allow_bitmapped_fonts) {
|
||||
AP(FcPatternAddBool, FC_OUTLINE, true, "outline");
|
||||
AP(FcPatternAddBool, FC_SCALABLE, true, "scalable");
|
||||
}
|
||||
if (size_in_pts > 0) { AP(FcPatternAddDouble, FC_SIZE, size_in_pts, "size"); }
|
||||
if (dpi > 0) { AP(FcPatternAddDouble, FC_DPI, dpi, "dpi"); }
|
||||
if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
|
||||
if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
|
||||
if (character > 0) {
|
||||
charset = FcCharSetCreate();
|
||||
if (charset == NULL) { PyErr_NoMemory(); goto end; }
|
||||
if (!FcCharSetAddChar(charset, character)) { PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset"); goto end; }
|
||||
AP(FcPatternAddCharSet, FC_CHARSET, charset, "charset");
|
||||
}
|
||||
#undef AP
|
||||
FcConfigSubstitute(NULL, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
match = FcFontMatch(NULL, pat, &result);
|
||||
if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; }
|
||||
|
||||
#define GI(func, which, out, desc) \
|
||||
if (func(match, which, 0, & out) != FcResultMatch) { \
|
||||
PyErr_Format(PyExc_RuntimeError, "Failed to get %s from match object", desc, NULL); goto end; \
|
||||
}
|
||||
|
||||
GI(FcPatternGetString, FC_FILE, path, "file path");
|
||||
GI(FcPatternGetInteger, FC_INDEX, index, "face index");
|
||||
GI(FcPatternGetInteger, FC_WEIGHT, weight, "weight");
|
||||
GI(FcPatternGetInteger, FC_SLANT, slant, "slant");
|
||||
GI(FcPatternGetInteger, FC_HINT_STYLE, hint_style, "hint style");
|
||||
GI(FcPatternGetBool, FC_HINTING, hinting, "hinting");
|
||||
GI(FcPatternGetBool, FC_SCALABLE, scalable, "scalable");
|
||||
GI(FcPatternGetBool, FC_OUTLINE, outline, "outline");
|
||||
#undef GI
|
||||
|
||||
#define BP(x) (x ? Py_True : Py_False)
|
||||
ans = Py_BuildValue("siiOOOii", path, index, hint_style, BP(hinting), BP(scalable), BP(outline), weight, slant);
|
||||
#undef BP
|
||||
|
||||
end:
|
||||
if (pat != NULL) FcPatternDestroy(pat);
|
||||
if (match != NULL) FcPatternDestroy(match);
|
||||
if (charset != NULL) FcCharSetDestroy(charset);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
return ans;
|
||||
}
|
||||
|
||||
bool
|
||||
init_fontconfig_library(PyObject UNUSED *m) {
|
||||
if (!FcInit()) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize the fontconfig library");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ def draw_hline(buf, width, x1, x2, y, level):
|
||||
|
||||
|
||||
def draw_vline(buf, width, y1, y2, x, level):
|
||||
' Draw a horizontal line between [y1, y2) centered at x with the thickness given by level '
|
||||
' Draw a vertical line between [y1, y2) centered at x with the thickness given by level '
|
||||
sz = thickness(level=level, horizontal=True)
|
||||
start = x - sz // 2
|
||||
for x in range(start, start + sz):
|
||||
@@ -345,6 +345,15 @@ def render_box_char(ch, buf, width, height):
|
||||
return buf
|
||||
|
||||
|
||||
def render_missing_glyph(buf, width, height):
|
||||
hgap = thickness(level=0, horizontal=True) + 1
|
||||
vgap = thickness(level=0, horizontal=False) + 1
|
||||
draw_hline(buf, width, hgap, width - hgap + 1, vgap, 0)
|
||||
draw_hline(buf, width, hgap, width - hgap + 1, height - vgap, 0)
|
||||
draw_vline(buf, width, vgap, height - vgap + 1, hgap, 0)
|
||||
draw_vline(buf, width, vgap, height - vgap + 1, width - hgap, 0)
|
||||
|
||||
|
||||
def join_rows(width, height, rows):
|
||||
import ctypes
|
||||
ans = (ctypes.c_ubyte * (width * height * len(rows)))()
|
||||
|
||||
@@ -7,10 +7,19 @@ from kitty.fast_data_types import CTFace as Face
|
||||
from kitty.utils import get_logical_dpi, wcwidth, ceil_int
|
||||
|
||||
main_font = {}
|
||||
symbol_map = {}
|
||||
cell_width = cell_height = baseline = CellTexture = WideCellTexture = underline_thickness = underline_position = None
|
||||
|
||||
|
||||
def set_font_family(opts, ignore_dpi_failure=False):
|
||||
def install_symbol_map(val, font_size, dpi):
|
||||
global symbol_map
|
||||
symbol_map = {}
|
||||
family_map = {f: Face(f, False, False, False, font_size, dpi) for f in set(val.values())}
|
||||
for ch, family in val.items():
|
||||
symbol_map[ch] = family_map[family]
|
||||
|
||||
|
||||
def set_font_family(opts, override_font_size=None, ignore_dpi_failure=False):
|
||||
global cell_width, cell_height, baseline, CellTexture, WideCellTexture, underline_thickness, underline_position
|
||||
try:
|
||||
dpi = get_logical_dpi()
|
||||
@@ -28,10 +37,12 @@ def set_font_family(opts, ignore_dpi_failure=False):
|
||||
if ans == 'auto' and (bold or italic):
|
||||
ans = get_family(False, False)
|
||||
return ans
|
||||
font_size = override_font_size or opts.font_size
|
||||
|
||||
for bold in (False, True):
|
||||
for italic in (False, True):
|
||||
main_font[(bold, italic)] = Face(get_family(bold, italic), bold, italic, True, opts.font_size, dpi)
|
||||
main_font[(bold, italic)] = Face(get_family(bold, italic), bold, italic, True, font_size, dpi)
|
||||
install_symbol_map(opts.symbol_map, font_size, dpi)
|
||||
mf = main_font[(False, False)]
|
||||
cell_width, cell_height = mf.cell_size()
|
||||
CellTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||
@@ -57,8 +68,9 @@ def split(buf, cell_width, cell_height):
|
||||
|
||||
|
||||
def render_cell(text=' ', bold=False, italic=False):
|
||||
width = wcwidth(text[0])
|
||||
face = main_font[(bold, italic)]
|
||||
ch = text[0]
|
||||
width = wcwidth(ch)
|
||||
face = symbol_map.get(ch) or main_font[(bold, italic)]
|
||||
if width == 2:
|
||||
buf, width = WideCellTexture(), cell_width * 2
|
||||
else:
|
||||
|
||||
@@ -2,57 +2,145 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from collections import namedtuple
|
||||
from functools import lru_cache
|
||||
from kitty.fast_data_types import Face
|
||||
|
||||
from kitty.fast_data_types import Face, get_fontconfig_font
|
||||
|
||||
|
||||
def escape_family_name(name):
|
||||
return re.sub(r'([-:,\\])', lambda m: '\\' + m.group(1), name)
|
||||
|
||||
|
||||
Font = namedtuple('Font', 'face hinting hintstyle bold italic')
|
||||
Font = namedtuple(
|
||||
'Font',
|
||||
'face hinting hintstyle bold italic scalable outline weight slant index'
|
||||
)
|
||||
|
||||
|
||||
def get_font(query, bold, italic):
|
||||
query += ':scalable=true:outline=true'
|
||||
raw = subprocess.check_output(['fc-match', query, '-f', '%{file}\x1e%{hinting}\x1e%{hintstyle}']).decode('utf-8')
|
||||
class FontNotFound(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def to_bool(x):
|
||||
return x.lower() == 'true'
|
||||
|
||||
|
||||
def font_not_found(err, char):
|
||||
msg = 'Failed to find font'
|
||||
if char is not None:
|
||||
msg = 'Failed to find font for character U+{:X}, error from fontconfig: {}'. format(ord(char[0]), err)
|
||||
return FontNotFound(msg)
|
||||
|
||||
|
||||
def get_font_subprocess(
|
||||
family='monospace',
|
||||
bold=False,
|
||||
italic=False,
|
||||
allow_bitmaped_fonts=False,
|
||||
size_in_pts=None,
|
||||
character=None,
|
||||
dpi=None
|
||||
):
|
||||
query = escape_family_name(family)
|
||||
if character is not None:
|
||||
query += ':charset={:x}'.format(ord(character[0]))
|
||||
if not allow_bitmaped_fonts:
|
||||
query += ':scalable=true:outline=true'
|
||||
if size_in_pts is not None:
|
||||
query += ':size={:.1f}'.format(size_in_pts)
|
||||
if dpi is not None:
|
||||
query += ':dpi={:.1f}'.format(dpi)
|
||||
if bold:
|
||||
query += ':weight=200'
|
||||
if italic:
|
||||
query += ':slant=100'
|
||||
try:
|
||||
raw = subprocess.check_output([
|
||||
'fc-match', query, '-f',
|
||||
'%{file}\x1e%{hinting}\x1e%{hintstyle}\x1e%{scalable}\x1e%{outline}\x1e%{weight}\x1e%{slant}\x1e%{index}'
|
||||
]).decode('utf-8')
|
||||
except subprocess.CalledProcessError as err:
|
||||
raise font_not_found(err, character)
|
||||
parts = raw.split('\x1e')
|
||||
hintstyle, hinting = 1, 'True'
|
||||
if len(parts) == 3:
|
||||
path, hinting, hintstyle = parts
|
||||
else:
|
||||
try:
|
||||
path, hinting, hintstyle, scalable, outline, weight, slant, index = parts
|
||||
except ValueError:
|
||||
path = parts[0]
|
||||
hinting = hinting.lower() == 'true'
|
||||
hintstyle = int(hintstyle)
|
||||
return Font(path, hinting, hintstyle, bold, italic)
|
||||
hintstyle, hinting, scalable, outline, weight, slant, index = 1, 'True', 'True', 'True', 100, 0, 0
|
||||
return Font(
|
||||
path,
|
||||
to_bool(hinting),
|
||||
int(hintstyle), bold, italic,
|
||||
to_bool(scalable),
|
||||
to_bool(outline), int(weight), int(slant), int(index)
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=4096)
|
||||
def find_font_for_character(family, char, bold=False, italic=False):
|
||||
q = escape_family_name(family) + ':charset={}'.format(hex(ord(char[0]))[2:])
|
||||
if bold:
|
||||
q += ':weight=200'
|
||||
if italic:
|
||||
q += ':slant=100'
|
||||
return get_font(q, bold, italic)
|
||||
def get_font_lib(
|
||||
family='monospace',
|
||||
bold=False,
|
||||
italic=False,
|
||||
allow_bitmaped_fonts=False,
|
||||
size_in_pts=None,
|
||||
character=None,
|
||||
dpi=None
|
||||
):
|
||||
try:
|
||||
path, index, hintstyle, hinting, scalable, outline, weight, slant = get_fontconfig_font(
|
||||
family, bold, italic, allow_bitmaped_fonts, size_in_pts or 0,
|
||||
0 if character is None else ord(character[0]), dpi or 0
|
||||
)
|
||||
except KeyError as err:
|
||||
raise font_not_found(err, character)
|
||||
|
||||
return Font(
|
||||
path, hinting, hintstyle, bold, italic, scalable, outline, weight,
|
||||
slant, index
|
||||
)
|
||||
|
||||
|
||||
@lru_cache(maxsize=64)
|
||||
def get_font_information(q, bold=False, italic=False):
|
||||
q = escape_family_name(q)
|
||||
if bold:
|
||||
q += ':weight=200'
|
||||
if italic:
|
||||
q += ':slant=100'
|
||||
return get_font(q, bold, italic)
|
||||
get_font = get_font_lib
|
||||
|
||||
|
||||
def find_font_for_character(
|
||||
family,
|
||||
char,
|
||||
bold=False,
|
||||
italic=False,
|
||||
allow_bitmaped_fonts=False,
|
||||
size_in_pts=None,
|
||||
dpi=None
|
||||
):
|
||||
ans = get_font(
|
||||
family,
|
||||
bold,
|
||||
italic,
|
||||
character=char,
|
||||
allow_bitmaped_fonts=allow_bitmaped_fonts,
|
||||
size_in_pts=size_in_pts,
|
||||
dpi=dpi
|
||||
)
|
||||
if not ans.face or not os.path.exists(ans.face):
|
||||
raise FontNotFound(
|
||||
'Failed to find font for character U+{:X}'.format(ord(char[0]))
|
||||
)
|
||||
return ans
|
||||
|
||||
|
||||
def get_font_information(family, bold=False, italic=False):
|
||||
return get_font(family, bold, italic)
|
||||
|
||||
|
||||
def get_font_files(opts):
|
||||
ans = {}
|
||||
attr_map = {'bold': 'bold_font', 'italic': 'italic_font', 'bi': 'bold_italic_font'}
|
||||
attr_map = {
|
||||
'bold': 'bold_font',
|
||||
'italic': 'italic_font',
|
||||
'bi': 'bold_italic_font'
|
||||
}
|
||||
|
||||
def get_family(key=None):
|
||||
ans = getattr(opts, attr_map.get(key, 'font_family'))
|
||||
@@ -61,11 +149,21 @@ def get_font_files(opts):
|
||||
return ans
|
||||
|
||||
n = get_font_information(get_family())
|
||||
ans['regular'] = Font(Face(n.face), n.hinting, n.hintstyle, n.bold, n.italic)
|
||||
ans['regular'] = n._replace(face=Face(n.face, n.index))
|
||||
|
||||
def do(key):
|
||||
b = get_font_information(get_family(key), bold=key in ('bold', 'bi'), italic=key in ('italic', 'bi'))
|
||||
b = get_font_information(
|
||||
get_family(key),
|
||||
bold=key in ('bold', 'bi'),
|
||||
italic=key in ('italic', 'bi')
|
||||
)
|
||||
if b.face != n.face:
|
||||
ans[key] = Font(Face(b.face), b.hinting, b.hintstyle, b.bold, b.italic)
|
||||
ans[key] = b._replace(face=Face(b.face, b.index))
|
||||
|
||||
do('bold'), do('italic'), do('bi')
|
||||
return ans
|
||||
|
||||
|
||||
def font_for_family(family):
|
||||
ans = get_font_information(family)
|
||||
return ans._replace(face=Face(ans.face, ans.index))
|
||||
|
||||
@@ -2,22 +2,26 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import unicodedata
|
||||
import ctypes
|
||||
import sys
|
||||
import unicodedata
|
||||
from collections import namedtuple
|
||||
from functools import lru_cache
|
||||
from itertools import chain
|
||||
from threading import Lock
|
||||
|
||||
from kitty.fast_data_types import Face, FT_PIXEL_MODE_GRAY
|
||||
from kitty.utils import ceil_int
|
||||
from .fontconfig import find_font_for_character, get_font_files
|
||||
|
||||
from kitty.utils import get_logical_dpi, wcwidth
|
||||
from kitty.fast_data_types import FT_PIXEL_MODE_GRAY, Face
|
||||
from kitty.fonts.box_drawing import render_missing_glyph
|
||||
from kitty.utils import ceil_int, get_logical_dpi, safe_print, wcwidth
|
||||
|
||||
from .fontconfig import (
|
||||
FontNotFound, find_font_for_character, font_for_family, get_font_files
|
||||
)
|
||||
|
||||
current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None
|
||||
CharTexture = underline_position = underline_thickness = None
|
||||
alt_face_cache = {}
|
||||
symbol_map = {}
|
||||
|
||||
|
||||
def set_char_size(face, width=0, height=0, hres=0, vres=0):
|
||||
@@ -34,44 +38,81 @@ def calc_cell_width(font, face):
|
||||
ch = chr(i)
|
||||
load_char(font, face, ch)
|
||||
m = face.glyph_metrics()
|
||||
ans = max(ans, max(ceil_int(m.horiAdvance / 64), face.bitmap().width))
|
||||
ans = max(ans, ceil_int(m.horiAdvance / 64))
|
||||
return ans
|
||||
|
||||
|
||||
@lru_cache(maxsize=2**10)
|
||||
def font_for_char(char, bold=False, italic=False):
|
||||
return find_font_for_character(current_font_family_name, char, bold, italic)
|
||||
def font_for_char(char, bold=False, italic=False, allow_bitmaped_fonts=False):
|
||||
if allow_bitmaped_fonts:
|
||||
return find_font_for_character(
|
||||
current_font_family_name,
|
||||
char,
|
||||
bold,
|
||||
italic,
|
||||
allow_bitmaped_fonts=True,
|
||||
size_in_pts=cff_size['width'] / 64,
|
||||
dpi=(cff_size['hres'] + cff_size['vres']) / 2
|
||||
)
|
||||
return find_font_for_character(
|
||||
current_font_family_name, char, bold, italic
|
||||
)
|
||||
|
||||
|
||||
def install_symbol_map(val):
|
||||
global symbol_map
|
||||
symbol_map = {}
|
||||
family_map = {f: font_for_family(f) for f in set(val.values())}
|
||||
for ch, family in val.items():
|
||||
symbol_map[ch] = family_map[family]
|
||||
|
||||
|
||||
def font_units_to_pixels(x, units_per_em, size_in_pts, dpi):
|
||||
return ceil_int(x * ((size_in_pts * dpi) / (72 * units_per_em)))
|
||||
|
||||
|
||||
def set_font_family(opts):
|
||||
def set_font_family(opts, override_font_size=None):
|
||||
global current_font_family, current_font_family_name, cff_size, cell_width, cell_height, CharTexture, baseline
|
||||
global underline_position, underline_thickness
|
||||
size_in_pts = opts.font_size
|
||||
find_font_for_character.cache_clear()
|
||||
size_in_pts = override_font_size or opts.font_size
|
||||
current_font_family = get_font_files(opts)
|
||||
current_font_family_name = opts.font_family
|
||||
dpi = get_logical_dpi()
|
||||
cff_size = ceil_int(64 * size_in_pts)
|
||||
cff_size = {'width': cff_size, 'height': cff_size, 'hres': int(dpi[0]), 'vres': int(dpi[1])}
|
||||
for fobj in current_font_family.values():
|
||||
cff_size = {
|
||||
'width': cff_size,
|
||||
'height': cff_size,
|
||||
'hres': int(dpi[0]),
|
||||
'vres': int(dpi[1])
|
||||
}
|
||||
install_symbol_map(opts.symbol_map)
|
||||
for fobj in chain(current_font_family.values(), symbol_map.values()):
|
||||
set_char_size(fobj.face, **cff_size)
|
||||
face = current_font_family['regular'].face
|
||||
cell_width = calc_cell_width(current_font_family['regular'], face)
|
||||
cell_height = font_units_to_pixels(face.height, face.units_per_EM, size_in_pts, dpi[1])
|
||||
baseline = font_units_to_pixels(face.ascender, face.units_per_EM, size_in_pts, dpi[1])
|
||||
underline_position = min(baseline - font_units_to_pixels(face.underline_position, face.units_per_EM, size_in_pts, dpi[1]), cell_height - 1)
|
||||
underline_thickness = font_units_to_pixels(face.underline_thickness, face.units_per_EM, size_in_pts, dpi[1])
|
||||
cell_height = font_units_to_pixels(
|
||||
face.height, face.units_per_EM, size_in_pts, dpi[1]
|
||||
)
|
||||
baseline = font_units_to_pixels(
|
||||
face.ascender, face.units_per_EM, size_in_pts, dpi[1]
|
||||
)
|
||||
underline_position = min(
|
||||
baseline - font_units_to_pixels(
|
||||
face.underline_position, face.units_per_EM, size_in_pts, dpi[1]
|
||||
), cell_height - 1
|
||||
)
|
||||
underline_thickness = font_units_to_pixels(
|
||||
face.underline_thickness, face.units_per_EM, size_in_pts, dpi[1]
|
||||
)
|
||||
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||
font_for_char.cache_clear()
|
||||
alt_face_cache.clear()
|
||||
return cell_width, cell_height
|
||||
|
||||
|
||||
CharBitmap = namedtuple('CharBitmap', 'data bearingX bearingY advance rows columns')
|
||||
CharBitmap = namedtuple(
|
||||
'CharBitmap', 'data bearingX bearingY advance rows columns'
|
||||
)
|
||||
freetype_lock = Lock()
|
||||
|
||||
|
||||
@@ -80,45 +121,66 @@ def render_to_bitmap(font, face, text):
|
||||
bitmap = face.bitmap()
|
||||
if bitmap.pixel_mode != FT_PIXEL_MODE_GRAY:
|
||||
raise ValueError(
|
||||
'FreeType rendered the glyph for {!r} with an unsupported pixel mode: {}'.format(text, bitmap.pixel_mode))
|
||||
'FreeType rendered the glyph for {!r} with an unsupported pixel mode: {}'.
|
||||
format(text, bitmap.pixel_mode)
|
||||
)
|
||||
return bitmap
|
||||
|
||||
|
||||
def render_using_face(font, face, text, width, italic, bold):
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
if width == 1 and bitmap.width > cell_width:
|
||||
extra = bitmap.width - cell_width
|
||||
if italic and extra < cell_width // 2:
|
||||
bitmap = face.trim_to_width(bitmap, cell_width)
|
||||
elif extra > max(2, 0.3 * cell_width) and face.is_scalable:
|
||||
# rescale the font size so that the glyph is visible in a single
|
||||
# cell and hope somebody updates libc's wcwidth
|
||||
sz = cff_size.copy()
|
||||
sz['width'] = int(sz['width'] * cell_width / bitmap.width)
|
||||
# Preserve aspect ratio
|
||||
sz['height'] = int(sz['height'] * cell_width / bitmap.width)
|
||||
try:
|
||||
set_char_size(face, **sz)
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
finally:
|
||||
set_char_size(face, **cff_size)
|
||||
m = face.glyph_metrics()
|
||||
return CharBitmap(
|
||||
bitmap.buffer,
|
||||
ceil_int(abs(m.horiBearingX) / 64),
|
||||
ceil_int(abs(m.horiBearingY) / 64),
|
||||
ceil_int(m.horiAdvance / 64), bitmap.rows, bitmap.width
|
||||
)
|
||||
|
||||
|
||||
def render_char(text, bold=False, italic=False, width=1):
|
||||
key = 'regular'
|
||||
if bold:
|
||||
key = 'bi' if italic else 'bold'
|
||||
elif italic:
|
||||
key = 'italic'
|
||||
ch = text[0]
|
||||
with freetype_lock:
|
||||
font = current_font_family.get(key) or current_font_family['regular']
|
||||
face = font.face
|
||||
if not face.get_char_index(text[0]):
|
||||
font = font_for_char(text[0], bold, italic)
|
||||
face = alt_face_cache.get(font)
|
||||
if face is None:
|
||||
face = alt_face_cache[font] = Face(font.face)
|
||||
set_char_size(face, **cff_size)
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
if width == 1 and bitmap.width > cell_width:
|
||||
extra = bitmap.width - cell_width
|
||||
if italic and extra < cell_width // 2:
|
||||
bitmap = face.trim_to_width(bitmap, cell_width)
|
||||
elif extra > max(2, 0.1 * cell_width):
|
||||
# rescale the font size so that the glyph is visible in a single
|
||||
# cell and hope somebody updates libc's wcwidth
|
||||
sz = cff_size.copy()
|
||||
sz['width'] = int(sz['width'] * cell_width / bitmap.width)
|
||||
# Preserve aspect ratio
|
||||
sz['height'] = int(sz['height'] * cell_width / bitmap.width)
|
||||
font = symbol_map.get(ch)
|
||||
if font is None or not font.face.get_char_index(ch):
|
||||
font = current_font_family.get(key) or current_font_family['regular']
|
||||
face = font.face
|
||||
if not face.get_char_index(ch):
|
||||
try:
|
||||
set_char_size(face, **sz)
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
finally:
|
||||
set_char_size(face, **cff_size)
|
||||
m = face.glyph_metrics()
|
||||
return CharBitmap(bitmap.buffer, ceil_int(abs(m.horiBearingX) / 64),
|
||||
ceil_int(abs(m.horiBearingY) / 64), ceil_int(m.horiAdvance / 64), bitmap.rows, bitmap.width)
|
||||
font = font_for_char(ch, bold, italic)
|
||||
except FontNotFound:
|
||||
font = font_for_char(
|
||||
ch, bold, italic, allow_bitmaped_fonts=True
|
||||
)
|
||||
face = alt_face_cache.get(font)
|
||||
if face is None:
|
||||
face = alt_face_cache[font] = Face(font.face, font.index)
|
||||
if face.is_scalable:
|
||||
set_char_size(face, **cff_size)
|
||||
else:
|
||||
face = font.face
|
||||
return render_using_face(font, face, text, width, italic, bold)
|
||||
|
||||
|
||||
def place_char_in_cell(bitmap_char):
|
||||
@@ -133,7 +195,9 @@ def place_char_in_cell(bitmap_char):
|
||||
extra = dest_start_column + bitmap_char.columns - cell_width
|
||||
if extra > 0:
|
||||
dest_start_column -= extra
|
||||
column_count = min(bitmap_char.columns - src_start_column, cell_width - dest_start_column)
|
||||
column_count = min(
|
||||
bitmap_char.columns - src_start_column, cell_width - dest_start_column
|
||||
)
|
||||
|
||||
# Calculate row bounds, making sure the baseline is aligned with the cell
|
||||
# baseline
|
||||
@@ -141,9 +205,13 @@ def place_char_in_cell(bitmap_char):
|
||||
src_start_row, dest_start_row = bitmap_char.bearingY - baseline, 0
|
||||
else:
|
||||
src_start_row, dest_start_row = 0, baseline - bitmap_char.bearingY
|
||||
row_count = min(bitmap_char.rows - src_start_row, cell_height - dest_start_row)
|
||||
return create_cell_buffer(bitmap_char, src_start_row, dest_start_row, row_count,
|
||||
src_start_column, dest_start_column, column_count)
|
||||
row_count = min(
|
||||
bitmap_char.rows - src_start_row, cell_height - dest_start_row
|
||||
)
|
||||
return create_cell_buffer(
|
||||
bitmap_char, src_start_row, dest_start_row, row_count,
|
||||
src_start_column, dest_start_column, column_count
|
||||
)
|
||||
|
||||
|
||||
def split_char_bitmap(bitmap_char):
|
||||
@@ -168,12 +236,31 @@ def current_cell():
|
||||
return CharTexture, cell_width, cell_height, baseline, underline_thickness, underline_position
|
||||
|
||||
|
||||
@lru_cache(maxsize=8)
|
||||
def missing_glyph(width):
|
||||
w = cell_width * width
|
||||
ans = bytearray(w * cell_height)
|
||||
render_missing_glyph(ans, w, cell_height)
|
||||
first, second = CharBitmap(ans, 0, 0, 0, cell_height, w), None
|
||||
if width == 2:
|
||||
first, second = split_char_bitmap(first)
|
||||
second = create_cell_buffer(
|
||||
second, 0, 0, second.rows, 0, 0, second.columns
|
||||
)
|
||||
first = create_cell_buffer(first, 0, 0, first.rows, 0, 0, first.columns)
|
||||
return first, second
|
||||
|
||||
|
||||
def render_cell(text=' ', bold=False, italic=False):
|
||||
# TODO: Handle non-normalizable combining chars. Probably need to use
|
||||
# harfbuzz for that
|
||||
text = unicodedata.normalize('NFC', text)[0]
|
||||
width = wcwidth(text)
|
||||
bitmap_char = render_char(text, bold, italic, width)
|
||||
try:
|
||||
bitmap_char = render_char(text, bold, italic, width)
|
||||
except FontNotFound as err:
|
||||
safe_print('ERROR:', err, file=sys.stderr)
|
||||
return missing_glyph(width)
|
||||
second = None
|
||||
if width == 2:
|
||||
if bitmap_char.columns > cell_width:
|
||||
@@ -187,11 +274,16 @@ def render_cell(text=' ', bold=False, italic=False):
|
||||
return first, second
|
||||
|
||||
|
||||
def create_cell_buffer(bitmap_char, src_start_row, dest_start_row, row_count, src_start_column, dest_start_column, column_count):
|
||||
def create_cell_buffer(
|
||||
bitmap_char, src_start_row, dest_start_row, row_count, src_start_column,
|
||||
dest_start_column, column_count
|
||||
):
|
||||
src = bitmap_char.data
|
||||
src_stride = bitmap_char.columns
|
||||
dest = CharTexture()
|
||||
for r in range(row_count):
|
||||
sr, dr = src_start_column + (src_start_row + r) * src_stride, dest_start_column + (dest_start_row + r) * cell_width
|
||||
sr, dr = src_start_column + (
|
||||
src_start_row + r
|
||||
) * src_stride, dest_start_column + (dest_start_row + r) * cell_width
|
||||
dest[dr:dr + column_count] = src[sr:sr + column_count]
|
||||
return dest
|
||||
|
||||
@@ -15,6 +15,7 @@ typedef struct {
|
||||
FT_Face face;
|
||||
unsigned int units_per_EM;
|
||||
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness;
|
||||
bool is_scalable;
|
||||
} Face;
|
||||
|
||||
|
||||
@@ -56,18 +57,20 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
Face *self;
|
||||
char *path;
|
||||
int error;
|
||||
long index;
|
||||
/* unsigned int columns=80, lines=24, scrollback=0; */
|
||||
if (!PyArg_ParseTuple(args, "s", &path)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "sl", &path, &index)) return NULL;
|
||||
|
||||
self = (Face *)type->tp_alloc(type, 0);
|
||||
if (self != NULL) {
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
error = FT_New_Face(library, path, 0, &(self->face));
|
||||
error = FT_New_Face(library, path, index, &(self->face));
|
||||
Py_END_ALLOW_THREADS;
|
||||
if(error) { set_freetype_error("Failed to load face, with error:", error); Py_CLEAR(self); return NULL; }
|
||||
#define CPY(n) self->n = self->face->n;
|
||||
CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness);
|
||||
#undef CPY
|
||||
self->is_scalable = FT_IS_SCALABLE(self->face);
|
||||
}
|
||||
return (PyObject*)self;
|
||||
}
|
||||
@@ -137,7 +140,7 @@ static PyStructSequence_Field gm_fields[] = {
|
||||
};
|
||||
|
||||
static PyStructSequence_Desc gm_desc = {"GlpyhMetrics", NULL, gm_fields, 8};
|
||||
static PyTypeObject GlpyhMetricsType = {0};
|
||||
static PyTypeObject GlpyhMetricsType = {{{0}}};
|
||||
|
||||
static PyObject*
|
||||
glyph_metrics(Face *self) {
|
||||
@@ -164,7 +167,7 @@ static PyStructSequence_Field bm_fields[] = {
|
||||
{NULL}
|
||||
};
|
||||
static PyStructSequence_Desc bm_desc = {"Bitmap", NULL, bm_fields, 7};
|
||||
static PyTypeObject BitmapType = {0};
|
||||
static PyTypeObject BitmapType = {{{0}}};
|
||||
|
||||
static PyObject*
|
||||
bitmap(Face *self) {
|
||||
@@ -236,6 +239,7 @@ static PyMemberDef members[] = {
|
||||
MEM(max_advance_height, T_INT),
|
||||
MEM(underline_position, T_INT),
|
||||
MEM(underline_thickness, T_INT),
|
||||
MEM(is_scalable, T_BOOL),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
151
kitty/gl.h
151
kitty/gl.h
@@ -84,6 +84,16 @@ Uniform2ui(PyObject UNUSED *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
Uniform2i(PyObject UNUSED *self, PyObject *args) {
|
||||
int location;
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "iii", &location, &x, &y)) return NULL;
|
||||
glUniform2i(location, x, y);
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
Uniform1i(PyObject UNUSED *self, PyObject *args) {
|
||||
int location;
|
||||
@@ -141,10 +151,13 @@ _glewInit(PyObject UNUSED *self) {
|
||||
PyErr_Format(PyExc_RuntimeError, "GLEW init failed: %s", glewGetErrorString(err));
|
||||
return NULL;
|
||||
}
|
||||
if(!GLEW_ARB_texture_storage) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "OpenGL is missing the required ARB_texture_storage extension");
|
||||
return NULL;
|
||||
#define ARB_TEST(name) \
|
||||
if (!GLEW_ARB_##name) { \
|
||||
PyErr_Format(PyExc_RuntimeError, "The OpenGL driver on this system is missing the required extension: ARB_%s", #name); \
|
||||
return NULL; \
|
||||
}
|
||||
ARB_TEST(texture_storage);
|
||||
#undef ARB_TEST
|
||||
#endif
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -524,29 +537,37 @@ GetTexImage(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
NamedBufferData(PyObject UNUSED *self, PyObject *args) {
|
||||
int usage;
|
||||
unsigned long size, target;
|
||||
GetBufferSubData(PyObject UNUSED *self, PyObject *args) {
|
||||
int buftype;
|
||||
unsigned long size, target, offset;
|
||||
PyObject *address;
|
||||
if (!PyArg_ParseTuple(args, "kkO!i", &target, &size, &PyLong_Type, &address, &usage)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "ikkkO!", &buftype, &target, &size, &offset, &PyLong_Type, &address)) return NULL;
|
||||
void *data = PyLong_AsVoidPtr(address);
|
||||
if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; }
|
||||
glBindBuffer(buftype, target);
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
#ifdef glNamedBufferData
|
||||
#ifdef __APPLE__
|
||||
if(false) {
|
||||
#else
|
||||
if(GLEW_VERSION_4_5) {
|
||||
#endif
|
||||
glNamedBufferData(target, size, data, usage);
|
||||
} else {
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, target); glBufferData(GL_TEXTURE_BUFFER, size, data, usage); glBindBuffer(GL_TEXTURE_BUFFER, 0);
|
||||
}
|
||||
#else
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, target); glBufferData(GL_TEXTURE_BUFFER, size, data, usage); glBindBuffer(GL_TEXTURE_BUFFER, 0);
|
||||
#endif
|
||||
glGetBufferSubData(buftype, offset, size, data);
|
||||
Py_END_ALLOW_THREADS;
|
||||
CHECK_ERROR;
|
||||
glBindBuffer(buftype, 0);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
replace_or_create_buffer(PyObject UNUSED *self, PyObject *args) {
|
||||
int usage, buftype;
|
||||
unsigned long size, prev_sz, target;
|
||||
PyObject *address;
|
||||
if (!PyArg_ParseTuple(args, "kkkO!ii", &target, &size, &prev_sz, &PyLong_Type, &address, &usage, &buftype)) return NULL;
|
||||
void *data = PyLong_AsVoidPtr(address);
|
||||
if (data == NULL) { PyErr_SetString(PyExc_TypeError, "Not a valid data pointer"); return NULL; }
|
||||
glBindBuffer(buftype, target);
|
||||
Py_BEGIN_ALLOW_THREADS;
|
||||
if (prev_sz == 0 || prev_sz != size) glBufferData(buftype, size, data, usage);
|
||||
else glBufferSubData(buftype, 0, size, data);
|
||||
Py_END_ALLOW_THREADS;
|
||||
CHECK_ERROR;
|
||||
glBindBuffer(buftype, 0);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -602,6 +623,14 @@ DeleteBuffer(PyObject UNUSED *self, PyObject *val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
DeleteVertexArray(PyObject UNUSED *self, PyObject *val) {
|
||||
GLuint tex_id = (GLuint)PyLong_AsUnsignedLong(val);
|
||||
glDeleteVertexArrays(1, &tex_id);
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
BlendFunc(PyObject UNUSED *self, PyObject *args) {
|
||||
int s, d;
|
||||
@@ -637,17 +666,70 @@ EnableVertexAttribArray(PyObject UNUSED *self, PyObject *val) {
|
||||
|
||||
static PyObject*
|
||||
VertexAttribPointer(PyObject UNUSED *self, PyObject *args) {
|
||||
unsigned int index, stride;
|
||||
int type=GL_FLOAT, normalized, size;
|
||||
void *offset;
|
||||
PyObject *l;
|
||||
if (!PyArg_ParseTuple(args, "IiipIO!", &index, &size, &type, &normalized, &stride, &PyLong_Type, &l)) return NULL;
|
||||
offset = PyLong_AsVoidPtr(l);
|
||||
glVertexAttribPointer(index, size, type, normalized, stride, offset);
|
||||
unsigned int index;
|
||||
int type, normalized, size, stride;
|
||||
unsigned long offset;
|
||||
if (!PyArg_ParseTuple(args, "Iiipik", &index, &size, &type, &normalized, &stride, &offset)) return NULL;
|
||||
switch(type) {
|
||||
case GL_BYTE:
|
||||
case GL_UNSIGNED_BYTE:
|
||||
case GL_SHORT:
|
||||
case GL_UNSIGNED_SHORT:
|
||||
case GL_INT:
|
||||
case GL_UNSIGNED_INT:
|
||||
glVertexAttribIPointer(index, size, type, stride, (GLvoid*)offset);
|
||||
break;
|
||||
default:
|
||||
glVertexAttribPointer(index, size, type, normalized ? GL_TRUE : GL_FALSE, stride, (GLvoid*)offset);
|
||||
break;
|
||||
}
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
VertexAttribDivisor(PyObject UNUSED *self, PyObject *args) {
|
||||
unsigned int index, divisor;
|
||||
if (!PyArg_ParseTuple(args, "II", &index, &divisor)) return NULL;
|
||||
glVertexAttribDivisor(index, divisor);
|
||||
CHECK_ERROR;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
Flush(PyObject UNUSED *self) {
|
||||
glFinish();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
Finish(PyObject UNUSED *self) {
|
||||
glFinish();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
check_for_extensions(PyObject UNUSED *self) {
|
||||
GLint n = 0, i, left = 2;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &n);
|
||||
bool texture_storage = false;
|
||||
#define CHECK(name) if (!name) { \
|
||||
if (strstr((const char*)ext, "GL_ARB_" #name) == (const char *)ext) { left--; name = true; } \
|
||||
}
|
||||
for (i = 0; i < n; i++) {
|
||||
const GLubyte *ext = glGetStringi(GL_EXTENSIONS, i);
|
||||
CHECK(texture_storage);
|
||||
if (left < 1) break;
|
||||
}
|
||||
#undef CHECK
|
||||
if (left > 0) {
|
||||
#define CHECK(name) if (!name) { PyErr_Format(PyExc_RuntimeError, "The OpenGL driver on this system is missing the required extension: GL_ARB_%s", #name); return NULL; }
|
||||
CHECK(texture_storage);
|
||||
#undef CHECK
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
int add_module_gl_constants(PyObject *module) {
|
||||
#define GLC(x) if (PyModule_AddIntConstant(module, #x, x) != 0) { PyErr_NoMemory(); return 0; }
|
||||
GLC(GL_VERSION);
|
||||
@@ -663,17 +745,17 @@ int add_module_gl_constants(PyObject *module) {
|
||||
GLC(GL_COMPILE_STATUS);
|
||||
GLC(GL_LINK_STATUS);
|
||||
GLC(GL_TEXTURE0); GLC(GL_TEXTURE1); GLC(GL_TEXTURE2); GLC(GL_TEXTURE3); GLC(GL_TEXTURE4); GLC(GL_TEXTURE5); GLC(GL_TEXTURE6); GLC(GL_TEXTURE7); GLC(GL_TEXTURE8);
|
||||
GLC(GL_MAX_ARRAY_TEXTURE_LAYERS);
|
||||
GLC(GL_MAX_ARRAY_TEXTURE_LAYERS); GLC(GL_TEXTURE_BINDING_BUFFER); GLC(GL_MAX_TEXTURE_BUFFER_SIZE);
|
||||
GLC(GL_MAX_TEXTURE_SIZE);
|
||||
GLC(GL_TEXTURE_2D_ARRAY);
|
||||
GLC(GL_LINEAR); GLC(GL_CLAMP_TO_EDGE); GLC(GL_NEAREST);
|
||||
GLC(GL_TEXTURE_MIN_FILTER); GLC(GL_TEXTURE_MAG_FILTER);
|
||||
GLC(GL_TEXTURE_WRAP_S); GLC(GL_TEXTURE_WRAP_T);
|
||||
GLC(GL_UNPACK_ALIGNMENT);
|
||||
GLC(GL_R8); GLC(GL_RED); GLC(GL_UNSIGNED_BYTE); GLC(GL_RGB32UI); GLC(GL_RGBA);
|
||||
GLC(GL_R8); GLC(GL_RED); GLC(GL_UNSIGNED_BYTE); GLC(GL_R32UI); GLC(GL_RGB32UI); GLC(GL_RGBA);
|
||||
GLC(GL_TEXTURE_BUFFER); GLC(GL_STATIC_DRAW); GLC(GL_STREAM_DRAW);
|
||||
GLC(GL_SRC_ALPHA); GLC(GL_ONE_MINUS_SRC_ALPHA);
|
||||
GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_ARRAY_BUFFER);
|
||||
GLC(GL_BLEND); GLC(GL_FLOAT); GLC(GL_UNSIGNED_INT); GLC(GL_ARRAY_BUFFER);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -681,13 +763,16 @@ int add_module_gl_constants(PyObject *module) {
|
||||
#define GL_METHODS \
|
||||
{"enable_automatic_opengl_error_checking", (PyCFunction)enable_automatic_error_checking, METH_O, NULL}, \
|
||||
{"copy_image_sub_data", (PyCFunction)copy_image_sub_data, METH_VARARGS, NULL}, \
|
||||
{"replace_or_create_buffer", (PyCFunction)replace_or_create_buffer, METH_VARARGS, NULL}, \
|
||||
{"glewInit", (PyCFunction)_glewInit, METH_NOARGS, NULL}, \
|
||||
{"check_for_extensions", (PyCFunction)check_for_extensions, METH_NOARGS, NULL}, \
|
||||
METH(Viewport, METH_VARARGS) \
|
||||
METH(CheckError, METH_NOARGS) \
|
||||
METH(ClearColor, METH_VARARGS) \
|
||||
METH(GetProgramiv, METH_VARARGS) \
|
||||
METH(GetShaderiv, METH_VARARGS) \
|
||||
METH(Uniform2ui, METH_VARARGS) \
|
||||
METH(Uniform2i, METH_VARARGS) \
|
||||
METH(Uniform1i, METH_VARARGS) \
|
||||
METH(Uniform2f, METH_VARARGS) \
|
||||
METH(Uniform4f, METH_VARARGS) \
|
||||
@@ -698,6 +783,7 @@ int add_module_gl_constants(PyObject *module) {
|
||||
METH(CompileShader, METH_O) \
|
||||
METH(DeleteTexture, METH_O) \
|
||||
METH(DeleteBuffer, METH_O) \
|
||||
METH(DeleteVertexArray, METH_O) \
|
||||
METH(GetString, METH_O) \
|
||||
METH(GetIntegerv, METH_O) \
|
||||
METH(Clear, METH_O) \
|
||||
@@ -714,6 +800,7 @@ int add_module_gl_constants(PyObject *module) {
|
||||
METH(Disable, METH_O) \
|
||||
METH(EnableVertexAttribArray, METH_O) \
|
||||
METH(VertexAttribPointer, METH_VARARGS) \
|
||||
METH(VertexAttribDivisor, METH_VARARGS) \
|
||||
METH(GetProgramInfoLog, METH_O) \
|
||||
METH(GetShaderInfoLog, METH_O) \
|
||||
METH(ActiveTexture, METH_O) \
|
||||
@@ -731,6 +818,8 @@ int add_module_gl_constants(PyObject *module) {
|
||||
METH(CopyImageSubData, METH_VARARGS) \
|
||||
METH(TexSubImage3D, METH_VARARGS) \
|
||||
METH(GetTexImage, METH_VARARGS) \
|
||||
METH(NamedBufferData, METH_VARARGS) \
|
||||
METH(GetBufferSubData, METH_VARARGS) \
|
||||
METH(BlendFunc, METH_VARARGS) \
|
||||
METH(Finish, METH_NOARGS) \
|
||||
METH(Flush, METH_NOARGS) \
|
||||
|
||||
|
||||
100
kitty/glfw.c
100
kitty/glfw.c
@@ -7,6 +7,10 @@
|
||||
#include "data-types.h"
|
||||
#include <structmember.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#if defined(__APPLE__)
|
||||
#define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#include <GLFW/glfw3native.h>
|
||||
#endif
|
||||
|
||||
#if GLFW_VERSION_MAJOR < 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR < 2)
|
||||
#error "glfw >= 3.2 required"
|
||||
@@ -93,7 +97,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
self = (Window *)type->tp_alloc(type, 0);
|
||||
if (self != NULL) {
|
||||
self->window = glfwCreateWindow(width, height, title, NULL, NULL);
|
||||
if (self->window == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create GLFWWindow"); return NULL; }
|
||||
if (self->window == NULL) { Py_CLEAR(self); PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; }
|
||||
for(i = 0; i < MAX_WINDOWS; i++) {
|
||||
if (window_weakrefs[i] == NULL) { window_weakrefs[i] = self; break; }
|
||||
}
|
||||
@@ -178,10 +182,8 @@ glfw_post_empty_event(PyObject UNUSED *self) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_get_physical_dpi(PyObject UNUSED *self) {
|
||||
GLFWmonitor *m = glfwGetPrimaryMonitor();
|
||||
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
|
||||
static PyObject*
|
||||
get_physical_dpi(GLFWmonitor *m) {
|
||||
int width = 0, height = 0;
|
||||
glfwGetMonitorPhysicalSize(m, &width, &height);
|
||||
if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor size"); return NULL; }
|
||||
@@ -192,6 +194,13 @@ glfw_get_physical_dpi(PyObject UNUSED *self) {
|
||||
return Py_BuildValue("ff", dpix, dpiy);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_get_physical_dpi(PyObject UNUSED *self) {
|
||||
GLFWmonitor *m = glfwGetPrimaryMonitor();
|
||||
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
|
||||
return get_physical_dpi(m);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
|
||||
int key, scancode;
|
||||
@@ -199,6 +208,18 @@ glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
|
||||
return Py_BuildValue("s", glfwGetKeyName(key, scancode));
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_init_hint_string(PyObject UNUSED *self, PyObject *args) {
|
||||
int hint_id;
|
||||
char *hint;
|
||||
if (!PyArg_ParseTuple(args, "is", &hint_id, &hint)) return NULL;
|
||||
#ifdef glfwInitHintString
|
||||
glfwInitHintString(hint_id, hint);
|
||||
#endif
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
// }}}
|
||||
|
||||
static void
|
||||
@@ -318,6 +339,64 @@ get_window_size(Window *self) {
|
||||
return Py_BuildValue("ii", w, h);
|
||||
}
|
||||
|
||||
static GLFWmonitor*
|
||||
current_monitor(GLFWwindow *window) {
|
||||
// Find the monitor that has the maximum overlap with this window
|
||||
int nmonitors, i;
|
||||
int wx, wy, ww, wh;
|
||||
int mx, my, mw, mh;
|
||||
int overlap = 0, bestoverlap = 0;
|
||||
GLFWmonitor *bestmonitor = NULL;
|
||||
GLFWmonitor **monitors = NULL;
|
||||
const GLFWvidmode *mode;
|
||||
|
||||
glfwGetWindowPos(window, &wx, &wy);
|
||||
glfwGetWindowSize(window, &ww, &wh);
|
||||
monitors = glfwGetMonitors(&nmonitors);
|
||||
if (monitors == NULL || nmonitors < 1) { PyErr_SetString(PyExc_ValueError, "No monitors connected"); return NULL; }
|
||||
|
||||
for (i = 0; i < nmonitors; i++) {
|
||||
mode = glfwGetVideoMode(monitors[i]);
|
||||
glfwGetMonitorPos(monitors[i], &mx, &my);
|
||||
mw = mode->width;
|
||||
mh = mode->height;
|
||||
|
||||
overlap =
|
||||
MAX(0, MIN(wx + ww, mx + mw) - MAX(wx, mx)) *
|
||||
MAX(0, MIN(wy + wh, my + mh) - MAX(wy, my));
|
||||
|
||||
if (bestoverlap < overlap || bestmonitor == NULL) {
|
||||
bestoverlap = overlap;
|
||||
bestmonitor = monitors[i];
|
||||
}
|
||||
}
|
||||
|
||||
return bestmonitor;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
current_monitor_dpi(Window *self) {
|
||||
GLFWmonitor *m = current_monitor(self->window);
|
||||
if (m == NULL) return NULL;
|
||||
return get_physical_dpi(m);
|
||||
}
|
||||
|
||||
#ifdef glfwRequestWindowAttention
|
||||
static PyObject*
|
||||
request_window_attention(Window *self) {
|
||||
glfwRequestWindowAttention(self->window);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __APPLE__
|
||||
static PyObject*
|
||||
cocoa_window_id(Window *self) {
|
||||
void *wid = glfwGetCocoaWindow(self->window);
|
||||
if (wid == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get native window handle"); return NULL; }
|
||||
return PyLong_FromVoidPtr(wid);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Boilerplate {{{
|
||||
#define MND(name, args) {#name, (PyCFunction)name, args, ""}
|
||||
@@ -329,6 +408,13 @@ static PyMethodDef methods[] = {
|
||||
MND(should_close, METH_NOARGS),
|
||||
MND(get_framebuffer_size, METH_NOARGS),
|
||||
MND(get_window_size, METH_NOARGS),
|
||||
MND(current_monitor_dpi, METH_NOARGS),
|
||||
#ifdef glfwRequestWindowAttention
|
||||
MND(request_window_attention, METH_NOARGS),
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
MND(cocoa_window_id, METH_NOARGS),
|
||||
#endif
|
||||
MND(set_should_close, METH_VARARGS),
|
||||
MND(set_input_mode, METH_VARARGS),
|
||||
MND(is_key_pressed, METH_VARARGS),
|
||||
@@ -374,6 +460,10 @@ init_glfw(PyObject *m) {
|
||||
PyEval_InitThreads();
|
||||
glfwSetErrorCallback(cb_error_callback);
|
||||
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
|
||||
#ifdef GLFW_X11_WM_CLASS_NAME
|
||||
ADDC(GLFW_X11_WM_CLASS_NAME)
|
||||
ADDC(GLFW_X11_WM_CLASS_CLASS)
|
||||
#endif
|
||||
ADDC(GLFW_RELEASE);
|
||||
ADDC(GLFW_PRESS);
|
||||
ADDC(GLFW_REPEAT);
|
||||
|
||||
18
kitty/glfw.h
18
kitty/glfw.h
@@ -17,6 +17,21 @@ PyObject* glfw_wait_events(PyObject UNUSED *self, PyObject*);
|
||||
PyObject* glfw_post_empty_event(PyObject UNUSED *self);
|
||||
PyObject* glfw_get_physical_dpi(PyObject UNUSED *self);
|
||||
PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
|
||||
PyObject* glfw_init_hint_string(PyObject UNUSED *self, PyObject *args);
|
||||
|
||||
#ifdef __APPLE__
|
||||
PyObject* cocoa_get_lang(PyObject UNUSED *self);
|
||||
PyObject* cocoa_make_window_resizable(PyObject UNUSED *self, PyObject *window_id);
|
||||
PyObject* cocoa_create_global_menu(PyObject UNUSED *self);
|
||||
PyObject* cocoa_update_title(PyObject UNUSED *self, PyObject *title);
|
||||
#define COCOA_FUNCS \
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, \
|
||||
{"cocoa_make_window_resizable", (PyCFunction)cocoa_make_window_resizable, METH_O, ""}, \
|
||||
{"cocoa_create_global_menu", (PyCFunction)cocoa_create_global_menu, METH_NOARGS, ""}, \
|
||||
{"cocoa_update_title", (PyCFunction)cocoa_update_title, METH_O, ""},
|
||||
#else
|
||||
#define COCOA_FUNCS
|
||||
#endif
|
||||
|
||||
#define GLFW_FUNC_WRAPPERS \
|
||||
{"glfw_set_error_callback", (PyCFunction)glfw_set_error_callback, METH_O, ""}, \
|
||||
@@ -28,4 +43,5 @@ PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
|
||||
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \
|
||||
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""}, \
|
||||
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \
|
||||
|
||||
{"glfw_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, \
|
||||
COCOA_FUNCS
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
#include "data-types.h"
|
||||
#include <structmember.h>
|
||||
|
||||
#define CELL_SIZE_H (CELL_SIZE + 1)
|
||||
#define CELL_FIELD_COUNT_H (CELL_FIELD_COUNT + 1)
|
||||
#define CELL_SIZE_H (CELL_FIELD_COUNT_H * 4)
|
||||
|
||||
static inline uint8_t*
|
||||
static inline uint32_t*
|
||||
start_of(HistoryBuf *self, index_type num) {
|
||||
// Pointer to the start of the line with index (buffer position) num
|
||||
return self->buf + CELL_SIZE_H * num * self->xnum;
|
||||
return self->buf + CELL_FIELD_COUNT_H * num * self->xnum;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
@@ -32,7 +33,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
if (self != NULL) {
|
||||
self->xnum = xnum;
|
||||
self->ynum = ynum;
|
||||
self->buf = PyMem_Calloc(xnum * ynum, CELL_SIZE_H);
|
||||
self->buf = (uint32_t*)PyMem_Calloc(xnum * ynum, CELL_SIZE_H);
|
||||
self->line = alloc_line();
|
||||
if (self->buf == NULL || self->line == NULL) {
|
||||
PyErr_NoMemory();
|
||||
@@ -41,7 +42,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
} else {
|
||||
self->line->xnum = xnum;
|
||||
for(index_type y = 0; y < self->ynum; y++) {
|
||||
self->line->chars = (char_type*)(start_of(self, y) + 1);
|
||||
self->line->chars = start_of(self, y) + 1;
|
||||
for (index_type i = 0; i < self->xnum; i++) self->line->chars[i] = (1 << ATTRS_SHIFT) | 32;
|
||||
}
|
||||
}
|
||||
@@ -69,11 +70,12 @@ index_of(HistoryBuf *self, index_type lnum) {
|
||||
static inline void
|
||||
init_line(HistoryBuf *self, index_type num, Line *l) {
|
||||
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
|
||||
uint8_t *start_ptr = start_of(self, num);
|
||||
l->continued = *start_ptr;
|
||||
uint32_t *start_ptr = start_of(self, num);
|
||||
l->continued = (bool)(*start_ptr);
|
||||
l->chars = (char_type*)(start_ptr + 1);
|
||||
l->colors = (color_type*)(l->chars + self->xnum);
|
||||
l->decoration_fg = (decoration_type*)(l->colors + self->xnum);
|
||||
l->fg_colors = (color_type*)(l->chars + self->xnum);
|
||||
l->bg_colors = (color_type*)(l->fg_colors + self->xnum);
|
||||
l->decoration_fg = (color_type*)(l->bg_colors + self->xnum);
|
||||
l->combining_chars = (combining_type*)(l->decoration_fg + self->xnum);
|
||||
}
|
||||
|
||||
@@ -93,16 +95,16 @@ index_type historybuf_push(HistoryBuf *self) {
|
||||
|
||||
bool
|
||||
historybuf_resize(HistoryBuf *self, index_type lines) {
|
||||
HistoryBuf t = {0};
|
||||
HistoryBuf t = {{0}};
|
||||
t.xnum=self->xnum;
|
||||
t.ynum=lines;
|
||||
if (t.ynum > 0 && t.ynum != self->ynum) {
|
||||
t.buf = PyMem_Calloc(t.xnum * t.ynum, CELL_SIZE_H);
|
||||
t.buf = (uint32_t*)PyMem_Calloc(t.xnum * t.ynum, CELL_SIZE_H);
|
||||
if (t.buf == NULL) { PyErr_NoMemory(); return false; }
|
||||
t.count = MIN(self->count, t.ynum);
|
||||
if (t.count > 0) {
|
||||
for (index_type s=0; s < t.count; s++) {
|
||||
uint8_t *src = start_of(self, index_of(self, s)), *dest = start_of(&t, index_of(&t, s));
|
||||
uint32_t *src = start_of(self, index_of(self, s)), *dest = start_of(&t, index_of(&t, s));
|
||||
memcpy(dest, src, CELL_SIZE_H * t.xnum);
|
||||
}
|
||||
}
|
||||
@@ -216,7 +218,7 @@ HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
|
||||
|
||||
#define init_src_line(src_y) init_line(src, map_src_index(src_y), src->line);
|
||||
|
||||
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? *(start_of(src, map_src_index(src_y + 1))) : false)
|
||||
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (bool)(*(start_of(src, map_src_index(src_y + 1)))) : false)
|
||||
|
||||
#define next_dest_line(cont) *start_of(dest, historybuf_push(dest)) = cont; dest->line->continued = cont;
|
||||
|
||||
|
||||
304
kitty/key_encoding.py
Normal file
304
kitty/key_encoding.py
Normal file
@@ -0,0 +1,304 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import string
|
||||
|
||||
from . import fast_data_types as defines
|
||||
|
||||
# ENCODING {{{
|
||||
ENCODING = {
|
||||
'0': 'G',
|
||||
'1': 'H',
|
||||
'2': 'I',
|
||||
'3': 'J',
|
||||
'4': 'K',
|
||||
'5': 'L',
|
||||
'6': 'M',
|
||||
'7': 'N',
|
||||
'8': 'O',
|
||||
'9': 'P',
|
||||
'A': 'S',
|
||||
'APOSTROPHE': 'B',
|
||||
'B': 'T',
|
||||
'BACKSLASH': 't',
|
||||
'BACKSPACE': '1',
|
||||
'C': 'U',
|
||||
'CAPS LOCK': ':',
|
||||
'COMMA': 'C',
|
||||
'D': 'V',
|
||||
'DELETE': '3',
|
||||
'DOWN': '6',
|
||||
'E': 'W',
|
||||
'END': '-',
|
||||
'ENTER': 'z',
|
||||
'EQUAL': 'R',
|
||||
'ESCAPE': 'y',
|
||||
'F': 'X',
|
||||
'F1': '/',
|
||||
'F10': ']',
|
||||
'F11': '{',
|
||||
'F12': '}',
|
||||
'F13': '@',
|
||||
'F14': '%',
|
||||
'F15': '$',
|
||||
'F16': '#',
|
||||
'F17': 'BA',
|
||||
'F18': 'BB',
|
||||
'F19': 'BC',
|
||||
'F2': '*',
|
||||
'F20': 'BD',
|
||||
'F21': 'BE',
|
||||
'F22': 'BF',
|
||||
'F23': 'BG',
|
||||
'F24': 'BH',
|
||||
'F25': 'BI',
|
||||
'F3': '?',
|
||||
'F4': '&',
|
||||
'F5': '<',
|
||||
'F6': '>',
|
||||
'F7': '(',
|
||||
'F8': ')',
|
||||
'F9': '[',
|
||||
'G': 'Y',
|
||||
'GRAVE ACCENT': 'v',
|
||||
'H': 'Z',
|
||||
'HOME': '.',
|
||||
'I': 'a',
|
||||
'INSERT': '2',
|
||||
'J': 'b',
|
||||
'K': 'c',
|
||||
'KP 0': 'BJ',
|
||||
'KP 1': 'BK',
|
||||
'KP 2': 'BL',
|
||||
'KP 3': 'BM',
|
||||
'KP 4': 'BN',
|
||||
'KP 5': 'BO',
|
||||
'KP 6': 'BP',
|
||||
'KP 7': 'BQ',
|
||||
'KP 8': 'BR',
|
||||
'KP 9': 'BS',
|
||||
'KP ADD': 'BX',
|
||||
'KP DECIMAL': 'BT',
|
||||
'KP DIVIDE': 'BU',
|
||||
'KP ENTER': 'BY',
|
||||
'KP EQUAL': 'BZ',
|
||||
'KP MULTIPLY': 'BV',
|
||||
'KP SUBTRACT': 'BW',
|
||||
'L': 'd',
|
||||
'LEFT': '5',
|
||||
'LEFT ALT': 'Bc',
|
||||
'LEFT BRACKET': 's',
|
||||
'LEFT CONTROL': 'Bb',
|
||||
'LEFT SHIFT': 'Ba',
|
||||
'LEFT SUPER': 'Bd',
|
||||
'M': 'e',
|
||||
'MINUS': 'D',
|
||||
'N': 'f',
|
||||
'NUM LOCK': '=',
|
||||
'O': 'g',
|
||||
'P': 'h',
|
||||
'PAGE DOWN': '9',
|
||||
'PAGE UP': '8',
|
||||
'PAUSE': '!',
|
||||
'PERIOD': 'E',
|
||||
'PRINT SCREEN': '^',
|
||||
'Q': 'i',
|
||||
'R': 'j',
|
||||
'RIGHT': '4',
|
||||
'RIGHT ALT': 'Bg',
|
||||
'RIGHT BRACKET': 'u',
|
||||
'RIGHT CONTROL': 'Bf',
|
||||
'RIGHT SHIFT': 'Be',
|
||||
'RIGHT SUPER': 'Bh',
|
||||
'S': 'k',
|
||||
'SCROLL LOCK': '+',
|
||||
'SEMICOLON': 'Q',
|
||||
'SLASH': 'F',
|
||||
'SPACE': 'A',
|
||||
'T': 'l',
|
||||
'TAB': '0',
|
||||
'U': 'm',
|
||||
'UP': '7',
|
||||
'V': 'n',
|
||||
'W': 'o',
|
||||
'WORLD 1': 'w',
|
||||
'WORLD 2': 'x',
|
||||
'X': 'p',
|
||||
'Y': 'q',
|
||||
'Z': 'r'
|
||||
}
|
||||
KEY_MAP = {
|
||||
32: 'A',
|
||||
39: 'B',
|
||||
44: 'C',
|
||||
45: 'D',
|
||||
46: 'E',
|
||||
47: 'F',
|
||||
48: 'G',
|
||||
49: 'H',
|
||||
50: 'I',
|
||||
51: 'J',
|
||||
52: 'K',
|
||||
53: 'L',
|
||||
54: 'M',
|
||||
55: 'N',
|
||||
56: 'O',
|
||||
57: 'P',
|
||||
59: 'Q',
|
||||
61: 'R',
|
||||
65: 'S',
|
||||
66: 'T',
|
||||
67: 'U',
|
||||
68: 'V',
|
||||
69: 'W',
|
||||
70: 'X',
|
||||
71: 'Y',
|
||||
72: 'Z',
|
||||
73: 'a',
|
||||
74: 'b',
|
||||
75: 'c',
|
||||
76: 'd',
|
||||
77: 'e',
|
||||
78: 'f',
|
||||
79: 'g',
|
||||
80: 'h',
|
||||
81: 'i',
|
||||
82: 'j',
|
||||
83: 'k',
|
||||
84: 'l',
|
||||
85: 'm',
|
||||
86: 'n',
|
||||
87: 'o',
|
||||
88: 'p',
|
||||
89: 'q',
|
||||
90: 'r',
|
||||
91: 's',
|
||||
92: 't',
|
||||
93: 'u',
|
||||
96: 'v',
|
||||
161: 'w',
|
||||
162: 'x',
|
||||
256: 'y',
|
||||
257: 'z',
|
||||
258: '0',
|
||||
259: '1',
|
||||
260: '2',
|
||||
261: '3',
|
||||
262: '4',
|
||||
263: '5',
|
||||
264: '6',
|
||||
265: '7',
|
||||
266: '8',
|
||||
267: '9',
|
||||
268: '.',
|
||||
269: '-',
|
||||
280: ':',
|
||||
281: '+',
|
||||
282: '=',
|
||||
283: '^',
|
||||
284: '!',
|
||||
290: '/',
|
||||
291: '*',
|
||||
292: '?',
|
||||
293: '&',
|
||||
294: '<',
|
||||
295: '>',
|
||||
296: '(',
|
||||
297: ')',
|
||||
298: '[',
|
||||
299: ']',
|
||||
300: '{',
|
||||
301: '}',
|
||||
302: '@',
|
||||
303: '%',
|
||||
304: '$',
|
||||
305: '#',
|
||||
306: 'BA',
|
||||
307: 'BB',
|
||||
308: 'BC',
|
||||
309: 'BD',
|
||||
310: 'BE',
|
||||
311: 'BF',
|
||||
312: 'BG',
|
||||
313: 'BH',
|
||||
314: 'BI',
|
||||
320: 'BJ',
|
||||
321: 'BK',
|
||||
322: 'BL',
|
||||
323: 'BM',
|
||||
324: 'BN',
|
||||
325: 'BO',
|
||||
326: 'BP',
|
||||
327: 'BQ',
|
||||
328: 'BR',
|
||||
329: 'BS',
|
||||
330: 'BT',
|
||||
331: 'BU',
|
||||
332: 'BV',
|
||||
333: 'BW',
|
||||
334: 'BX',
|
||||
335: 'BY',
|
||||
336: 'BZ',
|
||||
340: 'Ba',
|
||||
341: 'Bb',
|
||||
342: 'Bc',
|
||||
343: 'Bd',
|
||||
344: 'Be',
|
||||
345: 'Bf',
|
||||
346: 'Bg',
|
||||
347: 'Bh'
|
||||
}
|
||||
|
||||
# END_ENCODING }}}
|
||||
|
||||
|
||||
def encode(
|
||||
integer,
|
||||
chars=string.ascii_uppercase + string.ascii_lowercase + string.digits +
|
||||
'.-:+=^!/*?&<>()[]{}@%$#'
|
||||
):
|
||||
ans = ''
|
||||
d = len(chars)
|
||||
while True:
|
||||
integer, remainder = divmod(integer, d)
|
||||
ans = chars[remainder] + ans
|
||||
if integer == 0:
|
||||
break
|
||||
return ans
|
||||
|
||||
|
||||
def symbolic_name(glfw_name):
|
||||
return glfw_name[9:].replace('_', ' ')
|
||||
|
||||
|
||||
def update_encoding():
|
||||
import re
|
||||
import subprocess
|
||||
keys = {a for a in dir(defines) if a.startswith('GLFW_KEY_')}
|
||||
ans = ENCODING
|
||||
key_map = {}
|
||||
i = len(ans)
|
||||
for k in sorted(keys, key=lambda k: getattr(defines, k)):
|
||||
val = getattr(defines, k)
|
||||
name = symbolic_name(k)
|
||||
if val < defines.GLFW_KEY_LAST and val != defines.GLFW_KEY_UNKNOWN:
|
||||
if name not in ans:
|
||||
ans[name] = encode(i)
|
||||
i += 1
|
||||
key_map[val] = ans[name]
|
||||
with open(__file__, 'r+') as f:
|
||||
raw = f.read()
|
||||
nraw = re.sub(
|
||||
r'^ENCODING = {.+^# END_ENCODING',
|
||||
'ENCODING = {!r}\nKEY_MAP={!r}\n# END_ENCODING'.format(
|
||||
ans, key_map
|
||||
),
|
||||
raw,
|
||||
flags=re.MULTILINE | re.DOTALL
|
||||
)
|
||||
if raw == nraw:
|
||||
raise SystemExit('Failed to replace ENCODING dict')
|
||||
f.seek(0), f.truncate()
|
||||
f.write(nraw)
|
||||
subprocess.check_call(['yapf', '-i', __file__])
|
||||
197
kitty/keys.py
197
kitty/keys.py
@@ -2,10 +2,33 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import kitty.fast_data_types as defines
|
||||
from . import fast_data_types as defines
|
||||
from .terminfo import key_as_bytes
|
||||
from .utils import base64_encode
|
||||
from .key_encoding import KEY_MAP
|
||||
|
||||
key_map = {
|
||||
|
||||
def modify_key_bytes(keybytes, amt):
|
||||
ans = bytearray(keybytes)
|
||||
amt = str(amt).encode('ascii')
|
||||
if ans[-1] == ord('~'):
|
||||
return bytes(ans[:-1] + bytearray(b';' + amt + b'~'))
|
||||
if ans[1] == ord('O'):
|
||||
return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:])
|
||||
raise ValueError('Unknown key type')
|
||||
|
||||
|
||||
def modify_complex_key(name, amt):
|
||||
return modify_key_bytes(key_as_bytes(name), amt)
|
||||
|
||||
|
||||
control_codes = {}
|
||||
smkx_key_map = {}
|
||||
alt_codes = {defines.GLFW_KEY_TAB: b'\033\t'}
|
||||
shift_alt_codes = {defines.GLFW_KEY_TAB: key_as_bytes('kcbt')}
|
||||
alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
|
||||
|
||||
for kf, kn in {
|
||||
defines.GLFW_KEY_UP: 'kcuu1',
|
||||
defines.GLFW_KEY_DOWN: 'kcud1',
|
||||
defines.GLFW_KEY_LEFT: 'kcub1',
|
||||
@@ -16,17 +39,36 @@ key_map = {
|
||||
defines.GLFW_KEY_DELETE: 'kdch1',
|
||||
defines.GLFW_KEY_PAGE_UP: 'kpp',
|
||||
defines.GLFW_KEY_PAGE_DOWN: 'knp',
|
||||
}
|
||||
key_map = {k: key_as_bytes(v) for k, v in key_map.items()}
|
||||
}.items():
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
for f in range(1, 13):
|
||||
key_map[getattr(defines, 'GLFW_KEY_F{}'.format(f))] = key_as_bytes('kf{}'.format(f))
|
||||
del f
|
||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||
kn = 'kf{}'.format(f)
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
f = {k: k for k in '0123456789'}
|
||||
f.update({
|
||||
'COMMA': ',',
|
||||
'PERIOD': '.',
|
||||
'SEMICOLON': ';',
|
||||
'APOSTROPHE': "'",
|
||||
'MINUS': '-',
|
||||
'EQUAL': '=',
|
||||
})
|
||||
for kf, kn in f.items():
|
||||
control_codes[getattr(defines, 'GLFW_KEY_' + kf)] = (ord(kn),)
|
||||
del f, kf, kn
|
||||
|
||||
key_map[defines.GLFW_KEY_ESCAPE] = b'\033'
|
||||
key_map[defines.GLFW_KEY_ENTER] = b'\r'
|
||||
key_map[defines.GLFW_KEY_KP_ENTER] = b'\r'
|
||||
key_map[defines.GLFW_KEY_BACKSPACE] = key_as_bytes('kbs')
|
||||
key_map[defines.GLFW_KEY_TAB] = b'\t'
|
||||
smkx_key_map[defines.GLFW_KEY_ESCAPE] = b'\033'
|
||||
smkx_key_map[defines.GLFW_KEY_ENTER] = b'\r'
|
||||
smkx_key_map[defines.GLFW_KEY_KP_ENTER] = b'\r'
|
||||
smkx_key_map[defines.GLFW_KEY_BACKSPACE] = key_as_bytes('kbs')
|
||||
smkx_key_map[defines.GLFW_KEY_TAB] = b'\t'
|
||||
|
||||
SHIFTED_KEYS = {
|
||||
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
|
||||
@@ -36,26 +78,57 @@ SHIFTED_KEYS = {
|
||||
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'),
|
||||
}
|
||||
|
||||
control_codes = {k: (1 + i,) for i, k in enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1))}
|
||||
control_codes[defines.GLFW_KEY_UP] = bytearray(key_as_bytes('cuu1').replace(b'[', b'[1;5'))
|
||||
control_codes[defines.GLFW_KEY_DOWN] = bytearray(key_as_bytes('cud1').replace(b'[', b'[1;5'))
|
||||
control_codes[defines.GLFW_KEY_LEFT] = bytearray(key_as_bytes('cub1').replace(b'[', b'[1;5'))
|
||||
control_codes[defines.GLFW_KEY_RIGHT] = bytearray(key_as_bytes('cuf1').replace(b'[', b'[1;5'))
|
||||
control_codes[defines.GLFW_KEY_HOME] = bytearray(key_as_bytes('khome').replace(b'O', b'[1;5'))
|
||||
control_codes[defines.GLFW_KEY_END] = bytearray(key_as_bytes('kend').replace(b'O', b'[1;5'))
|
||||
control_codes[defines.GLFW_KEY_PAGE_UP] = bytearray(key_as_bytes('kpp').replace(b'~', b';5~'))
|
||||
control_codes[defines.GLFW_KEY_PAGE_DOWN] = bytearray(key_as_bytes('knp').replace(b'~', b';5~'))
|
||||
control_codes[defines.GLFW_KEY_DELETE] = bytearray(key_as_bytes('kdch1').replace(b'~', b';5~'))
|
||||
alt_codes = {k: (0x1b, k) for i, k in enumerate(range(defines.GLFW_KEY_SPACE, defines.GLFW_KEY_RIGHT_BRACKET + 1))}
|
||||
control_codes.update({
|
||||
k: (1 + i, )
|
||||
for i, k in
|
||||
enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1))
|
||||
})
|
||||
control_codes[defines.GLFW_KEY_6] = (30,)
|
||||
control_codes[defines.GLFW_KEY_SLASH] = (31,)
|
||||
control_codes[defines.GLFW_KEY_SPACE] = (0,)
|
||||
|
||||
|
||||
rmkx_key_map = smkx_key_map.copy()
|
||||
rmkx_key_map.update({
|
||||
defines.GLFW_KEY_UP: b'\033[A',
|
||||
defines.GLFW_KEY_DOWN: b'\033[B',
|
||||
defines.GLFW_KEY_LEFT: b'\033[D',
|
||||
defines.GLFW_KEY_RIGHT: b'\033[C',
|
||||
defines.GLFW_KEY_HOME: b'\033[H',
|
||||
defines.GLFW_KEY_END: b'\033[F',
|
||||
})
|
||||
|
||||
cursor_key_mode_map = {True: smkx_key_map, False: rmkx_key_map}
|
||||
|
||||
|
||||
def get_key_map(screen):
|
||||
return cursor_key_mode_map[screen.cursor_key_mode]
|
||||
|
||||
|
||||
def keyboard_mode_name(screen):
|
||||
if screen.extended_keyboard:
|
||||
return 'kitty'
|
||||
return 'application' if screen.cursor_key_mode else 'normal'
|
||||
|
||||
|
||||
valid_localized_key_names = {
|
||||
k: getattr(defines, 'GLFW_KEY_' + k) for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
k: getattr(defines, 'GLFW_KEY_' + k)
|
||||
for k in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
}
|
||||
|
||||
for name, ch in {
|
||||
'APOSTROPHE': "'", 'COMMA': ',', 'PERIOD': '.', 'SLASH': '/', 'MINUS': '-', 'SEMICOLON': ';', 'EQUAL': '=',
|
||||
'LEFT_BRACKET': '[', 'RIGHT_BRACKET': ']', 'GRAVE_ACCENT': '`', 'BACKSLASH': '\\'}.items():
|
||||
'APOSTROPHE': "'",
|
||||
'COMMA': ',',
|
||||
'PERIOD': '.',
|
||||
'SLASH': '/',
|
||||
'MINUS': '-',
|
||||
'SEMICOLON': ';',
|
||||
'EQUAL': '=',
|
||||
'LEFT_BRACKET': '[',
|
||||
'RIGHT_BRACKET': ']',
|
||||
'GRAVE_ACCENT': '`',
|
||||
'BACKSLASH': '\\'
|
||||
}.items():
|
||||
valid_localized_key_names[ch] = getattr(defines, 'GLFW_KEY_' + name)
|
||||
|
||||
|
||||
@@ -64,26 +137,62 @@ def get_localized_key(key, scancode):
|
||||
return valid_localized_key_names.get((name or '').upper(), key)
|
||||
|
||||
|
||||
def interpret_key_event(key, scancode, mods):
|
||||
data = bytearray()
|
||||
action_map = {
|
||||
defines.GLFW_PRESS: b'p',
|
||||
defines.GLFW_RELEASE: b'r',
|
||||
defines.GLFW_REPEAT: b't'
|
||||
}
|
||||
|
||||
|
||||
def extended_key_event(key, scancode, mods, action):
|
||||
if key >= defines.GLFW_KEY_LAST or key == defines.GLFW_KEY_UNKNOWN or (
|
||||
# Shifted printable key should be handled by interpret_text_event()
|
||||
mods == defines.GLFW_MOD_SHIFT and 32 <= key <= 126
|
||||
):
|
||||
return b''
|
||||
if mods == 0 and key in (
|
||||
defines.GLFW_KEY_BACKSPACE, defines.GLFW_KEY_ENTER
|
||||
):
|
||||
return smkx_key_map[key]
|
||||
name = KEY_MAP.get(key)
|
||||
if name is None:
|
||||
return b''
|
||||
return '\033_K{}{}{}\033\\'.format(
|
||||
action_map[action], base64_encode(mods), name
|
||||
).encode('ascii')
|
||||
|
||||
|
||||
def interpret_key_event(key, scancode, mods, window, action, get_localized_key=get_localized_key):
|
||||
screen = window.screen
|
||||
key = get_localized_key(key, scancode)
|
||||
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
|
||||
# Map Ctrl-key to ascii control code
|
||||
data.extend(control_codes[key])
|
||||
elif mods == defines.GLFW_MOD_ALT and key in alt_codes:
|
||||
# Map Alt+key to Esc-key
|
||||
data.extend(alt_codes[key])
|
||||
else:
|
||||
x = key_map.get(key)
|
||||
if x is not None:
|
||||
if mods == defines.GLFW_MOD_SHIFT:
|
||||
x = SHIFTED_KEYS.get(key, x)
|
||||
data.extend(x)
|
||||
if screen.extended_keyboard:
|
||||
return extended_key_event(key, scancode, mods, action)
|
||||
data = bytearray()
|
||||
if action == defines.GLFW_PRESS or (
|
||||
action == defines.GLFW_REPEAT and screen.auto_repeat_enabled
|
||||
):
|
||||
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
|
||||
# Map Ctrl-key to ascii control code
|
||||
data.extend(control_codes[key])
|
||||
elif mods in alt_mods and key in alt_codes:
|
||||
# Printable keys handled by interpret_text_event()
|
||||
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
|
||||
else:
|
||||
key_map = get_key_map(screen)
|
||||
x = key_map.get(key)
|
||||
if x is not None:
|
||||
if mods == defines.GLFW_MOD_SHIFT:
|
||||
x = SHIFTED_KEYS.get(key, x)
|
||||
data.extend(x)
|
||||
return bytes(data)
|
||||
|
||||
|
||||
def interpret_text_event(codepoint, mods):
|
||||
def interpret_text_event(codepoint, mods, window):
|
||||
screen = window.screen
|
||||
if mods > defines.GLFW_MOD_SHIFT:
|
||||
if mods in alt_mods and not screen.extended_keyboard:
|
||||
data = chr(codepoint).encode('utf-8')
|
||||
return b'\x1b' + data
|
||||
return b'' # Handled by interpret_key_event above
|
||||
data = chr(codepoint).encode('utf-8')
|
||||
return data
|
||||
@@ -92,3 +201,11 @@ def interpret_text_event(codepoint, mods):
|
||||
def get_shortcut(keymap, mods, key, scancode):
|
||||
key = get_localized_key(key, scancode)
|
||||
return keymap.get((mods & 0b1111, key))
|
||||
|
||||
|
||||
def get_sent_data(send_text_map, key, scancode, mods, window, action):
|
||||
if action in (defines.GLFW_PRESS, defines.GLFW_REPEAT):
|
||||
key = get_localized_key(key, scancode)
|
||||
m = keyboard_mode_name(window.screen)
|
||||
keymap = send_text_map[m]
|
||||
return keymap.get((mods & 0b1111, key))
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# that have many weight variants like Book, Medium, Thick, etc. For example:
|
||||
# font_family Operator Mono Book
|
||||
# bold_font Operator Mono Thick
|
||||
# bold_talic_font Operator Mono Medium
|
||||
# bold_italic_font Operator Mono Medium
|
||||
font_family monospace
|
||||
italic_font auto
|
||||
bold_font auto
|
||||
@@ -15,6 +15,10 @@ bold_italic_font auto
|
||||
# Font size (in pts)
|
||||
font_size 11.0
|
||||
|
||||
# The amount the font size is changed by (in pts) when increasing/decreasing
|
||||
# the font size in a running terminal.
|
||||
font_size_delta 2
|
||||
|
||||
# The foreground color
|
||||
foreground #dddddd
|
||||
|
||||
@@ -41,7 +45,7 @@ cursor_shape block
|
||||
cursor_blink_interval 0.5
|
||||
|
||||
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to
|
||||
# zero or a negative number to never stop blinking.
|
||||
# zero to never stop blinking.
|
||||
cursor_stop_blinking_after 15.0
|
||||
|
||||
# Number of lines of history to keep in memory for scrolling back
|
||||
@@ -55,7 +59,8 @@ scrollback_pager less +G -R
|
||||
# When viewing scrollback in a new window, put it in a new tab as well
|
||||
scrollback_in_new_tab no
|
||||
|
||||
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel)
|
||||
# Wheel scroll multiplier (modify the amount scrolled by the mouse wheel). Use negative
|
||||
# numbers to change scroll direction.
|
||||
wheel_scroll_multiplier 5.0
|
||||
|
||||
# The interval between successive clicks to detect double/triple clicks (in seconds)
|
||||
@@ -67,7 +72,7 @@ click_interval 0.5
|
||||
select_by_word_characters :@-./_~?&=%+#
|
||||
|
||||
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to
|
||||
# zero or a negative number to disable mouse cursor hiding.
|
||||
# zero to disable mouse cursor hiding.
|
||||
mouse_hide_wait 3.0
|
||||
|
||||
# The enabled window layouts. A comma separated list of layout names. The special value * means
|
||||
@@ -87,6 +92,13 @@ initial_window_height 400
|
||||
# that sufficient for most uses.
|
||||
repaint_delay 10
|
||||
|
||||
# Visual bell duration. Flash the screen when a bell occurs for the specified number of
|
||||
# seconds. Set to zero to disable.
|
||||
visual_bell_duration 0.0
|
||||
|
||||
# Enable/disable the audio bell. Useful in environments that require silence.
|
||||
enable_audio_bell yes
|
||||
|
||||
# The modifier keys to press when clicking with the mouse on URLs to open the URL
|
||||
open_url_modifiers ctrl+shift
|
||||
|
||||
@@ -109,6 +121,12 @@ term xterm-kitty
|
||||
# The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||
window_border_width 1
|
||||
|
||||
# The window margin (in pts) (blank area outside the border)
|
||||
window_margin_width 0
|
||||
|
||||
# The window padding (in pts) (blank area between the text and the window border)
|
||||
window_padding_width 0
|
||||
|
||||
# The color for the border of the active window
|
||||
active_border_color #00ff00
|
||||
|
||||
@@ -207,3 +225,45 @@ map ctrl+shift+q close_tab
|
||||
map ctrl+shift+l next_layout
|
||||
map ctrl+shift+. move_tab_forward
|
||||
map ctrl+shift+, move_tab_backward
|
||||
|
||||
# Miscellaneous
|
||||
map ctrl+shift+equal increase_font_size
|
||||
map ctrl+shift+minus decrease_font_size
|
||||
map ctrl+shift+backspace restore_font_size
|
||||
|
||||
# Sending arbitrary text on shortcut key presses
|
||||
# You can tell kitty to send arbitrary (UTF-8) encoded text to
|
||||
# the client program when pressing specified shortcut keys. For example:
|
||||
# send_text all ctrl+alt+a Special text
|
||||
# This will send "Special text" when you press the Ctrl+Alt+a key combination.
|
||||
# The text to be sent is a python string literal so you can use escapes like
|
||||
# \x1b to send control codes or \u21fb to send unicode characters (or you can
|
||||
# just input the unicode characters directly as UTF-8 text). The first argument
|
||||
# to send_text is the keyboard modes in which to activate the shortcut. The possible
|
||||
# values are normal or application or kitty or a comma separated combination of them.
|
||||
# The special keyword all means all modes. The modes normal and application refer to
|
||||
# the DECCKM cursor key mode for terminals, and kitty refers to the special kitty
|
||||
# extended keyboard protocol. Another example, that outputs a word and then moves the cursor
|
||||
# to the start of the line (same as pressing the Home key):
|
||||
# send_text normal ctrl+alt+a Word\x1b[H
|
||||
# send_text application ctrl+alt+a Word\x1bOH
|
||||
|
||||
# Symbol mapping (special font for specified unicode code points). Map the
|
||||
# specified unicode codepoints to a particular font. Useful if you need special
|
||||
# rendering for some symbols, such as for Powerline. Avoids the need for
|
||||
# patched fonts. Each unicode code point is specified in the form U+<code point
|
||||
# in hexadecimal>. You can specify multiple code points, separated by commas
|
||||
# and ranges separated by hyphens. symbol_map itself can be specified multiple times.
|
||||
# Syntax is:
|
||||
#
|
||||
# symbol_map codepoints Font Family Name
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
|
||||
|
||||
|
||||
# OS specific tweaks
|
||||
|
||||
# Hide the kitty window's title bar on macOS.
|
||||
macos_hide_titlebar no
|
||||
|
||||
@@ -6,25 +6,29 @@ from collections import namedtuple
|
||||
from itertools import islice
|
||||
|
||||
from .constants import WindowGeometry, viewport_size, cell_size, get_boss
|
||||
from .utils import pt_to_px
|
||||
|
||||
|
||||
def available_height():
|
||||
return viewport_size.height - get_boss().current_tab_bar_height
|
||||
|
||||
|
||||
def layout_dimension(length, cell_length, number_of_windows=1, border_length=0, left_align=False):
|
||||
def layout_dimension(length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False):
|
||||
number_of_cells = length // cell_length
|
||||
border_length += padding_length
|
||||
space_needed_for_border = number_of_windows * 2 * border_length
|
||||
space_needed_for_padding = number_of_windows * 2 * margin_length
|
||||
space_needed = space_needed_for_padding + space_needed_for_border
|
||||
extra = length - number_of_cells * cell_length
|
||||
while extra < space_needed_for_border:
|
||||
while extra < space_needed:
|
||||
number_of_cells -= 1
|
||||
extra = length - number_of_cells * cell_length
|
||||
cells_per_window = number_of_cells // number_of_windows
|
||||
extra -= space_needed_for_border
|
||||
extra -= space_needed
|
||||
pos = 0 if left_align else (extra // 2)
|
||||
pos += border_length
|
||||
pos += border_length + margin_length
|
||||
inner_length = cells_per_window * cell_length
|
||||
window_length = 2 * border_length + inner_length
|
||||
window_length = 2 * (border_length + margin_length) + inner_length
|
||||
extra = number_of_cells - (cells_per_window * number_of_windows)
|
||||
while number_of_windows > 0:
|
||||
number_of_windows -= 1
|
||||
@@ -43,6 +47,8 @@ class Layout:
|
||||
def __init__(self, opts, border_width, windows):
|
||||
self.opts = opts
|
||||
self.border_width = border_width
|
||||
self.margin_width = pt_to_px(opts.window_margin_width)
|
||||
self.padding_width = pt_to_px(opts.window_padding_width)
|
||||
# A set of rectangles corresponding to the blank spaces at the edges of
|
||||
# this layout, i.e. spaces that are not covered by any window
|
||||
self.blank_rects = ()
|
||||
@@ -76,9 +82,9 @@ def window_geometry(xstart, xnum, ystart, ynum):
|
||||
return WindowGeometry(left=xstart, top=ystart, xnum=xnum, ynum=ynum, right=xstart + cell_size.width * xnum, bottom=ystart + cell_size.height * ynum)
|
||||
|
||||
|
||||
def layout_single_window():
|
||||
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width))
|
||||
ystart, ynum = next(layout_dimension(available_height(), cell_size.height))
|
||||
def layout_single_window(margin_length, padding_length):
|
||||
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width, margin_length=margin_length, padding_length=padding_length))
|
||||
ystart, ynum = next(layout_dimension(available_height(), cell_size.height, margin_length=margin_length, padding_length=padding_length))
|
||||
return window_geometry(xstart, xnum, ystart, ynum)
|
||||
|
||||
|
||||
@@ -120,7 +126,7 @@ class Stack(Layout):
|
||||
|
||||
def __call__(self, windows, active_window_idx):
|
||||
self.blank_rects = []
|
||||
wg = layout_single_window()
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
for i, w in enumerate(windows):
|
||||
w.is_visible_in_layout = i == active_window_idx
|
||||
w.set_geometry(wg)
|
||||
@@ -135,17 +141,23 @@ class Tall(Layout):
|
||||
def __call__(self, windows, active_window_idx):
|
||||
self.blank_rects = br = []
|
||||
if len(windows) == 1:
|
||||
wg = layout_single_window()
|
||||
wg = layout_single_window(self.margin_width, self.padding_width)
|
||||
windows[0].set_geometry(wg)
|
||||
self.blank_rects = blank_rects_for_window(windows[0])
|
||||
return
|
||||
xlayout = layout_dimension(viewport_size.width, cell_size.width, 2, self.border_width)
|
||||
xlayout = layout_dimension(
|
||||
viewport_size.width, cell_size.width, 2, self.border_width,
|
||||
margin_length=self.margin_width, padding_length=self.padding_width)
|
||||
xstart, xnum = next(xlayout)
|
||||
ystart, ynum = next(layout_dimension(available_height(), cell_size.height, 1, self.border_width, left_align=True))
|
||||
ystart, ynum = next(layout_dimension(
|
||||
available_height(), cell_size.height, 1, self.border_width, left_align=True,
|
||||
margin_length=self.margin_width, padding_length=self.padding_width))
|
||||
windows[0].set_geometry(window_geometry(xstart, xnum, ystart, ynum))
|
||||
vh = available_height()
|
||||
xstart, xnum = next(xlayout)
|
||||
ylayout = layout_dimension(available_height(), cell_size.height, len(windows) - 1, self.border_width, left_align=True)
|
||||
ylayout = layout_dimension(
|
||||
available_height(), cell_size.height, len(windows) - 1, self.border_width, left_align=True,
|
||||
margin_length=self.margin_width, padding_length=self.padding_width)
|
||||
for w, (ystart, ynum) in zip(islice(windows, 1, None), ylayout):
|
||||
w.set_geometry(window_geometry(xstart, xnum, ystart, ynum))
|
||||
left_blank_rect(windows[0], br, vh), top_blank_rect(windows[0], br, vh), right_blank_rect(windows[-1], br, vh)
|
||||
|
||||
@@ -63,8 +63,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
Py_CLEAR(self);
|
||||
} else {
|
||||
self->chars = (char_type*)self->buf;
|
||||
self->colors = (color_type*)(self->chars + self->block_size);
|
||||
self->decoration_fg = (decoration_type*)(self->colors + self->block_size);
|
||||
self->fg_colors = (color_type*)(self->chars + self->block_size);
|
||||
self->bg_colors = (color_type*)(self->fg_colors + self->block_size);
|
||||
self->decoration_fg = (color_type*)(self->bg_colors + self->block_size);
|
||||
self->combining_chars = (combining_type*)(self->decoration_fg + self->block_size);
|
||||
self->line->xnum = xnum;
|
||||
for(index_type i = 0; i < ynum; i++) {
|
||||
@@ -89,7 +90,8 @@ dealloc(LineBuf* self) {
|
||||
|
||||
#define INIT_LINE(lb, l, ynum) \
|
||||
(l)->chars = (lb)->chars + (ynum) * (lb)->xnum; \
|
||||
(l)->colors = (lb)->colors + (ynum) * (lb)->xnum; \
|
||||
(l)->fg_colors = (lb)->fg_colors + (ynum) * (lb)->xnum; \
|
||||
(l)->bg_colors = (lb)->bg_colors + (ynum) * (lb)->xnum; \
|
||||
(l)->decoration_fg = (lb)->decoration_fg + (ynum) * (lb)->xnum; \
|
||||
(l)->combining_chars = (lb)->combining_chars + (ynum) * (lb)->xnum;
|
||||
|
||||
@@ -150,19 +152,22 @@ static inline int
|
||||
allocate_line_storage(Line *line, bool initialize) {
|
||||
if (initialize) {
|
||||
line->chars = PyMem_Calloc(line->xnum, sizeof(char_type));
|
||||
line->colors = PyMem_Calloc(line->xnum, sizeof(color_type));
|
||||
line->decoration_fg = PyMem_Calloc(line->xnum, sizeof(decoration_type));
|
||||
line->fg_colors = PyMem_Calloc(line->xnum, sizeof(color_type));
|
||||
line->bg_colors = PyMem_Calloc(line->xnum, sizeof(color_type));
|
||||
line->decoration_fg = PyMem_Calloc(line->xnum, sizeof(color_type));
|
||||
line->combining_chars = PyMem_Calloc(line->xnum, sizeof(combining_type));
|
||||
for (index_type i = 0; i < line->xnum; i++) line->chars[i] = (1 << ATTRS_SHIFT) | 32;
|
||||
} else {
|
||||
line->chars = PyMem_Malloc(line->xnum * sizeof(char_type));
|
||||
line->colors = PyMem_Malloc(line->xnum * sizeof(color_type));
|
||||
line->decoration_fg = PyMem_Malloc(line->xnum * sizeof(decoration_type));
|
||||
line->fg_colors = PyMem_Malloc(line->xnum * sizeof(color_type));
|
||||
line->bg_colors = PyMem_Malloc(line->xnum * sizeof(color_type));
|
||||
line->decoration_fg = PyMem_Malloc(line->xnum * sizeof(color_type));
|
||||
line->combining_chars = PyMem_Malloc(line->xnum * sizeof(combining_type));
|
||||
}
|
||||
if (line->chars == NULL || line->colors == NULL || line->decoration_fg == NULL || line->combining_chars == NULL) {
|
||||
if (line->chars == NULL || line->fg_colors == NULL || line->bg_colors == NULL || line->decoration_fg == NULL || line->combining_chars == NULL) {
|
||||
PyMem_Free(line->chars); line->chars = NULL;
|
||||
PyMem_Free(line->colors); line->colors = NULL;
|
||||
PyMem_Free(line->fg_colors); line->fg_colors = NULL;
|
||||
PyMem_Free(line->bg_colors); line->bg_colors = NULL;
|
||||
PyMem_Free(line->decoration_fg); line->decoration_fg = NULL;
|
||||
PyMem_Free(line->combining_chars); line->combining_chars = NULL;
|
||||
PyErr_NoMemory();
|
||||
@@ -210,7 +215,7 @@ copy_line_to(LineBuf *self, PyObject *args) {
|
||||
void linebuf_clear_line(LineBuf *self, index_type y) {
|
||||
Line l;
|
||||
INIT_LINE(self, &l, self->line_map[y]);
|
||||
CLEAR_LINE(&l, 0, self->xnum);
|
||||
CLEAR_LINE(&l, self->xnum);
|
||||
self->continued_map[y] = 0;
|
||||
}
|
||||
|
||||
@@ -295,7 +300,7 @@ void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsig
|
||||
Line l;
|
||||
for (i = y; i < y + num; i++) {
|
||||
INIT_LINE(self, &l, self->line_map[i]);
|
||||
CLEAR_LINE(&l, 0, self->xnum);
|
||||
CLEAR_LINE(&l, self->xnum);
|
||||
self->continued_map[i] = 0;
|
||||
}
|
||||
}
|
||||
@@ -330,7 +335,7 @@ linebuf_delete_lines(LineBuf *self, index_type num, index_type y, index_type bot
|
||||
Line l;
|
||||
for (i = ylimit - num; i < ylimit; i++) {
|
||||
INIT_LINE(self, &l, self->line_map[i]);
|
||||
CLEAR_LINE(&l, 0, self->xnum);
|
||||
CLEAR_LINE(&l, self->xnum);
|
||||
self->continued_map[i] = 0;
|
||||
}
|
||||
}
|
||||
@@ -418,7 +423,7 @@ copy_old(LineBuf *self, PyObject *y) {
|
||||
if (!PyObject_TypeCheck(y, &LineBuf_Type)) { PyErr_SetString(PyExc_TypeError, "Not a LineBuf object"); return NULL; }
|
||||
LineBuf *other = (LineBuf*)y;
|
||||
if (other->xnum != self->xnum) { PyErr_SetString(PyExc_ValueError, "LineBuf has a different number of columns"); return NULL; }
|
||||
Line sl = {0}, ol = {0};
|
||||
Line sl = {{0}}, ol = {{0}};
|
||||
sl.xnum = self->xnum; ol.xnum = other->xnum;
|
||||
|
||||
for (index_type i = 0; i < MIN(self->ynum, other->ynum); i++) {
|
||||
|
||||
62
kitty/line.c
62
kitty/line.c
@@ -17,7 +17,8 @@ static void
|
||||
dealloc(Line* self) {
|
||||
if (self->needs_free) {
|
||||
PyMem_Free(self->chars);
|
||||
PyMem_Free(self->colors);
|
||||
PyMem_Free(self->fg_colors);
|
||||
PyMem_Free(self->bg_colors);
|
||||
PyMem_Free(self->decoration_fg);
|
||||
PyMem_Free(self->combining_chars);
|
||||
}
|
||||
@@ -170,9 +171,8 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
|
||||
WRITE_SGR(0); break;
|
||||
}
|
||||
}
|
||||
color_type col = self->colors[pos];
|
||||
CHECK_COLOR(fg, col & COL_MASK, 38);
|
||||
CHECK_COLOR(bg, col >> COL_SHIFT, 48);
|
||||
CHECK_COLOR(fg, self->fg_colors[pos], 38);
|
||||
CHECK_COLOR(bg, self->bg_colors[pos], 48);
|
||||
CHECK_COLOR(decoration_fg, self->decoration_fg[pos], DECORATION_FG_CODE);
|
||||
WRITE_CH(ch);
|
||||
char_type cc = self->combining_chars[pos];
|
||||
@@ -200,6 +200,14 @@ as_ansi(Line* self) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
is_continued(Line* self) {
|
||||
#define is_continued_doc "Return the line's continued flag"
|
||||
PyObject *ans = self->continued ? Py_True : Py_False;
|
||||
Py_INCREF(ans);
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
__repr__(Line* self) {
|
||||
PyObject *s = as_unicode(self);
|
||||
@@ -218,15 +226,6 @@ width(Line *self, PyObject *val) {
|
||||
return PyLong_FromUnsignedLong((unsigned long) (attrs & WIDTH_MASK));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
basic_cell_data(Line *self, PyObject *val) {
|
||||
#define basic_cell_data_doc "basic_cell_data(x) -> ch, attrs, colors"
|
||||
unsigned long x = PyLong_AsUnsignedLong(val);
|
||||
if (x >= self->xnum) { PyErr_SetString(PyExc_ValueError, "Out of bounds"); return NULL; }
|
||||
char_type ch = self->chars[x];
|
||||
return Py_BuildValue("IBK", (unsigned int)(ch & CHAR_MASK), (unsigned char)(ch >> ATTRS_SHIFT), (unsigned long long)self->colors[x]);
|
||||
}
|
||||
|
||||
void line_add_combining_char(Line *self, uint32_t ch, unsigned int x) {
|
||||
combining_type c = self->combining_chars[x];
|
||||
if (c & CC_MASK) self->combining_chars[x] = (c & CC_MASK) | ( (ch & CC_MASK) << CC_SHIFT );
|
||||
@@ -271,12 +270,13 @@ set_text(Line* self, PyObject *args) {
|
||||
return NULL;
|
||||
}
|
||||
attrs = CURSOR_TO_ATTRS(cursor, 1);
|
||||
color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT);
|
||||
decoration_type dfg = cursor->decoration_fg & COL_MASK;
|
||||
color_type fg = (cursor->fg & COL_MASK), bg = cursor->bg & COL_MASK;
|
||||
color_type dfg = cursor->decoration_fg & COL_MASK;
|
||||
|
||||
for (index_type i = cursor->x; offset < limit && i < self->xnum; i++, offset++) {
|
||||
self->chars[i] = (PyUnicode_READ(kind, buf, offset) & CHAR_MASK) | attrs;
|
||||
self->colors[i] = col;
|
||||
self->fg_colors[i] = fg;
|
||||
self->bg_colors[i] = bg;
|
||||
self->decoration_fg[i] = dfg;
|
||||
self->combining_chars[i] = 0;
|
||||
}
|
||||
@@ -299,13 +299,14 @@ cursor_from(Line* self, PyObject *args) {
|
||||
ans->x = x; ans->y = y;
|
||||
char_type attrs = self->chars[x] >> ATTRS_SHIFT;
|
||||
ATTRS_TO_CURSOR(attrs, ans);
|
||||
COLORS_TO_CURSOR(self->colors[x], ans);
|
||||
ans->fg = self->fg_colors[x]; ans->bg = self->bg_colors[x];
|
||||
ans->decoration_fg = self->decoration_fg[x] & COL_MASK;
|
||||
|
||||
return (PyObject*)ans;
|
||||
}
|
||||
|
||||
void line_clear_text(Line *self, unsigned int at, unsigned int num, int ch) {
|
||||
void
|
||||
line_clear_text(Line *self, unsigned int at, unsigned int num, int ch) {
|
||||
const char_type repl = ((char_type)ch & CHAR_MASK) | (1 << ATTRS_SHIFT);
|
||||
for (index_type i = at; i < MIN(self->xnum, at + num); i++) {
|
||||
self->chars[i] = (self->chars[i] & ATTRS_MASK_WITHOUT_WIDTH) | repl;
|
||||
@@ -323,10 +324,11 @@ clear_text(Line* self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char) {
|
||||
void
|
||||
line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num, bool clear_char) {
|
||||
char_type attrs = CURSOR_TO_ATTRS(cursor, 1);
|
||||
color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT);
|
||||
decoration_type dfg = cursor->decoration_fg & COL_MASK;
|
||||
color_type fg = (cursor->fg & COL_MASK), bg = (cursor->bg & COL_MASK);
|
||||
color_type dfg = cursor->decoration_fg & COL_MASK;
|
||||
if (!clear_char) attrs = ((attrs >> ATTRS_SHIFT) & ~WIDTH_MASK) << ATTRS_SHIFT;
|
||||
|
||||
for (index_type i = at; i < self->xnum && i < at + num; i++) {
|
||||
@@ -337,7 +339,7 @@ void line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int
|
||||
char_type w = ((self->chars[i] >> ATTRS_SHIFT) & WIDTH_MASK) << ATTRS_SHIFT;
|
||||
self->chars[i] = (self->chars[i] & CHAR_MASK) | attrs | w;
|
||||
}
|
||||
self->colors[i] = col;
|
||||
self->fg_colors[i] = fg; self->bg_colors[i] = bg;
|
||||
self->decoration_fg[i] = dfg;
|
||||
}
|
||||
}
|
||||
@@ -390,13 +392,15 @@ left_shift(Line *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
void line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Cursor *cursor) {
|
||||
void
|
||||
line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Cursor *cursor) {
|
||||
char_type attrs;
|
||||
if (cursor == NULL) {
|
||||
attrs = (((self->chars[at] >> ATTRS_SHIFT) & ~3) | (width & 3)) << ATTRS_SHIFT;
|
||||
} else {
|
||||
attrs = CURSOR_TO_ATTRS(cursor, width & 3);
|
||||
self->colors[at] = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT);
|
||||
self->fg_colors[at] = (cursor->fg & COL_MASK);
|
||||
self->bg_colors[at] = (cursor->bg & COL_MASK);
|
||||
self->decoration_fg[at] = cursor->decoration_fg & COL_MASK;
|
||||
}
|
||||
self->chars[at] = (ch & CHAR_MASK) | attrs;
|
||||
@@ -438,8 +442,9 @@ __len__(PyObject *self) {
|
||||
static int __eq__(Line *a, Line *b) {
|
||||
return a->xnum == b->xnum && \
|
||||
memcmp(a->chars, b->chars, sizeof(char_type) * a->xnum) == 0 && \
|
||||
memcmp(a->colors, b->colors, sizeof(color_type) * a->xnum) == 0 && \
|
||||
memcmp(a->decoration_fg, b->decoration_fg, sizeof(decoration_type) * a->xnum) == 0 && \
|
||||
memcmp(a->fg_colors, b->fg_colors, sizeof(color_type) * a->xnum) == 0 && \
|
||||
memcmp(a->bg_colors, b->bg_colors, sizeof(color_type) * a->xnum) == 0 && \
|
||||
memcmp(a->decoration_fg, b->decoration_fg, sizeof(color_type) * a->xnum) == 0 && \
|
||||
memcmp(a->combining_chars, b->combining_chars, sizeof(combining_type) * a->xnum) == 0;
|
||||
}
|
||||
|
||||
@@ -470,8 +475,8 @@ static PyMethodDef methods[] = {
|
||||
METHOD(set_attribute, METH_VARARGS)
|
||||
METHOD(as_base_text, METH_NOARGS)
|
||||
METHOD(as_ansi, METH_NOARGS)
|
||||
METHOD(is_continued, METH_NOARGS)
|
||||
METHOD(width, METH_O)
|
||||
METHOD(basic_cell_data, METH_O)
|
||||
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
@@ -510,7 +515,8 @@ copy_char(Line* self, PyObject *args) {
|
||||
return NULL;
|
||||
}
|
||||
to->chars[dest] = self->chars[src];
|
||||
to->colors[dest] = self->colors[src];
|
||||
to->fg_colors[dest] = self->fg_colors[src];
|
||||
to->bg_colors[dest] = self->bg_colors[src];
|
||||
to->decoration_fg[dest] = self->decoration_fg[src];
|
||||
to->combining_chars[dest] = self->combining_chars[src];
|
||||
Py_RETURN_NONE;
|
||||
|
||||
226
kitty/main.py
226
kitty/main.py
@@ -3,53 +3,154 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import argparse
|
||||
import tempfile
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
from queue import Empty
|
||||
import tempfile
|
||||
from gettext import gettext as _
|
||||
from queue import Empty
|
||||
|
||||
|
||||
from .config import load_config, load_cached_values, cached_values, save_cached_values
|
||||
from .constants import appname, str_version, config_dir, viewport_size, isosx, logo_data_file
|
||||
from .layout import all_layouts
|
||||
from .boss import Boss
|
||||
from .shaders import GL_VERSION
|
||||
from .fast_data_types import (
|
||||
glewInit, enable_automatic_opengl_error_checking, glClear, glClearColor,
|
||||
GL_COLOR_BUFFER_BIT, GLFW_CONTEXT_VERSION_MAJOR, GLFW_STENCIL_BITS,
|
||||
GLFW_CONTEXT_VERSION_MINOR, GLFW_OPENGL_PROFILE,
|
||||
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_CORE_PROFILE, GLFW_SAMPLES,
|
||||
glfw_set_error_callback, glfw_init, glfw_terminate, glfw_window_hint,
|
||||
glfw_swap_interval, glfw_wait_events, Window, change_wcwidth
|
||||
from .config import (
|
||||
cached_values, load_cached_values, load_config, save_cached_values
|
||||
)
|
||||
from .utils import safe_print
|
||||
from .constants import (
|
||||
appname, config_dir, isosx, logo_data_file, str_version, viewport_size
|
||||
)
|
||||
from .fast_data_types import (
|
||||
GL_COLOR_BUFFER_BIT, GLFW_CONTEXT_VERSION_MAJOR,
|
||||
GLFW_CONTEXT_VERSION_MINOR, GLFW_DECORATED, GLFW_OPENGL_CORE_PROFILE,
|
||||
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES,
|
||||
GLFW_STENCIL_BITS, Window, change_wcwidth, check_for_extensions,
|
||||
enable_automatic_opengl_error_checking, glClear, glClearColor, glewInit,
|
||||
glfw_init, glfw_init_hint_string, glfw_set_error_callback,
|
||||
glfw_swap_interval, glfw_terminate, glfw_wait_events, glfw_window_hint
|
||||
)
|
||||
from .layout import all_layouts
|
||||
from .shaders import GL_VERSION
|
||||
from .utils import detach, safe_print
|
||||
|
||||
try:
|
||||
from .fast_data_types import GLFW_X11_WM_CLASS_NAME, GLFW_X11_WM_CLASS_CLASS
|
||||
except ImportError:
|
||||
GLFW_X11_WM_CLASS_NAME = GLFW_X11_WM_CLASS_CLASS = None
|
||||
|
||||
|
||||
defconf = os.path.join(config_dir, 'kitty.conf')
|
||||
|
||||
|
||||
def option_parser():
|
||||
parser = argparse.ArgumentParser(prog=appname, description=_('The {} terminal emulator').format(appname))
|
||||
defconf = os.path.join(config_dir, 'kitty.conf')
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=appname,
|
||||
description=_('The {} terminal emulator').format(appname)
|
||||
)
|
||||
a = parser.add_argument
|
||||
a('--class', default=appname, dest='cls', help=_('Set the WM_CLASS property'))
|
||||
a('--config', default=defconf, help=_('Specify a path to the config file to use. Default: {}').format(defconf))
|
||||
a('--cmd', '-c', default=None, help=_('Run python code in the kitty context'))
|
||||
a('-d', '--directory', default='.', help=_('Change to the specified directory when launching'))
|
||||
a('--version', '-v', action='version', version='{} {} by Kovid Goyal'.format(appname, str_version))
|
||||
a('--profile', action='store_true', default=False, help=_('Show profiling data after exit'))
|
||||
a('--dump-commands', action='store_true', default=False, help=_('Output commands received from child process to stdout'))
|
||||
a('--replay-commands', default=None, help=_('Replay previously dumped commands'))
|
||||
a('--window-layout', default=None, choices=frozenset(all_layouts.keys()), help=_(
|
||||
'The window layout to use on startup'))
|
||||
a('--session', default=None, help=_(
|
||||
'Path to a file containing the startup session (tabs, windows, layout, programs)'))
|
||||
a('args', nargs=argparse.REMAINDER, help=_(
|
||||
'The remaining arguments are used to launch a program other than the default shell. Any further options are passed'
|
||||
' directly to the program being invoked.'
|
||||
))
|
||||
a(
|
||||
'--class',
|
||||
default=appname,
|
||||
dest='cls',
|
||||
help=_('Set the WM_CLASS property')
|
||||
)
|
||||
a(
|
||||
'--config',
|
||||
action='append',
|
||||
help=_(
|
||||
'Specify a path to the config file(s) to use.'
|
||||
' Can be specified multiple times to read multiple'
|
||||
' config files in sequence, which are merged. Default: {}'
|
||||
).format(defconf)
|
||||
)
|
||||
a(
|
||||
'--override',
|
||||
'-o',
|
||||
action='append',
|
||||
help=_(
|
||||
'Override individual configuration options, can be specified'
|
||||
' multiple times. Syntax: name=value. For example: {}'
|
||||
).format('-o font_size=20')
|
||||
)
|
||||
a(
|
||||
'--cmd',
|
||||
'-c',
|
||||
default=None,
|
||||
help=_('Run python code in the kitty context')
|
||||
)
|
||||
a(
|
||||
'-d',
|
||||
'--directory',
|
||||
default='.',
|
||||
help=_('Change to the specified directory when launching')
|
||||
)
|
||||
a(
|
||||
'--version',
|
||||
'-v',
|
||||
action='version',
|
||||
version='{} {} by Kovid Goyal'.format(appname, str_version)
|
||||
)
|
||||
a(
|
||||
'--profile',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Show profiling data after exit')
|
||||
)
|
||||
a(
|
||||
'--dump-commands',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Output commands received from child process to stdout')
|
||||
)
|
||||
if not isosx:
|
||||
a(
|
||||
'--detach',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Detach from the controlling terminal, if any')
|
||||
)
|
||||
a(
|
||||
'--replay-commands',
|
||||
default=None,
|
||||
help=_('Replay previously dumped commands')
|
||||
)
|
||||
a(
|
||||
'--dump-bytes',
|
||||
help=_('Path to file in which to store the raw bytes received from the'
|
||||
' child process. Useful for debugging.')
|
||||
)
|
||||
a(
|
||||
'--debug-gl',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help=_('Debug OpenGL commands. This will cause all OpenGL calls'
|
||||
' to check for errors instead of ignoring them. Useful'
|
||||
' when debugging rendering problems.')
|
||||
)
|
||||
a(
|
||||
'--window-layout',
|
||||
default=None,
|
||||
choices=frozenset(all_layouts.keys()),
|
||||
help=_('The window layout to use on startup')
|
||||
)
|
||||
a(
|
||||
'--session',
|
||||
default=None,
|
||||
help=_(
|
||||
'Path to a file containing the startup session (tabs, windows, layout, programs)'
|
||||
)
|
||||
)
|
||||
a(
|
||||
'args',
|
||||
nargs=argparse.REMAINDER,
|
||||
help=_(
|
||||
'The remaining arguments are used to launch a program other than the default shell. Any further options are passed'
|
||||
' directly to the program being invoked.'
|
||||
)
|
||||
)
|
||||
return parser
|
||||
|
||||
|
||||
def setup_opengl():
|
||||
def setup_opengl(opts):
|
||||
if opts.macos_hide_titlebar:
|
||||
glfw_window_hint(GLFW_DECORATED, False)
|
||||
glfw_window_hint(GLFW_CONTEXT_VERSION_MAJOR, GL_VERSION[0])
|
||||
glfw_window_hint(GLFW_CONTEXT_VERSION_MINOR, GL_VERSION[1])
|
||||
glfw_window_hint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
|
||||
@@ -87,7 +188,7 @@ def dispatch_pending_calls(boss):
|
||||
|
||||
|
||||
def run_app(opts, args):
|
||||
setup_opengl()
|
||||
setup_opengl(opts)
|
||||
load_cached_values()
|
||||
if 'window-size' in cached_values and opts.remember_window_size:
|
||||
ws = cached_values['window-size']
|
||||
@@ -100,11 +201,22 @@ def run_app(opts, args):
|
||||
else:
|
||||
viewport_size.width = opts.initial_window_width
|
||||
viewport_size.height = opts.initial_window_height
|
||||
window = Window(
|
||||
viewport_size.width, viewport_size.height, args.cls)
|
||||
try:
|
||||
window = Window(viewport_size.width, viewport_size.height, args.cls)
|
||||
except ValueError:
|
||||
safe_print('Failed to create GLFW window with initial size:', viewport_size)
|
||||
viewport_size.width = 640
|
||||
viewport_size.height = 400
|
||||
window = Window(viewport_size.width, viewport_size.height, args.cls)
|
||||
window.set_title(appname)
|
||||
window.make_context_current()
|
||||
if not isosx:
|
||||
if isosx:
|
||||
from .fast_data_types import cocoa_make_window_resizable, cocoa_create_global_menu
|
||||
check_for_extensions()
|
||||
cocoa_create_global_menu()
|
||||
if opts.macos_hide_titlebar:
|
||||
cocoa_make_window_resizable(window.cocoa_window_id())
|
||||
else:
|
||||
with open(logo_data_file, 'rb') as f:
|
||||
window.set_icon(f.read(), 256, 256)
|
||||
viewport_size.width, viewport_size.height = window.get_framebuffer_size()
|
||||
@@ -137,10 +249,38 @@ def on_glfw_error(code, msg):
|
||||
safe_print('[glfw error] ', msg, file=sys.stderr)
|
||||
|
||||
|
||||
def ensure_osx_locale():
|
||||
# Ensure the LANG env var is set. See
|
||||
# https://github.com/kovidgoyal/kitty/issues/90
|
||||
from .fast_data_types import cocoa_get_lang
|
||||
if 'LANG' not in os.environ:
|
||||
lang = cocoa_get_lang()
|
||||
if lang is not None:
|
||||
os.environ['LANG'] = lang + '.UTF-8'
|
||||
|
||||
|
||||
def main():
|
||||
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1' and getattr(sys, 'frozen', True):
|
||||
if isosx:
|
||||
ensure_osx_locale()
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except Exception:
|
||||
if not isosx:
|
||||
raise
|
||||
print('Failed to set locale with LANG:', os.environ.get('LANG'), file=sys.stderr)
|
||||
os.environ.pop('LANG')
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
except Exception:
|
||||
print('Failed to set locale with no LANG, ignoring', file=sys.stderr)
|
||||
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES',
|
||||
None) == '1' and getattr(sys, 'frozen', True):
|
||||
os.chdir(os.path.expanduser('~'))
|
||||
if not os.path.isdir(os.getcwd()):
|
||||
os.chdir(os.path.expanduser('~'))
|
||||
args = option_parser().parse_args()
|
||||
if getattr(args, 'detach', False):
|
||||
detach()
|
||||
if args.cmd:
|
||||
exec(args.cmd)
|
||||
return
|
||||
@@ -148,10 +288,14 @@ def main():
|
||||
from kitty.client import main
|
||||
main(args.replay_commands)
|
||||
return
|
||||
opts = load_config(args.config)
|
||||
config = args.config or (defconf, )
|
||||
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
|
||||
opts = load_config(*config, overrides=overrides)
|
||||
change_wcwidth(not opts.use_system_wcwidth)
|
||||
glfw_set_error_callback(on_glfw_error)
|
||||
enable_automatic_opengl_error_checking(False)
|
||||
enable_automatic_opengl_error_checking(args.debug_gl)
|
||||
if GLFW_X11_WM_CLASS_CLASS is not None:
|
||||
glfw_init_hint_string(GLFW_X11_WM_CLASS_CLASS, opts.cls)
|
||||
if not glfw_init():
|
||||
raise SystemExit('GLFW initialization failed')
|
||||
try:
|
||||
|
||||
@@ -75,3 +75,9 @@
|
||||
#define BRACKETED_PASTE (2004 << 5)
|
||||
#define BRACKETED_PASTE_START "\033[200~"
|
||||
#define BRACKETED_PASTE_END "\033[201~"
|
||||
|
||||
// Styled underlines
|
||||
#define STYLED_UNDERLINES (2016 << 5)
|
||||
|
||||
// Extended keyboard protocol
|
||||
#define EXTENDED_KEYBOARD (2017 << 5)
|
||||
|
||||
@@ -58,5 +58,5 @@ def encode_mouse_event(tracking_mode, tracking_protocol, button, action, mods, x
|
||||
ans = bytes(ans)
|
||||
else:
|
||||
if x <= 223 and y <= 223:
|
||||
ans = bytearray([0o33, ord('['), cb + 32, x + 32, y + 32])
|
||||
ans = bytearray([0o33, ord('['), ord('M'), cb + 32, x + 32, y + 32])
|
||||
return ans
|
||||
|
||||
@@ -96,6 +96,9 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
|
||||
#define REPORT_OSC(name, string) \
|
||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", #name, string)); PyErr_Clear();
|
||||
|
||||
#define REPORT_OSC2(name, code, string) \
|
||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sIO", #name, code, string)); PyErr_Clear();
|
||||
|
||||
#else
|
||||
|
||||
#define DUMP_UNUSED UNUSED
|
||||
@@ -107,6 +110,7 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
|
||||
#define REPORT_PARAMS(...)
|
||||
#define FLUSH_DRAW
|
||||
#define REPORT_OSC(name, string)
|
||||
#define REPORT_OSC2(name, code, string)
|
||||
|
||||
#endif
|
||||
|
||||
@@ -225,10 +229,12 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
|
||||
switch(ch) {
|
||||
case '@':
|
||||
REPORT_COMMAND(screen_use_latin1, 1);
|
||||
screen->use_latin1 = true; screen->utf8_state = 0; break;
|
||||
screen_use_latin1(screen, true);
|
||||
break;
|
||||
case 'G':
|
||||
REPORT_COMMAND(screen_use_latin1, 0);
|
||||
screen->use_latin1 = false; screen->utf8_state = 0; break;
|
||||
screen_use_latin1(screen, false);
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Unhandled Esc %% code: 0x%x", ch); break;
|
||||
}
|
||||
@@ -264,7 +270,7 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
|
||||
static inline void
|
||||
dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
#define DISPATCH_OSC(name) REPORT_OSC(name, string); name(screen, string);
|
||||
#define SET_COLOR(name) REPORT_OSC(name, string); name(screen, code, string);
|
||||
#define SET_COLOR(name) REPORT_OSC2(name, code, string); name(screen, code, string);
|
||||
const unsigned int limit = screen->parser_buf_pos;
|
||||
unsigned int code=0, i;
|
||||
for (i = 0; i < MIN(limit, 5); i++) {
|
||||
@@ -293,8 +299,14 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
break;
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
case 17:
|
||||
case 19:
|
||||
case 110:
|
||||
case 111:
|
||||
case 112:
|
||||
case 117:
|
||||
case 119:
|
||||
SET_COLOR(set_dynamic_color);
|
||||
break;
|
||||
default:
|
||||
@@ -314,7 +326,8 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
case '"': \
|
||||
case '*': \
|
||||
case '\'': \
|
||||
case ' ':
|
||||
case ' ': \
|
||||
case '$':
|
||||
|
||||
|
||||
static inline void
|
||||
@@ -322,11 +335,7 @@ screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, fa
|
||||
static inline void
|
||||
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
|
||||
static inline void
|
||||
screen_indexn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1, count); i++) screen_index(s); }
|
||||
static inline void
|
||||
screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1, count); i++) screen_tab(s); }
|
||||
static inline void
|
||||
screen_reverse_indexn(Screen *s, unsigned int count) { for (index_type i=0; i < count; i++) screen_reverse_index(s); }
|
||||
static inline void
|
||||
save_cursor(Screen *s, unsigned int UNUSED param, bool private) {
|
||||
if (private) fprintf(stderr, "%s %s", ERROR_PREFIX, "CSI s in private mode not supported");
|
||||
@@ -353,6 +362,12 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
name(screen, p1, private); \
|
||||
break;
|
||||
|
||||
#define CALL_CSI_HANDLER1S(name, defval) \
|
||||
p1 = num_params > 0 ? params[0] : defval; \
|
||||
REPORT_COMMAND(name, p1, start_modifier); \
|
||||
name(screen, p1, start_modifier); \
|
||||
break;
|
||||
|
||||
#define CALL_CSI_HANDLER1M(name, defval) \
|
||||
p1 = num_params > 0 ? params[0] : defval; \
|
||||
REPORT_COMMAND(name, p1, end_modifier); \
|
||||
@@ -449,7 +464,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
case ECH:
|
||||
CALL_CSI_HANDLER1(screen_erase_characters, 1);
|
||||
case DA:
|
||||
CALL_CSI_HANDLER1P(report_device_attributes, 0, '>');
|
||||
CALL_CSI_HANDLER1S(report_device_attributes, 0);
|
||||
case TBC:
|
||||
CALL_CSI_HANDLER1(screen_clear_tab_stop, 0);
|
||||
case SM:
|
||||
@@ -469,9 +484,17 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
||||
case DECSCUSR:
|
||||
CALL_CSI_HANDLER1M(screen_set_cursor, 1);
|
||||
case SU:
|
||||
CALL_CSI_HANDLER1(screen_indexn, 1);
|
||||
CALL_CSI_HANDLER1(screen_scroll, 1);
|
||||
case SD:
|
||||
CALL_CSI_HANDLER1(screen_reverse_indexn, 1);
|
||||
CALL_CSI_HANDLER1(screen_reverse_scroll, 1);
|
||||
case DECSTR:
|
||||
if (end_modifier == '$') {
|
||||
// DECRQM
|
||||
CALL_CSI_HANDLER1P(report_mode_status, 0, '?');
|
||||
} else {
|
||||
REPORT_ERROR("Unknown DECSTR CSI sequence with start and end modifiers: 0x%x 0x%x", start_modifier, end_modifier);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
REPORT_ERROR("Unknown CSI code: 0x%x", code);
|
||||
}
|
||||
@@ -510,16 +533,17 @@ accumulate_osc(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
|
||||
switch(ch) {
|
||||
case ST:
|
||||
return true;
|
||||
case ESC_ST:
|
||||
if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos - 1] == ESC) {
|
||||
screen->parser_buf_pos--;
|
||||
return true;
|
||||
}
|
||||
case BEL:
|
||||
return true;
|
||||
case NUL:
|
||||
case DEL:
|
||||
break;
|
||||
case ESC_ST:
|
||||
if (screen->parser_buf_pos > 0 && screen->parser_buf[screen->parser_buf_pos - 1] == ESC) {
|
||||
screen->parser_buf_pos--;
|
||||
return true;
|
||||
}
|
||||
/* fallthrough */
|
||||
default:
|
||||
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
||||
REPORT_ERROR("OSC sequence too long, truncating.");
|
||||
@@ -571,6 +595,7 @@ accumulate_oth(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_callback)
|
||||
screen->parser_buf_pos--;
|
||||
return true;
|
||||
}
|
||||
/* fallthrough */
|
||||
default:
|
||||
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
|
||||
REPORT_ERROR("OTH sequence too long, truncating.");
|
||||
@@ -679,17 +704,17 @@ dispatch_unicode_char(Screen *screen, uint32_t codepoint, PyObject DUMP_UNUSED *
|
||||
|
||||
static inline void
|
||||
_parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) {
|
||||
uint32_t prev = screen->utf8_state, codepoint = 0;
|
||||
uint32_t prev = screen->utf8_state;
|
||||
for (unsigned int i = 0; i < len; i++) {
|
||||
if (screen->use_latin1) dispatch_unicode_char(screen, latin1_charset[buf[i]], dump_callback);
|
||||
else {
|
||||
switch (decode_utf8(&screen->utf8_state, &codepoint, buf[i])) {
|
||||
switch (decode_utf8(&screen->utf8_state, &screen->utf8_codepoint, buf[i])) {
|
||||
case UTF8_ACCEPT:
|
||||
dispatch_unicode_char(screen, codepoint, dump_callback);
|
||||
dispatch_unicode_char(screen, screen->utf8_codepoint, dump_callback);
|
||||
break;
|
||||
case UTF8_REJECT:
|
||||
screen->utf8_state = UTF8_ACCEPT;
|
||||
if (prev != UTF8_ACCEPT) i--;
|
||||
if (prev != UTF8_ACCEPT && i > 0) i--;
|
||||
break;
|
||||
}
|
||||
prev = screen->utf8_state;
|
||||
@@ -744,6 +769,11 @@ FNAME(read_bytes)(PyObject UNUSED *self, PyObject *args) {
|
||||
/* PyObject_Print(Py_BuildValue("y#", screen->read_buf, len), stderr, 0); */
|
||||
break;
|
||||
}
|
||||
#ifdef DUMP_COMMANDS
|
||||
if (len > 0) {
|
||||
Py_XDECREF(PyObject_CallFunction(dump_callback, "sy#", "bytes", screen->read_buf, len)); PyErr_Clear();
|
||||
}
|
||||
#endif
|
||||
_parse_bytes(screen, screen->read_buf, len, dump_callback);
|
||||
if(len > 0) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
|
||||
@@ -43,8 +43,9 @@
|
||||
|
||||
static inline void copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_type num) {
|
||||
memcpy(dest->chars + dest_at, src->chars + src_at, num * sizeof(char_type));
|
||||
memcpy(dest->colors + dest_at, src->colors + src_at, num * sizeof(color_type));
|
||||
memcpy(dest->decoration_fg + dest_at, src->decoration_fg + src_at, num * sizeof(decoration_type));
|
||||
memcpy(dest->fg_colors + dest_at, src->fg_colors + src_at, num * sizeof(color_type));
|
||||
memcpy(dest->bg_colors + dest_at, src->bg_colors + src_at, num * sizeof(color_type));
|
||||
memcpy(dest->decoration_fg + dest_at, src->decoration_fg + src_at, num * sizeof(color_type));
|
||||
memcpy(dest->combining_chars + dest_at, src->combining_chars + src_at, num * sizeof(combining_type));
|
||||
}
|
||||
|
||||
|
||||
816
kitty/rgb.py
Normal file
816
kitty/rgb.py
Normal file
@@ -0,0 +1,816 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
Color = namedtuple('Color', 'red green blue')
|
||||
|
||||
color_pat = re.compile(r'^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$')
|
||||
color_pat2 = re.compile(
|
||||
r'rgb:([a-f0-9]{2})/([a-f0-9]{2})/([a-f0-9]{2})$', re.IGNORECASE
|
||||
)
|
||||
|
||||
|
||||
def to_color(raw, validate=False):
|
||||
x = raw.strip().lower()
|
||||
ans = color_names.get(x)
|
||||
if ans is not None:
|
||||
return ans
|
||||
m = color_pat.match(x)
|
||||
val = None
|
||||
if m is not None:
|
||||
val = m.group(1)
|
||||
if len(val) == 3:
|
||||
val = ''.join(2 * s for s in val)
|
||||
else:
|
||||
m = color_pat2.match(x)
|
||||
if m is not None:
|
||||
val = m.group(1) + m.group(2) + m.group(3)
|
||||
if val is None:
|
||||
if validate:
|
||||
raise ValueError('Invalid color name: {}'.format(raw))
|
||||
return
|
||||
return Color(int(val[:2], 16), int(val[2:4], 16), int(val[4:], 16))
|
||||
|
||||
|
||||
# BEGIN_DATA_SECTION {{{
|
||||
color_names = {
|
||||
'alice blue': Color(240, 248, 255),
|
||||
'aliceblue': Color(240, 248, 255),
|
||||
'antique white': Color(250, 235, 215),
|
||||
'antiquewhite': Color(250, 235, 215),
|
||||
'antiquewhite1': Color(255, 239, 219),
|
||||
'antiquewhite2': Color(238, 223, 204),
|
||||
'antiquewhite3': Color(205, 192, 176),
|
||||
'antiquewhite4': Color(139, 131, 120),
|
||||
'aquamarine': Color(127, 255, 212),
|
||||
'aquamarine1': Color(127, 255, 212),
|
||||
'aquamarine2': Color(118, 238, 198),
|
||||
'aquamarine3': Color(102, 205, 170),
|
||||
'aquamarine4': Color(69, 139, 116),
|
||||
'azure': Color(240, 255, 255),
|
||||
'azure1': Color(240, 255, 255),
|
||||
'azure2': Color(224, 238, 238),
|
||||
'azure3': Color(193, 205, 205),
|
||||
'azure4': Color(131, 139, 139),
|
||||
'beige': Color(245, 245, 220),
|
||||
'bisque': Color(255, 228, 196),
|
||||
'bisque1': Color(255, 228, 196),
|
||||
'bisque2': Color(238, 213, 183),
|
||||
'bisque3': Color(205, 183, 158),
|
||||
'bisque4': Color(139, 125, 107),
|
||||
'black': Color(0, 0, 0),
|
||||
'blanched almond': Color(255, 235, 205),
|
||||
'blanchedalmond': Color(255, 235, 205),
|
||||
'blue': Color(0, 0, 255),
|
||||
'blue violet': Color(138, 43, 226),
|
||||
'blue1': Color(0, 0, 255),
|
||||
'blue2': Color(0, 0, 238),
|
||||
'blue3': Color(0, 0, 205),
|
||||
'blue4': Color(0, 0, 139),
|
||||
'blueviolet': Color(138, 43, 226),
|
||||
'brown': Color(165, 42, 42),
|
||||
'brown1': Color(255, 64, 64),
|
||||
'brown2': Color(238, 59, 59),
|
||||
'brown3': Color(205, 51, 51),
|
||||
'brown4': Color(139, 35, 35),
|
||||
'burlywood': Color(222, 184, 135),
|
||||
'burlywood1': Color(255, 211, 155),
|
||||
'burlywood2': Color(238, 197, 145),
|
||||
'burlywood3': Color(205, 170, 125),
|
||||
'burlywood4': Color(139, 115, 85),
|
||||
'cadet blue': Color(95, 158, 160),
|
||||
'cadetblue': Color(95, 158, 160),
|
||||
'cadetblue1': Color(152, 245, 255),
|
||||
'cadetblue2': Color(142, 229, 238),
|
||||
'cadetblue3': Color(122, 197, 205),
|
||||
'cadetblue4': Color(83, 134, 139),
|
||||
'chartreuse': Color(127, 255, 0),
|
||||
'chartreuse1': Color(127, 255, 0),
|
||||
'chartreuse2': Color(118, 238, 0),
|
||||
'chartreuse3': Color(102, 205, 0),
|
||||
'chartreuse4': Color(69, 139, 0),
|
||||
'chocolate': Color(210, 105, 30),
|
||||
'chocolate1': Color(255, 127, 36),
|
||||
'chocolate2': Color(238, 118, 33),
|
||||
'chocolate3': Color(205, 102, 29),
|
||||
'chocolate4': Color(139, 69, 19),
|
||||
'coral': Color(255, 127, 80),
|
||||
'coral1': Color(255, 114, 86),
|
||||
'coral2': Color(238, 106, 80),
|
||||
'coral3': Color(205, 91, 69),
|
||||
'coral4': Color(139, 62, 47),
|
||||
'cornflower blue': Color(100, 149, 237),
|
||||
'cornflowerblue': Color(100, 149, 237),
|
||||
'cornsilk': Color(255, 248, 220),
|
||||
'cornsilk1': Color(255, 248, 220),
|
||||
'cornsilk2': Color(238, 232, 205),
|
||||
'cornsilk3': Color(205, 200, 177),
|
||||
'cornsilk4': Color(139, 136, 120),
|
||||
'cyan': Color(0, 255, 255),
|
||||
'cyan1': Color(0, 255, 255),
|
||||
'cyan2': Color(0, 238, 238),
|
||||
'cyan3': Color(0, 205, 205),
|
||||
'cyan4': Color(0, 139, 139),
|
||||
'dark blue': Color(0, 0, 139),
|
||||
'dark cyan': Color(0, 139, 139),
|
||||
'dark goldenrod': Color(184, 134, 11),
|
||||
'dark gray': Color(169, 169, 169),
|
||||
'dark green': Color(0, 100, 0),
|
||||
'dark grey': Color(169, 169, 169),
|
||||
'dark khaki': Color(189, 183, 107),
|
||||
'dark magenta': Color(139, 0, 139),
|
||||
'dark olive green': Color(85, 107, 47),
|
||||
'dark orange': Color(255, 140, 0),
|
||||
'dark orchid': Color(153, 50, 204),
|
||||
'dark red': Color(139, 0, 0),
|
||||
'dark salmon': Color(233, 150, 122),
|
||||
'dark sea green': Color(143, 188, 143),
|
||||
'dark slate blue': Color(72, 61, 139),
|
||||
'dark slate gray': Color(47, 79, 79),
|
||||
'dark slate grey': Color(47, 79, 79),
|
||||
'dark turquoise': Color(0, 206, 209),
|
||||
'dark violet': Color(148, 0, 211),
|
||||
'darkblue': Color(0, 0, 139),
|
||||
'darkcyan': Color(0, 139, 139),
|
||||
'darkgoldenrod': Color(184, 134, 11),
|
||||
'darkgoldenrod1': Color(255, 185, 15),
|
||||
'darkgoldenrod2': Color(238, 173, 14),
|
||||
'darkgoldenrod3': Color(205, 149, 12),
|
||||
'darkgoldenrod4': Color(139, 101, 8),
|
||||
'darkgray': Color(169, 169, 169),
|
||||
'darkgreen': Color(0, 100, 0),
|
||||
'darkgrey': Color(169, 169, 169),
|
||||
'darkkhaki': Color(189, 183, 107),
|
||||
'darkmagenta': Color(139, 0, 139),
|
||||
'darkolivegreen': Color(85, 107, 47),
|
||||
'darkolivegreen1': Color(202, 255, 112),
|
||||
'darkolivegreen2': Color(188, 238, 104),
|
||||
'darkolivegreen3': Color(162, 205, 90),
|
||||
'darkolivegreen4': Color(110, 139, 61),
|
||||
'darkorange': Color(255, 140, 0),
|
||||
'darkorange1': Color(255, 127, 0),
|
||||
'darkorange2': Color(238, 118, 0),
|
||||
'darkorange3': Color(205, 102, 0),
|
||||
'darkorange4': Color(139, 69, 0),
|
||||
'darkorchid': Color(153, 50, 204),
|
||||
'darkorchid1': Color(191, 62, 255),
|
||||
'darkorchid2': Color(178, 58, 238),
|
||||
'darkorchid3': Color(154, 50, 205),
|
||||
'darkorchid4': Color(104, 34, 139),
|
||||
'darkred': Color(139, 0, 0),
|
||||
'darksalmon': Color(233, 150, 122),
|
||||
'darkseagreen': Color(143, 188, 143),
|
||||
'darkseagreen1': Color(193, 255, 193),
|
||||
'darkseagreen2': Color(180, 238, 180),
|
||||
'darkseagreen3': Color(155, 205, 155),
|
||||
'darkseagreen4': Color(105, 139, 105),
|
||||
'darkslateblue': Color(72, 61, 139),
|
||||
'darkslategray': Color(47, 79, 79),
|
||||
'darkslategray1': Color(151, 255, 255),
|
||||
'darkslategray2': Color(141, 238, 238),
|
||||
'darkslategray3': Color(121, 205, 205),
|
||||
'darkslategray4': Color(82, 139, 139),
|
||||
'darkslategrey': Color(47, 79, 79),
|
||||
'darkturquoise': Color(0, 206, 209),
|
||||
'darkviolet': Color(148, 0, 211),
|
||||
'debianred': Color(215, 7, 81),
|
||||
'deep pink': Color(255, 20, 147),
|
||||
'deep sky blue': Color(0, 191, 255),
|
||||
'deeppink': Color(255, 20, 147),
|
||||
'deeppink1': Color(255, 20, 147),
|
||||
'deeppink2': Color(238, 18, 137),
|
||||
'deeppink3': Color(205, 16, 118),
|
||||
'deeppink4': Color(139, 10, 80),
|
||||
'deepskyblue': Color(0, 191, 255),
|
||||
'deepskyblue1': Color(0, 191, 255),
|
||||
'deepskyblue2': Color(0, 178, 238),
|
||||
'deepskyblue3': Color(0, 154, 205),
|
||||
'deepskyblue4': Color(0, 104, 139),
|
||||
'dim gray': Color(105, 105, 105),
|
||||
'dim grey': Color(105, 105, 105),
|
||||
'dimgray': Color(105, 105, 105),
|
||||
'dimgrey': Color(105, 105, 105),
|
||||
'dodger blue': Color(30, 144, 255),
|
||||
'dodgerblue': Color(30, 144, 255),
|
||||
'dodgerblue1': Color(30, 144, 255),
|
||||
'dodgerblue2': Color(28, 134, 238),
|
||||
'dodgerblue3': Color(24, 116, 205),
|
||||
'dodgerblue4': Color(16, 78, 139),
|
||||
'firebrick': Color(178, 34, 34),
|
||||
'firebrick1': Color(255, 48, 48),
|
||||
'firebrick2': Color(238, 44, 44),
|
||||
'firebrick3': Color(205, 38, 38),
|
||||
'firebrick4': Color(139, 26, 26),
|
||||
'floral white': Color(255, 250, 240),
|
||||
'floralwhite': Color(255, 250, 240),
|
||||
'forest green': Color(34, 139, 34),
|
||||
'forestgreen': Color(34, 139, 34),
|
||||
'gainsboro': Color(220, 220, 220),
|
||||
'ghost white': Color(248, 248, 255),
|
||||
'ghostwhite': Color(248, 248, 255),
|
||||
'gold': Color(255, 215, 0),
|
||||
'gold1': Color(255, 215, 0),
|
||||
'gold2': Color(238, 201, 0),
|
||||
'gold3': Color(205, 173, 0),
|
||||
'gold4': Color(139, 117, 0),
|
||||
'goldenrod': Color(218, 165, 32),
|
||||
'goldenrod1': Color(255, 193, 37),
|
||||
'goldenrod2': Color(238, 180, 34),
|
||||
'goldenrod3': Color(205, 155, 29),
|
||||
'goldenrod4': Color(139, 105, 20),
|
||||
'gray': Color(190, 190, 190),
|
||||
'gray0': Color(0, 0, 0),
|
||||
'gray1': Color(3, 3, 3),
|
||||
'gray10': Color(26, 26, 26),
|
||||
'gray100': Color(255, 255, 255),
|
||||
'gray11': Color(28, 28, 28),
|
||||
'gray12': Color(31, 31, 31),
|
||||
'gray13': Color(33, 33, 33),
|
||||
'gray14': Color(36, 36, 36),
|
||||
'gray15': Color(38, 38, 38),
|
||||
'gray16': Color(41, 41, 41),
|
||||
'gray17': Color(43, 43, 43),
|
||||
'gray18': Color(46, 46, 46),
|
||||
'gray19': Color(48, 48, 48),
|
||||
'gray2': Color(5, 5, 5),
|
||||
'gray20': Color(51, 51, 51),
|
||||
'gray21': Color(54, 54, 54),
|
||||
'gray22': Color(56, 56, 56),
|
||||
'gray23': Color(59, 59, 59),
|
||||
'gray24': Color(61, 61, 61),
|
||||
'gray25': Color(64, 64, 64),
|
||||
'gray26': Color(66, 66, 66),
|
||||
'gray27': Color(69, 69, 69),
|
||||
'gray28': Color(71, 71, 71),
|
||||
'gray29': Color(74, 74, 74),
|
||||
'gray3': Color(8, 8, 8),
|
||||
'gray30': Color(77, 77, 77),
|
||||
'gray31': Color(79, 79, 79),
|
||||
'gray32': Color(82, 82, 82),
|
||||
'gray33': Color(84, 84, 84),
|
||||
'gray34': Color(87, 87, 87),
|
||||
'gray35': Color(89, 89, 89),
|
||||
'gray36': Color(92, 92, 92),
|
||||
'gray37': Color(94, 94, 94),
|
||||
'gray38': Color(97, 97, 97),
|
||||
'gray39': Color(99, 99, 99),
|
||||
'gray4': Color(10, 10, 10),
|
||||
'gray40': Color(102, 102, 102),
|
||||
'gray41': Color(105, 105, 105),
|
||||
'gray42': Color(107, 107, 107),
|
||||
'gray43': Color(110, 110, 110),
|
||||
'gray44': Color(112, 112, 112),
|
||||
'gray45': Color(115, 115, 115),
|
||||
'gray46': Color(117, 117, 117),
|
||||
'gray47': Color(120, 120, 120),
|
||||
'gray48': Color(122, 122, 122),
|
||||
'gray49': Color(125, 125, 125),
|
||||
'gray5': Color(13, 13, 13),
|
||||
'gray50': Color(127, 127, 127),
|
||||
'gray51': Color(130, 130, 130),
|
||||
'gray52': Color(133, 133, 133),
|
||||
'gray53': Color(135, 135, 135),
|
||||
'gray54': Color(138, 138, 138),
|
||||
'gray55': Color(140, 140, 140),
|
||||
'gray56': Color(143, 143, 143),
|
||||
'gray57': Color(145, 145, 145),
|
||||
'gray58': Color(148, 148, 148),
|
||||
'gray59': Color(150, 150, 150),
|
||||
'gray6': Color(15, 15, 15),
|
||||
'gray60': Color(153, 153, 153),
|
||||
'gray61': Color(156, 156, 156),
|
||||
'gray62': Color(158, 158, 158),
|
||||
'gray63': Color(161, 161, 161),
|
||||
'gray64': Color(163, 163, 163),
|
||||
'gray65': Color(166, 166, 166),
|
||||
'gray66': Color(168, 168, 168),
|
||||
'gray67': Color(171, 171, 171),
|
||||
'gray68': Color(173, 173, 173),
|
||||
'gray69': Color(176, 176, 176),
|
||||
'gray7': Color(18, 18, 18),
|
||||
'gray70': Color(179, 179, 179),
|
||||
'gray71': Color(181, 181, 181),
|
||||
'gray72': Color(184, 184, 184),
|
||||
'gray73': Color(186, 186, 186),
|
||||
'gray74': Color(189, 189, 189),
|
||||
'gray75': Color(191, 191, 191),
|
||||
'gray76': Color(194, 194, 194),
|
||||
'gray77': Color(196, 196, 196),
|
||||
'gray78': Color(199, 199, 199),
|
||||
'gray79': Color(201, 201, 201),
|
||||
'gray8': Color(20, 20, 20),
|
||||
'gray80': Color(204, 204, 204),
|
||||
'gray81': Color(207, 207, 207),
|
||||
'gray82': Color(209, 209, 209),
|
||||
'gray83': Color(212, 212, 212),
|
||||
'gray84': Color(214, 214, 214),
|
||||
'gray85': Color(217, 217, 217),
|
||||
'gray86': Color(219, 219, 219),
|
||||
'gray87': Color(222, 222, 222),
|
||||
'gray88': Color(224, 224, 224),
|
||||
'gray89': Color(227, 227, 227),
|
||||
'gray9': Color(23, 23, 23),
|
||||
'gray90': Color(229, 229, 229),
|
||||
'gray91': Color(232, 232, 232),
|
||||
'gray92': Color(235, 235, 235),
|
||||
'gray93': Color(237, 237, 237),
|
||||
'gray94': Color(240, 240, 240),
|
||||
'gray95': Color(242, 242, 242),
|
||||
'gray96': Color(245, 245, 245),
|
||||
'gray97': Color(247, 247, 247),
|
||||
'gray98': Color(250, 250, 250),
|
||||
'gray99': Color(252, 252, 252),
|
||||
'green': Color(0, 255, 0),
|
||||
'green yellow': Color(173, 255, 47),
|
||||
'green1': Color(0, 255, 0),
|
||||
'green2': Color(0, 238, 0),
|
||||
'green3': Color(0, 205, 0),
|
||||
'green4': Color(0, 139, 0),
|
||||
'greenyellow': Color(173, 255, 47),
|
||||
'grey': Color(190, 190, 190),
|
||||
'grey0': Color(0, 0, 0),
|
||||
'grey1': Color(3, 3, 3),
|
||||
'grey10': Color(26, 26, 26),
|
||||
'grey100': Color(255, 255, 255),
|
||||
'grey11': Color(28, 28, 28),
|
||||
'grey12': Color(31, 31, 31),
|
||||
'grey13': Color(33, 33, 33),
|
||||
'grey14': Color(36, 36, 36),
|
||||
'grey15': Color(38, 38, 38),
|
||||
'grey16': Color(41, 41, 41),
|
||||
'grey17': Color(43, 43, 43),
|
||||
'grey18': Color(46, 46, 46),
|
||||
'grey19': Color(48, 48, 48),
|
||||
'grey2': Color(5, 5, 5),
|
||||
'grey20': Color(51, 51, 51),
|
||||
'grey21': Color(54, 54, 54),
|
||||
'grey22': Color(56, 56, 56),
|
||||
'grey23': Color(59, 59, 59),
|
||||
'grey24': Color(61, 61, 61),
|
||||
'grey25': Color(64, 64, 64),
|
||||
'grey26': Color(66, 66, 66),
|
||||
'grey27': Color(69, 69, 69),
|
||||
'grey28': Color(71, 71, 71),
|
||||
'grey29': Color(74, 74, 74),
|
||||
'grey3': Color(8, 8, 8),
|
||||
'grey30': Color(77, 77, 77),
|
||||
'grey31': Color(79, 79, 79),
|
||||
'grey32': Color(82, 82, 82),
|
||||
'grey33': Color(84, 84, 84),
|
||||
'grey34': Color(87, 87, 87),
|
||||
'grey35': Color(89, 89, 89),
|
||||
'grey36': Color(92, 92, 92),
|
||||
'grey37': Color(94, 94, 94),
|
||||
'grey38': Color(97, 97, 97),
|
||||
'grey39': Color(99, 99, 99),
|
||||
'grey4': Color(10, 10, 10),
|
||||
'grey40': Color(102, 102, 102),
|
||||
'grey41': Color(105, 105, 105),
|
||||
'grey42': Color(107, 107, 107),
|
||||
'grey43': Color(110, 110, 110),
|
||||
'grey44': Color(112, 112, 112),
|
||||
'grey45': Color(115, 115, 115),
|
||||
'grey46': Color(117, 117, 117),
|
||||
'grey47': Color(120, 120, 120),
|
||||
'grey48': Color(122, 122, 122),
|
||||
'grey49': Color(125, 125, 125),
|
||||
'grey5': Color(13, 13, 13),
|
||||
'grey50': Color(127, 127, 127),
|
||||
'grey51': Color(130, 130, 130),
|
||||
'grey52': Color(133, 133, 133),
|
||||
'grey53': Color(135, 135, 135),
|
||||
'grey54': Color(138, 138, 138),
|
||||
'grey55': Color(140, 140, 140),
|
||||
'grey56': Color(143, 143, 143),
|
||||
'grey57': Color(145, 145, 145),
|
||||
'grey58': Color(148, 148, 148),
|
||||
'grey59': Color(150, 150, 150),
|
||||
'grey6': Color(15, 15, 15),
|
||||
'grey60': Color(153, 153, 153),
|
||||
'grey61': Color(156, 156, 156),
|
||||
'grey62': Color(158, 158, 158),
|
||||
'grey63': Color(161, 161, 161),
|
||||
'grey64': Color(163, 163, 163),
|
||||
'grey65': Color(166, 166, 166),
|
||||
'grey66': Color(168, 168, 168),
|
||||
'grey67': Color(171, 171, 171),
|
||||
'grey68': Color(173, 173, 173),
|
||||
'grey69': Color(176, 176, 176),
|
||||
'grey7': Color(18, 18, 18),
|
||||
'grey70': Color(179, 179, 179),
|
||||
'grey71': Color(181, 181, 181),
|
||||
'grey72': Color(184, 184, 184),
|
||||
'grey73': Color(186, 186, 186),
|
||||
'grey74': Color(189, 189, 189),
|
||||
'grey75': Color(191, 191, 191),
|
||||
'grey76': Color(194, 194, 194),
|
||||
'grey77': Color(196, 196, 196),
|
||||
'grey78': Color(199, 199, 199),
|
||||
'grey79': Color(201, 201, 201),
|
||||
'grey8': Color(20, 20, 20),
|
||||
'grey80': Color(204, 204, 204),
|
||||
'grey81': Color(207, 207, 207),
|
||||
'grey82': Color(209, 209, 209),
|
||||
'grey83': Color(212, 212, 212),
|
||||
'grey84': Color(214, 214, 214),
|
||||
'grey85': Color(217, 217, 217),
|
||||
'grey86': Color(219, 219, 219),
|
||||
'grey87': Color(222, 222, 222),
|
||||
'grey88': Color(224, 224, 224),
|
||||
'grey89': Color(227, 227, 227),
|
||||
'grey9': Color(23, 23, 23),
|
||||
'grey90': Color(229, 229, 229),
|
||||
'grey91': Color(232, 232, 232),
|
||||
'grey92': Color(235, 235, 235),
|
||||
'grey93': Color(237, 237, 237),
|
||||
'grey94': Color(240, 240, 240),
|
||||
'grey95': Color(242, 242, 242),
|
||||
'grey96': Color(245, 245, 245),
|
||||
'grey97': Color(247, 247, 247),
|
||||
'grey98': Color(250, 250, 250),
|
||||
'grey99': Color(252, 252, 252),
|
||||
'honeydew': Color(240, 255, 240),
|
||||
'honeydew1': Color(240, 255, 240),
|
||||
'honeydew2': Color(224, 238, 224),
|
||||
'honeydew3': Color(193, 205, 193),
|
||||
'honeydew4': Color(131, 139, 131),
|
||||
'hot pink': Color(255, 105, 180),
|
||||
'hotpink': Color(255, 105, 180),
|
||||
'hotpink1': Color(255, 110, 180),
|
||||
'hotpink2': Color(238, 106, 167),
|
||||
'hotpink3': Color(205, 96, 144),
|
||||
'hotpink4': Color(139, 58, 98),
|
||||
'indian red': Color(205, 92, 92),
|
||||
'indianred': Color(205, 92, 92),
|
||||
'indianred1': Color(255, 106, 106),
|
||||
'indianred2': Color(238, 99, 99),
|
||||
'indianred3': Color(205, 85, 85),
|
||||
'indianred4': Color(139, 58, 58),
|
||||
'ivory': Color(255, 255, 240),
|
||||
'ivory1': Color(255, 255, 240),
|
||||
'ivory2': Color(238, 238, 224),
|
||||
'ivory3': Color(205, 205, 193),
|
||||
'ivory4': Color(139, 139, 131),
|
||||
'khaki': Color(240, 230, 140),
|
||||
'khaki1': Color(255, 246, 143),
|
||||
'khaki2': Color(238, 230, 133),
|
||||
'khaki3': Color(205, 198, 115),
|
||||
'khaki4': Color(139, 134, 78),
|
||||
'lavender': Color(230, 230, 250),
|
||||
'lavender blush': Color(255, 240, 245),
|
||||
'lavenderblush': Color(255, 240, 245),
|
||||
'lavenderblush1': Color(255, 240, 245),
|
||||
'lavenderblush2': Color(238, 224, 229),
|
||||
'lavenderblush3': Color(205, 193, 197),
|
||||
'lavenderblush4': Color(139, 131, 134),
|
||||
'lawn green': Color(124, 252, 0),
|
||||
'lawngreen': Color(124, 252, 0),
|
||||
'lemon chiffon': Color(255, 250, 205),
|
||||
'lemonchiffon': Color(255, 250, 205),
|
||||
'lemonchiffon1': Color(255, 250, 205),
|
||||
'lemonchiffon2': Color(238, 233, 191),
|
||||
'lemonchiffon3': Color(205, 201, 165),
|
||||
'lemonchiffon4': Color(139, 137, 112),
|
||||
'light blue': Color(173, 216, 230),
|
||||
'light coral': Color(240, 128, 128),
|
||||
'light cyan': Color(224, 255, 255),
|
||||
'light goldenrod': Color(238, 221, 130),
|
||||
'light goldenrod yellow': Color(250, 250, 210),
|
||||
'light gray': Color(211, 211, 211),
|
||||
'light green': Color(144, 238, 144),
|
||||
'light grey': Color(211, 211, 211),
|
||||
'light pink': Color(255, 182, 193),
|
||||
'light salmon': Color(255, 160, 122),
|
||||
'light sea green': Color(32, 178, 170),
|
||||
'light sky blue': Color(135, 206, 250),
|
||||
'light slate blue': Color(132, 112, 255),
|
||||
'light slate gray': Color(119, 136, 153),
|
||||
'light slate grey': Color(119, 136, 153),
|
||||
'light steel blue': Color(176, 196, 222),
|
||||
'light yellow': Color(255, 255, 224),
|
||||
'lightblue': Color(173, 216, 230),
|
||||
'lightblue1': Color(191, 239, 255),
|
||||
'lightblue2': Color(178, 223, 238),
|
||||
'lightblue3': Color(154, 192, 205),
|
||||
'lightblue4': Color(104, 131, 139),
|
||||
'lightcoral': Color(240, 128, 128),
|
||||
'lightcyan': Color(224, 255, 255),
|
||||
'lightcyan1': Color(224, 255, 255),
|
||||
'lightcyan2': Color(209, 238, 238),
|
||||
'lightcyan3': Color(180, 205, 205),
|
||||
'lightcyan4': Color(122, 139, 139),
|
||||
'lightgoldenrod': Color(238, 221, 130),
|
||||
'lightgoldenrod1': Color(255, 236, 139),
|
||||
'lightgoldenrod2': Color(238, 220, 130),
|
||||
'lightgoldenrod3': Color(205, 190, 112),
|
||||
'lightgoldenrod4': Color(139, 129, 76),
|
||||
'lightgoldenrodyellow': Color(250, 250, 210),
|
||||
'lightgray': Color(211, 211, 211),
|
||||
'lightgreen': Color(144, 238, 144),
|
||||
'lightgrey': Color(211, 211, 211),
|
||||
'lightpink': Color(255, 182, 193),
|
||||
'lightpink1': Color(255, 174, 185),
|
||||
'lightpink2': Color(238, 162, 173),
|
||||
'lightpink3': Color(205, 140, 149),
|
||||
'lightpink4': Color(139, 95, 101),
|
||||
'lightsalmon': Color(255, 160, 122),
|
||||
'lightsalmon1': Color(255, 160, 122),
|
||||
'lightsalmon2': Color(238, 149, 114),
|
||||
'lightsalmon3': Color(205, 129, 98),
|
||||
'lightsalmon4': Color(139, 87, 66),
|
||||
'lightseagreen': Color(32, 178, 170),
|
||||
'lightskyblue': Color(135, 206, 250),
|
||||
'lightskyblue1': Color(176, 226, 255),
|
||||
'lightskyblue2': Color(164, 211, 238),
|
||||
'lightskyblue3': Color(141, 182, 205),
|
||||
'lightskyblue4': Color(96, 123, 139),
|
||||
'lightslateblue': Color(132, 112, 255),
|
||||
'lightslategray': Color(119, 136, 153),
|
||||
'lightslategrey': Color(119, 136, 153),
|
||||
'lightsteelblue': Color(176, 196, 222),
|
||||
'lightsteelblue1': Color(202, 225, 255),
|
||||
'lightsteelblue2': Color(188, 210, 238),
|
||||
'lightsteelblue3': Color(162, 181, 205),
|
||||
'lightsteelblue4': Color(110, 123, 139),
|
||||
'lightyellow': Color(255, 255, 224),
|
||||
'lightyellow1': Color(255, 255, 224),
|
||||
'lightyellow2': Color(238, 238, 209),
|
||||
'lightyellow3': Color(205, 205, 180),
|
||||
'lightyellow4': Color(139, 139, 122),
|
||||
'lime green': Color(50, 205, 50),
|
||||
'limegreen': Color(50, 205, 50),
|
||||
'linen': Color(250, 240, 230),
|
||||
'magenta': Color(255, 0, 255),
|
||||
'magenta1': Color(255, 0, 255),
|
||||
'magenta2': Color(238, 0, 238),
|
||||
'magenta3': Color(205, 0, 205),
|
||||
'magenta4': Color(139, 0, 139),
|
||||
'maroon': Color(176, 48, 96),
|
||||
'maroon1': Color(255, 52, 179),
|
||||
'maroon2': Color(238, 48, 167),
|
||||
'maroon3': Color(205, 41, 144),
|
||||
'maroon4': Color(139, 28, 98),
|
||||
'medium aquamarine': Color(102, 205, 170),
|
||||
'medium blue': Color(0, 0, 205),
|
||||
'medium orchid': Color(186, 85, 211),
|
||||
'medium purple': Color(147, 112, 219),
|
||||
'medium sea green': Color(60, 179, 113),
|
||||
'medium slate blue': Color(123, 104, 238),
|
||||
'medium spring green': Color(0, 250, 154),
|
||||
'medium turquoise': Color(72, 209, 204),
|
||||
'medium violet red': Color(199, 21, 133),
|
||||
'mediumaquamarine': Color(102, 205, 170),
|
||||
'mediumblue': Color(0, 0, 205),
|
||||
'mediumorchid': Color(186, 85, 211),
|
||||
'mediumorchid1': Color(224, 102, 255),
|
||||
'mediumorchid2': Color(209, 95, 238),
|
||||
'mediumorchid3': Color(180, 82, 205),
|
||||
'mediumorchid4': Color(122, 55, 139),
|
||||
'mediumpurple': Color(147, 112, 219),
|
||||
'mediumpurple1': Color(171, 130, 255),
|
||||
'mediumpurple2': Color(159, 121, 238),
|
||||
'mediumpurple3': Color(137, 104, 205),
|
||||
'mediumpurple4': Color(93, 71, 139),
|
||||
'mediumseagreen': Color(60, 179, 113),
|
||||
'mediumslateblue': Color(123, 104, 238),
|
||||
'mediumspringgreen': Color(0, 250, 154),
|
||||
'mediumturquoise': Color(72, 209, 204),
|
||||
'mediumvioletred': Color(199, 21, 133),
|
||||
'midnight blue': Color(25, 25, 112),
|
||||
'midnightblue': Color(25, 25, 112),
|
||||
'mint cream': Color(245, 255, 250),
|
||||
'mintcream': Color(245, 255, 250),
|
||||
'misty rose': Color(255, 228, 225),
|
||||
'mistyrose': Color(255, 228, 225),
|
||||
'mistyrose1': Color(255, 228, 225),
|
||||
'mistyrose2': Color(238, 213, 210),
|
||||
'mistyrose3': Color(205, 183, 181),
|
||||
'mistyrose4': Color(139, 125, 123),
|
||||
'moccasin': Color(255, 228, 181),
|
||||
'navajo white': Color(255, 222, 173),
|
||||
'navajowhite': Color(255, 222, 173),
|
||||
'navajowhite1': Color(255, 222, 173),
|
||||
'navajowhite2': Color(238, 207, 161),
|
||||
'navajowhite3': Color(205, 179, 139),
|
||||
'navajowhite4': Color(139, 121, 94),
|
||||
'navy': Color(0, 0, 128),
|
||||
'navy blue': Color(0, 0, 128),
|
||||
'navyblue': Color(0, 0, 128),
|
||||
'old lace': Color(253, 245, 230),
|
||||
'oldlace': Color(253, 245, 230),
|
||||
'olive drab': Color(107, 142, 35),
|
||||
'olivedrab': Color(107, 142, 35),
|
||||
'olivedrab1': Color(192, 255, 62),
|
||||
'olivedrab2': Color(179, 238, 58),
|
||||
'olivedrab3': Color(154, 205, 50),
|
||||
'olivedrab4': Color(105, 139, 34),
|
||||
'orange': Color(255, 165, 0),
|
||||
'orange red': Color(255, 69, 0),
|
||||
'orange1': Color(255, 165, 0),
|
||||
'orange2': Color(238, 154, 0),
|
||||
'orange3': Color(205, 133, 0),
|
||||
'orange4': Color(139, 90, 0),
|
||||
'orangered': Color(255, 69, 0),
|
||||
'orangered1': Color(255, 69, 0),
|
||||
'orangered2': Color(238, 64, 0),
|
||||
'orangered3': Color(205, 55, 0),
|
||||
'orangered4': Color(139, 37, 0),
|
||||
'orchid': Color(218, 112, 214),
|
||||
'orchid1': Color(255, 131, 250),
|
||||
'orchid2': Color(238, 122, 233),
|
||||
'orchid3': Color(205, 105, 201),
|
||||
'orchid4': Color(139, 71, 137),
|
||||
'pale goldenrod': Color(238, 232, 170),
|
||||
'pale green': Color(152, 251, 152),
|
||||
'pale turquoise': Color(175, 238, 238),
|
||||
'pale violet red': Color(219, 112, 147),
|
||||
'palegoldenrod': Color(238, 232, 170),
|
||||
'palegreen': Color(152, 251, 152),
|
||||
'palegreen1': Color(154, 255, 154),
|
||||
'palegreen2': Color(144, 238, 144),
|
||||
'palegreen3': Color(124, 205, 124),
|
||||
'palegreen4': Color(84, 139, 84),
|
||||
'paleturquoise': Color(175, 238, 238),
|
||||
'paleturquoise1': Color(187, 255, 255),
|
||||
'paleturquoise2': Color(174, 238, 238),
|
||||
'paleturquoise3': Color(150, 205, 205),
|
||||
'paleturquoise4': Color(102, 139, 139),
|
||||
'palevioletred': Color(219, 112, 147),
|
||||
'palevioletred1': Color(255, 130, 171),
|
||||
'palevioletred2': Color(238, 121, 159),
|
||||
'palevioletred3': Color(205, 104, 137),
|
||||
'palevioletred4': Color(139, 71, 93),
|
||||
'papaya whip': Color(255, 239, 213),
|
||||
'papayawhip': Color(255, 239, 213),
|
||||
'peach puff': Color(255, 218, 185),
|
||||
'peachpuff': Color(255, 218, 185),
|
||||
'peachpuff1': Color(255, 218, 185),
|
||||
'peachpuff2': Color(238, 203, 173),
|
||||
'peachpuff3': Color(205, 175, 149),
|
||||
'peachpuff4': Color(139, 119, 101),
|
||||
'peru': Color(205, 133, 63),
|
||||
'pink': Color(255, 192, 203),
|
||||
'pink1': Color(255, 181, 197),
|
||||
'pink2': Color(238, 169, 184),
|
||||
'pink3': Color(205, 145, 158),
|
||||
'pink4': Color(139, 99, 108),
|
||||
'plum': Color(221, 160, 221),
|
||||
'plum1': Color(255, 187, 255),
|
||||
'plum2': Color(238, 174, 238),
|
||||
'plum3': Color(205, 150, 205),
|
||||
'plum4': Color(139, 102, 139),
|
||||
'powder blue': Color(176, 224, 230),
|
||||
'powderblue': Color(176, 224, 230),
|
||||
'purple': Color(160, 32, 240),
|
||||
'purple1': Color(155, 48, 255),
|
||||
'purple2': Color(145, 44, 238),
|
||||
'purple3': Color(125, 38, 205),
|
||||
'purple4': Color(85, 26, 139),
|
||||
'red': Color(255, 0, 0),
|
||||
'red1': Color(255, 0, 0),
|
||||
'red2': Color(238, 0, 0),
|
||||
'red3': Color(205, 0, 0),
|
||||
'red4': Color(139, 0, 0),
|
||||
'rosy brown': Color(188, 143, 143),
|
||||
'rosybrown': Color(188, 143, 143),
|
||||
'rosybrown1': Color(255, 193, 193),
|
||||
'rosybrown2': Color(238, 180, 180),
|
||||
'rosybrown3': Color(205, 155, 155),
|
||||
'rosybrown4': Color(139, 105, 105),
|
||||
'royal blue': Color(65, 105, 225),
|
||||
'royalblue': Color(65, 105, 225),
|
||||
'royalblue1': Color(72, 118, 255),
|
||||
'royalblue2': Color(67, 110, 238),
|
||||
'royalblue3': Color(58, 95, 205),
|
||||
'royalblue4': Color(39, 64, 139),
|
||||
'saddle brown': Color(139, 69, 19),
|
||||
'saddlebrown': Color(139, 69, 19),
|
||||
'salmon': Color(250, 128, 114),
|
||||
'salmon1': Color(255, 140, 105),
|
||||
'salmon2': Color(238, 130, 98),
|
||||
'salmon3': Color(205, 112, 84),
|
||||
'salmon4': Color(139, 76, 57),
|
||||
'sandy brown': Color(244, 164, 96),
|
||||
'sandybrown': Color(244, 164, 96),
|
||||
'sea green': Color(46, 139, 87),
|
||||
'seagreen': Color(46, 139, 87),
|
||||
'seagreen1': Color(84, 255, 159),
|
||||
'seagreen2': Color(78, 238, 148),
|
||||
'seagreen3': Color(67, 205, 128),
|
||||
'seagreen4': Color(46, 139, 87),
|
||||
'seashell': Color(255, 245, 238),
|
||||
'seashell1': Color(255, 245, 238),
|
||||
'seashell2': Color(238, 229, 222),
|
||||
'seashell3': Color(205, 197, 191),
|
||||
'seashell4': Color(139, 134, 130),
|
||||
'sienna': Color(160, 82, 45),
|
||||
'sienna1': Color(255, 130, 71),
|
||||
'sienna2': Color(238, 121, 66),
|
||||
'sienna3': Color(205, 104, 57),
|
||||
'sienna4': Color(139, 71, 38),
|
||||
'sky blue': Color(135, 206, 235),
|
||||
'skyblue': Color(135, 206, 235),
|
||||
'skyblue1': Color(135, 206, 255),
|
||||
'skyblue2': Color(126, 192, 238),
|
||||
'skyblue3': Color(108, 166, 205),
|
||||
'skyblue4': Color(74, 112, 139),
|
||||
'slate blue': Color(106, 90, 205),
|
||||
'slate gray': Color(112, 128, 144),
|
||||
'slate grey': Color(112, 128, 144),
|
||||
'slateblue': Color(106, 90, 205),
|
||||
'slateblue1': Color(131, 111, 255),
|
||||
'slateblue2': Color(122, 103, 238),
|
||||
'slateblue3': Color(105, 89, 205),
|
||||
'slateblue4': Color(71, 60, 139),
|
||||
'slategray': Color(112, 128, 144),
|
||||
'slategray1': Color(198, 226, 255),
|
||||
'slategray2': Color(185, 211, 238),
|
||||
'slategray3': Color(159, 182, 205),
|
||||
'slategray4': Color(108, 123, 139),
|
||||
'slategrey': Color(112, 128, 144),
|
||||
'snow': Color(255, 250, 250),
|
||||
'snow1': Color(255, 250, 250),
|
||||
'snow2': Color(238, 233, 233),
|
||||
'snow3': Color(205, 201, 201),
|
||||
'snow4': Color(139, 137, 137),
|
||||
'spring green': Color(0, 255, 127),
|
||||
'springgreen': Color(0, 255, 127),
|
||||
'springgreen1': Color(0, 255, 127),
|
||||
'springgreen2': Color(0, 238, 118),
|
||||
'springgreen3': Color(0, 205, 102),
|
||||
'springgreen4': Color(0, 139, 69),
|
||||
'steel blue': Color(70, 130, 180),
|
||||
'steelblue': Color(70, 130, 180),
|
||||
'steelblue1': Color(99, 184, 255),
|
||||
'steelblue2': Color(92, 172, 238),
|
||||
'steelblue3': Color(79, 148, 205),
|
||||
'steelblue4': Color(54, 100, 139),
|
||||
'tan': Color(210, 180, 140),
|
||||
'tan1': Color(255, 165, 79),
|
||||
'tan2': Color(238, 154, 73),
|
||||
'tan3': Color(205, 133, 63),
|
||||
'tan4': Color(139, 90, 43),
|
||||
'thistle': Color(216, 191, 216),
|
||||
'thistle1': Color(255, 225, 255),
|
||||
'thistle2': Color(238, 210, 238),
|
||||
'thistle3': Color(205, 181, 205),
|
||||
'thistle4': Color(139, 123, 139),
|
||||
'tomato': Color(255, 99, 71),
|
||||
'tomato1': Color(255, 99, 71),
|
||||
'tomato2': Color(238, 92, 66),
|
||||
'tomato3': Color(205, 79, 57),
|
||||
'tomato4': Color(139, 54, 38),
|
||||
'turquoise': Color(64, 224, 208),
|
||||
'turquoise1': Color(0, 245, 255),
|
||||
'turquoise2': Color(0, 229, 238),
|
||||
'turquoise3': Color(0, 197, 205),
|
||||
'turquoise4': Color(0, 134, 139),
|
||||
'violet': Color(238, 130, 238),
|
||||
'violet red': Color(208, 32, 144),
|
||||
'violetred': Color(208, 32, 144),
|
||||
'violetred1': Color(255, 62, 150),
|
||||
'violetred2': Color(238, 58, 140),
|
||||
'violetred3': Color(205, 50, 120),
|
||||
'violetred4': Color(139, 34, 82),
|
||||
'wheat': Color(245, 222, 179),
|
||||
'wheat1': Color(255, 231, 186),
|
||||
'wheat2': Color(238, 216, 174),
|
||||
'wheat3': Color(205, 186, 150),
|
||||
'wheat4': Color(139, 126, 102),
|
||||
'white': Color(255, 255, 255),
|
||||
'white smoke': Color(245, 245, 245),
|
||||
'whitesmoke': Color(245, 245, 245),
|
||||
'yellow': Color(255, 255, 0),
|
||||
'yellow green': Color(154, 205, 50),
|
||||
'yellow1': Color(255, 255, 0),
|
||||
'yellow2': Color(238, 238, 0),
|
||||
'yellow3': Color(205, 205, 0),
|
||||
'yellow4': Color(139, 139, 0),
|
||||
'yellowgreen': Color(154, 205, 50)}
|
||||
# END_DATA_SECTION }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Read RGB color table from specified rgb.txt file
|
||||
import sys
|
||||
import pprint
|
||||
data = {}
|
||||
for line in open(sys.argv[-1]):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('!'):
|
||||
continue
|
||||
parts = line.split()
|
||||
r, g, b = map(int, parts[:3])
|
||||
name = ' '.join(parts[3:]).lower()
|
||||
data[name] = data[name.replace(' ', '')] = r, g, b
|
||||
data = pprint.pformat(data).replace('{', '{\n ').replace('(', 'Color(')
|
||||
with open(__file__, 'r+') as src:
|
||||
raw = src.read()
|
||||
raw = re.sub(
|
||||
r'^# BEGIN_DATA_SECTION {{{$.*^# END_DATA_SECTION }}}',
|
||||
'# BEGIN_DATA_SECTION {{{\ncolor_names = %s\n# END_DATA_SECTION }}}' % data,
|
||||
raw, flags=re.DOTALL | re.MULTILINE
|
||||
)
|
||||
src.seek(0), src.truncate(), src.write(raw)
|
||||
153
kitty/screen.c
153
kitty/screen.c
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "data-types.h"
|
||||
#include <structmember.h>
|
||||
#include <limits.h>
|
||||
#include "unicode-data.h"
|
||||
#include "tracker.h"
|
||||
#include "modes.h"
|
||||
@@ -29,6 +30,7 @@ init_tabstops(bool *tabstops, index_type count) {
|
||||
self->g1_charset = self->g0_charset; \
|
||||
self->g_charset = self->g0_charset; \
|
||||
self->utf8_state = 0; \
|
||||
self->utf8_codepoint = 0; \
|
||||
self->use_latin1 = false;
|
||||
|
||||
static PyObject*
|
||||
@@ -43,6 +45,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
self->columns = columns; self->lines = lines;
|
||||
self->modes = empty_modes;
|
||||
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
||||
self->default_fg = 0; self->default_bg = 0;
|
||||
self->highlight_fg = 0; self->highlight_bg = 0;
|
||||
self->cursor_color = 0;
|
||||
RESET_CHARSETS;
|
||||
self->callbacks = callbacks; Py_INCREF(callbacks);
|
||||
self->cursor = alloc_cursor();
|
||||
@@ -67,6 +72,9 @@ screen_reset(Screen *self) {
|
||||
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
|
||||
linebuf_clear(self->linebuf, ' ');
|
||||
self->modes = empty_modes;
|
||||
self->default_fg = 0; self->default_bg = 0;
|
||||
self->highlight_fg = 0; self->highlight_bg = 0;
|
||||
self->cursor_color = 0;
|
||||
RESET_CHARSETS;
|
||||
self->margin_top = 0; self->margin_bottom = self->lines - 1;
|
||||
screen_normal_keypad_mode(self);
|
||||
@@ -396,6 +404,7 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
|
||||
SIMPLE_MODE(IRM)
|
||||
SIMPLE_MODE(DECARM)
|
||||
SIMPLE_MODE(BRACKETED_PASTE)
|
||||
SIMPLE_MODE(EXTENDED_KEYBOARD)
|
||||
SIMPLE_MODE(FOCUS_TRACKING)
|
||||
MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE)
|
||||
MOUSE_MODE(MOUSE_MOTION_TRACKING, mouse_tracking_mode, MOTION_MODE)
|
||||
@@ -404,10 +413,12 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
|
||||
MOUSE_MODE(MOUSE_SGR_MODE, mouse_tracking_protocol, SGR_PROTOCOL)
|
||||
MOUSE_MODE(MOUSE_URXVT_MODE, mouse_tracking_protocol, URXVT_PROTOCOL)
|
||||
|
||||
case DECCKM:
|
||||
case DECSCLM:
|
||||
case DECNRCM:
|
||||
break; // we ignore these modes
|
||||
case DECCKM:
|
||||
self->modes.mDECCKM = val;
|
||||
break;
|
||||
case DECTCEM:
|
||||
self->modes.mDECTCEM = val;
|
||||
tracker_cursor_changed(self->change_tracker);
|
||||
@@ -572,36 +583,64 @@ screen_cursor_to_column(Screen *self, unsigned int column) {
|
||||
}
|
||||
}
|
||||
|
||||
#define INDEX_UP \
|
||||
linebuf_index(self->linebuf, top, bottom); \
|
||||
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) { \
|
||||
/* Only add to history when no page margins have been set */ \
|
||||
linebuf_init_line(self->linebuf, bottom); \
|
||||
historybuf_add_line(self->historybuf, self->linebuf->line); \
|
||||
tracker_line_added_to_history(self->change_tracker); \
|
||||
} \
|
||||
linebuf_clear_line(self->linebuf, bottom); \
|
||||
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker); \
|
||||
else tracker_update_line_range(self->change_tracker, top, bottom);
|
||||
|
||||
void
|
||||
screen_index(Screen *self) {
|
||||
// Move cursor down one line, scrolling screen if needed
|
||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||
if (self->cursor->y == bottom) {
|
||||
linebuf_index(self->linebuf, top, bottom);
|
||||
if (self->linebuf == self->main_linebuf && bottom == self->lines - 1) {
|
||||
// Only add to history when no page margins have been set
|
||||
linebuf_init_line(self->linebuf, bottom);
|
||||
historybuf_add_line(self->historybuf, self->linebuf->line);
|
||||
tracker_line_added_to_history(self->change_tracker);
|
||||
}
|
||||
linebuf_clear_line(self->linebuf, bottom);
|
||||
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker);
|
||||
else tracker_update_line_range(self->change_tracker, top, bottom);
|
||||
INDEX_UP;
|
||||
} else screen_cursor_down(self, 1);
|
||||
}
|
||||
|
||||
void
|
||||
screen_scroll(Screen *self, unsigned int count) {
|
||||
// Scroll the screen up by count lines, not moving the cursor
|
||||
count = MIN(self->lines, count);
|
||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||
while (count > 0) {
|
||||
count--;
|
||||
INDEX_UP;
|
||||
}
|
||||
}
|
||||
|
||||
#define INDEX_DOWN \
|
||||
linebuf_reverse_index(self->linebuf, top, bottom); \
|
||||
linebuf_clear_line(self->linebuf, top); \
|
||||
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker); \
|
||||
else tracker_update_line_range(self->change_tracker, top, bottom);
|
||||
|
||||
void
|
||||
screen_reverse_index(Screen *self) {
|
||||
// Move cursor up one line, scrolling screen if needed
|
||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||
if (self->cursor->y == top) {
|
||||
linebuf_reverse_index(self->linebuf, top, bottom);
|
||||
linebuf_clear_line(self->linebuf, top);
|
||||
if (bottom - top > self->lines - 1) tracker_update_screen(self->change_tracker);
|
||||
else tracker_update_line_range(self->change_tracker, top, bottom);
|
||||
INDEX_DOWN;
|
||||
} else screen_cursor_up(self, 1, false, -1);
|
||||
}
|
||||
|
||||
void
|
||||
screen_reverse_scroll(Screen *self, unsigned int count) {
|
||||
// Scroll the screen down by count lines, not moving the cursor
|
||||
count = MIN(self->lines, count);
|
||||
unsigned int top = self->margin_top, bottom = self->margin_bottom;
|
||||
while (count > 0) {
|
||||
count--;
|
||||
INDEX_DOWN;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
screen_carriage_return(Screen *self) {
|
||||
@@ -635,6 +674,7 @@ savepoints_pop(SavepointBuffer *self) {
|
||||
|
||||
#define COPY_CHARSETS(self, sp) \
|
||||
sp->utf8_state = self->utf8_state; \
|
||||
sp->utf8_codepoint = self->utf8_codepoint; \
|
||||
sp->g0_charset = self->g0_charset; \
|
||||
sp->g1_charset = self->g1_charset; \
|
||||
sp->g_charset = self->g_charset; \
|
||||
@@ -699,13 +739,7 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
|
||||
|
||||
void
|
||||
screen_cursor_to_line(Screen *self, unsigned int line) {
|
||||
unsigned int y = MAX(line, 1) - 1;
|
||||
y += self->margin_top;
|
||||
if (y != self->cursor->y) {
|
||||
self->cursor->y = y;
|
||||
screen_ensure_bounds(self, false); // TODO: should we also restrict the cursor to the scrolling region?
|
||||
tracker_cursor_changed(self->change_tracker);
|
||||
}
|
||||
screen_cursor_position(self, line, self->cursor->x + 1);
|
||||
}
|
||||
|
||||
// }}}
|
||||
@@ -852,14 +886,19 @@ void screen_erase_characters(Screen *self, unsigned int count) {
|
||||
|
||||
// Device control {{{
|
||||
|
||||
void
|
||||
screen_use_latin1(Screen *self, bool on) {
|
||||
self->use_latin1 = on; self->utf8_state = 0; self->utf8_codepoint = 0;
|
||||
PyObject_CallMethod(self->callbacks, "use_utf8", "O", on ? Py_False : Py_True);
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
void
|
||||
screen_bell(Screen UNUSED *self) {
|
||||
FILE *f = fopen("/dev/tty", "w");
|
||||
static const char *bell = "\007";
|
||||
if (f != NULL) {
|
||||
fwrite(bell, 1, 1, f);
|
||||
fclose(f);
|
||||
}
|
||||
PyObject_CallMethod(self->callbacks, "bell", NULL);
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
PyErr_Clear();
|
||||
}
|
||||
|
||||
static inline void
|
||||
@@ -871,8 +910,17 @@ callback(const char *name, Screen *self, const char *data, unsigned int sz) {
|
||||
}
|
||||
|
||||
void
|
||||
report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary) {
|
||||
callback("write_to_child", self, "\x1b[>1;4600;0c", 0); // same as libvte
|
||||
report_device_attributes(Screen *self, unsigned int mode, char start_modifier) {
|
||||
if (mode == 0) {
|
||||
switch(start_modifier) {
|
||||
case 0:
|
||||
callback("write_to_child", self, "\x1b[?62;c", 0); // VT-220 with no extra info
|
||||
break;
|
||||
case '>':
|
||||
callback("write_to_child", self, "\x1b[>1;" xstr(PRIMARY_VERSION) ";" xstr(SECONDARY_VERSION) "c", 0); // VT-220 + primary version + secondary version
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -898,6 +946,34 @@ report_device_status(Screen *self, unsigned int which, bool private) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
report_mode_status(Screen *self, unsigned int which, bool private) {
|
||||
unsigned int q = private ? which << 5 : which;
|
||||
unsigned int ans = 0;
|
||||
char buf[50] = {0};
|
||||
switch(q) {
|
||||
#define KNOWN_MODE(x) \
|
||||
case x: \
|
||||
ans = self->modes.m##x ? 1 : 2; break;
|
||||
KNOWN_MODE(LNM);
|
||||
KNOWN_MODE(IRM);
|
||||
KNOWN_MODE(DECTCEM);
|
||||
KNOWN_MODE(DECSCNM);
|
||||
KNOWN_MODE(DECOM);
|
||||
KNOWN_MODE(DECAWM);
|
||||
KNOWN_MODE(DECCOLM);
|
||||
KNOWN_MODE(DECARM);
|
||||
KNOWN_MODE(DECCKM);
|
||||
KNOWN_MODE(BRACKETED_PASTE);
|
||||
KNOWN_MODE(EXTENDED_KEYBOARD);
|
||||
KNOWN_MODE(FOCUS_TRACKING);
|
||||
#undef KNOWN_MODE
|
||||
case STYLED_UNDERLINES:
|
||||
ans = 3; break;
|
||||
}
|
||||
if (snprintf(buf, sizeof(buf) - 1, "\x1b[%s%u;%u$y", (private ? "?" : ""), which, ans)) callback("write_to_child", self, buf, 0);
|
||||
}
|
||||
|
||||
void
|
||||
screen_set_margins(Screen *self, unsigned int top, unsigned int bottom) {
|
||||
if (!top) top = 1;
|
||||
@@ -1044,9 +1120,11 @@ WRAP1B(erase_in_display, 0)
|
||||
static int name##_set(Screen *self, PyObject *val, void UNUSED *closure) { if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } set_mode_from_const(self, uname, PyObject_IsTrue(val) ? true : false); return 0; }
|
||||
|
||||
MODE_GETSET(in_bracketed_paste_mode, BRACKETED_PASTE)
|
||||
MODE_GETSET(extended_keyboard, EXTENDED_KEYBOARD)
|
||||
MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING)
|
||||
MODE_GETSET(auto_repeat_enabled, DECARM)
|
||||
MODE_GETSET(cursor_visible, DECTCEM)
|
||||
MODE_GETSET(cursor_key_mode, DECCKM)
|
||||
|
||||
static PyObject*
|
||||
mouse_tracking_mode(Screen *self) {
|
||||
@@ -1247,12 +1325,22 @@ static PyMethodDef methods[] = {
|
||||
|
||||
static PyGetSetDef getsetters[] = {
|
||||
GETSET(in_bracketed_paste_mode)
|
||||
GETSET(extended_keyboard)
|
||||
GETSET(auto_repeat_enabled)
|
||||
GETSET(focus_tracking_enabled)
|
||||
GETSET(cursor_visible)
|
||||
GETSET(cursor_key_mode)
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
#if UINT_MAX == UINT32_MAX
|
||||
#define T_COL T_UINT
|
||||
#elif ULONG_MAX == UINT32_MAX
|
||||
#define T_COL T_ULONG
|
||||
#else
|
||||
#error Neither int nor long is 4-bytes in size
|
||||
#endif
|
||||
|
||||
static PyMemberDef members[] = {
|
||||
{"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"},
|
||||
{"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
|
||||
@@ -1262,6 +1350,11 @@ static PyMemberDef members[] = {
|
||||
{"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
|
||||
{"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
|
||||
{"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"},
|
||||
{"default_fg", T_COL, offsetof(Screen, default_fg), 0, "default_fg"},
|
||||
{"default_bg", T_COL, offsetof(Screen, default_bg), 0, "default_bg"},
|
||||
{"highlight_fg", T_COL, offsetof(Screen, highlight_fg), 0, "highlight_fg"},
|
||||
{"highlight_bg", T_COL, offsetof(Screen, highlight_bg), 0, "highlight_bg"},
|
||||
{"cursor_color", T_COL, offsetof(Screen, cursor_color), 0, "cursor_color"},
|
||||
{NULL}
|
||||
};
|
||||
|
||||
|
||||
190
kitty/shaders.py
190
kitty/shaders.py
@@ -2,39 +2,106 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
from ctypes import addressof, sizeof
|
||||
from functools import lru_cache
|
||||
from threading import Lock
|
||||
|
||||
from .fonts.render import render_cell
|
||||
from .fast_data_types import (
|
||||
glCreateProgram, glAttachShader, GL_FRAGMENT_SHADER, GL_VERTEX_SHADER,
|
||||
glLinkProgram, GL_TRUE, GL_LINK_STATUS, glGetProgramiv,
|
||||
glGetProgramInfoLog, glDeleteShader, glDeleteProgram, glGenVertexArrays,
|
||||
glCreateShader, glShaderSource, glCompileShader, glGetShaderiv,
|
||||
GL_COMPILE_STATUS, glGetShaderInfoLog, glGetUniformLocation,
|
||||
glGetAttribLocation, glUseProgram, glBindVertexArray, GL_TEXTURE0,
|
||||
GL_TEXTURE1, glGetIntegerv, GL_MAX_ARRAY_TEXTURE_LAYERS, glNamedBufferData,
|
||||
GL_MAX_TEXTURE_SIZE, glDeleteTexture, GL_TEXTURE_2D_ARRAY, glGenTextures,
|
||||
glBindTexture, glTexParameteri, GL_CLAMP_TO_EDGE, glDeleteBuffer,
|
||||
GL_TEXTURE_MAG_FILTER, GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S,
|
||||
GL_NEAREST, GL_TEXTURE_WRAP_T, glGenBuffers, GL_R8, GL_RED,
|
||||
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_STATIC_DRAW, GL_STREAM_DRAW,
|
||||
GL_TEXTURE_BUFFER, GL_RGB32UI, GL_FLOAT, GL_ARRAY_BUFFER, glBindBuffer,
|
||||
glPixelStorei, glTexBuffer, glActiveTexture, glTexStorage3D,
|
||||
glCopyImageSubData, glTexSubImage3D, ITALIC, BOLD, SpriteMap,
|
||||
glEnableVertexAttribArray, glVertexAttribPointer, copy_image_sub_data
|
||||
BOLD, GL_ARRAY_BUFFER, GL_CLAMP_TO_EDGE, GL_COMPILE_STATUS, GL_FLOAT,
|
||||
GL_FRAGMENT_SHADER, GL_LINK_STATUS, GL_MAX_ARRAY_TEXTURE_LAYERS,
|
||||
GL_MAX_TEXTURE_SIZE, GL_NEAREST, GL_R8, GL_RED, GL_STREAM_DRAW,
|
||||
GL_TEXTURE0, GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER,
|
||||
GL_TEXTURE_MIN_FILTER, GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T, GL_TRUE,
|
||||
GL_UNPACK_ALIGNMENT, GL_UNSIGNED_BYTE, GL_VERTEX_SHADER, ITALIC, SpriteMap,
|
||||
copy_image_sub_data, glActiveTexture, glAttachShader, glBindBuffer,
|
||||
glBindTexture, glBindVertexArray, glCompileShader, glCopyImageSubData,
|
||||
glCreateProgram, glCreateShader, glDeleteBuffer, glDeleteProgram,
|
||||
glDeleteShader, glDeleteTexture, glDeleteVertexArray,
|
||||
glEnableVertexAttribArray, glGenBuffers, glGenTextures, glGenVertexArrays,
|
||||
glGetAttribLocation, glGetBufferSubData, glGetIntegerv,
|
||||
glGetProgramInfoLog, glGetProgramiv, glGetShaderInfoLog, glGetShaderiv,
|
||||
glGetUniformLocation, glLinkProgram, glPixelStorei, glShaderSource,
|
||||
glTexParameteri, glTexStorage3D, glTexSubImage3D, glUseProgram,
|
||||
glVertexAttribDivisor, glVertexAttribPointer, replace_or_create_buffer
|
||||
)
|
||||
from .fonts.render import render_cell
|
||||
from .utils import safe_print
|
||||
|
||||
GL_VERSION = (3, 3)
|
||||
VERSION = GL_VERSION[0] * 100 + GL_VERSION[1] * 10
|
||||
ITALIC_MASK = 1 << ITALIC
|
||||
BOLD_MASK = 1 << BOLD
|
||||
BASE = os.path.dirname(os.path.abspath(__file__))
|
||||
VertexArray = namedtuple('VertexArray', 'name size dtype normalized stride offset divisor')
|
||||
|
||||
|
||||
class Sprites:
|
||||
@lru_cache()
|
||||
def load_shaders(name):
|
||||
vert = open(os.path.join(BASE, '{}_vertex.glsl'.format(name))).read()
|
||||
frag = open(os.path.join(BASE, '{}_fragment.glsl'.format(name))).read()
|
||||
return vert, frag
|
||||
|
||||
|
||||
class BufferManager: # {{{
|
||||
|
||||
def __init__(self):
|
||||
self.sizes = {}
|
||||
self.types = {}
|
||||
self.ctypes_types = {}
|
||||
self.name_count = 0
|
||||
|
||||
def create(self, for_use=GL_ARRAY_BUFFER):
|
||||
buf_id = glGenBuffers(1)
|
||||
self.types[buf_id] = for_use
|
||||
self.sizes.pop(buf_id, None)
|
||||
self.ctypes_types.pop(buf_id, None)
|
||||
return buf_id
|
||||
|
||||
def delete(self, buf_id):
|
||||
if buf_id in self.types:
|
||||
glDeleteBuffer(buf_id)
|
||||
self.sizes.pop(buf_id, None)
|
||||
self.types.pop(buf_id)
|
||||
self.ctypes_types.pop(buf_id, None)
|
||||
|
||||
def set_data(self, buf_id, data, usage=GL_STREAM_DRAW, verify=False):
|
||||
prev_sz = self.sizes.get(buf_id, 0)
|
||||
new_sz = sizeof(data)
|
||||
replace_or_create_buffer(buf_id, new_sz, prev_sz, addressof(data), usage, self.types[buf_id])
|
||||
self.sizes[buf_id] = new_sz
|
||||
self.ctypes_types[buf_id] = type(data)
|
||||
if verify:
|
||||
verify_data = self.get_data(buf_id)
|
||||
if list(data) != list(verify_data):
|
||||
raise RuntimeError('OpenGL failed to upload to buffer')
|
||||
|
||||
def get_data(self, buf_id):
|
||||
verify_data = self.ctypes_types[buf_id]()
|
||||
glGetBufferSubData(self.types[buf_id], buf_id, self.sizes[buf_id], 0, addressof(verify_data))
|
||||
return verify_data
|
||||
|
||||
def bind(self, buf_id):
|
||||
glBindBuffer(self.types[buf_id], buf_id)
|
||||
|
||||
def unbind(self, buf_id):
|
||||
glBindBuffer(self.types[buf_id], 0)
|
||||
|
||||
@contextmanager
|
||||
def bound_buffer(self, buf_id):
|
||||
self.bind(buf_id)
|
||||
yield
|
||||
self.unbind(buf_id)
|
||||
|
||||
|
||||
buffer_manager = BufferManager()
|
||||
# }}}
|
||||
|
||||
|
||||
class Sprites: # {{{
|
||||
''' Maintain sprite sheets of all rendered characters on the GPU as a texture
|
||||
array with each texture being a sprite sheet. '''
|
||||
|
||||
@@ -43,15 +110,14 @@ class Sprites:
|
||||
|
||||
def __init__(self):
|
||||
self.xnum = self.ynum = 1
|
||||
self.sampler_num = 0
|
||||
self.buffer_sampler_num = 1
|
||||
self.first_cell_cache = {}
|
||||
self.second_cell_cache = {}
|
||||
self.x = self.y = self.z = 0
|
||||
self.texture_id = self.buffer_texture_id = None
|
||||
self.texture_id = None
|
||||
self.last_num_of_layers = 1
|
||||
self.last_ynum = -1
|
||||
self.texture_unit = GL_TEXTURE0
|
||||
self.sampler_num = 0
|
||||
self.texture_unit = GL_TEXTURE0 + self.sampler_num
|
||||
self.backend = SpriteMap(glGetIntegerv(GL_MAX_TEXTURE_SIZE), glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS))
|
||||
self.lock = Lock()
|
||||
|
||||
@@ -103,7 +169,6 @@ class Sprites:
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
|
||||
x, y = x * self.cell_width, y * self.cell_height
|
||||
glTexSubImage3D(tgt, 0, x, y, z, self.cell_width, self.cell_height, 1, GL_RED, GL_UNSIGNED_BYTE, addressof(buf))
|
||||
glBindTexture(tgt, 0)
|
||||
|
||||
def realloc_texture(self):
|
||||
tgt = GL_TEXTURE_2D_ARRAY
|
||||
@@ -138,50 +203,28 @@ class Sprites:
|
||||
self.last_num_of_layers = znum
|
||||
self.last_ynum = self.backend.ynum
|
||||
self.texture_id = tex
|
||||
glBindTexture(tgt, 0)
|
||||
|
||||
def destroy(self):
|
||||
if self.texture_id is not None:
|
||||
glDeleteTexture(self.texture_id)
|
||||
self.texture_id = None
|
||||
if self.buffer_texture_id is not None:
|
||||
glDeleteTexture(self.buffer_texture_id)
|
||||
|
||||
def ensure_state(self):
|
||||
if self.texture_id is None:
|
||||
self.realloc_texture()
|
||||
self.buffer_texture_id = glGenTextures(1)
|
||||
self.buffer_texture_unit = GL_TEXTURE1
|
||||
|
||||
def add_sprite_map(self):
|
||||
return glGenBuffers(1)
|
||||
|
||||
def set_sprite_map(self, buf_id, data, usage=GL_STREAM_DRAW):
|
||||
glNamedBufferData(buf_id, sizeof(data), addressof(data), usage)
|
||||
|
||||
def bind_sprite_map(self, buf_id):
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, buf_id)
|
||||
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32UI, buf_id)
|
||||
|
||||
def destroy_sprite_map(self, buf_id):
|
||||
glDeleteBuffer(buf_id)
|
||||
|
||||
def __enter__(self):
|
||||
self.ensure_state()
|
||||
glActiveTexture(self.texture_unit)
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, self.texture_id)
|
||||
|
||||
glActiveTexture(self.buffer_texture_unit)
|
||||
glBindTexture(GL_TEXTURE_BUFFER, self.buffer_texture_id)
|
||||
|
||||
def __exit__(self, *a):
|
||||
glBindTexture(GL_TEXTURE_2D_ARRAY, 0)
|
||||
glBindTexture(GL_TEXTURE_BUFFER, 0)
|
||||
glBindBuffer(GL_TEXTURE_BUFFER, 0)
|
||||
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32UI, 0)
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
class ShaderProgram:
|
||||
class ShaderProgram: # {{{
|
||||
""" Helper class for using GLSL shader programs """
|
||||
|
||||
def __init__(self, vertex, fragment):
|
||||
@@ -205,23 +248,43 @@ class ShaderProgram:
|
||||
raise ValueError('Error linking shader program: \n%s' % info.decode('utf-8'))
|
||||
glDeleteShader(vs_id)
|
||||
glDeleteShader(frag_id)
|
||||
self.vao_id = glGenVertexArrays(1)
|
||||
self.buffers = {}
|
||||
self.vertex_arrays = {}
|
||||
|
||||
def add_vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0):
|
||||
glBindVertexArray(self.vao_id)
|
||||
if name not in self.buffers:
|
||||
self.buffers[name] = glGenBuffers(1)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, self.buffers[name])
|
||||
aid = self.attribute_location(name)
|
||||
glEnableVertexAttribArray(aid)
|
||||
glVertexAttribPointer(aid, size, dtype, normalized, stride, offset)
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
||||
def vertex_array(self, name, size=3, dtype=GL_FLOAT, normalized=False, stride=0, offset=0, divisor=0):
|
||||
return VertexArray(name, size, dtype, normalized, stride, offset, divisor)
|
||||
|
||||
def add_vertex_arrays(self, *arrays):
|
||||
vao_id = glGenVertexArrays(1)
|
||||
self.vertex_arrays[vao_id] = buf_id = buffer_manager.create(for_use=GL_ARRAY_BUFFER)
|
||||
with self.bound_vertex_array(vao_id), buffer_manager.bound_buffer(buf_id):
|
||||
for x in arrays:
|
||||
aid = self.attribute_location(x.name)
|
||||
if aid > -1:
|
||||
glEnableVertexAttribArray(aid)
|
||||
glVertexAttribPointer(aid, x.size, x.dtype, x.normalized, x.stride, x.offset)
|
||||
if x.divisor > 0:
|
||||
glVertexAttribDivisor(aid, x.divisor)
|
||||
return vao_id
|
||||
|
||||
@contextmanager
|
||||
def bound_vertex_array(self, vao_id):
|
||||
glBindVertexArray(vao_id)
|
||||
yield
|
||||
glBindVertexArray(0)
|
||||
|
||||
def send_vertex_data(self, name, data, usage=GL_STATIC_DRAW):
|
||||
bufid = self.buffers[name]
|
||||
glNamedBufferData(bufid, sizeof(data), addressof(data), usage)
|
||||
def remove_vertex_array(self, vao_id):
|
||||
buf_id = self.vertex_arrays.pop(vao_id, None)
|
||||
if buf_id is not None:
|
||||
glDeleteVertexArray(vao_id)
|
||||
buffer_manager.delete(buf_id)
|
||||
|
||||
def send_vertex_data(self, vao_id, data, usage=GL_STREAM_DRAW):
|
||||
bufid = self.vertex_arrays[vao_id]
|
||||
buffer_manager.set_data(bufid, data, usage=usage)
|
||||
|
||||
def get_vertex_data(self, vao_id):
|
||||
bufid = self.vertex_arrays[vao_id]
|
||||
return buffer_manager.get_data(bufid)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return self.program_id
|
||||
@@ -259,8 +322,7 @@ class ShaderProgram:
|
||||
|
||||
def __enter__(self):
|
||||
glUseProgram(self.program_id)
|
||||
glBindVertexArray(self.vao_id)
|
||||
|
||||
def __exit__(self, *args):
|
||||
glUseProgram(0)
|
||||
glBindVertexArray(0)
|
||||
# }}}
|
||||
|
||||
@@ -160,8 +160,8 @@ update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned
|
||||
data[offset] = sp->x;
|
||||
data[offset+1] = sp->y;
|
||||
data[offset+2] = sp->z;
|
||||
data[offset+(reverse ? 4 : 3)] = to_color(color_profile, line->colors[i] & COL_MASK, default_fg);
|
||||
data[offset+(reverse ? 3 : 4)] = to_color(color_profile, line->colors[i] >> COL_SHIFT, default_bg);
|
||||
data[offset+(reverse ? 4 : 3)] = to_color(color_profile, line->fg_colors[i] & COL_MASK, default_fg);
|
||||
data[offset+(reverse ? 3 : 4)] = to_color(color_profile, line->bg_colors[i] & COL_MASK, default_bg);
|
||||
unsigned int decoration_fg = to_color(color_profile, line->decoration_fg[i] & COL_MASK, data[offset+3]);
|
||||
data[offset+5] = (decoration_fg & COL_MASK) | (decoration << 24) | (strikethrough << 26);
|
||||
previous_ch = ch; previous_width = (attrs) & WIDTH_MASK;
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from collections import deque
|
||||
from ctypes import addressof
|
||||
from functools import partial
|
||||
from threading import Lock
|
||||
from ctypes import addressof
|
||||
|
||||
from .borders import Borders
|
||||
from .char_grid import calculate_gl_geometry, render_cells
|
||||
from .child import Child
|
||||
from .config import build_ansi_color_table
|
||||
from .constants import get_boss, appname, shell_path, cell_size, queue_action, viewport_size, WindowGeometry, GLuint
|
||||
from .fast_data_types import glfw_post_empty_event, Screen, DECAWM, DATA_CELL_SIZE, ColorProfile
|
||||
from .char_grid import calculate_gl_geometry, render_cells
|
||||
from .layout import all_layouts
|
||||
from .constants import (
|
||||
GLuint, WindowGeometry, appname, cell_size, get_boss, queue_action,
|
||||
shell_path, viewport_size
|
||||
)
|
||||
from .fast_data_types import (
|
||||
DATA_CELL_SIZE, DECAWM, ColorProfile, Screen, glfw_post_empty_event
|
||||
)
|
||||
from .layout import Rect, all_layouts
|
||||
from .utils import color_as_int
|
||||
from .borders import Borders
|
||||
from .window import Window
|
||||
|
||||
|
||||
@@ -32,9 +37,12 @@ class Tab:
|
||||
self.borders = Borders(opts)
|
||||
self.windows = deque()
|
||||
self.active_window_idx = 0
|
||||
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
|
||||
setattr(self, which + '_window', partial(self.nth_window, num=i))
|
||||
if session_tab is None:
|
||||
self.cwd = args.directory
|
||||
l = self.enabled_layouts[0]
|
||||
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
|
||||
if special_window is None:
|
||||
queue_action(self.new_window)
|
||||
else:
|
||||
@@ -42,10 +50,8 @@ class Tab:
|
||||
else:
|
||||
self.cwd = session_tab.cwd or args.directory
|
||||
l = session_tab.layout
|
||||
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
|
||||
queue_action(self.startup, session_tab)
|
||||
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
|
||||
for i, which in enumerate('first second third fourth fifth sixth seventh eighth ninth tenth'.split()):
|
||||
setattr(self, which + '_window', partial(self.nth_window, num=i))
|
||||
|
||||
def startup(self, session_tab):
|
||||
for cmd in session_tab.windows:
|
||||
@@ -79,7 +85,9 @@ class Tab:
|
||||
self.relayout_borders()
|
||||
|
||||
def relayout_borders(self):
|
||||
self.borders(self.windows, self.active_window, self.current_layout, self.current_layout.needs_window_borders and len(self.windows) > 1)
|
||||
tm = get_boss().tab_manager
|
||||
self.borders(self.windows, self.active_window, self.current_layout, tm.blank_rects,
|
||||
self.current_layout.needs_window_borders and len(self.windows) > 1)
|
||||
|
||||
def next_layout(self):
|
||||
if len(self.opts.enabled_layouts) > 1:
|
||||
@@ -199,16 +207,17 @@ class TabManager:
|
||||
|
||||
def __init__(self, opts, args, startup_session):
|
||||
self.opts, self.args = opts, args
|
||||
self.buffer_id = None
|
||||
self.vao_id = None
|
||||
self.tabbar_lock = Lock()
|
||||
self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs]
|
||||
self.cell_ranges = []
|
||||
self.active_tab_idx = startup_session.active_tab_idx
|
||||
self.tabbar_lock = Lock()
|
||||
self.tabbar_dirty = True
|
||||
self.color_profile = ColorProfile()
|
||||
self.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
|
||||
self.default_fg = color_as_int(opts.inactive_tab_foreground)
|
||||
self.default_bg = color_as_int(opts.inactive_tab_background)
|
||||
self.tab_bar_blank_rects = ()
|
||||
|
||||
def as_rgb(x):
|
||||
return (x << 8) | 2
|
||||
@@ -216,12 +225,16 @@ class TabManager:
|
||||
self.active_bg = as_rgb(color_as_int(opts.active_tab_background))
|
||||
self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground))
|
||||
self.can_render = False
|
||||
if len(self.tabs) > 1:
|
||||
self.layout_tab_bar()
|
||||
|
||||
def resize(self, only_tabs=False):
|
||||
if not only_tabs:
|
||||
self.layout_tab_bar()
|
||||
for tab in self.tabs:
|
||||
tab.relayout()
|
||||
if only_tabs:
|
||||
return
|
||||
|
||||
def layout_tab_bar(self):
|
||||
self.can_render = False
|
||||
ncells = viewport_size.width // cell_size.width
|
||||
s = Screen(None, 1, ncells)
|
||||
@@ -232,9 +245,13 @@ class TabManager:
|
||||
self.tab_bar_screen = s
|
||||
self.tabbar_dirty = True
|
||||
margin = (viewport_size.width - ncells * cell_size.width) // 2
|
||||
self.window_geometry = WindowGeometry(
|
||||
self.window_geometry = g = WindowGeometry(
|
||||
margin, viewport_size.height - cell_size.height, viewport_size.width - margin, viewport_size.height, s.columns, s.lines)
|
||||
self.screen_geometry = calculate_gl_geometry(self.window_geometry)
|
||||
if margin > 0:
|
||||
self.tab_bar_blank_rects = (Rect(0, g.top, g.left, g.bottom), Rect(g.right - 1, g.top, viewport_size.width, g.bottom))
|
||||
else:
|
||||
self.tab_bar_blank_rects = ()
|
||||
self.screen_geometry = calculate_gl_geometry(g)
|
||||
self.screen = s
|
||||
self.can_render = True
|
||||
|
||||
@@ -254,10 +271,6 @@ class TabManager:
|
||||
def __len__(self):
|
||||
return len(self.tabs)
|
||||
|
||||
def new_tab(self, special_window=None):
|
||||
self.active_tab_idx = len(self.tabs)
|
||||
self.tabs.append(Tab(self.opts, self.args, self.title_changed, special_window=special_window))
|
||||
|
||||
@property
|
||||
def active_tab(self):
|
||||
return self.tabs[self.active_tab_idx] if self.tabs else None
|
||||
@@ -278,6 +291,16 @@ class TabManager:
|
||||
with self.tabbar_lock:
|
||||
self.tabbar_dirty = True
|
||||
|
||||
def new_tab(self, special_window=None):
|
||||
' Must be called in the GUI thread '
|
||||
needs_resize = len(self.tabs) == 1
|
||||
self.active_tab_idx = len(self.tabs)
|
||||
self.tabs.append(Tab(self.opts, self.args, self.title_changed, special_window=special_window))
|
||||
if needs_resize:
|
||||
if not self.can_render:
|
||||
queue_action(self.layout_tab_bar)
|
||||
queue_action(get_boss().tabbar_visibility_changed)
|
||||
|
||||
def remove(self, tab):
|
||||
' Must be called in the GUI thread '
|
||||
needs_resize = len(self.tabs) == 2
|
||||
@@ -288,7 +311,7 @@ class TabManager:
|
||||
if needs_resize:
|
||||
queue_action(get_boss().tabbar_visibility_changed)
|
||||
|
||||
def update_tab_bar_data(self, sprites):
|
||||
def update_tab_bar_data(self, sprites, cell_program):
|
||||
s = self.tab_bar_screen
|
||||
s.cursor.x = 0
|
||||
s.erase_in_line(2, False)
|
||||
@@ -311,15 +334,16 @@ class TabManager:
|
||||
s.cursor.bold = s.cursor.italic = False
|
||||
s.cursor.fg = s.cursor.bg = 0
|
||||
s.draw('┇')
|
||||
if s.cursor.x > s.columns - max_title_length:
|
||||
if s.cursor.x > s.columns - max_title_length and t is not self.tabs[-1]:
|
||||
s.draw('…')
|
||||
break
|
||||
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
|
||||
s.update_cell_data(
|
||||
sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True)
|
||||
sprites.render_dirty_cells()
|
||||
if self.buffer_id is None:
|
||||
self.buffer_id = sprites.add_sprite_map()
|
||||
sprites.set_sprite_map(self.buffer_id, self.sprite_map)
|
||||
if self.vao_id is None:
|
||||
self.vao_id = cell_program.create_sprite_map()
|
||||
cell_program.send_vertex_data(self.vao_id, self.sprite_map)
|
||||
|
||||
def activate_tab_at(self, x):
|
||||
x = (x - self.window_geometry.left) // cell_size.width
|
||||
@@ -328,10 +352,16 @@ class TabManager:
|
||||
queue_action(self.set_active_tab, i)
|
||||
return
|
||||
|
||||
@property
|
||||
def blank_rects(self):
|
||||
if len(self.tabs) < 2:
|
||||
return ()
|
||||
return self.tab_bar_blank_rects
|
||||
|
||||
def render(self, cell_program, sprites):
|
||||
if not self.can_render or len(self.tabs) < 2:
|
||||
return
|
||||
with self.tabbar_lock:
|
||||
if self.tabbar_dirty:
|
||||
self.update_tab_bar_data(sprites)
|
||||
render_cells(self.buffer_id, self.screen_geometry, cell_program, sprites)
|
||||
self.update_tab_bar_data(sprites, cell_program)
|
||||
render_cells(self.vao_id, self.screen_geometry, cell_program, sprites)
|
||||
|
||||
@@ -92,15 +92,15 @@ string_capabilities = {
|
||||
# Make cursor appear normal
|
||||
'cnorm': r'\E[?12l\E[?25h',
|
||||
# Carriage return
|
||||
'cr': r'^M',
|
||||
'cr': r'^M', # CR (carriage return \r)
|
||||
# Change scroll region
|
||||
'csr': r'\E[%i%p1%d;%p2%dr',
|
||||
# Move cursor to the left by the specified amount
|
||||
'cub': r'\E[%p1%dD',
|
||||
'cub1': r'^H',
|
||||
'cub1': r'^H', # BS (backspace)
|
||||
# Move cursor down specified number of lines
|
||||
'cud': r'\E[%p1%dB',
|
||||
'cud1': r'^J',
|
||||
'cud1': r'^J', # LF (line-feed \n)
|
||||
# Move cursor to the right by the specified amount
|
||||
'cuf': r'\E[%p1%dC',
|
||||
'cuf1': r'\E[C',
|
||||
@@ -203,6 +203,8 @@ string_capabilities = {
|
||||
'rmcup': r'\E[?1049l',
|
||||
# Exit insert mode
|
||||
'rmir': r'\E[4l',
|
||||
# Exit application keypad mode
|
||||
'rmkx': r'\E[?1l',
|
||||
# Exit standout mode
|
||||
'rmso': r'\E[27m',
|
||||
# Exit underline mode
|
||||
@@ -227,6 +229,8 @@ string_capabilities = {
|
||||
'smcup': r'\E[?1049h',
|
||||
# Enster insert mode
|
||||
'smir': r'\E[4h',
|
||||
# Enter application keymap mode
|
||||
'smkx': r'\E[?1h',
|
||||
# Enter standout mode
|
||||
'smso': r'\E[7m',
|
||||
# Enter underline mode
|
||||
@@ -373,6 +377,8 @@ termcap_aliases.update({
|
||||
'ZR': 'ritm',
|
||||
'as': 'smacs',
|
||||
'ae': 'rmacs',
|
||||
'ks': 'smkx',
|
||||
'ke': 'rmkx',
|
||||
'#2': 'kHOM',
|
||||
'#4': 'kLFT',
|
||||
'*7': 'kEND',
|
||||
|
||||
296
kitty/utils.py
296
kitty/utils.py
@@ -2,19 +2,20 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import re
|
||||
import os
|
||||
import signal
|
||||
import shlex
|
||||
import subprocess
|
||||
import math
|
||||
from collections import namedtuple
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import signal
|
||||
import string
|
||||
import subprocess
|
||||
from contextlib import contextmanager
|
||||
from functools import lru_cache
|
||||
from time import monotonic
|
||||
|
||||
from .constants import isosx
|
||||
from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl
|
||||
from .fast_data_types import glfw_get_physical_dpi, wcwidth as wcwidth_impl, redirect_std_streams
|
||||
from .rgb import Color, to_color
|
||||
|
||||
|
||||
def safe_print(*a, **k):
|
||||
@@ -36,6 +37,13 @@ def wcwidth(c: str) -> int:
|
||||
return wcwidth_impl(ord(c[0]))
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def pt_to_px(pts):
|
||||
dpix, dpiy = get_dpi()['logical']
|
||||
dpi = (dpix + dpiy) / 2
|
||||
return round(pts * dpi / 72)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def timeit(name, do_timing=False):
|
||||
if do_timing:
|
||||
@@ -49,15 +57,71 @@ def sanitize_title(x):
|
||||
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', x))
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def load_libx11():
|
||||
import ctypes
|
||||
from ctypes.util import find_library
|
||||
libx11 = ctypes.CDLL(find_library('X11'))
|
||||
ans = []
|
||||
|
||||
def cdef(name, restype, *argtypes):
|
||||
f = getattr(libx11, name)
|
||||
if restype is not None:
|
||||
f.restype = restype
|
||||
if argtypes:
|
||||
f.argtypes = argtypes
|
||||
ans.append(f)
|
||||
|
||||
cdef('XOpenDisplay', ctypes.c_void_p, ctypes.c_char_p)
|
||||
cdef('XCloseDisplay', ctypes.c_int, ctypes.c_void_p)
|
||||
cdef('XResourceManagerString', ctypes.c_char_p, ctypes.c_void_p)
|
||||
return ans
|
||||
|
||||
|
||||
def parse_xrdb(raw):
|
||||
q = 'Xft.dpi:\t'
|
||||
for line in raw.decode('utf-8').splitlines():
|
||||
if line.startswith(q):
|
||||
return float(line[len(q):])
|
||||
|
||||
|
||||
def x11_dpi_native():
|
||||
XOpenDisplay, XCloseDisplay, XResourceManagerString = load_libx11()
|
||||
display = XOpenDisplay(None)
|
||||
if display is None:
|
||||
raise RuntimeError('Could not connect to the X server')
|
||||
try:
|
||||
raw = XResourceManagerString(display)
|
||||
return parse_xrdb(raw)
|
||||
finally:
|
||||
XCloseDisplay(display)
|
||||
|
||||
|
||||
def x11_dpi():
|
||||
try:
|
||||
return x11_dpi_native()
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
raw = subprocess.check_output(['xrdb', '-query'])
|
||||
return parse_xrdb(raw)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def get_logical_dpi():
|
||||
if not hasattr(get_logical_dpi, 'ans'):
|
||||
if isosx:
|
||||
# TODO: Investigate if this needs a different implementation on OS X
|
||||
get_logical_dpi.ans = glfw_get_physical_dpi()
|
||||
else:
|
||||
raw = subprocess.check_output(['xdpyinfo']).decode('utf-8')
|
||||
m = re.search(r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE)
|
||||
get_logical_dpi.ans = int(m.group(1)), int(m.group(2))
|
||||
# See https://github.com/glfw/glfw/issues/1019 for why we cant use
|
||||
# glfw_get_physical_dpi()
|
||||
dpi = x11_dpi()
|
||||
if dpi is None:
|
||||
get_logical_dpi.ans = glfw_get_physical_dpi()
|
||||
else:
|
||||
get_logical_dpi.ans = dpi, dpi
|
||||
return get_logical_dpi.ans
|
||||
|
||||
|
||||
@@ -67,190 +131,15 @@ def get_dpi():
|
||||
get_dpi.ans = {'physical': pdpi, 'logical': get_logical_dpi()}
|
||||
return get_dpi.ans
|
||||
|
||||
# Color names {{{
|
||||
|
||||
|
||||
color_pat = re.compile(r'^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$')
|
||||
color_pat2 = re.compile(r'rgb:([a-f0-9]{2})/([a-f0-9]{2})/([a-f0-9]{2})$', re.IGNORECASE)
|
||||
|
||||
color_names = {
|
||||
'aliceblue': 'f0f8ff',
|
||||
'antiquewhite': 'faebd7',
|
||||
'aqua': '00ffff',
|
||||
'aquamarine': '7fffd4',
|
||||
'azure': 'f0ffff',
|
||||
'beige': 'f5f5dc',
|
||||
'bisque': 'ffe4c4',
|
||||
'black': '000000',
|
||||
'blanchedalmond': 'ffebcd',
|
||||
'blue': '0000ff',
|
||||
'blueviolet': '8a2be2',
|
||||
'brown': 'a52a2a',
|
||||
'burlywood': 'deb887',
|
||||
'cadetblue': '5f9ea0',
|
||||
'chartreuse': '7fff00',
|
||||
'chocolate': 'd2691e',
|
||||
'coral': 'ff7f50',
|
||||
'cornflowerblue': '6495ed',
|
||||
'cornsilk': 'fff8dc',
|
||||
'crimson': 'dc143c',
|
||||
'cyan': '00ffff',
|
||||
'darkblue': '00008b',
|
||||
'darkcyan': '008b8b',
|
||||
'darkgoldenrod': 'b8860b',
|
||||
'darkgray': 'a9a9a9',
|
||||
'darkgrey': 'a9a9a9',
|
||||
'darkgreen': '006400',
|
||||
'darkkhaki': 'bdb76b',
|
||||
'darkmagenta': '8b008b',
|
||||
'darkolivegreen': '556b2f',
|
||||
'darkorange': 'ff8c00',
|
||||
'darkorchid': '9932cc',
|
||||
'darkred': '8b0000',
|
||||
'darksalmon': 'e9967a',
|
||||
'darkseagreen': '8fbc8f',
|
||||
'darkslateblue': '483d8b',
|
||||
'darkslategray': '2f4f4f',
|
||||
'darkslategrey': '2f4f4f',
|
||||
'darkturquoise': '00ced1',
|
||||
'darkviolet': '9400d3',
|
||||
'deeppink': 'ff1493',
|
||||
'deepskyblue': '00bfff',
|
||||
'dimgray': '696969',
|
||||
'dimgrey': '696969',
|
||||
'dodgerblue': '1e90ff',
|
||||
'firebrick': 'b22222',
|
||||
'floralwhite': 'fffaf0',
|
||||
'forestgreen': '228b22',
|
||||
'fuchsia': 'ff00ff',
|
||||
'gainsboro': 'dcdcdc',
|
||||
'ghostwhite': 'f8f8ff',
|
||||
'gold': 'ffd700',
|
||||
'goldenrod': 'daa520',
|
||||
'gray': '808080',
|
||||
'grey': '808080',
|
||||
'green': '008000',
|
||||
'greenyellow': 'adff2f',
|
||||
'honeydew': 'f0fff0',
|
||||
'hotpink': 'ff69b4',
|
||||
'indianred': 'cd5c5c',
|
||||
'indigo': '4b0082',
|
||||
'ivory': 'fffff0',
|
||||
'khaki': 'f0e68c',
|
||||
'lavender': 'e6e6fa',
|
||||
'lavenderblush': 'fff0f5',
|
||||
'lawngreen': '7cfc00',
|
||||
'lemonchiffon': 'fffacd',
|
||||
'lightblue': 'add8e6',
|
||||
'lightcoral': 'f08080',
|
||||
'lightcyan': 'e0ffff',
|
||||
'lightgoldenrodyellow': 'fafad2',
|
||||
'lightgray': 'd3d3d3',
|
||||
'lightgrey': 'd3d3d3',
|
||||
'lightgreen': '90ee90',
|
||||
'lightpink': 'ffb6c1',
|
||||
'lightsalmon': 'ffa07a',
|
||||
'lightseagreen': '20b2aa',
|
||||
'lightskyblue': '87cefa',
|
||||
'lightslategray': '778899',
|
||||
'lightslategrey': '778899',
|
||||
'lightsteelblue': 'b0c4de',
|
||||
'lightyellow': 'ffffe0',
|
||||
'lime': '00ff00',
|
||||
'limegreen': '32cd32',
|
||||
'linen': 'faf0e6',
|
||||
'magenta': 'ff00ff',
|
||||
'maroon': '800000',
|
||||
'mediumaquamarine': '66cdaa',
|
||||
'mediumblue': '0000cd',
|
||||
'mediumorchid': 'ba55d3',
|
||||
'mediumpurple': '9370db',
|
||||
'mediumseagreen': '3cb371',
|
||||
'mediumslateblue': '7b68ee',
|
||||
'mediumspringgreen': '00fa9a',
|
||||
'mediumturquoise': '48d1cc',
|
||||
'mediumvioletred': 'c71585',
|
||||
'midnightblue': '191970',
|
||||
'mintcream': 'f5fffa',
|
||||
'mistyrose': 'ffe4e1',
|
||||
'moccasin': 'ffe4b5',
|
||||
'navajowhite': 'ffdead',
|
||||
'navy': '000080',
|
||||
'oldlace': 'fdf5e6',
|
||||
'olive': '808000',
|
||||
'olivedrab': '6b8e23',
|
||||
'orange': 'ffa500',
|
||||
'orangered': 'ff4500',
|
||||
'orchid': 'da70d6',
|
||||
'palegoldenrod': 'eee8aa',
|
||||
'palegreen': '98fb98',
|
||||
'paleturquoise': 'afeeee',
|
||||
'palevioletred': 'db7093',
|
||||
'papayawhip': 'ffefd5',
|
||||
'peachpuff': 'ffdab9',
|
||||
'per': 'cd853f',
|
||||
'pink': 'ffc0cb',
|
||||
'plum': 'dda0dd',
|
||||
'powderblue': 'b0e0e6',
|
||||
'purple': '800080',
|
||||
'red': 'ff0000',
|
||||
'rosybrown': 'bc8f8f',
|
||||
'royalblue': '4169e1',
|
||||
'saddlebrown': '8b4513',
|
||||
'salmon': 'fa8072',
|
||||
'sandybrown': 'f4a460',
|
||||
'seagreen': '2e8b57',
|
||||
'seashell': 'fff5ee',
|
||||
'sienna': 'a0522d',
|
||||
'silver': 'c0c0c0',
|
||||
'skyblue': '87ceeb',
|
||||
'slateblue': '6a5acd',
|
||||
'slategray': '708090',
|
||||
'slategrey': '708090',
|
||||
'snow': 'fffafa',
|
||||
'springgreen': '00ff7f',
|
||||
'steelblue': '4682b4',
|
||||
'tan': 'd2b48c',
|
||||
'teal': '008080',
|
||||
'thistle': 'd8bfd8',
|
||||
'tomato': 'ff6347',
|
||||
'turquoise': '40e0d0',
|
||||
'violet': 'ee82ee',
|
||||
'wheat': 'f5deb3',
|
||||
'white': 'ffffff',
|
||||
'whitesmoke': 'f5f5f5',
|
||||
'yellow': 'ffff00',
|
||||
'yellowgreen': '9acd32',
|
||||
}
|
||||
Color = namedtuple('Color', 'red green blue')
|
||||
# }}}
|
||||
|
||||
|
||||
def to_color(raw, validate=False):
|
||||
x = raw.strip().lower()
|
||||
m = color_pat.match(x)
|
||||
val = None
|
||||
if m is not None:
|
||||
val = m.group(1)
|
||||
if len(val) == 3:
|
||||
val = ''.join(2 * s for s in val)
|
||||
else:
|
||||
m = color_pat2.match(x)
|
||||
if m is not None:
|
||||
val = m.group(1) + m.group(2) + m.group(3)
|
||||
else:
|
||||
val = color_names.get(x)
|
||||
if val is None:
|
||||
if validate:
|
||||
raise ValueError('Invalid color name: {}'.format(raw))
|
||||
return
|
||||
return Color(int(val[:2], 16), int(val[2:4], 16), int(val[4:], 16))
|
||||
|
||||
|
||||
def color_as_int(val):
|
||||
return val[0] << 16 | val[1] << 8 | val[2]
|
||||
|
||||
|
||||
def color_from_int(val):
|
||||
return Color((val >> 16) & 0xFF, (val >> 8) & 0xFF, val & 0xFF)
|
||||
|
||||
|
||||
def parse_color_set(raw):
|
||||
parts = raw.split(';')
|
||||
for c, spec in [parts[i:i + 2] for i in range(0, len(parts), 2)]:
|
||||
@@ -292,7 +181,21 @@ def get_primary_selection():
|
||||
return '' # There is no primary selection on OS X
|
||||
# glfw has no way to get the primary selection
|
||||
# https://github.com/glfw/glfw/issues/894
|
||||
return subprocess.check_output(['xsel', '-p']).decode('utf-8')
|
||||
return subprocess.check_output(['xsel', '-p'], stderr=open(os.devnull, 'wb'), stdin=open(os.devnull, 'rb')).decode('utf-8')
|
||||
|
||||
|
||||
def base64_encode(
|
||||
integer,
|
||||
chars=string.ascii_uppercase + string.ascii_lowercase + string.digits +
|
||||
'+/'
|
||||
):
|
||||
ans = ''
|
||||
while True:
|
||||
integer, remainder = divmod(integer, 64)
|
||||
ans = chars[remainder] + ans
|
||||
if integer == 0:
|
||||
break
|
||||
return ans
|
||||
|
||||
|
||||
def set_primary_selection(text):
|
||||
@@ -300,7 +203,7 @@ def set_primary_selection(text):
|
||||
return # There is no primary selection on OS X
|
||||
if isinstance(text, str):
|
||||
text = text.encode('utf-8')
|
||||
p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE)
|
||||
p = subprocess.Popen(['xsel', '-i', '-p'], stdin=subprocess.PIPE, stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT)
|
||||
p.stdin.write(text), p.stdin.close()
|
||||
p.wait()
|
||||
|
||||
@@ -312,3 +215,14 @@ def open_url(url, program='default'):
|
||||
cmd = shlex.split(program)
|
||||
cmd.append(url)
|
||||
subprocess.Popen(cmd).wait()
|
||||
|
||||
|
||||
def detach(fork=True, setsid=True, redirect=True):
|
||||
if fork:
|
||||
# Detach from the controlling process.
|
||||
if os.fork() != 0:
|
||||
raise SystemExit(0)
|
||||
if setsid:
|
||||
os.setsid()
|
||||
if redirect:
|
||||
redirect_std_streams(os.devnull)
|
||||
|
||||
@@ -8,7 +8,7 @@ from collections import deque
|
||||
from functools import partial
|
||||
from time import monotonic
|
||||
|
||||
from .char_grid import CharGrid
|
||||
from .char_grid import CharGrid, DynamicColor
|
||||
from .constants import wakeup, get_boss, appname, WindowGeometry, is_key_pressed, mouse_button_pressed, cell_size
|
||||
from .fast_data_types import (
|
||||
BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump,
|
||||
@@ -17,15 +17,26 @@ from .fast_data_types import (
|
||||
GLFW_MOUSE_BUTTON_5, ANY_MODE, MOTION_MODE, GLFW_KEY_LEFT_SHIFT,
|
||||
GLFW_KEY_RIGHT_SHIFT, GLFW_KEY_UP, GLFW_KEY_DOWN, GLFW_MOUSE_BUTTON_4
|
||||
)
|
||||
from .keys import key_map
|
||||
from .keys import get_key_map
|
||||
from .mouse import encode_mouse_event, PRESS, RELEASE, MOVE, DRAG
|
||||
from .terminfo import get_capabilities
|
||||
from .utils import sanitize_title, get_primary_selection, parse_color_set, safe_print
|
||||
|
||||
DYNAMIC_COLOR_CODES = {
|
||||
10: DynamicColor.default_fg,
|
||||
11: DynamicColor.default_bg,
|
||||
12: DynamicColor.cursor_color,
|
||||
17: DynamicColor.highlight_bg,
|
||||
19: DynamicColor.highlight_fg,
|
||||
}
|
||||
DYNAMIC_COLOR_CODES.update({k+100: v for k, v in DYNAMIC_COLOR_CODES.items()})
|
||||
dump_bytes_opened = False
|
||||
|
||||
|
||||
class Window:
|
||||
|
||||
def __init__(self, tab, child, opts, args):
|
||||
global dump_bytes_opened
|
||||
self.tabref = weakref.ref(tab)
|
||||
self.override_title = None
|
||||
self.last_mouse_cursor_pos = 0, 0
|
||||
@@ -37,8 +48,13 @@ class Window:
|
||||
self._is_visible_in_layout = True
|
||||
self.child, self.opts = child, opts
|
||||
self.child_fd = child.child_fd
|
||||
self.start_visual_bell_at = None
|
||||
self.screen = Screen(self, 24, 80, opts.scrollback_lines)
|
||||
self.read_bytes = partial(read_bytes_dump, self.dump_commands) if args.dump_commands else read_bytes
|
||||
self.read_bytes = partial(read_bytes_dump, self.dump_commands) if args.dump_commands or args.dump_bytes else read_bytes
|
||||
if args.dump_bytes:
|
||||
mode = 'ab' if dump_bytes_opened else 'wb'
|
||||
self.dump_bytes_to = open(args.dump_bytes, mode)
|
||||
dump_bytes_opened = True
|
||||
self.draw_dump_buf = []
|
||||
self.write_buf = memoryview(b'')
|
||||
self.char_grid = CharGrid(self.screen, opts)
|
||||
@@ -106,6 +122,22 @@ class Window:
|
||||
self.write_buf = memoryview(self.write_buf.tobytes() + data)
|
||||
wakeup()
|
||||
|
||||
def bell(self):
|
||||
if self.opts.enable_audio_bell:
|
||||
try:
|
||||
with open('/dev/tty', 'wb') as f:
|
||||
f.write(b'\007')
|
||||
except EnvironmentError:
|
||||
pass # failure to beep is not critical
|
||||
if self.opts.visual_bell_duration > 0:
|
||||
self.start_visual_bell_at = monotonic()
|
||||
tm = get_boss()
|
||||
tm.queue_ui_action(tm.request_attention)
|
||||
glfw_post_empty_event()
|
||||
|
||||
def use_utf8(self, on):
|
||||
self.child.set_iutf8(on)
|
||||
|
||||
def update_screen(self):
|
||||
self.char_grid.update_cell_data()
|
||||
glfw_post_empty_event()
|
||||
@@ -130,12 +162,11 @@ class Window:
|
||||
pass # TODO: Implement this
|
||||
|
||||
def set_dynamic_color(self, code, value):
|
||||
wmap = {10: 'fg', 11: 'bg', 110: 'fg', 111: 'bg'}
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode('utf-8')
|
||||
color_changes = {}
|
||||
for val in value.split(';'):
|
||||
w = wmap.get(code)
|
||||
w = DYNAMIC_COLOR_CODES.get(code)
|
||||
if w is not None:
|
||||
if code >= 110:
|
||||
val = None
|
||||
@@ -210,7 +241,7 @@ class Window:
|
||||
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
|
||||
self.last_mouse_cursor_pos = x, y
|
||||
tm = get_boss()
|
||||
tm.queue_ui_action(get_boss().change_mouse_cursor, self.char_grid.has_url_at(x, y))
|
||||
tm.queue_ui_action(tm.change_mouse_cursor, self.char_grid.has_url_at(x, y))
|
||||
if send_event:
|
||||
x, y = self.char_grid.cell_for_pos(x, y)
|
||||
if x is not None:
|
||||
@@ -254,12 +285,20 @@ class Window:
|
||||
if ev:
|
||||
self.write_to_child(ev)
|
||||
else:
|
||||
k = key_map[GLFW_KEY_UP if upwards else GLFW_KEY_DOWN]
|
||||
k = get_key_map(self.screen)[GLFW_KEY_UP if upwards else GLFW_KEY_DOWN]
|
||||
self.write_to_child(k * abs(s))
|
||||
|
||||
def buf_toggled(self, is_main_linebuf):
|
||||
self.char_grid.scroll('full', False)
|
||||
|
||||
def render_cells(self, render_data, program, sprites):
|
||||
invert_colors = False
|
||||
if self.start_visual_bell_at is not None:
|
||||
invert_colors = monotonic() - self.start_visual_bell_at <= self.opts.visual_bell_duration
|
||||
if not invert_colors:
|
||||
self.start_visual_bell_at = None
|
||||
self.char_grid.render_cells(render_data, program, sprites, invert_colors)
|
||||
|
||||
# actions {{{
|
||||
|
||||
def show_scrollback(self):
|
||||
@@ -327,6 +366,9 @@ class Window:
|
||||
self.draw_dump_buf = []
|
||||
else:
|
||||
self.draw_dump_buf.append(a[1])
|
||||
elif a[0] == 'bytes':
|
||||
self.dump_bytes_to.write(a[1])
|
||||
self.dump_bytes_to.flush()
|
||||
else:
|
||||
if self.draw_dump_buf:
|
||||
safe_print('draw', ''.join(self.draw_dump_buf))
|
||||
|
||||
@@ -33,10 +33,14 @@ class Callbacks:
|
||||
def buf_toggled(self, is_alt):
|
||||
self.is_alt = is_alt
|
||||
|
||||
def use_utf8(self, on):
|
||||
self.iutf8 = on
|
||||
|
||||
def clear(self):
|
||||
self.wtcbuf = b''
|
||||
self.iconbuf = self.titlebuf = self.colorbuf = self.qbuf = self.ctbuf = ''
|
||||
self.is_alt = False
|
||||
self.iutf8 = True
|
||||
|
||||
|
||||
def filled_line_buf(ynum=5, xnum=5, cursor=Cursor()):
|
||||
|
||||
81
kitty_tests/keys.py
Normal file
81
kitty_tests/keys.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from functools import partial
|
||||
|
||||
import kitty.fast_data_types as defines
|
||||
from kitty.keys import (
|
||||
interpret_key_event, modify_complex_key, modify_key_bytes, smkx_key_map
|
||||
)
|
||||
|
||||
from . import BaseTest
|
||||
|
||||
|
||||
class DummyWindow:
|
||||
|
||||
def __init__(self):
|
||||
self.screen = self
|
||||
self.extended_keyboard = False
|
||||
self.cursor_key_mode = True
|
||||
|
||||
|
||||
class TestParser(BaseTest):
|
||||
|
||||
def test_modify_complex_key(self):
|
||||
self.ae(modify_complex_key('kcuu1', 4), b'\033[1;4A')
|
||||
self.ae(modify_complex_key('kcuu1', 3), b'\033[1;3A')
|
||||
self.ae(modify_complex_key('kf5', 3), b'\033[15;3~')
|
||||
self.assertRaises(ValueError, modify_complex_key, 'kri', 3)
|
||||
|
||||
def test_interpret_key_event(self):
|
||||
# test rmkx/smkx
|
||||
w = DummyWindow()
|
||||
|
||||
def k(expected, key, mods=0):
|
||||
actual = interpret_key_event(
|
||||
getattr(defines, 'GLFW_KEY_' + key),
|
||||
0,
|
||||
mods,
|
||||
w,
|
||||
defines.GLFW_PRESS,
|
||||
get_localized_key=lambda k, s: k
|
||||
)
|
||||
self.ae(b'\033' + expected.encode('ascii'), actual)
|
||||
|
||||
for ckm, mch in {True: 'O', False: '['}.items():
|
||||
w.cursor_key_mode = ckm
|
||||
for name, ch in {
|
||||
'UP': 'A',
|
||||
'DOWN': 'B',
|
||||
'RIGHT': 'C',
|
||||
'LEFT': 'D',
|
||||
'HOME': 'H',
|
||||
'END': 'F',
|
||||
}.items():
|
||||
k(mch + ch, name)
|
||||
w.cursor_key_mode = True
|
||||
|
||||
# test remaining special keys
|
||||
for key, num in zip('INSERT DELETE PAGE_UP PAGE_DOWN'.split(), '2356'):
|
||||
k('[' + num + '~', key)
|
||||
for key, num in zip('1234', 'PQRS'):
|
||||
k('O' + num, 'F' + key)
|
||||
for key, num in zip(range(5, 13), (15, 17, 18, 19, 20, 21, 23, 24)):
|
||||
k('[' + str(num) + '~', 'F{}'.format(key))
|
||||
|
||||
# test modifiers
|
||||
SPECIAL_KEYS = 'UP DOWN RIGHT LEFT HOME END INSERT DELETE PAGE_UP PAGE_DOWN '
|
||||
for i in range(1, 13):
|
||||
SPECIAL_KEYS += 'F{} '.format(i)
|
||||
SPECIAL_KEYS = SPECIAL_KEYS.strip().split()
|
||||
for mods, num in zip(('CONTROL', 'ALT', 'SHIFT+ALT'), '534'):
|
||||
fmods = 0
|
||||
num = int(num)
|
||||
for m in mods.split('+'):
|
||||
fmods |= getattr(defines, 'GLFW_MOD_' + m)
|
||||
km = partial(k, mods=fmods)
|
||||
for key in SPECIAL_KEYS:
|
||||
keycode = getattr(defines, 'GLFW_KEY_' + key)
|
||||
base_key = smkx_key_map[keycode]
|
||||
km(modify_key_bytes(base_key, num).decode('ascii')[1:], key)
|
||||
@@ -72,11 +72,17 @@ class TestParser(BaseTest):
|
||||
self.ae(str(s.line(0)), '123 ')
|
||||
|
||||
def test_charsets(self):
|
||||
s = self.create_screen()
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
pb(b'\xc3')
|
||||
pb(b'\xa1', ('draw', b'\xc3\xa1'.decode('utf-8')))
|
||||
s = self.create_screen()
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
pb('\033)0\x0e/_', ('screen_designate_charset', 1, ord('0')), ('screen_change_charset', 1), '/_')
|
||||
self.ae(str(s.line(0)), '/\xa0 ')
|
||||
pb('\033%G_', ('screen_use_latin1', 0), '_')
|
||||
self.assertTrue(s.callbacks.iutf8)
|
||||
pb('\033%@_', ('screen_use_latin1', 1), '_')
|
||||
self.assertFalse(s.callbacks.iutf8)
|
||||
s = self.create_screen()
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
pb('\033(0/_', ('screen_designate_charset', 0, ord('0')), '/_')
|
||||
@@ -132,6 +138,18 @@ class TestParser(BaseTest):
|
||||
c.clear()
|
||||
pb('\033[6n', ('report_device_status', 6, 0))
|
||||
self.ae(c.wtcbuf, b'\033[2;1R')
|
||||
c.clear()
|
||||
s.cursor_key_mode = True
|
||||
pb('\033[?1$p', ('report_mode_status', 1, 1))
|
||||
self.ae(c.wtcbuf, b'\033[?1;1$y')
|
||||
pb('\033[?1l', ('screen_reset_mode', 1, 1))
|
||||
self.assertFalse(s.cursor_key_mode)
|
||||
c.clear()
|
||||
pb('\033[?2016$p', ('report_mode_status', 2016, 1))
|
||||
self.ae(c.wtcbuf, b'\033[?2016;3$y')
|
||||
c.clear()
|
||||
pb('\033[?1$p', ('report_mode_status', 1, 1))
|
||||
self.ae(c.wtcbuf, b'\033[?1;2$y')
|
||||
pb('\033[2;4r', ('screen_set_margins', 2, 4))
|
||||
self.ae(s.margin_top, 1), self.ae(s.margin_bottom, 3)
|
||||
pb('\033[r', ('screen_set_margins', 0, 0))
|
||||
@@ -144,9 +162,9 @@ class TestParser(BaseTest):
|
||||
s = self.create_screen()
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
c = s.callbacks
|
||||
pb('a\033]2;xyz\x9cbcde', 'a', ('set_title', 'xyz'), 'bcde')
|
||||
pb('a\033]2;x\\ryz\x9cbcde', 'a', ('set_title', 'x\\ryz'), 'bcde')
|
||||
self.ae(str(s.line(0)), 'abcde')
|
||||
self.ae(c.titlebuf, 'xyz')
|
||||
self.ae(c.titlebuf, 'x\\ryz')
|
||||
c.clear()
|
||||
pb('\033]\x07', ('set_title', ''), ('set_icon', ''))
|
||||
self.ae(c.titlebuf, ''), self.ae(c.iconbuf, '')
|
||||
@@ -155,7 +173,7 @@ class TestParser(BaseTest):
|
||||
c.clear()
|
||||
pb('\033]2;;;;\x07', ('set_title', ';;;'))
|
||||
self.ae(c.titlebuf, ';;;')
|
||||
pb('\033]110\x07', ('set_dynamic_color', ''))
|
||||
pb('\033]110\x07', ('set_dynamic_color', 110, ''))
|
||||
self.ae(c.colorbuf, '')
|
||||
|
||||
def test_dcs_codes(self):
|
||||
@@ -169,4 +187,4 @@ class TestParser(BaseTest):
|
||||
pb = partial(self.parse_bytes_dump, s)
|
||||
for prefix in '\033_', '\033^', '\u009e', '\u009f':
|
||||
for suffix in '\u009c', '\033\\':
|
||||
pb('a{}+++{}bcde'.format(prefix, suffix), 'abcde')
|
||||
pb('a{}+\\++{}bcde'.format(prefix, suffix), 'abcde')
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
os.chdir(base)
|
||||
|
||||
|
||||
defns = defaultdict(list)
|
||||
|
||||
for line in open('kitty/kitty.conf'):
|
||||
@@ -17,13 +18,35 @@ for line in open('kitty/kitty.conf'):
|
||||
_, sc, name = line.split(maxsplit=3)
|
||||
defns[name].append('`' + sc + '`')
|
||||
|
||||
defns = [':sc_{}: pass:quotes[{}]'.format(name, ' or '.join(defns[name])) for name in sorted(defns)]
|
||||
defns = [
|
||||
':sc_{}: pass:quotes[{}]'.format(name, ' or '.join(defns[name]))
|
||||
for name in sorted(defns)
|
||||
]
|
||||
defns = '\n'.join(defns)
|
||||
|
||||
raw = open('README.asciidoc').read()
|
||||
pat = re.compile(r'^// START_SHORTCUT_BLOCK$.+?^// END_SHORTCUT_BLOCK$', re.M | re.DOTALL)
|
||||
nraw = pat.sub('// START_SHORTCUT_BLOCK\n' +
|
||||
defns + '\n// END_SHORTCUT_BLOCK', raw)
|
||||
pat = re.compile(
|
||||
r'^// START_SHORTCUT_BLOCK$.+?^// END_SHORTCUT_BLOCK$', re.M | re.DOTALL
|
||||
)
|
||||
nraw = pat.sub(
|
||||
'// START_SHORTCUT_BLOCK\n' + defns + '\n// END_SHORTCUT_BLOCK', raw
|
||||
)
|
||||
if raw != nraw:
|
||||
print('Updating shortcuts block')
|
||||
open('README.asciidoc', 'w').write(nraw)
|
||||
|
||||
raw = subprocess.check_output([
|
||||
'kitty', '-c',
|
||||
'from kitty.key_encoding import *; import json; print(json.dumps(ENCODING))'
|
||||
]).decode('utf-8')
|
||||
key_map = json.loads(raw)
|
||||
lines = [
|
||||
'See link:protocol-extensions.asciidoc#keyboard-handling[Keyboard Handling protocol extension]',
|
||||
'', '|===', '| Name | Encoded representation (base64)', ''
|
||||
]
|
||||
for k in sorted(key_map):
|
||||
lines.append('| {:15s} | `{}`'.format(k.replace('_', ' '), key_map[k].replace('`', '\\`')))
|
||||
lines += ['', '|===']
|
||||
with open('key_encoding.asciidoc', 'w') as f:
|
||||
print('= Key encoding for extended keyboard protocol\n', file=f)
|
||||
print('\n'.join(lines), file=f)
|
||||
|
||||
@@ -5,7 +5,19 @@
|
||||
kitty has a few extensions to the xterm protocol, to enable advanced features.
|
||||
These are typically in the form of new or re-purposed escape codes. While these
|
||||
extensions are currently kitty specific, it would be nice to get some of them
|
||||
adopted more broadly.
|
||||
adopted more broadly, to push the state of terminal emulators forward.
|
||||
|
||||
The goal of these extensions is to be as small an unobtrusive as possible,
|
||||
while filling in some gaps in the existing xterm protocol. In particular, one
|
||||
of the goals of this specification is explicitly not to "re-imagine" the tty.
|
||||
The tty should remain what it is -- a device for efficiently processing text
|
||||
received as a simple byte stream. Another objective is to only move the minimum
|
||||
possible amount of extra functionality into the terminal program itself. This
|
||||
is to make it as easy to implement these protocol extensions as possible,
|
||||
thereby hopefully encouraging their widespread adoption.
|
||||
|
||||
If you wish to discuss these extensions, propose additions/changes to them
|
||||
please do so by opening issues in the github bug tracker.
|
||||
|
||||
toc::[]
|
||||
|
||||
@@ -16,9 +28,6 @@ in terminal editors such as vim and emacs to display red, wavy underlines under
|
||||
mis-spelled words and/or syntax errors. This is done by re-purposing some SGR escape codes
|
||||
that are not used in modern terminals (https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_codes)
|
||||
|
||||
Setting the width and/or height to zero means that no drawing is done and the
|
||||
cursor position remains unchanged.
|
||||
|
||||
To change the underline style from straight line to curl (this used to be the
|
||||
code for rapid blinking text, only previous use I know of was in MS-DOS ANSI.sys):
|
||||
|
||||
@@ -41,6 +50,23 @@ To reset the underline color (also previously reserved and unused):
|
||||
<ESC>[59m
|
||||
```
|
||||
|
||||
A client can query if the terminal emulator supports this underline extension using the
|
||||
standard link:http://vt100.net/docs/vt510-rm/DECRQM[DECRQM] escape sequence, with the
|
||||
mode value `2016`, like this:
|
||||
|
||||
Client sends:
|
||||
|
||||
```
|
||||
<ESC>[?2016$p
|
||||
```
|
||||
|
||||
If the terminal supports this underline extension, it must respond with
|
||||
link:http://vt100.net/docs/vt510-rm/DECRPM[DECRPM]
|
||||
|
||||
```
|
||||
<ESC>[?2016;3$p
|
||||
```
|
||||
|
||||
|
||||
== Graphics rendering
|
||||
|
||||
@@ -54,6 +80,8 @@ emulator. The major design goals are
|
||||
the existing cursor based protocols.
|
||||
* Should use optimizations when the client is running on the same computer as the terminal emulator.
|
||||
|
||||
For some discussion regarding the design choices, see link:../../issues/33[#33].
|
||||
|
||||
=== Getting the window size
|
||||
|
||||
In order to know what size of images to display and how to position them, the client must be able to get the
|
||||
@@ -296,3 +324,83 @@ the file repeatedly with no overhead.
|
||||
| y | _y-offset_ -- the row in the pixel data to start from (0-based)
|
||||
|
||||
|===
|
||||
|
||||
|
||||
== Keyboard handling
|
||||
|
||||
There are various problems with the current state of keyboard handling. They
|
||||
include:
|
||||
|
||||
* No way to use modifiers other than `Ctrl` and `Alt`
|
||||
* No way to use multiple modifier keys, other than, `Shift+Alt`.
|
||||
* No way to handle different types of keyboard events, such as press, release or repeat
|
||||
* No reliable way to distinguish single `Esc` keypresses from the
|
||||
start of a escape sequence. Currently, client programs use
|
||||
fragile timing related hacks for this, leading to bugs, for example:
|
||||
link:https://github.com/neovim/neovim/issues/2035[neovim #2035]
|
||||
|
||||
There are already two distinct keyboard handling modes, _normal mode_ and
|
||||
_application mode_. These modes generate different escape sequences for the
|
||||
various special keys (arrow keys, function keys, home/end etc.) Most terminals
|
||||
start out in normal mode, however, most shell programs like `bash` switch them to
|
||||
application mode. We propose adding a third mode, named _full mode_ that addresses
|
||||
the shortcomings listed above.
|
||||
|
||||
Switching to the new _full mode_ is accomplished using the standard private
|
||||
mode DECSET escape sequence
|
||||
|
||||
```
|
||||
<ESC>[?2017h
|
||||
```
|
||||
|
||||
and to leave _full mode_, use DECRST
|
||||
|
||||
```
|
||||
<ESC>[?2017l
|
||||
```
|
||||
|
||||
The number `2017` above is not used for any existing modes, as far as I know.
|
||||
Client programs can query if the terminal emulator is in _full mode_ by using
|
||||
the standard link:http://vt100.net/docs/vt510-rm/DECRQM[DECRQM] escape sequence.
|
||||
|
||||
The new mode works as follows:
|
||||
|
||||
* All printable key presses without modifier keys are sent just as in the
|
||||
_normal mode_. This means all printable ASCII characters and in addition,
|
||||
`Enter`, `Space` and `Backspace`. Also any unicode characters generated by
|
||||
platform specific extended input modes, such as using the `AltGr` key. This
|
||||
is done so that client programs that are not aware of this mode can still
|
||||
handle basic text entry, so if a _full mode_ using program crashes and does
|
||||
not reset, the user can still issue a `reset` command in the shell to restore
|
||||
normal key handling. Note that this includes pressing the `Shift` modifier
|
||||
and printable keys.
|
||||
|
||||
* For non printable keys and key combinations including one or more modifiers,
|
||||
an escape sequence encoding the key event is sent. For details on the
|
||||
escape sequence, see below.
|
||||
|
||||
The escape sequence encodes the following properties:
|
||||
|
||||
* Type of event: `press,repeat,release`
|
||||
* Modifiers pressed at the time of the event
|
||||
* The actual key being pressed
|
||||
|
||||
```
|
||||
<ESC>_K<type><modifiers><key><ESC>\
|
||||
```
|
||||
|
||||
Where `<type>` is one of `p` -- press, `r` -- release and `t` -- repeat.
|
||||
Modifiers is a bitmask represented as a single base64 digit. Shift -- `0x1`,
|
||||
Control -- `0x2`, Alt -- `0x4` and Super -- `0x8`. `<key>` is a number
|
||||
(encoded in base85) corresponding to the key pressed. The key name to number
|
||||
mapping is defined in link:key_encoding.asciidoc[this table].
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
<ESC>_KpGp<ESC>\ is <Ctrl>+<Alt>+x (press)
|
||||
<ESC>_KrP8<ESC>\ is <Ctrl>+<Alt>+<Shift>+<Super>+PageUp (release)
|
||||
```
|
||||
|
||||
This encoding means each key event is represented by 8 or 9 printable ascii
|
||||
only bytes, for maximum robustness.
|
||||
|
||||
22
publish.py
22
publish.py
@@ -65,8 +65,10 @@ class ReadFileWithProgressReporting(io.BufferedReader): # {{{
|
||||
return data
|
||||
|
||||
def report_progress(self, size):
|
||||
sys.stdout.buffer.write(b'\x1b[s')
|
||||
sys.stdout.buffer.write(b'\x1b[K')
|
||||
def write(*args):
|
||||
print(*args, end='')
|
||||
|
||||
write('\x1b[s\x1b[K')
|
||||
frac = float(self.tell()) / self._total
|
||||
mb_pos = self.tell() / float(1024**2)
|
||||
mb_tot = self._total / float(1024**2)
|
||||
@@ -75,14 +77,13 @@ class ReadFileWithProgressReporting(io.BufferedReader): # {{{
|
||||
bit_rate = kb_rate * 1024
|
||||
eta = int((self._total - self.tell()) / bit_rate) + 1
|
||||
eta_m, eta_s = eta / 60, eta % 60
|
||||
sys.stdout.write(
|
||||
write(
|
||||
' %.1f%% %.1f/%.1fMB %.1f KB/sec %d minutes, %d seconds left'
|
||||
% (frac * 100, mb_pos, mb_tot, kb_rate, eta_m, eta_s))
|
||||
sys.stdout.buffer.write(b'\x1b[u')
|
||||
write('\x1b[u')
|
||||
if self.tell() >= self._total:
|
||||
sys.stdout.write('\n')
|
||||
t = int(time.time() - self.start_time) + 1
|
||||
print('Upload took %d minutes and %d seconds at %.1f KB/sec' %
|
||||
print('\nUpload took %d minutes and %d seconds at %.1f KB/sec' %
|
||||
(t / 60, t % 60, kb_rate))
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -202,7 +203,7 @@ class GitHub(Base): # {{{
|
||||
def fail(self, r, msg):
|
||||
print(msg, ' Status Code: %s' % r.status_code, file=sys.stderr)
|
||||
print("JSON from response:", file=sys.stderr)
|
||||
pprint(dict(r.json()), stream=sys.stderr)
|
||||
pprint.pprint(dict(r.json()), stream=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
def already_exists(self, r):
|
||||
@@ -253,7 +254,7 @@ class GitHub(Base): # {{{
|
||||
|
||||
|
||||
def get_github_data():
|
||||
with open(os.path.expanduser('~/work/env/private/github')) as f:
|
||||
with open(os.environ['PENV'] + '/github') as f:
|
||||
un, pw = f.read().strip().split(':')
|
||||
return {'username': un, 'password': pw}
|
||||
|
||||
@@ -263,14 +264,13 @@ def run_upload(args):
|
||||
os.path.join(build_path, 'build', f.format(version)): desc
|
||||
for f, desc in {
|
||||
'osx/dist/kitty-{}.dmg': 'macOS dmg',
|
||||
}
|
||||
}.items()
|
||||
}
|
||||
for f in files:
|
||||
if not os.path.exists(f):
|
||||
raise SystemExit('The installer {} does not exist'.format(f))
|
||||
gd = get_github_data()
|
||||
gh = GitHub(files, appname, version, 'kovidgoyal', gd['username'],
|
||||
gd['password'])
|
||||
gh = GitHub(files, appname, version, gd['username'], gd['password'])
|
||||
gh()
|
||||
|
||||
|
||||
|
||||
11
setup.cfg
11
setup.cfg
@@ -1,3 +1,14 @@
|
||||
[flake8]
|
||||
max-line-length = 160
|
||||
exclude==template.py,linux-package
|
||||
|
||||
[yapf]
|
||||
based_on_style = pep8
|
||||
split_penalty_import_names = 100
|
||||
dedent_closing_brackets = True
|
||||
coalesce_brackets = True
|
||||
blank_line_before_nested_class_or_def = True
|
||||
|
||||
[isort]
|
||||
combine_as_imports = True
|
||||
multi_line_output = 5
|
||||
|
||||
217
setup.py
217
setup.py
@@ -17,18 +17,32 @@ constants = os.path.join(base, 'kitty', 'constants.py')
|
||||
with open(constants, 'rb') as f:
|
||||
constants = f.read().decode('utf-8')
|
||||
appname = re.search(r"^appname = '([^']+)'", constants, re.MULTILINE).group(1)
|
||||
version = tuple(map(int, re.search(r"^version = \((\d+), (\d+), (\d+)\)", constants, re.MULTILINE).group(1, 2, 3)))
|
||||
version = tuple(
|
||||
map(
|
||||
int,
|
||||
re.search(
|
||||
r"^version = \((\d+), (\d+), (\d+)\)", constants, re.MULTILINE
|
||||
).group(1, 2, 3)
|
||||
)
|
||||
)
|
||||
_plat = sys.platform.lower()
|
||||
isosx = 'darwin' in _plat
|
||||
is_travis = os.environ.get('TRAVIS') == 'true'
|
||||
|
||||
|
||||
cflags = ldflags = cc = ldpaths = None
|
||||
PKGCONFIG = os.environ.get('PKGCONFIG_EXE', 'pkg-config')
|
||||
|
||||
|
||||
def pkg_config(pkg, *args):
|
||||
return list(filter(None, shlex.split(subprocess.check_output([PKGCONFIG, pkg] + list(args)).decode('utf-8'))))
|
||||
return list(
|
||||
filter(
|
||||
None,
|
||||
shlex.split(
|
||||
subprocess.check_output([PKGCONFIG, pkg] + list(args))
|
||||
.decode('utf-8')
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def cc_version():
|
||||
@@ -39,11 +53,13 @@ def cc_version():
|
||||
ver = tuple(map(int, ver))
|
||||
except Exception:
|
||||
ver = (0, 0)
|
||||
return ver
|
||||
return cc, ver
|
||||
|
||||
|
||||
def get_python_flags(cflags):
|
||||
cflags.extend('-I' + sysconfig.get_path(x) for x in 'include platinclude'.split())
|
||||
cflags.extend(
|
||||
'-I' + sysconfig.get_path(x) for x in 'include platinclude'.split()
|
||||
)
|
||||
libs = []
|
||||
libs += sysconfig.get_config_var('LIBS').split()
|
||||
libs += sysconfig.get_config_var('SYSLIBS').split()
|
||||
@@ -53,72 +69,104 @@ def get_python_flags(cflags):
|
||||
val = sysconfig.get_path(var)
|
||||
if val and '/{}.framework'.format(fw) in val:
|
||||
fdir = val[:val.index('/{}.framework'.format(fw))]
|
||||
if os.path.isdir(os.path.join(fdir, '{}.framework'.format(fw))):
|
||||
if os.path.isdir(
|
||||
os.path.join(fdir, '{}.framework'.format(fw))
|
||||
):
|
||||
framework_dir = fdir
|
||||
break
|
||||
else:
|
||||
raise SystemExit('Failed to find Python framework')
|
||||
libs.append(os.path.join(framework_dir, sysconfig.get_config_var('LDLIBRARY')))
|
||||
libs.append(
|
||||
os.path.join(framework_dir, sysconfig.get_config_var('LDLIBRARY'))
|
||||
)
|
||||
else:
|
||||
libs += ['-L' + sysconfig.get_config_var('LIBDIR')]
|
||||
libs += ['-lpython' + sysconfig.get_config_var('VERSION') + sys.abiflags]
|
||||
libs += [
|
||||
'-lpython' + sysconfig.get_config_var('VERSION') + sys.abiflags
|
||||
]
|
||||
libs += sysconfig.get_config_var('LINKFORSHARED').split()
|
||||
return libs
|
||||
|
||||
|
||||
def init_env(debug=False, asan=False):
|
||||
def get_sanitize_args(cc, ccver):
|
||||
sanitize_args = set()
|
||||
sanitize_args.add('-fno-omit-frame-pointer')
|
||||
sanitize_args.add('-fsanitize=address')
|
||||
if (cc == 'gcc' and ccver >= (5, 0)) or cc == 'clang':
|
||||
sanitize_args.add('-fsanitize=undefined')
|
||||
# if cc == 'gcc' or (cc == 'clang' and ccver >= (4, 2)):
|
||||
# sanitize_args.add('-fno-sanitize-recover=all')
|
||||
return sanitize_args
|
||||
|
||||
|
||||
def init_env(debug=False, sanitize=False, native_optimizations=True):
|
||||
global cflags, ldflags, cc, ldpaths
|
||||
ccver = cc_version()
|
||||
native_optimizations = native_optimizations and not sanitize and not debug
|
||||
cc, ccver = cc_version()
|
||||
print('CC:', cc, ccver)
|
||||
stack_protector = '-fstack-protector'
|
||||
if ccver >= (4, 9):
|
||||
if ccver >= (4, 9) and cc == 'gcc':
|
||||
stack_protector += '-strong'
|
||||
missing_braces = ''
|
||||
if ccver < (5, 2):
|
||||
if ccver < (5, 2) and cc == 'gcc':
|
||||
missing_braces = '-Wno-missing-braces'
|
||||
cc = os.environ.get('CC', 'gcc')
|
||||
optimize = '-O3'
|
||||
if debug or asan:
|
||||
optimize = '-ggdb'
|
||||
if asan:
|
||||
optimize += ' -fsanitize=address -fno-omit-frame-pointer'
|
||||
cflags = os.environ.get('OVERRIDE_CFLAGS', (
|
||||
'-Wextra -Wno-missing-field-initializers -Wall -std=c99 -D_XOPEN_SOURCE=700'
|
||||
' -pedantic-errors -Werror {} -DNDEBUG -fwrapv {} {} -pipe').format(optimize, stack_protector, missing_braces))
|
||||
cflags = shlex.split(cflags) + shlex.split(sysconfig.get_config_var('CCSHARED'))
|
||||
ldflags = os.environ.get('OVERRIDE_LDFLAGS', '-Wall ' + (
|
||||
'-fsanitize=address' if asan else ('' if debug else '-O3')))
|
||||
optimize = '-ggdb' if debug or sanitize else '-O3'
|
||||
sanitize_args = get_sanitize_args(cc, ccver) if sanitize else set()
|
||||
cflags = os.environ.get(
|
||||
'OVERRIDE_CFLAGS', (
|
||||
'-Wextra -Wno-missing-field-initializers -Wall -std=c99 -D_XOPEN_SOURCE=700'
|
||||
' -pedantic-errors -Werror {} {} -D{}DEBUG -fwrapv {} {} -pipe {} -fvisibility=hidden'
|
||||
).format(
|
||||
optimize, ' '.join(sanitize_args), ('' if debug else 'N'), stack_protector, missing_braces, '-march=native'
|
||||
if native_optimizations else ''
|
||||
)
|
||||
)
|
||||
cflags = shlex.split(cflags
|
||||
) + shlex.split(sysconfig.get_config_var('CCSHARED'))
|
||||
ldflags = os.environ.get(
|
||||
'OVERRIDE_LDFLAGS', '-Wall ' + ' '.join(sanitize_args) + ('' if debug else ' -O3')
|
||||
)
|
||||
ldflags = shlex.split(ldflags)
|
||||
cflags += shlex.split(os.environ.get('CFLAGS', ''))
|
||||
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
|
||||
|
||||
cflags.append('-pthread')
|
||||
if not is_travis and not isosx and subprocess.Popen([PKGCONFIG, 'glew', '--atleast-version=2']).wait() != 0:
|
||||
# We add 4000 to the primary version because vim turns on SGR mouse mode
|
||||
# automatically if this version is high enough
|
||||
cflags.append('-DPRIMARY_VERSION={}'.format(version[0] + 4000))
|
||||
cflags.append('-DSECONDARY_VERSION={}'.format(version[1]))
|
||||
if not is_travis and not isosx and subprocess.Popen(
|
||||
[PKGCONFIG, 'glew', '--atleast-version=2']
|
||||
).wait() != 0:
|
||||
try:
|
||||
ver = subprocess.check_output([PKGCONFIG, 'glew', '--modversion']).decode('utf-8').strip()
|
||||
ver = subprocess.check_output([PKGCONFIG, 'glew', '--modversion']
|
||||
).decode('utf-8').strip()
|
||||
major = int(re.match(r'\d+', ver).group())
|
||||
except Exception:
|
||||
ver = 'not found'
|
||||
major = 0
|
||||
if major < 2:
|
||||
raise SystemExit('glew >= 2.0.0 is required, found version: ' + ver)
|
||||
raise SystemExit(
|
||||
'glew >= 2.0.0 is required, found version: ' + ver
|
||||
)
|
||||
if not isosx:
|
||||
cflags.extend(pkg_config('glew', '--cflags-only-I'))
|
||||
if isosx:
|
||||
font_libs = ['-framework', 'CoreText', '-framework', 'CoreGraphics']
|
||||
else:
|
||||
cflags.extend(pkg_config('freetype2', '--cflags-only-I'))
|
||||
font_libs = pkg_config('freetype2', '--libs')
|
||||
cflags.extend(pkg_config('fontconfig', '--cflags-only-I'))
|
||||
font_libs = pkg_config('fontconfig', '--libs')
|
||||
cflags.extend(pkg_config('glfw3', '--cflags-only-I'))
|
||||
ldflags.append('-shared')
|
||||
pylib = get_python_flags(cflags)
|
||||
if isosx:
|
||||
glfw_ldflags = pkg_config('--libs', '--static', 'glfw3') + ['-framework', 'OpenGL']
|
||||
glfw_ldflags = pkg_config('--libs', '--static', 'glfw3'
|
||||
) + ['-framework', 'OpenGL']
|
||||
glew_libs = []
|
||||
else:
|
||||
glfw_ldflags = pkg_config('glfw3', '--libs')
|
||||
glew_libs = pkg_config('glew', '--libs')
|
||||
ldpaths = pylib + \
|
||||
glew_libs + font_libs + glfw_ldflags
|
||||
ldpaths = pylib + glew_libs + font_libs + glfw_ldflags
|
||||
|
||||
try:
|
||||
os.mkdir(build_dir)
|
||||
@@ -158,7 +206,10 @@ def newer(dest, *sources):
|
||||
|
||||
def compile_c_extension(module, incremental, sources, headers):
|
||||
prefix = os.path.basename(module)
|
||||
objects = [os.path.join(build_dir, prefix + '-' + os.path.basename(src) + '.o') for src in sources]
|
||||
objects = [
|
||||
os.path.join(build_dir, prefix + '-' + os.path.basename(src) + '.o')
|
||||
for src in sources
|
||||
]
|
||||
|
||||
for src, dest in zip(sources, objects):
|
||||
cflgs = cflags[:]
|
||||
@@ -176,42 +227,80 @@ def compile_c_extension(module, incremental, sources, headers):
|
||||
|
||||
def option_parser():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('action', nargs='?', default='build', choices='build test linux-package osx-bundle'.split(), help='Action to perform (default is build)')
|
||||
p.add_argument('--debug', default=False, action='store_true',
|
||||
help='Build extension modules with debugging symbols')
|
||||
p.add_argument('--asan', default=False, action='store_true',
|
||||
help='Turn on address sanitization to detect memory access errors. Note that if you do turn it on,'
|
||||
' you have to run kitty with the environment variable LD_PRELOAD=/usr/lib/libasan.so')
|
||||
p.add_argument('--prefix', default='./linux-package', help='Where to create the linux package')
|
||||
p.add_argument('--incremental', default=False, action='store_true', help='Only build changed files')
|
||||
p.add_argument(
|
||||
'action',
|
||||
nargs='?',
|
||||
default='build',
|
||||
choices='build test linux-package osx-bundle'.split(),
|
||||
help='Action to perform (default is build)'
|
||||
)
|
||||
p.add_argument(
|
||||
'--debug',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Build extension modules with debugging symbols'
|
||||
)
|
||||
p.add_argument(
|
||||
'--sanitize',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Turn on sanitization to detect memory access errors and undefined behavior. Note that if you do turn it on,'
|
||||
' a special executable will be built for running the test suite. If you want to run normal kitty'
|
||||
' with sanitization, use LD_PRELOAD=libasan.so (for gcc) and'
|
||||
' LD_PRELOAD=/usr/lib/clang/4.0.0/lib/linux/libclang_rt.asan-x86_64.so (for clang, changing path as appropriate).'
|
||||
)
|
||||
p.add_argument(
|
||||
'--prefix',
|
||||
default='./linux-package',
|
||||
help='Where to create the linux package'
|
||||
)
|
||||
p.add_argument(
|
||||
'--incremental',
|
||||
default=False,
|
||||
action='store_true',
|
||||
help='Only build changed files'
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def find_c_files():
|
||||
ans, headers = [], []
|
||||
d = os.path.join(base, 'kitty')
|
||||
exclude = {'freetype.c'} if isosx else {'core_text.m'}
|
||||
exclude = {'freetype.c', 'fontconfig.c'} if isosx else {'core_text.m', 'cocoa_window.m'}
|
||||
for x in os.listdir(d):
|
||||
ext = os.path.splitext(x)[1]
|
||||
if ext in ('.c', '.m') and os.path.basename(x) not in exclude:
|
||||
ans.append(os.path.join('kitty', x))
|
||||
elif ext == '.h':
|
||||
headers.append(os.path.join('kitty', x))
|
||||
ans.sort(key=lambda x: os.path.getmtime(os.path.join(base, x)), reverse=True)
|
||||
ans.sort(
|
||||
key=lambda x: os.path.getmtime(os.path.join(base, x)), reverse=True
|
||||
)
|
||||
ans.append('kitty/parser_dump.c')
|
||||
return tuple(ans), tuple(headers)
|
||||
|
||||
|
||||
def build(args):
|
||||
init_env(args.debug, args.asan)
|
||||
compile_c_extension('kitty/fast_data_types', args.incremental, *find_c_files())
|
||||
def build(args, native_optimizations=True):
|
||||
init_env(args.debug, args.sanitize, native_optimizations)
|
||||
compile_c_extension(
|
||||
'kitty/fast_data_types', args.incremental, *find_c_files()
|
||||
)
|
||||
|
||||
|
||||
def safe_makedirs(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except FileExistsError:
|
||||
pass
|
||||
os.makedirs(path, exist_ok=True)
|
||||
|
||||
|
||||
def build_test_launcher(args):
|
||||
cc, ccver = cc_version()
|
||||
cflags = '-g -Wall -Werror -fpie'.split()
|
||||
pylib = get_python_flags(cflags)
|
||||
sanitize_lib = (['-lasan'] if cc == 'gcc' else []) if args.sanitize else []
|
||||
cflags.extend(get_sanitize_args(cc, ccver) if args.sanitize else [])
|
||||
cmd = [cc] + cflags + [
|
||||
'test-launcher.c', '-o', 'test-launcher',
|
||||
] + sanitize_lib + pylib
|
||||
run_tool(cmd)
|
||||
|
||||
|
||||
def package(args, for_bundle=False): # {{{
|
||||
@@ -228,7 +317,10 @@ def package(args, for_bundle=False): # {{{
|
||||
shutil.copy2('logo/kitty.rgba', os.path.join(libdir, 'logo'))
|
||||
|
||||
def src_ignore(parent, entries):
|
||||
return [x for x in entries if '.' in x and x.rpartition('.')[2] not in ('py', 'so', 'conf')]
|
||||
return [
|
||||
x for x in entries
|
||||
if '.' in x and x.rpartition('.')[2] not in ('py', 'so', 'conf', 'glsl')
|
||||
]
|
||||
|
||||
shutil.copytree('kitty', os.path.join(libdir, 'kitty'), ignore=src_ignore)
|
||||
import compileall
|
||||
@@ -239,12 +331,14 @@ def package(args, for_bundle=False): # {{{
|
||||
os.chmod(path, 0o755 if f.endswith('.so') else 0o644)
|
||||
launcher_dir = os.path.join(ddir, 'bin')
|
||||
safe_makedirs(launcher_dir)
|
||||
cflags = '-O3 -Wall -Werror'.split()
|
||||
cflags = '-O3 -Wall -Werror -fpie'.split()
|
||||
if for_bundle:
|
||||
cflags.append('-DFOR_BUNDLE')
|
||||
cflags.append('-DPYVER="{}"'.format(sysconfig.get_python_version()))
|
||||
pylib = get_python_flags(cflags)
|
||||
cmd = [cc] + cflags + ['linux-launcher.c', '-o', os.path.join(launcher_dir, 'kitty')] + pylib
|
||||
cmd = [cc] + cflags + [
|
||||
'linux-launcher.c', '-o', os.path.join(launcher_dir, 'kitty')
|
||||
] + pylib
|
||||
run_tool(cmd)
|
||||
if not isosx: # {{{ linux desktop gunk
|
||||
icdir = os.path.join(ddir, 'share', 'icons', 'hicolor', '256x256')
|
||||
@@ -253,7 +347,8 @@ def package(args, for_bundle=False): # {{{
|
||||
deskdir = os.path.join(ddir, 'share', 'applications')
|
||||
safe_makedirs(deskdir)
|
||||
with open(os.path.join(deskdir, 'kitty.desktop'), 'w') as f:
|
||||
f.write('''\
|
||||
f.write(
|
||||
'''\
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Type=Application
|
||||
@@ -264,7 +359,8 @@ TryExec=kitty
|
||||
Exec=kitty
|
||||
Icon=kitty
|
||||
Categories=System;
|
||||
''')
|
||||
'''
|
||||
)
|
||||
# }}}
|
||||
|
||||
if for_bundle: # OS X bundle gunk {{{
|
||||
@@ -275,7 +371,7 @@ Categories=System;
|
||||
os.rename('../bin', 'MacOS')
|
||||
os.rename('../lib', 'Frameworks')
|
||||
# }}}
|
||||
# }}}
|
||||
# }}}
|
||||
|
||||
|
||||
def main():
|
||||
@@ -286,13 +382,16 @@ def main():
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
if args.action == 'build':
|
||||
build(args)
|
||||
build_test_launcher(args)
|
||||
elif args.action == 'test':
|
||||
os.execlp(sys.executable, sys.executable, os.path.join(base, 'test.py'))
|
||||
os.execlp(
|
||||
sys.executable, sys.executable, os.path.join(base, 'test.py')
|
||||
)
|
||||
elif args.action == 'linux-package':
|
||||
build(args)
|
||||
build(args, native_optimizations=False)
|
||||
package(args)
|
||||
elif args.action == 'osx-bundle':
|
||||
build(args)
|
||||
build(args, native_optimizations=False)
|
||||
package(args, for_bundle=True)
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
@@ -101,6 +101,7 @@ xterm-kitty|KovIdTTY,
|
||||
rmam=\E[?7l,
|
||||
rmcup=\E[?1049l,
|
||||
rmir=\E[4l,
|
||||
rmkx=\E[?1l,
|
||||
rmso=\E[27m,
|
||||
rmul=\E[24m,
|
||||
rs1=\Ec,
|
||||
@@ -114,6 +115,7 @@ xterm-kitty|KovIdTTY,
|
||||
smam=\E[?7h,
|
||||
smcup=\E[?1049h,
|
||||
smir=\E[4h,
|
||||
smkx=\E[?1h,
|
||||
smso=\E[7m,
|
||||
smul=\E[4m,
|
||||
tbc=\E[3g,
|
||||
|
||||
Binary file not shown.
13
test-launcher.c
Normal file
13
test-launcher.c
Normal file
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* linux-launcher.c
|
||||
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include <Python.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
wchar_t *wargv[2] = {L"kitty-test", L"test.py"};
|
||||
return Py_Main(2, wargv);
|
||||
}
|
||||
Reference in New Issue
Block a user