Compare commits

...

157 Commits

Author SHA1 Message Date
Kovid Goyal
5117a2c17a version 0.2.8 2017-08-05 08:21:20 +05:30
Kovid Goyal
ebce1d7c61 Fix incorrect handling of escape codes to change default foreground and background colors. Fixes #104 2017-08-04 21:57:22 +05:30
Kovid Goyal
e4668c1aff Replay set_dynamic_color and set_color_table_color 2017-08-04 20:47:59 +05:30
Kovid Goyal
2519c49c02 Fix incorrect escape codes generated when using the obsolete "normal" mouse protocol.
Fixes #105
2017-08-04 20:09:18 +05:30
Kovid Goyal
9e512ff58a Ensure queue_action is only called after the Tab object is fully initialized
Fix #103
2017-08-04 07:52:25 +05:30
Kovid Goyal
ed50595aca Add a --detach option
Allows kitty to detach itself from the controlling terminal. Useful
when launching kitty from a GUI environment with broken stdout/stderr or
when launching from a terminal that you want to close later without
affecting the launched kitty instance.
2017-08-03 22:35:47 +05:30
Kovid Goyal
9d62d087e0 Redirect xsel std streams to /dev/null
Prevents hanging if the streams are blocked. Fixes #102
2017-08-03 21:55:09 +05:30
Kovid Goyal
6b933e33f5 If the saved initial window size fails, retry creating the window with a standard size. Fixes #98 2017-07-30 08:32:51 +05:30
Kovid Goyal
a86931f401 Use the new glfw API to set WM_CLASS if available 2017-07-27 18:11:23 +05:30
Kovid Goyal
fc4e1595e8 Print a warning about unknown config keys 2017-07-23 22:59:30 +05:30
Kovid Goyal
b36c3f3425 Clean up config merging 2017-07-23 22:45:57 +05:30
Kovid Goyal
e427fd1233 Fix #95 2017-07-23 21:36:43 +05:30
Kovid Goyal
dd3af45043 Implement a send_text action to allow using keyboard shortcuts to send arbitrary text
Fixes #94
2017-07-23 14:37:15 +05:30
Kovid Goyal
304d42d4c2 Fix #93
Ignore invalid language names returned by the coca framework on macOS
2017-07-16 22:49:05 +05:30
Kovid Goyal
abca9280e7 Fix #92 2017-07-15 08:22:40 +05:30
Kovid Goyal
9a103f3979 version 0.2.7 2017-07-14 10:46:35 +05:30
Kovid Goyal
270dde7020 Use erase in line instead as it is more efficient 2017-07-13 19:32:18 +05:30
Kovid Goyal
11de18e737 Fix #91 2017-07-13 19:24:26 +05:30
Kovid Goyal
62db44c71e macOS: Ensure the LANG environment variable is set
Fixes #90
2017-06-25 20:44:16 +05:30
Kovid Goyal
3d5c65eaea Fix #89 2017-06-21 10:19:45 +05:30
Kovid Goyal
ea298f95f2 Another place we can use exist_ok 2017-06-09 00:00:02 +05:30
Kovid Goyal
2f21e0e341 Merge branch 'patch-2' of https://github.com/mimi1vx/kitty 2017-06-08 23:59:14 +05:30
Ondřej Súkup
ae62d36a4a Remove unneeded try except block
os.makedirs has from python-3.2 option `exist_ok`
2017-06-08 20:25:34 +02:00
Kovid Goyal
76e3101d9b Hide all symbols 2017-06-07 11:19:53 +05:30
Kovid Goyal
c3442545a8 Add a note about changing mouse wheel scroll direction 2017-06-06 00:06:43 +05:30
Kovid Goyal
3a9b0faa06 Be more positive ;) 2017-06-06 00:03:43 +05:30
Kovid Goyal
4989b1f8bb ... 2017-06-05 23:58:36 +05:30
Kovid Goyal
348fe4ada4 Option for window padding
Fixes #85
2017-06-05 23:57:17 +05:30
Kovid Goyal
bbc6b2d86a Option to size window margin (blank area outside window borders)
Defaults to zero
2017-06-05 22:27:47 +05:30
Kovid Goyal
b4d4ed718f version 0.2.6 2017-06-03 09:58:38 +05:30
Kovid Goyal
836724709e Add an option to hide the window title bar on macOS
Fixes #84
2017-06-03 09:57:29 +05:30
Kovid Goyal
96d2567815 Fix compilation with gcc >= 7
Requires explicit fallthrough comments in switch statements
2017-06-03 08:59:09 +05:30
Kovid Goyal
419f43ceed Fix deprecation warning on macOS 10.12 2017-06-03 08:58:11 +05:30
Kovid Goyal
47851ebb1b Fix conversion of type 2 terminal colors not working because of missing break 2017-06-03 08:51:00 +05:30
Kovid Goyal
24a4fbd987 Add a function to hide the title bar on OS X 2017-06-03 08:45:27 +05:30
Kovid Goyal
8047743882 0.2.5 2017-05-26 14:02:14 +05:30
Kovid Goyal
e9b5963610 Fix incorrect replay of screen cursor down 2017-05-26 13:21:16 +05:30
Kovid Goyal
a1d4630a25 Fix incorrect implementation of the Vertical Position Adjust (VPA) CSI code
Fixes #80
2017-05-26 13:11:27 +05:30
Kovid Goyal
fafd710ce3 Clean exit for client on EOF/interrupt 2017-05-26 12:36:36 +05:30
Kovid Goyal
a79bb3add2 Correct the DEBUG define 2017-05-24 07:47:11 +05:30
Kovid Goyal
b3a718b1e4 ... 2017-05-23 20:28:50 +05:30
Kovid Goyal
952aa7ad4a A tribute to Thomas E. Dickey 2017-05-23 20:27:51 +05:30
Kovid Goyal
149d606154 Add note about impossibility of using multiple modifier keys 2017-05-23 08:25:41 +05:30
Kovid Goyal
ad21c7ed0f Mimic behavior of xterm when pressing Ctrl+<key>
Where <key> is one of ,.;'-=

These dont map to control codes, xterm and libvte just ignore the Ctrl
and echo the key as if control was not pressed. Mimic that behavior
2017-05-22 21:09:20 +05:30
Kovid Goyal
38d2839206 Fix Ctrl+6 and Ctrl+/ not working
Fixes #79
2017-05-22 21:01:14 +05:30
Kovid Goyal
24d0bb8bd5 Allow IME to generate unicode characters using Alt+key which is used on OSX, I think 2017-05-20 12:01:50 +05:30
Kovid Goyal
08f336769f Add tests for key mapping
Also fix Alt+Special keys no generating correct codes
2017-05-20 11:41:21 +05:30
Kovid Goyal
5525d4db49 Fix alt+key resulting in the upper case version of key even when shift is not pressed
Fixes #78
2017-05-20 08:47:02 +05:30
Kovid Goyal
448ba26257 DRYer 2017-05-20 00:58:40 +05:30
Kovid Goyal
1d1138ca31 Be a little more stringent with Xft parsing 2017-05-20 00:26:26 +05:30
Kovid Goyal
357a415386 ... 2017-05-20 00:23:43 +05:30
Kovid Goyal
a65856ec98 Use Xlib to directly query the xrm system.Only if it fails do we try to shell out to xrd 2017-05-20 00:21:09 +05:30
Kovid Goyal
83855e16ce On linux query xrdb for Xft.dpi and use that, if set as the logical DPI.
Fall back to the actual physical dpi as returned by GLFW if that fails.
2017-05-19 23:26:09 +05:30
Kovid Goyal
ccf66fc621 A method on the window object to get the current monitor for that window 2017-05-19 20:37:54 +05:30
Kovid Goyal
c27b597951 Fix incorrect implementation of the CSI scroll commands
I was lazy and just assumed they were n indexes, but they actually
scroll the screen without moving the cursor. Fixes #76
2017-05-19 19:25:41 +05:30
Kovid Goyal
85dbae1de4 Ensure that dump_bytes truncates the file it is dumping to 2017-05-19 18:09:37 +05:30
Kovid Goyal
cd1ba334c1 Forgot to change test 2017-05-19 15:54:34 +05:30
Kovid Goyal
1cff4f9d29 ... 2017-05-19 15:45:07 +05:30
Kovid Goyal
d180601711 More command replaying 2017-05-19 15:43:15 +05:30
Kovid Goyal
01d0e7474f Change reported version to >= 4000 so that vim autodetects SGR mouse support correctly 2017-05-19 14:23:11 +05:30
Kovid Goyal
bd75fa6ed3 version 0.2.4 2017-05-17 10:38:30 +05:30
Kovid Goyal
14a66762a6 Fix incorrect response to request for device attributes 2017-05-17 10:33:33 +05:30
Kovid Goyal
507edc95a8 Exclude rgb.py for LOC count 2017-05-17 09:35:05 +05:30
Kovid Goyal
9fdadfabcc DRYer 2017-05-17 09:29:57 +05:30
Kovid Goyal
b58ecde740 Track cursor color as a property of Screen not Cursor
This means that the savepoint functionality will not restore
cursor color
2017-05-17 09:27:38 +05:30
Kovid Goyal
3b71a016a6 Fix reset not resetting default foreground and background colors 2017-05-17 09:22:06 +05:30
Kovid Goyal
f0c546208d Implement OSC codes for changing selection colors 2017-05-17 08:55:57 +05:30
Kovid Goyal
e5799321f8 Simplify cursor color management 2017-05-17 08:10:39 +05:30
Kovid Goyal
6133fd581a Generate X11 RGB color name table from rgb.txt 2017-05-17 07:38:56 +05:30
Kovid Goyal
0c8231d356 Implement OSC codes for changing cursor colors 2017-05-17 07:10:23 +05:30
Kovid Goyal
788f09e855 Use a special test launcher that links the sanitize runtime library directly 2017-05-15 12:07:36 +05:30
Kovid Goyal
ba7a6e8106 Turn on the undefined behavior sanitizer on Travis 2017-05-15 12:06:06 +05:30
Kovid Goyal
59ac12570c Fix unaligned read/writes in the History buffer 2017-05-15 12:06:06 +05:30
Kovid Goyal
69b187a743 Split up the color field into fg/bg colors
Avoid a lot of unnecessary masking as well as unaligned read/writes
2017-05-15 12:06:06 +05:30
Kovid Goyal
800291c147 Call set_locale() at startup 2017-05-15 12:06:06 +05:30
Kovid Goyal
1b336bf864 Ask the window manager to mark the window as requiring attention when a bell occurs
Requires glfw 3.3. Fixes #51
2017-05-15 12:06:06 +05:30
Jakob Schramm
2630668fa5 Fix possessive "its"
Its, when used possessively, does not use an apostrophe.
2017-04-29 15:55:57 +02:00
Kovid Goyal
339dd9b412 version 0.2.3 2017-04-28 09:48:27 +05:30
Kovid Goyal
3f272d102b Test for toggling IUTF8 2017-04-28 09:41:47 +05:30
Kovid Goyal
29988c94fa Fix #70 2017-04-28 09:21:40 +05:30
Kovid Goyal
35f2b3254a A spot of refactoring 2017-04-28 08:54:06 +05:30
Kovid Goyal
898a8075be Fix #69 2017-04-28 08:31:07 +05:30
Kovid Goyal
f5d957e8ff Merge branch 'binor-patch-1' of https://github.com/binor/kitty 2017-04-07 08:24:33 +05:30
binor
2cc3cabd3f Fix mouse coordinates off on macOS Retina display 2017-04-06 19:53:14 +02:00
Kovid Goyal
be5185ab1a version 0.2.2 2017-04-05 09:12:10 +05:30
Kovid Goyal
01c289e440 Add ST test to OTH tests as well 2017-04-05 09:09:41 +05:30
Kovid Goyal
db2d14d9ed Fix backslashes in OSC codes not being parsed correctly
Fixes #61
2017-04-05 09:07:55 +05:30
Kovid Goyal
c504b96b60 Add a --dump-bytes option, useful for reproducing problems from users computers 2017-04-05 00:13:31 +05:30
Kovid Goyal
15c4b1961e Add a few missing commands to replay 2017-04-04 23:51:20 +05:30
Kovid Goyal
2cc20e4b27 Allow changing font size in a running terminal using keyboard shortcuts.
Fixes #57
2017-03-31 10:00:56 +05:30
Kovid Goyal
0923d6ecee version 0.2.1 2017-03-15 16:31:28 +05:30
Kovid Goyal
defda41861 Use rmkx codes as basis for control+special key codes 2017-03-15 16:27:17 +05:30
Kovid Goyal
66c51798b8 Fix a regression that broke Ctrl+Left and Ctrl+Down
Fixes #56
2017-03-15 16:16:27 +05:30
Kovid Goyal
ff966f667c Fix newlines being inserted at wrapping boundaries when copying. Fixes #55 2017-03-10 10:02:22 +05:30
Kovid Goyal
96be8dcb2c Remove spurious sentence 2017-03-09 09:14:17 +05:30
Kovid Goyal
892d3df6eb DRYer 2017-02-24 15:56:04 +05:30
Kovid Goyal
54e79a6901 Add a test for ARB_texture_buffer_object_rgb32
There are apparently some drivers on linux that are missing it,
see #54
2017-02-24 15:27:41 +05:30
Kovid Goyal
7d0c05e20d Add a command line option to debug OpenGL calls 2017-02-24 14:38:30 +05:30
Kovid Goyal
421ae6d289 Ensure texture buffer is bound before uploading to it 2017-02-24 14:35:02 +05:30
Kovid Goyal
3af501b715 Build with -march=native by default 2017-02-21 16:35:25 +05:30
Kovid Goyal
c73d6913da Some meta goals for the protocol extensions 2017-02-17 22:06:11 +05:30
Kovid Goyal
f7cb3e3f9e Option to disable audio bell 2017-02-14 08:10:00 +05:30
Kovid Goyal
d96c7d71a7 version 0.2.0 2017-02-13 17:31:17 +05:30
Kovid Goyal
7d8e172ce2 Keyboard maps should be respected in extended keyboard mode as well 2017-02-12 14:27:03 +05:30
Kovid Goyal
a671c7a184 When detecting URLs remove trailing "] and similar 2017-02-11 12:12:02 +05:30
Kovid Goyal
72125701f5 No need ot build the key map on every startup 2017-02-11 10:48:36 +05:30
Kovid Goyal
a66d2b0890 Ensure the extended keyboard protocol key encoding is stable
Also use base85 instead of base64 for keyname encoding to reduce average
length
2017-02-11 10:41:04 +05:30
Kovid Goyal
211b771316 pep8 2017-02-11 10:02:48 +05:30
Kovid Goyal
41ffad8e5c Ignore failures to beep 2017-02-11 09:50:19 +05:30
Kovid Goyal
91144a8b63 typo in comment 2017-02-11 08:58:51 +05:30
Kovid Goyal
0c408fa4af Add an optional "visual" bell
Fix #51
2017-02-11 08:56:40 +05:30
Kovid Goyal
8b8186660b Move bell handling into python 2017-02-11 08:47:42 +05:30
Kovid Goyal
7d2332da21 API to easily invert colors in a window 2017-02-11 08:28:10 +05:30
Kovid Goyal
2b4d0a4ac9 Fix regression that caused scrolling to return to origin when releasing keys 2017-02-10 16:03:20 +05:30
Kovid Goyal
edab1aebaa Allow clients to query if the terminal emulator support styled underlines 2017-02-10 16:00:51 +05:30
Kovid Goyal
924172f1ac Note on representation 2017-02-10 15:37:39 +05:30
Kovid Goyal
d9563e52c8 Reduce size of encoded key event by using base64 encoding 2017-02-10 15:34:06 +05:30
Kovid Goyal
ded9cf227a ... 2017-02-10 15:14:48 +05:30
Kovid Goyal
063d6652e0 ... 2017-02-10 15:13:22 +05:30
Kovid Goyal
0f8b83755a Implement the extended keyboard protocol 2017-02-10 15:11:07 +05:30
Kovid Goyal
45334d6b35 Recognize extended keyboard mode 2017-02-10 12:13:31 +05:30
Kovid Goyal
96921e3a39 Forgot one limitation 2017-02-10 11:48:39 +05:30
Kovid Goyal
7080168bdd Improved formatting 2017-02-10 11:42:20 +05:30
Kovid Goyal
48a2a395c4 Spec for extended keyboard protocol 2017-02-10 11:34:38 +05:30
Kovid Goyal
b6c0eb0909 ... 2017-02-10 09:47:46 +05:30
Kovid Goyal
b9a2524992 Add a link to the issue for graphics rendering 2017-02-10 09:45:41 +05:30
Kovid Goyal
28f0dc1e51 oops 2017-02-10 01:12:07 +05:30
Kovid Goyal
cbe599735c Allow overriding individual configuration options on the command line 2017-02-10 00:48:17 +05:30
Kovid Goyal
e012e9459d pep8 2017-02-10 00:34:53 +05:30
Kovid Goyal
eb71799b42 Allow specifying multiple config files
Can be used to create a "base" profile and then modify only a few
settings in different config files, that are merged when read.
2017-02-10 00:31:00 +05:30
Kovid Goyal
fe3f0932ea pep8 2017-02-10 00:23:35 +05:30
Kovid Goyal
783717b8a4 pep8 2017-02-10 00:04:05 +05:30
Kovid Goyal
bb37516d6f Add a note to the README about kitty's font control features 2017-02-09 21:38:05 +05:30
Kovid Goyal
6c6f000229 Implement symbol maps
A config option to use special fonts for specified unicode characters.
Useful for things like Powerline without needing patched fonts.
2017-02-09 21:15:53 +05:30
Kovid Goyal
a4715de5dc Fix tab bar borders not being blanked 2017-02-09 17:34:56 +05:30
Kovid Goyal
cbf0959fbf Fix tab bar not being rendered after first new tab is created 2017-02-09 17:02:02 +05:30
Kovid Goyal
49c81da763 Adjust rescale threshold 2017-02-09 13:38:19 +05:30
Kovid Goyal
7e34807859 Linux: use only advances for calculating cell sizes
There are apparently monospace fonts whose characters are not actually
monospaced when rendered, for example, Liberation Mono. It has
characters that when rendered result in bitmaps wider than the advance.

So we use only the advance as that is what most linux software seems to
do. Fixes #47
2017-02-09 13:36:44 +05:30
Kovid Goyal
e2ac9ec118 Fix building on OS X 2017-02-09 09:18:41 +05:30
Kovid Goyal
c58be6ddf5 A spot of refactoring 2017-02-08 22:24:58 +05:30
Kovid Goyal
b9b15d41f4 Explicitly depend only on fontconfig since it in turn depends on freetype 2017-02-08 21:59:05 +05:30
Kovid Goyal
dea60cdaf0 Forgot to also catch KeyError 2017-02-08 21:57:39 +05:30
Kovid Goyal
02ef3c6dc8 Linux: Use libfontconfig directly instead of calling fc-match
There are apparently some linux systems out there with versions of
fontconfig that do not understanf the :charset query when passed to
fc-match. See #46
2017-02-08 21:52:10 +05:30
Kovid Goyal
882a4f2ab3 Linux: Add support for .ttc files 2017-02-08 21:07:10 +05:30
Kovid Goyal
85e05a447d Linux: Fallback to using bitmapped fonts for characters that are not present in any scalable fonts on the system
Fixes #46
2017-02-08 11:29:24 +05:30
Kovid Goyal
1ff4e9703a Refactor the fontconfig API
Makes it more flexible and DRYer
2017-02-08 10:10:07 +05:30
Kovid Goyal
0b2af7c33a Configure yapf/isort 2017-02-08 09:59:36 +05:30
Kovid Goyal
68115b50a5 Simplification 2017-02-08 09:45:27 +05:30
Kovid Goyal
9aa1d74f83 Linux: Don't crash when fontconfig is unable to find a font for a character.
Instead render it as a missing glyph
2017-02-08 09:32:15 +05:30
Kovid Goyal
4532194b01 ... 2017-02-06 22:18:59 +05:30
Kovid Goyal
ebacb16f67 Compile the launcher with -fpie 2017-02-06 16:19:33 +05:30
Kovid Goyal
585a01fff6 Start out in normal key mode
Matches behavior of xterm
2017-02-05 16:39:40 +05:30
Kovid Goyal
bb7edb5f8f Implement DECRQM
Also add tests for DECRQM and DECCKM
2017-02-05 16:21:13 +05:30
Kovid Goyal
142c883b0c Implement DECCKM 2017-02-05 15:30:16 +05:30
Kovid Goyal
bf3f6f6014 Add a note about the availability of the dmg 2017-02-02 16:35:02 +05:30
Kovid Goyal
688d0f74c4 Use print() instead of stdout.buffer.write() 2017-02-02 16:30:53 +05:30
Kovid Goyal
71b4ea5a60 oops 2017-02-02 16:24:38 +05:30
58 changed files with 3764 additions and 824 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ build
README.html README.html
linux-package linux-package
logo/*.iconset logo/*.iconset
test-launcher

View File

@@ -6,13 +6,13 @@ matrix:
group: beta group: beta
sudo: false sudo: false
env: env:
- CC=gcc ASANLIB=libasan.so.0 ASAN_ARG=--asan - CC=gcc SANITIZE_ARG=--sanitize
language: python language: python
python: "3.5" python: "3.5"
addons: addons:
apt: apt:
packages: packages:
- libfreetype6-dev - libfontconfig1-dev
- libglew-dev - libglew-dev
- libxi-dev - libxi-dev
- libxrandr-dev - libxrandr-dev
@@ -24,13 +24,31 @@ matrix:
group: beta group: beta
sudo: false sudo: false
env: env:
- CC=clang RUN_FLAKE=1 BUILD_PKG=1 - CC=clang SANITIZE_ARG=--sanitize
language: python language: python
python: "3.5" python: "3.5"
addons: addons:
apt: apt:
packages: 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 - libglew-dev
- libxi-dev - libxi-dev
- libxrandr-dev - libxrandr-dev
@@ -45,6 +63,13 @@ matrix:
language: generic language: generic
env: USE_BREW=1 BUILD_PKG=1 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: | install: |
set -e set -e
if [[ "$RUN_FLAKE" == "1" ]]; then pip install flake8; fi if [[ "$RUN_FLAKE" == "1" ]]; then pip install flake8; fi
@@ -59,7 +84,7 @@ install: |
fi fi
else else
wget -O glfw-3.2.1.zip https://github.com/glfw/glfw/archive/3.2.1.zip 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 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 cmake -DBUILD_SHARED_LIBS=ON -DGLFW_BUILD_EXAMPLES=OFF -DGLFW_BUILD_TESTS=OFF -DGLFW_BUILD_DOCS=OFF -DCMAKE_INSTALL_PREFIX=$HOME/glfw
make make
@@ -67,17 +92,18 @@ install: |
cd .. cd ..
fi fi
pkg-config --cflags glfw3 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 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: 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: script:
- LD_PRELOAD=$ASANLIB $PYTHON setup.py test - ./test-launcher
- if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi - if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi
- if [[ "$BUILD_PKG" == "1" ]]; then $PYTHON setup.py linux-package; fi - if [[ "$BUILD_PKG" == "1" ]]; then $PYTHON setup.py linux-package; fi

View File

@@ -21,7 +21,7 @@
:sc_next_window: pass:quotes[`ctrl+shift+]`] :sc_next_window: pass:quotes[`ctrl+shift+]`]
:sc_ninth_window: pass:quotes[`ctrl+shift+9`] :sc_ninth_window: pass:quotes[`ctrl+shift+9`]
:sc_paste_from_clipboard: pass:quotes[`ctrl+shift+v`] :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_tab: pass:quotes[`ctrl+shift+left`]
:sc_previous_window: pass:quotes[`ctrl+shift+[`] :sc_previous_window: pass:quotes[`ctrl+shift+[`]
:sc_scroll_end: pass:quotes[`ctrl+shift+end`] :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 * 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 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] 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 * python >= 3.5
* glfw >= 3.2 * glfw >= 3.2
* glew >= 2.0 (not needed on macOS) * glew >= 2.0 (not needed on macOS)
* freetype, fontconfig (not needed on macOS) * fontconfig (not needed on macOS)
* xdpyinfo and xsel (only on X11 based systems) * xrdb and xsel (only on X11 based systems)
* gcc or clang (required only for building) * gcc or clang (required only for building)
* pkg-config (required only for building) * pkg-config (required only for building)
@@ -111,23 +111,24 @@ python3 /path/to/kitty/folder
=== macOS packages === macOS packages
Currently, no one has made macOS packages for kitty, but you can use the above kitty is available as a macOS `dmg` file for easy installation from the
install from source instructions to run kitty on macOS, after installing its link:../../releases[releases page]. You can also run kitty directly from
two dependencies (python >= 3.5 and glfw >= 3.2 using http://brew.sh/[brew] or source using the above install from source instructions, after installing its
a similar package manager) two dependencies (`python >= 3.5` and `glfw >= 3.2` using http://brew.sh/[brew]
or a similar package manager)
== Design philosophy == Design philosophy
kitty is designed for power keyboard users. To that end all its controls kitty is designed for power keyboard users. To that end all its controls
work with the keyboard (although it fully supports mouse interactions as 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). 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 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 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 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 Finally, kitty is designed from the ground up to support all modern
terminal features, such as unicode, true color, bold/italic fonts, text 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, kitty has a few extensions to the xterm protocol, to enable advanced features,
see link:protocol-extensions.asciidoc[Protocol Extensions]. 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 == 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. 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 This applies to creating packages for kitty for macOS package managers such as
brew or MacPorts as well. 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 == Resources on terminal behavior
http://invisible-island.net/xterm/ctlseqs/ctlseqs.html http://invisible-island.net/xterm/ctlseqs/ctlseqs.html

View File

@@ -1,2 +1,2 @@
#!/bin/bash #!/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
View 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`
|===

View File

@@ -3,12 +3,14 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from ctypes import addressof from ctypes import addressof
from itertools import chain
from threading import Lock from threading import Lock
from functools import partial
from .constants import viewport_size, GLfloat, GLint, GLuint from .constants import GLfloat, GLint, GLuint, viewport_size
from .fast_data_types import glUniform3fv, GL_TRIANGLE_FAN, glMultiDrawArrays from .fast_data_types import GL_TRIANGLE_FAN, glMultiDrawArrays, glUniform3fv
from .utils import get_dpi
from .shaders import ShaderProgram from .shaders import ShaderProgram
from .utils import pt_to_px
def as_color(c): def as_color(c):
@@ -30,7 +32,8 @@ def as_rect(left, top, right, bottom, color=0):
class BordersProgram(ShaderProgram): class BordersProgram(ShaderProgram):
def __init__(self): def __init__(self):
ShaderProgram.__init__(self, '''\ ShaderProgram.__init__(
self, '''\
uniform vec3 colors[3]; uniform vec3 colors[3];
in vec3 rect; in vec3 rect;
out vec3 color; out vec3 color;
@@ -56,34 +59,73 @@ void main() {
glUniform3fv(self.uniform_location('colors'), 3, addressof(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: class Borders:
def __init__(self, opts): def __init__(self, opts):
self.is_dirty = False self.is_dirty = False
self.lock = Lock() self.lock = Lock()
self.can_render = False self.can_render = False
dpix, dpiy = get_dpi()['logical'] self.border_width = pt_to_px(opts.window_border_width)
dpi = (dpix + dpiy) / 2 self.padding_width = pt_to_px(opts.window_padding_width)
self.border_width = round(opts.window_border_width * dpi / 72)
self.color_buf = (GLfloat * 9)( self.color_buf = (GLfloat * 9)(
*as_color(opts.background), *as_color(opts.background), *as_color(opts.active_border_color),
*as_color(opts.active_border_color), *as_color(opts.inactive_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 = [] rects = []
for br in current_layout.blank_rects: for br in chain(current_layout.blank_rects, extra_blank_rects):
rects.extend(as_rect(*br)) rects.extend(as_rect(*br))
if draw_window_borders and self.border_width > 0: bw, pw = self.border_width, self.padding_width
bw = self.border_width fw = bw + pw
border = border_maker(rects)
if fw > 0:
for w in windows: for w in windows:
g = w.geometry g = w.geometry
color = 1 if w is active_window else 2 if bw > 0 and draw_window_borders:
rects.extend(as_rect(g.left - bw, g.top - bw, g.left, g.bottom + bw, color)) # Draw the border rectangles
rects.extend(as_rect(g.left - bw, g.top - bw, g.right + bw, g.top, color)) color = 1 if w is active_window else 2
rects.extend(as_rect(g.right, g.top - bw, g.right + bw, g.bottom + bw, color)) border(
rects.extend(as_rect(g.left - bw, g.bottom, g.right + bw, g.bottom + bw, color)) 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: with self.lock:
self.num_of_rects = len(rects) // 12 self.num_of_rects = len(rects) // 12
self.rects = (GLfloat * len(rects))() self.rects = (GLfloat * len(rects))()
@@ -107,4 +149,7 @@ class Borders:
program.send_data(self.rects) program.send_data(self.rects)
program.set_colors(self.color_buf) program.set_colors(self.color_buf)
self.is_dirty = False self.is_dirty = False
glMultiDrawArrays(GL_TRIANGLE_FAN, addressof(self.starts), addressof(self.counts), self.num_of_rects) glMultiDrawArrays(
GL_TRIANGLE_FAN,
addressof(self.starts),
addressof(self.counts), self.num_of_rects)

View File

@@ -14,6 +14,7 @@ from time import monotonic
from queue import Queue, Empty from queue import Queue, Empty
from gettext import gettext as _ from gettext import gettext as _
from .config import MINIMUM_FONT_SIZE
from .constants import ( from .constants import (
viewport_size, set_boss, wakeup, cell_size, MODIFIER_KEYS, viewport_size, set_boss, wakeup, cell_size, MODIFIER_KEYS,
main_thread, mouse_button_pressed, mouse_cursor_pos main_thread, mouse_button_pressed, mouse_cursor_pos
@@ -27,7 +28,7 @@ from .fonts.render import set_font_family
from .borders import BordersProgram from .borders import BordersProgram
from .char_grid import cursor_shader, cell_shader from .char_grid import cursor_shader, cell_shader
from .constants import is_key_pressed from .constants import is_key_pressed
from .keys import interpret_text_event, interpret_key_event, get_shortcut from .keys import interpret_text_event, interpret_key_event, get_shortcut, get_sent_data
from .session import create_session from .session import create_session
from .shaders import Sprites, ShaderProgram from .shaders import Sprites, ShaderProgram
from .tabs import TabManager, SpecialWindow from .tabs import TabManager, SpecialWindow
@@ -86,6 +87,7 @@ class Boss(Thread):
self.pending_ui_thread_calls = Queue() self.pending_ui_thread_calls = Queue()
self.write_dispatch_map = {} self.write_dispatch_map = {}
set_boss(self) set_boss(self)
self.current_font_size = opts.font_size
cell_size.width, cell_size.height = set_font_family(opts) cell_size.width, cell_size.height = set_font_family(opts)
self.opts, self.args = opts, args self.opts, self.args = opts, args
self.glfw_window = glfw_window self.glfw_window = glfw_window
@@ -228,6 +230,28 @@ class Boss(Thread):
self.pending_resize = False self.pending_resize = False
glfw_post_empty_event() 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): def tabbar_visibility_changed(self):
self.tab_manager.resize(only_tabs=True) self.tab_manager.resize(only_tabs=True)
glfw_post_empty_event() glfw_post_empty_event()
@@ -247,18 +271,20 @@ class Boss(Thread):
@callback @callback
def on_text_input(self, window, codepoint, mods): def on_text_input(self, window, codepoint, mods):
data = interpret_text_event(codepoint, mods) w = self.active_window
if data: if w is not None:
w = self.active_window yield w
if w is not None: if w is not None:
yield w data = interpret_text_event(codepoint, mods, w)
w.write_to_child(data) if data:
w.write_to_child(data)
@callback @callback
def on_key(self, window, key, scancode, action, mods): def on_key(self, window, key, scancode, action, mods):
is_key_pressed[key] = action == GLFW_PRESS is_key_pressed[key] = action == GLFW_PRESS
self.start_cursor_blink() self.start_cursor_blink()
self.cursor_blink_zero_time = monotonic() self.cursor_blink_zero_time = monotonic()
func = None
if action == GLFW_PRESS or action == GLFW_REPEAT: if action == GLFW_PRESS or action == GLFW_REPEAT:
func = get_shortcut(self.opts.keymap, mods, key, scancode) func = get_shortcut(self.opts.keymap, mods, key, scancode)
if func is not None: if func is not None:
@@ -267,24 +293,24 @@ class Boss(Thread):
passthrough = f() passthrough = f()
if not passthrough: if not passthrough:
return return
tab = self.active_tab tab = self.active_tab
if tab is None: if tab is None:
return return
window = self.active_window window = self.active_window
if window is not None: if window is None:
yield window return
if func is not None: yield window
f = getattr(tab, func, getattr(window, func, None)) if func is not None:
if f is not None: f = getattr(tab, func, getattr(window, func, None))
passthrough = f() if f is not None:
if not passthrough: passthrough = f()
return if not passthrough:
if window.screen.auto_repeat_enabled or action == GLFW_PRESS: return
if window.char_grid.scrolled_by and key not in MODIFIER_KEYS: if window.char_grid.scrolled_by and key not in MODIFIER_KEYS and action == GLFW_PRESS:
window.scroll_end() window.scroll_end()
data = interpret_key_event(key, scancode, mods) 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: if data:
window.write_to_child(data) window.write_to_child(data)
@callback @callback
def on_focus(self, window, focused): def on_focus(self, window, focused):
@@ -339,7 +365,7 @@ class Boss(Thread):
@callback @callback
def on_mouse_move(self, window, xpos, ypos): 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() self.show_mouse_cursor()
w = self.window_for_pos(xpos, ypos) w = self.window_for_pos(xpos, ypos)
if w is not None: if w is not None:
@@ -369,6 +395,12 @@ class Boss(Thread):
def change_mouse_cursor(self, click=False): def change_mouse_cursor(self, click=False):
self.glfw_window.set_click_cursor(click) 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): def start_cursor_blink(self):
self.cursor_blinking = True self.cursor_blinking = True
if self.opts.cursor_stop_blinking_after > 0: if self.opts.cursor_stop_blinking_after > 0:
@@ -396,7 +428,7 @@ class Boss(Thread):
self.tab_manager.render(self.cell_program, self.sprites) self.tab_manager.render(self.cell_program, self.sprites)
for window, rd in render_data.items(): for window, rd in render_data.items():
if rd is not None: 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 active = self.active_window
rd = render_data.get(active) rd = render_data.get(active)
if rd is not None: if rd is not None:

View File

@@ -4,20 +4,33 @@
import re import re
import sys import sys
from enum import Enum
from collections import namedtuple from collections import namedtuple
from ctypes import addressof, memmove, sizeof from ctypes import addressof, memmove, sizeof
from threading import Lock from threading import Lock
from .config import build_ansi_color_table, defaults from .config import build_ansi_color_table, defaults
from .constants import get_boss, viewport_size, cell_size, ScreenGeometry, GLuint from .constants import (
from .utils import get_logical_dpi, to_color, set_primary_selection, open_url, color_as_int, safe_print GLuint, ScreenGeometry, cell_size, get_boss, viewport_size
)
from .fast_data_types import ( from .fast_data_types import (
glUniform2ui, glUniform4f, glUniform1i, glUniform2f, glDrawArraysInstanced, CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_BLEND,
GL_TRIANGLE_FAN, glEnable, glDisable, GL_BLEND, glDrawArrays, ColorProfile, GL_LINE_LOOP, GL_TRIANGLE_FAN, ColorProfile, glDisable, glDrawArrays,
CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE, DATA_CELL_SIZE, GL_LINE_LOOP glDrawArraysInstanced, glEnable, glUniform1i, glUniform2f, glUniform2ui,
glUniform4f
)
from .rgb import to_color
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') Cursor = namedtuple('Cursor', 'x y shape blink')
class DynamicColor(Enum):
default_fg, default_bg, cursor_color, highlight_fg, highlight_bg = range(1, 6)
if DATA_CELL_SIZE % 3: if DATA_CELL_SIZE % 3:
raise ValueError('Incorrect data cell size, must be a multiple of 3') raise ValueError('Incorrect data cell size, must be a multiple of 3')
@@ -31,6 +44,7 @@ uniform uvec2 dimensions; // xnum, ynum
uniform vec4 steps; // xstart, ystart, dx, dy uniform vec4 steps; // xstart, ystart, dx, dy
uniform vec2 sprite_layout; // dx, dy uniform vec2 sprite_layout; // dx, dy
uniform usamplerBuffer sprite_map; // gl_InstanceID -> x, y, z uniform usamplerBuffer sprite_map; // gl_InstanceID -> x, y, z
uniform uvec2 color_indices; // which color to use as fg and which as bg
out vec3 sprite_pos; out vec3 sprite_pos;
out vec3 underline_pos; out vec3 underline_pos;
out vec3 strike_pos; out vec3 strike_pos;
@@ -78,8 +92,8 @@ void main() {
uvec4 spos = texelFetch(sprite_map, sprite_id); uvec4 spos = texelFetch(sprite_map, sprite_id);
uvec4 colors = texelFetch(sprite_map, sprite_id + 1); uvec4 colors = texelFetch(sprite_map, sprite_id + 1);
sprite_pos = to_sprite_pos(pos, spos[0], spos[1], spos[2]); sprite_pos = to_sprite_pos(pos, spos[0], spos[1], spos[2]);
foreground = to_color(colors[0]); foreground = to_color(colors[color_indices[0]]);
background = to_color(colors[1]); background = to_color(colors[color_indices[1]]);
uint decoration = colors[2]; uint decoration = colors[2];
decoration_fg = to_color(decoration); decoration_fg = to_color(decoration);
underline_pos = to_sprite_pos(pos, (decoration >> 24) & SMASK, ZERO, ZERO); underline_pos = to_sprite_pos(pos, (decoration >> 24) & SMASK, ZERO, ZERO);
@@ -195,12 +209,15 @@ class Selection: # {{{
if y == b[0]: if y == b[0]:
endx = max(0, min(b[1], endx)) endx = max(0, min(b[1], endx))
l = line(y) l = line(y)
is_continued = l.is_continued()
if endx - startx >= linebuf.xnum - 1: if endx - startx >= linebuf.xnum - 1:
l = str(l).rstrip(' ') l = str(l).rstrip(' ')
else: else:
l = ''.join(l[x] for x in range(startx, endx + 1)) 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) lines.append(l)
return '\n'.join(lines) return ''.join(lines)
# }}} # }}}
@@ -213,10 +230,11 @@ def calculate_gl_geometry(window_geometry):
return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy)
def render_cells(buffer_id, sg, cell_program, sprites): def render_cells(buffer_id, sg, cell_program, sprites, invert_colors=False):
sprites.bind_sprite_map(buffer_id) sprites.bind_sprite_map(buffer_id)
ul = cell_program.uniform_location ul = cell_program.uniform_location
glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum) glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum)
glUniform2ui(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) glUniform4f(ul('steps'), sg.xstart, sg.ystart, sg.dx, sg.dy)
glUniform1i(ul('sprites'), sprites.sampler_num) glUniform1i(ul('sprites'), sprites.sampler_num)
glUniform1i(ul('sprite_map'), sprites.buffer_sampler_num) glUniform1i(ul('sprite_map'), sprites.buffer_sampler_num)
@@ -240,17 +258,13 @@ class CharGrid:
self.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) self.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
self.screen = screen self.screen = screen
self.opts = opts self.opts = opts
self.original_bg = opts.background self.default_bg = color_as_int(opts.background)
self.original_fg = opts.foreground self.default_fg = color_as_int(opts.foreground)
self.default_bg = color_as_int(self.original_bg)
self.default_fg = color_as_int(self.original_fg)
self.dpix, self.dpiy = get_logical_dpi() self.dpix, self.dpiy = get_logical_dpi()
self.opts = opts 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.opts = opts
self.original_bg = opts.background self.highlight_fg, self.highlight_bg = map(color_as_int, (opts.selection_foreground, opts.selection_background))
self.original_fg = opts.foreground
self.selection_foreground, self.selection_background = 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 self.sprite_map_type = self.main_sprite_map = self.scroll_sprite_map = self.render_buf = None
def escape(chars): def escape(chars):
@@ -278,16 +292,19 @@ class CharGrid:
def change_colors(self, changes): def change_colors(self, changes):
dirtied = False 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(): for which, val in changes.items():
if which in ('fg', 'bg'): val = item(val)
if not val: if val is None:
setattr(self, 'default_' + which, color_as_int(getattr(self, 'original_' + which))) continue
dirtied = True dirtied = True
else: setattr(self.screen, which.name, val)
val = to_color(val)
if val is not None:
setattr(self, 'default_' + which, color_as_int(val))
dirtied = True
if dirtied: if dirtied:
self.screen.mark_as_dirty() self.screen.mark_as_dirty()
@@ -305,12 +322,15 @@ class CharGrid:
sprites = get_boss().sprites sprites = get_boss().sprites
is_dirty = self.screen.is_dirty() is_dirty = self.screen.is_dirty()
with sprites.lock: 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( 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: if self.scrolled_by:
self.scrolled_by = min(self.scrolled_by + history_line_added_count, self.screen.historybuf.count) self.scrolled_by = min(self.scrolled_by + history_line_added_count, self.screen.historybuf.count)
self.screen.set_scroll_cell_data( 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)) self.scrolled_by, addressof(self.scroll_sprite_map))
data = self.scroll_sprite_map if self.scrolled_by else self.main_sprite_map data = self.scroll_sprite_map if self.scrolled_by else self.main_sprite_map
@@ -322,7 +342,7 @@ class CharGrid:
self.render_buf_is_dirty = True self.render_buf_is_dirty = True
if cursor_changed: if cursor_changed:
c = self.screen.cursor 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): def cell_for_pos(self, x, y):
x, y = int(x // cell_size.width), int(y // cell_size.height) x, y = int(x // cell_size.width), int(y // cell_size.height)
@@ -372,6 +392,10 @@ class CharGrid:
for m in self.url_pat.finditer(text): for m in self.url_pat.finditer(text):
if m.start() <= x < m.end(): if m.start() <= x < m.end():
url = ''.join(l[i] for i in range(*m.span())).rstrip('.') 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: if m.start() > 0:
before = l[m.start() - 1] before = l[m.start() - 1]
closing = {'(': ')', '[': ']', '{': '}', '<': '>', '"': '"', "'": "'", '`': '`', '|': '|', ':': ':'}.get(before) closing = {'(': ')', '[': ']', '{': '}', '<': '>', '"': '"', "'": "'", '`': '`', '|': '|', ':': ':'}.get(before)
@@ -449,15 +473,19 @@ class CharGrid:
buf = self.selection_buf buf = self.selection_buf
if self.render_buf_is_dirty or sel != self.last_rendered_selection: if self.render_buf_is_dirty or sel != self.last_rendered_selection:
memmove(buf, self.render_buf, sizeof(type(buf))) 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: if self.render_buf_is_dirty or self.last_rendered_selection != sel:
sprites.set_sprite_map(self.buffer_id, buf) sprites.set_sprite_map(self.buffer_id, buf)
self.render_buf_is_dirty = False self.render_buf_is_dirty = False
self.last_rendered_selection = sel self.last_rendered_selection = sel
return sg return sg
def render_cells(self, sg, cell_program, sprites): def render_cells(self, sg, cell_program, sprites, invert_colors=False):
render_cells(self.buffer_id, sg, cell_program, sprites) render_cells(self.buffer_id, sg, cell_program, sprites, invert_colors=invert_colors)
def render_cursor(self, sg, cursor_program, is_focused): def render_cursor(self, sg, cursor_program, is_focused):
cursor = self.current_cursor cursor = self.current_cursor
@@ -473,7 +501,8 @@ class CharGrid:
ul = cursor_program.uniform_location ul = cursor_program.uniform_location
left = sg.xstart + cursor.x * sg.dx left = sg.xstart + cursor.x * sg.dx
top = sg.ystart - cursor.y * sg.dy 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 shape = cursor.shape or self.default_cursor.shape
alpha = self.opts.cursor_opacity alpha = self.opts.cursor_opacity
if alpha < 1.0 and shape == CURSOR_BLOCK: if alpha < 1.0 and shape == CURSOR_BLOCK:
@@ -483,7 +512,7 @@ class CharGrid:
bottom = top - sg.dy bottom = top - sg.dy
if shape == CURSOR_UNDERLINE: if shape == CURSOR_UNDERLINE:
top = bottom + width(vert=False) 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('xpos'), left, right)
glUniform2f(ul('ypos'), top, bottom) glUniform2f(ul('ypos'), top, bottom)
glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4) glDrawArrays(GL_TRIANGLE_FAN if is_focused else GL_LINE_LOOP, 0, 4)

View File

@@ -10,6 +10,7 @@ import signal
from threading import Thread from threading import Thread
from .constants import terminfo_dir from .constants import terminfo_dir
import kitty.fast_data_types as fast_data_types
def remove_cloexec(fd): def remove_cloexec(fd):
@@ -33,6 +34,7 @@ class Child:
self.forked = True self.forked = True
master, slave = os.openpty() # Note that master and slave are in blocking mode master, slave = os.openpty() # Note that master and slave are in blocking mode
remove_cloexec(slave) remove_cloexec(slave)
self.set_iutf8(fd=master)
stdin, self.stdin = self.stdin, None stdin, self.stdin = self.stdin, None
if stdin is not None: if stdin is not None:
stdin_read_fd, stdin_write_fd = os.pipe() stdin_read_fd, stdin_write_fd = os.pipe()
@@ -79,6 +81,16 @@ class Child:
if self.child_fd is not None: if self.child_fd is not None:
fcntl.ioctl(self.child_fd, termios.TIOCSWINSZ, struct.pack('4H', h, w, ww, wh)) 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): def hangup(self):
if self.pid is not None: if self.pid is not None:
pid, self.pid = self.pid, None pid, self.pid = self.pid, None

View File

@@ -12,6 +12,7 @@ import sys
CSI = '\033[' CSI = '\033['
OSC = '\033]'
def write(x): def write(x):
@@ -19,10 +20,30 @@ def write(x):
sys.stdout.flush() sys.stdout.flush()
def set_title(*args):
pass
def set_icon(*args):
pass
def screen_bell():
pass
def screen_cursor_position(y, x): def screen_cursor_position(y, x):
write(CSI + '%s;%sH' % (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): def screen_designate_charset(which, to):
which = '()'[int(which)] which = '()'[int(which)]
to = chr(int(to)) to = chr(int(to))
@@ -53,18 +74,34 @@ def screen_set_margins(t, b):
write(CSI + '%d;%dr' % (t, b)) write(CSI + '%d;%dr' % (t, b))
def screen_indexn(n):
write(CSI + '%dS' % n)
def screen_erase_in_display(how, private): def screen_erase_in_display(how, private):
write(CSI + ('?' if private else '') + str(how) + 'J') 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): def screen_cursor_up2(count):
write(CSI + '%dA' % count) write(CSI + '%dA' % count)
def screen_cursor_down(count):
write(CSI + '%dB' % count)
def screen_carriage_return(): def screen_carriage_return():
write('\r') write('\r')
def screen_linefeed():
write('\n')
def screen_backspace(): def screen_backspace():
write('\x08') write('\x08')
@@ -73,12 +110,30 @@ def draw(*a):
write(' '.join(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): def replay(raw):
for line in raw.splitlines(): for line in raw.splitlines():
if line.strip(): if line.strip():
cmd, rest = line.partition(' ')[::2] cmd, rest = line.partition(' ')[::2]
if cmd == 'draw': if cmd in {'draw', 'set_title', 'set_icon', 'set_dynamic_color', 'set_color_table_color'}:
draw(rest) globals()[cmd](rest)
else: else:
rest = map(int, rest.split()) if rest else () rest = map(int, rest.split()) if rest else ()
globals()[cmd](*rest) globals()[cmd](*rest)
@@ -87,4 +142,7 @@ def replay(raw):
def main(path): def main(path):
raw = open(path).read() raw = open(path).read()
replay(raw) replay(raw)
input() try:
input()
except (EOFError, KeyboardInterrupt):
pass

43
kitty/cocoa_window.m Normal file
View File

@@ -0,0 +1,43 @@
/*
* 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>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskTitled NSTitledWindowMask
#endif
PyObject*
cocoa_hide_titlebar(PyObject UNUSED *self, PyObject *window_id) {
NSWindow *window = (NSWindow*)PyLong_AsVoidPtr(window_id);
@try {
[window setStyleMask:
[window styleMask] & ~NSWindowStyleMaskTitled];
} @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]);
}

View File

@@ -99,6 +99,7 @@ as_color(ColorProfile *self, PyObject *val) {
break; break;
case 2: case 2:
col = entry >> 8; col = entry >> 8;
break;
default: default:
ans = Py_None; Py_INCREF(Py_None); ans = Py_None; Py_INCREF(Py_None);
} }

View File

@@ -2,37 +2,45 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import re import ast
import sys
import os
import shlex
import json import json
import os
import re
import shlex
import sys
import tempfile import tempfile
from collections import namedtuple from collections import namedtuple
from .fast_data_types import ( from . import fast_data_types as defines
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 .constants import config_dir 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+(.+)$') key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
MINIMUM_FONT_SIZE = 6
def to_font_size(x): 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): def to_cursor_shape(x):
try: try:
return cshapes[x.lower()] return cshapes[x.lower()]
except KeyError: 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): def to_bool(x):
@@ -53,33 +61,122 @@ def parse_mods(parts):
try: try:
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper())) mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
except AttributeError: 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
return mods return mods
named_keys = {"'": 'APOSTROPHE', ',': 'COMMA', '-': 'MINUS', '.': 'PERIOD', named_keys = {
'/': 'SLASH', ';': 'SEMICOLON', '=': 'EQUAL', '[': 'LEFT_BRACKET', "'": 'APOSTROPHE',
']': 'RIGHT_BRACKET', '`': 'GRAVE_ACCENT'} ',': 'COMMA',
'-': 'MINUS',
'.': 'PERIOD',
'/': 'SLASH',
';': 'SEMICOLON',
'=': 'EQUAL',
'[': 'LEFT_BRACKET',
']': 'RIGHT_BRACKET',
'`': 'GRAVE_ACCENT'
}
def parse_key(val, keymap): def parse_shortcut(sc):
sc, action = val.partition(' ')[::2]
action = action.strip()
sc = sc.strip()
if not sc or not action:
return
parts = sc.split('+') parts = sc.split('+')
mods = parse_mods(parts[:-1]) mods = parse_mods(parts[:-1])
key = parts[-1].upper() key = parts[-1].upper()
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None) 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: 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 return
keymap[(mods, key)] = action 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): def to_open_url_modifiers(val):
return parse_mods(val.split('+')) return parse_mods(val.split('+'))
@@ -93,29 +190,46 @@ def to_layout_names(raw):
raise ValueError('The window layout {} is unknown'.format(p)) 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 = { type_map = {
'scrollback_lines': int, 'scrollback_lines': positive_int,
'scrollback_pager': shlex.split, 'scrollback_pager': shlex.split,
'scrollback_in_new_tab': to_bool, 'scrollback_in_new_tab': to_bool,
'font_size': to_font_size, 'font_size': to_font_size,
'font_size_delta': positive_float,
'cursor_shape': to_cursor_shape, 'cursor_shape': to_cursor_shape,
'cursor_opacity': to_opacity, 'cursor_opacity': to_opacity,
'open_url_modifiers': to_open_url_modifiers, 'open_url_modifiers': to_open_url_modifiers,
'repaint_delay': int, 'repaint_delay': positive_int,
'window_border_width': float, 'window_border_width': positive_float,
'window_margin_width': positive_float,
'window_padding_width': positive_float,
'wheel_scroll_multiplier': float, 'wheel_scroll_multiplier': float,
'click_interval': float, 'visual_bell_duration': positive_float,
'mouse_hide_wait': float, 'enable_audio_bell': to_bool,
'cursor_blink_interval': float, 'click_interval': positive_float,
'cursor_stop_blinking_after': float, 'mouse_hide_wait': positive_float,
'cursor_blink_interval': positive_float,
'cursor_stop_blinking_after': positive_float,
'enabled_layouts': to_layout_names, 'enabled_layouts': to_layout_names,
'remember_window_size': to_bool, 'remember_window_size': to_bool,
'initial_window_width': int, 'initial_window_width': positive_int,
'initial_window_height': int, 'initial_window_height': positive_int,
'use_system_wcwidth': to_bool, '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) type_map[name] = lambda x: to_color(x, validate=True)
for i in range(16): for i in range(16):
type_map['color%d' % i] = lambda x: to_color(x, validate=True) 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) type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True)
def parse_config(lines): def parse_config(lines, check_keys=True):
ans = {'keymap': {}} ans = {'keymap': {}, 'symbol_map': {}, 'send_text_map': {'kitty': {}, 'normal': {}, 'application': {}}}
if check_keys:
all_keys = defaults._asdict()
for line in lines: for line in lines:
line = line.strip() line = line.strip()
if not line or line.startswith('#'): if not line or line.startswith('#'):
@@ -136,6 +252,18 @@ def parse_config(lines):
if key == 'map': if key == 'map':
parse_key(val, ans['keymap']) parse_key(val, ans['keymap'])
continue 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) tm = type_map.get(key)
if tm is not None: if tm is not None:
val = tm(val) val = tm(val)
@@ -143,46 +271,74 @@ def parse_config(lines):
return ans return ans
with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')) as f: with open(
defaults = parse_config(f.readlines()) 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())) Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults) defaults = Options(**defaults)
actions = frozenset(defaults.keymap.values())
def update_dict(a, b): def merge_keymaps(defaults, newvals):
a.update(b) ans = defaults.copy()
return a 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): def merge_dicts(defaults, newvals):
return {k: update_dict(v, vals.get(k, {})) if isinstance(v, dict) else vals.get(k, v) for k, v in defaults.items()} ans = defaults.copy()
ans.update(newvals)
return ans
def load_config(path: str) -> Options: def merge_configs(defaults, vals):
if not path: ans = {}
return defaults for k, v in defaults.items():
try: if isinstance(v, dict):
f = open(path) newvals = vals.get(k, {})
except FileNotFoundError: if k == 'keymap':
return defaults 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() ans = defaults._asdict()
actions = frozenset(defaults.keymap.values()) for path in paths:
with f: if not path:
vals = parse_config(f) continue
remove_keys = {k for k, v in vals.get('keymap', {}).items() if v in ('noop', 'no-op', 'no_op')} try:
vals['keymap'] = {k: v for k, v in vals.get('keymap', {}).items() if v in actions} f = open(path)
ans = merge_dicts(vals, ans) except FileNotFoundError:
for k in remove_keys: continue
ans['keymap'].pop(k, None) 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) return Options(**ans)
def build_ansi_color_table(opts: Options=defaults): def build_ansi_color_table(opts: Options=defaults):
def as_int(x): def as_int(x):
return (x[0] << 16) | (x[1] << 8) | x[2] return (x[0] << 16) | (x[1] << 8) | x[2]
def col(i): def col(i):
return as_int(getattr(opts, 'color{}'.format(i))) return as_int(getattr(opts, 'color{}'.format(i)))
return list(map(col, range(16))) return list(map(col, range(16)))
@@ -198,21 +354,33 @@ def load_cached_values():
except FileNotFoundError: except FileNotFoundError:
pass pass
except Exception as err: 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(): 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: try:
with os.fdopen(fd, 'wb') as f: with os.fdopen(fd, 'wb') as f:
f.write(json.dumps(cached_values).encode('utf-8')) f.write(json.dumps(cached_values).encode('utf-8'))
os.rename(p, cached_path) os.rename(p, cached_path)
except Exception as err: 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: finally:
try: try:
os.remove(p) os.remove(p)
except FileNotFoundError: except FileNotFoundError:
pass pass
except Exception as err: 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
)

View File

@@ -15,7 +15,7 @@ from .fast_data_types import (
GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER) GLFW_KEY_LEFT_SUPER, GLFW_KEY_RIGHT_SUPER)
appname = 'kitty' appname = 'kitty'
version = (0, 1, 0) version = (0, 2, 8)
str_version = '.'.join(map(str, version)) str_version = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
isosx = 'darwin' in _plat 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'))) 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) ans = os.path.join(candidate, appname)
try: os.makedirs(ans, exist_ok=True)
os.makedirs(ans)
except FileExistsError:
pass
return ans return ans

View File

@@ -24,15 +24,15 @@ dealloc(Cursor* self) {
#define EQ(x) (a->x == b->x) #define EQ(x) (a->x == b->x)
static int __eq__(Cursor *a, Cursor *b) { 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) #define BOOL(x) ((x) ? Py_True : Py_False)
static PyObject * static PyObject *
repr(Cursor *self) { repr(Cursor *self) {
return PyUnicode_FromFormat( 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)", "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->color, self->fg, self->bg, BOOL(self->bold), BOOL(self->italic), BOOL(self->reverse), BOOL(self->strikethrough), self->decoration, self->decoration_fg 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); cursor_reset_display_attrs(self);
self->x = 0; self->y = 0; self->x = 0; self->y = 0;
self->shape = 0; self->blink = false; self->shape = 0; self->blink = false;
self->color = 0;
} }
void cursor_copy_to(Cursor *src, Cursor *dest) { void cursor_copy_to(Cursor *src, Cursor *dest) {
#define CCY(x) dest->x = src->x; #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); 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); copy(Cursor *self);
#define copy_doc "Create a clone of this cursor" #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 {{{ // Boilerplate {{{
BOOL_GETSET(Cursor, bold) BOOL_GETSET(Cursor, bold)
@@ -82,7 +76,6 @@ static PyMemberDef members[] = {
{"x", T_UINT, offsetof(Cursor, x), 0, "x"}, {"x", T_UINT, offsetof(Cursor, x), 0, "x"},
{"y", T_UINT, offsetof(Cursor, y), 0, "y"}, {"y", T_UINT, offsetof(Cursor, y), 0, "y"},
{"shape", T_UBYTE, offsetof(Cursor, shape), 0, "shape"}, {"shape", T_UBYTE, offsetof(Cursor, shape), 0, "shape"},
{"color", T_ULONG, offsetof(Cursor, color), 0, "color"},
{"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"}, {"decoration", T_UBYTE, offsetof(Cursor, decoration), 0, "decoration"},
{"fg", T_ULONG, offsetof(Cursor, fg), 0, "fg"}, {"fg", T_ULONG, offsetof(Cursor, fg), 0, "fg"},
{"bg", T_ULONG, offsetof(Cursor, bg), 0, "bg"}, {"bg", T_ULONG, offsetof(Cursor, bg), 0, "bg"},
@@ -96,7 +89,6 @@ static PyGetSetDef getseters[] = {
GETSET(reverse) GETSET(reverse)
GETSET(strikethrough) GETSET(strikethrough)
GETSET(blink) GETSET(blink)
{"color", (getter) color_get, NULL, "color", NULL},
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };

View File

@@ -31,6 +31,17 @@ change_wcwidth_wrap(PyObject UNUSED *self, PyObject *use9) {
Py_RETURN_NONE; 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[] = { static PyMethodDef module_methods[] = {
GL_METHODS GL_METHODS
{"drain_read", (PyCFunction)drain_read, METH_O, ""}, {"drain_read", (PyCFunction)drain_read, METH_O, ""},
@@ -38,8 +49,12 @@ static PyMethodDef module_methods[] = {
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""}, {"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
{"read_bytes", (PyCFunction)read_bytes, METH_VARARGS, ""}, {"read_bytes", (PyCFunction)read_bytes, METH_VARARGS, ""},
{"read_bytes_dump", (PyCFunction)read_bytes_dump, 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, ""}, {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"change_wcwidth", (PyCFunction)change_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 GLFW_FUNC_WRAPPERS
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };
@@ -53,7 +68,9 @@ static struct PyModuleDef module = {
.m_methods = module_methods .m_methods = module_methods
}; };
PyMODINIT_FUNC #include <termios.h>
EXPORTED PyMODINIT_FUNC
PyInit_fast_data_types(void) { PyInit_fast_data_types(void) {
PyObject *m; PyObject *m;
@@ -77,6 +94,7 @@ PyInit_fast_data_types(void) {
#else #else
if (!init_Face(m)) return NULL; if (!init_Face(m)) return NULL;
if (!init_freetype_library(m)) return NULL; if (!init_freetype_library(m)) return NULL;
if (!init_fontconfig_library(m)) return NULL;
#endif #endif
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
@@ -101,6 +119,9 @@ PyInit_fast_data_types(void) {
PyModule_AddIntMacro(m, NORMAL_PROTOCOL); PyModule_AddIntMacro(m, NORMAL_PROTOCOL);
PyModule_AddIntMacro(m, URXVT_PROTOCOL); PyModule_AddIntMacro(m, URXVT_PROTOCOL);
PyModule_AddIntMacro(m, UTF8_PROTOCOL); PyModule_AddIntMacro(m, UTF8_PROTOCOL);
#ifdef IUTF8
PyModule_AddIntMacro(m, IUTF8);
#endif
} }
return m; return m;

View File

@@ -13,12 +13,14 @@
#define PY_SSIZE_T_CLEAN #define PY_SSIZE_T_CLEAN
#include <Python.h> #include <Python.h>
#define UNUSED __attribute__ ((unused)) #define UNUSED __attribute__ ((unused))
#define EXPORTED __attribute__ ((visibility ("default")))
#define MAX(x, y) (((x) > (y)) ? (x) : (y)) #define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) > (y)) ? (y) : (x)) #define MIN(x, y) (((x) > (y)) ? (y) : (x))
#define xstr(s) str(s)
#define str(s) #s
typedef Py_UCS4 char_type; typedef Py_UCS4 char_type;
typedef uint64_t color_type; typedef uint32_t color_type;
typedef uint32_t decoration_type;
typedef uint32_t combining_type; typedef uint32_t combining_type;
typedef unsigned int index_type; typedef unsigned int index_type;
@@ -31,7 +33,8 @@ typedef unsigned int index_type;
#define SGR_PROTOCOL 2 #define SGR_PROTOCOL 2
#define URXVT_PROTOCOL 3 #define URXVT_PROTOCOL 3
#define CELL_SIZE (sizeof(char_type) + sizeof(color_type) + sizeof(decoration_type) + sizeof(combining_type)) #define CELL_FIELD_COUNT 5
#define CELL_SIZE (CELL_FIELD_COUNT * 4)
// The data cell size must be a multiple of 3 // The data cell size must be a multiple of 3
#define DATA_CELL_SIZE 2 * 3 #define DATA_CELL_SIZE 2 * 3
@@ -46,8 +49,6 @@ typedef unsigned int index_type;
#define REVERSE_SHIFT 6 #define REVERSE_SHIFT 6
#define STRIKE_SHIFT 7 #define STRIKE_SHIFT 7
#define COL_MASK 0xFFFFFFFF #define COL_MASK 0xFFFFFFFF
#define COL_SHIFT 32
#define HAS_BG_MASK (0xFF << COL_SHIFT)
#define CC_MASK 0xFFFF #define CC_MASK 0xFFFF
#define CC_SHIFT 16 #define CC_SHIFT 16
#define UTF8_ACCEPT 0 #define UTF8_ACCEPT 0
@@ -77,7 +78,8 @@ typedef unsigned int index_type;
#define COPY_CELL(src, s, dest, d) \ #define COPY_CELL(src, s, dest, d) \
(dest)->chars[d] = (src)->chars[s]; \ (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)->decoration_fg[d] = (src)->decoration_fg[s]; \
(dest)->combining_chars[d] = (src)->combining_chars[s]; (dest)->combining_chars[d] = (src)->combining_chars[s];
@@ -85,18 +87,17 @@ typedef unsigned int index_type;
#define COPY_LINE(src, dest) \ #define COPY_LINE(src, dest) \
memcpy((dest)->chars, (src)->chars, sizeof(char_type) * MIN((src)->xnum, (dest)->xnum)); \ 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)->fg_colors, (src)->fg_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)->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)); memcpy((dest)->combining_chars, (src)->combining_chars, sizeof(combining_type) * MIN((src)->xnum, (dest)->xnum));
#define CLEAR_LINE(l, at, num) \ #define CLEAR_LINE(l, num) \
for (index_type i = (at); i < (num); i++) (l)->chars[i] = (1 << ATTRS_SHIFT) | 32; \ for (index_type i = 0; i < (num); i++) (l)->chars[i] = (1 << ATTRS_SHIFT) | 32; \
memset((l)->colors, (at), (num) * sizeof(color_type)); \ memset((l)->fg_colors, 0, (num) * sizeof(color_type)); \
memset((l)->decoration_fg, (at), (num) * sizeof(decoration_type)); \ memset((l)->bg_colors, 0, (num) * sizeof(color_type)); \
memset((l)->combining_chars, (at), (num) * sizeof(combining_type)); memset((l)->decoration_fg, 0, (num) * sizeof(color_type)); \
memset((l)->combining_chars, 0, (num) * sizeof(combining_type));
#define COLORS_TO_CURSOR(col, c) \
c->fg = col & COL_MASK; c->bg = (col >> COL_SHIFT)
#define METHOD(name, arg_type) {#name, (PyCFunction)name, arg_type, name##_doc}, #define METHOD(name, arg_type) {#name, (PyCFunction)name, arg_type, name##_doc},
@@ -145,8 +146,9 @@ typedef struct {
PyObject_HEAD PyObject_HEAD
char_type *chars; char_type *chars;
color_type *colors; color_type *fg_colors;
decoration_type *decoration_fg; color_type *bg_colors;
color_type *decoration_fg;
combining_type *combining_chars; combining_type *combining_chars;
index_type xnum, ynum; index_type xnum, ynum;
bool continued; bool continued;
@@ -166,8 +168,9 @@ typedef struct {
// Pointers into buf // Pointers into buf
char_type *chars; char_type *chars;
color_type *colors; color_type *fg_colors;
decoration_type *decoration_fg; color_type *bg_colors;
color_type *decoration_fg;
combining_type *combining_chars; combining_type *combining_chars;
} LineBuf; } LineBuf;
PyTypeObject LineBuf_Type; PyTypeObject LineBuf_Type;
@@ -175,7 +178,7 @@ PyTypeObject LineBuf_Type;
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
uint8_t *buf; uint32_t *buf;
index_type xnum, ynum; index_type xnum, ynum;
Line *line; Line *line;
index_type start_of_data, count; index_type start_of_data, count;
@@ -188,7 +191,7 @@ typedef struct {
bool bold, italic, reverse, strikethrough, blink; bool bold, italic, reverse, strikethrough, blink;
unsigned int x, y; unsigned int x, y;
uint8_t decoration, shape; uint8_t decoration, shape;
unsigned long fg, bg, decoration_fg, color; unsigned long fg, bg, decoration_fg;
} Cursor; } Cursor;
PyTypeObject Cursor_Type; PyTypeObject Cursor_Type;
@@ -244,8 +247,8 @@ PyTypeObject ChangeTracker_Type;
typedef struct { typedef struct {
bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, bool mLNM, mIRM, mDECTCEM, mDECSCNM, mDECOM, mDECAWM, mDECCOLM, mDECARM, mDECCKM,
mBRACKETED_PASTE, mFOCUS_TRACKING; mBRACKETED_PASTE, mFOCUS_TRACKING, mEXTENDED_KEYBOARD;
unsigned long mouse_tracking_mode, mouse_tracking_protocol; unsigned long mouse_tracking_mode, mouse_tracking_protocol;
} ScreenModes; } ScreenModes;
PyTypeObject ScreenModes_Type; PyTypeObject ScreenModes_Type;
@@ -253,7 +256,7 @@ PyTypeObject ScreenModes_Type;
#define SAVEPOINTS_SZ 256 #define SAVEPOINTS_SZ 256
typedef struct { 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; bool use_latin1;
Cursor cursor; Cursor cursor;
bool mDECOM, mDECAWM, mDECSCNM; bool mDECOM, mDECAWM, mDECSCNM;
@@ -274,7 +277,7 @@ typedef struct {
PyObject_HEAD PyObject_HEAD
unsigned int columns, lines, margin_top, margin_bottom, charset; 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; bool use_latin1;
Cursor *cursor; Cursor *cursor;
SavepointBuffer main_savepoints, alt_savepoints; SavepointBuffer main_savepoints, alt_savepoints;
@@ -289,6 +292,7 @@ typedef struct {
unsigned int parser_state, parser_text_start, parser_buf_pos; unsigned int parser_state, parser_text_start, parser_buf_pos;
bool parser_has_pending_text; bool parser_has_pending_text;
uint8_t read_buf[READ_BUF_SZ]; uint8_t read_buf[READ_BUF_SZ];
uint32_t default_fg, default_bg, highlight_fg, highlight_bg, cursor_color;
} Screen; } Screen;
PyTypeObject Screen_Type; PyTypeObject Screen_Type;
@@ -373,6 +377,8 @@ void screen_change_default_color(Screen *self, unsigned int which, uint32_t col)
void screen_alignment_display(Screen *self); void screen_alignment_display(Screen *self);
void screen_reverse_index(Screen *self); void screen_reverse_index(Screen *self);
void screen_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_reset(Screen *self);
void screen_set_tab_stop(Screen *self); void screen_set_tab_stop(Screen *self);
void screen_tab(Screen *self); void screen_tab(Screen *self);
@@ -396,6 +402,7 @@ void screen_erase_characters(Screen *self, unsigned int count);
void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom); void screen_set_margins(Screen *self, unsigned int top, unsigned int bottom);
void screen_change_charset(Screen *, uint32_t to); void screen_change_charset(Screen *, uint32_t to);
void screen_designate_charset(Screen *, uint32_t which, uint32_t as); 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_title(Screen *self, PyObject*);
void set_icon(Screen *self, PyObject*); void set_icon(Screen *self, PyObject*);
void set_dynamic_color(Screen *self, unsigned int code, PyObject*); void set_dynamic_color(Screen *self, unsigned int code, PyObject*);
@@ -403,9 +410,10 @@ void set_color_table_color(Screen *self, unsigned int code, PyObject*);
uint32_t* translation_table(uint32_t which); uint32_t* translation_table(uint32_t which);
uint32_t *latin1_charset; uint32_t *latin1_charset;
void screen_request_capabilities(Screen *, PyObject *); 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 select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count);
void report_device_status(Screen *self, unsigned int which, bool UNUSED); 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); #define DECLARE_CH_SCREEN_HANDLER(name) void screen_##name(Screen *screen);
DECLARE_CH_SCREEN_HANDLER(bell) DECLARE_CH_SCREEN_HANDLER(bell)
DECLARE_CH_SCREEN_HANDLER(backspace) DECLARE_CH_SCREEN_HANDLER(backspace)
@@ -414,3 +422,5 @@ DECLARE_CH_SCREEN_HANDLER(linefeed)
DECLARE_CH_SCREEN_HANDLER(carriage_return) DECLARE_CH_SCREEN_HANDLER(carriage_return)
bool init_freetype_library(PyObject*); bool init_freetype_library(PyObject*);
bool init_fontconfig_library(PyObject*);
PyObject *get_fontconfig_font(PyObject *self, PyObject *args);

84
kitty/fontconfig.c Normal file
View 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;
}

View File

@@ -26,7 +26,7 @@ def draw_hline(buf, width, x1, x2, y, level):
def draw_vline(buf, width, y1, y2, x, 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) sz = thickness(level=level, horizontal=True)
start = x - sz // 2 start = x - sz // 2
for x in range(start, start + sz): for x in range(start, start + sz):
@@ -345,6 +345,15 @@ def render_box_char(ch, buf, width, height):
return buf 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): def join_rows(width, height, rows):
import ctypes import ctypes
ans = (ctypes.c_ubyte * (width * height * len(rows)))() ans = (ctypes.c_ubyte * (width * height * len(rows)))()

View File

@@ -7,10 +7,19 @@ from kitty.fast_data_types import CTFace as Face
from kitty.utils import get_logical_dpi, wcwidth, ceil_int from kitty.utils import get_logical_dpi, wcwidth, ceil_int
main_font = {} main_font = {}
symbol_map = {}
cell_width = cell_height = baseline = CellTexture = WideCellTexture = underline_thickness = underline_position = None 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 global cell_width, cell_height, baseline, CellTexture, WideCellTexture, underline_thickness, underline_position
try: try:
dpi = get_logical_dpi() dpi = get_logical_dpi()
@@ -28,10 +37,12 @@ def set_font_family(opts, ignore_dpi_failure=False):
if ans == 'auto' and (bold or italic): if ans == 'auto' and (bold or italic):
ans = get_family(False, False) ans = get_family(False, False)
return ans return ans
font_size = override_font_size or opts.font_size
for bold in (False, True): for bold in (False, True):
for italic 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)] mf = main_font[(False, False)]
cell_width, cell_height = mf.cell_size() cell_width, cell_height = mf.cell_size()
CellTexture = ctypes.c_ubyte * (cell_width * cell_height) 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): def render_cell(text=' ', bold=False, italic=False):
width = wcwidth(text[0]) ch = text[0]
face = main_font[(bold, italic)] width = wcwidth(ch)
face = symbol_map.get(ch) or main_font[(bold, italic)]
if width == 2: if width == 2:
buf, width = WideCellTexture(), cell_width * 2 buf, width = WideCellTexture(), cell_width * 2
else: else:

View File

@@ -2,57 +2,145 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re import re
import subprocess import subprocess
from collections import namedtuple 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): def escape_family_name(name):
return re.sub(r'([-:,\\])', lambda m: '\\' + m.group(1), 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): class FontNotFound(ValueError):
query += ':scalable=true:outline=true' pass
raw = subprocess.check_output(['fc-match', query, '-f', '%{file}\x1e%{hinting}\x1e%{hintstyle}']).decode('utf-8')
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') parts = raw.split('\x1e')
hintstyle, hinting = 1, 'True' try:
if len(parts) == 3: path, hinting, hintstyle, scalable, outline, weight, slant, index = parts
path, hinting, hintstyle = parts except ValueError:
else:
path = parts[0] path = parts[0]
hinting = hinting.lower() == 'true' hintstyle, hinting, scalable, outline, weight, slant, index = 1, 'True', 'True', 'True', 100, 0, 0
hintstyle = int(hintstyle) return Font(
return Font(path, hinting, hintstyle, bold, italic) 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 get_font_lib(
def find_font_for_character(family, char, bold=False, italic=False): family='monospace',
q = escape_family_name(family) + ':charset={}'.format(hex(ord(char[0]))[2:]) bold=False,
if bold: italic=False,
q += ':weight=200' allow_bitmaped_fonts=False,
if italic: size_in_pts=None,
q += ':slant=100' character=None,
return get_font(q, bold, italic) 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) get_font = get_font_lib
def get_font_information(q, bold=False, italic=False):
q = escape_family_name(q)
if bold: def find_font_for_character(
q += ':weight=200' family,
if italic: char,
q += ':slant=100' bold=False,
return get_font(q, bold, italic) 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): def get_font_files(opts):
ans = {} 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): def get_family(key=None):
ans = getattr(opts, attr_map.get(key, 'font_family')) ans = getattr(opts, attr_map.get(key, 'font_family'))
@@ -61,11 +149,21 @@ def get_font_files(opts):
return ans return ans
n = get_font_information(get_family()) 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): 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: 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') do('bold'), do('italic'), do('bi')
return ans return ans
def font_for_family(family):
ans = get_font_information(family)
return ans._replace(face=Face(ans.face, ans.index))

View File

@@ -2,22 +2,26 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import unicodedata
import ctypes import ctypes
import sys
import unicodedata
from collections import namedtuple from collections import namedtuple
from functools import lru_cache from functools import lru_cache
from itertools import chain
from threading import Lock from threading import Lock
from kitty.fast_data_types import Face, FT_PIXEL_MODE_GRAY from kitty.fast_data_types import FT_PIXEL_MODE_GRAY, Face
from kitty.utils import ceil_int from kitty.fonts.box_drawing import render_missing_glyph
from .fontconfig import find_font_for_character, get_font_files from kitty.utils import ceil_int, get_logical_dpi, safe_print, wcwidth
from kitty.utils import get_logical_dpi, 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 current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None
CharTexture = underline_position = underline_thickness = None CharTexture = underline_position = underline_thickness = None
alt_face_cache = {} alt_face_cache = {}
symbol_map = {}
def set_char_size(face, width=0, height=0, hres=0, vres=0): 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) ch = chr(i)
load_char(font, face, ch) load_char(font, face, ch)
m = face.glyph_metrics() 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 return ans
@lru_cache(maxsize=2**10) @lru_cache(maxsize=2**10)
def font_for_char(char, bold=False, italic=False): def font_for_char(char, bold=False, italic=False, allow_bitmaped_fonts=False):
return find_font_for_character(current_font_family_name, char, bold, italic) 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): 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))) 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 current_font_family, current_font_family_name, cff_size, cell_width, cell_height, CharTexture, baseline
global underline_position, underline_thickness global underline_position, underline_thickness
size_in_pts = opts.font_size size_in_pts = override_font_size or opts.font_size
find_font_for_character.cache_clear()
current_font_family = get_font_files(opts) current_font_family = get_font_files(opts)
current_font_family_name = opts.font_family current_font_family_name = opts.font_family
dpi = get_logical_dpi() dpi = get_logical_dpi()
cff_size = ceil_int(64 * size_in_pts) cff_size = ceil_int(64 * size_in_pts)
cff_size = {'width': cff_size, 'height': cff_size, 'hres': int(dpi[0]), 'vres': int(dpi[1])} cff_size = {
for fobj in current_font_family.values(): '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) set_char_size(fobj.face, **cff_size)
face = current_font_family['regular'].face face = current_font_family['regular'].face
cell_width = calc_cell_width(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]) cell_height = font_units_to_pixels(
baseline = font_units_to_pixels(face.ascender, face.units_per_EM, size_in_pts, dpi[1]) face.height, 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]) 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) CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
font_for_char.cache_clear() font_for_char.cache_clear()
alt_face_cache.clear() alt_face_cache.clear()
return cell_width, cell_height 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() freetype_lock = Lock()
@@ -80,45 +121,66 @@ def render_to_bitmap(font, face, text):
bitmap = face.bitmap() bitmap = face.bitmap()
if bitmap.pixel_mode != FT_PIXEL_MODE_GRAY: if bitmap.pixel_mode != FT_PIXEL_MODE_GRAY:
raise ValueError( 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 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): def render_char(text, bold=False, italic=False, width=1):
key = 'regular' key = 'regular'
if bold: if bold:
key = 'bi' if italic else 'bold' key = 'bi' if italic else 'bold'
elif italic: elif italic:
key = 'italic' key = 'italic'
ch = text[0]
with freetype_lock: with freetype_lock:
font = current_font_family.get(key) or current_font_family['regular'] font = symbol_map.get(ch)
face = font.face if font is None or not font.face.get_char_index(ch):
if not face.get_char_index(text[0]): font = current_font_family.get(key) or current_font_family['regular']
font = font_for_char(text[0], bold, italic) face = font.face
face = alt_face_cache.get(font) if not face.get_char_index(ch):
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)
try: try:
set_char_size(face, **sz) font = font_for_char(ch, bold, italic)
bitmap = render_to_bitmap(font, face, text) except FontNotFound:
finally: font = font_for_char(
set_char_size(face, **cff_size) ch, bold, italic, allow_bitmaped_fonts=True
m = face.glyph_metrics() )
return CharBitmap(bitmap.buffer, ceil_int(abs(m.horiBearingX) / 64), face = alt_face_cache.get(font)
ceil_int(abs(m.horiBearingY) / 64), ceil_int(m.horiAdvance / 64), bitmap.rows, bitmap.width) 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): 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 extra = dest_start_column + bitmap_char.columns - cell_width
if extra > 0: if extra > 0:
dest_start_column -= extra 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 # Calculate row bounds, making sure the baseline is aligned with the cell
# baseline # baseline
@@ -141,9 +205,13 @@ def place_char_in_cell(bitmap_char):
src_start_row, dest_start_row = bitmap_char.bearingY - baseline, 0 src_start_row, dest_start_row = bitmap_char.bearingY - baseline, 0
else: else:
src_start_row, dest_start_row = 0, baseline - bitmap_char.bearingY 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) row_count = min(
return create_cell_buffer(bitmap_char, src_start_row, dest_start_row, row_count, bitmap_char.rows - src_start_row, cell_height - dest_start_row
src_start_column, dest_start_column, column_count) )
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): def split_char_bitmap(bitmap_char):
@@ -168,12 +236,31 @@ def current_cell():
return CharTexture, cell_width, cell_height, baseline, underline_thickness, underline_position 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): def render_cell(text=' ', bold=False, italic=False):
# TODO: Handle non-normalizable combining chars. Probably need to use # TODO: Handle non-normalizable combining chars. Probably need to use
# harfbuzz for that # harfbuzz for that
text = unicodedata.normalize('NFC', text)[0] text = unicodedata.normalize('NFC', text)[0]
width = wcwidth(text) 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 second = None
if width == 2: if width == 2:
if bitmap_char.columns > cell_width: if bitmap_char.columns > cell_width:
@@ -187,11 +274,16 @@ def render_cell(text=' ', bold=False, italic=False):
return first, second 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 = bitmap_char.data
src_stride = bitmap_char.columns src_stride = bitmap_char.columns
dest = CharTexture() dest = CharTexture()
for r in range(row_count): 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] dest[dr:dr + column_count] = src[sr:sr + column_count]
return dest return dest

View File

@@ -15,6 +15,7 @@ typedef struct {
FT_Face face; FT_Face face;
unsigned int units_per_EM; unsigned int units_per_EM;
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness; int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness;
bool is_scalable;
} Face; } Face;
@@ -56,18 +57,20 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
Face *self; Face *self;
char *path; char *path;
int error; int error;
long index;
/* unsigned int columns=80, lines=24, scrollback=0; */ /* 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); self = (Face *)type->tp_alloc(type, 0);
if (self != NULL) { if (self != NULL) {
Py_BEGIN_ALLOW_THREADS; 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; Py_END_ALLOW_THREADS;
if(error) { set_freetype_error("Failed to load face, with error:", error); Py_CLEAR(self); return NULL; } 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; #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); 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 #undef CPY
self->is_scalable = FT_IS_SCALABLE(self->face);
} }
return (PyObject*)self; return (PyObject*)self;
} }
@@ -137,7 +140,7 @@ static PyStructSequence_Field gm_fields[] = {
}; };
static PyStructSequence_Desc gm_desc = {"GlpyhMetrics", NULL, gm_fields, 8}; static PyStructSequence_Desc gm_desc = {"GlpyhMetrics", NULL, gm_fields, 8};
static PyTypeObject GlpyhMetricsType = {0}; static PyTypeObject GlpyhMetricsType = {{{0}}};
static PyObject* static PyObject*
glyph_metrics(Face *self) { glyph_metrics(Face *self) {
@@ -164,7 +167,7 @@ static PyStructSequence_Field bm_fields[] = {
{NULL} {NULL}
}; };
static PyStructSequence_Desc bm_desc = {"Bitmap", NULL, bm_fields, 7}; static PyStructSequence_Desc bm_desc = {"Bitmap", NULL, bm_fields, 7};
static PyTypeObject BitmapType = {0}; static PyTypeObject BitmapType = {{{0}}};
static PyObject* static PyObject*
bitmap(Face *self) { bitmap(Face *self) {
@@ -236,6 +239,7 @@ static PyMemberDef members[] = {
MEM(max_advance_height, T_INT), MEM(max_advance_height, T_INT),
MEM(underline_position, T_INT), MEM(underline_position, T_INT),
MEM(underline_thickness, T_INT), MEM(underline_thickness, T_INT),
MEM(is_scalable, T_BOOL),
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };

View File

@@ -141,10 +141,14 @@ _glewInit(PyObject UNUSED *self) {
PyErr_Format(PyExc_RuntimeError, "GLEW init failed: %s", glewGetErrorString(err)); PyErr_Format(PyExc_RuntimeError, "GLEW init failed: %s", glewGetErrorString(err));
return NULL; return NULL;
} }
if(!GLEW_ARB_texture_storage) { #define ARB_TEST(name) \
PyErr_SetString(PyExc_RuntimeError, "OpenGL is missing the required ARB_texture_storage extension"); if (!GLEW_ARB_##name) { \
return NULL; PyErr_Format(PyExc_RuntimeError, "The OpenGL driver on this system is missing the required extension: ARB_%s", #name); \
return NULL; \
} }
ARB_TEST(texture_storage);
ARB_TEST(texture_buffer_object_rgb32);
#undef ARB_TEST
#endif #endif
Py_RETURN_NONE; Py_RETURN_NONE;
} }

View File

@@ -7,6 +7,10 @@
#include "data-types.h" #include "data-types.h"
#include <structmember.h> #include <structmember.h>
#include <GLFW/glfw3.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) #if GLFW_VERSION_MAJOR < 3 || (GLFW_VERSION_MAJOR == 3 && GLFW_VERSION_MINOR < 2)
#error "glfw >= 3.2 required" #error "glfw >= 3.2 required"
@@ -93,7 +97,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self = (Window *)type->tp_alloc(type, 0); self = (Window *)type->tp_alloc(type, 0);
if (self != NULL) { if (self != NULL) {
self->window = glfwCreateWindow(width, height, title, NULL, 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++) { for(i = 0; i < MAX_WINDOWS; i++) {
if (window_weakrefs[i] == NULL) { window_weakrefs[i] = self; break; } if (window_weakrefs[i] == NULL) { window_weakrefs[i] = self; break; }
} }
@@ -178,10 +182,8 @@ glfw_post_empty_event(PyObject UNUSED *self) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
PyObject* static PyObject*
glfw_get_physical_dpi(PyObject UNUSED *self) { get_physical_dpi(GLFWmonitor *m) {
GLFWmonitor *m = glfwGetPrimaryMonitor();
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
int width = 0, height = 0; int width = 0, height = 0;
glfwGetMonitorPhysicalSize(m, &width, &height); glfwGetMonitorPhysicalSize(m, &width, &height);
if (width == 0 || height == 0) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor size"); return NULL; } 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); 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* PyObject*
glfw_get_key_name(PyObject UNUSED *self, PyObject *args) { glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
int key, scancode; int key, scancode;
@@ -199,6 +208,18 @@ glfw_get_key_name(PyObject UNUSED *self, PyObject *args) {
return Py_BuildValue("s", glfwGetKeyName(key, scancode)); 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 static void
@@ -318,6 +339,64 @@ get_window_size(Window *self) {
return Py_BuildValue("ii", w, h); 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 {{{ // Boilerplate {{{
#define MND(name, args) {#name, (PyCFunction)name, args, ""} #define MND(name, args) {#name, (PyCFunction)name, args, ""}
@@ -329,6 +408,13 @@ static PyMethodDef methods[] = {
MND(should_close, METH_NOARGS), MND(should_close, METH_NOARGS),
MND(get_framebuffer_size, METH_NOARGS), MND(get_framebuffer_size, METH_NOARGS),
MND(get_window_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_should_close, METH_VARARGS),
MND(set_input_mode, METH_VARARGS), MND(set_input_mode, METH_VARARGS),
MND(is_key_pressed, METH_VARARGS), MND(is_key_pressed, METH_VARARGS),
@@ -374,6 +460,10 @@ init_glfw(PyObject *m) {
PyEval_InitThreads(); PyEval_InitThreads();
glfwSetErrorCallback(cb_error_callback); glfwSetErrorCallback(cb_error_callback);
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false; #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_RELEASE);
ADDC(GLFW_PRESS); ADDC(GLFW_PRESS);
ADDC(GLFW_REPEAT); ADDC(GLFW_REPEAT);

View File

@@ -17,6 +17,17 @@ PyObject* glfw_wait_events(PyObject UNUSED *self, PyObject*);
PyObject* glfw_post_empty_event(PyObject UNUSED *self); PyObject* glfw_post_empty_event(PyObject UNUSED *self);
PyObject* glfw_get_physical_dpi(PyObject UNUSED *self); PyObject* glfw_get_physical_dpi(PyObject UNUSED *self);
PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args); PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
PyObject* glfw_init_hint_string(PyObject UNUSED *self, PyObject *args);
#ifdef __APPLE__
PyObject* cocoa_hide_titlebar(PyObject UNUSED *self, PyObject *window_id);
PyObject* cocoa_get_lang(PyObject UNUSED *self);
#define COCOA_HIDE_TITLEBAR {"cocoa_hide_titlebar", (PyCFunction)cocoa_hide_titlebar, METH_O, ""},
#define COCOA_GET_LANG {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
#else
#define COCOA_HIDE_TITLEBAR
#define COCOA_GET_LANG
#endif
#define GLFW_FUNC_WRAPPERS \ #define GLFW_FUNC_WRAPPERS \
{"glfw_set_error_callback", (PyCFunction)glfw_set_error_callback, METH_O, ""}, \ {"glfw_set_error_callback", (PyCFunction)glfw_set_error_callback, METH_O, ""}, \
@@ -28,4 +39,7 @@ PyObject* glfw_get_key_name(PyObject UNUSED *self, PyObject *args);
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \ {"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""}, \
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, 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_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""}, \
{"glfw_init_hint_string", (PyCFunction)glfw_init_hint_string, METH_VARARGS, ""}, \
COCOA_HIDE_TITLEBAR \
COCOA_GET_LANG

View File

@@ -8,12 +8,13 @@
#include "data-types.h" #include "data-types.h"
#include <structmember.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) { start_of(HistoryBuf *self, index_type num) {
// Pointer to the start of the line with index (buffer position) 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 * static PyObject *
@@ -32,7 +33,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
if (self != NULL) { if (self != NULL) {
self->xnum = xnum; self->xnum = xnum;
self->ynum = ynum; 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(); self->line = alloc_line();
if (self->buf == NULL || self->line == NULL) { if (self->buf == NULL || self->line == NULL) {
PyErr_NoMemory(); PyErr_NoMemory();
@@ -41,7 +42,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
} else { } else {
self->line->xnum = xnum; self->line->xnum = xnum;
for(index_type y = 0; y < self->ynum; y++) { 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; 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 static inline void
init_line(HistoryBuf *self, index_type num, Line *l) { 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 // 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); uint32_t *start_ptr = start_of(self, num);
l->continued = *start_ptr; l->continued = (bool)(*start_ptr);
l->chars = (char_type*)(start_ptr + 1); l->chars = (char_type*)(start_ptr + 1);
l->colors = (color_type*)(l->chars + self->xnum); l->fg_colors = (color_type*)(l->chars + self->xnum);
l->decoration_fg = (decoration_type*)(l->colors + 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); l->combining_chars = (combining_type*)(l->decoration_fg + self->xnum);
} }
@@ -93,16 +95,16 @@ index_type historybuf_push(HistoryBuf *self) {
bool bool
historybuf_resize(HistoryBuf *self, index_type lines) { historybuf_resize(HistoryBuf *self, index_type lines) {
HistoryBuf t = {0}; HistoryBuf t = {{0}};
t.xnum=self->xnum; t.xnum=self->xnum;
t.ynum=lines; t.ynum=lines;
if (t.ynum > 0 && t.ynum != self->ynum) { 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; } if (t.buf == NULL) { PyErr_NoMemory(); return false; }
t.count = MIN(self->count, t.ynum); t.count = MIN(self->count, t.ynum);
if (t.count > 0) { if (t.count > 0) {
for (index_type s=0; s < t.count; s++) { 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); 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 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; #define next_dest_line(cont) *start_of(dest, historybuf_push(dest)) = cont; dest->line->continued = cont;

304
kitty/key_encoding.py Normal file
View 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__])

View File

@@ -2,10 +2,33 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # 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 .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_UP: 'kcuu1',
defines.GLFW_KEY_DOWN: 'kcud1', defines.GLFW_KEY_DOWN: 'kcud1',
defines.GLFW_KEY_LEFT: 'kcub1', defines.GLFW_KEY_LEFT: 'kcub1',
@@ -16,17 +39,36 @@ key_map = {
defines.GLFW_KEY_DELETE: 'kdch1', defines.GLFW_KEY_DELETE: 'kdch1',
defines.GLFW_KEY_PAGE_UP: 'kpp', defines.GLFW_KEY_PAGE_UP: 'kpp',
defines.GLFW_KEY_PAGE_DOWN: 'knp', defines.GLFW_KEY_PAGE_DOWN: 'knp',
} }.items():
key_map = {k: key_as_bytes(v) for k, v in key_map.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): for f in range(1, 13):
key_map[getattr(defines, 'GLFW_KEY_F{}'.format(f))] = key_as_bytes('kf{}'.format(f)) kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
del 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' smkx_key_map[defines.GLFW_KEY_ESCAPE] = b'\033'
key_map[defines.GLFW_KEY_ENTER] = b'\r' smkx_key_map[defines.GLFW_KEY_ENTER] = b'\r'
key_map[defines.GLFW_KEY_KP_ENTER] = b'\r' smkx_key_map[defines.GLFW_KEY_KP_ENTER] = b'\r'
key_map[defines.GLFW_KEY_BACKSPACE] = key_as_bytes('kbs') smkx_key_map[defines.GLFW_KEY_BACKSPACE] = key_as_bytes('kbs')
key_map[defines.GLFW_KEY_TAB] = b'\t' smkx_key_map[defines.GLFW_KEY_TAB] = b'\t'
SHIFTED_KEYS = { SHIFTED_KEYS = {
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'), defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
@@ -36,26 +78,57 @@ SHIFTED_KEYS = {
defines.GLFW_KEY_RIGHT: key_as_bytes('kRIT'), 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.update({
control_codes[defines.GLFW_KEY_UP] = bytearray(key_as_bytes('cuu1').replace(b'[', b'[1;5')) k: (1 + i, )
control_codes[defines.GLFW_KEY_DOWN] = bytearray(key_as_bytes('cud1').replace(b'[', b'[1;5')) for i, k in
control_codes[defines.GLFW_KEY_LEFT] = bytearray(key_as_bytes('cub1').replace(b'[', b'[1;5')) enumerate(range(defines.GLFW_KEY_A, defines.GLFW_KEY_RIGHT_BRACKET + 1))
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_6] = (30,)
control_codes[defines.GLFW_KEY_END] = bytearray(key_as_bytes('kend').replace(b'O', b'[1;5')) control_codes[defines.GLFW_KEY_SLASH] = (31,)
control_codes[defines.GLFW_KEY_PAGE_UP] = bytearray(key_as_bytes('kpp').replace(b'~', b';5~')) control_codes[defines.GLFW_KEY_SPACE] = (0,)
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))} 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 = { 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 { for name, ch in {
'APOSTROPHE': "'", 'COMMA': ',', 'PERIOD': '.', 'SLASH': '/', 'MINUS': '-', 'SEMICOLON': ';', 'EQUAL': '=', 'APOSTROPHE': "'",
'LEFT_BRACKET': '[', 'RIGHT_BRACKET': ']', 'GRAVE_ACCENT': '`', 'BACKSLASH': '\\'}.items(): 'COMMA': ',',
'PERIOD': '.',
'SLASH': '/',
'MINUS': '-',
'SEMICOLON': ';',
'EQUAL': '=',
'LEFT_BRACKET': '[',
'RIGHT_BRACKET': ']',
'GRAVE_ACCENT': '`',
'BACKSLASH': '\\'
}.items():
valid_localized_key_names[ch] = getattr(defines, 'GLFW_KEY_' + name) 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) return valid_localized_key_names.get((name or '').upper(), key)
def interpret_key_event(key, scancode, mods): action_map = {
data = bytearray() 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) key = get_localized_key(key, scancode)
if mods == defines.GLFW_MOD_CONTROL and key in control_codes: if screen.extended_keyboard:
# Map Ctrl-key to ascii control code return extended_key_event(key, scancode, mods, action)
data.extend(control_codes[key]) data = bytearray()
elif mods == defines.GLFW_MOD_ALT and key in alt_codes: if action == defines.GLFW_PRESS or (
# Map Alt+key to Esc-key action == defines.GLFW_REPEAT and screen.auto_repeat_enabled
data.extend(alt_codes[key]) ):
else: if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
x = key_map.get(key) # Map Ctrl-key to ascii control code
if x is not None: data.extend(control_codes[key])
if mods == defines.GLFW_MOD_SHIFT: elif mods in alt_mods and key in alt_codes:
x = SHIFTED_KEYS.get(key, x) # Printable keys handled by interpret_text_event()
data.extend(x) 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) 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 > 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 return b'' # Handled by interpret_key_event above
data = chr(codepoint).encode('utf-8') data = chr(codepoint).encode('utf-8')
return data return data
@@ -92,3 +201,11 @@ def interpret_text_event(codepoint, mods):
def get_shortcut(keymap, mods, key, scancode): def get_shortcut(keymap, mods, key, scancode):
key = get_localized_key(key, scancode) key = get_localized_key(key, scancode)
return keymap.get((mods & 0b1111, key)) 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))

View File

@@ -6,7 +6,7 @@
# that have many weight variants like Book, Medium, Thick, etc. For example: # that have many weight variants like Book, Medium, Thick, etc. For example:
# font_family Operator Mono Book # font_family Operator Mono Book
# bold_font Operator Mono Thick # bold_font Operator Mono Thick
# bold_talic_font Operator Mono Medium # bold_italic_font Operator Mono Medium
font_family monospace font_family monospace
italic_font auto italic_font auto
bold_font auto bold_font auto
@@ -15,6 +15,10 @@ bold_italic_font auto
# Font size (in pts) # Font size (in pts)
font_size 11.0 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 # The foreground color
foreground #dddddd foreground #dddddd
@@ -41,7 +45,7 @@ cursor_shape block
cursor_blink_interval 0.5 cursor_blink_interval 0.5
# Stop blinking cursor after the specified number of seconds of keyboard inactivity. Set to # 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 cursor_stop_blinking_after 15.0
# Number of lines of history to keep in memory for scrolling back # 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 # When viewing scrollback in a new window, put it in a new tab as well
scrollback_in_new_tab no 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 wheel_scroll_multiplier 5.0
# The interval between successive clicks to detect double/triple clicks (in seconds) # The interval between successive clicks to detect double/triple clicks (in seconds)
@@ -67,7 +72,7 @@ click_interval 0.5
select_by_word_characters :@-./_~?&=%+# select_by_word_characters :@-./_~?&=%+#
# Hide mouse cursor after the specified number of seconds of the mouse not being used. Set to # 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 mouse_hide_wait 3.0
# The enabled window layouts. A comma separated list of layout names. The special value * means # 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. # that sufficient for most uses.
repaint_delay 10 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 # The modifier keys to press when clicking with the mouse on URLs to open the URL
open_url_modifiers ctrl+shift 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. # The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
window_border_width 1 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 # The color for the border of the active window
active_border_color #00ff00 active_border_color #00ff00
@@ -207,3 +225,45 @@ map ctrl+shift+q close_tab
map ctrl+shift+l next_layout map ctrl+shift+l next_layout
map ctrl+shift+. move_tab_forward map ctrl+shift+. move_tab_forward
map ctrl+shift+, move_tab_backward 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

View File

@@ -6,25 +6,29 @@ from collections import namedtuple
from itertools import islice from itertools import islice
from .constants import WindowGeometry, viewport_size, cell_size, get_boss from .constants import WindowGeometry, viewport_size, cell_size, get_boss
from .utils import pt_to_px
def available_height(): def available_height():
return viewport_size.height - get_boss().current_tab_bar_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 number_of_cells = length // cell_length
border_length += padding_length
space_needed_for_border = number_of_windows * 2 * border_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 extra = length - number_of_cells * cell_length
while extra < space_needed_for_border: while extra < space_needed:
number_of_cells -= 1 number_of_cells -= 1
extra = length - number_of_cells * cell_length extra = length - number_of_cells * cell_length
cells_per_window = number_of_cells // number_of_windows 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 = 0 if left_align else (extra // 2)
pos += border_length pos += border_length + margin_length
inner_length = cells_per_window * cell_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) extra = number_of_cells - (cells_per_window * number_of_windows)
while number_of_windows > 0: while number_of_windows > 0:
number_of_windows -= 1 number_of_windows -= 1
@@ -43,6 +47,8 @@ class Layout:
def __init__(self, opts, border_width, windows): def __init__(self, opts, border_width, windows):
self.opts = opts self.opts = opts
self.border_width = border_width 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 # 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 # this layout, i.e. spaces that are not covered by any window
self.blank_rects = () 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) 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(): def layout_single_window(margin_length, padding_length):
xstart, xnum = next(layout_dimension(viewport_size.width, cell_size.width)) 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)) 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) return window_geometry(xstart, xnum, ystart, ynum)
@@ -120,7 +126,7 @@ class Stack(Layout):
def __call__(self, windows, active_window_idx): def __call__(self, windows, active_window_idx):
self.blank_rects = [] self.blank_rects = []
wg = layout_single_window() wg = layout_single_window(self.margin_width, self.padding_width)
for i, w in enumerate(windows): for i, w in enumerate(windows):
w.is_visible_in_layout = i == active_window_idx w.is_visible_in_layout = i == active_window_idx
w.set_geometry(wg) w.set_geometry(wg)
@@ -135,17 +141,23 @@ class Tall(Layout):
def __call__(self, windows, active_window_idx): def __call__(self, windows, active_window_idx):
self.blank_rects = br = [] self.blank_rects = br = []
if len(windows) == 1: if len(windows) == 1:
wg = layout_single_window() wg = layout_single_window(self.margin_width, self.padding_width)
windows[0].set_geometry(wg) windows[0].set_geometry(wg)
self.blank_rects = blank_rects_for_window(windows[0]) self.blank_rects = blank_rects_for_window(windows[0])
return 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) 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)) windows[0].set_geometry(window_geometry(xstart, xnum, ystart, ynum))
vh = available_height() vh = available_height()
xstart, xnum = next(xlayout) 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): for w, (ystart, ynum) in zip(islice(windows, 1, None), ylayout):
w.set_geometry(window_geometry(xstart, xnum, ystart, ynum)) 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) left_blank_rect(windows[0], br, vh), top_blank_rect(windows[0], br, vh), right_blank_rect(windows[-1], br, vh)

View File

@@ -63,8 +63,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
Py_CLEAR(self); Py_CLEAR(self);
} else { } else {
self->chars = (char_type*)self->buf; self->chars = (char_type*)self->buf;
self->colors = (color_type*)(self->chars + self->block_size); self->fg_colors = (color_type*)(self->chars + self->block_size);
self->decoration_fg = (decoration_type*)(self->colors + 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->combining_chars = (combining_type*)(self->decoration_fg + self->block_size);
self->line->xnum = xnum; self->line->xnum = xnum;
for(index_type i = 0; i < ynum; i++) { for(index_type i = 0; i < ynum; i++) {
@@ -89,7 +90,8 @@ dealloc(LineBuf* self) {
#define INIT_LINE(lb, l, ynum) \ #define INIT_LINE(lb, l, ynum) \
(l)->chars = (lb)->chars + (ynum) * (lb)->xnum; \ (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)->decoration_fg = (lb)->decoration_fg + (ynum) * (lb)->xnum; \
(l)->combining_chars = (lb)->combining_chars + (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) { allocate_line_storage(Line *line, bool initialize) {
if (initialize) { if (initialize) {
line->chars = PyMem_Calloc(line->xnum, sizeof(char_type)); line->chars = PyMem_Calloc(line->xnum, sizeof(char_type));
line->colors = PyMem_Calloc(line->xnum, sizeof(color_type)); line->fg_colors = PyMem_Calloc(line->xnum, sizeof(color_type));
line->decoration_fg = PyMem_Calloc(line->xnum, sizeof(decoration_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)); 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; for (index_type i = 0; i < line->xnum; i++) line->chars[i] = (1 << ATTRS_SHIFT) | 32;
} else { } else {
line->chars = PyMem_Malloc(line->xnum * sizeof(char_type)); line->chars = PyMem_Malloc(line->xnum * sizeof(char_type));
line->colors = PyMem_Malloc(line->xnum * sizeof(color_type)); line->fg_colors = PyMem_Malloc(line->xnum * sizeof(color_type));
line->decoration_fg = PyMem_Malloc(line->xnum * sizeof(decoration_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)); 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->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->decoration_fg); line->decoration_fg = NULL;
PyMem_Free(line->combining_chars); line->combining_chars = NULL; PyMem_Free(line->combining_chars); line->combining_chars = NULL;
PyErr_NoMemory(); PyErr_NoMemory();
@@ -210,7 +215,7 @@ copy_line_to(LineBuf *self, PyObject *args) {
void linebuf_clear_line(LineBuf *self, index_type y) { void linebuf_clear_line(LineBuf *self, index_type y) {
Line l; Line l;
INIT_LINE(self, &l, self->line_map[y]); INIT_LINE(self, &l, self->line_map[y]);
CLEAR_LINE(&l, 0, self->xnum); CLEAR_LINE(&l, self->xnum);
self->continued_map[y] = 0; self->continued_map[y] = 0;
} }
@@ -295,7 +300,7 @@ void linebuf_insert_lines(LineBuf *self, unsigned int num, unsigned int y, unsig
Line l; Line l;
for (i = y; i < y + num; i++) { for (i = y; i < y + num; i++) {
INIT_LINE(self, &l, self->line_map[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; 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; Line l;
for (i = ylimit - num; i < ylimit; i++) { for (i = ylimit - num; i < ylimit; i++) {
INIT_LINE(self, &l, self->line_map[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; 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; } if (!PyObject_TypeCheck(y, &LineBuf_Type)) { PyErr_SetString(PyExc_TypeError, "Not a LineBuf object"); return NULL; }
LineBuf *other = (LineBuf*)y; LineBuf *other = (LineBuf*)y;
if (other->xnum != self->xnum) { PyErr_SetString(PyExc_ValueError, "LineBuf has a different number of columns"); return NULL; } 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; sl.xnum = self->xnum; ol.xnum = other->xnum;
for (index_type i = 0; i < MIN(self->ynum, other->ynum); i++) { for (index_type i = 0; i < MIN(self->ynum, other->ynum); i++) {

View File

@@ -17,7 +17,8 @@ static void
dealloc(Line* self) { dealloc(Line* self) {
if (self->needs_free) { if (self->needs_free) {
PyMem_Free(self->chars); 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->decoration_fg);
PyMem_Free(self->combining_chars); PyMem_Free(self->combining_chars);
} }
@@ -170,9 +171,8 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
WRITE_SGR(0); break; WRITE_SGR(0); break;
} }
} }
color_type col = self->colors[pos]; CHECK_COLOR(fg, self->fg_colors[pos], 38);
CHECK_COLOR(fg, col & COL_MASK, 38); CHECK_COLOR(bg, self->bg_colors[pos], 48);
CHECK_COLOR(bg, col >> COL_SHIFT, 48);
CHECK_COLOR(decoration_fg, self->decoration_fg[pos], DECORATION_FG_CODE); CHECK_COLOR(decoration_fg, self->decoration_fg[pos], DECORATION_FG_CODE);
WRITE_CH(ch); WRITE_CH(ch);
char_type cc = self->combining_chars[pos]; char_type cc = self->combining_chars[pos];
@@ -200,6 +200,14 @@ as_ansi(Line* self) {
return ans; 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* static PyObject*
__repr__(Line* self) { __repr__(Line* self) {
PyObject *s = as_unicode(self); PyObject *s = as_unicode(self);
@@ -218,15 +226,6 @@ width(Line *self, PyObject *val) {
return PyLong_FromUnsignedLong((unsigned long) (attrs & WIDTH_MASK)); 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) { void line_add_combining_char(Line *self, uint32_t ch, unsigned int x) {
combining_type c = self->combining_chars[x]; combining_type c = self->combining_chars[x];
if (c & CC_MASK) self->combining_chars[x] = (c & CC_MASK) | ( (ch & CC_MASK) << CC_SHIFT ); 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; return NULL;
} }
attrs = CURSOR_TO_ATTRS(cursor, 1); attrs = CURSOR_TO_ATTRS(cursor, 1);
color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT); color_type fg = (cursor->fg & COL_MASK), bg = cursor->bg & COL_MASK;
decoration_type dfg = cursor->decoration_fg & COL_MASK; color_type dfg = cursor->decoration_fg & COL_MASK;
for (index_type i = cursor->x; offset < limit && i < self->xnum; i++, offset++) { 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->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->decoration_fg[i] = dfg;
self->combining_chars[i] = 0; self->combining_chars[i] = 0;
} }
@@ -299,13 +299,14 @@ cursor_from(Line* self, PyObject *args) {
ans->x = x; ans->y = y; ans->x = x; ans->y = y;
char_type attrs = self->chars[x] >> ATTRS_SHIFT; char_type attrs = self->chars[x] >> ATTRS_SHIFT;
ATTRS_TO_CURSOR(attrs, ans); 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; ans->decoration_fg = self->decoration_fg[x] & COL_MASK;
return (PyObject*)ans; 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); const char_type repl = ((char_type)ch & CHAR_MASK) | (1 << ATTRS_SHIFT);
for (index_type i = at; i < MIN(self->xnum, at + num); i++) { for (index_type i = at; i < MIN(self->xnum, at + num); i++) {
self->chars[i] = (self->chars[i] & ATTRS_MASK_WITHOUT_WIDTH) | repl; self->chars[i] = (self->chars[i] & ATTRS_MASK_WITHOUT_WIDTH) | repl;
@@ -323,10 +324,11 @@ clear_text(Line* self, PyObject *args) {
Py_RETURN_NONE; 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); char_type attrs = CURSOR_TO_ATTRS(cursor, 1);
color_type col = (cursor->fg & COL_MASK) | ((color_type)(cursor->bg & COL_MASK) << COL_SHIFT); color_type fg = (cursor->fg & COL_MASK), bg = (cursor->bg & COL_MASK);
decoration_type dfg = cursor->decoration_fg & COL_MASK; color_type dfg = cursor->decoration_fg & COL_MASK;
if (!clear_char) attrs = ((attrs >> ATTRS_SHIFT) & ~WIDTH_MASK) << ATTRS_SHIFT; if (!clear_char) attrs = ((attrs >> ATTRS_SHIFT) & ~WIDTH_MASK) << ATTRS_SHIFT;
for (index_type i = at; i < self->xnum && i < at + num; i++) { 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; char_type w = ((self->chars[i] >> ATTRS_SHIFT) & WIDTH_MASK) << ATTRS_SHIFT;
self->chars[i] = (self->chars[i] & CHAR_MASK) | attrs | w; 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; self->decoration_fg[i] = dfg;
} }
} }
@@ -390,13 +392,15 @@ left_shift(Line *self, PyObject *args) {
Py_RETURN_NONE; 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; char_type attrs;
if (cursor == NULL) { if (cursor == NULL) {
attrs = (((self->chars[at] >> ATTRS_SHIFT) & ~3) | (width & 3)) << ATTRS_SHIFT; attrs = (((self->chars[at] >> ATTRS_SHIFT) & ~3) | (width & 3)) << ATTRS_SHIFT;
} else { } else {
attrs = CURSOR_TO_ATTRS(cursor, width & 3); 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->decoration_fg[at] = cursor->decoration_fg & COL_MASK;
} }
self->chars[at] = (ch & CHAR_MASK) | attrs; self->chars[at] = (ch & CHAR_MASK) | attrs;
@@ -438,8 +442,9 @@ __len__(PyObject *self) {
static int __eq__(Line *a, Line *b) { static int __eq__(Line *a, Line *b) {
return a->xnum == b->xnum && \ return a->xnum == b->xnum && \
memcmp(a->chars, b->chars, sizeof(char_type) * a->xnum) == 0 && \ memcmp(a->chars, b->chars, sizeof(char_type) * a->xnum) == 0 && \
memcmp(a->colors, b->colors, sizeof(color_type) * a->xnum) == 0 && \ memcmp(a->fg_colors, b->fg_colors, sizeof(color_type) * a->xnum) == 0 && \
memcmp(a->decoration_fg, b->decoration_fg, sizeof(decoration_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; 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(set_attribute, METH_VARARGS)
METHOD(as_base_text, METH_NOARGS) METHOD(as_base_text, METH_NOARGS)
METHOD(as_ansi, METH_NOARGS) METHOD(as_ansi, METH_NOARGS)
METHOD(is_continued, METH_NOARGS)
METHOD(width, METH_O) METHOD(width, METH_O)
METHOD(basic_cell_data, METH_O)
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
@@ -510,7 +515,8 @@ copy_char(Line* self, PyObject *args) {
return NULL; return NULL;
} }
to->chars[dest] = self->chars[src]; 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->decoration_fg[dest] = self->decoration_fg[src];
to->combining_chars[dest] = self->combining_chars[src]; to->combining_chars[dest] = self->combining_chars[src];
Py_RETURN_NONE; Py_RETURN_NONE;

View File

@@ -3,49 +3,148 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import argparse import argparse
import tempfile import locale
import os import os
import sys import sys
from queue import Empty import tempfile
from gettext import gettext as _ 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 .boss import Boss
from .shaders import GL_VERSION from .config import (
from .fast_data_types import ( cached_values, load_cached_values, load_config, save_cached_values
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 .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_OPENGL_CORE_PROFILE,
GLFW_OPENGL_FORWARD_COMPAT, GLFW_OPENGL_PROFILE, GLFW_SAMPLES,
GLFW_STENCIL_BITS, Window, change_wcwidth,
enable_automatic_opengl_error_checking, glClear, glClearColor, glewInit,
glfw_init, glfw_set_error_callback, glfw_swap_interval, glfw_terminate,
glfw_wait_events, glfw_window_hint, glfw_init_hint_string
)
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
from .layout import all_layouts
from .shaders import GL_VERSION
from .utils import safe_print, detach
defconf = os.path.join(config_dir, 'kitty.conf')
def option_parser(): def option_parser():
parser = argparse.ArgumentParser(prog=appname, description=_('The {} terminal emulator').format(appname)) parser = argparse.ArgumentParser(
defconf = os.path.join(config_dir, 'kitty.conf') prog=appname,
description=_('The {} terminal emulator').format(appname)
)
a = parser.add_argument a = parser.add_argument
a('--class', default=appname, dest='cls', help=_('Set the WM_CLASS property')) a(
a('--config', default=defconf, help=_('Specify a path to the config file to use. Default: {}').format(defconf)) '--class',
a('--cmd', '-c', default=None, help=_('Run python code in the kitty context')) default=appname,
a('-d', '--directory', default='.', help=_('Change to the specified directory when launching')) dest='cls',
a('--version', '-v', action='version', version='{} {} by Kovid Goyal'.format(appname, str_version)) help=_('Set the WM_CLASS property')
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(
a('--replay-commands', default=None, help=_('Replay previously dumped commands')) '--config',
a('--window-layout', default=None, choices=frozenset(all_layouts.keys()), help=_( action='append',
'The window layout to use on startup')) help=_(
a('--session', default=None, help=_( 'Specify a path to the config file(s) to use.'
'Path to a file containing the startup session (tabs, windows, layout, programs)')) ' Can be specified multiple times to read multiple'
a('args', nargs=argparse.REMAINDER, help=_( ' config files in sequence, which are merged. Default: {}'
'The remaining arguments are used to launch a program other than the default shell. Any further options are passed' ).format(defconf)
' directly to the program being invoked.' )
)) 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 return parser
@@ -100,11 +199,20 @@ def run_app(opts, args):
else: else:
viewport_size.width = opts.initial_window_width viewport_size.width = opts.initial_window_width
viewport_size.height = opts.initial_window_height viewport_size.height = opts.initial_window_height
window = Window( try:
viewport_size.width, viewport_size.height, args.cls) 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.set_title(appname)
window.make_context_current() window.make_context_current()
if not isosx: if isosx:
if opts.macos_hide_titlebar:
from .fast_data_types import cocoa_hide_titlebar
cocoa_hide_titlebar(window.cocoa_window_id())
else:
with open(logo_data_file, 'rb') as f: with open(logo_data_file, 'rb') as f:
window.set_icon(f.read(), 256, 256) window.set_icon(f.read(), 256, 256)
viewport_size.width, viewport_size.height = window.get_framebuffer_size() viewport_size.width, viewport_size.height = window.get_framebuffer_size()
@@ -137,10 +245,38 @@ def on_glfw_error(code, msg):
safe_print('[glfw error] ', msg, file=sys.stderr) 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(): 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('~')) os.chdir(os.path.expanduser('~'))
args = option_parser().parse_args() args = option_parser().parse_args()
if getattr(args, 'detach', False):
detach()
if args.cmd: if args.cmd:
exec(args.cmd) exec(args.cmd)
return return
@@ -148,10 +284,14 @@ def main():
from kitty.client import main from kitty.client import main
main(args.replay_commands) main(args.replay_commands)
return 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) change_wcwidth(not opts.use_system_wcwidth)
glfw_set_error_callback(on_glfw_error) 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(): if not glfw_init():
raise SystemExit('GLFW initialization failed') raise SystemExit('GLFW initialization failed')
try: try:

View File

@@ -75,3 +75,9 @@
#define BRACKETED_PASTE (2004 << 5) #define BRACKETED_PASTE (2004 << 5)
#define BRACKETED_PASTE_START "\033[200~" #define BRACKETED_PASTE_START "\033[200~"
#define BRACKETED_PASTE_END "\033[201~" #define BRACKETED_PASTE_END "\033[201~"
// Styled underlines
#define STYLED_UNDERLINES (2016 << 5)
// Extended keyboard protocol
#define EXTENDED_KEYBOARD (2017 << 5)

View File

@@ -58,5 +58,5 @@ def encode_mouse_event(tracking_mode, tracking_protocol, button, action, mods, x
ans = bytes(ans) ans = bytes(ans)
else: else:
if x <= 223 and y <= 223: 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 return ans

View File

@@ -96,6 +96,9 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
#define REPORT_OSC(name, string) \ #define REPORT_OSC(name, string) \
Py_XDECREF(PyObject_CallFunction(dump_callback, "sO", #name, string)); PyErr_Clear(); 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 #else
#define DUMP_UNUSED UNUSED #define DUMP_UNUSED UNUSED
@@ -107,6 +110,7 @@ _report_params(PyObject *dump_callback, const char *name, unsigned int *params,
#define REPORT_PARAMS(...) #define REPORT_PARAMS(...)
#define FLUSH_DRAW #define FLUSH_DRAW
#define REPORT_OSC(name, string) #define REPORT_OSC(name, string)
#define REPORT_OSC2(name, code, string)
#endif #endif
@@ -225,10 +229,12 @@ handle_esc_mode_char(Screen *screen, uint32_t ch, PyObject DUMP_UNUSED *dump_cal
switch(ch) { switch(ch) {
case '@': case '@':
REPORT_COMMAND(screen_use_latin1, 1); REPORT_COMMAND(screen_use_latin1, 1);
screen->use_latin1 = true; screen->utf8_state = 0; break; screen_use_latin1(screen, true);
break;
case 'G': case 'G':
REPORT_COMMAND(screen_use_latin1, 0); REPORT_COMMAND(screen_use_latin1, 0);
screen->use_latin1 = false; screen->utf8_state = 0; break; screen_use_latin1(screen, false);
break;
default: default:
REPORT_ERROR("Unhandled Esc %% code: 0x%x", ch); break; 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 static inline void
dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
#define DISPATCH_OSC(name) REPORT_OSC(name, string); name(screen, string); #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; const unsigned int limit = screen->parser_buf_pos;
unsigned int code=0, i; unsigned int code=0, i;
for (i = 0; i < MIN(limit, 5); i++) { for (i = 0; i < MIN(limit, 5); i++) {
@@ -293,8 +299,14 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
break; break;
case 10: case 10:
case 11: case 11:
case 12:
case 17:
case 19:
case 110: case 110:
case 111: case 111:
case 112:
case 117:
case 119:
SET_COLOR(set_dynamic_color); SET_COLOR(set_dynamic_color);
break; break;
default: default:
@@ -314,7 +326,8 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case '"': \ case '"': \
case '*': \ case '*': \
case '\'': \ case '\'': \
case ' ': case ' ': \
case '$':
static inline void static inline void
@@ -322,11 +335,7 @@ screen_cursor_up2(Screen *s, unsigned int count) { screen_cursor_up(s, count, fa
static inline void static inline void
screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); } screen_cursor_back1(Screen *s, unsigned int count) { screen_cursor_back(s, count, -1); }
static inline void 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); } 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 static inline void
save_cursor(Screen *s, unsigned int UNUSED param, bool private) { 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"); 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); \ name(screen, p1, private); \
break; 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) \ #define CALL_CSI_HANDLER1M(name, defval) \
p1 = num_params > 0 ? params[0] : defval; \ p1 = num_params > 0 ? params[0] : defval; \
REPORT_COMMAND(name, p1, end_modifier); \ REPORT_COMMAND(name, p1, end_modifier); \
@@ -449,7 +464,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case ECH: case ECH:
CALL_CSI_HANDLER1(screen_erase_characters, 1); CALL_CSI_HANDLER1(screen_erase_characters, 1);
case DA: case DA:
CALL_CSI_HANDLER1P(report_device_attributes, 0, '>'); CALL_CSI_HANDLER1S(report_device_attributes, 0);
case TBC: case TBC:
CALL_CSI_HANDLER1(screen_clear_tab_stop, 0); CALL_CSI_HANDLER1(screen_clear_tab_stop, 0);
case SM: case SM:
@@ -469,9 +484,17 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
case DECSCUSR: case DECSCUSR:
CALL_CSI_HANDLER1M(screen_set_cursor, 1); CALL_CSI_HANDLER1M(screen_set_cursor, 1);
case SU: case SU:
CALL_CSI_HANDLER1(screen_indexn, 1); CALL_CSI_HANDLER1(screen_scroll, 1);
case SD: 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: default:
REPORT_ERROR("Unknown CSI code: 0x%x", code); 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) { switch(ch) {
case ST: case ST:
return true; 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: case BEL:
return true; return true;
case NUL: case NUL:
case DEL: case DEL:
break; 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: default:
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
REPORT_ERROR("OSC sequence too long, truncating."); 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--; screen->parser_buf_pos--;
return true; return true;
} }
/* fallthrough */
default: default:
if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) { if (screen->parser_buf_pos >= PARSER_BUF_SZ - 1) {
REPORT_ERROR("OTH sequence too long, truncating."); 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 static inline void
_parse_bytes(Screen *screen, uint8_t *buf, Py_ssize_t len, PyObject DUMP_UNUSED *dump_callback) { _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++) { for (unsigned int i = 0; i < len; i++) {
if (screen->use_latin1) dispatch_unicode_char(screen, latin1_charset[buf[i]], dump_callback); if (screen->use_latin1) dispatch_unicode_char(screen, latin1_charset[buf[i]], dump_callback);
else { else {
switch (decode_utf8(&screen->utf8_state, &codepoint, buf[i])) { switch (decode_utf8(&screen->utf8_state, &screen->utf8_codepoint, buf[i])) {
case UTF8_ACCEPT: case UTF8_ACCEPT:
dispatch_unicode_char(screen, codepoint, dump_callback); dispatch_unicode_char(screen, screen->utf8_codepoint, dump_callback);
break; break;
case UTF8_REJECT: case UTF8_REJECT:
screen->utf8_state = UTF8_ACCEPT; screen->utf8_state = UTF8_ACCEPT;
if (prev != UTF8_ACCEPT) i--; if (prev != UTF8_ACCEPT && i > 0) i--;
break; break;
} }
prev = screen->utf8_state; 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); */ /* PyObject_Print(Py_BuildValue("y#", screen->read_buf, len), stderr, 0); */
break; 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); _parse_bytes(screen, screen->read_buf, len, dump_callback);
if(len > 0) { Py_RETURN_TRUE; } if(len > 0) { Py_RETURN_TRUE; }
Py_RETURN_FALSE; Py_RETURN_FALSE;

View File

@@ -43,8 +43,9 @@
static inline void copy_range(Line *src, index_type src_at, Line* dest, index_type dest_at, index_type num) { 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->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->fg_colors + dest_at, src->fg_colors + src_at, num * sizeof(color_type));
memcpy(dest->decoration_fg + dest_at, src->decoration_fg + src_at, num * sizeof(decoration_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)); memcpy(dest->combining_chars + dest_at, src->combining_chars + src_at, num * sizeof(combining_type));
} }

816
kitty/rgb.py Normal file
View 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)

View File

@@ -7,6 +7,7 @@
#include "data-types.h" #include "data-types.h"
#include <structmember.h> #include <structmember.h>
#include <limits.h>
#include "unicode-data.h" #include "unicode-data.h"
#include "tracker.h" #include "tracker.h"
#include "modes.h" #include "modes.h"
@@ -29,6 +30,7 @@ init_tabstops(bool *tabstops, index_type count) {
self->g1_charset = self->g0_charset; \ self->g1_charset = self->g0_charset; \
self->g_charset = self->g0_charset; \ self->g_charset = self->g0_charset; \
self->utf8_state = 0; \ self->utf8_state = 0; \
self->utf8_codepoint = 0; \
self->use_latin1 = false; self->use_latin1 = false;
static PyObject* static PyObject*
@@ -43,6 +45,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
self->columns = columns; self->lines = lines; self->columns = columns; self->lines = lines;
self->modes = empty_modes; self->modes = empty_modes;
self->margin_top = 0; self->margin_bottom = self->lines - 1; 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; RESET_CHARSETS;
self->callbacks = callbacks; Py_INCREF(callbacks); self->callbacks = callbacks; Py_INCREF(callbacks);
self->cursor = alloc_cursor(); self->cursor = alloc_cursor();
@@ -67,6 +72,9 @@ screen_reset(Screen *self) {
if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self); if (self->linebuf == self->alt_linebuf) screen_toggle_screen_buffer(self);
linebuf_clear(self->linebuf, ' '); linebuf_clear(self->linebuf, ' ');
self->modes = empty_modes; 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; RESET_CHARSETS;
self->margin_top = 0; self->margin_bottom = self->lines - 1; self->margin_top = 0; self->margin_bottom = self->lines - 1;
screen_normal_keypad_mode(self); 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(IRM)
SIMPLE_MODE(DECARM) SIMPLE_MODE(DECARM)
SIMPLE_MODE(BRACKETED_PASTE) SIMPLE_MODE(BRACKETED_PASTE)
SIMPLE_MODE(EXTENDED_KEYBOARD)
SIMPLE_MODE(FOCUS_TRACKING) SIMPLE_MODE(FOCUS_TRACKING)
MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE) MOUSE_MODE(MOUSE_BUTTON_TRACKING, mouse_tracking_mode, BUTTON_MODE)
MOUSE_MODE(MOUSE_MOTION_TRACKING, mouse_tracking_mode, MOTION_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_SGR_MODE, mouse_tracking_protocol, SGR_PROTOCOL)
MOUSE_MODE(MOUSE_URXVT_MODE, mouse_tracking_protocol, URXVT_PROTOCOL) MOUSE_MODE(MOUSE_URXVT_MODE, mouse_tracking_protocol, URXVT_PROTOCOL)
case DECCKM:
case DECSCLM: case DECSCLM:
case DECNRCM: case DECNRCM:
break; // we ignore these modes break; // we ignore these modes
case DECCKM:
self->modes.mDECCKM = val;
break;
case DECTCEM: case DECTCEM:
self->modes.mDECTCEM = val; self->modes.mDECTCEM = val;
tracker_cursor_changed(self->change_tracker); 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 void
screen_index(Screen *self) { screen_index(Screen *self) {
// Move cursor down one line, scrolling screen if needed // Move cursor down one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == bottom) { if (self->cursor->y == bottom) {
linebuf_index(self->linebuf, top, bottom); INDEX_UP;
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);
} else screen_cursor_down(self, 1); } 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 void
screen_reverse_index(Screen *self) { screen_reverse_index(Screen *self) {
// Move cursor up one line, scrolling screen if needed // Move cursor up one line, scrolling screen if needed
unsigned int top = self->margin_top, bottom = self->margin_bottom; unsigned int top = self->margin_top, bottom = self->margin_bottom;
if (self->cursor->y == top) { if (self->cursor->y == top) {
linebuf_reverse_index(self->linebuf, top, bottom); INDEX_DOWN;
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);
} else screen_cursor_up(self, 1, false, -1); } 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 void
screen_carriage_return(Screen *self) { screen_carriage_return(Screen *self) {
@@ -635,6 +674,7 @@ savepoints_pop(SavepointBuffer *self) {
#define COPY_CHARSETS(self, sp) \ #define COPY_CHARSETS(self, sp) \
sp->utf8_state = self->utf8_state; \ sp->utf8_state = self->utf8_state; \
sp->utf8_codepoint = self->utf8_codepoint; \
sp->g0_charset = self->g0_charset; \ sp->g0_charset = self->g0_charset; \
sp->g1_charset = self->g1_charset; \ sp->g1_charset = self->g1_charset; \
sp->g_charset = self->g_charset; \ sp->g_charset = self->g_charset; \
@@ -699,13 +739,7 @@ screen_cursor_position(Screen *self, unsigned int line, unsigned int column) {
void void
screen_cursor_to_line(Screen *self, unsigned int line) { screen_cursor_to_line(Screen *self, unsigned int line) {
unsigned int y = MAX(line, 1) - 1; screen_cursor_position(self, line, self->cursor->x + 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);
}
} }
// }}} // }}}
@@ -852,14 +886,19 @@ void screen_erase_characters(Screen *self, unsigned int count) {
// Device control {{{ // 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 void
screen_bell(Screen UNUSED *self) { screen_bell(Screen UNUSED *self) {
FILE *f = fopen("/dev/tty", "w"); PyObject_CallMethod(self->callbacks, "bell", NULL);
static const char *bell = "\007"; if (PyErr_Occurred()) PyErr_Print();
if (f != NULL) { PyErr_Clear();
fwrite(bell, 1, 1, f);
fclose(f);
}
} }
static inline void static inline void
@@ -871,8 +910,17 @@ callback(const char *name, Screen *self, const char *data, unsigned int sz) {
} }
void void
report_device_attributes(Screen *self, unsigned int UNUSED mode, bool UNUSED secondary) { report_device_attributes(Screen *self, unsigned int mode, char start_modifier) {
callback("write_to_child", self, "\x1b[>1;4600;0c", 0); // same as libvte 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 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 void
screen_set_margins(Screen *self, unsigned int top, unsigned int bottom) { screen_set_margins(Screen *self, unsigned int top, unsigned int bottom) {
if (!top) top = 1; 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; } 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(in_bracketed_paste_mode, BRACKETED_PASTE)
MODE_GETSET(extended_keyboard, EXTENDED_KEYBOARD)
MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING) MODE_GETSET(focus_tracking_enabled, FOCUS_TRACKING)
MODE_GETSET(auto_repeat_enabled, DECARM) MODE_GETSET(auto_repeat_enabled, DECARM)
MODE_GETSET(cursor_visible, DECTCEM) MODE_GETSET(cursor_visible, DECTCEM)
MODE_GETSET(cursor_key_mode, DECCKM)
static PyObject* static PyObject*
mouse_tracking_mode(Screen *self) { mouse_tracking_mode(Screen *self) {
@@ -1247,12 +1325,22 @@ static PyMethodDef methods[] = {
static PyGetSetDef getsetters[] = { static PyGetSetDef getsetters[] = {
GETSET(in_bracketed_paste_mode) GETSET(in_bracketed_paste_mode)
GETSET(extended_keyboard)
GETSET(auto_repeat_enabled) GETSET(auto_repeat_enabled)
GETSET(focus_tracking_enabled) GETSET(focus_tracking_enabled)
GETSET(cursor_visible) GETSET(cursor_visible)
GETSET(cursor_key_mode)
{NULL} /* Sentinel */ {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[] = { static PyMemberDef members[] = {
{"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"}, {"callbacks", T_OBJECT_EX, offsetof(Screen, callbacks), 0, "callbacks"},
{"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"}, {"cursor", T_OBJECT_EX, offsetof(Screen, cursor), READONLY, "cursor"},
@@ -1262,6 +1350,11 @@ static PyMemberDef members[] = {
{"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"}, {"columns", T_UINT, offsetof(Screen, columns), READONLY, "columns"},
{"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"}, {"margin_top", T_UINT, offsetof(Screen, margin_top), READONLY, "margin_top"},
{"margin_bottom", T_UINT, offsetof(Screen, margin_bottom), READONLY, "margin_bottom"}, {"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} {NULL}
}; };

View File

@@ -157,6 +157,7 @@ class Sprites:
return glGenBuffers(1) return glGenBuffers(1)
def set_sprite_map(self, buf_id, data, usage=GL_STREAM_DRAW): def set_sprite_map(self, buf_id, data, usage=GL_STREAM_DRAW):
self.bind_sprite_map(buf_id)
glNamedBufferData(buf_id, sizeof(data), addressof(data), usage) glNamedBufferData(buf_id, sizeof(data), addressof(data), usage)
def bind_sprite_map(self, buf_id): def bind_sprite_map(self, buf_id):

View File

@@ -160,8 +160,8 @@ update_cell_range_data(ScreenModes *modes, SpriteMap *self, Line *line, unsigned
data[offset] = sp->x; data[offset] = sp->x;
data[offset+1] = sp->y; data[offset+1] = sp->y;
data[offset+2] = sp->z; data[offset+2] = sp->z;
data[offset+(reverse ? 4 : 3)] = to_color(color_profile, line->colors[i] & COL_MASK, default_fg); 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->colors[i] >> COL_SHIFT, default_bg); 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]); 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); data[offset+5] = (decoration_fg & COL_MASK) | (decoration << 24) | (strikethrough << 26);
previous_ch = ch; previous_width = (attrs) & WIDTH_MASK; previous_ch = ch; previous_width = (attrs) & WIDTH_MASK;

View File

@@ -12,7 +12,7 @@ from .config import build_ansi_color_table
from .constants import get_boss, appname, shell_path, cell_size, queue_action, viewport_size, WindowGeometry, GLuint 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 .fast_data_types import glfw_post_empty_event, Screen, DECAWM, DATA_CELL_SIZE, ColorProfile
from .char_grid import calculate_gl_geometry, render_cells from .char_grid import calculate_gl_geometry, render_cells
from .layout import all_layouts from .layout import all_layouts, Rect
from .utils import color_as_int from .utils import color_as_int
from .borders import Borders from .borders import Borders
from .window import Window from .window import Window
@@ -32,9 +32,12 @@ class Tab:
self.borders = Borders(opts) self.borders = Borders(opts)
self.windows = deque() self.windows = deque()
self.active_window_idx = 0 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: if session_tab is None:
self.cwd = args.directory self.cwd = args.directory
l = self.enabled_layouts[0] l = self.enabled_layouts[0]
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
if special_window is None: if special_window is None:
queue_action(self.new_window) queue_action(self.new_window)
else: else:
@@ -42,10 +45,8 @@ class Tab:
else: else:
self.cwd = session_tab.cwd or args.directory self.cwd = session_tab.cwd or args.directory
l = session_tab.layout l = session_tab.layout
self.current_layout = all_layouts[l](opts, self.borders.border_width, self.windows)
queue_action(self.startup, session_tab) 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): def startup(self, session_tab):
for cmd in session_tab.windows: for cmd in session_tab.windows:
@@ -79,7 +80,9 @@ class Tab:
self.relayout_borders() self.relayout_borders()
def relayout_borders(self): 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): def next_layout(self):
if len(self.opts.enabled_layouts) > 1: if len(self.opts.enabled_layouts) > 1:
@@ -200,15 +203,16 @@ class TabManager:
def __init__(self, opts, args, startup_session): def __init__(self, opts, args, startup_session):
self.opts, self.args = opts, args self.opts, self.args = opts, args
self.buffer_id = None self.buffer_id = None
self.tabbar_lock = Lock()
self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs] self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs]
self.cell_ranges = [] self.cell_ranges = []
self.active_tab_idx = startup_session.active_tab_idx self.active_tab_idx = startup_session.active_tab_idx
self.tabbar_lock = Lock()
self.tabbar_dirty = True self.tabbar_dirty = True
self.color_profile = ColorProfile() self.color_profile = ColorProfile()
self.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) self.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
self.default_fg = color_as_int(opts.inactive_tab_foreground) self.default_fg = color_as_int(opts.inactive_tab_foreground)
self.default_bg = color_as_int(opts.inactive_tab_background) self.default_bg = color_as_int(opts.inactive_tab_background)
self.tab_bar_blank_rects = ()
def as_rgb(x): def as_rgb(x):
return (x << 8) | 2 return (x << 8) | 2
@@ -216,12 +220,16 @@ class TabManager:
self.active_bg = as_rgb(color_as_int(opts.active_tab_background)) 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.active_fg = as_rgb(color_as_int(opts.active_tab_foreground))
self.can_render = False self.can_render = False
if len(self.tabs) > 1:
self.layout_tab_bar()
def resize(self, only_tabs=False): def resize(self, only_tabs=False):
if not only_tabs:
self.layout_tab_bar()
for tab in self.tabs: for tab in self.tabs:
tab.relayout() tab.relayout()
if only_tabs:
return def layout_tab_bar(self):
self.can_render = False self.can_render = False
ncells = viewport_size.width // cell_size.width ncells = viewport_size.width // cell_size.width
s = Screen(None, 1, ncells) s = Screen(None, 1, ncells)
@@ -232,9 +240,13 @@ class TabManager:
self.tab_bar_screen = s self.tab_bar_screen = s
self.tabbar_dirty = True self.tabbar_dirty = True
margin = (viewport_size.width - ncells * cell_size.width) // 2 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) 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.screen = s
self.can_render = True self.can_render = True
@@ -254,10 +266,6 @@ class TabManager:
def __len__(self): def __len__(self):
return len(self.tabs) 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 @property
def active_tab(self): def active_tab(self):
return self.tabs[self.active_tab_idx] if self.tabs else None return self.tabs[self.active_tab_idx] if self.tabs else None
@@ -278,6 +286,16 @@ class TabManager:
with self.tabbar_lock: with self.tabbar_lock:
self.tabbar_dirty = True 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): def remove(self, tab):
' Must be called in the GUI thread ' ' Must be called in the GUI thread '
needs_resize = len(self.tabs) == 2 needs_resize = len(self.tabs) == 2
@@ -311,9 +329,10 @@ class TabManager:
s.cursor.bold = s.cursor.italic = False s.cursor.bold = s.cursor.italic = False
s.cursor.fg = s.cursor.bg = 0 s.cursor.fg = s.cursor.bg = 0
s.draw('') 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('') s.draw('')
break break
s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab
s.update_cell_data( s.update_cell_data(
sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True) sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True)
sprites.render_dirty_cells() sprites.render_dirty_cells()
@@ -328,6 +347,12 @@ class TabManager:
queue_action(self.set_active_tab, i) queue_action(self.set_active_tab, i)
return return
@property
def blank_rects(self):
if len(self.tabs) < 2:
return ()
return self.tab_bar_blank_rects
def render(self, cell_program, sprites): def render(self, cell_program, sprites):
if not self.can_render or len(self.tabs) < 2: if not self.can_render or len(self.tabs) < 2:
return return

View File

@@ -92,15 +92,15 @@ string_capabilities = {
# Make cursor appear normal # Make cursor appear normal
'cnorm': r'\E[?12l\E[?25h', 'cnorm': r'\E[?12l\E[?25h',
# Carriage return # Carriage return
'cr': r'^M', 'cr': r'^M', # CR (carriage return \r)
# Change scroll region # Change scroll region
'csr': r'\E[%i%p1%d;%p2%dr', 'csr': r'\E[%i%p1%d;%p2%dr',
# Move cursor to the left by the specified amount # Move cursor to the left by the specified amount
'cub': r'\E[%p1%dD', 'cub': r'\E[%p1%dD',
'cub1': r'^H', 'cub1': r'^H', # BS (backspace)
# Move cursor down specified number of lines # Move cursor down specified number of lines
'cud': r'\E[%p1%dB', 'cud': r'\E[%p1%dB',
'cud1': r'^J', 'cud1': r'^J', # LF (line-feed \n)
# Move cursor to the right by the specified amount # Move cursor to the right by the specified amount
'cuf': r'\E[%p1%dC', 'cuf': r'\E[%p1%dC',
'cuf1': r'\E[C', 'cuf1': r'\E[C',
@@ -203,6 +203,8 @@ string_capabilities = {
'rmcup': r'\E[?1049l', 'rmcup': r'\E[?1049l',
# Exit insert mode # Exit insert mode
'rmir': r'\E[4l', 'rmir': r'\E[4l',
# Exit application keypad mode
'rmkx': r'\E[?1l',
# Exit standout mode # Exit standout mode
'rmso': r'\E[27m', 'rmso': r'\E[27m',
# Exit underline mode # Exit underline mode
@@ -227,6 +229,8 @@ string_capabilities = {
'smcup': r'\E[?1049h', 'smcup': r'\E[?1049h',
# Enster insert mode # Enster insert mode
'smir': r'\E[4h', 'smir': r'\E[4h',
# Enter application keymap mode
'smkx': r'\E[?1h',
# Enter standout mode # Enter standout mode
'smso': r'\E[7m', 'smso': r'\E[7m',
# Enter underline mode # Enter underline mode
@@ -373,6 +377,8 @@ termcap_aliases.update({
'ZR': 'ritm', 'ZR': 'ritm',
'as': 'smacs', 'as': 'smacs',
'ae': 'rmacs', 'ae': 'rmacs',
'ks': 'smkx',
'ke': 'rmkx',
'#2': 'kHOM', '#2': 'kHOM',
'#4': 'kLFT', '#4': 'kLFT',
'*7': 'kEND', '*7': 'kEND',

View File

@@ -2,19 +2,20 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import re
import os
import signal
import shlex
import subprocess
import math import math
from collections import namedtuple import os
import re
import shlex
import signal
import string
import subprocess
from contextlib import contextmanager from contextlib import contextmanager
from functools import lru_cache from functools import lru_cache
from time import monotonic from time import monotonic
from .constants import isosx 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): def safe_print(*a, **k):
@@ -36,6 +37,13 @@ def wcwidth(c: str) -> int:
return wcwidth_impl(ord(c[0])) 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 @contextmanager
def timeit(name, do_timing=False): def timeit(name, do_timing=False):
if do_timing: if do_timing:
@@ -49,15 +57,71 @@ def sanitize_title(x):
return re.sub(r'\s+', ' ', re.sub(r'[\0-\x19]', '', 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(): def get_logical_dpi():
if not hasattr(get_logical_dpi, 'ans'): if not hasattr(get_logical_dpi, 'ans'):
if isosx: if isosx:
# TODO: Investigate if this needs a different implementation on OS X # TODO: Investigate if this needs a different implementation on OS X
get_logical_dpi.ans = glfw_get_physical_dpi() get_logical_dpi.ans = glfw_get_physical_dpi()
else: else:
raw = subprocess.check_output(['xdpyinfo']).decode('utf-8') # See https://github.com/glfw/glfw/issues/1019 for why we cant use
m = re.search(r'^\s*resolution:\s*(\d+)+x(\d+)', raw, flags=re.MULTILINE) # glfw_get_physical_dpi()
get_logical_dpi.ans = int(m.group(1)), int(m.group(2)) 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 return get_logical_dpi.ans
@@ -67,190 +131,15 @@ def get_dpi():
get_dpi.ans = {'physical': pdpi, 'logical': get_logical_dpi()} get_dpi.ans = {'physical': pdpi, 'logical': get_logical_dpi()}
return get_dpi.ans 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): def color_as_int(val):
return val[0] << 16 | val[1] << 8 | val[2] 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): def parse_color_set(raw):
parts = raw.split(';') parts = raw.split(';')
for c, spec in [parts[i:i + 2] for i in range(0, len(parts), 2)]: 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 return '' # There is no primary selection on OS X
# glfw has no way to get the primary selection # glfw has no way to get the primary selection
# https://github.com/glfw/glfw/issues/894 # 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): def set_primary_selection(text):
@@ -300,7 +203,7 @@ def set_primary_selection(text):
return # There is no primary selection on OS X return # There is no primary selection on OS X
if isinstance(text, str): if isinstance(text, str):
text = text.encode('utf-8') 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.stdin.write(text), p.stdin.close()
p.wait() p.wait()
@@ -312,3 +215,14 @@ def open_url(url, program='default'):
cmd = shlex.split(program) cmd = shlex.split(program)
cmd.append(url) cmd.append(url)
subprocess.Popen(cmd).wait() 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)

View File

@@ -8,7 +8,7 @@ from collections import deque
from functools import partial from functools import partial
from time import monotonic 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 .constants import wakeup, get_boss, appname, WindowGeometry, is_key_pressed, mouse_button_pressed, cell_size
from .fast_data_types import ( from .fast_data_types import (
BRACKETED_PASTE_START, BRACKETED_PASTE_END, Screen, read_bytes_dump, 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_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 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 .mouse import encode_mouse_event, PRESS, RELEASE, MOVE, DRAG
from .terminfo import get_capabilities from .terminfo import get_capabilities
from .utils import sanitize_title, get_primary_selection, parse_color_set, safe_print 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: class Window:
def __init__(self, tab, child, opts, args): def __init__(self, tab, child, opts, args):
global dump_bytes_opened
self.tabref = weakref.ref(tab) self.tabref = weakref.ref(tab)
self.override_title = None self.override_title = None
self.last_mouse_cursor_pos = 0, 0 self.last_mouse_cursor_pos = 0, 0
@@ -37,8 +48,13 @@ class Window:
self._is_visible_in_layout = True self._is_visible_in_layout = True
self.child, self.opts = child, opts self.child, self.opts = child, opts
self.child_fd = child.child_fd self.child_fd = child.child_fd
self.start_visual_bell_at = None
self.screen = Screen(self, 24, 80, opts.scrollback_lines) 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.draw_dump_buf = []
self.write_buf = memoryview(b'') self.write_buf = memoryview(b'')
self.char_grid = CharGrid(self.screen, opts) self.char_grid = CharGrid(self.screen, opts)
@@ -106,6 +122,22 @@ class Window:
self.write_buf = memoryview(self.write_buf.tobytes() + data) self.write_buf = memoryview(self.write_buf.tobytes() + data)
wakeup() 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): def update_screen(self):
self.char_grid.update_cell_data() self.char_grid.update_cell_data()
glfw_post_empty_event() glfw_post_empty_event()
@@ -130,12 +162,11 @@ class Window:
pass # TODO: Implement this pass # TODO: Implement this
def set_dynamic_color(self, code, value): def set_dynamic_color(self, code, value):
wmap = {10: 'fg', 11: 'bg', 110: 'fg', 111: 'bg'}
if isinstance(value, bytes): if isinstance(value, bytes):
value = value.decode('utf-8') value = value.decode('utf-8')
color_changes = {} color_changes = {}
for val in value.split(';'): for val in value.split(';'):
w = wmap.get(code) w = DYNAMIC_COLOR_CODES.get(code)
if w is not None: if w is not None:
if code >= 110: if code >= 110:
val = None val = None
@@ -210,7 +241,7 @@ class Window:
x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top) x, y = max(0, x - self.geometry.left), max(0, y - self.geometry.top)
self.last_mouse_cursor_pos = x, y self.last_mouse_cursor_pos = x, y
tm = get_boss() 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: if send_event:
x, y = self.char_grid.cell_for_pos(x, y) x, y = self.char_grid.cell_for_pos(x, y)
if x is not None: if x is not None:
@@ -254,12 +285,20 @@ class Window:
if ev: if ev:
self.write_to_child(ev) self.write_to_child(ev)
else: 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)) self.write_to_child(k * abs(s))
def buf_toggled(self, is_main_linebuf): def buf_toggled(self, is_main_linebuf):
self.char_grid.scroll('full', False) 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 {{{ # actions {{{
def show_scrollback(self): def show_scrollback(self):
@@ -327,6 +366,9 @@ class Window:
self.draw_dump_buf = [] self.draw_dump_buf = []
else: else:
self.draw_dump_buf.append(a[1]) self.draw_dump_buf.append(a[1])
elif a[0] == 'bytes':
self.dump_bytes_to.write(a[1])
self.dump_bytes_to.flush()
else: else:
if self.draw_dump_buf: if self.draw_dump_buf:
safe_print('draw', ''.join(self.draw_dump_buf)) safe_print('draw', ''.join(self.draw_dump_buf))

View File

@@ -33,10 +33,14 @@ class Callbacks:
def buf_toggled(self, is_alt): def buf_toggled(self, is_alt):
self.is_alt = is_alt self.is_alt = is_alt
def use_utf8(self, on):
self.iutf8 = on
def clear(self): def clear(self):
self.wtcbuf = b'' self.wtcbuf = b''
self.iconbuf = self.titlebuf = self.colorbuf = self.qbuf = self.ctbuf = '' self.iconbuf = self.titlebuf = self.colorbuf = self.qbuf = self.ctbuf = ''
self.is_alt = False self.is_alt = False
self.iutf8 = True
def filled_line_buf(ynum=5, xnum=5, cursor=Cursor()): def filled_line_buf(ynum=5, xnum=5, cursor=Cursor()):

81
kitty_tests/keys.py Normal file
View 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)

View File

@@ -72,11 +72,17 @@ class TestParser(BaseTest):
self.ae(str(s.line(0)), '123 ') self.ae(str(s.line(0)), '123 ')
def test_charsets(self): 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() s = self.create_screen()
pb = partial(self.parse_bytes_dump, s) pb = partial(self.parse_bytes_dump, s)
pb('\033)0\x0e/_', ('screen_designate_charset', 1, ord('0')), ('screen_change_charset', 1), '/_') pb('\033)0\x0e/_', ('screen_designate_charset', 1, ord('0')), ('screen_change_charset', 1), '/_')
self.ae(str(s.line(0)), '/\xa0 ') 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() s = self.create_screen()
pb = partial(self.parse_bytes_dump, s) pb = partial(self.parse_bytes_dump, s)
pb('\033(0/_', ('screen_designate_charset', 0, ord('0')), '/_') pb('\033(0/_', ('screen_designate_charset', 0, ord('0')), '/_')
@@ -132,6 +138,18 @@ class TestParser(BaseTest):
c.clear() c.clear()
pb('\033[6n', ('report_device_status', 6, 0)) pb('\033[6n', ('report_device_status', 6, 0))
self.ae(c.wtcbuf, b'\033[2;1R') 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)) pb('\033[2;4r', ('screen_set_margins', 2, 4))
self.ae(s.margin_top, 1), self.ae(s.margin_bottom, 3) self.ae(s.margin_top, 1), self.ae(s.margin_bottom, 3)
pb('\033[r', ('screen_set_margins', 0, 0)) pb('\033[r', ('screen_set_margins', 0, 0))
@@ -144,9 +162,9 @@ class TestParser(BaseTest):
s = self.create_screen() s = self.create_screen()
pb = partial(self.parse_bytes_dump, s) pb = partial(self.parse_bytes_dump, s)
c = s.callbacks 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(str(s.line(0)), 'abcde')
self.ae(c.titlebuf, 'xyz') self.ae(c.titlebuf, 'x\\ryz')
c.clear() c.clear()
pb('\033]\x07', ('set_title', ''), ('set_icon', '')) pb('\033]\x07', ('set_title', ''), ('set_icon', ''))
self.ae(c.titlebuf, ''), self.ae(c.iconbuf, '') self.ae(c.titlebuf, ''), self.ae(c.iconbuf, '')
@@ -155,7 +173,7 @@ class TestParser(BaseTest):
c.clear() c.clear()
pb('\033]2;;;;\x07', ('set_title', ';;;')) pb('\033]2;;;;\x07', ('set_title', ';;;'))
self.ae(c.titlebuf, ';;;') self.ae(c.titlebuf, ';;;')
pb('\033]110\x07', ('set_dynamic_color', '')) pb('\033]110\x07', ('set_dynamic_color', 110, ''))
self.ae(c.colorbuf, '') self.ae(c.colorbuf, '')
def test_dcs_codes(self): def test_dcs_codes(self):
@@ -169,4 +187,4 @@ class TestParser(BaseTest):
pb = partial(self.parse_bytes_dump, s) pb = partial(self.parse_bytes_dump, s)
for prefix in '\033_', '\033^', '\u009e', '\u009f': for prefix in '\033_', '\033^', '\u009e', '\u009f':
for suffix in '\u009c', '\033\\': for suffix in '\u009c', '\033\\':
pb('a{}+++{}bcde'.format(prefix, suffix), 'abcde') pb('a{}+\\++{}bcde'.format(prefix, suffix), 'abcde')

View File

@@ -2,14 +2,15 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import json
import os import os
import re import re
import subprocess
from collections import defaultdict from collections import defaultdict
base = os.path.dirname(os.path.abspath(__file__)) base = os.path.dirname(os.path.abspath(__file__))
os.chdir(base) os.chdir(base)
defns = defaultdict(list) defns = defaultdict(list)
for line in open('kitty/kitty.conf'): for line in open('kitty/kitty.conf'):
@@ -17,13 +18,35 @@ for line in open('kitty/kitty.conf'):
_, sc, name = line.split(maxsplit=3) _, sc, name = line.split(maxsplit=3)
defns[name].append('`' + sc + '`') 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) defns = '\n'.join(defns)
raw = open('README.asciidoc').read() raw = open('README.asciidoc').read()
pat = re.compile(r'^// START_SHORTCUT_BLOCK$.+?^// END_SHORTCUT_BLOCK$', re.M | re.DOTALL) pat = re.compile(
nraw = pat.sub('// START_SHORTCUT_BLOCK\n' + r'^// START_SHORTCUT_BLOCK$.+?^// END_SHORTCUT_BLOCK$', re.M | re.DOTALL
defns + '\n// END_SHORTCUT_BLOCK', raw) )
nraw = pat.sub(
'// START_SHORTCUT_BLOCK\n' + defns + '\n// END_SHORTCUT_BLOCK', raw
)
if raw != nraw: if raw != nraw:
print('Updating shortcuts block') print('Updating shortcuts block')
open('README.asciidoc', 'w').write(nraw) 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)

View File

@@ -5,7 +5,19 @@
kitty has a few extensions to the xterm protocol, to enable advanced features. 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 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 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::[] 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 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) 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 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): 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 <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 == Graphics rendering
@@ -54,6 +80,8 @@ emulator. The major design goals are
the existing cursor based protocols. the existing cursor based protocols.
* Should use optimizations when the client is running on the same computer as the terminal emulator. * 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 === 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 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) | 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.

View File

@@ -65,8 +65,10 @@ class ReadFileWithProgressReporting(io.BufferedReader): # {{{
return data return data
def report_progress(self, size): def report_progress(self, size):
sys.stdout.buffer.write(b'\x1b[s') def write(*args):
sys.stdout.buffer.write(b'\x1b[K') print(*args, end='')
write('\x1b[s\x1b[K')
frac = float(self.tell()) / self._total frac = float(self.tell()) / self._total
mb_pos = self.tell() / float(1024**2) mb_pos = self.tell() / float(1024**2)
mb_tot = self._total / float(1024**2) mb_tot = self._total / float(1024**2)
@@ -75,14 +77,13 @@ class ReadFileWithProgressReporting(io.BufferedReader): # {{{
bit_rate = kb_rate * 1024 bit_rate = kb_rate * 1024
eta = int((self._total - self.tell()) / bit_rate) + 1 eta = int((self._total - self.tell()) / bit_rate) + 1
eta_m, eta_s = eta / 60, eta % 60 eta_m, eta_s = eta / 60, eta % 60
sys.stdout.write( write(
' %.1f%% %.1f/%.1fMB %.1f KB/sec %d minutes, %d seconds left' ' %.1f%% %.1f/%.1fMB %.1f KB/sec %d minutes, %d seconds left'
% (frac * 100, mb_pos, mb_tot, kb_rate, eta_m, eta_s)) % (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: if self.tell() >= self._total:
sys.stdout.write('\n')
t = int(time.time() - self.start_time) + 1 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)) (t / 60, t % 60, kb_rate))
sys.stdout.flush() sys.stdout.flush()
@@ -202,7 +203,7 @@ class GitHub(Base): # {{{
def fail(self, r, msg): def fail(self, r, msg):
print(msg, ' Status Code: %s' % r.status_code, file=sys.stderr) print(msg, ' Status Code: %s' % r.status_code, file=sys.stderr)
print("JSON from response:", 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) raise SystemExit(1)
def already_exists(self, r): def already_exists(self, r):
@@ -253,7 +254,7 @@ class GitHub(Base): # {{{
def get_github_data(): 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(':') un, pw = f.read().strip().split(':')
return {'username': un, 'password': pw} return {'username': un, 'password': pw}
@@ -263,14 +264,13 @@ def run_upload(args):
os.path.join(build_path, 'build', f.format(version)): desc os.path.join(build_path, 'build', f.format(version)): desc
for f, desc in { for f, desc in {
'osx/dist/kitty-{}.dmg': 'macOS dmg', 'osx/dist/kitty-{}.dmg': 'macOS dmg',
} }.items()
} }
for f in files: for f in files:
if not os.path.exists(f): if not os.path.exists(f):
raise SystemExit('The installer {} does not exist'.format(f)) raise SystemExit('The installer {} does not exist'.format(f))
gd = get_github_data() gd = get_github_data()
gh = GitHub(files, appname, version, 'kovidgoyal', gd['username'], gh = GitHub(files, appname, version, gd['username'], gd['password'])
gd['password'])
gh() gh()

View File

@@ -1,3 +1,14 @@
[flake8] [flake8]
max-line-length = 160 max-line-length = 160
exclude==template.py,linux-package 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
View File

@@ -17,18 +17,32 @@ constants = os.path.join(base, 'kitty', 'constants.py')
with open(constants, 'rb') as f: with open(constants, 'rb') as f:
constants = f.read().decode('utf-8') constants = f.read().decode('utf-8')
appname = re.search(r"^appname = '([^']+)'", constants, re.MULTILINE).group(1) 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() _plat = sys.platform.lower()
isosx = 'darwin' in _plat isosx = 'darwin' in _plat
is_travis = os.environ.get('TRAVIS') == 'true' is_travis = os.environ.get('TRAVIS') == 'true'
cflags = ldflags = cc = ldpaths = None cflags = ldflags = cc = ldpaths = None
PKGCONFIG = os.environ.get('PKGCONFIG_EXE', 'pkg-config') PKGCONFIG = os.environ.get('PKGCONFIG_EXE', 'pkg-config')
def pkg_config(pkg, *args): 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(): def cc_version():
@@ -39,11 +53,13 @@ def cc_version():
ver = tuple(map(int, ver)) ver = tuple(map(int, ver))
except Exception: except Exception:
ver = (0, 0) ver = (0, 0)
return ver return cc, ver
def get_python_flags(cflags): 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 = []
libs += sysconfig.get_config_var('LIBS').split() libs += sysconfig.get_config_var('LIBS').split()
libs += sysconfig.get_config_var('SYSLIBS').split() libs += sysconfig.get_config_var('SYSLIBS').split()
@@ -53,72 +69,104 @@ def get_python_flags(cflags):
val = sysconfig.get_path(var) val = sysconfig.get_path(var)
if val and '/{}.framework'.format(fw) in val: if val and '/{}.framework'.format(fw) in val:
fdir = val[:val.index('/{}.framework'.format(fw))] 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 framework_dir = fdir
break break
else: else:
raise SystemExit('Failed to find Python framework') 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: else:
libs += ['-L' + sysconfig.get_config_var('LIBDIR')] 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() libs += sysconfig.get_config_var('LINKFORSHARED').split()
return libs 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 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' stack_protector = '-fstack-protector'
if ccver >= (4, 9): if ccver >= (4, 9) and cc == 'gcc':
stack_protector += '-strong' stack_protector += '-strong'
missing_braces = '' missing_braces = ''
if ccver < (5, 2): if ccver < (5, 2) and cc == 'gcc':
missing_braces = '-Wno-missing-braces' missing_braces = '-Wno-missing-braces'
cc = os.environ.get('CC', 'gcc') optimize = '-ggdb' if debug or sanitize else '-O3'
optimize = '-O3' sanitize_args = get_sanitize_args(cc, ccver) if sanitize else set()
if debug or asan: cflags = os.environ.get(
optimize = '-ggdb' 'OVERRIDE_CFLAGS', (
if asan: '-Wextra -Wno-missing-field-initializers -Wall -std=c99 -D_XOPEN_SOURCE=700'
optimize += ' -fsanitize=address -fno-omit-frame-pointer' ' -pedantic-errors -Werror {} {} -D{}DEBUG -fwrapv {} {} -pipe {} -fvisibility=hidden'
cflags = os.environ.get('OVERRIDE_CFLAGS', ( ).format(
'-Wextra -Wno-missing-field-initializers -Wall -std=c99 -D_XOPEN_SOURCE=700' optimize, ' '.join(sanitize_args), ('' if debug else 'N'), stack_protector, missing_braces, '-march=native'
' -pedantic-errors -Werror {} -DNDEBUG -fwrapv {} {} -pipe').format(optimize, stack_protector, missing_braces)) if native_optimizations else ''
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'))) 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) ldflags = shlex.split(ldflags)
cflags += shlex.split(os.environ.get('CFLAGS', '')) cflags += shlex.split(os.environ.get('CFLAGS', ''))
ldflags += shlex.split(os.environ.get('LDFLAGS', '')) ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
cflags.append('-pthread') 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: 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()) major = int(re.match(r'\d+', ver).group())
except Exception: except Exception:
ver = 'not found' ver = 'not found'
major = 0 major = 0
if major < 2: 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: if not isosx:
cflags.extend(pkg_config('glew', '--cflags-only-I')) cflags.extend(pkg_config('glew', '--cflags-only-I'))
if isosx: if isosx:
font_libs = ['-framework', 'CoreText', '-framework', 'CoreGraphics'] font_libs = ['-framework', 'CoreText', '-framework', 'CoreGraphics']
else: else:
cflags.extend(pkg_config('freetype2', '--cflags-only-I')) cflags.extend(pkg_config('fontconfig', '--cflags-only-I'))
font_libs = pkg_config('freetype2', '--libs') font_libs = pkg_config('fontconfig', '--libs')
cflags.extend(pkg_config('glfw3', '--cflags-only-I')) cflags.extend(pkg_config('glfw3', '--cflags-only-I'))
ldflags.append('-shared') ldflags.append('-shared')
pylib = get_python_flags(cflags) pylib = get_python_flags(cflags)
if isosx: if isosx:
glfw_ldflags = pkg_config('--libs', '--static', 'glfw3') + ['-framework', 'OpenGL'] glfw_ldflags = pkg_config('--libs', '--static', 'glfw3'
) + ['-framework', 'OpenGL']
glew_libs = [] glew_libs = []
else: else:
glfw_ldflags = pkg_config('glfw3', '--libs') glfw_ldflags = pkg_config('glfw3', '--libs')
glew_libs = pkg_config('glew', '--libs') glew_libs = pkg_config('glew', '--libs')
ldpaths = pylib + \ ldpaths = pylib + glew_libs + font_libs + glfw_ldflags
glew_libs + font_libs + glfw_ldflags
try: try:
os.mkdir(build_dir) os.mkdir(build_dir)
@@ -158,7 +206,10 @@ def newer(dest, *sources):
def compile_c_extension(module, incremental, sources, headers): def compile_c_extension(module, incremental, sources, headers):
prefix = os.path.basename(module) 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): for src, dest in zip(sources, objects):
cflgs = cflags[:] cflgs = cflags[:]
@@ -176,42 +227,80 @@ def compile_c_extension(module, incremental, sources, headers):
def option_parser(): def option_parser():
p = argparse.ArgumentParser() 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(
p.add_argument('--debug', default=False, action='store_true', 'action',
help='Build extension modules with debugging symbols') nargs='?',
p.add_argument('--asan', default=False, action='store_true', default='build',
help='Turn on address sanitization to detect memory access errors. Note that if you do turn it on,' choices='build test linux-package osx-bundle'.split(),
' you have to run kitty with the environment variable LD_PRELOAD=/usr/lib/libasan.so') help='Action to perform (default is build)'
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(
'--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 return p
def find_c_files(): def find_c_files():
ans, headers = [], [] ans, headers = [], []
d = os.path.join(base, 'kitty') 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): for x in os.listdir(d):
ext = os.path.splitext(x)[1] ext = os.path.splitext(x)[1]
if ext in ('.c', '.m') and os.path.basename(x) not in exclude: if ext in ('.c', '.m') and os.path.basename(x) not in exclude:
ans.append(os.path.join('kitty', x)) ans.append(os.path.join('kitty', x))
elif ext == '.h': elif ext == '.h':
headers.append(os.path.join('kitty', x)) 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') ans.append('kitty/parser_dump.c')
return tuple(ans), tuple(headers) return tuple(ans), tuple(headers)
def build(args): def build(args, native_optimizations=True):
init_env(args.debug, args.asan) init_env(args.debug, args.sanitize, native_optimizations)
compile_c_extension('kitty/fast_data_types', args.incremental, *find_c_files()) compile_c_extension(
'kitty/fast_data_types', args.incremental, *find_c_files()
)
def safe_makedirs(path): def safe_makedirs(path):
try: os.makedirs(path, exist_ok=True)
os.makedirs(path)
except FileExistsError:
pass 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): # {{{ 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')) shutil.copy2('logo/kitty.rgba', os.path.join(libdir, 'logo'))
def src_ignore(parent, entries): 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')
]
shutil.copytree('kitty', os.path.join(libdir, 'kitty'), ignore=src_ignore) shutil.copytree('kitty', os.path.join(libdir, 'kitty'), ignore=src_ignore)
import compileall import compileall
@@ -239,12 +331,14 @@ def package(args, for_bundle=False): # {{{
os.chmod(path, 0o755 if f.endswith('.so') else 0o644) os.chmod(path, 0o755 if f.endswith('.so') else 0o644)
launcher_dir = os.path.join(ddir, 'bin') launcher_dir = os.path.join(ddir, 'bin')
safe_makedirs(launcher_dir) safe_makedirs(launcher_dir)
cflags = '-O3 -Wall -Werror'.split() cflags = '-O3 -Wall -Werror -fpie'.split()
if for_bundle: if for_bundle:
cflags.append('-DFOR_BUNDLE') cflags.append('-DFOR_BUNDLE')
cflags.append('-DPYVER="{}"'.format(sysconfig.get_python_version())) cflags.append('-DPYVER="{}"'.format(sysconfig.get_python_version()))
pylib = get_python_flags(cflags) 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) run_tool(cmd)
if not isosx: # {{{ linux desktop gunk if not isosx: # {{{ linux desktop gunk
icdir = os.path.join(ddir, 'share', 'icons', 'hicolor', '256x256') 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') deskdir = os.path.join(ddir, 'share', 'applications')
safe_makedirs(deskdir) safe_makedirs(deskdir)
with open(os.path.join(deskdir, 'kitty.desktop'), 'w') as f: with open(os.path.join(deskdir, 'kitty.desktop'), 'w') as f:
f.write('''\ f.write(
'''\
[Desktop Entry] [Desktop Entry]
Version=1.0 Version=1.0
Type=Application Type=Application
@@ -264,7 +359,8 @@ TryExec=kitty
Exec=kitty Exec=kitty
Icon=kitty Icon=kitty
Categories=System; Categories=System;
''') '''
)
# }}} # }}}
if for_bundle: # OS X bundle gunk {{{ if for_bundle: # OS X bundle gunk {{{
@@ -275,7 +371,7 @@ Categories=System;
os.rename('../bin', 'MacOS') os.rename('../bin', 'MacOS')
os.rename('../lib', 'Frameworks') os.rename('../lib', 'Frameworks')
# }}} # }}}
# }}} # }}}
def main(): def main():
@@ -286,13 +382,16 @@ def main():
os.chdir(os.path.dirname(os.path.abspath(__file__))) os.chdir(os.path.dirname(os.path.abspath(__file__)))
if args.action == 'build': if args.action == 'build':
build(args) build(args)
build_test_launcher(args)
elif args.action == 'test': 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': elif args.action == 'linux-package':
build(args) build(args, native_optimizations=False)
package(args) package(args)
elif args.action == 'osx-bundle': elif args.action == 'osx-bundle':
build(args) build(args, native_optimizations=False)
package(args, for_bundle=True) package(args, for_bundle=True)

Binary file not shown.

View File

@@ -101,6 +101,7 @@ xterm-kitty|KovIdTTY,
rmam=\E[?7l, rmam=\E[?7l,
rmcup=\E[?1049l, rmcup=\E[?1049l,
rmir=\E[4l, rmir=\E[4l,
rmkx=\E[?1l,
rmso=\E[27m, rmso=\E[27m,
rmul=\E[24m, rmul=\E[24m,
rs1=\Ec, rs1=\Ec,
@@ -114,6 +115,7 @@ xterm-kitty|KovIdTTY,
smam=\E[?7h, smam=\E[?7h,
smcup=\E[?1049h, smcup=\E[?1049h,
smir=\E[4h, smir=\E[4h,
smkx=\E[?1h,
smso=\E[7m, smso=\E[7m,
smul=\E[4m, smul=\E[4m,
tbc=\E[3g, tbc=\E[3g,

Binary file not shown.

13
test-launcher.c Normal file
View 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);
}