Compare commits

...

146 Commits

Author SHA1 Message Date
Kovid Goyal
627b91ffba version 0.7.0 2018-01-24 15:33:16 +05:30
Kovid Goyal
9ad65f2e1d Cleanup layouts patch
Fix bottom rect in horizontal layout not covering all windows. Fix pep8
issues. Add README and CHANGELOG entries.
2018-01-23 19:35:30 +05:30
Guybrush Threepwood
a6a606ff3d Added vertical and horizontal layouts 2018-01-23 19:30:41 +05:30
Kovid Goyal
63317f9c78 macOS: Fix alt+arrow keys not working when disabling the macos_option_as_alt config option. 2018-01-21 15:41:56 +05:30
Kovid Goyal
46d9dfb614 ... 2018-01-20 17:33:55 +05:30
Kovid Goyal
be3b33af06 Update CHANGELOG 2018-01-19 17:08:21 +05:30
Kovid Goyal
04e1b8b1ae tab bar blank rects dont extend down enough 2018-01-19 17:05:18 +05:30
Kovid Goyal
213768b6e0 Option to place tab bar at the top edge
Fixes #288
2018-01-19 17:04:58 +05:30
Kovid Goyal
aff1abdb26 Fix top line of hollow block cursor 1px too high 2018-01-19 16:23:47 +05:30
Kovid Goyal
44a413df81 Fix blank rects on the sides of the tab bar not being drawn 2018-01-19 16:02:07 +05:30
Kovid Goyal
4f12c91fff Use the new region function everywhere
Centralizes window layout calculation and no longer assumes
tab bar geometry
2018-01-19 15:56:07 +05:30
Kovid Goyal
b6188bf436 Move window region calculations into one place 2018-01-19 11:31:44 +05:30
Kovid Goyal
b9a14e0e4a A Makefile target for building kitty.app 2018-01-19 10:42:12 +05:30
Kovid Goyal
6882c23f43 Update CHANGELOG 2018-01-18 16:26:33 +05:30
Kovid Goyal
80301d465b Handle non-BMP combining characters
Use a level of indirection to store combining characters. This allows
combining characters to be stored using only two bytes, even if they are
after USHORT_MAX
2018-01-18 16:25:42 +05:30
Kovid Goyal
32632264ee Mapping that can be used to store unicode mark symbols in only two bytes 2018-01-18 16:06:07 +05:30
Kovid Goyal
409bd37db5 Store combining chars as an array in the Cell
Makes it easier to increase the number of combining chars if needed,
rather than storing them as a mask.
2018-01-18 14:10:58 +05:30
Kovid Goyal
5fd4bc965f Annotate Cell structure
Make it clear what parts are needed only on the CPU
2018-01-18 12:47:49 +05:30
Kovid Goyal
e86e5ce1d8 ... 2018-01-18 00:15:39 +05:30
Kovid Goyal
5faa649452 Drop the dependency on libunistring 2018-01-18 00:09:40 +05:30
Kovid Goyal
2ddc7e8c80 Have all marks return a wcwidth of -1
Mirrors recent change to onsidering all marks combining chars instead of
only the marks with a non-zero combining class
2018-01-17 23:25:59 +05:30
Kovid Goyal
ed700ff830 ... 2018-01-17 21:59:10 +05:30
Kovid Goyal
33ed873997 Remove unnecessary extra test for combinig characters
There are not combining characters with a non-zero combining class that
are not in the marks category.
2018-01-17 21:56:30 +05:30
Kovid Goyal
9125c00f96 Update CHANGELOG 2018-01-17 21:50:00 +05:30
Kovid Goyal
73fe3293be Fix copying from selection being restricted to only the visible part of the selection. Fixes #287 2018-01-17 21:39:24 +05:30
Kovid Goyal
804c4fbe19 Recognize characters from the unicode Mark categories as combining characters, even if they do not have a combining class (i.e. are not re-ordered). Fixes #286 2018-01-15 11:24:11 +05:30
Kovid Goyal
d6c82ba21c oops 2018-01-14 11:18:44 +05:30
Kovid Goyal
67308f2ca1 Add some documentation for the shaping algorithm 2018-01-14 11:18:44 +05:30
Kovid Goyal
4a17a946fb Ensure groups dont have too many cells 2018-01-14 11:18:44 +05:30
Kovid Goyal
1ae7ae4a1d Fix consecutive separate ligatures being rendered in the same group
Rendering multiple ligatures in the smae group is bad for performance
and also causes incorrect rendering if the last ligature in the group
does not fit into the number of cells in the group.
2018-01-14 11:18:44 +05:30
Kovid Goyal
404ca23de3 Make handling of last glyph a little more intelligent 2018-01-14 11:18:44 +05:30
Kovid Goyal
d385952fef Add a bounds check 2018-01-14 11:18:44 +05:30
Kovid Goyal
f03cac8a9d Allow storing more flags in the special glyph cache 2018-01-14 11:18:44 +05:30
Kovid Goyal
4b501fd694 Fix rendering of ligatures with ligature fonts that dont use dummy glyphs 2018-01-14 11:18:43 +05:30
Kovid Goyal
872d39eb2c Have the ctrl+shift+<key> generate the ASCII C0 control codes for the few shifted control codes there are.
See #285
2018-01-14 11:18:30 +05:30
Kovid Goyal
0341cae922 Fix unused function warning 2018-01-13 15:49:44 +05:30
Kovid Goyal
5357a9a1cc Merge branch 'us/master' of https://github.com/koekeishiya/kitty 2018-01-13 07:33:30 +05:30
koekeishiya
3695538a11 fix mac os_window focus issue 2018-01-12 20:24:11 +01:00
Kovid Goyal
7780d52930 DRYer 2018-01-12 11:27:23 +05:30
Kovid Goyal
6a23b4ee47 Allow kitty @xxx in addition to kitty @ xxx 2018-01-12 11:23:34 +05:30
Kovid Goyal
f48b770036 macOS: Fix focus not being transferred on OSWindow close
Fixes #280
2018-01-12 08:05:12 +05:30
Kovid Goyal
81b5d2c4f2 Option to copy to clipboard on mouse select 2018-01-12 07:33:21 +05:30
Kovid Goyal
d43d6ab0a9 Update bundled glfw 2018-01-12 05:34:53 +05:30
Kovid Goyal
5a4e883360 Merge branch 'macos-logo-make' of https://github.com/zchee/kitty 2018-01-11 22:31:18 +05:30
Koichi Shiraishi
eb93cd4048 Add missing 64 size because doesn't create icon_32x32@2x.png 2018-01-12 00:59:19 +09:00
Koichi Shiraishi
d83e3fddb0 Add install 'optipng' description because logo/make.py depends 'optipng' 2018-01-12 00:58:02 +09:00
Kovid Goyal
80ca25e879 ... 2018-01-11 16:03:23 +05:30
Kovid Goyal
c86b34aacd Move to_color into config_utils 2018-01-11 16:02:52 +05:30
Kovid Goyal
8da426039b Make the config parsing code more re-useable 2018-01-11 12:17:18 +05:30
Kovid Goyal
9184e9a28f Update CHANGELOG 2018-01-11 10:46:50 +05:30
Kovid Goyal
578d537d5f kitty icat: Fix some PNG images not being displayed
Work around for bug in ImageMagick where it converts images
into rgba data with fewer rows than specified. Fixes #276
2018-01-11 09:56:40 +05:30
Kovid Goyal
236c92b2b4 No longer use a shell script for the wrapper .app 2018-01-11 08:40:35 +05:30
Kovid Goyal
66a46279f4 ... 2018-01-11 00:26:29 +05:30
Kovid Goyal
eb35edbe20 kitty icat: Fix typo that broke the SIGWNCH signal handler 2018-01-11 00:24:26 +05:30
Kovid Goyal
7cf2404331 Update CHANGELOG 2018-01-10 15:29:58 +05:30
Kovid Goyal
9ccc6bf835 Fix incorrect handling of zero extra pixel offset 2018-01-10 13:54:51 +05:30
Kovid Goyal
025a46bb35 Fix the in cell offset calculations 2018-01-10 13:50:44 +05:30
Kovid Goyal
677e8a1ae9 Make the signal handler a little simpler 2018-01-10 13:37:43 +05:30
Kovid Goyal
03702772d3 kitty icat: Add a --scale-up option to expand images when used with the --place operator 2018-01-10 13:32:05 +05:30
Kovid Goyal
3cd744b247 Implement @focus-window 2018-01-10 13:23:04 +05:30
Kovid Goyal
88523b2692 Add a note about @ls to the tutorial 2018-01-10 13:00:59 +05:30
Kovid Goyal
dcb39eb210 ... 2018-01-10 12:57:15 +05:30
Kovid Goyal
102823809b Add a tutorial for controlling kitty from scripts 2018-01-10 12:54:21 +05:30
Kovid Goyal
23f2b29069 Add --keep-focus for @new-window 2018-01-10 12:36:15 +05:30
Kovid Goyal
7b33a87725 @send-text allow sending from files and stdin 2018-01-10 10:37:43 +05:30
Kovid Goyal
6dde573ed9 Fix __name__ != __main__ when using runpy in the osx-bundle 2018-01-10 08:31:22 +05:30
Kovid Goyal
566e641be1 Allow easily building a kitty.app wrapper to run kitty from source 2018-01-10 07:44:16 +05:30
Kovid Goyal
08d5448523 Move creation of Info.plist and kitty.icns into osx-bundle 2018-01-10 06:58:57 +05:30
Kovid Goyal
c11a20f1ec Add --new-tab to @new-window 2018-01-09 23:09:57 +05:30
Kovid Goyal
e5b9bd2e5a Implement @get-text 2018-01-09 22:48:24 +05:30
Kovid Goyal
6ad49bd7fb Add an option to override the default shell 2018-01-09 16:28:46 +05:30
Kovid Goyal
5d082d8d5a Implement @new-window 2018-01-09 16:13:28 +05:30
Kovid Goyal
977575da73 Implement @close-tab 2018-01-09 15:30:46 +05:30
Kovid Goyal
bced6f31b1 Implement @close-window 2018-01-09 15:27:12 +05:30
Kovid Goyal
6bf04e22cc Fix send_text parsing choking on ''' 2018-01-09 13:10:28 +05:30
Kovid Goyal
2f9784809d Chunk up overly large send text inputs 2018-01-09 02:51:22 +05:30
Kovid Goyal
3e85497c2c Implement @set-tab-title 2018-01-08 16:13:38 +05:30
Kovid Goyal
3b551a1dee Implement @set-window-title 2018-01-08 14:04:27 +05:30
Kovid Goyal
d2fdb1c1eb DRYer 2018-01-08 13:57:43 +05:30
Kovid Goyal
db92af68e9 Fix a couple of typos 2018-01-08 13:54:08 +05:30
Kovid Goyal
815087460c ... 2018-01-08 13:25:25 +05:30
Kovid Goyal
b4cb9d7c4d ... 2018-01-08 13:07:47 +05:30
Kovid Goyal
c6d10c6a23 Use the launch time argv for cmdline on macOS 2018-01-08 13:07:06 +05:30
Kovid Goyal
464c7a80e2 Implement window matching for remote control 2018-01-08 13:01:57 +05:30
Kovid Goyal
4f56cce727 Fix client eating up send-text when seding to same window 2018-01-08 12:48:15 +05:30
Kovid Goyal
7dea6e0ef4 Initial implementation of send-text remote cmd 2018-01-08 12:48:15 +05:30
Kovid Goyal
8c143db381 Add documentation for the ls command 2018-01-08 12:48:15 +05:30
Kovid Goyal
0cea6d5a1b Implement window listing 2018-01-08 12:48:15 +05:30
Kovid Goyal
eff881f4bb Allow sending arbitrary sized escpae codes to children 2018-01-08 12:48:14 +05:30
Kovid Goyal
c64fd1760c More work on remote control 2018-01-08 12:48:14 +05:30
Kovid Goyal
9e361700d2 Handle reading of RC response via stdin 2018-01-08 12:48:14 +05:30
Kovid Goyal
1f976644bf Refactor read with timeout logic into its own function 2018-01-08 12:48:14 +05:30
Kovid Goyal
f3cb68ee40 Framework for remote control 2018-01-08 12:48:14 +05:30
Kovid Goyal
85a3da057f Session file: Allow setting the title for windows 2018-01-08 12:47:47 +05:30
Kovid Goyal
c551384369 Session file: Allow specifying multiple cd directives per tab to have different windows launched with different working directories 2018-01-08 12:38:01 +05:30
Kovid Goyal
ce230b071b Add tests for SGR protocol encoding 2018-01-07 23:48:16 +05:30
Kovid Goyal
13d5f04ad9 A spot of refactoring 2018-01-07 21:26:28 +05:30
Daniels Kursits
3b861de3d2 Fix mouse move events being reported as drag events
when using the SGR mouse reporting protocol

Fixes #271
2018-01-07 21:12:50 +05:30
Kovid Goyal
1ef010689d Add support for clearing the scrollback buffer
kitty now supports using \E3J to clear the screen and scrollback buffer.
See #268
2018-01-06 23:55:49 +05:30
Kovid Goyal
3523ab283f More trailing whitespace 2018-01-06 09:12:23 +05:30
Kovid Goyal
5b2a9b101d Fix trailing whitespace 2018-01-05 21:27:24 +05:30
Kovid Goyal
fdcfcfa1b9 Make alt+backspace delete the previous word
i.e. alt+backspace now sends ^W (werase). THis matches the behavior of
terminal.app and gnome-terminal. Fixes #264
2018-01-05 21:08:42 +05:30
Kovid Goyal
88e9c21a3b Ensure a kitty executable is on PATH even when running from source 2018-01-05 14:56:01 +05:30
Kovid Goyal
973352a98d Allow + prefixing of special invocations 2018-01-05 14:36:31 +05:30
Kovid Goyal
f06f871dfc Handle fork() failures more gracefully 2018-01-05 13:57:49 +05:30
Kovid Goyal
e03c713294 Remove a few more signal unsafe function calls between fork() and exec() 2018-01-05 00:24:15 +05:30
Kovid Goyal
aa107956f9 ... 2018-01-04 23:55:03 +05:30
Kovid Goyal
d725578e1b Silence spurious compiler warning 2018-01-04 23:54:35 +05:30
Kovid Goyal
239eb8202b Move fork()+exec() of child processes into C
By avoiding python in the child process before exec we ensure that
malloc and other unsafe to use after fork functions are not used.
Should also mean that less pages will need to be copied into thec hild
process, leading to marginally faster startups.
2018-01-04 23:19:09 +05:30
Kovid Goyal
58d7439719 Add a note on installing dev packages for X11 on linux
Fixes #260
2018-01-04 18:06:11 +05:30
Kovid Goyal
cc145ec3dd DRYer 2018-01-04 12:38:35 +05:30
Kovid Goyal
847ce37160 Take the mouse wheel multiplier into account when generating keypresses for mouse wheel events. Fixes #262 2018-01-04 12:34:31 +05:30
Kovid Goyal
0305009766 Remove --without-cairo from brew install since it is ignored anyway 2018-01-03 09:22:23 +05:30
Kovid Goyal
fe1b056371 Allow using macOS specific modifier key names in the config file
cmd and option can now be used instead of super and alt respectively.
2018-01-03 08:59:36 +05:30
Kovid Goyal
0c4804db79 DRYer 2017-12-31 09:04:22 +05:30
Kovid Goyal
f7e84df332 Add an explicit OpenGL version test
Apparently there are some pre 3.3 drivers that include the
texture_storage extension. Fixes #258
2017-12-31 08:36:50 +05:30
Kovid Goyal
bd654d22f4 Read the required wayland protocols version from upstream instead of hardcoding it 2017-12-28 14:12:46 +05:30
Kovid Goyal
115de36599 version 0.6.1 2017-12-28 10:40:42 +05:30
Kovid Goyal
b01c3483de Add an option to fade text in inactive windows
Fixes #256
2017-12-28 10:36:48 +05:30
Kovid Goyal
db7f566a0c Update Changelog 2017-12-27 10:17:34 +05:30
Kovid Goyal
9f806e76e7 Fix incorrect condition for aborting cursor geometry calculation 2017-12-27 10:07:21 +05:30
Kovid Goyal
12bdd4dddd Bump min version of wayland-protocols to match upstream 2017-12-27 07:24:38 +05:30
Kovid Goyal
72a1eb237d Fix regression that broke drawing of hollow cursor when window loses focus
Fixes #255
2017-12-27 07:15:48 +05:30
Kovid Goyal
6a89289907 Merge branch 'recmod' of https://github.com/grimpy/kitty 2017-12-27 06:40:53 +05:30
Kovid Goyal
7ed835cf13 Fix a crash when detecting URLs continued onto multiple lines
Fixes #244
2017-12-27 06:35:09 +05:30
Kovid Goyal
4621ff41d9 Skip building the wayland backend on systems with missing wayland protocol definitions
Fixes #254
2017-12-27 06:22:36 +05:30
Jo De Boeck
60e17d5224 Make it possible to modify rectangle selection modifiers
Signed-off-by: Jo De Boeck <deboeck.jo@gmail.com>
2017-12-24 18:40:25 +02:00
Kovid Goyal
1e2f7c705d ... 2017-12-22 12:36:40 +05:30
Kovid Goyal
1ebf0b95c4 Implement reading of cwd of a process on macOS as well 2017-12-21 12:30:34 +05:30
Kovid Goyal
c5acd94456 Add new actions to open tabs/windows with the working directory set to the working directory of the current window
Fixes #237
2017-12-21 11:29:21 +05:30
Kovid Goyal
448ffe01c3 Fix new window being created with different DPI not changing global DPI 2017-12-21 09:06:07 +05:30
Kovid Goyal
5c882f3290 Fix Travis flake failure on gen-wcwidth.py because of old python version 2017-12-20 23:38:16 +05:30
Kovid Goyal
70a4720bf9 Fix for window being opened on secondary monitor using primary monitor DPI 2017-12-20 23:01:06 +05:30
Kovid Goyal
c8aee8c881 Automatically adjust cell height when DPI changes
DPI changes are detected as a change in the ration of the window size to
the framebuffer size. I dont know how reliable that is. Possiblly Fix #77
2017-12-20 22:55:05 +05:30
Kovid Goyal
c2ad0607b8 Fix trailing whitespace in generated unicde data files 2017-12-20 22:44:20 +05:30
Kovid Goyal
d1282b9f55 Add a script to generate wcwidth as well
Generated function is more efficient than the implementation from
wcwidth9 and also makes it easy to update when the unicode standard
changes.
2017-12-20 16:18:05 +05:30
Kovid Goyal
11ee317884 Fix some emoji glyphs not colored on Linux
Linux has a plethora of fonts that contain some emoji glyphs. If one of
those fonts is used as a fallback font for a non-emoji character, it
also gets used for emojis -- leading to non-colored emoji. Fix by making
sure the first fallback font is a dedicated emoji font.
2017-12-20 10:40:27 +05:30
Kovid Goyal
e69de2f968 Use the new glfw API for setting WM_CLASS 2017-12-20 09:18:29 +05:30
Kovid Goyal
2f4f3e3331 Update bundled glfw 2017-12-20 09:07:06 +05:30
Kovid Goyal
020f8a2047 Add trailing space test to travis 2017-12-20 08:52:06 +05:30
Kovid Goyal
b8faba2a16 More trailing space removal 2017-12-20 08:50:09 +05:30
Kovid Goyal
0fcce6ec58 Remove trailing whitespace from native code files 2017-12-20 08:44:47 +05:30
Kovid Goyal
15f5ea92b8 Merge branch 'master' of https://github.com/FabioLolix/kitty 2017-12-20 08:24:20 +05:30
FabioLolix
2bbb5d25eb Updated readme 2017-12-20 00:12:58 +01:00
Kovid Goyal
8d94285a5d Ensure underlines are rendered even for fonts with very poor metrics
Fixes #236
2017-12-19 00:54:04 +05:30
Kovid Goyal
eed11361c5 Fix typo in config directory override env var 2017-12-18 06:59:19 +05:30
105 changed files with 10214 additions and 3826 deletions

3
.gitattributes vendored
View File

@@ -1,8 +1,9 @@
kitty/wcwidth9.h linguist-generated=true kitty/wcwidth-std.h linguist-generated=true
kitty/emoji.h linguist-generated=true kitty/emoji.h linguist-generated=true
kitty/keys.h linguist-generated=true kitty/keys.h linguist-generated=true
kitty/charsets.c linguist-generated=true kitty/charsets.c linguist-generated=true
kitty/key_encoding.py linguist-generated=true kitty/key_encoding.py linguist-generated=true
kitty/unicode-data.c
kitty/rgb.py linguist-generated=true kitty/rgb.py linguist-generated=true
kitty/gl-wrapper.* linguist-generated=true kitty/gl-wrapper.* linguist-generated=true
kitty/khrplatform.h linguist-generated=true kitty/khrplatform.h linguist-generated=true

View File

@@ -56,7 +56,7 @@ matrix:
env: env:
- RUN_FLAKE=1 BUILD_PKG=1 - RUN_FLAKE=1 BUILD_PKG=1
language: python language: python
python: "3.5" python: "3.6"
addons: addons:
apt: apt:
packages: packages:
@@ -95,7 +95,7 @@ install: |
if [[ "$USE_BREW" == "1" ]]; then if [[ "$USE_BREW" == "1" ]]; then
brew update; brew update;
brew install python3; brew install python3;
brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype --without-cairo; brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype;
else else
mkdir -p $SW; mkdir -p $SW;
curl https://download.calibre-ebook.com/travis/kitty/osx.tar.xz | tar xJ -C $SW; curl https://download.calibre-ebook.com/travis/kitty/osx.tar.xz | tar xJ -C $SW;
@@ -123,6 +123,7 @@ before_script:
- $PYTHON setup.py build --debug --verbose $SANITIZE_ARG; - $PYTHON setup.py build --debug --verbose $SANITIZE_ARG;
script: script:
- if grep -Inr '\s$' kitty kitty_tests *.py *.asciidoc .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
- if [[ -z $SANITIZE_ARG ]]; then $PYTHON test.py; else ./asan-launcher test.py; fi - if [[ -z $SANITIZE_ARG ]]; then $PYTHON test.py; else ./asan-launcher test.py; fi
- 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

@@ -3,6 +3,79 @@ Changelog
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator. kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
version 0.7.0 [2018-01-24]
---------------------------
- Allow controlling kitty from the shell prompt/scripts. You can
open/close/rename windows and tabs and even send input to specific windows.
See the README for details.
- Add option to put tab bar at the top instead of the bottom
- Add option to override the default shell
- Add "Horizontal" and "Vertical" window layouts
- Sessions: Allow setting titles and working directories for individual windows
- Option to copy to clipboard on mouse select
- Fix incorrect reporting of mouse move events when using the SGR protocol
- Make alt+backspace delete the previous word
- Take the mouse wheel multiplier option in to account when generating fake key
scroll events
- macOS: Fix closing top-level window does not transfer focus to other
top-level windows.
- macOS: Fix alt+arrow keys not working when disabling the macos_option_as_alt
config option.
- kitty icat: Workaround for bug in ImageMagick that would cause some images
to fail to display at certain sizes.
- Fix rendering of text with ligature fonts that do not use dummy glyphs
- Fix a regression that caused copying of the selection to clipboard to only
copy the visible part of the selection
- Fix incorrect handling of some unicode combining marks that are not re-ordered
- Fix handling on non-BMP combining characters
- Drop the dependency on libunistring
version 0.6.1 [2017-12-28]
---------------------------
- Add an option to fade the text in inactive windows
- Add new actions to open windows/tabs/etc. with the working directory set to
the working directory of the current window.
- Automatically adjust cell size when DPI changes, for example when kitty is
moved from one monitor to another with a different DPI
- Ensure underlines are rendered even for fonts with very poor metrics
- Fix some emoji glyphs not colored on Linux
- Internal wcwidth() implementation is now auto-generated from the unicode
standard database
- Allow configuring the modifiers to use for rectangular selection with the
mouse.
- Fix incorrect minimum wayland version in the build script
- Fix a crash when detecting a URL that ends at the end of the line
- Fix regression that broke drawing of hollow cursor when window loses focus
version 0.6.0 [2017-12-18] version 0.6.0 [2017-12-18]
--------------------------- ---------------------------

View File

@@ -21,3 +21,11 @@ debug:
# Build with the ASAN and UBSAN sanitizers # Build with the ASAN and UBSAN sanitizers
asan: asan:
python3 setup.py build $(VVAL) --debug --sanitize python3 setup.py build $(VVAL) --debug --sanitize
logo/kitty.iconset/icon_256x256.png: logo/kitty.svg logo/make.py
logo/make.py
rendered_logo: logo/kitty.iconset/icon_256x256.png
app: rendered_logo
python3 setup.py app $(VVAL)

View File

@@ -51,19 +51,21 @@ image::https://travis-ci.org/kovidgoyal/kitty.svg?branch=master[Build status, li
== Major Features == Major Features
* Supports tiling multiple terminal windows side by side in different * Uses OpenGL for rendering, offloads rendering to the GPU for lower system
layouts without needing to use an extra program like tmux load and buttery smooth scrolling. Uses threaded rendering to minimize input
latency.
* Supports all modern terminal features: graphics (images), unicode, * Supports all modern terminal features: graphics (images), unicode,
true-color, OpenType ligatures, mouse protocol, focus tracking, bracketed paste and so on. true-color, OpenType ligatures, mouse protocol, focus tracking, bracketed paste and so on.
* Supports tiling multiple terminal windows side by side in different
layouts without needing to use an extra program like tmux
* Can be controlled from scripts or the shell prompt, even over SSH.
* Supports startup sessions which allow you to specify the window/tab layout, * Supports startup sessions which allow you to specify the window/tab layout,
working directories and programs to run on startup. working directories and programs to run on startup.
* Uses OpenGL for rendering, offloads rendering to the GPU for lower system
load and buttery smooth scrolling. Uses threaded rendering to minimize input
latency.
* Cross-platform support: kitty currently works on Linux and macOS, but because * Cross-platform support: kitty currently works on Linux and macOS, but because
it uses only OpenGL for rendering, it should be trivial to port to other it uses only OpenGL for rendering, it should be trivial to port to other
platforms. platforms.
@@ -73,8 +75,6 @@ window using arbitrary programs of your choice. This is useful for browsing
the history comfortably in a pager or editor and also for doing things like the history comfortably in a pager or editor and also for doing things like
opening URLs using only the keyboard. opening URLs using only the keyboard.
* Easily hackable (UI layer written in python, inner loops in C for
speed). Less than fifteen thousand lines of code.
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]
@@ -90,7 +90,6 @@ the following dependencies are installed first.
* python >= 3.5 * python >= 3.5
* harfbuzz >= 1.5.0 * harfbuzz >= 1.5.0
* libunistring
* zlib * zlib
* libpng * libpng
* freetype (not needed on macOS) * freetype (not needed on macOS)
@@ -98,6 +97,7 @@ the following dependencies are installed first.
* ImageMagick (optional, needed to use the `kitty icat` tool to display images in the terminal) * ImageMagick (optional, needed to use the `kitty icat` tool to display images in the terminal)
* 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)
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `xinerama` and `xkbcommon-x11`, if they are not already installed by your distro.
=== Install and run from source === Install and run from source
@@ -119,7 +119,9 @@ python3 /path/to/kitty/folder
=== Linux packages === Linux packages
* Arch linux: AUR git package https://aur.archlinux.org/packages/kitty-git/ * Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
* Arch Linux: AUR stable package https://aur.archlinux.org/packages/kitty/
* NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix * NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix
@@ -228,6 +230,15 @@ windows are:
|=== |===
== Controlling kitty from scripts
You can control kitty from the command line/scripts by sending it messages.
You can tell kitty to open/close/rename tabs and windows. You can even send
arbitrary input/text to any specified window. Messages can be sent using
`kitty @`. Note that you must set `allow_remote_control yes` in your
kitty.conf to use this feature. It even works over SSH connections. This
feature is best illustrated with a link:remote-control.asciidoc[tutorial].
== The scrollback buffer == The scrollback buffer
@@ -254,8 +265,10 @@ hold down `ctrl+shift` and click on a URL to open it in a browser.
Currently, there are three layouts available, Currently, there are three layouts available,
* Stack -- Only a single maximized window is shown at a time * Stack -- Only a single maximized window is shown at a time
* Tall -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right. * Tall -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
* Grid -- All windows are shown in a grid * Grid -- All windows are shown in a grid
* Horizontal -- All windows are shown side-by-side
* Vertical -- All windows are shown one below the other
You can switch between layouts using the {sc_next_layout} key combination. You can switch between layouts using the {sc_next_layout} key combination.
@@ -283,11 +296,13 @@ command line flag. For example:
.... ....
# Set the window layout for the current tab # Set the window layout for the current tab
layout tall layout tall
# Set the working directory for the current tab # Set the working directory for windows in the current tab
cd ~ cd ~
# Create a window and run the specified command in it # Create a window and run the specified command in it
launch zsh launch zsh
launch vim launch vim
# Set the title for the next window
title Chat with x
launch irssi --profile x launch irssi --profile x
# Create a new tab (the part after new_tab is the optional tab name which will # Create a new tab (the part after new_tab is the optional tab name which will
@@ -417,33 +432,21 @@ the approach using ligatures.
=== How do I build kitty.app on macOS? === How do I build kitty.app on macOS?
You dont need to build kitty.app to run kitty from source. Simply install the dependencies Install `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
listed above using brew/MacPorts or similar and then compile and run with:
And run:
``` ```
make make app
python3 .
``` ```
You can also create a shell script to run kitty: This `kitty.app` unlike the released one does not include its own copy of
python and the other dependencies. So if you ever un-install/upgrade those dependencies
you might have to rebuild the app.
``` Note that the released kitty.dmg includes all dependencies, unlike the
#!/bin/sh `kitty.app` built above and is built automatically by using the `kitty` branch of
exec python3 /path/to/kitty/source/dir
```
And using something like link:https://mathiasbynens.be/notes/shell-script-mac-apps[app-wrapper-for-shell-script]
you can create a simple launcher app to run the shell script.
If you only want to make some small changes to a python file, you can do that
and simply replace the corresponding file inside an existing kitty.app. (Note
that kitty.app will contain `.pyo` files which are compiled versions of `.py`
files, delete the `.pyo` file and replace it with a `.py` file).
Finally, note that the released kitty.dmg is built automatically by using the
`kitty` branch of
link:https://github.com/kovidgoyal/build-calibre[build-calibre] however, that link:https://github.com/kovidgoyal/build-calibre[build-calibre] however, that
is designed to run on linux and is not for the faint of heart. is designed to run on Linux and is not for the faint of heart.
== A tribute == A tribute

View File

@@ -4,12 +4,52 @@
import sys import sys
if len(sys.argv) > 1 and sys.argv[1] == 'icat':
def icat(args):
from kitty.icat import main from kitty.icat import main
main(sys.argv[1:]) main(args)
elif len(sys.argv) > 1 and sys.argv[1] == 'list-fonts':
def list_fonts(args):
from kitty.fonts.list import main from kitty.fonts.list import main
main(sys.argv[1:]) main(args)
else:
from kitty.main import main
def remote_control(args):
from kitty.remote_control import main
main(args)
def namespaced(args):
func = namespaced_entry_points[args[1]]
func(args[1:])
entry_points = {
# These two are here for backwards compat
'icat': icat,
'list-fonts': list_fonts,
'@': remote_control,
'+': namespaced,
}
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
def main():
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
func = entry_points.get(first_arg)
if func is None:
if first_arg.startswith('@'):
remote_control(['@', first_arg[1:]] + sys.argv[2:])
elif first_arg.startswith('+'):
namespaced(['+', first_arg[1:]] + sys.argv[2:])
else:
from kitty.main import main
main()
else:
func(sys.argv[1:])
if __name__ == '__main__':
main() main()

View File

@@ -1,2 +1,2 @@
#!/bin/bash #!/bin/bash
cloc --exclude-list-file <(echo -e 'kitty/wcwidth9.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty

View File

@@ -1,75 +0,0 @@
#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
from collections import defaultdict
from functools import partial
from itertools import groupby
from operator import itemgetter
from urllib.request import urlopen
os.chdir(os.path.dirname(os.path.abspath(__file__)))
raw = urlopen('http://unicode.org/Public/emoji/5.0/emoji-data.txt').read().decode('utf-8')
seen = set()
cmap = defaultdict(set)
for line in raw.splitlines():
line = line.strip()
if not line or line.startswith('#'):
continue
spec, rest = line.partition(';')[::2]
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
if '.' in spec:
spec = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
spec = set(range(spec[0], spec[1] + 1))
else:
spec = {int(spec, 16)}
cmap[rest] |= spec
seen |= spec
items = list(seen)
def get_ranges(items):
items.sort()
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
group = tuple(map(itemgetter(1), g))
a, b = group[0], group[-1]
if a == b:
yield a
else:
yield a, b
def write_case(spec, p):
if isinstance(spec, tuple):
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
else:
p('\t\tcase 0x{:x}:'.format(spec))
with open('kitty/emoji.h', 'w') as f:
p = partial(print, file=f)
p('#pragma once')
p('#include "data-types.h"\n')
p('START_ALLOW_CASE_RANGE')
p('static inline bool is_emoji(uint32_t code) {')
p('\tswitch(code) {')
for spec in get_ranges(items):
last = spec[1] if isinstance(spec, tuple) else spec
if last < 0x231a:
continue
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false; \n}')
p('static inline bool is_emoji_modifier(uint32_t code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(cmap['Emoji_Modifier'])):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false; \n}')
p('END_ALLOW_CASE_RANGE')

243
gen-wcwidth.py Executable file
View File

@@ -0,0 +1,243 @@
#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
from contextlib import contextmanager
from datetime import date
from functools import partial
from itertools import groupby
from operator import itemgetter
from urllib.request import urlopen
# We ignore the first few emojis as they are widely assumed to be single width
# in legacy applications
FIRST_EMOJI = 0x2194
os.chdir(os.path.dirname(os.path.abspath(__file__)))
def get_data(fname, folder='UCD'):
url = f'https://www.unicode.org/Public/{folder}/latest/{fname}'
bn = os.path.basename(url)
local = os.path.join('/tmp', bn)
if os.path.exists(local):
data = open(local, 'rb').read()
else:
data = urlopen(url).read()
open(local, 'wb').write(data)
for line in data.decode('utf-8').splitlines():
line = line.strip()
if line and not line.startswith('#'):
yield line
# Map of class names to set of codepoints in class
class_maps = {}
marks = set()
not_assigned = set(range(0, sys.maxunicode))
def parse_ucd():
first = None
for line in get_data('ucd/UnicodeData.txt'):
parts = [x.strip() for x in line.split(';')]
codepoint = int(parts[0], 16)
category = parts[2]
s = class_maps.setdefault(category, set())
desc = parts[1]
codepoints = (codepoint,)
if first is None:
if desc.endswith(', First>'):
first = codepoint
continue
else:
codepoints = range(first, codepoint + 1)
first = None
for codepoint in codepoints:
s.add(codepoint)
not_assigned.discard(codepoint)
if category.startswith('M'):
marks.add(codepoint)
def split_two(line):
spec, rest = line.split(';', 1)
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
if '..' in spec:
chars = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
chars = set(range(chars[0], chars[1] + 1))
else:
chars = {int(spec, 16)}
return chars, rest
all_emoji = set()
emoji_categories = {}
def parse_emoji():
for line in get_data('emoji-data.txt', 'emoji'):
chars, rest = split_two(line)
if max(chars) >= FIRST_EMOJI:
s = emoji_categories.setdefault(rest, set())
s |= chars
all_emoji.update(chars)
doublewidth, ambiguous = set(), set()
def parse_eaw():
global doublewidth, ambiguous
seen = set()
for line in get_data('ucd/EastAsianWidth.txt'):
chars, eaw = split_two(line)
if eaw == 'A':
ambiguous |= chars
seen |= chars
elif eaw == 'W' or eaw == 'F':
doublewidth |= chars
seen |= chars
doublewidth |= set(range(0x3400, 0x4DBF + 1)) - seen
doublewidth |= set(range(0x4E00, 0x9FFF + 1)) - seen
doublewidth |= set(range(0xF900, 0xFAFF + 1)) - seen
doublewidth |= set(range(0x20000, 0x2FFFD + 1)) - seen
doublewidth |= set(range(0x30000, 0x3FFFD + 1)) - seen
def get_ranges(items):
items.sort()
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
group = tuple(map(itemgetter(1), g))
a, b = group[0], group[-1]
if a == b:
yield a
else:
yield a, b
def write_case(spec, p):
if isinstance(spec, tuple):
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
else:
p('\t\tcase 0x{:x}:'.format(spec))
@contextmanager
def create_header(path):
f = open(path, 'w')
p = partial(print, file=f)
p('// unicode data, built from the unicode standard on:', date.today())
p('// see gen-wcwidth.py')
if path.endswith('.h'):
p('#pragma once')
p('#include "data-types.h"\n')
p('START_ALLOW_CASE_RANGE')
p()
yield p
p()
p('END_ALLOW_CASE_RANGE')
f.close()
def gen_emoji():
with create_header('kitty/emoji.h') as p:
p('static inline bool\nis_emoji(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(all_emoji)):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false;\n}')
p('static inline bool\nis_emoji_modifier(char_type code) {')
p('\tswitch(code) {')
for spec in get_ranges(list(emoji_categories['Emoji_Modifier'])):
write_case(spec, p)
p('\t\t\treturn true;')
p('\t\tdefault: return false;')
p('\t}')
p('\treturn false;\n}')
def category_test(name, p, classes, comment, static=False):
static = 'static inline ' if static else ''
chars = set()
for c in classes:
chars |= class_maps[c]
p(f'{static}bool\n{name}(char_type code) {{')
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
p('\tswitch(code) {')
for spec in get_ranges(list(chars)):
write_case(spec, p)
p(f'\t\t\treturn true;')
p('\t} // }}}\n')
p('\treturn false;\n}\n')
def gen_ucd():
with create_header('kitty/unicode-data.c') as p:
p('#include "unicode-data.h"')
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters (Cc Cf Cs)')
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
category_test('is_CZ_category', p, {c for c in class_maps if c[0] in 'CZ'}, 'C and Z categories')
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
mark_map = [0] + list(sorted(marks))
p('char_type codepoint_for_mark(combining_type m) {')
p(f'\tstatic char_type map[{len(mark_map)}] =', '{', ', '.join(map(str, mark_map)), '}; // {{{ mapping }}}')
p('\tif (m < arraysz(map)) return map[m];')
p('\treturn 0;')
p('}\n')
p('combining_type mark_for_codepoint(char_type c) {')
p('\tswitch(c) { // {{{')
rmap = {c: m for m, c in enumerate(mark_map)}
for spec in get_ranges(mark_map):
if isinstance(spec, tuple):
s = rmap[spec[0]]
p(f'\t\tcase {spec[0]} ... {spec[1]}: return {s} + c - {spec[0]};')
else:
p(f'\t\tcase {spec}: return {rmap[spec]};')
p('default: return 0;')
p('\t} // }}}')
p('}\n')
def gen_wcwidth():
seen = set()
def add(p, comment, chars_, ret):
chars = chars_ - seen
seen.update(chars)
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
for spec in get_ranges(list(chars)):
write_case(spec, p)
p(f'\t\t\treturn {ret};')
p('\t\t// }}}\n')
with create_header('kitty/wcwidth-std.h') as p:
p('static int\nwcwidth_std(int32_t code) {')
p('\tswitch(code) {')
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
add(p, 'Non-printing characters', non_printing, -1)
add(p, 'Marks', marks, -1)
add(p, 'Private use', class_maps['Co'], -3)
add(p, 'East Asian ambiguous width', ambiguous, -2)
add(p, 'East Asian double width', doublewidth, 2)
add(p, 'Emoji', all_emoji, 2)
add(p, 'Not assigned in the unicode character database', not_assigned, -1)
p('\t\tdefault: return 1;')
p('\t}')
p('\treturn 1;\n}')
parse_ucd()
parse_emoji()
parse_eaw()
gen_ucd()
gen_wcwidth()
gen_emoji()

View File

@@ -88,9 +88,10 @@ typedef struct _GLFWwindowNS
GLFWbool maximized; GLFWbool maximized;
// Cached window and framebuffer sizes used to filter out duplicate events // Cached window properties to filter out duplicate events
int width, height; int width, height;
int fbWidth, fbHeight; int fbWidth, fbHeight;
float xscale, yscale;
// The total sum of the distances the cursor has been warped // The total sum of the distances the cursor has been warped
// since the last cursor motion event was processed // since the last cursor motion event was processed

View File

@@ -43,6 +43,7 @@
#define NSEventModifierFlagControl NSControlKeyMask #define NSEventModifierFlagControl NSControlKeyMask
#define NSEventModifierFlagOption NSAlternateKeyMask #define NSEventModifierFlagOption NSAlternateKeyMask
#define NSEventModifierFlagShift NSShiftKeyMask #define NSEventModifierFlagShift NSShiftKeyMask
#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask #define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
#define NSEventMaskAny NSAnyEventMask #define NSEventMaskAny NSAnyEventMask
#define NSEventTypeApplicationDefined NSApplicationDefined #define NSEventTypeApplicationDefined NSApplicationDefined
@@ -177,6 +178,8 @@ static int translateFlags(NSUInteger flags)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if (flags & NSEventModifierFlagCommand) if (flags & NSEventModifierFlagCommand)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (flags & NSEventModifierFlagCapsLock)
mods |= GLFW_MOD_CAPS_LOCK;
return mods; return mods;
} }
@@ -571,6 +574,16 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
window->ns.fbHeight = fbRect.size.height; window->ns.fbHeight = fbRect.size.height;
_glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height); _glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
} }
const float xscale = fbRect.size.width / contentRect.size.width;
const float yscale = fbRect.size.height / contentRect.size.height;
if (xscale != window->ns.xscale || yscale != window->ns.yscale)
{
window->ns.xscale = xscale;
window->ns.yscale = yscale;
_glfwInputWindowContentScale(window, xscale, yscale);
}
} }
- (void)drawRect:(NSRect)rect - (void)drawRect:(NSRect)rect
@@ -1087,8 +1100,8 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
[window->ns.object zoom:nil]; [window->ns.object zoom:nil];
} }
if (wndconfig->ns.frame) if (strlen(wndconfig->ns.frameName))
[window->ns.object setFrameAutosaveName:[NSString stringWithUTF8String:wndconfig->title]]; [window->ns.object setFrameAutosaveName:[NSString stringWithUTF8String:wndconfig->ns.frameName]];
window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window]; window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
@@ -1475,6 +1488,20 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return [window->ns.object isZoomed]; return [window->ns.object isZoomed];
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
const NSPoint point = [NSEvent mouseLocation];
if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] !=
[window->ns.object windowNumber])
{
return GLFW_FALSE;
}
return NSPointInRect(point,
[window->ns.object convertRectToScreen:[window->ns.view bounds]]);
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return ![window->ns.object isOpaque] && ![window->ns.view isOpaque]; return ![window->ns.object isOpaque] && ![window->ns.view isOpaque];

16
glfw/context.c vendored
View File

@@ -321,10 +321,11 @@ const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
return closest; return closest;
} }
GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig) GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig)
{ {
int i; int i;
_GLFWwindow* window; _GLFWwindow* previous;
const char* version; const char* version;
const char* prefixes[] = const char* prefixes[] =
{ {
@@ -334,11 +335,12 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
NULL NULL
}; };
window = _glfwPlatformGetTls(&_glfw.contextSlot);
window->context.source = ctxconfig->source; window->context.source = ctxconfig->source;
window->context.client = GLFW_OPENGL_API; window->context.client = GLFW_OPENGL_API;
previous = _glfwPlatformGetTls(&_glfw.contextSlot);;
glfwMakeContextCurrent((GLFWwindow*) window);
window->context.GetIntegerv = (PFNGLGETINTEGERVPROC) window->context.GetIntegerv = (PFNGLGETINTEGERVPROC)
window->context.getProcAddress("glGetIntegerv"); window->context.getProcAddress("glGetIntegerv");
window->context.GetString = (PFNGLGETSTRINGPROC) window->context.GetString = (PFNGLGETSTRINGPROC)
@@ -346,6 +348,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
if (!window->context.GetIntegerv || !window->context.GetString) if (!window->context.GetIntegerv || !window->context.GetString)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken"); _glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken");
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -363,6 +366,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
"OpenGL ES version string retrieval is broken"); "OpenGL ES version string retrieval is broken");
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -394,6 +398,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
"No version found in OpenGL ES version string"); "No version found in OpenGL ES version string");
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -423,6 +428,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
window->context.major, window->context.minor); window->context.major, window->context.minor);
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
@@ -438,6 +444,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
{ {
_glfwInputError(GLFW_PLATFORM_ERROR, _glfwInputError(GLFW_PLATFORM_ERROR,
"Entry point retrieval is broken"); "Entry point retrieval is broken");
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_FALSE; return GLFW_FALSE;
} }
} }
@@ -544,6 +551,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
window->context.swapBuffers(window); window->context.swapBuffers(window);
} }
glfwMakeContextCurrent((GLFWwindow*) previous);
return GLFW_TRUE; return GLFW_TRUE;
} }

View File

@@ -52,7 +52,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
ans.ldpaths.extend(('-framework', f)) ans.ldpaths.extend(('-framework', f))
elif module == 'wayland': elif module == 'wayland':
at_least_version('wayland-protocols', 1, 1) at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0]) ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0]) ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
ans.wayland_protocols = tuple(sinfo[module]['protocols']) ans.wayland_protocols = tuple(sinfo[module]['protocols'])
@@ -69,6 +69,8 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir): def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
for protocol in env.wayland_protocols: for protocol in env.wayland_protocols:
src = os.path.join(env.wayland_packagedir, protocol) src = os.path.join(env.wayland_packagedir, protocol)
if not os.path.exists(src):
raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol))
for ext in 'hc': for ext in 'hc':
dest = wayland_protocol_file_name(src, ext) dest = wayland_protocol_file_name(src, ext)
dest = os.path.join(dest_dir, dest) dest = os.path.join(dest_dir, dest)
@@ -80,6 +82,7 @@ def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
def collect_source_information(): def collect_source_information():
raw = open('src/CMakeLists.txt').read() raw = open('src/CMakeLists.txt').read()
mraw = open('CMakeLists.txt').read()
def extract_sources(group, start_pos=0): def extract_sources(group, start_pos=0):
for which in 'HEADERS SOURCES'.split(): for which in 'HEADERS SOURCES'.split():
@@ -91,8 +94,11 @@ def collect_source_information():
).group(1).strip().split() ).group(1).strip().split()
) )
wayland_protocols = re.search('WaylandProtocols\s+(\S+)\s+', mraw).group(1)
wayland_protocols = list(map(int, wayland_protocols.split('.')))
ans = { ans = {
'common': dict(extract_sources('common')), 'common': dict(extract_sources('common')),
'wayland_protocols': wayland_protocols,
} }
for group in 'cocoa win32 x11 wayland osmesa'.split(): for group in 'cocoa win32 x11 wayland osmesa'.split():
m = re.search('_GLFW_' + group.upper(), raw) m = re.search('_GLFW_' + group.upper(), raw)
@@ -242,9 +248,10 @@ def main():
sinfo = collect_source_information() sinfo = collect_source_information()
files_to_copy = set() files_to_copy = set()
for x in sinfo.values(): for x in sinfo.values():
headers, sources = x['headers'], x['sources'] if isinstance(x, dict):
for name in headers + sources: headers, sources = x['headers'], x['sources']
files_to_copy.add(os.path.abspath(os.path.join('src', name))) for name in headers + sources:
files_to_copy.add(os.path.abspath(os.path.join('src', name)))
glfw_header = os.path.abspath('include/GLFW/glfw3.h') glfw_header = os.path.abspath('include/GLFW/glfw3.h')
glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h') glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h')
os.chdir(base) os.chdir(base)

307
glfw/glfw3.h vendored
View File

@@ -493,17 +493,37 @@ extern "C" {
* @{ */ * @{ */
/*! @brief If this bit is set one or more Shift keys were held down. /*! @brief If this bit is set one or more Shift keys were held down.
*
* If this bit is set one or more Shift keys were held down.
*/ */
#define GLFW_MOD_SHIFT 0x0001 #define GLFW_MOD_SHIFT 0x0001
/*! @brief If this bit is set one or more Control keys were held down. /*! @brief If this bit is set one or more Control keys were held down.
*
* If this bit is set one or more Control keys were held down.
*/ */
#define GLFW_MOD_CONTROL 0x0002 #define GLFW_MOD_CONTROL 0x0002
/*! @brief If this bit is set one or more Alt keys were held down. /*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/ */
#define GLFW_MOD_ALT 0x0004 #define GLFW_MOD_ALT 0x0004
/*! @brief If this bit is set one or more Super keys were held down. /*! @brief If this bit is set one or more Super keys were held down.
*
* If this bit is set one or more Super keys were held down.
*/ */
#define GLFW_MOD_SUPER 0x0008 #define GLFW_MOD_SUPER 0x0008
/*! @brief If this bit is set the Caps Lock key is enabled.
*
* If this bit is set the Caps Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_CAPS_LOCK 0x0010
/*! @brief If this bit is set the Num Lock key is enabled.
*
* If this bit is set the Num Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_NUM_LOCK 0x0020
/*! @} */ /*! @} */
@@ -794,6 +814,7 @@ extern "C" {
* [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib). * [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib).
*/ */
#define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A #define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A
#define GLFW_HOVERED 0x0002000B
/*! @brief Framebuffer bit depth hint. /*! @brief Framebuffer bit depth hint.
* *
@@ -944,8 +965,11 @@ extern "C" {
#define GLFW_CONTEXT_CREATION_API 0x0002200B #define GLFW_CONTEXT_CREATION_API 0x0002200B
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
#define GLFW_COCOA_FRAME_AUTOSAVE 0x00023002 #define GLFW_COCOA_FRAME_NAME 0x00023002
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003 #define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
#define GLFW_X11_CLASS_NAME 0x00024001
#define GLFW_X11_INSTANCE_NAME 0x00024002
/*! @} */ /*! @} */
#define GLFW_NO_API 0 #define GLFW_NO_API 0
@@ -963,6 +987,7 @@ extern "C" {
#define GLFW_CURSOR 0x00033001 #define GLFW_CURSOR 0x00033001
#define GLFW_STICKY_KEYS 0x00033002 #define GLFW_STICKY_KEYS 0x00033002
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_HIDDEN 0x00034002
@@ -1025,9 +1050,6 @@ extern "C" {
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001 #define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
#define GLFW_COCOA_MENUBAR 0x00051002 #define GLFW_COCOA_MENUBAR 0x00051002
#define GLFW_X11_WM_CLASS_NAME 0x00052001
#define GLFW_X11_WM_CLASS_CLASS 0x00052002
/*! @} */ /*! @} */
#define GLFW_DONT_CARE -1 #define GLFW_DONT_CARE -1
@@ -1046,7 +1068,7 @@ extern "C" {
* @sa @ref glfwGetProcAddress * @sa @ref glfwGetProcAddress
* *
* @since Added in version 3.0. * @since Added in version 3.0.
*
* @ingroup context * @ingroup context
*/ */
typedef void (*GLFWglproc)(void); typedef void (*GLFWglproc)(void);
@@ -1257,6 +1279,24 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
*/ */
typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
/*! @brief The function signature for window content scale callbacks.
*
* This is the function signature for window content scale callback
* functions.
*
* @param[in] window The window whose content scale changed.
* @param[in] xscale The new x-axis content scale of the window.
* @param[in] yscale The new y-axis content scale of the window.
*
* @sa @ref window_scale
* @sa @ref glfwSetWindowContentScaleCallback
*
* @since Added in version 3.3.
*
* @ingroup window
*/
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
/*! @brief The function signature for mouse button callbacks. /*! @brief The function signature for mouse button callbacks.
* *
* This is the function signature for mouse button callback functions. * This is the function signature for mouse button callback functions.
@@ -1623,8 +1663,7 @@ GLFWAPI void glfwTerminate(void);
/*! @brief Sets the specified init hint to the desired value. /*! @brief Sets the specified init hint to the desired value.
* *
* This function sets hints for the next initialization of GLFW. Only integer * This function sets hints for the next initialization of GLFW.
* type hints can be set with this function.
* *
* The values you set hints to are never reset by GLFW, but they only take * The values you set hints to are never reset by GLFW, but they only take
* effect during initialization. Once GLFW has been initialized, any values * effect during initialization. Once GLFW has been initialized, any values
@@ -1647,7 +1686,6 @@ GLFWAPI void glfwTerminate(void);
* *
* @sa init_hints * @sa init_hints
* @sa glfwInit * @sa glfwInit
* @sa glfwInitHintString
* *
* @since Added in version 3.3. * @since Added in version 3.3.
* *
@@ -1655,40 +1693,6 @@ GLFWAPI void glfwTerminate(void);
*/ */
GLFWAPI void glfwInitHint(int hint, int value); GLFWAPI void glfwInitHint(int hint, int value);
/*! @brief Sets the specified init hint to the desired value.
*
* This function sets hints for the next initialization of GLFW. Only string
* type hints can be set with this function.
*
* The values you set hints to are never reset by GLFW, but they only take
* effect during initialization. Once GLFW has been initialized, any values
* you set will be ignored until the library is terminated and initialized
* again.
*
* Some hints are platform specific. These may be set on any platform but they
* will only affect their specific platform. Other platforms will ignore them.
* Setting these hints requires no platform specific headers or functions.
*
* @param[in] hint The [init hint](@ref init_hints) to set.
* @param[in] value The new value of the init hint.
*
* @errors Possible errors include @ref GLFW_INVALID_ENUM and @ref
* GLFW_INVALID_VALUE.
*
* @remarks This function may be called before @ref glfwInit.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa init_hints
* @sa glfwInit
* @sa glfwInitHint
*
* @since Added in version 3.3.
*
* @ingroup init
*/
GLFWAPI void glfwInitHintString(int hint, const char* value);
/*! @brief Retrieves the version of the GLFW library. /*! @brief Retrieves the version of the GLFW library.
* *
* This function retrieves the major, minor and revision numbers of the GLFW * This function retrieves the major, minor and revision numbers of the GLFW
@@ -1985,6 +1989,56 @@ GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* monitor, float* xscale, flo
*/ */
GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor); GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor);
/*! @brief Sets the user pointer of the specified monitor.
*
* This function sets the user-defined pointer of the specified monitor. The
* current value is retained until the monitor is disconnected. The initial
* value is `NULL`.
*
* This function may be called from the monitor callback, even for a monitor
* that is being disconnected.
*
* @param[in] monitor The monitor whose pointer to set.
* @param[in] pointer The new value.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref monitor_userptr
* @sa @ref glfwGetMonitorUserPointer
*
* @since Added in version 3.3.
*
* @ingroup monitor
*/
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* monitor, void* pointer);
/*! @brief Returns the user pointer of the specified monitor.
*
* This function returns the current value of the user-defined pointer of the
* specified monitor. The initial value is `NULL`.
*
* This function may be called from the monitor callback, even for a monitor
* that is being disconnected.
*
* @param[in] monitor The monitor whose pointer to return.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref monitor_userptr
* @sa @ref glfwSetMonitorUserPointer
*
* @since Added in version 3.3.
*
* @ingroup monitor
*/
GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* monitor);
/*! @brief Sets the monitor configuration callback. /*! @brief Sets the monitor configuration callback.
* *
* This function sets the monitor configuration callback, or removes the * This function sets the monitor configuration callback, or removes the
@@ -2184,6 +2238,7 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* monitor, const GLFWgammaramp* ramp);
* *
* @sa @ref window_hints * @sa @ref window_hints
* @sa @ref glfwWindowHint * @sa @ref glfwWindowHint
* @sa @ref glfwWindowHintString
* *
* @since Added in version 3.0. * @since Added in version 3.0.
* *
@@ -2194,14 +2249,20 @@ GLFWAPI void glfwDefaultWindowHints(void);
/*! @brief Sets the specified window hint to the desired value. /*! @brief Sets the specified window hint to the desired value.
* *
* This function sets hints for the next call to @ref glfwCreateWindow. The * This function sets hints for the next call to @ref glfwCreateWindow. The
* hints, once set, retain their values until changed by a call to @ref * hints, once set, retain their values until changed by a call to this
* glfwWindowHint or @ref glfwDefaultWindowHints, or until the library is * function or @ref glfwDefaultWindowHints, or until the library is terminated.
* terminated. *
* Only integer value hints can be set with this function. String value hints
* are set with @ref glfwWindowHintString.
* *
* This function does not check whether the specified hint values are valid. * This function does not check whether the specified hint values are valid.
* If you set hints to invalid values this will instead be reported by the next * If you set hints to invalid values this will instead be reported by the next
* call to @ref glfwCreateWindow. * call to @ref glfwCreateWindow.
* *
* Some hints are platform specific. These may be set on any platform but they
* will only affect their specific platform. Other platforms will ignore them.
* Setting these hints requires no platform specific headers or functions.
*
* @param[in] hint The [window hint](@ref window_hints) to set. * @param[in] hint The [window hint](@ref window_hints) to set.
* @param[in] value The new value of the window hint. * @param[in] value The new value of the window hint.
* *
@@ -2211,6 +2272,7 @@ GLFWAPI void glfwDefaultWindowHints(void);
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
* @sa @ref window_hints * @sa @ref window_hints
* @sa @ref glfwWindowHintString
* @sa @ref glfwDefaultWindowHints * @sa @ref glfwDefaultWindowHints
* *
* @since Added in version 3.0. Replaces `glfwOpenWindowHint`. * @since Added in version 3.0. Replaces `glfwOpenWindowHint`.
@@ -2219,6 +2281,44 @@ GLFWAPI void glfwDefaultWindowHints(void);
*/ */
GLFWAPI void glfwWindowHint(int hint, int value); GLFWAPI void glfwWindowHint(int hint, int value);
/*! @brief Sets the specified window hint to the desired value.
*
* This function sets hints for the next call to @ref glfwCreateWindow. The
* hints, once set, retain their values until changed by a call to this
* function or @ref glfwDefaultWindowHints, or until the library is terminated.
*
* Only string type hints can be set with this function. Integer value hints
* are set with @ref glfwWindowHint.
*
* This function does not check whether the specified hint values are valid.
* If you set hints to invalid values this will instead be reported by the next
* call to @ref glfwCreateWindow.
*
* Some hints are platform specific. These may be set on any platform but they
* will only affect their specific platform. Other platforms will ignore them.
* Setting these hints requires no platform specific headers or functions.
*
* @param[in] hint The [window hint](@ref window_hints) to set.
* @param[in] value The new value of the window hint.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM.
*
* @pointer_lifetime The specified string is copied before this function
* returns.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_hints
* @sa @ref glfwWindowHint
* @sa @ref glfwDefaultWindowHints
*
* @since Added in version 3.3.
*
* @ingroup window
*/
GLFWAPI void glfwWindowHintString(int hint, const char* value);
/*! @brief Creates a window and its associated context. /*! @brief Creates a window and its associated context.
* *
* This function creates a window and its associated OpenGL or OpenGL ES * This function creates a window and its associated OpenGL or OpenGL ES
@@ -2329,9 +2429,8 @@ GLFWAPI void glfwWindowHint(int hint, int value);
* `CMake/MacOSXBundleInfo.plist.in` in the source tree. * `CMake/MacOSXBundleInfo.plist.in` in the source tree.
* *
* @remark @macos When activating frame autosaving with * @remark @macos When activating frame autosaving with
* [GLFW_COCOA_FRAME_AUTOSAVE](@ref GLFW_COCOA_FRAME_AUTOSAVE_hint), the * [GLFW_COCOA_FRAME_NAME](@ref GLFW_COCOA_FRAME_NAME_hint), the specified
* specified window size may be overriden by a previously saved size and * window size and position may be overriden by previously saved values.
* position.
* *
* @remark @x11 Some window managers will not respect the placement of * @remark @x11 Some window managers will not respect the placement of
* initially hidden windows. * initially hidden windows.
@@ -2341,15 +2440,12 @@ GLFWAPI void glfwWindowHint(int hint, int value);
* query the final size, position or other attributes directly after window * query the final size, position or other attributes directly after window
* creation. * creation.
* *
* @remark @x11 The name and class of the `WM_CLASS` window property will by * @remark @x11 The class part of the `WM_CLASS` window property will by
* default be set to the window title passed to this function. Set the @ref * default be set to the window title passed to this function. The instance
* GLFW_X11_WM_CLASS_NAME and @ref GLFW_X11_WM_CLASS_CLASS init hints before * part will use the contents of the `RESOURCE_NAME` environment variable, if
* initialization to override this. You can also set the title int he following * present and not empty, or fall back to the window title. Set the @ref
* special format, which allows setting the two parts of the WM_CLASS property * GLFW_X11_CLASS_NAME and @ref GLFW_X11_INSTANCE_NAME window hints to override
* and the window title independently: * this.
* <01> WM_CLASS name <30> WM_CLASS class <30> title
* Here <01> refers to the byte value 01 (ASCII start-of-header) and <30> refers
* to the byte value 30 (ASCII record separator).
* *
* @remark @wayland The window frame is currently unimplemented, as if * @remark @wayland The window frame is currently unimplemented, as if
* [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`. * [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`.
@@ -2836,6 +2932,7 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
* @thread_safety This function must only be called from the main thread. * @thread_safety This function must only be called from the main thread.
* *
* @sa @ref window_scale * @sa @ref window_scale
* @sa @ref glfwSetWindowContentScaleCallback
* @sa @ref glfwGetMonitorContentScale * @sa @ref glfwGetMonitorContentScale
* *
* @since Added in version 3.3. * @since Added in version 3.3.
@@ -3525,6 +3622,30 @@ GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window,
*/ */
GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun); GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun);
/*! @brief Sets the window content scale callback for the specified window.
*
* This function sets the window content scale callback of the specified window,
* which is called when the content scale of the specified window changes.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or the
* library had not been [initialized](@ref intro_init).
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref window_scale
* @sa @ref glfwGetWindowContentScale
*
* @since Added in version 3.3.
*
* @ingroup window
*/
GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* window, GLFWwindowcontentscalefun cbfun);
/*! @brief Processes all pending events. /*! @brief Processes all pending events.
* *
* This function processes only those events that are already in the event * This function processes only those events that are already in the event
@@ -3689,12 +3810,12 @@ GLFWAPI void glfwPostEmptyEvent(void);
/*! @brief Returns the value of an input option for the specified window. /*! @brief Returns the value of an input option for the specified window.
* *
* This function returns the value of an input option for the specified window. * This function returns the value of an input option for the specified window.
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS or * The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS. * @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
* *
* @param[in] window The window to query. * @param[in] window The window to query.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`. * `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM. * GLFW_INVALID_ENUM.
@@ -3712,8 +3833,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
/*! @brief Sets an input option for the specified window. /*! @brief Sets an input option for the specified window.
* *
* This function sets an input mode option for the specified window. The mode * This function sets an input mode option for the specified window. The mode
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS or * must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS. * @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
* *
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor * If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
* modes: * modes:
@@ -3739,9 +3860,15 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* you are only interested in whether mouse buttons have been pressed but not * you are only interested in whether mouse buttons have been pressed but not
* when or in which order. * when or in which order.
* *
* If the mode is `GLFW_LOCK_KEY_MODS`, the value must be either `GLFW_TRUE` to
* enable lock key modifier bits, or `GLFW_FALSE` to disable them. If enabled,
* callbacks that receive modifier bits will also have the @ref
* GLFW_MOD_CAPS_LOCK bit set when the event was generated with Caps Lock on,
* and the @ref GLFW_MOD_NUM_LOCK bit when Num Lock was on.
*
* @param[in] window The window whose input mode to set. * @param[in] window The window whose input mode to set.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or * @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`. * `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
* @param[in] value The new value of the specified input mode. * @param[in] value The new value of the specified input mode.
* *
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref * @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
@@ -4588,6 +4715,56 @@ GLFWAPI const char* glfwGetJoystickName(int jid);
*/ */
GLFWAPI const char* glfwGetJoystickGUID(int jid); GLFWAPI const char* glfwGetJoystickGUID(int jid);
/*! @brief Sets the user pointer of the specified joystick.
*
* This function sets the user-defined pointer of the specified joystick. The
* current value is retained until the joystick is disconnected. The initial
* value is `NULL`.
*
* This function may be called from the joystick callback, even for a joystick
* that is being disconnected.
*
* @param[in] joystick The joystick whose pointer to set.
* @param[in] pointer The new value.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref joystick_userptr
* @sa @ref glfwGetJoystickUserPointer
*
* @since Added in version 3.3.
*
* @ingroup input
*/
GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer);
/*! @brief Returns the user pointer of the specified joystick.
*
* This function returns the current value of the user-defined pointer of the
* specified joystick. The initial value is `NULL`.
*
* This function may be called from the joystick callback, even for a joystick
* that is being disconnected.
*
* @param[in] joystick The joystick whose pointer to return.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
*
* @thread_safety This function may be called from any thread. Access is not
* synchronized.
*
* @sa @ref joystick_userptr
* @sa @ref glfwSetJoystickUserPointer
*
* @since Added in version 3.3.
*
* @ingroup input
*/
GLFWAPI void* glfwGetJoystickUserPointer(int jid);
/*! @brief Returns whether the specified joystick has a gamepad mapping. /*! @brief Returns whether the specified joystick has a gamepad mapping.
* *
* This function returns whether the specified joystick is both present and has * This function returns whether the specified joystick is both present and has

26
glfw/init.c vendored
View File

@@ -53,10 +53,6 @@ static _GLFWinitconfig _glfwInitHints =
{ {
GLFW_TRUE, // macOS menu bar GLFW_TRUE, // macOS menu bar
GLFW_TRUE // macOS bundle chdir GLFW_TRUE // macOS bundle chdir
},
{
"", // X11 WM_CLASS name
"" // X11 WM_CLASS class
} }
}; };
@@ -260,27 +256,7 @@ GLFWAPI void glfwInitHint(int hint, int value)
} }
_glfwInputError(GLFW_INVALID_ENUM, _glfwInputError(GLFW_INVALID_ENUM,
"Invalid integer type init hint 0x%08X", hint); "Invalid init hint 0x%08X", hint);
}
GLFWAPI void glfwInitHintString(int hint, const char* value)
{
assert(value != NULL);
switch (hint)
{
case GLFW_X11_WM_CLASS_NAME:
strncpy(_glfwInitHints.x11.className, value,
sizeof(_glfwInitHints.x11.className) - 1);
return;
case GLFW_X11_WM_CLASS_CLASS:
strncpy(_glfwInitHints.x11.classClass, value,
sizeof(_glfwInitHints.x11.classClass) - 1);
return;
}
_glfwInputError(GLFW_INVALID_ENUM,
"Invalid string type init hint 0x%08X", hint);
} }
GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev) GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev)

109
glfw/input.c vendored
View File

@@ -57,6 +57,58 @@ static _GLFWmapping* findMapping(const char* guid)
return NULL; return NULL;
} }
// Checks whether a gamepad mapping element is present in the hardware
//
static GLFWbool isValidElementForJoystick(const _GLFWmapelement* e,
const _GLFWjoystick* js)
{
if (e->type == _GLFW_JOYSTICK_HATBIT && (e->value >> 4) >= js->hatCount)
return GLFW_FALSE;
else if (e->type == _GLFW_JOYSTICK_BUTTON && e->value >= js->buttonCount)
return GLFW_FALSE;
else if (e->type == _GLFW_JOYSTICK_AXIS && e->value >= js->axisCount)
return GLFW_FALSE;
return GLFW_TRUE;
}
// Finds a mapping based on joystick GUID and verifies element indices
//
static _GLFWmapping* findValidMapping(const _GLFWjoystick* js)
{
_GLFWmapping* mapping = findMapping(js->guid);
if (mapping)
{
int i;
for (i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++)
{
if (!isValidElementForJoystick(mapping->buttons + i, js))
{
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid button in gamepad mapping %s (%s)",
mapping->guid,
mapping->name);
return NULL;
}
}
for (i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++)
{
if (!isValidElementForJoystick(mapping->axes + i, js))
{
_glfwInputError(GLFW_INVALID_VALUE,
"Invalid axis in gamepad mapping %s (%s)",
mapping->guid,
mapping->name);
return NULL;
}
}
}
return mapping;
}
// Parses an SDL_GameControllerDB line and adds it to the mapping list // Parses an SDL_GameControllerDB line and adds it to the mapping list
// //
static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string) static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
@@ -193,6 +245,9 @@ void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int m
action = GLFW_REPEAT; action = GLFW_REPEAT;
} }
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.key) if (window->callbacks.key)
window->callbacks.key((GLFWwindow*) window, key, scancode, action, mods); window->callbacks.key((GLFWwindow*) window, key, scancode, action, mods);
} }
@@ -202,6 +257,9 @@ void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWb
if (codepoint < 32 || (codepoint > 126 && codepoint < 160)) if (codepoint < 32 || (codepoint > 126 && codepoint < 160))
return; return;
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (window->callbacks.charmods) if (window->callbacks.charmods)
window->callbacks.charmods((GLFWwindow*) window, codepoint, mods); window->callbacks.charmods((GLFWwindow*) window, codepoint, mods);
@@ -223,6 +281,9 @@ void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods)
if (button < 0 || button > GLFW_MOUSE_BUTTON_LAST) if (button < 0 || button > GLFW_MOUSE_BUTTON_LAST)
return; return;
if (!window->lockKeyMods)
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
if (action == GLFW_RELEASE && window->stickyMouseButtons) if (action == GLFW_RELEASE && window->stickyMouseButtons)
window->mouseButtons[button] = _GLFW_STICK; window->mouseButtons[button] = _GLFW_STICK;
else else
@@ -318,9 +379,9 @@ _GLFWjoystick* _glfwAllocJoystick(const char* name,
js->axisCount = axisCount; js->axisCount = axisCount;
js->buttonCount = buttonCount; js->buttonCount = buttonCount;
js->hatCount = hatCount; js->hatCount = hatCount;
js->mapping = findMapping(guid);
strcpy(js->guid, guid); strcpy(js->guid, guid);
js->mapping = findValidMapping(js);
return js; return js;
} }
@@ -354,6 +415,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
return window->stickyKeys; return window->stickyKeys;
case GLFW_STICKY_MOUSE_BUTTONS: case GLFW_STICKY_MOUSE_BUTTONS:
return window->stickyMouseButtons; return window->stickyMouseButtons;
case GLFW_LOCK_KEY_MODS:
return window->lockKeyMods;
} }
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
@@ -409,7 +472,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
} }
} }
window->stickyKeys = value ? GLFW_TRUE : GLFW_FALSE; window->stickyKeys = value;
} }
else if (mode == GLFW_STICKY_MOUSE_BUTTONS) else if (mode == GLFW_STICKY_MOUSE_BUTTONS)
{ {
@@ -429,8 +492,10 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
} }
} }
window->stickyMouseButtons = value ? GLFW_TRUE : GLFW_FALSE; window->stickyMouseButtons = value;
} }
else if (mode == GLFW_LOCK_KEY_MODS)
window->lockKeyMods = value ? GLFW_TRUE : GLFW_FALSE;
else else
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode); _glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
} }
@@ -914,6 +979,38 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid)
return js->guid; return js->guid;
} }
GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer)
{
_GLFWjoystick* js;
assert(jid >= GLFW_JOYSTICK_1);
assert(jid <= GLFW_JOYSTICK_LAST);
_GLFW_REQUIRE_INIT();
js = _glfw.joysticks + jid;
if (!js->present)
return;
js->userPointer = pointer;
}
GLFWAPI void* glfwGetJoystickUserPointer(int jid)
{
_GLFWjoystick* js;
assert(jid >= GLFW_JOYSTICK_1);
assert(jid <= GLFW_JOYSTICK_LAST);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
js = _glfw.joysticks + jid;
if (!js->present)
return NULL;
return js->userPointer;
}
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun) GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
{ {
_GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -973,7 +1070,7 @@ GLFWAPI int glfwUpdateGamepadMappings(const char* string)
{ {
_GLFWjoystick* js = _glfw.joysticks + jid; _GLFWjoystick* js = _glfw.joysticks + jid;
if (js->present) if (js->present)
js->mapping = findMapping(js->guid); js->mapping = findValidMapping(js);
} }
return GLFW_TRUE; return GLFW_TRUE;
@@ -1085,8 +1182,8 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
state->axes[i] = js->axes[js->mapping->axes[i].value]; state->axes[i] = js->axes[js->mapping->axes[i].value];
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT) else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT)
{ {
const unsigned int hat = js->mapping->buttons[i].value >> 4; const unsigned int hat = js->mapping->axes[i].value >> 4;
const unsigned int bit = js->mapping->buttons[i].value & 0xf; const unsigned int bit = js->mapping->axes[i].value & 0xf;
if (js->hats[hat] & bit) if (js->hats[hat] & bit)
state->axes[i] = 1.f; state->axes[i] = 1.f;
} }

26
glfw/internal.h vendored
View File

@@ -281,10 +281,6 @@ struct _GLFWinitconfig
GLFWbool menubar; GLFWbool menubar;
GLFWbool chdir; GLFWbool chdir;
} ns; } ns;
struct {
char className[256];
char classClass[256];
} x11;
}; };
/*! @brief Window configuration. /*! @brief Window configuration.
@@ -308,8 +304,12 @@ struct _GLFWwndconfig
GLFWbool centerCursor; GLFWbool centerCursor;
struct { struct {
GLFWbool retina; GLFWbool retina;
GLFWbool frame; char frameName[256];
} ns; } ns;
struct {
char className[256];
char instanceName[256];
} x11;
}; };
/*! @brief Context configuration. /*! @brief Context configuration.
@@ -419,6 +419,7 @@ struct _GLFWwindow
GLFWbool stickyKeys; GLFWbool stickyKeys;
GLFWbool stickyMouseButtons; GLFWbool stickyMouseButtons;
GLFWbool lockKeyMods;
int cursorMode; int cursorMode;
char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1]; char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1];
char keys[GLFW_KEY_LAST + 1]; char keys[GLFW_KEY_LAST + 1];
@@ -436,6 +437,7 @@ struct _GLFWwindow
GLFWwindowiconifyfun iconify; GLFWwindowiconifyfun iconify;
GLFWwindowmaximizefun maximize; GLFWwindowmaximizefun maximize;
GLFWframebuffersizefun fbsize; GLFWframebuffersizefun fbsize;
GLFWwindowcontentscalefun scale;
GLFWmousebuttonfun mouseButton; GLFWmousebuttonfun mouseButton;
GLFWcursorposfun cursorPos; GLFWcursorposfun cursorPos;
GLFWcursorenterfun cursorEnter; GLFWcursorenterfun cursorEnter;
@@ -455,6 +457,7 @@ struct _GLFWwindow
struct _GLFWmonitor struct _GLFWmonitor
{ {
char* name; char* name;
void* userPointer;
// Physical dimensions in millimeters. // Physical dimensions in millimeters.
int widthMM, heightMM; int widthMM, heightMM;
@@ -513,6 +516,7 @@ struct _GLFWjoystick
unsigned char* hats; unsigned char* hats;
int hatCount; int hatCount;
char* name; char* name;
void* userPointer;
char guid[33]; char guid[33];
_GLFWmapping* mapping; _GLFWmapping* mapping;
@@ -687,6 +691,7 @@ int _glfwPlatformWindowFocused(_GLFWwindow* window);
int _glfwPlatformWindowIconified(_GLFWwindow* window); int _glfwPlatformWindowIconified(_GLFWwindow* window);
int _glfwPlatformWindowVisible(_GLFWwindow* window); int _glfwPlatformWindowVisible(_GLFWwindow* window);
int _glfwPlatformWindowMaximized(_GLFWwindow* window); int _glfwPlatformWindowMaximized(_GLFWwindow* window);
int _glfwPlatformWindowHovered(_GLFWwindow* window);
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window); int _glfwPlatformFramebufferTransparent(_GLFWwindow* window);
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window); float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled); void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
@@ -752,6 +757,14 @@ void _glfwInputWindowSize(_GLFWwindow* window, int width, int height);
*/ */
void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height); void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height);
/*! @brief Notifies shared code that a window content scale has changed.
* @param[in] window The window that received the event.
* @param[in] xscale The new x-axis content scale of the window.
* @param[in] yscale The new y-axis content scale of the window.
* @ingroup event
*/
void _glfwInputWindowContentScale(_GLFWwindow* window, float xscale, float yscale);
/*! @brief Notifies shared code that a window has been iconified or restored. /*! @brief Notifies shared code that a window has been iconified or restored.
* @param[in] window The window that received the event. * @param[in] window The window that received the event.
* @param[in] iconified `GLFW_TRUE` if the window was iconified, or * @param[in] iconified `GLFW_TRUE` if the window was iconified, or
@@ -952,7 +965,8 @@ const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
* unusable. * unusable.
* @ingroup utility * @ingroup utility
*/ */
GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig); GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig);
/*! @brief Checks whether the desired context attributes are valid. /*! @brief Checks whether the desired context attributes are valid.
* @param[in] ctxconfig The context attributes to check. * @param[in] ctxconfig The context attributes to check.

28
glfw/monitor.c vendored
View File

@@ -351,6 +351,24 @@ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle)
return monitor->name; return monitor->name;
} }
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* handle, void* pointer)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT();
monitor->userPointer = pointer;
}
GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* handle)
{
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
assert(monitor != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return monitor->userPointer;
}
GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun) GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun)
{ {
_GLFW_REQUIRE_INIT_OR_RETURN(NULL); _GLFW_REQUIRE_INIT_OR_RETURN(NULL);
@@ -406,16 +424,16 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
for (i = 0; i < 256; i++) for (i = 0; i < 256; i++)
{ {
double value; float value;
// Calculate intensity // Calculate intensity
value = i / 255.0; value = i / 255.f;
// Apply gamma curve // Apply gamma curve
value = pow(value, 1.0 / gamma) * 65535.0 + 0.5; value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
// Clamp to value range // Clamp to value range
if (value > 65535.0) if (value > 65535.f)
value = 65535.0; value = 65535.f;
values[i] = (unsigned short) value; values[i] = (unsigned short) value;
} }

5
glfw/null_window.c vendored
View File

@@ -165,6 +165,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return GLFW_FALSE; return GLFW_FALSE;
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
return GLFW_FALSE;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return GLFW_FALSE; return GLFW_FALSE;

View File

@@ -79,6 +79,10 @@
"osmesa_context.c" "osmesa_context.c"
] ]
}, },
"wayland_protocols": [
1,
6
],
"win32": { "win32": {
"headers": [ "headers": [
"win32_platform.h", "win32_platform.h",

91
glfw/wgl_context.c vendored
View File

@@ -329,21 +329,52 @@ static void destroyContextWGL(_GLFWwindow* window)
} }
} }
// Initialize WGL-specific extensions
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize WGL
// //
static void loadWGLExtensions(void) GLFWbool _glfwInitWGL(void)
{ {
PIXELFORMATDESCRIPTOR pfd; PIXELFORMATDESCRIPTOR pfd;
HGLRC rc; HGLRC prc, rc;
HDC dc = GetDC(_glfw.win32.helperWindowHandle);; HDC pdc, dc;
_glfw.wgl.extensionsLoaded = GLFW_TRUE; if (_glfw.wgl.instance)
return GLFW_TRUE;
_glfw.wgl.instance = LoadLibraryA("opengl32.dll");
if (!_glfw.wgl.instance)
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to load opengl32.dll");
return GLFW_FALSE;
}
_glfw.wgl.CreateContext = (PFN_wglCreateContext)
GetProcAddress(_glfw.wgl.instance, "wglCreateContext");
_glfw.wgl.DeleteContext = (PFN_wglDeleteContext)
GetProcAddress(_glfw.wgl.instance, "wglDeleteContext");
_glfw.wgl.GetProcAddress = (PFN_wglGetProcAddress)
GetProcAddress(_glfw.wgl.instance, "wglGetProcAddress");
_glfw.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentDC");
_glfw.wgl.GetCurrentContext = (PFN_wglGetCurrentContext)
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentContext");
_glfw.wgl.MakeCurrent = (PFN_wglMakeCurrent)
GetProcAddress(_glfw.wgl.instance, "wglMakeCurrent");
_glfw.wgl.ShareLists = (PFN_wglShareLists)
GetProcAddress(_glfw.wgl.instance, "wglShareLists");
// NOTE: A dummy context has to be created for opengl32.dll to load the // NOTE: A dummy context has to be created for opengl32.dll to load the
// OpenGL ICD, from which we can then query WGL extensions // OpenGL ICD, from which we can then query WGL extensions
// NOTE: This code will accept the Microsoft GDI ICD; accelerated context // NOTE: This code will accept the Microsoft GDI ICD; accelerated context
// creation failure occurs during manual pixel format enumeration // creation failure occurs during manual pixel format enumeration
dc = GetDC(_glfw.win32.helperWindowHandle);;
ZeroMemory(&pfd, sizeof(pfd)); ZeroMemory(&pfd, sizeof(pfd));
pfd.nSize = sizeof(pfd); pfd.nSize = sizeof(pfd);
pfd.nVersion = 1; pfd.nVersion = 1;
@@ -355,7 +386,7 @@ static void loadWGLExtensions(void)
{ {
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR, _glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to set pixel format for dummy context"); "WGL: Failed to set pixel format for dummy context");
return; return GLFW_FALSE;
} }
rc = wglCreateContext(dc); rc = wglCreateContext(dc);
@@ -363,15 +394,19 @@ static void loadWGLExtensions(void)
{ {
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR, _glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to create dummy context"); "WGL: Failed to create dummy context");
return; return GLFW_FALSE;
} }
pdc = wglGetCurrentDC();
prc = wglGetCurrentContext();
if (!wglMakeCurrent(dc, rc)) if (!wglMakeCurrent(dc, rc))
{ {
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR, _glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to make dummy context current"); "WGL: Failed to make dummy context current");
wglMakeCurrent(pdc, prc);
wglDeleteContext(rc); wglDeleteContext(rc);
return; return GLFW_FALSE;
} }
// NOTE: Functions must be loaded first as they're needed to retrieve the // NOTE: Functions must be loaded first as they're needed to retrieve the
@@ -414,43 +449,8 @@ static void loadWGLExtensions(void)
_glfw.wgl.ARB_context_flush_control = _glfw.wgl.ARB_context_flush_control =
extensionSupportedWGL("WGL_ARB_context_flush_control"); extensionSupportedWGL("WGL_ARB_context_flush_control");
wglMakeCurrent(dc, NULL); wglMakeCurrent(pdc, prc);
wglDeleteContext(rc); wglDeleteContext(rc);
}
//////////////////////////////////////////////////////////////////////////
////// GLFW internal API //////
//////////////////////////////////////////////////////////////////////////
// Initialize WGL
//
GLFWbool _glfwInitWGL(void)
{
if (_glfw.wgl.instance)
return GLFW_TRUE;
_glfw.wgl.instance = LoadLibraryA("opengl32.dll");
if (!_glfw.wgl.instance)
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to load opengl32.dll");
return GLFW_FALSE;
}
_glfw.wgl.CreateContext = (PFN_wglCreateContext)
GetProcAddress(_glfw.wgl.instance, "wglCreateContext");
_glfw.wgl.DeleteContext = (PFN_wglDeleteContext)
GetProcAddress(_glfw.wgl.instance, "wglDeleteContext");
_glfw.wgl.GetProcAddress = (PFN_wglGetProcAddress)
GetProcAddress(_glfw.wgl.instance, "wglGetProcAddress");
_glfw.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentDC");
_glfw.wgl.MakeCurrent = (PFN_wglMakeCurrent)
GetProcAddress(_glfw.wgl.instance, "wglMakeCurrent");
_glfw.wgl.ShareLists = (PFN_wglShareLists)
GetProcAddress(_glfw.wgl.instance, "wglShareLists");
return GLFW_TRUE; return GLFW_TRUE;
} }
@@ -480,9 +480,6 @@ GLFWbool _glfwCreateContextWGL(_GLFWwindow* window,
PIXELFORMATDESCRIPTOR pfd; PIXELFORMATDESCRIPTOR pfd;
HGLRC share = NULL; HGLRC share = NULL;
if (!_glfw.wgl.extensionsLoaded)
loadWGLExtensions();
if (ctxconfig->share) if (ctxconfig->share)
share = ctxconfig->share->context.wgl.handle; share = ctxconfig->share->context.wgl.handle;

5
glfw/wgl_context.h vendored
View File

@@ -86,6 +86,7 @@ typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC);
typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC); typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC);
typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR); typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR);
typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void); typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void);
typedef HGLRC (WINAPI * PFN_wglGetCurrentContext)(void);
typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC); typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC);
typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC); typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC);
@@ -94,6 +95,7 @@ typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC);
#define wglDeleteContext _glfw.wgl.DeleteContext #define wglDeleteContext _glfw.wgl.DeleteContext
#define wglGetProcAddress _glfw.wgl.GetProcAddress #define wglGetProcAddress _glfw.wgl.GetProcAddress
#define wglGetCurrentDC _glfw.wgl.GetCurrentDC #define wglGetCurrentDC _glfw.wgl.GetCurrentDC
#define wglGetCurrentContext _glfw.wgl.GetCurrentContext
#define wglMakeCurrent _glfw.wgl.MakeCurrent #define wglMakeCurrent _glfw.wgl.MakeCurrent
#define wglShareLists _glfw.wgl.ShareLists #define wglShareLists _glfw.wgl.ShareLists
@@ -124,11 +126,10 @@ typedef struct _GLFWlibraryWGL
PFN_wglDeleteContext DeleteContext; PFN_wglDeleteContext DeleteContext;
PFN_wglGetProcAddress GetProcAddress; PFN_wglGetProcAddress GetProcAddress;
PFN_wglGetCurrentDC GetCurrentDC; PFN_wglGetCurrentDC GetCurrentDC;
PFN_wglGetCurrentContext GetCurrentContext;
PFN_wglMakeCurrent MakeCurrent; PFN_wglMakeCurrent MakeCurrent;
PFN_wglShareLists ShareLists; PFN_wglShareLists ShareLists;
GLFWbool extensionsLoaded;
PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT; PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT;
PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB; PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB;
PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT; PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT;

10
glfw/win32_init.c vendored
View File

@@ -351,9 +351,10 @@ static HWND createHelperWindow(void)
dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
dbi.dbcc_classguid = GUID_DEVINTERFACE_HID; dbi.dbcc_classguid = GUID_DEVINTERFACE_HID;
RegisterDeviceNotificationW(window, _glfw.win32.deviceNotificationHandle =
(DEV_BROADCAST_HDR*) &dbi, RegisterDeviceNotificationW(window,
DEVICE_NOTIFY_WINDOW_HANDLE); (DEV_BROADCAST_HDR*) &dbi,
DEVICE_NOTIFY_WINDOW_HANDLE);
} }
while (PeekMessageW(&msg, _glfw.win32.helperWindowHandle, 0, 0, PM_REMOVE)) while (PeekMessageW(&msg, _glfw.win32.helperWindowHandle, 0, 0, PM_REMOVE))
@@ -543,6 +544,9 @@ int _glfwPlatformInit(void)
void _glfwPlatformTerminate(void) void _glfwPlatformTerminate(void)
{ {
if (_glfw.win32.deviceNotificationHandle)
UnregisterDeviceNotification(_glfw.win32.deviceNotificationHandle);
if (_glfw.win32.helperWindowHandle) if (_glfw.win32.helperWindowHandle)
DestroyWindow(_glfw.win32.helperWindowHandle); DestroyWindow(_glfw.win32.helperWindowHandle);

12
glfw/win32_platform.h vendored
View File

@@ -105,12 +105,11 @@
#endif #endif
#if WINVER < 0x0601 #if WINVER < 0x0601
typedef struct tagCHANGEFILTERSTRUCT typedef struct
{ {
DWORD cbSize; DWORD cbSize;
DWORD ExtStatus; DWORD ExtStatus;
} CHANGEFILTERSTRUCT;
} CHANGEFILTERSTRUCT, *PCHANGEFILTERSTRUCT;
#ifndef MSGFLT_ALLOW #ifndef MSGFLT_ALLOW
#define MSGFLT_ALLOW 1 #define MSGFLT_ALLOW 1
#endif #endif
@@ -129,13 +128,13 @@ typedef struct
#endif /*Windows Vista*/ #endif /*Windows Vista*/
#ifndef DPI_ENUMS_DECLARED #ifndef DPI_ENUMS_DECLARED
typedef enum PROCESS_DPI_AWARENESS typedef enum
{ {
PROCESS_DPI_UNAWARE = 0, PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1, PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2 PROCESS_PER_MONITOR_DPI_AWARE = 2
} PROCESS_DPI_AWARENESS; } PROCESS_DPI_AWARENESS;
typedef enum MONITOR_DPI_TYPE typedef enum
{ {
MDT_EFFECTIVE_DPI = 0, MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1, MDT_ANGULAR_DPI = 1,
@@ -209,7 +208,7 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
// user32.dll function pointer typedefs // user32.dll function pointer typedefs
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void); typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,PCHANGEFILTERSTRUCT); typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_ #define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_ #define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
@@ -289,6 +288,7 @@ typedef struct _GLFWwindowWin32
typedef struct _GLFWlibraryWin32 typedef struct _GLFWlibraryWin32
{ {
HWND helperWindowHandle; HWND helperWindowHandle;
HDEVNOTIFY deviceNotificationHandle;
DWORD foregroundLockTimeout; DWORD foregroundLockTimeout;
int acquiredMonitorCount; int acquiredMonitorCount;
char* clipboardString; char* clipboardString;

39
glfw/win32_window.c vendored
View File

@@ -384,14 +384,18 @@ static int getKeyMods(void)
{ {
int mods = 0; int mods = 0;
if (GetKeyState(VK_SHIFT) & (1 << 31)) if (GetKeyState(VK_SHIFT) & 0x8000)
mods |= GLFW_MOD_SHIFT; mods |= GLFW_MOD_SHIFT;
if (GetKeyState(VK_CONTROL) & (1 << 31)) if (GetKeyState(VK_CONTROL) & 0x8000)
mods |= GLFW_MOD_CONTROL; mods |= GLFW_MOD_CONTROL;
if (GetKeyState(VK_MENU) & (1 << 31)) if (GetKeyState(VK_MENU) & 0x8000)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1 << 31)) if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (GetKeyState(VK_CAPITAL) & 1)
mods |= GLFW_MOD_CAPS_LOCK;
if (GetKeyState(VK_NUMLOCK) & 1)
mods |= GLFW_MOD_NUM_LOCK;
return mods; return mods;
} }
@@ -402,14 +406,18 @@ static int getAsyncKeyMods(void)
{ {
int mods = 0; int mods = 0;
if (GetAsyncKeyState(VK_SHIFT) & (1 << 31)) if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
mods |= GLFW_MOD_SHIFT; mods |= GLFW_MOD_SHIFT;
if (GetAsyncKeyState(VK_CONTROL) & (1 << 31)) if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
mods |= GLFW_MOD_CONTROL; mods |= GLFW_MOD_CONTROL;
if (GetAsyncKeyState(VK_MENU) & (1 << 31)) if (GetAsyncKeyState(VK_MENU) & 0x8000)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & (1 << 31)) if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & 0x8000)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (GetAsyncKeyState(VK_CAPITAL) & 1)
mods |= GLFW_MOD_CAPS_LOCK;
if (GetAsyncKeyState(VK_NUMLOCK) & 1)
mods |= GLFW_MOD_NUM_LOCK;
return mods; return mods;
} }
@@ -988,6 +996,14 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
return 0; return 0;
} }
case WM_DPICHANGED:
{
const float xscale = HIWORD(wParam) / 96.f;
const float yscale = LOWORD(wParam) / 96.f;
_glfwInputWindowContentScale(window, xscale, yscale);
break;
}
case WM_SETCURSOR: case WM_SETCURSOR:
{ {
if (LOWORD(lParam) == HTCLIENT) if (LOWORD(lParam) == HTCLIENT)
@@ -1443,7 +1459,7 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
void _glfwPlatformShowWindow(_GLFWwindow* window) void _glfwPlatformShowWindow(_GLFWwindow* window)
{ {
ShowWindow(window->win32.handle, SW_SHOW); ShowWindow(window->win32.handle, SW_SHOWNA);
} }
void _glfwPlatformHideWindow(_GLFWwindow* window) void _glfwPlatformHideWindow(_GLFWwindow* window)
@@ -1567,6 +1583,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return IsZoomed(window->win32.handle); return IsZoomed(window->win32.handle);
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
return cursorInClientArea(window);
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return window->win32.transparent && _glfwIsCompositionEnabledWin32(); return window->win32.transparent && _glfwIsCompositionEnabledWin32();

63
glfw/window.c vendored
View File

@@ -94,6 +94,12 @@ void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height)
window->callbacks.fbsize((GLFWwindow*) window, width, height); window->callbacks.fbsize((GLFWwindow*) window, width, height);
} }
void _glfwInputWindowContentScale(_GLFWwindow* window, float xscale, float yscale)
{
if (window->callbacks.scale)
window->callbacks.scale((GLFWwindow*) window, xscale, yscale);
}
void _glfwInputWindowDamage(_GLFWwindow* window) void _glfwInputWindowDamage(_GLFWwindow* window)
{ {
if (window->callbacks.refresh) if (window->callbacks.refresh)
@@ -127,7 +133,6 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
_GLFWctxconfig ctxconfig; _GLFWctxconfig ctxconfig;
_GLFWwndconfig wndconfig; _GLFWwndconfig wndconfig;
_GLFWwindow* window; _GLFWwindow* window;
_GLFWwindow* previous;
assert(title != NULL); assert(title != NULL);
assert(width >= 0); assert(width >= 0);
@@ -191,33 +196,20 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
window->numer = GLFW_DONT_CARE; window->numer = GLFW_DONT_CARE;
window->denom = GLFW_DONT_CARE; window->denom = GLFW_DONT_CARE;
// Save the currently current context so it can be restored later
previous = _glfwPlatformGetTls(&_glfw.contextSlot);
if (ctxconfig.client != GLFW_NO_API)
glfwMakeContextCurrent(NULL);
// Open the actual window and create its context // Open the actual window and create its context
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig)) if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
{ {
glfwMakeContextCurrent((GLFWwindow*) previous);
glfwDestroyWindow((GLFWwindow*) window); glfwDestroyWindow((GLFWwindow*) window);
return NULL; return NULL;
} }
if (ctxconfig.client != GLFW_NO_API) if (ctxconfig.client != GLFW_NO_API)
{ {
window->context.makeCurrent(window); if (!_glfwRefreshContextAttribs(window, &ctxconfig))
// Retrieve the actual (as opposed to requested) context attributes
if (!_glfwRefreshContextAttribs(&ctxconfig))
{ {
glfwMakeContextCurrent((GLFWwindow*) previous);
glfwDestroyWindow((GLFWwindow*) window); glfwDestroyWindow((GLFWwindow*) window);
return NULL; return NULL;
} }
// Restore the previously current context (or NULL)
glfwMakeContextCurrent((GLFWwindow*) previous);
} }
if (!window->monitor) if (!window->monitor)
@@ -349,9 +341,6 @@ GLFWAPI void glfwWindowHint(int hint, int value)
case GLFW_COCOA_RETINA_FRAMEBUFFER: case GLFW_COCOA_RETINA_FRAMEBUFFER:
_glfw.hints.window.ns.retina = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.window.ns.retina = value ? GLFW_TRUE : GLFW_FALSE;
return; return;
case GLFW_COCOA_FRAME_AUTOSAVE:
_glfw.hints.window.ns.frame = value ? GLFW_TRUE : GLFW_FALSE;
return;
case GLFW_COCOA_GRAPHICS_SWITCHING: case GLFW_COCOA_GRAPHICS_SWITCHING:
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE; _glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
return; return;
@@ -396,6 +385,31 @@ GLFWAPI void glfwWindowHint(int hint, int value)
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint 0x%08X", hint); _glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint 0x%08X", hint);
} }
GLFWAPI void glfwWindowHintString(int hint, const char* value)
{
assert(value != NULL);
_GLFW_REQUIRE_INIT();
switch (hint)
{
case GLFW_COCOA_FRAME_NAME:
strncpy(_glfw.hints.window.ns.frameName, value,
sizeof(_glfw.hints.window.ns.frameName) - 1);
return;
case GLFW_X11_CLASS_NAME:
strncpy(_glfw.hints.window.x11.className, value,
sizeof(_glfw.hints.window.x11.className) - 1);
return;
case GLFW_X11_INSTANCE_NAME:
strncpy(_glfw.hints.window.x11.instanceName, value,
sizeof(_glfw.hints.window.x11.instanceName) - 1);
return;
}
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
}
GLFWAPI void glfwDestroyWindow(GLFWwindow* handle) GLFWAPI void glfwDestroyWindow(GLFWwindow* handle)
{ {
_GLFWwindow* window = (_GLFWwindow*) handle; _GLFWwindow* window = (_GLFWwindow*) handle;
@@ -781,6 +795,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
return _glfwPlatformWindowVisible(window); return _glfwPlatformWindowVisible(window);
case GLFW_MAXIMIZED: case GLFW_MAXIMIZED:
return _glfwPlatformWindowMaximized(window); return _glfwPlatformWindowMaximized(window);
case GLFW_HOVERED:
return _glfwPlatformWindowHovered(window);
case GLFW_TRANSPARENT_FRAMEBUFFER: case GLFW_TRANSPARENT_FRAMEBUFFER:
return _glfwPlatformFramebufferTransparent(window); return _glfwPlatformFramebufferTransparent(window);
case GLFW_RESIZABLE: case GLFW_RESIZABLE:
@@ -1015,6 +1031,17 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* handle
return cbfun; return cbfun;
} }
GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* handle,
GLFWwindowcontentscalefun cbfun)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
assert(window != NULL);
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
_GLFW_SWAP_POINTERS(window->callbacks.scale, cbfun);
return cbfun;
}
GLFWAPI void glfwPollEvents(void) GLFWAPI void glfwPollEvents(void)
{ {
_GLFW_REQUIRE_INIT(); _GLFW_REQUIRE_INIT();

40
glfw/wl_init.c vendored
View File

@@ -54,6 +54,8 @@ static void pointerHandleEnter(void* data,
_glfw.wl.pointerSerial = serial; _glfw.wl.pointerSerial = serial;
_glfw.wl.pointerFocus = window; _glfw.wl.pointerFocus = window;
window->wl.hovered = GLFW_TRUE;
_glfwPlatformSetCursor(window, window->wl.currentCursor); _glfwPlatformSetCursor(window, window->wl.currentCursor);
_glfwInputCursorEnter(window, GLFW_TRUE); _glfwInputCursorEnter(window, GLFW_TRUE);
} }
@@ -68,6 +70,8 @@ static void pointerHandleLeave(void* data,
if (!window) if (!window)
return; return;
window->wl.hovered = GLFW_FALSE;
_glfw.wl.pointerSerial = serial; _glfw.wl.pointerSerial = serial;
_glfw.wl.pointerFocus = NULL; _glfw.wl.pointerFocus = NULL;
_glfwInputCursorEnter(window, GLFW_FALSE); _glfwInputCursorEnter(window, GLFW_FALSE);
@@ -132,11 +136,14 @@ static void pointerHandleAxis(void* data,
{ {
_GLFWwindow* window = _glfw.wl.pointerFocus; _GLFWwindow* window = _glfw.wl.pointerFocus;
double scrollFactor; double scrollFactor;
double x = 0.0, y = 0.0; double x = 0, y = 0;
if (!window) if (!window)
return; return;
assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
/* Wayland scroll events are in pointer motion coordinate space (think /* Wayland scroll events are in pointer motion coordinate space (think
* two finger scroll). The factor 10 is commonly used to convert to * two finger scroll). The factor 10 is commonly used to convert to
* "scroll step means 1.0. */ * "scroll step means 1.0. */
@@ -152,9 +159,6 @@ static void pointerHandleAxis(void* data,
x = 0.0; x = 0.0;
y = wl_fixed_to_double(value) * scrollFactor; y = wl_fixed_to_double(value) * scrollFactor;
break; break;
default:
assert(GLFW_FALSE);
break;
} }
_glfwInputScroll(window, x, y); _glfwInputScroll(window, x, y);
@@ -264,6 +268,10 @@ static void keyboardHandleKeymap(void* data,
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift"); 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift");
_glfw.wl.xkb.superMask = _glfw.wl.xkb.superMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4"); 1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4");
_glfw.wl.xkb.capsLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Lock");
_glfw.wl.xkb.numLockMask =
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod2");
} }
static void keyboardHandleEnter(void* data, static void keyboardHandleEnter(void* data,
@@ -409,6 +417,10 @@ static void keyboardHandleModifiers(void* data,
modifiers |= GLFW_MOD_SHIFT; modifiers |= GLFW_MOD_SHIFT;
if (mask & _glfw.wl.xkb.superMask) if (mask & _glfw.wl.xkb.superMask)
modifiers |= GLFW_MOD_SUPER; modifiers |= GLFW_MOD_SUPER;
if (mask & _glfw.wl.xkb.capsLockMask)
modifiers |= GLFW_MOD_CAPS_LOCK;
if (mask & _glfw.wl.xkb.numLockMask)
modifiers |= GLFW_MOD_NUM_LOCK;
_glfw.wl.xkb.modifiers = modifiers; _glfw.wl.xkb.modifiers = modifiers;
} }
@@ -763,15 +775,22 @@ void _glfwPlatformTerminate(void)
_glfwTerminateJoysticksLinux(); _glfwTerminateJoysticksLinux();
#ifdef HAVE_XKBCOMMON_COMPOSE_H #ifdef HAVE_XKBCOMMON_COMPOSE_H
xkb_compose_state_unref(_glfw.wl.xkb.composeState); if (_glfw.wl.xkb.composeState)
xkb_compose_state_unref(_glfw.wl.xkb.composeState);
#endif #endif
xkb_keymap_unref(_glfw.wl.xkb.keymap); if (_glfw.wl.xkb.keymap)
xkb_state_unref(_glfw.wl.xkb.state); xkb_keymap_unref(_glfw.wl.xkb.keymap);
xkb_context_unref(_glfw.wl.xkb.context); if (_glfw.wl.xkb.state)
xkb_state_unref(_glfw.wl.xkb.state);
if (_glfw.wl.xkb.context)
xkb_context_unref(_glfw.wl.xkb.context);
dlclose(_glfw.wl.xkb.handle); if (_glfw.wl.xkb.handle)
_glfw.wl.xkb.handle = NULL; {
_glfw_dlclose(_glfw.wl.xkb.handle);
_glfw.wl.xkb.handle = NULL;
}
if (_glfw.wl.cursorTheme) if (_glfw.wl.cursorTheme)
wl_cursor_theme_destroy(_glfw.wl.cursorTheme); wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
@@ -818,4 +837,3 @@ const char* _glfwPlatformGetVersionString(void)
#endif #endif
; ;
} }

3
glfw/wl_platform.h vendored
View File

@@ -117,6 +117,7 @@ typedef struct _GLFWwindowWayland
int width, height; int width, height;
GLFWbool visible; GLFWbool visible;
GLFWbool maximized; GLFWbool maximized;
GLFWbool hovered;
GLFWbool transparent; GLFWbool transparent;
struct wl_surface* surface; struct wl_surface* surface;
struct wl_egl_window* native; struct wl_egl_window* native;
@@ -183,6 +184,8 @@ typedef struct _GLFWlibraryWayland
xkb_mod_mask_t altMask; xkb_mod_mask_t altMask;
xkb_mod_mask_t shiftMask; xkb_mod_mask_t shiftMask;
xkb_mod_mask_t superMask; xkb_mod_mask_t superMask;
xkb_mod_mask_t capsLockMask;
xkb_mod_mask_t numLockMask;
unsigned int modifiers; unsigned int modifiers;
PFN_xkb_context_new context_new; PFN_xkb_context_new context_new;

6
glfw/wl_window.c vendored
View File

@@ -125,6 +125,7 @@ static void checkScaleChange(_GLFWwindow* window)
wl_surface_set_buffer_scale(window->wl.surface, scale); wl_surface_set_buffer_scale(window->wl.surface, scale);
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0); wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight); _glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
_glfwInputWindowContentScale(window, scale, scale);
} }
} }
@@ -700,6 +701,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return window->wl.maximized; return window->wl.maximized;
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
return window->wl.hovered;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
return window->wl.transparent; return window->wl.transparent;

76
glfw/x11_window.c vendored
View File

@@ -212,6 +212,10 @@ static int translateState(int state)
mods |= GLFW_MOD_ALT; mods |= GLFW_MOD_ALT;
if (state & Mod4Mask) if (state & Mod4Mask)
mods |= GLFW_MOD_SUPER; mods |= GLFW_MOD_SUPER;
if (state & LockMask)
mods |= GLFW_MOD_CAPS_LOCK;
if (state & Mod2Mask)
mods |= GLFW_MOD_NUM_LOCK;
return mods; return mods;
} }
@@ -703,49 +707,34 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
updateNormalHints(window, wndconfig->width, wndconfig->height); updateNormalHints(window, wndconfig->width, wndconfig->height);
// Set ICCCM WM_CLASS property and window title // Set ICCCM WM_CLASS property
{ {
XClassHint* hint = XAllocClassHint(); XClassHint* hint = XAllocClassHint();
char *wm_cclass = NULL, *wm_cname = NULL;
const char *real_title = wndconfig->title;
if (strlen(_glfw.hints.init.x11.className) && if (strlen(wndconfig->x11.instanceName) &&
strlen(_glfw.hints.init.x11.classClass)) strlen(wndconfig->x11.className))
{ {
hint->res_name = (char*) _glfw.hints.init.x11.className; hint->res_name = (char*) wndconfig->x11.instanceName;
hint->res_class = (char*) _glfw.hints.init.x11.classClass; hint->res_class = (char*) wndconfig->x11.className;
}
else if (strlen(real_title))
{
if (*real_title == 1) {
char *p = strchr(real_title, 30);
if (p && p > real_title + 1) {
wm_cname = calloc(p - real_title + 1, 1);
if (wm_cname) memcpy(wm_cname, real_title + 1, p - real_title - 1);
hint->res_name = wm_cname;
char *q = strchr(p + 1, 30);
if (q && q > p + 1) {
wm_cclass = calloc(q - p + 1, 1);
if (wm_cclass) memcpy(wm_cclass, p + 1, q - p - 1);
hint->res_class = wm_cclass;
real_title = q + 1;
}
}
} else {
hint->res_name = (char*) real_title;
hint->res_class = (char*) real_title;
}
} }
else else
{ {
hint->res_name = (char*) "glfw-application"; const char* resourceName = getenv("RESOURCE_NAME");
hint->res_class = (char*) "GLFW-Application"; if (resourceName && strlen(resourceName))
hint->res_name = (char*) resourceName;
else if (strlen(wndconfig->title))
hint->res_name = (char*) wndconfig->title;
else
hint->res_name = (char*) "glfw-application";
if (strlen(wndconfig->title))
hint->res_class = (char*) wndconfig->title;
else
hint->res_class = (char*) "GLFW-Application";
} }
XSetClassHint(_glfw.x11.display, window->x11.handle, hint); XSetClassHint(_glfw.x11.display, window->x11.handle, hint);
XFree(hint); XFree(hint);
free(wm_cclass); free(wm_cname);
_glfwPlatformSetWindowTitle(window, real_title);
} }
// Announce support for Xdnd (drag and drop) // Announce support for Xdnd (drag and drop)
@@ -756,6 +745,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
PropModeReplace, (unsigned char*) &version, 1); PropModeReplace, (unsigned char*) &version, 1);
} }
_glfwPlatformSetWindowTitle(window, wndconfig->title);
if (_glfw.x11.im) if (_glfw.x11.im)
{ {
@@ -2468,6 +2458,28 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
return maximized; return maximized;
} }
int _glfwPlatformWindowHovered(_GLFWwindow* window)
{
Window w = _glfw.x11.root;
while (w)
{
Window root;
int rootX, rootY, childX, childY;
unsigned int mask;
if (!XQueryPointer(_glfw.x11.display, w,
&root, &w, &rootX, &rootY, &childX, &childY, &mask))
{
return GLFW_FALSE;
}
if (w == window->x11.handle)
return GLFW_TRUE;
}
return GLFW_FALSE;
}
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window) int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{ {
if (!window->x11.transparent) if (!window->x11.transparent)

View File

@@ -2,26 +2,28 @@
# 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
from gettext import gettext as _ from gettext import gettext as _
from weakref import WeakValueDictionary from weakref import WeakValueDictionary
from .cli import create_opts, parse_args from .cli import create_opts, parse_args
from .config import MINIMUM_FONT_SIZE, cached_values, initial_window_size from .config import MINIMUM_FONT_SIZE, cached_values, initial_window_size
from .constants import set_boss, wakeup from .constants import appname, set_boss, wakeup
from .fast_data_types import ( from .fast_data_types import (
ChildMonitor, create_os_window, current_os_window, destroy_global_data, ChildMonitor, create_os_window, current_os_window, destroy_global_data,
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event, destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
layout_sprite_map, mark_os_window_for_close, show_window, layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
toggle_fullscreen, viewport_for_window set_dpi_from_os_window, show_window, toggle_fullscreen,
viewport_for_window
) )
from .fonts.render import prerender, resize_fonts, set_font_family from .fonts.render import prerender, resize_fonts, set_font_family
from .keys import get_shortcut from .keys import get_shortcut
from .remote_control import handle_cmd
from .session import create_session from .session import create_session
from .tabs import SpecialWindow, TabManager from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
from .utils import ( from .utils import (
encode_wm_class, end_startup_notification, get_primary_selection, end_startup_notification, get_primary_selection, init_startup_notification,
init_startup_notification, open_url, safe_print, set_primary_selection, open_url, safe_print, set_primary_selection, single_instance
single_instance
) )
@@ -79,18 +81,84 @@ class Boss:
startup_session = create_session(opts, args) startup_session = create_session(opts, args)
self.add_os_window(startup_session, os_window_id=os_window_id) self.add_os_window(startup_session, os_window_id=os_window_id)
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, visible=True): def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, startup_id=None):
dpi_changed = False
if os_window_id is None: if os_window_id is None:
w, h = initial_window_size(self.opts) if size is None else size w, h = initial_window_size(self.opts) if size is None else size
os_window_id = create_os_window(w, h, encode_wm_class(wname or self.args.name, wclass or self.args.cls), visible) cls = wclass or self.args.cls or appname
os_window_id = create_os_window(w, h, appname, wname or self.args.name or cls, cls)
if startup_id:
ctx = init_startup_notification(os_window_id, startup_id)
dpi_changed = show_window(os_window_id)
if startup_id:
end_startup_notification(ctx)
tm = TabManager(os_window_id, self.opts, self.args, startup_session) tm = TabManager(os_window_id, self.opts, self.args, startup_session)
self.os_window_map[os_window_id] = tm self.os_window_map[os_window_id] = tm
return os_window_id if dpi_changed:
self.on_dpi_change(os_window_id)
def list_os_windows(self):
for os_window_id, tm in self.os_window_map.items():
yield {
'id': os_window_id,
'tabs': list(tm.list_tabs()),
}
def match_windows(self, match):
field, exp = match.split(':', 1)
pat = re.compile(exp)
for tm in self.os_window_map.values():
for tab in tm:
for window in tab:
if window.matches(field, pat):
yield window
def tab_for_window(self, window):
for tm in self.os_window_map.values():
for tab in tm:
for w in tab:
if w.id == window.id:
return tab
def match_tabs(self, match):
field, exp = match.split(':', 1)
pat = re.compile(exp)
tms = tuple(self.os_window_map.values())
found = False
if field in ('title', 'id'):
for tm in tms:
for tab in tm:
if tab.matches(field, pat):
yield tab
found = True
if not found:
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
for tab in tabs:
if tab:
yield tab
def set_active_window(self, window):
for tm in self.os_window_map.values():
for tab in tm:
for w in tab:
if w.id == window.id:
if tab is not self.active_tab:
tm.set_active_tab(tab)
tab.set_active_window(w)
return
def _new_os_window(self, args, cwd_from=None):
sw = self.args_to_special_window(args, cwd_from) if args else None
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
self.add_os_window(startup_session)
def new_os_window(self, *args): def new_os_window(self, *args):
sw = self.args_to_special_window(args) if args else None self._new_os_window(args)
startup_session = create_session(self.opts, special_window=sw)
return self.add_os_window(startup_session) def new_os_window_with_cwd(self, *args):
w = self.active_window
cwd_from = w.child.pid if w is not None else None
self._new_os_window(args, cwd_from)
def add_child(self, window): def add_child(self, window):
self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen) self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen)
@@ -106,15 +174,24 @@ class Boss:
args.args = rest args.args = rest
opts = create_opts(args) opts = create_opts(args)
session = create_session(opts, args) session = create_session(opts, args)
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts), visible=False) self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts), startup_id=startup_id)
if startup_id:
ctx = init_startup_notification(os_window_id, startup_id)
show_window(os_window_id)
if startup_id:
end_startup_notification(ctx)
else: else:
safe_print('Unknown message received from peer, ignoring') safe_print('Unknown message received from peer, ignoring')
def handle_remote_cmd(self, cmd, window=None):
response = None
if self.opts.allow_remote_control:
try:
response = handle_cmd(self, window, cmd)
except Exception as err:
import traceback
response = {'ok': False, 'error': str(err), 'tb': traceback.format_exc()}
else:
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
if response is not None:
if window is not None:
window.send_cmd_response(response)
def on_child_death(self, window_id): def on_child_death(self, window_id):
window = self.window_id_map.pop(window_id, None) window = self.window_id_map.pop(window_id, None)
if window is None: if window is None:
@@ -162,10 +239,16 @@ class Boss:
if tm is not None: if tm is not None:
tm.activate_tab_at(x) tm.activate_tab_at(x)
def on_window_resize(self, os_window_id, w, h): def on_window_resize(self, os_window_id, w, h, dpi_changed):
tm = self.os_window_map.get(os_window_id) tm = self.os_window_map.get(os_window_id)
if tm is not None: if tm is not None:
tm.resize() if dpi_changed:
if set_dpi_from_os_window(os_window_id):
self.on_dpi_change(os_window_id)
else:
tm.resize()
else:
tm.resize()
def increase_font_size(self): def increase_font_size(self):
self.change_font_size( self.change_font_size(
@@ -182,10 +265,9 @@ class Boss:
def restore_font_size(self): def restore_font_size(self):
self.change_font_size(self.opts.font_size) self.change_font_size(self.opts.font_size)
def change_font_size(self, new_size): def _change_font_size(self, new_size=None):
if new_size == self.current_font_size: if new_size is not None:
return self.current_font_size = new_size
self.current_font_size = new_size
old_cell_width, old_cell_height = viewport_for_window()[-2:] old_cell_width, old_cell_height = viewport_for_window()[-2:]
windows = tuple(filter(None, self.window_id_map.values())) windows = tuple(filter(None, self.window_id_map.values()))
resize_fonts(self.current_font_size) resize_fonts(self.current_font_size)
@@ -199,6 +281,14 @@ class Boss:
tm.refresh_sprite_positions() tm.refresh_sprite_positions()
glfw_post_empty_event() glfw_post_empty_event()
def change_font_size(self, new_size):
if new_size == self.current_font_size:
return
self._change_font_size(new_size)
def on_dpi_change(self, os_window_id):
self._change_font_size()
@property @property
def active_tab_manager(self): def active_tab_manager(self):
os_window_id = current_os_window() os_window_id = current_os_window()
@@ -326,6 +416,8 @@ class Boss:
text = w.text_for_selection() text = w.text_for_selection()
if text: if text:
set_primary_selection(text) set_primary_selection(text)
if self.opts.copy_on_select:
set_clipboard_string(text)
def goto_tab(self, tab_num): def goto_tab(self, tab_num):
tm = self.active_tab_manager tm = self.active_tab_manager
@@ -342,7 +434,7 @@ class Boss:
if tm is not None: if tm is not None:
tm.next_tab(-1) tm.next_tab(-1)
def args_to_special_window(self, args): def args_to_special_window(self, args, cwd_from=None):
args = list(args) args = list(args)
stdin = None stdin = None
w = self.active_window w = self.active_window
@@ -368,23 +460,42 @@ class Boss:
if not arg: if not arg:
continue continue
cmd.append(arg) cmd.append(arg)
return SpecialWindow(cmd, stdin) return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
def new_tab(self, *args): def _new_tab(self, args, cwd_from=None):
special_window = None special_window = None
if args: if args:
special_window = self.args_to_special_window(args) if isinstance(args, SpecialWindowInstance):
special_window = args
else:
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
tm = self.active_tab_manager tm = self.active_tab_manager
if tm is not None: if tm is not None:
tm.new_tab(special_window=special_window) tm.new_tab(special_window=special_window, cwd_from=cwd_from)
def new_window(self, *args): def new_tab(self, *args):
self._new_tab(args)
def new_tab_with_cwd(self, *args):
w = self.active_window
cwd_from = w.child.pid if w is not None else None
self._new_tab(args, cwd_from=cwd_from)
def _new_window(self, args, cwd_from=None):
tab = self.active_tab tab = self.active_tab
if tab is not None: if tab is not None:
if args: if args:
tab.new_special_window(self.args_to_special_window(args)) tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
else: else:
tab.new_window() tab.new_window(cwd_from=cwd_from)
def new_window(self, *args):
self._new_window(args)
def new_window_with_cwd(self, *args):
w = self.active_window
cwd_from = w.child.pid if w is not None else None
self._new_window(args, cwd_from=cwd_from)
def move_tab_forward(self): def move_tab_forward(self):
tm = self.active_tab_manager tm = self.active_tab_manager

View File

@@ -19,6 +19,7 @@ in float bg_alpha;
#ifdef NEEDS_FOREGROUND #ifdef NEEDS_FOREGROUND
uniform sampler2DArray sprites; uniform sampler2DArray sprites;
uniform float inactive_text_alpha;
in vec3 sprite_pos; in vec3 sprite_pos;
in vec3 underline_pos; in vec3 underline_pos;
in vec3 strike_pos; in vec3 strike_pos;
@@ -60,7 +61,7 @@ vec4 calculate_foreground() {
// Since strike and text are the same color, we simply add the alpha values // Since strike and text are the same color, we simply add the alpha values
float combined_alpha = min(text_alpha + strike_alpha, 1.0f); float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
// Underline color might be different, so alpha blend // Underline color might be different, so alpha blend
return alpha_blend(decoration_fg, underline_alpha, fg, combined_alpha); return alpha_blend(decoration_fg, underline_alpha * inactive_text_alpha, fg, combined_alpha * inactive_text_alpha);
} }
#endif #endif
@@ -99,7 +100,7 @@ void main() {
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha); final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
final_color = vec4(final_color.rgb / final_color.a, final_color.a); final_color = vec4(final_color.rgb / final_color.a, final_color.a);
#else #else
// since background alpha is 1.0, it is effectively premultipled // since background alpha is 1.0, it is effectively pre-multiplied
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f); final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
final_color = vec4(final_color.rgb / final_color.a, final_color.a); final_color = vec4(final_color.rgb / final_color.a, final_color.a);
#endif #endif

View File

@@ -517,13 +517,23 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
ColorProfile *cp = rd->screen->color_profile; ColorProfile *cp = rd->screen->color_profile;
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape); ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color); ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
if (ans->shape == CURSOR_BLOCK) return; ans->is_focused = os_window->is_focused;
if (ans->shape == CURSOR_BLOCK && ans->is_focused) return;
double left = rd->xstart + cursor->x * rd->dx; double left = rd->xstart + cursor->x * rd->dx;
double top = rd->ystart - cursor->y * rd->dy; double top = rd->ystart - cursor->y * rd->dy;
unsigned long mult = MAX(1, screen_current_char_width(rd->screen)); unsigned long mult = MAX(1, screen_current_char_width(rd->screen));
double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult); double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
double bottom = top - rd->dy; double bottom = top - rd->dy;
if (ans->shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false, os_window); switch (ans->shape) {
case CURSOR_UNDERLINE:
top = bottom + cursor_width(2.0, false, os_window);
break;
case CURSOR_BLOCK:
top -= 2.0 / os_window->viewport_height; // 1px adjustment for width of line
break;
default:
break;
}
ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom; ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom;
} }
@@ -550,7 +560,7 @@ simple_render_screen(PyObject UNUSED *self, PyObject *args) {
if (vao_idx == -1) vao_idx = create_cell_vao(); if (vao_idx == -1) vao_idx = create_cell_vao();
if (gvao_idx == -1) gvao_idx = create_graphics_vao(); if (gvao_idx == -1) gvao_idx = create_graphics_vao();
if (!PyArg_ParseTuple(args, "O!ffff", &Screen_Type, &screen, &xstart, &ystart, &dx, &dy)) return NULL; if (!PyArg_ParseTuple(args, "O!ffff", &Screen_Type, &screen, &xstart, &ystart, &dx, &dy)) return NULL;
draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window()); draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window(), true);
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@@ -576,8 +586,8 @@ render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id
collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window); collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window);
update_window_title(w, os_window); update_window_title(w, os_window);
} else WD.screen->cursor_render_info.is_visible = false; } else WD.screen->cursor_render_info.is_visible = false;
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window); draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window, is_active_window);
if (is_active_window && WD.screen->cursor_render_info.is_visible && WD.screen->cursor_render_info.shape != CURSOR_BLOCK) { if (is_active_window && WD.screen->cursor_render_info.is_visible && (!WD.screen->cursor_render_info.is_focused || WD.screen->cursor_render_info.shape != CURSOR_BLOCK)) {
draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused); draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused);
} }
if (WD.screen->start_visual_bell_at != 0) { if (WD.screen->start_visual_bell_at != 0) {
@@ -608,11 +618,11 @@ render(double now) {
w->viewport_size_dirty = false; w->viewport_size_dirty = false;
} }
unsigned int active_window_id = 0; unsigned int active_window_id = 0;
render_os_window(w, now, &active_window_id);
Tab *active_tab = w->tabs + w->active_tab; Tab *active_tab = w->tabs + w->active_tab;
BorderRects *br = &active_tab->border_rects; BorderRects *br = &active_tab->border_rects;
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, w->viewport_width, w->viewport_height); draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, w->viewport_width, w->viewport_height);
if (TD.screen && w->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, w); if (TD.screen && w->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, w, true);
render_os_window(w, now, &active_window_id);
swap_window_buffers(w); swap_window_buffers(w);
w->last_active_tab = w->active_tab; w->last_num_tabs = w->num_tabs; w->last_active_window_id = active_window_id; w->last_active_tab = w->active_tab; w->last_num_tabs = w->num_tabs; w->last_active_window_id = active_window_id;
br->is_dirty = false; br->is_dirty = false;
@@ -1029,4 +1039,3 @@ init_child_monitor(PyObject *module) {
} }
// }}} // }}}

113
kitty/child.c Normal file
View File

@@ -0,0 +1,113 @@
/*
* child.c
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
static inline char**
serialize_string_tuple(PyObject *src) {
Py_ssize_t sz = PyTuple_GET_SIZE(src);
char **ans = calloc(sz + 1, sizeof(char*));
if (!ans) fatal("Out of memory");
for (Py_ssize_t i = 0; i < sz; i++) ans[i] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(src, i));
return ans;
}
extern char **environ;
static inline void
write_to_stderr(const char *text) {
size_t sz = strlen(text);
size_t written = 0;
while(written < sz) {
ssize_t amt = write(2, text + written, sz - written);
if (amt == 0) break;
if (amt < 0) {
if (errno == EAGAIN || errno == EINTR) continue;
break;
}
written += amt;
}
}
static PyObject*
spawn(PyObject *self UNUSED, PyObject *args) {
PyObject *argv_p, *env_p;
int master, slave, stdin_read_fd, stdin_write_fd;
char* cwd;
if (!PyArg_ParseTuple(args, "sO!O!iiii", &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
char name[2048] = {0};
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
char **argv = serialize_string_tuple(argv_p);
char **env = serialize_string_tuple(env_p);
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
pid_t pid = fork();
switch(pid) {
case 0:
// child
// Use only signal-safe functions (man 7 signal-safety)
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
if (setsid() == -1) exit_on_err("setsid() in child process failed");
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
if (stdin_read_fd > -1) {
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
close(stdin_read_fd);
close(stdin_write_fd);
} else {
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
}
close(slave);
close(master);
for (int c = 3; c < 201; c++) close(c);
// Establish the controlling terminal (see man 7 credentials)
int tfd = open(name, O_RDWR);
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
close(tfd);
environ = env;
execvp(argv[0], argv);
// Report the failure and exec a shell instead, so that we are not left
// with a forked but not exec'ed process
write_to_stderr("Failed to launch child: ");
write_to_stderr(argv[0]);
write_to_stderr("\nWith error: ");
write_to_stderr(strerror(errno));
write_to_stderr("\nPress Enter to exit.\n");
execlp("sh", "sh", "-c", "read w", NULL);
exit(EXIT_FAILURE);
break;
case -1:
PyErr_SetFromErrno(PyExc_OSError);
break;
default:
break;
}
#undef exit_on_err
free(argv);
free(env);
if (PyErr_Occurred()) return NULL;
return PyLong_FromLong(pid);
}
static PyMethodDef module_methods[] = {
METHODB(spawn, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_child(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

View File

@@ -4,11 +4,26 @@
import fcntl import fcntl
import os import os
import sys
import kitty.fast_data_types as fast_data_types import kitty.fast_data_types as fast_data_types
from .constants import terminfo_dir from .constants import terminfo_dir, is_macos
def cwd_of_process(pid):
if is_macos:
from kitty.fast_data_types import cwd_of_process
ans = cwd_of_process(pid)
else:
ans = '/proc/{}/cwd'.format(pid)
return os.path.realpath(ans)
def cmdline_of_process(pid):
if is_macos:
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
raise NotImplementedError()
return open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')
def remove_cloexec(fd): def remove_cloexec(fd):
@@ -20,9 +35,17 @@ class Child:
child_fd = pid = None child_fd = pid = None
forked = False forked = False
def __init__(self, argv, cwd, opts, stdin=None, env=None): def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
self.argv = argv self.argv = argv
self.cwd = os.path.abspath(os.path.expandvars(os.path.expanduser(cwd or os.getcwd()))) if cwd_from is not None:
try:
cwd = cwd_of_process(cwd_from)
except Exception:
import traceback
traceback.print_exc()
else:
cwd = os.path.expandvars(os.path.expanduser(cwd or os.getcwd()))
self.cwd = os.path.abspath(cwd)
self.opts = opts self.opts = opts
self.stdin = stdin self.stdin = stdin
self.env = env or {} self.env = env or {}
@@ -38,44 +61,34 @@ class Child:
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()
remove_cloexec(stdin_read_fd) remove_cloexec(stdin_read_fd)
env = self.env else:
pid = os.fork() stdin_read_fd = stdin_write_fd = -1
if pid == 0: # child env = os.environ.copy()
try: env.update(self.env)
os.chdir(self.cwd) env['TERM'] = self.opts.term
except EnvironmentError: env['COLORTERM'] = 'truecolor'
os.chdir('/') if os.path.isdir(terminfo_dir):
os.setsid() env['TERMINFO'] = terminfo_dir
for i in range(3): env = tuple('{}={}'.format(k, v) for k, v in env.items())
if stdin is not None and i == 0: pid = fast_data_types.spawn(self.cwd, tuple(self.argv), env, master, slave, stdin_read_fd, stdin_write_fd)
os.dup2(stdin_read_fd, i) os.close(slave)
os.close(stdin_read_fd), os.close(stdin_write_fd) self.pid = pid
else: self.child_fd = master
os.dup2(slave, i) if stdin is not None:
os.close(slave), os.close(master) os.close(stdin_read_fd)
os.closerange(3, 200) fast_data_types.thread_write(stdin_write_fd, stdin)
# Establish the controlling terminal (see man 7 credentials) return pid
os.close(os.open(os.ttyname(1), os.O_RDWR))
os.environ.update(env) @property
os.environ['TERM'] = self.opts.term def cmdline(self):
os.environ['COLORTERM'] = 'truecolor' try:
if os.path.isdir(terminfo_dir): return cmdline_of_process(self.pid)
os.environ['TERMINFO'] = terminfo_dir except Exception:
try: return list(self.argv)
os.execvp(self.argv[0], self.argv)
except Exception as err: @property
# Report he failure and exec a shell instead so that def current_cwd(self):
# we are not left with a forked but not execed process try:
print('Could not launch:', self.argv[0]) return cwd_of_process(self.pid)
print('\t', err) except Exception:
print('\nPress Enter to exit:', end=' ') pass
sys.stdout.flush()
os.execvp('/bin/sh', ['/bin/sh', '-c', 'read w'])
else: # master
os.close(slave)
self.pid = pid
self.child_fd = master
if stdin is not None:
os.close(stdin_read_fd)
fast_data_types.thread_write(stdin_write_fd, stdin)
return pid

View File

@@ -206,7 +206,7 @@ def prettify(text):
def sub(m): def sub(m):
t = m.group(2) t = m.group(2)
for ch in m.group(1): for ch in m.group(1):
t = {'C': cyan, '_': italic, '*': bold}[ch](t) t = {'C': cyan, '_': italic, '*': bold, 'G': green, 'T': title}[ch](t)
return t return t
text = re.sub(r'[|]([a-zA-Z_*]+?) (.+?)[|]', sub, text) text = re.sub(r'[|]([a-zA-Z_*]+?) (.+?)[|]', sub, text)
@@ -263,18 +263,25 @@ def print_help_for_seq(seq, usage, message, appname):
if leading_indent is None: if leading_indent is None:
leading_indent = indent leading_indent = indent
j = '\n' + (' ' * indent) j = '\n' + (' ' * indent)
a((' ' * leading_indent) + j.join(wrap(text, limit=linesz - indent))) lines = []
for l in text.splitlines():
if l:
lines.extend(wrap(l, limit=linesz - indent))
else:
lines.append('')
a((' ' * leading_indent) + j.join(lines))
usage = usage or '[program-to-run ...]' usage = usage or '[program-to-run ...]'
a('{}: {} [options] {}'.format(title('Usage'), bold(yellow(appname)), usage)) a('{}: {} [options] {}'.format(title('Usage'), bold(yellow(appname)), usage))
a('') a('')
message = message or ( message = message or (
'Run the |_ {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal' 'Run the |G {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal'
' arguments following the |_ options|. For example: {appname} /bin/sh' ' arguments following the |_ options|. For example: {appname} /bin/sh'
).format(appname=appname) ).format(appname=appname)
wa(prettify(message)) wa(prettify(message))
a('') a('')
a('{}:'.format(title('Options'))) if seq:
a('{}:'.format(title('Options')))
for opt in seq: for opt in seq:
if isinstance(opt, str): if isinstance(opt, str):
a('{}:'.format(title(opt))) a('{}:'.format(title(opt)))

View File

@@ -12,6 +12,8 @@
#include <AvailabilityMacros.h> #include <AvailabilityMacros.h>
// Needed for _NSGetProgname // Needed for _NSGetProgname
#include <crt_externs.h> #include <crt_externs.h>
typedef void* rusage_info_t; // needed for libproc.h
#include <libproc.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200) #if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
#define NSWindowStyleMaskResizable NSResizableWindowMask #define NSWindowStyleMaskResizable NSResizableWindowMask
@@ -166,8 +168,19 @@ cocoa_get_lang(PyObject UNUSED *self) {
return Py_BuildValue("s", [locale UTF8String]); return Py_BuildValue("s", [locale UTF8String]);
} }
PyObject*
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
long pid = PyLong_AsLong(pid_);
struct proc_vnodepathinfo vpi;
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
}
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""}, {"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
{NULL, NULL, 0, NULL} /* Sentinel */ {NULL, NULL, 0, NULL} /* Sentinel */
}; };

View File

@@ -12,13 +12,15 @@ import tempfile
from collections import namedtuple from collections import namedtuple
from . import fast_data_types as defines from . import fast_data_types as defines
from .config_utils import (
init_config, parse_config_base, positive_float, positive_int, to_bool,
to_color, unit_float
)
from .constants import config_dir from .constants import config_dir
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
from .layout import all_layouts from .layout import all_layouts
from .rgb import to_color
from .utils import safe_print from .utils import safe_print
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
MINIMUM_FONT_SIZE = 4 MINIMUM_FONT_SIZE = 4
@@ -44,14 +46,10 @@ def to_cursor_shape(x):
) )
def to_bool(x):
return x.lower() in 'y yes true'.split()
def parse_mods(parts): def parse_mods(parts):
def map_mod(m): def map_mod(m):
return {'CTRL': 'CONTROL', 'CMD': 'CONTROL'}.get(m, m) return {'CTRL': 'CONTROL', 'CMD': 'SUPER', 'OPTION': 'ALT'}.get(m, m)
mods = 0 mods = 0
for m in parts: for m in parts:
@@ -94,7 +92,7 @@ def parse_shortcut(sc):
KeyAction = namedtuple('KeyAction', 'func args') KeyAction = namedtuple('KeyAction', 'func args')
shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab'} shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window', 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'}
def parse_key_action(action): def parse_key_action(action):
@@ -174,7 +172,7 @@ def parse_symbol_map(val):
def parse_send_text_bytes(text): def parse_send_text_bytes(text):
return ast.literal_eval("'''" + text + "'''").encode('utf-8') return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''").encode('utf-8')
def parse_send_text(val, keymap): def parse_send_text(val, keymap):
@@ -195,7 +193,7 @@ def parse_send_text(val, keymap):
return parse_key(key_str, keymap) return parse_key(key_str, keymap)
def to_open_url_modifiers(val): def to_modifiers(val):
return parse_mods(val.split('+')) return parse_mods(val.split('+'))
@@ -209,18 +207,6 @@ def to_layout_names(raw):
return parts return parts
def positive_int(x):
return max(0, int(x))
def positive_float(x):
return max(0, float(x))
def unit_float(x):
return max(0, min(float(x), 1))
def adjust_line_height(x): def adjust_line_height(x):
if x.endswith('%'): if x.endswith('%'):
return float(x[:-1].strip()) / 100.0 return float(x[:-1].strip()) / 100.0
@@ -240,7 +226,7 @@ def tab_separator(x):
x = x[1:-1] x = x[1:-1]
break break
if not x.strip(): if not x.strip():
x = defaults.tab_separator x = ('\xa0' * len(x)) if x else defaults.tab_separator
return x return x
@@ -248,6 +234,10 @@ def tab_font_style(x):
return {'bold-italic': (True, True), 'bold': (True, False), 'italic': (False, True)}.get(x.lower().replace('_', '-'), (False, False)) return {'bold-italic': (True, True), 'bold': (True, False), 'italic': (False, True)}.get(x.lower().replace('_', '-'), (False, False))
def tab_bar_edge(x):
return {'top': 1, 'bottom': 3}.get(x.lower(), 3)
def url_style(x): def url_style(x):
return url_style.map.get(x, url_style.map['curly']) return url_style.map.get(x, url_style.map['curly'])
@@ -256,6 +246,7 @@ url_style.map = dict(((v, i) for i, v in enumerate('none single double curly'.sp
type_map = { type_map = {
'allow_remote_control': to_bool,
'adjust_line_height': adjust_line_height, 'adjust_line_height': adjust_line_height,
'scrollback_lines': positive_int, 'scrollback_lines': positive_int,
'scrollback_pager': shlex.split, 'scrollback_pager': shlex.split,
@@ -264,7 +255,8 @@ type_map = {
'font_size_delta': positive_float, 'font_size_delta': positive_float,
'focus_follows_mouse': to_bool, 'focus_follows_mouse': to_bool,
'cursor_shape': to_cursor_shape, 'cursor_shape': to_cursor_shape,
'open_url_modifiers': to_open_url_modifiers, 'open_url_modifiers': to_modifiers,
'rectangle_select_modifiers': to_modifiers,
'repaint_delay': positive_int, 'repaint_delay': positive_int,
'input_delay': positive_int, 'input_delay': positive_int,
'window_border_width': positive_float, 'window_border_width': positive_float,
@@ -290,20 +282,40 @@ type_map = {
'tab_separator': tab_separator, 'tab_separator': tab_separator,
'active_tab_font_style': tab_font_style, 'active_tab_font_style': tab_font_style,
'inactive_tab_font_style': tab_font_style, 'inactive_tab_font_style': tab_font_style,
'inactive_text_alpha': unit_float,
'url_style': url_style, 'url_style': url_style,
'prefer_color_emoji': to_bool, 'prefer_color_emoji': to_bool,
'copy_on_select': to_bool,
'tab_bar_edge': tab_bar_edge,
} }
for name in ( for name in (
'foreground background cursor active_border_color inactive_border_color' 'foreground background cursor active_border_color inactive_border_color'
' selection_foreground selection_background url_color' ' selection_foreground selection_background url_color'
).split(): ).split():
type_map[name] = lambda x: to_color(x, validate=True) type_map[name] = to_color
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] = to_color
for a in ('active', 'inactive'): for a in ('active', 'inactive'):
for b in ('foreground', 'background'): for b in ('foreground', 'background'):
type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True) type_map['%s_tab_%s' % (a, b)] = to_color
def special_handling(key, val, ans):
if key == 'map':
parse_key(val, ans['keymap'])
return True
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
return True
if key == 'send_text':
# For legacy compatibility
parse_send_text(val, ans['keymap'])
return True
defaults = None
default_config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')
def parse_config(lines, check_keys=True): def parse_config(lines, check_keys=True):
@@ -311,46 +323,14 @@ def parse_config(lines, check_keys=True):
'keymap': {}, 'keymap': {},
'symbol_map': {}, 'symbol_map': {},
} }
if check_keys: parse_config_base(lines, defaults, type_map, special_handling, ans, check_keys=check_keys)
all_keys = defaults._asdict()
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue
m = key_pat.match(line)
if m is not None:
key, val = m.groups()
if key == 'map':
parse_key(val, ans['keymap'])
continue
if key == 'symbol_map':
ans['symbol_map'].update(parse_symbol_map(val))
continue
if key == 'send_text':
# For legacy compatibility
parse_send_text(val, ans['keymap'])
continue
if check_keys:
if key not in all_keys:
safe_print(
'Ignoring unknown config key: {}'.format(key),
file=sys.stderr
)
continue
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
return ans return ans
with open( Options, defaults = init_config(default_config_path, parse_config)
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf'), 'rb' actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset(
) as f: 'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.split()
defaults = parse_config(f.read().decode('utf-8').splitlines(), check_keys=False) )
Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults)
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset({'combine', 'send_text', 'goto_tab'})
no_op_actions = frozenset({'noop', 'no-op', 'no_op'}) no_op_actions = frozenset({'noop', 'no-op', 'no_op'})

67
kitty/config_utils.py Normal file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import re
import sys
from collections import namedtuple
from .utils import safe_print
from .rgb import to_color as as_color
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
def to_color(x):
return as_color(x, validate=True)
def positive_int(x):
return max(0, int(x))
def positive_float(x):
return max(0, float(x))
def unit_float(x):
return max(0, min(float(x), 1))
def to_bool(x):
return x.lower() in 'y yes true'.split()
def parse_config_base(
lines, defaults, type_map, special_handling, ans, check_keys=True
):
if check_keys:
all_keys = defaults._asdict()
for line in lines:
line = line.strip()
if not line or line.startswith('#'):
continue
m = key_pat.match(line)
if m is not None:
key, val = m.groups()
if special_handling(key, val, ans):
continue
if check_keys:
if key not in all_keys:
safe_print(
'Ignoring unknown config key: {}'.format(key),
file=sys.stderr
)
continue
tm = type_map.get(key)
if tm is not None:
val = tm(val)
ans[key] = val
def init_config(defaults_path, parse_config):
with open(defaults_path, encoding='utf-8') as f:
defaults = parse_config(f.read().splitlines(), check_keys=False)
Options = namedtuple('Defaults', ','.join(defaults.keys()))
defaults = Options(**defaults)
return Options, defaults

View File

@@ -10,7 +10,7 @@ from collections import namedtuple
from .fast_data_types import set_boss as set_c_boss from .fast_data_types import set_boss as set_c_boss
appname = 'kitty' appname = 'kitty'
version = (0, 6, 0) version = (0, 7, 0)
str_version = '.'.join(map(str, version)) str_version = '.'.join(map(str, version))
_plat = sys.platform.lower() _plat = sys.platform.lower()
is_macos = 'darwin' in _plat is_macos = 'darwin' in _plat
@@ -22,9 +22,8 @@ WindowGeometry = namedtuple('WindowGeometry', 'left top right bottom xnum ynum')
def _get_config_dir(): def _get_config_dir():
# This must be called before calling setApplicationName
if 'KITTY_CONFIG_DIRECTORY' in os.environ: if 'KITTY_CONFIG_DIRECTORY' in os.environ:
return os.path.abspath(os.path.expanduser(os.environ['VISE_CONFIG_DIRECTORY'])) return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config'))) candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config')))
ans = os.path.join(candidate, appname) ans = os.path.join(candidate, appname)

View File

@@ -180,7 +180,7 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
PyObject* PyObject*
create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic) { create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic) {
CTFace *self = (CTFace*)base_face; CTFace *self = (CTFace*)base_face;
char text[128] = {0}; char text[256] = {0};
cell_as_utf8(cell, true, text, ' '); cell_as_utf8(cell, true, text, ' ');
CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8); CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
if (str == NULL) return PyErr_NoMemory(); if (str == NULL) return PyErr_NoMemory();
@@ -200,6 +200,15 @@ glyph_id_for_codepoint(PyObject *s, char_type ch) {
return glyphs[0]; return glyphs[0];
} }
bool
is_glyph_empty(PyObject *s, glyph_index g) {
CTFace *self = (CTFace*)s;
CGGlyph gg = g;
CGRect bounds;
CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1);
return bounds.size.width <= 0;
}
static inline float static inline float
scaled_point_sz() { scaled_point_sz() {
return ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts; return ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts;

View File

@@ -180,10 +180,12 @@ extern bool init_fontconfig_library(PyObject*);
extern bool init_desktop(PyObject*); extern bool init_desktop(PyObject*);
extern bool init_fonts(PyObject*); extern bool init_fonts(PyObject*);
extern bool init_glfw(PyObject *m); extern bool init_glfw(PyObject *m);
extern bool init_child(PyObject *m);
extern bool init_state(PyObject *module); extern bool init_state(PyObject *module);
extern bool init_keys(PyObject *module); extern bool init_keys(PyObject *module);
extern bool init_graphics(PyObject *module); extern bool init_graphics(PyObject *module);
extern bool init_shaders(PyObject *module); extern bool init_shaders(PyObject *module);
extern bool init_mouse(PyObject *module);
#ifdef __APPLE__ #ifdef __APPLE__
extern int init_CoreText(PyObject *); extern int init_CoreText(PyObject *);
extern bool init_cocoa(PyObject *module); extern bool init_cocoa(PyObject *module);
@@ -211,10 +213,12 @@ PyInit_fast_data_types(void) {
if (!init_ColorProfile(m)) return NULL; if (!init_ColorProfile(m)) return NULL;
if (!init_Screen(m)) return NULL; if (!init_Screen(m)) return NULL;
if (!init_glfw(m)) return NULL; if (!init_glfw(m)) return NULL;
if (!init_child(m)) return NULL;
if (!init_state(m)) return NULL; if (!init_state(m)) return NULL;
if (!init_keys(m)) return NULL; if (!init_keys(m)) return NULL;
if (!init_graphics(m)) return NULL; if (!init_graphics(m)) return NULL;
if (!init_shaders(m)) return NULL; if (!init_shaders(m)) return NULL;
if (!init_mouse(m)) return NULL;
#ifdef __APPLE__ #ifdef __APPLE__
if (!init_CoreText(m)) return NULL; if (!init_CoreText(m)) return NULL;
if (!init_cocoa(m)) return NULL; if (!init_cocoa(m)) return NULL;
@@ -227,7 +231,7 @@ PyInit_fast_data_types(void) {
#define OOF(n) #n, offsetof(Cell, n) #define OOF(n) #n, offsetof(Cell, n)
if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}", if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}",
OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL; OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc_idx), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL;
#undef OOF #undef OOF
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT); PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT); PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);

View File

@@ -25,12 +25,13 @@
#define MIN(x, y) (((x) > (y)) ? (y) : (x)) #define MIN(x, y) (((x) > (y)) ? (y) : (x))
#define xstr(s) str(s) #define xstr(s) str(s)
#define str(s) #s #define str(s) #s
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); } #define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); }
typedef unsigned long long id_type; typedef unsigned long long id_type;
typedef uint32_t char_type; typedef uint32_t char_type;
typedef uint32_t color_type; typedef uint32_t color_type;
typedef uint32_t combining_type; typedef uint16_t combining_type;
typedef uint32_t pixel; typedef uint32_t pixel;
typedef unsigned int index_type; typedef unsigned int index_type;
typedef uint16_t sprite_index; typedef uint16_t sprite_index;
@@ -55,8 +56,6 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
#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 CC_MASK 0xFFFF
#define CC_SHIFT 16
#define UTF8_ACCEPT 0 #define UTF8_ACCEPT 0
#define UTF8_REJECT 1 #define UTF8_REJECT 1
#define DECORATION_FG_CODE 58 #define DECORATION_FG_CODE 58
@@ -133,11 +132,12 @@ typedef struct {
} Region; } Region;
typedef struct { typedef struct {
char_type ch;
color_type fg, bg, decoration_fg; color_type fg, bg, decoration_fg;
combining_type cc;
sprite_index sprite_x, sprite_y, sprite_z; sprite_index sprite_x, sprite_y, sprite_z;
attrs_type attrs; attrs_type attrs;
// The following are only needed on the CPU, not the GPU
char_type ch;
combining_type cc_idx[2];
} Cell; } Cell;
typedef struct { typedef struct {
@@ -181,7 +181,7 @@ typedef struct {
} Cursor; } Cursor;
typedef struct { typedef struct {
bool is_visible; bool is_visible, is_focused;
CursorShape shape; CursorShape shape;
unsigned int x, y; unsigned int x, y;
double left, right, top, bottom; double left, right, top, bottom;
@@ -278,7 +278,7 @@ void mouse_event(int, int);
void focus_in_event(); void focus_in_event();
void wakeup_io_loop(bool); void wakeup_io_loop(bool);
void scroll_event(double, double); void scroll_event(double, double);
void fake_scroll(bool); void fake_scroll(int, bool);
void set_special_key_combo(int glfw_key, int mods); void set_special_key_combo(int glfw_key, int mods);
void on_text_input(unsigned int codepoint, int mods); void on_text_input(unsigned int codepoint, int mods);
void on_key_input(int key, int scancode, int action, int mods); void on_key_input(int key, int scancode, int action, int mods);

14
kitty/emoji.h generated
View File

@@ -1,9 +1,17 @@
// unicode data, built from the unicode standard on: 2018-01-18
// see gen-wcwidth.py
#pragma once #pragma once
#include "data-types.h" #include "data-types.h"
START_ALLOW_CASE_RANGE START_ALLOW_CASE_RANGE
static inline bool is_emoji(uint32_t code) {
static inline bool
is_emoji(char_type code) {
switch(code) { switch(code) {
case 0x2194 ... 0x2199:
return true;
case 0x21a9 ... 0x21aa:
return true;
case 0x231a ... 0x231b: case 0x231a ... 0x231b:
return true; return true;
case 0x2328: case 0x2328:
@@ -276,7 +284,8 @@ static inline bool is_emoji(uint32_t code) {
} }
return false; return false;
} }
static inline bool is_emoji_modifier(uint32_t code) { static inline bool
is_emoji_modifier(char_type code) {
switch(code) { switch(code) {
case 0x1f3fb ... 0x1f3ff: case 0x1f3fb ... 0x1f3ff:
return true; return true;
@@ -284,4 +293,5 @@ static inline bool is_emoji_modifier(uint32_t code) {
} }
return false; return false;
} }
END_ALLOW_CASE_RANGE END_ALLOW_CASE_RANGE

View File

@@ -1,4 +1,5 @@
/* /*
* vim:fileencoding=utf-8
* fonts.c * fonts.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net> * Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
* *
@@ -8,11 +9,11 @@
#include "fonts.h" #include "fonts.h"
#include "state.h" #include "state.h"
#include "emoji.h" #include "emoji.h"
#include "unicode-data.h"
#define MISSING_GLYPH 4 #define MISSING_GLYPH 4
#define MAX_NUM_EXTRA_GLYPHS 8 #define MAX_NUM_EXTRA_GLYPHS 8
typedef uint16_t glyph_index;
typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*); typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*);
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL; send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
static PyObject *python_send_to_gpu_impl = NULL; static PyObject *python_send_to_gpu_impl = NULL;
@@ -36,11 +37,15 @@ struct SpritePosition {
ExtraGlyphs extra_glyphs; ExtraGlyphs extra_glyphs;
}; };
#define SPECIAL_FILLED_MASK 1
#define SPECIAL_VALUE_MASK 2
#define EMPTY_FILLED_MASK 4
#define EMPTY_VALUE_MASK 8
struct SpecialGlyphCache { struct SpecialGlyphCache {
SpecialGlyphCache *next; SpecialGlyphCache *next;
glyph_index glyph; glyph_index glyph;
bool is_special, filled; uint8_t data;
}; };
typedef struct { typedef struct {
@@ -51,7 +56,7 @@ typedef struct {
static GPUSpriteTracker sprite_tracker = {0}; static GPUSpriteTracker sprite_tracker = {0};
static hb_buffer_t *harfbuzz_buffer = NULL; static hb_buffer_t *harfbuzz_buffer = NULL;
static char_type shape_buffer[2048] = {0}; static char_type shape_buffer[4096] = {0};
typedef struct { typedef struct {
@@ -150,13 +155,13 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui
return s; return s;
} }
static SpecialGlyphCache* static inline SpecialGlyphCache*
special_glyph_cache_for(Font *font, glyph_index glyph) { special_glyph_cache_for(Font *font, glyph_index glyph, uint8_t mask) {
SpecialGlyphCache *s = font->special_glyph_cache + (glyph & 0x3ff); SpecialGlyphCache *s = font->special_glyph_cache + (glyph & 0x3ff);
// Optimize for the common case of glyph under 1024 already in the cache // Optimize for the common case of glyph under 1024 already in the cache
if (LIKELY(s->glyph == glyph && s->filled)) return s; // Cache hit if (LIKELY(s->glyph == glyph && s->data & mask)) return s; // Cache hit
while(true) { while(true) {
if (s->filled) { if (s->data & mask) {
if (s->glyph == glyph) return s; // Cache hit if (s->glyph == glyph) return s; // Cache hit
} else { } else {
break; break;
@@ -211,7 +216,7 @@ clear_sprite_map(Font *font) {
void void
clear_special_glyph_cache(Font *font) { clear_special_glyph_cache(Font *font) {
#define CLEAR(s) s->filled = false; s->glyph = 0; #define CLEAR(s) s->data = 0; s->glyph = 0;
SpecialGlyphCache *s; SpecialGlyphCache *s;
for (size_t i = 0; i < sizeof(font->special_glyph_cache)/sizeof(font->special_glyph_cache[0]); i++) { for (size_t i = 0; i < sizeof(font->special_glyph_cache)/sizeof(font->special_glyph_cache[0]); i++) {
s = font->special_glyph_cache + i; s = font->special_glyph_cache + i;
@@ -289,6 +294,8 @@ update_cell_metrics() {
if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; } if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; }
if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; } if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; }
underline_position = MIN(cell_height - 1, underline_position); underline_position = MIN(cell_height - 1, underline_position);
// ensure there is at least a couple of pixels available to render styled underlines
while (underline_position > baseline + 1 && cell_height - underline_position < 2) underline_position--;
if (line_height_adjustment > 1) { if (line_height_adjustment > 1) {
baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2); baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2); underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
@@ -321,28 +328,17 @@ face_has_codepoint(PyObject* face, char_type cp) {
static inline bool static inline bool
has_cell_text(Font *self, Cell *cell) { has_cell_text(Font *self, Cell *cell) {
if (!face_has_codepoint(self->face, cell->ch)) return false; if (!face_has_codepoint(self->face, cell->ch)) return false;
if (cell->cc) { for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
if (!face_has_codepoint(self->face, cell->cc & CC_MASK)) return false; if (!face_has_codepoint(self->face, codepoint_for_mark(cell->cc_idx[i]))) return false;
char_type cc = cell->cc >> 16;
if (cc && !face_has_codepoint(self->face, cc)) return false;
} }
return true; return true;
} }
static inline ssize_t static inline ssize_t
fallback_font(Cell *cell) { load_fallback_font(Cell *cell, bool bold, bool italic) {
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
ssize_t f;
for (size_t i = 0, j = fonts.first_fallback_font_idx; i < fonts.fallback_fonts_count; i++, j++) {
Font *ff = fonts.fonts +j;
if (ff->bold == bold && ff->italic == italic && has_cell_text(ff, cell)) {
return j;
}
}
if (fonts.fallback_fonts_count > 100) { fprintf(stderr, "Too many fallback fonts\n"); return MISSING_FONT; } if (fonts.fallback_fonts_count > 100) { fprintf(stderr, "Too many fallback fonts\n"); return MISSING_FONT; }
ssize_t f;
if (bold) f = fonts.italic_font_idx > 0 ? fonts.bi_font_idx : fonts.bold_font_idx; if (bold) f = fonts.italic_font_idx > 0 ? fonts.bi_font_idx : fonts.bold_font_idx;
else f = italic ? fonts.italic_font_idx : fonts.medium_font_idx; else f = italic ? fonts.italic_font_idx : fonts.medium_font_idx;
@@ -363,6 +359,31 @@ fallback_font(Cell *cell) {
return ans; return ans;
} }
static inline ssize_t
fallback_font(Cell *cell) {
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
// Load the emoji fallback font first as on Linux there are a bunch of
// non-color fonts that provide some emoji glyphs.
if (fonts.fallback_fonts_count < 1) {
Cell c = {0};
c.ch = 0x1f648; // 🙈
load_fallback_font(&c, false, false);
}
// Check if one of the existing fallback fonts has this text
for (size_t i = 0, j = fonts.first_fallback_font_idx; i < fonts.fallback_fonts_count; i++, j++) {
Font *ff = fonts.fonts +j;
if (ff->bold == bold && ff->italic == italic && has_cell_text(ff, cell)) {
return j;
}
}
return load_fallback_font(cell, bold, italic);
}
static inline ssize_t static inline ssize_t
in_symbol_maps(char_type ch) { in_symbol_maps(char_type ch) {
for (size_t i = 0; i < fonts.symbol_maps_count; i++) { for (size_t i = 0; i < fonts.symbol_maps_count; i++) {
@@ -472,14 +493,12 @@ load_hb_buffer(Cell *first_cell, index_type num_cells) {
hb_buffer_clear_contents(harfbuzz_buffer); hb_buffer_clear_contents(harfbuzz_buffer);
while (num_cells) { while (num_cells) {
attrs_type prev_width = 0; attrs_type prev_width = 0;
for (num = 0; num_cells && num < sizeof(shape_buffer)/sizeof(shape_buffer[0]) - 20; first_cell++, num_cells--) { for (num = 0; num_cells && num < arraysz(shape_buffer) - 20 - arraysz(first_cell->cc_idx); first_cell++, num_cells--) {
if (prev_width == 2) { prev_width = 0; continue; } if (prev_width == 2) { prev_width = 0; continue; }
shape_buffer[num++] = first_cell->ch; shape_buffer[num++] = first_cell->ch;
prev_width = first_cell->attrs & WIDTH_MASK; prev_width = first_cell->attrs & WIDTH_MASK;
if (first_cell->cc) { for (unsigned i = 0; i < arraysz(first_cell->cc_idx) && first_cell->cc_idx[i]; i++) {
shape_buffer[num++] = first_cell->cc & CC_MASK; shape_buffer[num++] = codepoint_for_mark(first_cell->cc_idx[i]);
combining_type cc2 = first_cell->cc >> 16;
if (cc2) shape_buffer[num++] = cc2 & CC_MASK;
} }
} }
hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num); hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num);
@@ -538,28 +557,84 @@ typedef struct {
char_type current_codepoint; char_type current_codepoint;
} CellData; } CellData;
typedef struct {
unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells;
} Group;
typedef struct {
uint32_t previous_cluster;
bool prev_was_special, prev_was_empty;
CellData current_cell_data;
Group *groups;
size_t groups_capacity, group_idx, glyph_idx, cell_idx, num_cells, num_glyphs;
Cell *first_cell, *last_cell;
hb_glyph_info_t *info;
hb_glyph_position_t *positions;
} GroupState;
static GroupState group_state = {0};
static inline unsigned int
num_codepoints_in_cell(Cell *cell) {
unsigned int ans = 1;
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++;
return ans;
}
static inline void
shape(Cell *first_cell, index_type num_cells, hb_font_t *font) {
if (group_state.groups_capacity <= 2 * num_cells) {
group_state.groups_capacity = MAX(128, 2 * num_cells); // avoid unnecessary reallocs
group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity);
if (!group_state.groups) fatal("Out of memory");
}
group_state.previous_cluster = UINT32_MAX;
group_state.prev_was_special = false;
group_state.prev_was_empty = false;
group_state.current_cell_data.cell = first_cell; group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cell); group_state.current_cell_data.codepoints_consumed = 0; group_state.current_cell_data.current_codepoint = first_cell->ch;
memset(group_state.groups, 0, sizeof(Group) * group_state.groups_capacity);
group_state.group_idx = 0;
group_state.glyph_idx = 0;
group_state.cell_idx = 0;
group_state.num_cells = num_cells;
group_state.first_cell = first_cell;
group_state.last_cell = first_cell + (num_cells ? num_cells - 1 : 0);
load_hb_buffer(first_cell, num_cells);
hb_shape(font, harfbuzz_buffer, NULL, 0);
unsigned int info_length, positions_length;
group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
group_state.positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length);
if (!group_state.info || !group_state.positions) group_state.num_glyphs = 0;
else group_state.num_glyphs = MIN(info_length, positions_length);
}
static inline bool static inline bool
is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) { is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) {
// A glyph is special if the codepoint it corresponds to matches a // A glyph is special if the codepoint it corresponds to matches a
// different glyph in the font // different glyph in the font
SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id); SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, SPECIAL_FILLED_MASK);
if (s == NULL) return false; if (s == NULL) return false;
if (!s->filled) { if (!(s->data & SPECIAL_FILLED_MASK)) {
s->is_special = cell_data->current_codepoint ? ( bool is_special = cell_data->current_codepoint ? (
glyph_id != glyph_id_for_codepoint(font->face, cell_data->current_codepoint) ? true : false) glyph_id != glyph_id_for_codepoint(font->face, cell_data->current_codepoint) ? true : false)
: :
false; false;
s->filled = true; uint8_t val = is_special ? SPECIAL_VALUE_MASK : 0;
s->data |= val | SPECIAL_FILLED_MASK;
} }
return s->is_special; return s->data & SPECIAL_VALUE_MASK;
} }
static inline unsigned int static inline bool
num_codepoints_in_cell(Cell *cell) { is_empty_glyph(glyph_index glyph_id, Font *font) {
unsigned int ans = 1; // A glyph is empty if its metrics have a width of zero
if (cell->cc) ans += ((cell->cc >> CC_SHIFT) & CC_MASK) ? 2 : 1; SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, EMPTY_FILLED_MASK);
return ans; if (s == NULL) return false;
if (!(s->data & EMPTY_FILLED_MASK)) {
uint8_t val = is_glyph_empty(font->face, glyph_id) ? EMPTY_VALUE_MASK : 0;
s->data |= val | EMPTY_FILLED_MASK;
}
return s->data & EMPTY_VALUE_MASK;
} }
static inline unsigned int static inline unsigned int
@@ -579,100 +654,140 @@ check_cell_consumed(CellData *cell_data, Cell *last_cell) {
case 0: case 0:
cell_data->current_codepoint = cell_data->cell->ch; cell_data->current_codepoint = cell_data->cell->ch;
break; break;
case 1:
cell_data->current_codepoint = cell_data->cell->cc & CC_MASK;
break;
case 2:
cell_data->current_codepoint = (cell_data->cell->cc >> CC_SHIFT) & CC_MASK;
break;
default: default:
cell_data->current_codepoint = 0; cell_data->current_codepoint = codepoint_for_mark(cell_data->cell->cc_idx[cell_data->codepoints_consumed - 1]);
break; break;
} }
} }
return 0; return 0;
} }
static inline glyph_index
next_group(Font *font, unsigned int *num_group_cells, unsigned int *num_group_glyphs, Cell *cells, hb_glyph_info_t *info, unsigned int max_num_glyphs, unsigned int max_num_cells, ExtraGlyphs *extra_glyphs) {
// See https://github.com/behdad/harfbuzz/issues/615 for a discussion of
// how to break text into cells. In addition, we have to deal with
// monospace ligature fonts that use dummy glyphs of zero size to implement
// their ligatures.
CellData cell_data;
cell_data.cell = cells; cell_data.num_codepoints = num_codepoints_in_cell(cells); cell_data.codepoints_consumed = 0; cell_data.current_codepoint = cells->ch;
#define LIMIT (MAX_NUM_EXTRA_GLYPHS + 1)
glyph_index glyphs_in_group[LIMIT];
unsigned int ncells = 0, nglyphs = 0, n;
uint32_t previous_cluster = UINT32_MAX, cluster;
Cell *last_cell = cells + max_num_cells;
unsigned int cell_limit = MIN(max_num_cells, LIMIT + 1), glyph_limit = MIN(LIMIT, max_num_glyphs);
bool is_special, prev_was_special = false;
while(nglyphs < glyph_limit && ncells < cell_limit) {
glyph_index glyph_id = info[nglyphs].codepoint;
cluster = info[nglyphs].cluster;
is_special = is_special_glyph(glyph_id, font, &cell_data);
if (prev_was_special && !is_special) break;
glyphs_in_group[nglyphs++] = glyph_id;
// Soak up a number of codepoints indicated by the difference in cluster numbers.
if (cluster > previous_cluster || nglyphs == 1) {
n = nglyphs == 1 ? 1 : cluster - previous_cluster;
unsigned int before = ncells;
while(n-- && ncells < max_num_cells) ncells += check_cell_consumed(&cell_data, last_cell);
if (ncells > before && !is_special) break;
}
previous_cluster = cluster;
prev_was_special = is_special;
}
*num_group_cells = MAX(1, MIN(ncells, cell_limit));
*num_group_glyphs = MAX(1, MIN(nglyphs, glyph_limit));
memset(extra_glyphs, 0, sizeof(ExtraGlyphs));
switch(nglyphs) {
case 0:
case 1:
break;
default:
memcpy(&extra_glyphs->data, glyphs_in_group + 1, sizeof(glyph_index) * MIN(MAX_NUM_EXTRA_GLYPHS, nglyphs - 1));
break;
}
#undef LIMIT
return glyphs_in_group[0];
}
static inline unsigned int
shape(Cell *first_cell, index_type num_cells, hb_font_t *font, hb_glyph_info_t **info, hb_glyph_position_t **positions) {
load_hb_buffer(first_cell, num_cells);
hb_shape(font, harfbuzz_buffer, NULL, 0);
unsigned int info_length, positions_length;
*info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
*positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length);
if (!info || !positions) return 0;
return MIN(info_length, positions_length);
}
static inline void static inline void
shape_run(Cell *first_cell, index_type num_cells, Font *font) { shape_run(Cell *first_cell, index_type num_cells, Font *font) {
hb_glyph_info_t *info; shape(first_cell, num_cells, harfbuzz_font_for_face(font->face));
hb_glyph_position_t *positions;
unsigned int num_glyphs = shape(first_cell, num_cells, harfbuzz_font_for_face(font->face), &info, &positions);
#if 0 #if 0
// You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text // You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS); hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
printf("\n%s\n", (char*)canvas); printf("\n%s\n", (char*)canvas);
clear_canvas(); clear_canvas();
#endif #endif
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells; /* Now distribute the glyphs into groups of cells
ExtraGlyphs extra_glyphs; * Considerations to keep in mind:
glyph_index first_glyph; * Group sizes should be as small as possible for best performance
while(run_pos < num_glyphs && cell_pos < num_cells) { * Combining chars can result in multiple glyphs rendered into a single cell
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, first_cell + cell_pos, info + run_pos, num_glyphs - run_pos, num_cells - cell_pos, &extra_glyphs); * Emoji and East Asian wide chars can cause a single glyph to be rendered over multiple cells
/* printf("Group: num_group_cells: %u num_group_glyphs: %u\n", num_group_cells, num_group_glyphs); */ * Ligature fonts, take two common approaches:
render_group(num_group_cells, num_group_glyphs, first_cell + cell_pos, info + run_pos, positions + run_pos, font, first_glyph, &extra_glyphs); * 1. ABC becomes EMPTY, EMPTY, WIDE GLYPH this means we have to render N glyphs in N cells (example Fira Code)
run_pos += num_group_glyphs; cell_pos += num_group_cells; * 2. ABC becomes WIDE GLYPH this means we have to render one glyph in N cells (example Operator Mono Lig)
*
* We rely on the cluster numbers from harfbuzz to tell us how many unicode codepoints a glyph corresponds to.
* Then we check if the glyph is a ligature glyph (is_special_glyph) and if it is an empty glyph. These three
* datapoints give us enough information to satisfy the constraint above, for a wide variety of fonts.
*/
uint32_t cluster, next_cluster;
bool add_to_current_group;
#define G(x) (group_state.x)
#define MAX_GLYPHS_IN_GROUP (MAX_NUM_EXTRA_GLYPHS + 1)
while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) {
glyph_index glyph_id = G(info)[G(glyph_idx)].codepoint;
cluster = G(info)[G(glyph_idx)].cluster;
bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data));
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
uint32_t num_codepoints_used_by_glyph = 0;
bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1;
Group *current_group = G(groups) + G(group_idx);
if (is_last_glyph) {
num_codepoints_used_by_glyph = UINT32_MAX;
} else {
next_cluster = G(info)[G(glyph_idx) + 1].cluster;
if (next_cluster > cluster) num_codepoints_used_by_glyph = next_cluster - cluster;
}
if (!current_group->num_glyphs) {
add_to_current_group = true;
} else {
if (is_special) {
add_to_current_group = G(prev_was_empty);
} else {
add_to_current_group = !G(prev_was_special);
}
}
if (current_group->num_glyphs >= MAX_GLYPHS_IN_GROUP || current_group->num_cells >= MAX_GLYPHS_IN_GROUP) add_to_current_group = false;
if (!add_to_current_group) { G(group_idx)++; current_group = G(groups) + G(group_idx); }
if (!current_group->num_glyphs++) {
current_group->first_glyph_idx = G(glyph_idx);
current_group->first_cell_idx = G(cell_idx);
}
#define MOVE_GLYPH_TO_NEXT_GROUP(start_cell_idx) { \
current_group->num_glyphs--; \
G(group_idx)++; current_group = G(groups) + G(group_idx); \
current_group->first_cell_idx = start_cell_idx; \
current_group->num_glyphs = 1; \
current_group->first_glyph_idx = G(glyph_idx); \
}
if (is_last_glyph) {
// soak up all remaining cells
if (G(cell_idx) < G(num_cells)) {
unsigned int num_left = G(num_cells) - G(cell_idx);
if (current_group->num_cells + num_left > MAX_GLYPHS_IN_GROUP) MOVE_GLYPH_TO_NEXT_GROUP(G(cell_idx));
current_group->num_cells += num_left;
if (current_group->num_cells > MAX_GLYPHS_IN_GROUP) current_group->num_cells = MAX_GLYPHS_IN_GROUP; // leave any trailing cells empty
G(cell_idx) += num_left;
}
} else {
unsigned int num_cells_consumed = 0, start_cell_idx = G(cell_idx);
while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) {
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cell));
G(cell_idx) += w;
num_cells_consumed += w;
num_codepoints_used_by_glyph--;
}
if (num_cells_consumed) {
if (num_cells_consumed > MAX_GLYPHS_IN_GROUP) {
// Nasty, a single glyph used more than MAX_GLYPHS_IN_GROUP cells, we cannot render this case correctly
fprintf(stderr, "The glyph: %u needs more than %u cells, cannot render it\n", glyph_id, MAX_GLYPHS_IN_GROUP);
current_group->num_glyphs--;
while (num_cells_consumed) {
G(group_idx)++; current_group = G(groups) + G(group_idx);
current_group->num_glyphs = 1; current_group->first_glyph_idx = G(glyph_idx);
current_group->num_cells = MIN(num_cells_consumed, MAX_GLYPHS_IN_GROUP);
current_group->first_cell_idx = start_cell_idx;
start_cell_idx += current_group->num_cells;
num_cells_consumed -= current_group->num_cells;
}
} else {
if (num_cells_consumed + current_group->num_cells > MAX_GLYPHS_IN_GROUP) MOVE_GLYPH_TO_NEXT_GROUP(start_cell_idx);
current_group->num_cells += num_cells_consumed;
if (!is_special) { // not a ligature, end the group
G(group_idx)++; current_group = G(groups) + G(group_idx);
}
}
}
}
G(prev_was_special) = is_special;
G(prev_was_empty) = is_empty;
G(previous_cluster) = cluster;
G(glyph_idx)++;
}
#undef MOVE_GLYPH_TO_NEXT_GROUP
#undef MAX_GLYPHS_IN_GROUP
}
static inline void
render_groups(Font *font) {
unsigned idx = 0;
ExtraGlyphs ed;
while (idx <= G(group_idx)) {
Group *group = G(groups) + idx;
if (!group->num_cells) break;
/* printf("Group: idx: %u num_cells: %u num_glyphs: %u first_glyph_idx: %u first_cell_idx: %u total_num_glyphs: %zu\n", */
/* idx, group->num_cells, group->num_glyphs, group->first_glyph_idx, group->first_cell_idx, group_state.num_glyphs); */
glyph_index primary = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
for (unsigned i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) ed.data[i-1] = G(info)[group->first_glyph_idx + i].codepoint;
render_group(group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
idx++;
} }
} }
@@ -692,30 +807,32 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
font = calloc(1, sizeof(Font)); font = calloc(1, sizeof(Font));
font->face = face; font->face = face;
} }
hb_glyph_info_t *info; shape_run(line->cells, num, font);
hb_glyph_position_t *positions;
unsigned int num_glyphs = shape(line->cells, num, harfbuzz_font_for_face(font->face), &info, &positions);
PyObject *ans = PyList_New(0); PyObject *ans = PyList_New(0);
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells; unsigned int idx = 0;
ExtraGlyphs extra_glyphs;
glyph_index first_glyph; glyph_index first_glyph;
while(run_pos < num_glyphs && cell_pos < num) { while (idx <= G(group_idx)) {
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, line->cells + cell_pos, info + run_pos, num_glyphs - run_pos, num - cell_pos, &extra_glyphs); Group *group = G(groups) + idx;
if (!group->num_cells) break;
first_glyph = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS); PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS);
for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", extra_glyphs.data[g])); for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", g + 1 < group->num_glyphs ? G(info)[group->first_glyph_idx + g].codepoint : 0));
PyList_Append(ans, Py_BuildValue("IIIN", num_group_cells, num_group_glyphs, first_glyph, eg)); PyList_Append(ans, Py_BuildValue("IIIN", group->num_cells, group->num_glyphs, first_glyph, eg));
run_pos += num_group_glyphs; cell_pos += num_group_cells; idx++;
} }
if (face) { Py_CLEAR(face); free(font); } if (face) { Py_CLEAR(face); free(font); }
return ans; return ans;
} }
#undef G
static inline void static inline void
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) { render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) {
switch(font_idx) { switch(font_idx) {
default: default:
shape_run(first_cell, num_cells, &fonts.fonts[font_idx]); shape_run(first_cell, num_cells, &fonts.fonts[font_idx]);
render_groups(&fonts.fonts[font_idx]);
break; break;
case BLANK_FONT: case BLANK_FONT:
while(num_cells--) set_sprite(first_cell++, 0, 0, 0); while(num_cells--) set_sprite(first_cell++, 0, 0, 0);
@@ -793,6 +910,7 @@ finalize(void) {
for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i); for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i);
free(fonts.symbol_maps); free(fonts.fonts); free(fonts.symbol_maps); free(fonts.fonts);
if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer); if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer);
free(group_state.groups);
} }
static PyObject* static PyObject*
@@ -926,12 +1044,11 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
PyObject *text; PyObject *text;
int bold, italic; int bold, italic;
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL; if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
static Py_UCS4 char_buf[16];
if (!PyUnicode_AsUCS4(text, char_buf, sizeof(char_buf)/sizeof(char_buf[0]), 1)) return NULL;
Cell cell = {0}; Cell cell = {0};
static Py_UCS4 char_buf[2 + arraysz(cell.cc_idx)];
if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL;
cell.ch = char_buf[0]; cell.ch = char_buf[0];
if (PyUnicode_GetLength(text) > 1) cell.cc |= char_buf[1] & CC_MASK; for (unsigned i = 0; i + 1 < PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
if (PyUnicode_GetLength(text) > 2) cell.cc |= (char_buf[2] & CC_MASK) << 16;
if (bold) cell.attrs |= 1 << BOLD_SHIFT; if (bold) cell.attrs |= 1 << BOLD_SHIFT;
if (italic) cell.attrs |= 1 << ITALIC_SHIFT; if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
ssize_t ans = fallback_font(&cell); ssize_t ans = fallback_font(&cell);

View File

@@ -14,7 +14,9 @@
// API that font backends need to implement // API that font backends need to implement
typedef uint16_t glyph_index;
unsigned int glyph_id_for_codepoint(PyObject *, char_type); unsigned int glyph_id_for_codepoint(PyObject *, char_type);
bool is_glyph_empty(PyObject *, glyph_index);
hb_font_t* harfbuzz_font_for_face(PyObject*); hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, unsigned int, bool); bool set_size_for_face(PyObject*, unsigned int, bool);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*); void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);

View File

@@ -73,7 +73,7 @@ def resize_fonts(new_sz):
def add_line(buf, cell_width, position, thickness, cell_height): def add_line(buf, cell_width, position, thickness, cell_height):
y = position - thickness // 2 y = position - thickness // 2
while thickness > 0 and y > -1 and y < cell_height - 1: while thickness > 0 and y > -1 and y < cell_height:
thickness -= 1 thickness -= 1
ctypes.memset(ctypes.addressof(buf) + (cell_width * y), 255, cell_width) ctypes.memset(ctypes.addressof(buf) + (cell_width * y), 255, cell_width)
y += 1 y += 1

View File

@@ -300,6 +300,17 @@ glyph_id_for_codepoint(PyObject *s, char_type cp) {
return FT_Get_Char_Index(((Face*)s)->face, cp); return FT_Get_Char_Index(((Face*)s)->face, cp);
} }
bool
is_glyph_empty(PyObject *s, glyph_index g) {
Face *self = (Face*)s;
if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; }
#define M self->face->glyph->metrics
/* printf("glyph: %u horiBearingX: %ld horiBearingY: %ld width: %ld height: %ld\n", g, M.horiBearingX, M.horiBearingY, M.width, M.height); */
return M.width == 0;
#undef M
}
hb_font_t* hb_font_t*
harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; } harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; }

View File

@@ -59,6 +59,9 @@ gl_init() {
ARB_TEST(texture_storage); ARB_TEST(texture_storage);
#undef ARB_TEST #undef ARB_TEST
glad_loaded = true; glad_loaded = true;
if (GLVersion.major < OPENGL_REQUIRED_VERSION_MAJOR || (GLVersion.major == OPENGL_REQUIRED_VERSION_MAJOR && GLVersion.minor < OPENGL_REQUIRED_VERSION_MINOR)) {
fatal("OpenGL version is %d.%d, version >= 3.3 required for kitty", GLVersion.major, GLVersion.minor);
}
} }
} }

21
kitty/glfw-wrapper.c generated
View File

@@ -23,9 +23,6 @@ load_glfw(const char* path) {
*(void **) (&glfwInitHint_impl) = dlsym(handle, "glfwInitHint"); *(void **) (&glfwInitHint_impl) = dlsym(handle, "glfwInitHint");
if (glfwInitHint_impl == NULL) fail("Failed to load glfw function glfwInitHint with error: %s", dlerror()); if (glfwInitHint_impl == NULL) fail("Failed to load glfw function glfwInitHint with error: %s", dlerror());
*(void **) (&glfwInitHintString_impl) = dlsym(handle, "glfwInitHintString");
if (glfwInitHintString_impl == NULL) fail("Failed to load glfw function glfwInitHintString with error: %s", dlerror());
*(void **) (&glfwGetVersion_impl) = dlsym(handle, "glfwGetVersion"); *(void **) (&glfwGetVersion_impl) = dlsym(handle, "glfwGetVersion");
if (glfwGetVersion_impl == NULL) fail("Failed to load glfw function glfwGetVersion with error: %s", dlerror()); if (glfwGetVersion_impl == NULL) fail("Failed to load glfw function glfwGetVersion with error: %s", dlerror());
@@ -56,6 +53,12 @@ load_glfw(const char* path) {
*(void **) (&glfwGetMonitorName_impl) = dlsym(handle, "glfwGetMonitorName"); *(void **) (&glfwGetMonitorName_impl) = dlsym(handle, "glfwGetMonitorName");
if (glfwGetMonitorName_impl == NULL) fail("Failed to load glfw function glfwGetMonitorName with error: %s", dlerror()); if (glfwGetMonitorName_impl == NULL) fail("Failed to load glfw function glfwGetMonitorName with error: %s", dlerror());
*(void **) (&glfwSetMonitorUserPointer_impl) = dlsym(handle, "glfwSetMonitorUserPointer");
if (glfwSetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetMonitorUserPointer with error: %s", dlerror());
*(void **) (&glfwGetMonitorUserPointer_impl) = dlsym(handle, "glfwGetMonitorUserPointer");
if (glfwGetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetMonitorUserPointer with error: %s", dlerror());
*(void **) (&glfwSetMonitorCallback_impl) = dlsym(handle, "glfwSetMonitorCallback"); *(void **) (&glfwSetMonitorCallback_impl) = dlsym(handle, "glfwSetMonitorCallback");
if (glfwSetMonitorCallback_impl == NULL) fail("Failed to load glfw function glfwSetMonitorCallback with error: %s", dlerror()); if (glfwSetMonitorCallback_impl == NULL) fail("Failed to load glfw function glfwSetMonitorCallback with error: %s", dlerror());
@@ -80,6 +83,9 @@ load_glfw(const char* path) {
*(void **) (&glfwWindowHint_impl) = dlsym(handle, "glfwWindowHint"); *(void **) (&glfwWindowHint_impl) = dlsym(handle, "glfwWindowHint");
if (glfwWindowHint_impl == NULL) fail("Failed to load glfw function glfwWindowHint with error: %s", dlerror()); if (glfwWindowHint_impl == NULL) fail("Failed to load glfw function glfwWindowHint with error: %s", dlerror());
*(void **) (&glfwWindowHintString_impl) = dlsym(handle, "glfwWindowHintString");
if (glfwWindowHintString_impl == NULL) fail("Failed to load glfw function glfwWindowHintString with error: %s", dlerror());
*(void **) (&glfwCreateWindow_impl) = dlsym(handle, "glfwCreateWindow"); *(void **) (&glfwCreateWindow_impl) = dlsym(handle, "glfwCreateWindow");
if (glfwCreateWindow_impl == NULL) fail("Failed to load glfw function glfwCreateWindow with error: %s", dlerror()); if (glfwCreateWindow_impl == NULL) fail("Failed to load glfw function glfwCreateWindow with error: %s", dlerror());
@@ -197,6 +203,9 @@ load_glfw(const char* path) {
*(void **) (&glfwSetFramebufferSizeCallback_impl) = dlsym(handle, "glfwSetFramebufferSizeCallback"); *(void **) (&glfwSetFramebufferSizeCallback_impl) = dlsym(handle, "glfwSetFramebufferSizeCallback");
if (glfwSetFramebufferSizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetFramebufferSizeCallback with error: %s", dlerror()); if (glfwSetFramebufferSizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetFramebufferSizeCallback with error: %s", dlerror());
*(void **) (&glfwSetWindowContentScaleCallback_impl) = dlsym(handle, "glfwSetWindowContentScaleCallback");
if (glfwSetWindowContentScaleCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowContentScaleCallback with error: %s", dlerror());
*(void **) (&glfwPollEvents_impl) = dlsym(handle, "glfwPollEvents"); *(void **) (&glfwPollEvents_impl) = dlsym(handle, "glfwPollEvents");
if (glfwPollEvents_impl == NULL) fail("Failed to load glfw function glfwPollEvents with error: %s", dlerror()); if (glfwPollEvents_impl == NULL) fail("Failed to load glfw function glfwPollEvents with error: %s", dlerror());
@@ -287,6 +296,12 @@ load_glfw(const char* path) {
*(void **) (&glfwGetJoystickGUID_impl) = dlsym(handle, "glfwGetJoystickGUID"); *(void **) (&glfwGetJoystickGUID_impl) = dlsym(handle, "glfwGetJoystickGUID");
if (glfwGetJoystickGUID_impl == NULL) fail("Failed to load glfw function glfwGetJoystickGUID with error: %s", dlerror()); if (glfwGetJoystickGUID_impl == NULL) fail("Failed to load glfw function glfwGetJoystickGUID with error: %s", dlerror());
*(void **) (&glfwSetJoystickUserPointer_impl) = dlsym(handle, "glfwSetJoystickUserPointer");
if (glfwSetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetJoystickUserPointer with error: %s", dlerror());
*(void **) (&glfwGetJoystickUserPointer_impl) = dlsym(handle, "glfwGetJoystickUserPointer");
if (glfwGetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetJoystickUserPointer with error: %s", dlerror());
*(void **) (&glfwJoystickIsGamepad_impl) = dlsym(handle, "glfwJoystickIsGamepad"); *(void **) (&glfwJoystickIsGamepad_impl) = dlsym(handle, "glfwJoystickIsGamepad");
if (glfwJoystickIsGamepad_impl == NULL) fail("Failed to load glfw function glfwJoystickIsGamepad with error: %s", dlerror()); if (glfwJoystickIsGamepad_impl == NULL) fail("Failed to load glfw function glfwJoystickIsGamepad with error: %s", dlerror());

78
kitty/glfw-wrapper.h generated
View File

@@ -251,17 +251,37 @@
* @{ */ * @{ */
/*! @brief If this bit is set one or more Shift keys were held down. /*! @brief If this bit is set one or more Shift keys were held down.
*
* If this bit is set one or more Shift keys were held down.
*/ */
#define GLFW_MOD_SHIFT 0x0001 #define GLFW_MOD_SHIFT 0x0001
/*! @brief If this bit is set one or more Control keys were held down. /*! @brief If this bit is set one or more Control keys were held down.
*
* If this bit is set one or more Control keys were held down.
*/ */
#define GLFW_MOD_CONTROL 0x0002 #define GLFW_MOD_CONTROL 0x0002
/*! @brief If this bit is set one or more Alt keys were held down. /*! @brief If this bit is set one or more Alt keys were held down.
*
* If this bit is set one or more Alt keys were held down.
*/ */
#define GLFW_MOD_ALT 0x0004 #define GLFW_MOD_ALT 0x0004
/*! @brief If this bit is set one or more Super keys were held down. /*! @brief If this bit is set one or more Super keys were held down.
*
* If this bit is set one or more Super keys were held down.
*/ */
#define GLFW_MOD_SUPER 0x0008 #define GLFW_MOD_SUPER 0x0008
/*! @brief If this bit is set the Caps Lock key is enabled.
*
* If this bit is set the Caps Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_CAPS_LOCK 0x0010
/*! @brief If this bit is set the Num Lock key is enabled.
*
* If this bit is set the Num Lock key is enabled and the @ref
* GLFW_LOCK_KEY_MODS input mode is set.
*/
#define GLFW_MOD_NUM_LOCK 0x0020
/*! @} */ /*! @} */
@@ -552,6 +572,7 @@
* [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib). * [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib).
*/ */
#define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A #define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A
#define GLFW_HOVERED 0x0002000B
/*! @brief Framebuffer bit depth hint. /*! @brief Framebuffer bit depth hint.
* *
@@ -702,8 +723,11 @@
#define GLFW_CONTEXT_CREATION_API 0x0002200B #define GLFW_CONTEXT_CREATION_API 0x0002200B
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001 #define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
#define GLFW_COCOA_FRAME_AUTOSAVE 0x00023002 #define GLFW_COCOA_FRAME_NAME 0x00023002
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003 #define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
#define GLFW_X11_CLASS_NAME 0x00024001
#define GLFW_X11_INSTANCE_NAME 0x00024002
/*! @} */ /*! @} */
#define GLFW_NO_API 0 #define GLFW_NO_API 0
@@ -721,6 +745,7 @@
#define GLFW_CURSOR 0x00033001 #define GLFW_CURSOR 0x00033001
#define GLFW_STICKY_KEYS 0x00033002 #define GLFW_STICKY_KEYS 0x00033002
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003 #define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_CURSOR_NORMAL 0x00034001 #define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002 #define GLFW_CURSOR_HIDDEN 0x00034002
@@ -783,9 +808,6 @@
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001 #define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
#define GLFW_COCOA_MENUBAR 0x00051002 #define GLFW_COCOA_MENUBAR 0x00051002
#define GLFW_X11_WM_CLASS_NAME 0x00052001
#define GLFW_X11_WM_CLASS_CLASS 0x00052002
/*! @} */ /*! @} */
#define GLFW_DONT_CARE -1 #define GLFW_DONT_CARE -1
@@ -804,7 +826,7 @@
* @sa @ref glfwGetProcAddress * @sa @ref glfwGetProcAddress
* *
* @since Added in version 3.0. * @since Added in version 3.0.
*
* @ingroup context * @ingroup context
*/ */
typedef void (*GLFWglproc)(void); typedef void (*GLFWglproc)(void);
@@ -1015,6 +1037,24 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
*/ */
typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int); typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
/*! @brief The function signature for window content scale callbacks.
*
* This is the function signature for window content scale callback
* functions.
*
* @param[in] window The window whose content scale changed.
* @param[in] xscale The new x-axis content scale of the window.
* @param[in] yscale The new y-axis content scale of the window.
*
* @sa @ref window_scale
* @sa @ref glfwSetWindowContentScaleCallback
*
* @since Added in version 3.3.
*
* @ingroup window
*/
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
/*! @brief The function signature for mouse button callbacks. /*! @brief The function signature for mouse button callbacks.
* *
* This is the function signature for mouse button callback functions. * This is the function signature for mouse button callback functions.
@@ -1359,10 +1399,6 @@ typedef void (*glfwInitHint_func)(int, int);
glfwInitHint_func glfwInitHint_impl; glfwInitHint_func glfwInitHint_impl;
#define glfwInitHint glfwInitHint_impl #define glfwInitHint glfwInitHint_impl
typedef void (*glfwInitHintString_func)(int, const char*);
glfwInitHintString_func glfwInitHintString_impl;
#define glfwInitHintString glfwInitHintString_impl
typedef void (*glfwGetVersion_func)(int*, int*, int*); typedef void (*glfwGetVersion_func)(int*, int*, int*);
glfwGetVersion_func glfwGetVersion_impl; glfwGetVersion_func glfwGetVersion_impl;
#define glfwGetVersion glfwGetVersion_impl #define glfwGetVersion glfwGetVersion_impl
@@ -1403,6 +1439,14 @@ typedef const char* (*glfwGetMonitorName_func)(GLFWmonitor*);
glfwGetMonitorName_func glfwGetMonitorName_impl; glfwGetMonitorName_func glfwGetMonitorName_impl;
#define glfwGetMonitorName glfwGetMonitorName_impl #define glfwGetMonitorName glfwGetMonitorName_impl
typedef void (*glfwSetMonitorUserPointer_func)(GLFWmonitor*, void*);
glfwSetMonitorUserPointer_func glfwSetMonitorUserPointer_impl;
#define glfwSetMonitorUserPointer glfwSetMonitorUserPointer_impl
typedef void* (*glfwGetMonitorUserPointer_func)(GLFWmonitor*);
glfwGetMonitorUserPointer_func glfwGetMonitorUserPointer_impl;
#define glfwGetMonitorUserPointer glfwGetMonitorUserPointer_impl
typedef GLFWmonitorfun (*glfwSetMonitorCallback_func)(GLFWmonitorfun); typedef GLFWmonitorfun (*glfwSetMonitorCallback_func)(GLFWmonitorfun);
glfwSetMonitorCallback_func glfwSetMonitorCallback_impl; glfwSetMonitorCallback_func glfwSetMonitorCallback_impl;
#define glfwSetMonitorCallback glfwSetMonitorCallback_impl #define glfwSetMonitorCallback glfwSetMonitorCallback_impl
@@ -1435,6 +1479,10 @@ typedef void (*glfwWindowHint_func)(int, int);
glfwWindowHint_func glfwWindowHint_impl; glfwWindowHint_func glfwWindowHint_impl;
#define glfwWindowHint glfwWindowHint_impl #define glfwWindowHint glfwWindowHint_impl
typedef void (*glfwWindowHintString_func)(int, const char*);
glfwWindowHintString_func glfwWindowHintString_impl;
#define glfwWindowHintString glfwWindowHintString_impl
typedef GLFWwindow* (*glfwCreateWindow_func)(int, int, const char*, GLFWmonitor*, GLFWwindow*); typedef GLFWwindow* (*glfwCreateWindow_func)(int, int, const char*, GLFWmonitor*, GLFWwindow*);
glfwCreateWindow_func glfwCreateWindow_impl; glfwCreateWindow_func glfwCreateWindow_impl;
#define glfwCreateWindow glfwCreateWindow_impl #define glfwCreateWindow glfwCreateWindow_impl
@@ -1591,6 +1639,10 @@ typedef GLFWframebuffersizefun (*glfwSetFramebufferSizeCallback_func)(GLFWwindow
glfwSetFramebufferSizeCallback_func glfwSetFramebufferSizeCallback_impl; glfwSetFramebufferSizeCallback_func glfwSetFramebufferSizeCallback_impl;
#define glfwSetFramebufferSizeCallback glfwSetFramebufferSizeCallback_impl #define glfwSetFramebufferSizeCallback glfwSetFramebufferSizeCallback_impl
typedef GLFWwindowcontentscalefun (*glfwSetWindowContentScaleCallback_func)(GLFWwindow*, GLFWwindowcontentscalefun);
glfwSetWindowContentScaleCallback_func glfwSetWindowContentScaleCallback_impl;
#define glfwSetWindowContentScaleCallback glfwSetWindowContentScaleCallback_impl
typedef void (*glfwPollEvents_func)(); typedef void (*glfwPollEvents_func)();
glfwPollEvents_func glfwPollEvents_impl; glfwPollEvents_func glfwPollEvents_impl;
#define glfwPollEvents glfwPollEvents_impl #define glfwPollEvents glfwPollEvents_impl
@@ -1711,6 +1763,14 @@ typedef const char* (*glfwGetJoystickGUID_func)(int);
glfwGetJoystickGUID_func glfwGetJoystickGUID_impl; glfwGetJoystickGUID_func glfwGetJoystickGUID_impl;
#define glfwGetJoystickGUID glfwGetJoystickGUID_impl #define glfwGetJoystickGUID glfwGetJoystickGUID_impl
typedef void (*glfwSetJoystickUserPointer_func)(int, void*);
glfwSetJoystickUserPointer_func glfwSetJoystickUserPointer_impl;
#define glfwSetJoystickUserPointer glfwSetJoystickUserPointer_impl
typedef void* (*glfwGetJoystickUserPointer_func)(int);
glfwGetJoystickUserPointer_func glfwGetJoystickUserPointer_impl;
#define glfwGetJoystickUserPointer glfwGetJoystickUserPointer_impl
typedef int (*glfwJoystickIsGamepad_func)(int); typedef int (*glfwJoystickIsGamepad_func)(int);
glfwJoystickIsGamepad_func glfwJoystickIsGamepad_impl; glfwJoystickIsGamepad_func glfwJoystickIsGamepad_impl;
#define glfwJoystickIsGamepad glfwJoystickIsGamepad_impl #define glfwJoystickIsGamepad glfwJoystickIsGamepad_impl

View File

@@ -21,12 +21,14 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
int w, h; int w, h;
glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height); glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height);
glfwGetWindowSize(window->handle, &w, &h); glfwGetWindowSize(window->handle, &w, &h);
double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio;
window->viewport_x_ratio = (double)window->viewport_width / (double)w; window->viewport_x_ratio = (double)window->viewport_width / (double)w;
window->viewport_y_ratio = (double)window->viewport_height / (double)h; window->viewport_y_ratio = (double)window->viewport_height / (double)h;
bool dpi_changed = (xr != 0.0 && xr != window->viewport_x_ratio) || (yr != 0.0 && yr != window->viewport_y_ratio);
window->viewport_size_dirty = true; window->viewport_size_dirty = true;
window->has_pending_resizes = false; window->has_pending_resizes = false;
if (notify_boss) { if (notify_boss) {
call_boss(on_window_resize, "Kii", window->id, window->viewport_width, window->viewport_height); call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
} }
window->last_resize_at = monotonic(); window->last_resize_at = monotonic();
} }
@@ -141,6 +143,32 @@ scroll_callback(GLFWwindow *w, double xoffset, double yoffset) {
global_state.callback_os_window = NULL; global_state.callback_os_window = NULL;
} }
static struct {
id_type entries[16];
int next_entry;
} focus_history;
#ifdef __APPLE__
static inline id_type
pop_focus_history() {
int index = --focus_history.next_entry;
if (index < 0) {
focus_history.next_entry = index = 0;
}
id_type result = focus_history.entries[index];
focus_history.entries[index] = 0;
return result;
}
#endif
static inline void
push_focus_history(OSWindow *w) {
focus_history.entries[focus_history.next_entry++] = w->id;
focus_history.next_entry %= (sizeof(focus_history.entries) / sizeof(*(focus_history.entries)));
}
static void static void
window_focus_callback(GLFWwindow *w, int focused) { window_focus_callback(GLFWwindow *w, int focused) {
if (!set_callback_window(w)) return; if (!set_callback_window(w)) return;
@@ -148,6 +176,7 @@ window_focus_callback(GLFWwindow *w, int focused) {
if (focused) { if (focused) {
show_mouse_cursor(w); show_mouse_cursor(w);
focus_in_event(); focus_in_event();
push_focus_history(global_state.callback_os_window);
} }
double now = monotonic(); double now = monotonic();
global_state.callback_os_window->last_mouse_activity_at = now; global_state.callback_os_window->last_mouse_activity_at = now;
@@ -246,8 +275,8 @@ current_monitor(GLFWwindow *window) {
} }
static inline void void
set_dpi_from_window(OSWindow *w) { set_dpi_from_os_window(OSWindow *w) {
GLFWmonitor *monitor = NULL; GLFWmonitor *monitor = NULL;
if (w) { monitor = current_monitor(w->handle); } if (w) { monitor = current_monitor(w->handle); }
if (monitor == NULL) monitor = glfwGetPrimaryMonitor(); if (monitor == NULL) monitor = glfwGetPrimaryMonitor();
@@ -265,13 +294,14 @@ set_dpi_from_window(OSWindow *w) {
static PyObject* static PyObject*
create_os_window(PyObject UNUSED *self, PyObject *args) { create_os_window(PyObject UNUSED *self, PyObject *args) {
int width, height, visible = 1, swap_interval = 0, x = -1, y = -1; int width, height, swap_interval = 0, x = -1, y = -1;
char *title; char *title, *wm_class_class, *wm_class_name;
PyObject *load_programs = NULL; PyObject *load_programs = NULL;
if (!PyArg_ParseTuple(args, "iis|pOiii", &width, &height, &title, &visible, &load_programs, &swap_interval, &x, &y)) return NULL; if (!PyArg_ParseTuple(args, "iisss|Oiii", &width, &height, &title, &wm_class_name, &wm_class_class, &load_programs, &swap_interval, &x, &y)) return NULL;
bool is_first_window = standard_cursor == NULL; bool is_first_window = standard_cursor == NULL;
if (is_first_window) { if (is_first_window) {
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
@@ -292,11 +322,15 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
} }
} }
#ifndef __APPLE__
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
#endif
if (global_state.num_os_windows >= MAX_CHILDREN) { if (global_state.num_os_windows >= MAX_CHILDREN) {
PyErr_SetString(PyExc_ValueError, "Too many windows"); PyErr_SetString(PyExc_ValueError, "Too many windows");
return NULL; return NULL;
} }
glfwWindowHint(GLFW_VISIBLE, visible ? GLFW_TRUE : GLFW_FALSE);
bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false; bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false;
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent); glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent);
GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL); GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL);
@@ -310,7 +344,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
current_os_window_ctx = glfw_window; current_os_window_ctx = glfw_window;
glfwSwapInterval(swap_interval); // a value of 1 makes mouse selection laggy glfwSwapInterval(swap_interval); // a value of 1 makes mouse selection laggy
if (is_first_window) { if (is_first_window) {
set_dpi_from_window(NULL); set_dpi_from_os_window(NULL);
gl_init(); gl_init();
PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER)); PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER));
if (ret == NULL) return NULL; if (ret == NULL) return NULL;
@@ -364,15 +398,29 @@ static PyObject*
show_window(PyObject UNUSED *self, PyObject *args) { show_window(PyObject UNUSED *self, PyObject *args) {
id_type os_window_id; id_type os_window_id;
int yes = 1; int yes = 1;
bool dpi_changed = false;
if (!PyArg_ParseTuple(args, "K|p", &os_window_id, &yes)) return NULL; if (!PyArg_ParseTuple(args, "K|p", &os_window_id, &yes)) return NULL;
for (size_t i = 0; i < global_state.num_os_windows; i++) { for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *w = global_state.os_windows + i; OSWindow *w = global_state.os_windows + i;
if (w->id == os_window_id) { if (w->id == os_window_id) {
if (yes) glfwShowWindow(w->handle); else glfwHideWindow(w->handle); if (yes) {
bool first_show = !w->shown_once;
glfwShowWindow(w->handle);
w->shown_once = true;
push_focus_history(w);
if (first_show) {
double before_x = global_state.logical_dpi_x, before_y = global_state.logical_dpi_y;
set_dpi_from_os_window(w);
dpi_changed = before_x != global_state.logical_dpi_x || before_y != global_state.logical_dpi_y;
w->has_pending_resizes = true;
global_state.has_pending_resizes = true;
}
} else glfwHideWindow(w->handle);
break; break;
} }
} }
Py_RETURN_NONE; if (dpi_changed) Py_RETURN_TRUE;
Py_RETURN_FALSE;
} }
void void
@@ -382,6 +430,23 @@ destroy_os_window(OSWindow *w) {
if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL; if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL;
} }
w->handle = NULL; w->handle = NULL;
#ifdef __APPLE__
// On macOS when closing a window, any other existing windows belonging to the same application do not
// automatically get focus, so we do it manually.
bool change_focus = true;
while (change_focus) {
id_type new_focus_id = pop_focus_history();
if (new_focus_id == 0) break;
for (size_t i = 0; i < global_state.num_os_windows; i++) {
OSWindow *c = global_state.os_windows + i;
if (c->id != w->id && c->handle && c->shown_once && (c->id == new_focus_id)) {
glfwFocusWindow(c->handle);
change_focus = false;
break;
}
}
}
#endif
} }
// Global functions {{{ // Global functions {{{
@@ -710,8 +775,6 @@ bool
init_glfw(PyObject *m) { init_glfw(PyObject *m) {
if (PyModule_AddFunctions(m, module_methods) != 0) return false; if (PyModule_AddFunctions(m, module_methods) != 0) return false;
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false; #define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
ADDC(GLFW_X11_WM_CLASS_NAME)
ADDC(GLFW_X11_WM_CLASS_CLASS)
ADDC(GLFW_RELEASE); ADDC(GLFW_RELEASE);
ADDC(GLFW_PRESS); ADDC(GLFW_PRESS);
ADDC(GLFW_REPEAT); ADDC(GLFW_REPEAT);

View File

@@ -728,9 +728,14 @@ clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) {
return ref->start_row + (int32_t)ref->effective_num_rows > 0; return ref->start_row + (int32_t)ref->effective_num_rows > 0;
} }
static inline bool
clear_all_filter_func(ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data) {
return true;
}
void void
grman_clear(GraphicsManager *self) { grman_clear(GraphicsManager *self, bool all) {
filter_refs(self, NULL, true, clear_filter_func); filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func);
} }
static inline bool static inline bool

View File

@@ -83,7 +83,7 @@ typedef struct {
} ScrollData; } ScrollData;
GraphicsManager* grman_alloc(); GraphicsManager* grman_alloc();
void grman_clear(GraphicsManager*); void grman_clear(GraphicsManager*, bool);
const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty); const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty);
bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows); bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows);
void grman_scroll_images(GraphicsManager *self, const ScrollData*); void grman_scroll_images(GraphicsManager *self, const ScrollData*);

View File

@@ -2,12 +2,14 @@
#define ALPHA_TYPE #define ALPHA_TYPE
uniform sampler2D image; uniform sampler2D image;
uniform float inactive_text_alpha;
in vec2 texcoord; in vec2 texcoord;
out vec4 color; out vec4 color;
void main() { void main() {
color = texture(image, texcoord); color = texture(image, texcoord);
color.a *= inactive_text_alpha;
#ifdef PREMULT #ifdef PREMULT
color = vec4(color.rgb * color.a, color.a); color = vec4(color.rgb * color.a, color.a);
#endif #endif

View File

@@ -90,6 +90,12 @@ historybuf_mark_line_dirty(HistoryBuf *self, index_type y) {
self->line_attrs[index_of(self, y)] |= TEXT_DIRTY_MASK; self->line_attrs[index_of(self, y)] |= TEXT_DIRTY_MASK;
} }
inline void
historybuf_clear(HistoryBuf *self) {
self->count = 0;
self->start_of_data = 0;
}
static inline index_type static inline index_type
historybuf_push(HistoryBuf *self) { historybuf_push(HistoryBuf *self) {
index_type idx = (self->start_of_data + self->count) % self->ynum; index_type idx = (self->start_of_data + self->count) % self->ynum;

View File

@@ -7,22 +7,20 @@ import fcntl
import mimetypes import mimetypes
import os import os
import re import re
import selectors
import signal import signal
import struct import struct
import subprocess import subprocess
import sys import sys
import termios import termios
import tty
import zlib import zlib
from base64 import standard_b64encode from base64 import standard_b64encode
from collections import namedtuple from collections import namedtuple
from math import ceil, floor from math import ceil, floor
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from time import monotonic
from kitty.constants import appname from kitty.constants import appname
from kitty.cli import parse_args from kitty.cli import parse_args
from kitty.utils import read_with_timeout
try: try:
fsenc = sys.getfilesystemencoding() or 'utf-8' fsenc = sys.getfilesystemencoding() or 'utf-8'
@@ -56,6 +54,13 @@ are in cells (i.e. cursor positions) with the origin |_ (0, 0)| at
the top-left corner of the screen. the top-left corner of the screen.
--scale-up
type=bool-set
When used in combination with |_ --place| it will cause images that
are smaller than the specified area to be scaled up to use as much
of the specified area as possible.
--clear --clear
type=bool-set type=bool-set
Remove all images currently displayed on the screen. Remove all images currently displayed on the screen.
@@ -98,14 +103,18 @@ def options_spec():
Size = namedtuple('Size', 'rows cols width height') Size = namedtuple('Size', 'rows cols width height')
def screen_size(refresh=False): def screen_size():
if refresh or getattr(screen_size, 'ans', None) is None: if screen_size.changed:
s = struct.pack('HHHH', 0, 0, 0, 0) s = struct.pack('HHHH', 0, 0, 0, 0)
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s) x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
screen_size.ans = Size(*struct.unpack('HHHH', x)) screen_size.ans = Size(*struct.unpack('HHHH', x))
screen_size.changed = False
return screen_size.ans return screen_size.ans
screen_size.changed = True
def write_gr_cmd(cmd, payload=None): def write_gr_cmd(cmd, payload=None):
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items()) cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
w = sys.stdout.buffer.write w = sys.stdout.buffer.write
@@ -131,6 +140,17 @@ def fit_image(width, height, pwidth, pheight):
return int(width), int(height) return int(width), int(height)
def calculate_in_cell_x_offset(width, cell_width, align):
if align == 'left':
return 0
extra_pixels = width % cell_width
if not extra_pixels:
return 0
if align == 'right':
return cell_width - extra_pixels
return (cell_width - extra_pixels) // 2
def set_cursor(cmd, width, height, align): def set_cursor(cmd, width, height, align):
ss = screen_size() ss = screen_size()
cw = int(ss.width / ss.cols) cw = int(ss.width / ss.cols)
@@ -139,12 +159,9 @@ def set_cursor(cmd, width, height, align):
w, h = fit_image(width, height, ss.width, height) w, h = fit_image(width, height, ss.width, height)
ch = int(ss.height / ss.rows) ch = int(ss.height / ss.rows)
num_of_rows_needed = int(ceil(height / ch)) num_of_rows_needed = int(ceil(height / ch))
y_off = height % ch
cmd['c'], cmd['r'] = ss.cols, num_of_rows_needed cmd['c'], cmd['r'] = ss.cols, num_of_rows_needed
cmd['Y'] = y_off
else: else:
x_off = width % cw cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
cmd['X'] = x_off
extra_cells = 0 extra_cells = 0
if align == 'center': if align == 'center':
extra_cells = (ss.cols - num_of_cells_needed) // 2 extra_cells = (ss.cols - num_of_cells_needed) // 2
@@ -159,8 +176,7 @@ def set_cursor_for_place(place, cmd, width, height, align):
ss = screen_size() ss = screen_size()
cw = int(ss.width / ss.cols) cw = int(ss.width / ss.cols)
num_of_cells_needed = int(ceil(width / cw)) num_of_cells_needed = int(ceil(width / cw))
x_off = width % cw cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
cmd['X'] = x_off
extra_cells = 0 extra_cells = 0
if align == 'center': if align == 'center':
extra_cells = (place.width - num_of_cells_needed) // 2 extra_cells = (place.width - num_of_cells_needed) // 2
@@ -221,14 +237,29 @@ def identify(path):
return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode) return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode)
def convert(path, m, available_width, available_height): def convert(path, m, available_width, available_height, scale_up):
width, height = m.width, m.height width, height = m.width, m.height
cmd = ['convert', '-background', 'none', path] cmd = ['convert', '-background', 'none', path]
if scale_up:
if width < available_width:
r = available_width / width
width, height = available_width, int(height * r)
if width > available_width or height > available_height: if width > available_width or height > available_height:
width, height = fit_image(width, height, available_width, available_height) width, height = fit_image(width, height, available_width, available_height)
cmd += ['-resize', '{}x{}'.format(width, height)] cmd += ['-resize', '{}x{}'.format(width, height)]
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False) as outfile: with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False) as outfile:
run_imagemagick(path, cmd + [outfile.name]) run_imagemagick(path, cmd + [outfile.name])
# ImageMagick sometimes generated rgba images smaller than the specified
# size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
sz = os.path.getsize(outfile.name)
bytes_per_pixel = 3 if m.mode == 'rgb' else 4
expected_size = bytes_per_pixel * width * height
if sz < expected_size:
missing = expected_size - sz
if missing % (bytes_per_pixel * width) != 0:
raise SystemExit('ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
height -= missing // (bytes_per_pixel * width)
return outfile.name, width, height return outfile.name, width, height
@@ -238,6 +269,7 @@ def process(path, args):
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height
needs_scaling = m.width > available_width or m.height > available_height needs_scaling = m.width > available_width or m.height > available_height
needs_scaling = needs_scaling or args.scale_up
if m.fmt == 'png' and not needs_scaling: if m.fmt == 'png' and not needs_scaling:
outfile = path outfile = path
transmit_mode = 'f' transmit_mode = 'f'
@@ -246,7 +278,7 @@ def process(path, args):
else: else:
fmt = 24 if m.mode == 'rgb' else 32 fmt = 24 if m.mode == 'rgb' else 32
transmit_mode = 't' transmit_mode = 't'
outfile, width, height = convert(path, m, available_width, available_height) outfile, width, height = convert(path, m, available_width, available_height, args.scale_up)
show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place) show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place)
if not args.place: if not args.place:
print() # ensure cursor is on a new line print() # ensure cursor is on a new line
@@ -261,17 +293,11 @@ def scan(d):
def detect_support(wait_for=10, silent=False): def detect_support(wait_for=10, silent=False):
fd = sys.stdin.fileno()
old = termios.tcgetattr(fd)
oldfl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
if not silent: if not silent:
print('Checking for graphics ({}s max. wait)...'.format(wait_for), end='\r') print('Checking for graphics ({}s max. wait)...'.format(wait_for), end='\r')
sys.stdout.flush() sys.stdout.flush()
tty.setraw(fd)
try: try:
received = b'' received = b''
start_time = monotonic()
responses = {} responses = {}
def parse_responses(): def parse_responses():
@@ -282,29 +308,20 @@ def detect_support(wait_for=10, silent=False):
if iid not in responses: if iid not in responses:
responses[iid] = m.group(2) == b'OK' responses[iid] = m.group(2) == b'OK'
def read(): def more_needed(data):
nonlocal received nonlocal received
d = sys.stdin.buffer.read() received += data
if not d: # EOF
responses[1] = responses[2] = False
return
received += d
parse_responses() parse_responses()
return 1 not in responses or 2 not in responses
with NamedTemporaryFile() as f: with NamedTemporaryFile() as f:
f.write(b'abcd'), f.flush() f.write(b'abcd'), f.flush()
write_gr_cmd(dict(a='q', s=1, v=1, i=1), standard_b64encode(b'abcd')) write_gr_cmd(dict(a='q', s=1, v=1, i=1), standard_b64encode(b'abcd'))
write_gr_cmd(dict(a='q', s=1, v=1, i=2, t='f'), standard_b64encode(f.name.encode(fsenc))) write_gr_cmd(dict(a='q', s=1, v=1, i=2, t='f'), standard_b64encode(f.name.encode(fsenc)))
sel = selectors.DefaultSelector() read_with_timeout(more_needed, timeout=wait_for)
sel.register(sys.stdin, selectors.EVENT_READ, read)
while monotonic() - start_time < wait_for and 1 not in responses and 2 not in responses:
for key, mask in sel.select(0.1):
read()
finally: finally:
if not silent: if not silent:
sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush() sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush()
termios.tcsetattr(fd, termios.TCSADRAIN, old)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl)
detect_support.has_files = bool(responses.get(2)) detect_support.has_files = bool(responses.get(2))
return responses.get(1, False) return responses.get(1, False)
@@ -324,7 +341,7 @@ def main(args=sys.argv):
' Directories are scanned recursively for image files.') ' Directories are scanned recursively for image files.')
args, items = parse_args(args[1:], options_spec, 'image-file ...', msg, '{} icat'.format(appname)) args, items = parse_args(args[1:], options_spec, 'image-file ...', msg, '{} icat'.format(appname))
signal.signal(signal.SIGWINCH, lambda: screen_size(refresh=True)) signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
if not sys.stdout.isatty() or not sys.stdin.isatty(): if not sys.stdout.isatty() or not sys.stdin.isatty():
raise SystemExit( raise SystemExit(
'Must be run in a terminal, stdout and/or stdin is currently not a terminal' 'Must be run in a terminal, stdout and/or stdin is currently not a terminal'

View File

@@ -176,7 +176,9 @@ on_key_input(int key, int scancode, int action, int mods) {
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
} }
#ifdef __APPLE__ #ifdef __APPLE__
if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods)) return; if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods) && !(
key == GLFW_KEY_UP || key == GLFW_KEY_DOWN || key == GLFW_KEY_LEFT || key == GLFW_KEY_RIGHT) // alt+arrow keys do not generate text on macOS
) return;
#endif #endif
if ( if (
action == GLFW_PRESS || action == GLFW_PRESS ||
@@ -188,12 +190,14 @@ on_key_input(int key, int scancode, int action, int mods) {
} }
void void
fake_scroll(bool upwards) { fake_scroll(int amount, bool upwards) {
Window *w = active_window(); Window *w = active_window();
if (!w) return; if (!w) return;
Screen *screen = w->render_data.screen; int key = upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN;
send_key_to_child(w, upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN, 0, GLFW_PRESS); while (amount-- > 0) {
if (screen->modes.mEXTENDED_KEYBOARD) send_key_to_child(w, upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN, 0, GLFW_RELEASE); send_key_to_child(w, key, 0, GLFW_PRESS);
send_key_to_child(w, key, 0, GLFW_RELEASE);
}
} }
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)

48
kitty/keys.h generated
View File

@@ -760,6 +760,14 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key) } // end switch(key)
case 0x3: case 0x3:
switch(key & 0x7f) { default: return NULL; switch(key & 0x7f) { default: return NULL;
case 3: // MINUS
return "\x01\x1f";
case 5: // SLASH
return "\x01\x7f";
case 8: // 2
return "\x01\x00";
case 12: // 6
return "\x01\x1e";
case 50: // ESCAPE case 50: // ESCAPE
return "\x01\x1b"; return "\x01\x1b";
case 51: // ENTER case 51: // ENTER
@@ -920,7 +928,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x02\x1b\x09"; return "\x02\x1b\x09";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x33\x7e"; return "\x06\x1b\x5b\x32\x3b\x33\x7e";
case 55: // DELETE case 55: // DELETE
@@ -1071,7 +1079,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x03\x1b\x5b\x5a"; return "\x03\x1b\x5b\x5a";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x34\x7e"; return "\x06\x1b\x5b\x32\x3b\x34\x7e";
case 55: // DELETE case 55: // DELETE
@@ -1958,6 +1966,14 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key) } // end switch(key)
case 0x3: case 0x3:
switch(key & 0x7f) { default: return NULL; switch(key & 0x7f) { default: return NULL;
case 3: // MINUS
return "\x01\x1f";
case 5: // SLASH
return "\x01\x7f";
case 8: // 2
return "\x01\x00";
case 12: // 6
return "\x01\x1e";
case 50: // ESCAPE case 50: // ESCAPE
return "\x01\x1b"; return "\x01\x1b";
case 51: // ENTER case 51: // ENTER
@@ -2118,7 +2134,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x02\x1b\x09"; return "\x02\x1b\x09";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x33\x7e"; return "\x06\x1b\x5b\x32\x3b\x33\x7e";
case 55: // DELETE case 55: // DELETE
@@ -2269,7 +2285,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x03\x1b\x5b\x5a"; return "\x03\x1b\x5b\x5a";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x34\x7e"; return "\x06\x1b\x5b\x32\x3b\x34\x7e";
case 55: // DELETE case 55: // DELETE
@@ -3164,6 +3180,14 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key) } // end switch(key)
case 0x3: case 0x3:
switch(key & 0x7f) { default: return NULL; switch(key & 0x7f) { default: return NULL;
case 3: // MINUS
return "\x01\x1f";
case 5: // SLASH
return "\x01\x7f";
case 8: // 2
return "\x01\x00";
case 12: // 6
return "\x01\x1e";
case 50: // ESCAPE case 50: // ESCAPE
return "\x01\x1b"; return "\x01\x1b";
case 51: // ENTER case 51: // ENTER
@@ -3324,7 +3348,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x02\x1b\x09"; return "\x02\x1b\x09";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x33\x7e"; return "\x06\x1b\x5b\x32\x3b\x33\x7e";
case 55: // DELETE case 55: // DELETE
@@ -3475,7 +3499,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x03\x1b\x5b\x5a"; return "\x03\x1b\x5b\x5a";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x34\x7e"; return "\x06\x1b\x5b\x32\x3b\x34\x7e";
case 55: // DELETE case 55: // DELETE
@@ -4362,6 +4386,14 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
} // end switch(key) } // end switch(key)
case 0x3: case 0x3:
switch(key & 0x7f) { default: return NULL; switch(key & 0x7f) { default: return NULL;
case 3: // MINUS
return "\x01\x1f";
case 5: // SLASH
return "\x01\x7f";
case 8: // 2
return "\x01\x00";
case 12: // 6
return "\x01\x1e";
case 50: // ESCAPE case 50: // ESCAPE
return "\x01\x1b"; return "\x01\x1b";
case 51: // ENTER case 51: // ENTER
@@ -4522,7 +4554,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x02\x1b\x09"; return "\x02\x1b\x09";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x33\x7e"; return "\x06\x1b\x5b\x32\x3b\x33\x7e";
case 55: // DELETE case 55: // DELETE
@@ -4673,7 +4705,7 @@ key_lookup(uint8_t key, KeyboardMode mode, uint8_t mods, uint8_t action) {
case 52: // TAB case 52: // TAB
return "\x03\x1b\x5b\x5a"; return "\x03\x1b\x5b\x5a";
case 53: // BACKSPACE case 53: // BACKSPACE
return "\x02\x1b\x7f"; return "\x01\x17";
case 54: // INSERT case 54: // INSERT
return "\x06\x1b\x5b\x32\x3b\x34\x7e"; return "\x06\x1b\x5b\x32\x3b\x34\x7e";
case 55: // DELETE case 55: // DELETE

View File

@@ -26,10 +26,17 @@ def modify_complex_key(name, amt):
control_codes = {} control_codes = {}
smkx_key_map = {} smkx_key_map = {}
alt_codes = {defines.GLFW_KEY_TAB: b'\033\t', defines.GLFW_KEY_ENTER: b'\033\r', defines.GLFW_KEY_ESCAPE: b'\033\033', defines.GLFW_KEY_BACKSPACE: b'\033\177'} alt_codes = {
defines.GLFW_KEY_TAB: b'\033\t',
defines.GLFW_KEY_ENTER: b'\033\r',
defines.GLFW_KEY_ESCAPE: b'\033\033',
# alt+bs matches iTerm and gnome-terminal
defines.GLFW_KEY_BACKSPACE: b'\x17'
}
shift_alt_codes = alt_codes.copy() shift_alt_codes = alt_codes.copy()
shift_alt_codes[defines.GLFW_KEY_TAB] = key_as_bytes('kcbt') 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) alt_mods = (defines.GLFW_MOD_ALT, defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_ALT)
ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL
for kf, kn in { for kf, kn in {
defines.GLFW_KEY_UP: 'kcuu1', defines.GLFW_KEY_UP: 'kcuu1',
@@ -183,6 +190,18 @@ SHIFTED_PRINTABLE.update(pmap(
"{|}~" "{|}~"
)) ))
ASCII_C0_SHIFTED = {
# ^@
'2': b'\x00',
# ^^
'6': b'\x1e',
# ^_
'MINUS': b'\x1f',
# ^?
'SLASH': b'\x7f',
}
CTRL_SHIFT_KEYS = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
def key_to_bytes(key, smkx, extended, mods, action): def key_to_bytes(key, smkx, extended, mods, action):
if extended: if extended:
@@ -191,6 +210,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
if mods == defines.GLFW_MOD_CONTROL and key in control_codes: if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
# Map Ctrl-key to ascii control code # Map Ctrl-key to ascii control code
data.extend(control_codes[key]) data.extend(control_codes[key])
elif mods == ctrl_shift_mod and key in CTRL_SHIFT_KEYS:
data.extend(CTRL_SHIFT_KEYS[key])
elif mods in alt_mods: elif mods in alt_mods:
if key in alt_codes: if key in alt_codes:
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key]) data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
@@ -275,13 +296,13 @@ def generate_key_table():
i += 1 i += 1
ind('case {}:'.format(mode)) ind('case {}:'.format(mode))
i += 1 i += 1
ind('switch(action & 3) { case 3: return NULL; ') ind('switch(action & 3) { case 3: return NULL;')
for action in (defines.GLFW_RELEASE, defines.GLFW_PRESS, defines.GLFW_REPEAT): for action in (defines.GLFW_RELEASE, defines.GLFW_PRESS, defines.GLFW_REPEAT):
i += 1 i += 1
ind('case {}: // {}'.format(action, 'RELEASE PRESS REPEAT'.split()[action])) ind('case {}: // {}'.format(action, 'RELEASE PRESS REPEAT'.split()[action]))
i += 1 i += 1
if action != defines.GLFW_RELEASE or mode == 'EXTENDED': if action != defines.GLFW_RELEASE or mode == 'EXTENDED':
ind('switch (mods & 0xf) { ') ind('switch (mods & 0xf) {')
i += 1 i += 1
for mods in range(16): for mods in range(16):
key_bytes = {} key_bytes = {}

View File

@@ -104,6 +104,10 @@ click_interval 0.5
# database will be matched. # database will be matched.
select_by_word_characters :@-./_~?&=%+# select_by_word_characters :@-./_~?&=%+#
# The shell program to execute. The default value of . means
# to use whatever shell is set as the default shell for the current user.
shell .
# 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 to disable mouse cursor hiding. # zero to disable mouse cursor hiding.
mouse_hide_wait 3.0 mouse_hide_wait 3.0
@@ -149,6 +153,17 @@ open_url_modifiers ctrl+shift
# use the operating system's default URL handler. # use the operating system's default URL handler.
open_url_with default open_url_with default
# The modifiers to use rectangular selection (i.e. to select text in a
# rectangular block with the mouse)
rectangle_select_modifiers ctrl+alt
# Allow other programs to control kitty. If you turn this on other programs can
# control all aspects of kitty, including sending text to kitty windows,
# opening new windows, closing windows, reading the content of windows, etc.
# Note that this even works over ssh connections.
allow_remote_control no
# Choose whether to use the system implementation of wcwidth() (used to # Choose whether to use the system implementation of wcwidth() (used to
# control how many cells a character is rendered in). If you use the system # control how many cells a character is rendered in). If you use the system
# implementation, then kitty and any programs running in it will agree. The # implementation, then kitty and any programs running in it will agree. The
@@ -177,14 +192,23 @@ active_border_color #00ff00
# The color for the border of inactive windows # The color for the border of inactive windows
inactive_border_color #cccccc inactive_border_color #cccccc
# Tab-bar customization # Fade the text in inactive windows by the specified amount (a number between
# zero and one, with 0 being fully faded).
inactive_text_alpha 1.0
# Which edge to show the tab bar on, top or bottom
tab_bar_edge bottom
# The separator between tabs in the tab bar
tab_separator " ┇"
# Tab bar colors and styles
active_tab_foreground #000 active_tab_foreground #000
active_tab_background #eee active_tab_background #eee
active_tab_font_style bold-italic active_tab_font_style bold-italic
inactive_tab_foreground #444 inactive_tab_foreground #444
inactive_tab_background #999 inactive_tab_background #999
inactive_tab_font_style normal inactive_tab_font_style normal
tab_separator " ┇"
# The 16 terminal colors. There are 8 basic colors, each color has a dull and # The 16 terminal colors. There are 8 basic colors, each color has a dull and
@@ -278,14 +302,20 @@ map ctrl+shift+7 seventh_window
map ctrl+shift+8 eighth_window map ctrl+shift+8 eighth_window
map ctrl+shift+9 ninth_window map ctrl+shift+9 ninth_window
map ctrl+shift+0 tenth_window map ctrl+shift+0 tenth_window
# You can also open a new window running an arbitrary program, for example: # You can open a new window running an arbitrary program, for example:
# map ctrl+shift+y new_window mutt # map ctrl+shift+y new_window mutt
# You can also pass the current selection to the new program by using the @selection placeholder #
# You can pass the current selection to the new program by using the @selection placeholder
# map ctrl+shift+y new_window less @selection # map ctrl+shift+y new_window less @selection
# Finally, you can even send the contents of the current screen + history buffer as stdin using #
# You can even send the contents of the current screen + history buffer as stdin using
# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes) # the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes)
# For example, the following command opens the scrollback buffer in less in a new window. # For example, the following command opens the scrollback buffer in less in a new window.
# map ctrl+shift+y new_window @ansi less +G -R # map ctrl+shift+y new_window @ansi less +G -R
#
# You can open a new window with the current working directory set to the
# working directory of the current window using
# map ctrl+alt+enter new_window_with_cwd
# Tab management # Tab management
@@ -301,7 +331,7 @@ map ctrl+shift+, move_tab_backward
# map ctrl+alt+2 goto_tab 2 # map ctrl+alt+2 goto_tab 2
# Just as with new_window above, you can also pass the name of arbitrary # Just as with new_window above, you can also pass the name of arbitrary
# commands to run when using new_tab. # commands to run when using new_tab and use new_tab_with_cwd.
# Miscellaneous # Miscellaneous
@@ -344,6 +374,13 @@ map ctrl+shift+f11 toggle_fullscreen
# OS specific tweaks # OS specific tweaks
# Copy to clipboard on select. With this enabled, simply selecting text with
# the mouse will cause the text to be copied to clipboard. Useful on platforms
# such as macOS/Wayland that do not have the concept of primary selections. Note
# that this is a security risk, as all programs, including websites open in your
# browser can read the contents of the clipboard.
copy_on_select no
# Hide the kitty window's title bar on macOS. # Hide the kitty window's title bar on macOS.
macos_hide_titlebar no macos_hide_titlebar no

View File

@@ -6,13 +6,13 @@ from collections import namedtuple
from itertools import islice from itertools import islice
from .constants import WindowGeometry from .constants import WindowGeometry
from .fast_data_types import pt_to_px, viewport_for_window from .fast_data_types import pt_to_px, viewport_for_window, Region
viewport_width = viewport_height = available_height = 400 central = Region((0, 0, 199, 199, 200, 200))
cell_width = cell_height = 20 cell_width = cell_height = 20
def layout_dimension(length, cell_length, number_of_windows=1, border_length=0, margin_length=0, padding_length=0, left_align=False): def layout_dimension(start_at, 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 border_length += padding_length
space_needed_for_border = number_of_windows * 2 * border_length space_needed_for_border = number_of_windows * 2 * border_length
@@ -24,7 +24,9 @@ def layout_dimension(length, cell_length, number_of_windows=1, border_length=0,
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 extra -= space_needed
pos = 0 if left_align else (extra // 2) pos = start_at
if not left_align:
pos += extra // 2
pos += border_length + margin_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 + margin_length) + inner_length window_length = 2 * (border_length + margin_length) + inner_length
@@ -73,30 +75,30 @@ class Layout:
def xlayout(self, num): def xlayout(self, num):
return layout_dimension( return layout_dimension(
viewport_width, cell_width, num, self.border_width, central.left, central.width, cell_width, num, self.border_width,
margin_length=self.margin_width, padding_length=self.padding_width) margin_length=self.margin_width, padding_length=self.padding_width)
def ylayout(self, num, left_align=True): def ylayout(self, num, left_align=True):
return layout_dimension( return layout_dimension(
available_height, cell_height, num, self.border_width, left_align=left_align, central.top, central.height, cell_height, num, self.border_width, left_align=left_align,
margin_length=self.margin_width, padding_length=self.padding_width) margin_length=self.margin_width, padding_length=self.padding_width)
def simple_blank_rects(self, first_window, last_window): def simple_blank_rects(self, first_window, last_window):
br, vh = self.blank_rects, available_height br = self.blank_rects
left_blank_rect(first_window, br, vh), top_blank_rect(first_window, br, vh), right_blank_rect(last_window, br, vh) left_blank_rect(first_window, br), top_blank_rect(first_window, br), right_blank_rect(last_window, br)
def between_blank_rect(self, left_window, right_window): def between_blank_rect(self, left_window, right_window):
self.blank_rects.append(Rect(left_window.geometry.right, 0, right_window.geometry.left, available_height)) self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
def bottom_blank_rect(self, window): def bottom_blank_rect(self, window):
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, available_height)) self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
def set_active_window(self, windows, active_window_idx): def set_active_window(self, windows, active_window_idx):
pass pass
def __call__(self, windows, active_window_idx): def __call__(self, windows, active_window_idx):
global viewport_width, viewport_height, cell_width, cell_height, available_height global central, cell_width, cell_height
viewport_width, viewport_height, available_height, cell_width, cell_height = viewport_for_window(self.os_window_id) central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
self.do_layout(windows, active_window_idx) self.do_layout(windows, active_window_idx)
def do_layout(self, windows, active_window_idx): def do_layout(self, windows, active_window_idx):
@@ -108,35 +110,38 @@ def window_geometry(xstart, xnum, ystart, ynum):
def layout_single_window(margin_length, padding_length): def layout_single_window(margin_length, padding_length):
xstart, xnum = next(layout_dimension(viewport_width, cell_width, margin_length=margin_length, padding_length=padding_length)) xstart, xnum = next(layout_dimension(central.left, central.width, cell_width, margin_length=margin_length, padding_length=padding_length))
ystart, ynum = next(layout_dimension(available_height, cell_height, margin_length=margin_length, padding_length=padding_length)) ystart, ynum = next(layout_dimension(central.top, central.height, cell_height, margin_length=margin_length, padding_length=padding_length))
return window_geometry(xstart, xnum, ystart, ynum) return window_geometry(xstart, xnum, ystart, ynum)
def left_blank_rect(w, rects, vh): def left_blank_rect(w, rects):
if w.geometry.left > 0: lt = w.geometry.left
rects.append(Rect(0, 0, w.geometry.left, vh)) if lt > central.left:
rects.append(Rect(central.left, central.top, lt, central.bottom + 1))
def right_blank_rect(w, rects, vh): def right_blank_rect(w, rects):
if w.geometry.right < viewport_width: r = w.geometry.right
rects.append(Rect(w.geometry.right, 0, viewport_width, vh)) if r < central.right:
rects.append(Rect(r, central.top, central.right + 1, central.bottom + 1))
def top_blank_rect(w, rects, vh): def top_blank_rect(w, rects):
if w.geometry.top > 0: t = w.geometry.top
rects.append(Rect(0, 0, viewport_width, w.geometry.top)) if t > central.top:
rects.append(Rect(central.left, central.top, central.right + 1, t))
def bottom_blank_rect(w, rects, vh): def bottom_blank_rect(w, rects):
if w.geometry.bottom < available_height: b = w.geometry.bottom
rects.append(Rect(0, w.geometry.bottom, viewport_width, vh)) if b < central.bottom:
rects.append(Rect(central.left, b, central.right + 1, central.bottom + 1))
def blank_rects_for_window(w): def blank_rects_for_window(w):
ans = [] ans = []
vh = available_height left_blank_rect(w, ans), top_blank_rect(w, ans), right_blank_rect(w, ans), bottom_blank_rect(w, ans)
left_blank_rect(w, ans, vh), top_blank_rect(w, ans, vh), right_blank_rect(w, ans, vh), bottom_blank_rect(w, ans, vh)
return ans return ans
@@ -237,4 +242,61 @@ class Grid(Tall):
self.between_blank_rect(win_col_map[i][0], win_col_map[i + 1][0]) self.between_blank_rect(win_col_map[i][0], win_col_map[i + 1][0])
class Vertical(Layout):
name = 'vertical'
def do_layout(self, windows, active_window_idx):
self.blank_rects = []
window_count = len(windows)
if window_count == 1:
wg = layout_single_window(self.margin_width, self.padding_width)
windows[0].set_geometry(0, wg)
self.blank_rects = blank_rects_for_window(windows[0])
return
xlayout = self.xlayout(1)
xstart, xnum = next(xlayout)
ylayout = self.ylayout(window_count)
for i in range(window_count):
ystart, ynum = next(ylayout)
windows[i].set_geometry(i, window_geometry(xstart, xnum, ystart, ynum))
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
# bottom blank rect
self.bottom_blank_rect(windows[-1])
class Horizontal(Layout):
name = 'horizontal'
def do_layout(self, windows, active_window_idx):
self.blank_rects = []
window_count = len(windows)
if window_count == 1:
wg = layout_single_window(self.margin_width, self.padding_width)
windows[0].set_geometry(0, wg)
self.blank_rects = blank_rects_for_window(windows[0])
return
xlayout = self.xlayout(window_count)
ylayout = self.ylayout(1)
ystart, ynum = next(ylayout)
for i in range(window_count):
xstart, xnum = next(xlayout)
windows[i].set_geometry(i, window_geometry(xstart, xnum, ystart, ynum))
if i > 0:
# between blank rect
self.between_blank_rect(windows[i - 1], windows[i])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
# bottom blank rect
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
all_layouts = {o.name: o for o in globals().values() if isinstance(o, type) and issubclass(o, Layout) and o is not Layout} all_layouts = {o.name: o for o in globals().values() if isinstance(o, type) and issubclass(o, Layout) and o is not Layout}

View File

@@ -383,7 +383,15 @@ as_ansi(LineBuf *self, PyObject *callback) {
#define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line." #define as_ansi_doc "as_ansi(callback) -> The contents of this buffer as ANSI escaped text. callback is called with each successive line."
static Py_UCS4 t[5120]; static Py_UCS4 t[5120];
Line l = {.xnum=self->xnum}; Line l = {.xnum=self->xnum};
for(index_type i = 0; i < self->ynum; i++) { // remove trailing empty lines
index_type ylimit = self->ynum - 1;
do {
init_line(self, (&l), self->line_map[ylimit]);
if (line_as_ansi(&l, t, 5120) != 0) break;
ylimit--;
} while(ylimit > 0);
for(index_type i = 0; i <= ylimit; i++) {
l.continued = ((i < self->ynum - 1) ? self->line_attrs[i+1] : self->line_attrs[i]) & CONTINUED_MASK; l.continued = ((i < self->ynum - 1) ? self->line_attrs[i+1] : self->line_attrs[i]) & CONTINUED_MASK;
init_line(self, (&l), self->line_map[i]); init_line(self, (&l), self->line_map[i]);
index_type num = line_as_ansi(&l, t, 5120); index_type num = line_as_ansi(&l, t, 5120);

View File

@@ -35,17 +35,13 @@ line_length(Line *self) {
} }
PyObject* PyObject*
line_text_at(char_type ch, combining_type cc) { cell_text(Cell *cell) {
PyObject *ans; PyObject *ans;
if (LIKELY(cc == 0)) { unsigned num = 1;
ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, &ch, 1); static Py_UCS4 buf[arraysz(cell->cc_idx) + 1];
} else { buf[0] = cell->ch;
Py_UCS4 buf[3]; for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) buf[num++] = codepoint_for_mark(cell->cc_idx[i]);
buf[0] = ch; buf[1] = cc & CC_MASK; buf[2] = cc >> 16; ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
Py_UCS4 normalized = normalize(ch, buf[1], buf[2]);
if (normalized) ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, &normalized, 1);
else ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, buf[2] ? 3 : 2);
}
return ans; return ans;
} }
@@ -139,7 +135,7 @@ line_url_end_at(Line *self, index_type x, bool check_short) {
index_type ans = x; index_type ans = x;
if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0; if (x >= self->xnum || (check_short && self->xnum <= MIN_URL_LEN + 3)) return 0;
while (ans < self->xnum && is_url_char(self->cells[ans].ch)) ans++; while (ans < self->xnum && is_url_char(self->cells[ans].ch)) ans++;
ans--; if (ans) ans--;
while (ans > x && can_strip_from_end_of_url(self->cells[ans].ch)) ans--; while (ans > x && can_strip_from_end_of_url(self->cells[ans].ch)) ans--;
return ans; return ans;
} }
@@ -162,7 +158,7 @@ static PyObject*
text_at(Line* self, Py_ssize_t xval) { text_at(Line* self, Py_ssize_t xval) {
#define text_at_doc "[x] -> Return the text in the specified cell" #define text_at_doc "[x] -> Return the text in the specified cell"
if ((unsigned)xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; } if ((unsigned)xval >= self->xnum) { PyErr_SetString(PyExc_IndexError, "Column number out of bounds"); return NULL; }
return line_text_at(self->cells[xval].ch, self->cells[xval].cc); return cell_text(self->cells + xval);
} }
size_t size_t
@@ -170,13 +166,7 @@ cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char)
size_t n = 1; size_t n = 1;
buf[0] = cell->ch ? cell->ch : zero_char; buf[0] = cell->ch ? cell->ch : zero_char;
if (include_cc) { if (include_cc) {
char_type cc = cell->cc; for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) buf[n++] = codepoint_for_mark(cell->cc_idx[i]);
Py_UCS4 cc1 = cc & CC_MASK, cc2;
if (cc1) {
buf[1] = cc1; n++;
cc2 = cc >> 16;
if (cc2) { buf[2] = cc2; n++; }
}
} }
return n; return n;
} }
@@ -185,13 +175,7 @@ size_t
cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type zero_char) { cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type zero_char) {
size_t n = encode_utf8(cell->ch ? cell->ch : zero_char, buf); size_t n = encode_utf8(cell->ch ? cell->ch : zero_char, buf);
if (include_cc) { if (include_cc) {
char_type cc = cell->cc; for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) n += encode_utf8(codepoint_for_mark(cell->cc_idx[i]), buf + n);
Py_UCS4 cc1 = cc & CC_MASK, cc2;
if (cc1) {
n += encode_utf8(cc1, buf + n);
cc2 = cc >> 16;
if (cc2) { n += encode_utf8(cc2, buf + n); }
}
} }
buf[n] = 0; buf[n] = 0;
return n; return n;
@@ -204,7 +188,7 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc
static Py_UCS4 buf[4096]; static Py_UCS4 buf[4096];
if (leading_char) buf[n++] = leading_char; if (leading_char) buf[n++] = leading_char;
char_type previous_width = 0; char_type previous_width = 0;
for(index_type i = start; i < limit && n < sizeof(buf)/sizeof(buf[0]) - 4; i++) { for(index_type i = start; i < limit && n < arraysz(buf) - 2 - arraysz(self->cells->cc_idx); i++) {
char_type ch = self->cells[i].ch; char_type ch = self->cells[i].ch;
if (ch == 0) { if (ch == 0) {
if (previous_width == 2) { previous_width = 0; continue; }; if (previous_width == 2) { previous_width = 0; continue; };
@@ -244,6 +228,7 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
#define WRITE_CH(val) if (i > buflen - 1) return i; buf[i++] = val; #define WRITE_CH(val) if (i > buflen - 1) return i; buf[i++] = val;
index_type limit = xlimit_for_line(self), i=0; index_type limit = xlimit_for_line(self), i=0;
if (limit == 0) return 0;
char_type previous_width = 0; char_type previous_width = 0;
WRITE_SGR("0"); WRITE_SGR("0");
@@ -264,12 +249,8 @@ line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
t = prev_cursor; prev_cursor = cursor; cursor = t; t = prev_cursor; prev_cursor = cursor; cursor = t;
if (*sgr) WRITE_SGR(sgr); if (*sgr) WRITE_SGR(sgr);
WRITE_CH(ch); WRITE_CH(ch);
char_type cc = self->cells[pos].cc; for(unsigned c = 0; c < arraysz(self->cells[pos].cc_idx) && self->cells[pos].cc_idx[c]; c++) {
Py_UCS4 cc1 = cc & CC_MASK; WRITE_CH(codepoint_for_mark(self->cells[pos].cc_idx[c]));
if (cc1) {
WRITE_CH(cc1);
cc1 = cc >> 16;
if (cc1) { WRITE_CH(cc1); }
} }
previous_width = attrs & WIDTH_MASK; previous_width = attrs & WIDTH_MASK;
} }
@@ -318,10 +299,12 @@ width(Line *self, PyObject *val) {
void void
line_add_combining_char(Line *self, uint32_t ch, unsigned int x) { line_add_combining_char(Line *self, uint32_t ch, unsigned int x) {
if (!self->cells[x].ch) return; // dont allow adding combining chars to a null cell Cell *cell = self->cells + x;
combining_type c = self->cells[x].cc; if (!cell->ch) return; // dont allow adding combining chars to a null cell
if (c & CC_MASK) self->cells[x].cc = (c & CC_MASK) | ( (ch & CC_MASK) << CC_SHIFT ); for (unsigned i = 0; i < arraysz(cell->cc_idx); i++) {
else self->cells[x].cc = ch & CC_MASK; if (!cell->cc_idx[i]) { cell->cc_idx[i] = mark_for_codepoint(ch); return; }
}
cell->cc_idx[arraysz(cell->cc_idx) - 1] = mark_for_codepoint(ch);
} }
static PyObject* static PyObject*
@@ -371,7 +354,7 @@ set_text(Line* self, PyObject *args) {
self->cells[i].fg = fg; self->cells[i].fg = fg;
self->cells[i].bg = bg; self->cells[i].bg = bg;
self->cells[i].decoration_fg = dfg; self->cells[i].decoration_fg = dfg;
self->cells[i].cc = 0; memset(self->cells[i].cc_idx, 0, sizeof(self->cells[i].cc_idx));
} }
Py_RETURN_NONE; Py_RETURN_NONE;
@@ -403,7 +386,7 @@ line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch) {
attrs_type width = ch ? 1 : 0; attrs_type width = ch ? 1 : 0;
#define PREFIX \ #define PREFIX \
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->cells[i].ch = ch; self->cells[i].cc = 0; \ self->cells[i].ch = ch; memset(self->cells[i].cc_idx, 0, sizeof(self->cells[i].cc_idx)); \
self->cells[i].attrs = (self->cells[i].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width; \ self->cells[i].attrs = (self->cells[i].attrs & ATTRS_MASK_WITHOUT_WIDTH) | width; \
} }
if (CHAR_IS_BLANK(ch)) { if (CHAR_IS_BLANK(ch)) {
@@ -433,7 +416,7 @@ line_apply_cursor(Line *self, Cursor *cursor, unsigned int at, unsigned int num,
for (index_type i = at; i < self->xnum && i < at + num; i++) { for (index_type i = at; i < self->xnum && i < at + num; i++) {
if (clear_char) { if (clear_char) {
self->cells[i].ch = BLANK_CHAR; self->cells[i].ch = BLANK_CHAR;
self->cells[i].cc = 0; memset(self->cells[i].cc_idx, 0, sizeof(self->cells[i].cc_idx));
self->cells[i].attrs = attrs; self->cells[i].attrs = attrs;
clear_sprite_position(self->cells[i]); clear_sprite_position(self->cells[i]);
} else { } else {
@@ -508,7 +491,7 @@ line_set_char(Line *self, unsigned int at, uint32_t ch, unsigned int width, Curs
self->cells[at].decoration_fg = cursor->decoration_fg & COL_MASK; self->cells[at].decoration_fg = cursor->decoration_fg & COL_MASK;
} }
self->cells[at].ch = ch; self->cells[at].ch = ch;
self->cells[at].cc = 0; memset(self->cells[at].cc_idx, 0, sizeof(self->cells[at].cc_idx));
} }
static PyObject* static PyObject*
@@ -618,4 +601,3 @@ copy_char(Line* self, PyObject *args) {
COPY_CELL(self, src, to, dest); COPY_CELL(self, src, to, dest);
Py_RETURN_NONE; Py_RETURN_NONE;
} }

View File

@@ -44,7 +44,6 @@ xlimit_for_line(Line *line) {
return xlimit; return xlimit;
} }
PyObject* line_text_at(char_type, combining_type);
void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch); void line_clear_text(Line *self, unsigned int at, unsigned int num, char_type ch);
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);
void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, bool); void line_set_char(Line *, unsigned int , uint32_t , unsigned int , Cursor *, bool);
@@ -79,3 +78,4 @@ void historybuf_init_line(HistoryBuf *self, index_type num, Line *l);
void historybuf_mark_line_clean(HistoryBuf *self, index_type y); void historybuf_mark_line_clean(HistoryBuf *self, index_type y);
void historybuf_mark_line_dirty(HistoryBuf *self, index_type y); void historybuf_mark_line_dirty(HistoryBuf *self, index_type y);
void historybuf_refresh_sprite_positions(HistoryBuf *self); void historybuf_refresh_sprite_positions(HistoryBuf *self);
void historybuf_clear(HistoryBuf *self);

View File

@@ -12,15 +12,15 @@ from .borders import load_borders_program
from .boss import Boss from .boss import Boss
from .cli import create_opts, parse_args from .cli import create_opts, parse_args
from .config import initial_window_size, load_cached_values, save_cached_values from .config import initial_window_size, load_cached_values, save_cached_values
from .constants import glfw_path, is_macos, is_wayland, logo_data_file from .constants import appname, glfw_path, is_macos, is_wayland, logo_data_file
from .fast_data_types import ( from .fast_data_types import (
change_wcwidth, create_os_window, glfw_init, glfw_terminate, change_wcwidth, create_os_window, glfw_init, glfw_terminate,
install_sigchld_handler, set_default_window_icon, set_options, show_window install_sigchld_handler, set_default_window_icon, set_options, show_window
) )
from .fonts.box_drawing import set_scale from .fonts.box_drawing import set_scale
from .utils import ( from .utils import (
detach, encode_wm_class, end_startup_notification, detach, end_startup_notification, init_startup_notification,
init_startup_notification, single_instance single_instance
) )
from .window import load_shader_programs from .window import load_shader_programs
@@ -42,7 +42,7 @@ def run_app(opts, args):
set_options(opts, is_wayland, args.debug_gl) set_options(opts, is_wayland, args.debug_gl)
load_cached_values() load_cached_values()
w, h = initial_window_size(opts) w, h = initial_window_size(opts)
window_id = create_os_window(w, h, encode_wm_class(args.name, args.cls), False, load_all_shaders) window_id = create_os_window(w, h, appname, args.name or args.cls or appname, args.cls or appname, load_all_shaders)
startup_ctx = init_startup_notification(window_id) startup_ctx = init_startup_notification(window_id)
show_window(window_id) show_window(window_id)
if not is_wayland and not is_macos: # no window icons on wayland if not is_wayland and not is_macos: # no window icons on wayland
@@ -109,14 +109,20 @@ def main():
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
except Exception: except Exception:
print('Failed to set locale with no LANG, ignoring', file=sys.stderr) print('Failed to set locale with no LANG, ignoring', file=sys.stderr)
# Ensure kitty is in PATH
rpath = getattr(sys, 'bundle_exe_dir', None) rpath = getattr(sys, 'bundle_exe_dir', None)
if rpath: items = frozenset(os.environ['PATH'].split(os.pathsep))
# Ensure kitty bin directory is in PATH if not rpath:
items = frozenset(os.environ['PATH'].split(os.pathsep)) for candidate in items:
if rpath not in items: if os.access(os.path.join(candidate, 'kitty'), os.X_OK):
os.environ['PATH'] += os.pathsep + rpath break
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', else:
None) == '1' and getattr(sys, 'frozen', True): rpath = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'launcher')
if rpath and rpath not in items:
os.environ['PATH'] += os.pathsep + rpath
if os.environ.pop('KITTY_LAUNCHED_BY_LAUNCH_SERVICES', None) == '1':
os.chdir(os.path.expanduser('~')) os.chdir(os.path.expanduser('~'))
if not os.path.isdir(os.getcwd()): if not os.path.isdir(os.getcwd()):
os.chdir(os.path.expanduser('~')) os.chdir(os.path.expanduser('~'))

View File

@@ -42,24 +42,21 @@ button_map(int button) {
static char mouse_event_buf[64]; static char mouse_event_buf[64];
int static inline int
encode_mouse_event(Window *w, int button, MouseAction action, int mods) { encode_mouse_event_impl(unsigned int x, unsigned int y, int mouse_tracking_protocol, int button, MouseAction action, int mods) {
unsigned int x = w->mouse_cell_x + 1, y = w->mouse_cell_y + 1; // 1 based indexing
unsigned int cb = 0; unsigned int cb = 0;
Screen *screen = w->render_data.screen;
if (action == MOVE) { if (action == MOVE) {
if (screen->modes.mouse_tracking_protocol != SGR_PROTOCOL) cb = 3; cb = 3;
} else { } else {
cb = button_map(button); cb = button_map(button);
if (cb == UINT_MAX) return 0; if (cb == UINT_MAX) return 0;
} }
if (action == DRAG || action == MOVE) cb |= MOTION_INDICATOR; if (action == DRAG || action == MOVE) cb |= MOTION_INDICATOR;
else if (action == RELEASE && screen->modes.mouse_tracking_protocol != SGR_PROTOCOL) cb = 3; else if (action == RELEASE && mouse_tracking_protocol != SGR_PROTOCOL) cb = 3;
if (mods & GLFW_MOD_SHIFT) cb |= SHIFT_INDICATOR; if (mods & GLFW_MOD_SHIFT) cb |= SHIFT_INDICATOR;
if (mods & GLFW_MOD_ALT) cb |= ALT_INDICATOR; if (mods & GLFW_MOD_ALT) cb |= ALT_INDICATOR;
if (mods & GLFW_MOD_CONTROL) cb |= CONTROL_INDICATOR; if (mods & GLFW_MOD_CONTROL) cb |= CONTROL_INDICATOR;
switch(screen->modes.mouse_tracking_protocol) { switch(mouse_tracking_protocol) {
case SGR_PROTOCOL: case SGR_PROTOCOL:
return snprintf(mouse_event_buf, sizeof(mouse_event_buf), "<%d;%d;%d%s", cb, x, y, action == RELEASE ? "m" : "M"); return snprintf(mouse_event_buf, sizeof(mouse_event_buf), "<%d;%d;%d%s", cb, x, y, action == RELEASE ? "m" : "M");
break; break;
@@ -84,6 +81,14 @@ encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
return 0; return 0;
} }
static int
encode_mouse_event(Window *w, int button, MouseAction action, int mods) {
unsigned int x = w->mouse_cell_x + 1, y = w->mouse_cell_y + 1; // 1 based indexing
Screen *screen = w->render_data.screen;
return encode_mouse_event_impl(x, y, screen->modes.mouse_tracking_protocol, button, action, mods);
}
static inline bool static inline bool
contains_mouse(Window *w) { contains_mouse(Window *w) {
WindowGeometry *g = &w->geometry; WindowGeometry *g = &w->geometry;
@@ -111,7 +116,7 @@ update_drag(bool from_button, Window *w, bool is_release, int modifiers) {
Screen *screen = w->render_data.screen; Screen *screen = w->render_data.screen;
if (from_button) { if (from_button) {
if (is_release) screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true); if (is_release) screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, true);
else screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (GLFW_MOD_CONTROL | GLFW_MOD_ALT)); else screen_start_selection(screen, w->mouse_cell_x, w->mouse_cell_y, modifiers == (int)OPT(rectangle_select_modifiers));
} else if (screen->selection.in_progress) { } else if (screen->selection.in_progress) {
screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false); screen_update_selection(screen, w->mouse_cell_x, w->mouse_cell_y, false);
call_boss(set_primary_selection, NULL); call_boss(set_primary_selection, NULL);
@@ -315,9 +320,19 @@ handle_tab_bar_mouse(int button, int UNUSED modifiers) {
call_boss(activate_tab_at, "Kd", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x); call_boss(activate_tab_at, "Kd", global_state.callback_os_window->id, global_state.callback_os_window->mouse_x);
} }
static inline bool
mouse_in_region(Region *r) {
if (r->left == r->right) return false;
if (global_state.callback_os_window->mouse_y < r->top || global_state.callback_os_window->mouse_y > r->bottom) return false;
if (global_state.callback_os_window->mouse_x < r->left || global_state.callback_os_window->mouse_x > r->right) return false;
return true;
}
static inline Window* static inline Window*
window_for_event(unsigned int *window_idx, bool *in_tab_bar) { window_for_event(unsigned int *window_idx, bool *in_tab_bar) {
*in_tab_bar = global_state.callback_os_window->num_tabs > 1 && global_state.callback_os_window->mouse_y >= global_state.callback_os_window->viewport_height - global_state.cell_height; Region central, tab_bar;
os_window_regions(global_state.callback_os_window, &central, &tab_bar);
*in_tab_bar = mouse_in_region(&tab_bar);
if (!*in_tab_bar && global_state.callback_os_window->num_tabs > 0) { if (!*in_tab_bar && global_state.callback_os_window->num_tabs > 0) {
Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab; Tab *t = global_state.callback_os_window->tabs + global_state.callback_os_window->active_tab;
for (unsigned int i = 0; i < t->num_windows; i++) { for (unsigned int i = 0; i < t->num_windows; i++) {
@@ -375,8 +390,28 @@ scroll_event(double UNUSED xoffset, double yoffset) {
int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0); int sz = encode_mouse_event(w, upwards ? GLFW_MOUSE_BUTTON_4 : GLFW_MOUSE_BUTTON_5, PRESS, 0);
if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); } if (sz > 0) { mouse_event_buf[sz] = 0; write_escape_code_to_child(screen, CSI, mouse_event_buf); }
} else { } else {
fake_scroll(upwards); fake_scroll(abs(s), upwards);
} }
} }
} }
} }
static PyObject*
test_encode_mouse(PyObject *self UNUSED, PyObject *args) {
unsigned int x, y;
int mouse_tracking_protocol, button, action, mods;
if (!PyArg_ParseTuple(args, "IIiiii", &x, &y, &mouse_tracking_protocol, &button, &action, &mods)) return NULL;
int sz = encode_mouse_event_impl(x, y, mouse_tracking_protocol, button, action, mods);
return PyUnicode_FromStringAndSize(mouse_event_buf, sz);
}
static PyMethodDef module_methods[] = {
METHODB(test_encode_mouse, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_mouse(PyObject *module) {
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

View File

@@ -727,6 +727,17 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
// }}} // }}}
// DCS mode {{{ // DCS mode {{{
static inline bool
startswith(const uint32_t *string, size_t sz, const char *prefix) {
size_t l = strlen(prefix);
if (sz < l) return false;
for (size_t i = 0; i < l; i++) {
if (string[i] != (unsigned char)prefix[i]) return false;
}
return true;
}
static inline void static inline void
dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
if (screen->parser_buf_pos < 2) return; if (screen->parser_buf_pos < 2) return;
@@ -739,11 +750,23 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
REPORT_OSC2(screen_request_capabilities, (char)screen->parser_buf[0], string); REPORT_OSC2(screen_request_capabilities, (char)screen->parser_buf[0], string);
screen_request_capabilities(screen, (char)screen->parser_buf[0], string); screen_request_capabilities(screen, (char)screen->parser_buf[0], string);
Py_DECREF(string); Py_DECREF(string);
} } else PyErr_Clear();
} else { } else {
REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]); REPORT_ERROR("Unrecognized DCS %c code: 0x%x", (char)screen->parser_buf[0], screen->parser_buf[1]);
} }
break; break;
case '@':
if (startswith(screen->parser_buf + 1, screen->parser_buf_pos - 2, "kitty-cmd{")) {
PyObject *cmd = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, screen->parser_buf + 10, screen->parser_buf_pos - 10);
if (cmd != NULL) {
REPORT_OSC2(screen_handle_cmd, (char)screen->parser_buf[0], cmd);
screen_handle_cmd(screen, cmd);
Py_DECREF(cmd);
} else PyErr_Clear();
} else {
REPORT_ERROR("Unrecognized DCS @ code: 0x%x", screen->parser_buf[1]);
}
break;
default: default:
REPORT_ERROR("Unrecognized DCS code: 0x%x", screen->parser_buf[0]); REPORT_ERROR("Unrecognized DCS code: 0x%x", screen->parser_buf[0]);
break; break;

488
kitty/remote_control.py Normal file
View File

@@ -0,0 +1,488 @@
#!/usr/bin/env python
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import json
import re
import sys
import types
from functools import partial
from .cli import emph, parse_args
from .config import parse_send_text_bytes
from .constants import appname, version
from .tabs import SpecialWindow
from .utils import non_blocking_read, read_with_timeout
def cmd(short_desc, desc=None, options_spec=None, no_response=False):
def w(func):
func.short_desc = short_desc
func.desc = desc or short_desc
func.name = func.__name__[4:].replace('_', '-')
func.options_spec = options_spec
func.is_cmd = True
func.impl = lambda: globals()[func.__name__[4:]]
func.no_response = no_response
return func
return w
def parse_subcommand_cli(func, args):
opts, items = parse_args(args[1:], (func.options_spec or '\n').format, '...', func.desc, '{} @ {}'.format(appname, func.name))
return opts, items
@cmd(
'List all tabs/windows',
'List all windows. The list is returned as JSON tree. The top-level is a list of'
' operating system {appname} windows. Each OS window has an |_ id| and a list'
' of |_ tabs|. Each tab has its own |_ id|, a |_ title| and a list of |_ windows|.'
' Each window has an |_ id|, |_ title|, |_ current working directory|, |_ process id (PID)| and'
' |_ command-line| of the process running in the window.\n\n'
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname)
)
def cmd_ls(global_opts, opts, args):
pass
def ls(boss, window):
data = list(boss.list_os_windows())
data = json.dumps(data, indent=2, sort_keys=True)
return data
MATCH_WINDOW_OPTION = '''\
--match -m
The window to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of windows. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression.
'''
MATCH_TAB_OPTION = '''\
--match -m
The tab to match. Match specifications are of the form:
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
You can use the |_ ls| command to get a list of tabs. Note that for
numeric fields such as id and pid the expression is interpreted as a number,
not a regular expression. When using title or id, first a matching tab is
looked for and if not found a matching window is looked for, and the tab
for that window is used.
'''
@cmd(
'Send arbitrary text to specified windows',
'Send arbitrary text to specified windows. The text follows Python'
' escaping rules. So you can use escapes like |_ \\x1b| to send control codes'
' and |_ \\u21fa| to send unicode characters. If you use the |_ --match| option'
' the text will be sent to all matched windows. By default, text is sent to'
' only the currently active window.',
options_spec=MATCH_WINDOW_OPTION + '''\n
--stdin
type=bool-set
Read the text to be sent from |_ stdin|. Note that in this case the text is sent as is,
not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end reading.
--from-file
Path to a file whose contents you wish to send. Note that in this case the file contents
are sent as is, not interpreted for escapes.
''',
no_response=True
)
def cmd_send_text(global_opts, opts, args):
limit = 1024
ret = {'match': opts.match, 'is_binary': False}
def pipe(src=sys.stdin):
ret['is_binary'] = True
import select
with non_blocking_read() as fd:
keep_going = True
while keep_going:
rd = select.select([fd], [], [])
if rd:
data = sys.stdin.buffer.read()
if not data:
break
data = data.decode('utf-8')
if '\x04' in data:
data = data[:data.index('\x04')]
keep_going = False
while data:
ret['text'] = data[:limit]
yield ret
data = data[limit:]
else:
break
def chunks(text):
ret['is_binary'] = False
while text:
ret['text'] = text[:limit]
yield ret
text = text[limit:]
def file_pipe(path):
ret['is_binary'] = True
with open(path, encoding='utf-8') as f:
while True:
data = f.read(limit)
if not data:
break
ret['text'] = data
yield ret
sources = []
if opts.stdin:
sources.append(pipe())
if opts.from_file:
sources.append(file_pipe(opts.from_file))
text = ' '.join(args)
sources.append(chunks(text))
def chain():
for src in sources:
yield from src
return chain()
def send_text(boss, window, payload):
windows = [boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
for window in windows:
if window is not None:
window.write_to_child(data)
@cmd(
'Set the window title',
'Set the title for the specified window(s). If you use the |_ --match| option'
' the title will be set for all matched windows. By default, only the window'
' in which the command is run is affected. If you do not specify a title, the'
' last title set by the child process running in the window will be used.',
options_spec=MATCH_WINDOW_OPTION
)
def cmd_set_window_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_window_title(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
for window in windows:
if window:
window.set_title(payload['title'])
@cmd(
'Set the tab title',
'Set the title for the specified tab(s). If you use the |_ --match| option'
' the title will be set for all matched tabs. By default, only the tab'
' in which the command is run is affected. If you do not specify a title, the'
' title of the currently active window in the tab is used.',
options_spec=MATCH_TAB_OPTION
)
def cmd_set_tab_title(global_opts, opts, args):
return {'title': ' '.join(args), 'match': opts.match}
def set_tab_title(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
for tab in tabs:
if tab:
tab.set_title(payload['title'])
@cmd(
'Close the specified window(s)',
options_spec=MATCH_WINDOW_OPTION + '''\n
--self
type=bool-set
If specified close the window this command is run in, rather than the active window.
'''
)
def cmd_close_window(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_window(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
windows = [window if window and payload['self'] else boss.active_window]
for window in windows:
if window:
boss.close_window(window)
@cmd(
'Close the specified tab(s)',
options_spec=MATCH_TAB_OPTION + '''\n
--self
type=bool-set
If specified close the tab this command is run in, rather than the active tab.
'''
)
def cmd_close_tab(global_opts, opts, args):
return {'match': opts.match, 'self': opts.self}
def close_tab(boss, window, payload):
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
tabs = [boss.tab_for_window(window) if window and payload['self'] else boss.active_tab]
for tab in tabs:
if window:
if tab:
boss.close_tab(tab)
@cmd(
'Open new window',
'Open a new window in the specified tab. If you use the |_ --match| option'
' the first matching tab is used. Otherwise the currently active tab is used.'
' Prints out the id of the newly opened window. Any command line arguments'
' are assumed to be the command line used to run in the new window, if none'
' are provided, the default shell is run. For example:\n'
'|_ kitty @ new-window --title Email mutt|',
options_spec=MATCH_TAB_OPTION + '''\n
--title
The title for the new window. By default it will use the title set by the
program running in it.
--cwd
The initial working directory for the new window.
--keep-focus
type=bool-set
Keep the current window focused instead of switching to the newly opened window
--new-tab
type=bool-set
Open a new tab
--tab-title
When using --new-tab set the title of the tab.
'''
)
def cmd_new_window(global_opts, opts, args):
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
'keep_focus': opts.keep_focus, 'args': args or []}
def new_window(boss, window, payload):
w = SpecialWindow(cmd=payload['args'] or None, override_title=payload['title'], cwd=payload['cwd'])
old_window = boss.active_window
if payload['new_tab']:
boss._new_tab(w)
tab = boss.active_tab
if payload['tab_title']:
tab.set_title(payload['tab_title'])
wid = boss.active_window.id
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(wid)
match = payload['match']
if match:
tabs = tuple(boss.match_tabs(match))
if not tabs:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
tabs = [boss.active_tab]
tab = tabs[0]
w = tab.new_special_window(w)
if payload['keep_focus'] and old_window:
boss.set_active_window(old_window)
return str(w.id)
@cmd(
'Focus the specified window',
options_spec=MATCH_WINDOW_OPTION
)
def cmd_focus_window(global_opts, opts, args):
return {'match': opts.match}
def focus_window(boss, window, payload):
windows = [window or boss.active_window]
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
for window in windows:
if window:
boss.set_active_window(window)
break
@cmd(
'Get text from the specified window',
options_spec=MATCH_WINDOW_OPTION + '''\n
--extent
default=screen
choices=screen, all, selection
What text to get. The default of screen means all text currently on the screen. all means
all the screen+scrollback and selection means currently selected text.
--ansi
type=bool-set
By default, only plain text is returned. If you specify this flag, the text will
include the formatting escape codes for colors/bold/italic/etc. Note that when
getting the current selection, the result is always plain text.
--self
type=bool-set
If specified get text from the window this command is run in, rather than the active window.
'''
)
def cmd_get_text(global_opts, opts, args):
return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self}
def get_text(boss, window, payload):
match = payload['match']
if match:
windows = tuple(boss.match_windows(match))
if not windows:
raise ValueError('No matching windows for expression: {}'.format(match))
else:
windows = [window if window and payload['self'] else boss.active_window]
window = windows[0]
if payload['extent'] == 'selection':
ans = window.text_for_selection()
else:
f = window.buffer_as_ansi if payload['ansi'] else window.buffer_as_text
ans = f(add_history=payload['extent'] == 'all')
return ans
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
def handle_cmd(boss, window, cmd):
cmd = json.loads(cmd)
v = cmd['version']
if tuple(v)[:2] > version[:2]:
return {'ok': False, 'error': 'The kitty client you are using to send remote commands is newer than this kitty instance. This is not supported.'}
c = cmap[cmd['cmd']]
func = partial(c.impl(), boss, window)
payload = cmd.get('payload')
ans = func() if payload is None else func(payload)
response = {'ok': True}
if ans is not None:
response['data'] = ans
if not c.no_response:
return response
global_options_spec = partial('''\
'''.format, appname=appname)
def read_from_stdin(send, no_response):
send = ('@kitty-cmd' + json.dumps(send)).encode('ascii')
out = sys.stdout if sys.stdout.isatty() else sys.stderr
if not out.isatty():
raise SystemExit('Neither stdout nor stderr is a terminal')
out.buffer.write(b'\x1bP' + send + b'\x1b\\')
out.flush()
if no_response:
return {'ok': True}
if not sys.stdin.isatty():
raise SystemExit('stdin is not a terminal')
received = b''
dcs = re.compile(br'\x1bP@kitty-cmd([^\x1b]+)\x1b\\')
match = None
def more_needed(data):
nonlocal received, match
received += data
match = dcs.search(received)
return match is None
read_with_timeout(more_needed)
if match is None:
raise SystemExit('Failed to receive response from ' + appname)
response = json.loads(match.group(1).decode('ascii'))
return response
def main(args):
all_commands = tuple(sorted(cmap))
cmds = (' |G {}|\n {}'.format(cmap[c].name, cmap[c].short_desc) for c in all_commands)
msg = (
'Control {appname} by sending it commands. Add'
' |_ allow_remote_control yes| to kitty.conf for this'
' to work.\n\n|T Commands|:\n{cmds}\n\n'
'You can get help for each individual command by using:\n'
'{appname} @ |_ command| -h'
).format(appname=appname, cmds='\n'.join(cmds))
global_opts, items = parse_args(args[1:], global_options_spec, 'command ...', msg, '{} @'.format(appname))
if not items:
raise SystemExit('You must specify a command')
cmd = items[0]
try:
func = cmap[cmd]
except KeyError:
raise SystemExit('{} is not a known command. Known commands are: {}'.format(
emph(cmd), ', '.join(all_commands)))
opts, items = parse_subcommand_cli(func, items)
payload = func(global_opts, opts, items)
send = {
'cmd': cmd,
'version': version,
}
if func.no_response and isinstance(payload, types.GeneratorType):
for item in payload:
send['payload'] = item
read_from_stdin(send, func.no_response)
return
if payload is not None:
send['payload'] = payload
response = read_from_stdin(send, func.no_response)
if not response.get('ok'):
if response.get('tb'):
print(response['tb'], file=sys.stderr)
raise SystemExit(response['error'])
if 'data' in response:
print(response['data'])

View File

@@ -18,11 +18,15 @@
#include <fcntl.h> #include <fcntl.h>
#include "unicode-data.h" #include "unicode-data.h"
#include "modes.h" #include "modes.h"
#include "wcwidth9.h" #include "wcwidth-std.h"
#include "control-codes.h" #include "control-codes.h"
static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true}; static const ScreenModes empty_modes = {0, .mDECAWM=true, .mDECTCEM=true, .mDECARM=true};
static Selection EMPTY_SELECTION = {0}; static Selection EMPTY_SELECTION = {0};
typedef struct {
unsigned int x; int y;
} FullSelectionBoundary;
// Constructor/destructor {{{ // Constructor/destructor {{{
@@ -103,7 +107,7 @@ void
screen_reset(Screen *self) { 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, BLANK_CHAR); linebuf_clear(self->linebuf, BLANK_CHAR);
grman_clear(self->grman); grman_clear(self->grman, false);
self->modes = empty_modes; self->modes = empty_modes;
#define R(name) self->color_profile->overridden.name = 0 #define R(name) self->color_profile->overridden.name = 0
R(default_fg); R(default_bg); R(cursor_color); R(highlight_fg); R(highlight_bg); R(default_fg); R(default_bg); R(cursor_color); R(highlight_fg); R(highlight_bg);
@@ -275,8 +279,8 @@ safe_wcwidth(uint32_t ch) {
} }
void void
change_wcwidth(bool use9) { change_wcwidth(bool use_std) {
wcwidth_impl = (use9) ? wcwidth9 : wcwidth; wcwidth_impl = use_std ? wcwidth_std : wcwidth;
} }
@@ -387,28 +391,29 @@ write_to_child(Screen *self, const char *data, size_t sz) {
void void
write_escape_code_to_child(Screen *self, unsigned char which, const char *data) { write_escape_code_to_child(Screen *self, unsigned char which, const char *data) {
static char buf[512]; const char *prefix, *suffix = self->modes.eight_bit_controls ? "\x9c" : "\033\\";
size_t sz;
switch(which) { switch(which) {
case DCS: case DCS:
sz = snprintf(buf, sizeof(buf) - 1, "%s%s%s", self->modes.eight_bit_controls ? "\x90" : "\033P", data, self->modes.eight_bit_controls ? "\x9c" : "\033\\"); prefix = self->modes.eight_bit_controls ? "\x90" : "\033P";
break; break;
case CSI: case CSI:
sz = snprintf(buf, sizeof(buf) - 1, "%s%s", self->modes.eight_bit_controls ? "\x9b" : "\033[", data); prefix = self->modes.eight_bit_controls ? "\x9b" : "\033["; suffix = "";
break; break;
case OSC: case OSC:
sz = snprintf(buf, sizeof(buf) - 1, "%s%s%s", self->modes.eight_bit_controls ? "\x9d" : "\033]", data, self->modes.eight_bit_controls ? "\x9c" : "\033\\"); prefix = self->modes.eight_bit_controls ? "\x9d" : "\033]";
break; break;
case PM: case PM:
sz = snprintf(buf, sizeof(buf) - 1, "%s%s%s", self->modes.eight_bit_controls ? "\x9e" : "\033^", data, self->modes.eight_bit_controls ? "\x9c" : "\033\\"); prefix = self->modes.eight_bit_controls ? "\x9e" : "\033^";
break; break;
case APC: case APC:
sz = snprintf(buf, sizeof(buf) - 1, "%s%s%s", self->modes.eight_bit_controls ? "\x9f" : "\033_", data, self->modes.eight_bit_controls ? "\x9c" : "\033\\"); prefix = self->modes.eight_bit_controls ? "\x9f" : "\033_";
break; break;
default: default:
fatal("Unknown escape code to write: %u", which); fatal("Unknown escape code to write: %u", which);
} }
write_to_child(self, buf, sz); write_to_child(self, prefix, strlen(prefix));
write_to_child(self, data, strlen(data));
if (suffix[0]) write_to_child(self, suffix, strlen(suffix));
} }
void void
@@ -430,7 +435,7 @@ screen_handle_graphics_command(Screen *self, const GraphicsCommand *cmd, const u
void void
screen_toggle_screen_buffer(Screen *self) { screen_toggle_screen_buffer(Screen *self) {
bool to_alt = self->linebuf == self->main_linebuf; bool to_alt = self->linebuf == self->main_linebuf;
grman_clear(self->alt_grman); // always clear the alt buffer graphics to free up resources, since it has to be cleared when switching back to it anyway grman_clear(self->alt_grman, true); // always clear the alt buffer graphics to free up resources, since it has to be cleared when switching back to it anyway
if (to_alt) { if (to_alt) {
linebuf_clear(self->alt_linebuf, BLANK_CHAR); linebuf_clear(self->alt_linebuf, BLANK_CHAR);
screen_save_cursor(self); screen_save_cursor(self);
@@ -882,6 +887,7 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
including cursor position. including cursor position.
* ``2`` -- Erases complete display. All lines are erased * ``2`` -- Erases complete display. All lines are erased
and changed to single-width. Cursor does not move. and changed to single-width. Cursor does not move.
* ``3`` -- Erase complete display and scrollback buffer as well.
:param bool private: when ``True`` character attributes are left unchanged :param bool private: when ``True`` character attributes are left unchanged
*/ */
unsigned int a, b; unsigned int a, b;
@@ -891,7 +897,8 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
case 1: case 1:
a = 0; b = self->cursor->y; break; a = 0; b = self->cursor->y; break;
case 2: case 2:
grman_clear(self->grman); case 3:
grman_clear(self->grman, how == 3);
a = 0; b = self->lines; break; a = 0; b = self->lines; break;
default: default:
return; return;
@@ -911,6 +918,13 @@ screen_erase_in_display(Screen *self, unsigned int how, bool private) {
if (how != 2) { if (how != 2) {
screen_erase_in_line(self, how, private); screen_erase_in_line(self, how, private);
} }
if (how == 3 && self->linebuf == self->main_linebuf) {
historybuf_clear(self->historybuf);
if (self->scrolled_by != 0) {
self->scrolled_by = 0;
self->scroll_changed = true;
}
}
} }
void void
@@ -1132,6 +1146,11 @@ set_color_table_color(Screen *self, unsigned int code, PyObject *color) {
else { CALLBACK("set_color_table_color", "IO", code, color); } else { CALLBACK("set_color_table_color", "IO", code, color); }
} }
void
screen_handle_cmd(Screen *self, PyObject *cmd) {
CALLBACK("handle_remote_cmd", "O", cmd);
}
void void
screen_request_capabilities(Screen *self, char c, PyObject *q) { screen_request_capabilities(Screen *self, char c, PyObject *q) {
static char buf[128]; static char buf[128];
@@ -1219,6 +1238,7 @@ screen_update_cell_data(Screen *self, void *address, size_t UNUSED sz) {
} }
} }
static inline bool static inline bool
is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) { is_selection_empty(Screen *self, unsigned int start_x, unsigned int start_y, unsigned int end_x, unsigned int end_y) {
return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false; return (start_x >= self->columns || start_y >= self->lines || end_x >= self->columns || end_y >= self->lines || (start_x == end_x && start_y == end_y)) ? true : false;
@@ -1238,6 +1258,13 @@ selection_coord(Screen *self, unsigned int x, unsigned int y, unsigned int ydelt
} }
} }
static inline void
full_selection_coord(Screen *self, unsigned int x, unsigned int y, unsigned int ydelta, FullSelectionBoundary *ans) {
ans->x = MIN(x, self->columns - 1);
ans->y = y - ydelta;
if (y >= self->lines) { ans->x = self->columns - 1; ans->y = self->lines - 1; }
}
#define selection_limits_(which, left, right) { \ #define selection_limits_(which, left, right) { \
SelectionBoundary a, b; \ SelectionBoundary a, b; \
selection_coord(self, self->which.start_x, self->which.start_y, self->which.start_scrolled_by, &a); \ selection_coord(self, self->which.start_x, self->which.start_y, self->which.start_scrolled_by, &a); \
@@ -1246,6 +1273,15 @@ selection_coord(Screen *self, unsigned int x, unsigned int y, unsigned int ydelt
else { *(left) = b; *(right) = a; } \ else { *(left) = b; *(right) = a; } \
} }
#define full_selection_limits_(which, left, right) { \
FullSelectionBoundary a, b; \
full_selection_coord(self, self->which.start_x, self->which.start_y, self->which.start_scrolled_by, &a); \
full_selection_coord(self, self->which.end_x, self->which.end_y, self->which.end_scrolled_by, &b); \
if (a.y < b.y || (a.y == b.y && a.x <= b.x)) { *(left) = a; *(right) = b; } \
else { *(left) = b; *(right) = a; } \
}
static inline Line* static inline Line*
visual_line_(Screen *self, index_type y) { visual_line_(Screen *self, index_type y) {
if (self->scrolled_by) { if (self->scrolled_by) {
@@ -1259,18 +1295,28 @@ visual_line_(Screen *self, index_type y) {
return self->linebuf->line; return self->linebuf->line;
} }
#define iterate_over_rectangle(start, end) { \ static inline Line*
index_type min_y = MIN(start->y, end->y), max_y = MAX(start->y, end->y); \ range_line_(Screen *self, int y) {
if (y < 0) {
historybuf_init_line(self->historybuf, -y, self->historybuf->line);
return self->historybuf->line;
}
linebuf_init_line(self->linebuf, y);
return self->linebuf->line;
}
#define iterate_over_rectangle(start, end, line_func, y_type) { \
y_type min_y = MIN(start->y, end->y), max_y = MAX(start->y, end->y); \
index_type min_x = MIN(start->x, end->x), max_x = MAX(start->x, end->x); \ index_type min_x = MIN(start->x, end->x), max_x = MAX(start->x, end->x); \
for (index_type y = min_y; y <= max_y; y++) { \ for (y_type y = min_y; y <= max_y; y++) { \
Line *line = visual_line_(self, y); \ Line *line = line_func(self, y); \
index_type xlimit = xlimit_for_line(line); \ index_type xlimit = xlimit_for_line(line); \
xlimit = MIN(max_x + 1, xlimit); \ xlimit = MIN(max_x + 1, xlimit); \
index_type x_start = min_x; \ index_type x_start = min_x;
#define iterate_over_region(start, end) { \ #define iterate_over_region(start, end, line_func, y_type) { \
for (index_type y = start->y; y <= end->y; y++) { \ for (y_type y = start->y; y <= end->y; y++) { \
Line *line = visual_line_(self, y); \ Line *line = line_func(self, y); \
index_type xlimit = xlimit_for_line(line); \ index_type xlimit = xlimit_for_line(line); \
if (y == end->y) xlimit = MIN(end->x + 1, xlimit); \ if (y == end->y) xlimit = MIN(end->x + 1, xlimit); \
index_type x_start = y == start->y ? start->x : 0; index_type x_start = y == start->y ? start->x : 0;
@@ -1280,12 +1326,12 @@ static inline void
apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, SelectionBoundary *end, uint8_t set_mask, bool rectangle_select) { apply_selection(Screen *self, uint8_t *data, SelectionBoundary *start, SelectionBoundary *end, uint8_t set_mask, bool rectangle_select) {
if (is_selection_empty(self, start->x, start->y, end->x, end->y)) return; if (is_selection_empty(self, start->x, start->y, end->x, end->y)) return;
if (rectangle_select) { if (rectangle_select) {
iterate_over_rectangle(start, end) iterate_over_rectangle(start, end, visual_line_, index_type)
uint8_t *line_start = data + self->columns * y; uint8_t *line_start = data + self->columns * y;
for (index_type x = x_start; x < xlimit; x++) line_start[x] |= set_mask; for (index_type x = x_start; x < xlimit; x++) line_start[x] |= set_mask;
}} }}
} else { } else {
iterate_over_region(start, end) iterate_over_region(start, end, visual_line_, index_type)
uint8_t *line_start = data + self->columns * y; uint8_t *line_start = data + self->columns * y;
for (index_type x = x_start; x < xlimit; x++) line_start[x] |= set_mask; for (index_type x = x_start; x < xlimit; x++) line_start[x] |= set_mask;
}} }}
@@ -1304,35 +1350,35 @@ screen_apply_selection(Screen *self, void *address, size_t size) {
apply_selection(self, address, &self->last_rendered_url_start, &self->last_rendered_url_end, 2, false); apply_selection(self, address, &self->last_rendered_url_start, &self->last_rendered_url_end, 2, false);
} }
static inline PyObject* #define text_for_range_action(ans, insert_newlines) { \
text_for_range(Screen *self, SelectionBoundary start, SelectionBoundary end, bool rectangle_select, bool insert_newlines) { char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0; \
int num_of_lines = end.y - start.y + 1, i = 0; PyObject *text = unicode_in_range(line, x_start, xlimit, true, leading_char); \
PyObject *ans = PyTuple_New(num_of_lines); if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } \
if (ans == NULL) return PyErr_NoMemory(); PyTuple_SET_ITEM(ans, i++, text); \
#define action \
char leading_char = (i > 0 && insert_newlines && !line->continued) ? '\n' : 0; \
PyObject *text = unicode_in_range(line, x_start, xlimit, true, leading_char); \
if (text == NULL) { Py_DECREF(ans); return PyErr_NoMemory(); } \
PyTuple_SET_ITEM(ans, i++, text);
if (rectangle_select) {
iterate_over_rectangle((&start), (&end))
action
} }} else {
iterate_over_region((&start), (&end))
action
} }}
#undef action
return ans;
} }
#define text_for_range(ans, start, end, rectangle_select, insert_newlines, line_func, y_type) { \
int num_of_lines = end.y - start.y + 1, i = 0; \
ans = PyTuple_New(num_of_lines); \
if (ans == NULL) return PyErr_NoMemory(); \
if (rectangle_select) { \
iterate_over_rectangle((&start), (&end), line_func, y_type) \
text_for_range_action(ans, insert_newlines); \
} }} else { \
iterate_over_region((&start), (&end), line_func, y_type) \
text_for_range_action(ans, insert_newlines); \
} }} \
}
bool bool
screen_open_url(Screen *self) { screen_open_url(Screen *self) {
SelectionBoundary start, end; SelectionBoundary start, end;
selection_limits_(url_range, &start, &end); selection_limits_(url_range, &start, &end);
if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return false; if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return false;
PyObject *text = text_for_range(self, start, end, false, false); PyObject *url;
if (text) { call_boss(open_url_lines, "(O)", text); Py_CLEAR(text); } text_for_range(url, start, end, false, false, visual_line_, index_type);
if (url) { call_boss(open_url_lines, "(O)", url); Py_CLEAR(url); }
else PyErr_Print(); else PyErr_Print();
return true; return true;
} }
@@ -1343,10 +1389,10 @@ screen_open_url(Screen *self) {
#define WRAP0(name) static PyObject* name(Screen *self) { screen_##name(self); Py_RETURN_NONE; } #define WRAP0(name) static PyObject* name(Screen *self) { screen_##name(self); Py_RETURN_NONE; }
#define WRAP0x(name) static PyObject* xxx_##name(Screen *self) { screen_##name(self); Py_RETURN_NONE; } #define WRAP0x(name) static PyObject* xxx_##name(Screen *self) { screen_##name(self); Py_RETURN_NONE; }
#define WRAP1(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v); Py_RETURN_NONE; } #define WRAP1(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v); Py_RETURN_NONE; }
#define WRAP1E(name, defval, ...) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v, __VA_ARGS__); Py_RETURN_NONE; }
#define WRAP1B(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; int b=false; if(!PyArg_ParseTuple(args, "|Ip", &v, &b)) return NULL; screen_##name(self, v, b); Py_RETURN_NONE; } #define WRAP1B(name, defval) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; int b=false; if(!PyArg_ParseTuple(args, "|Ip", &v, &b)) return NULL; screen_##name(self, v, b); Py_RETURN_NONE; }
#define WRAP1E(name, defval, ...) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v, __VA_ARGS__); Py_RETURN_NONE; } #define WRAP1E(name, defval, ...) static PyObject* name(Screen *self, PyObject *args) { unsigned int v=defval; if(!PyArg_ParseTuple(args, "|I", &v)) return NULL; screen_##name(self, v, __VA_ARGS__); Py_RETURN_NONE; }
#define WRAP2(name, defval1, defval2) static PyObject* name(Screen *self, PyObject *args) { unsigned int a=defval1, b=defval2; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_##name(self, a, b); Py_RETURN_NONE; } #define WRAP2(name, defval1, defval2) static PyObject* name(Screen *self, PyObject *args) { unsigned int a=defval1, b=defval2; if(!PyArg_ParseTuple(args, "|II", &a, &b)) return NULL; screen_##name(self, a, b); Py_RETURN_NONE; }
#define WRAP2B(name) static PyObject* name(Screen *self, PyObject *args) { unsigned int a, b; int p; if(!PyArg_ParseTuple(args, "IIp", &a, &b, &p)) return NULL; screen_##name(self, a, b, (bool)p); Py_RETURN_NONE; }
static PyObject* static PyObject*
refresh_sprite_positions(Screen *self) { refresh_sprite_positions(Screen *self) {
@@ -1479,6 +1525,8 @@ WRAP0(carriage_return)
WRAP2(resize, 1, 1) WRAP2(resize, 1, 1)
WRAP2(set_margins, 1, 1) WRAP2(set_margins, 1, 1)
WRAP2(rescale_images, 1, 1) WRAP2(rescale_images, 1, 1)
WRAP2B(start_selection)
WRAP2B(update_selection)
static PyObject* static PyObject*
change_scrollback_size(Screen *self, PyObject *args) { change_scrollback_size(Screen *self, PyObject *args) {
@@ -1490,10 +1538,12 @@ change_scrollback_size(Screen *self, PyObject *args) {
static PyObject* static PyObject*
text_for_selection(Screen *self) { text_for_selection(Screen *self) {
SelectionBoundary start, end; FullSelectionBoundary start, end;
selection_limits_(selection, &start, &end); full_selection_limits_(selection, &start, &end);
if (is_selection_empty(self, start.x, start.y, end.x, end.y)) return PyTuple_New(0); PyObject *ans = NULL;
return text_for_range(self, start, end, self->rectangle_select, true); if (start.y == end.y && start.x == end.x) ans = PyTuple_New(0);
else text_for_range(ans, start, end, self->rectangle_select, true, range_line_, int);
return ans;
} }
bool bool
@@ -1694,6 +1744,8 @@ static PyMethodDef methods[] = {
MND(carriage_return, METH_NOARGS) MND(carriage_return, METH_NOARGS)
MND(set_tab_stop, METH_NOARGS) MND(set_tab_stop, METH_NOARGS)
MND(clear_tab_stop, METH_VARARGS) MND(clear_tab_stop, METH_VARARGS)
MND(start_selection, METH_VARARGS)
MND(update_selection, METH_VARARGS)
MND(reverse_index, METH_NOARGS) MND(reverse_index, METH_NOARGS)
MND(mark_as_dirty, METH_NOARGS) MND(mark_as_dirty, METH_NOARGS)
MND(resize, METH_VARARGS) MND(resize, METH_VARARGS)

View File

@@ -106,6 +106,7 @@ void screen_delete_characters(Screen *self, unsigned int count);
void screen_erase_characters(Screen *self, unsigned int count); 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_handle_cmd(Screen *, PyObject *cmd);
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 screen_use_latin1(Screen *, bool);
void set_title(Screen *self, PyObject*); void set_title(Screen *self, PyObject*);

View File

@@ -18,6 +18,7 @@ class Tab:
self.enabled_layouts = opts.enabled_layouts self.enabled_layouts = opts.enabled_layouts
self.layout = (self.enabled_layouts or ['tall'])[0] self.layout = (self.enabled_layouts or ['tall'])[0]
self.cwd = None self.cwd = None
self.next_title = None
class Session: class Session:
@@ -31,6 +32,9 @@ class Session:
del self.tabs[-1] del self.tabs[-1]
self.tabs.append(Tab(opts, name)) self.tabs.append(Tab(opts, name))
def set_next_title(self, title):
self.tabs[-1].next_title = title.strip()
def set_layout(self, val): def set_layout(self, val):
if val not in all_layouts: if val not in all_layouts:
raise ValueError('{} is not a valid layout'.format(val)) raise ValueError('{} is not a valid layout'.format(val))
@@ -41,7 +45,10 @@ class Session:
cmd = shlex.split(cmd) if isinstance(cmd, str) else cmd cmd = shlex.split(cmd) if isinstance(cmd, str) else cmd
else: else:
cmd = None cmd = None
self.tabs[-1].windows.append(cmd) from .tabs import SpecialWindow
t = self.tabs[-1]
t.windows.append(SpecialWindow(cmd, cwd=t.cwd, override_title=t.next_title))
t.next_title = None
def add_special_window(self, sw): def add_special_window(self, sw):
self.tabs[-1].windows.append(sw) self.tabs[-1].windows.append(sw)
@@ -57,6 +64,15 @@ class Session:
self.tabs[-1].cwd = val self.tabs[-1].cwd = val
def resolved_shell(opts):
ans = opts.shell
if ans == '.':
ans = [shell_path]
else:
ans = shlex.split(ans)
return ans
def parse_session(raw, opts): def parse_session(raw, opts):
ans = Session() ans = Session()
ans.add_tab(opts) ans.add_tab(opts)
@@ -77,15 +93,17 @@ def parse_session(raw, opts):
ans.set_enabled_layouts(rest) ans.set_enabled_layouts(rest)
elif cmd == 'cd': elif cmd == 'cd':
ans.set_cwd(rest) ans.set_cwd(rest)
elif cmd == 'title':
ans.set_next_title(rest)
else: else:
raise ValueError('Unknown command in session file: {}'.format(cmd)) raise ValueError('Unknown command in session file: {}'.format(cmd))
for t in ans.tabs: for t in ans.tabs:
if not t.windows: if not t.windows:
t.windows.append([shell_path]) t.windows.append(resolved_shell(opts))
return ans return ans
def create_session(opts, args=None, special_window=None): def create_session(opts, args=None, special_window=None, cwd_from=None):
if args and args.session: if args and args.session:
with open(args.session) as f: with open(args.session) as f:
return parse_session(f.read(), opts) return parse_session(f.read(), opts)
@@ -99,12 +117,11 @@ def create_session(opts, args=None, special_window=None):
ans.add_tab(opts) ans.add_tab(opts)
ans.tabs[-1].layout = current_layout ans.tabs[-1].layout = current_layout
if special_window is None: if special_window is None:
cmd = args.args if args and args.args else [shell_path] cmd = args.args if args and args.args else resolved_shell(opts)
from kitty.tabs import SpecialWindow
k = {'cwd_from': cwd_from}
if getattr(args, 'title', None): if getattr(args, 'title', None):
from kitty.tabs import SpecialWindow k['override_title'] = args.title
ans.add_window(SpecialWindow(cmd, override_title=args.title)) special_window = SpecialWindow(cmd, **k)
else: ans.add_special_window(special_window)
ans.add_window(cmd)
else:
ans.add_special_window(special_window)
return ans return ans

View File

@@ -201,6 +201,14 @@ create_graphics_vao() {
return vao_idx; return vao_idx;
} }
struct CellUniformData {
bool constants_set;
GLint gploc, gpploc, cploc, cfploc;
GLfloat prev_inactive_text_alpha;
};
static struct CellUniformData cell_uniform_data = {0, .prev_inactive_text_alpha=-1};
static inline void static inline void
cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted) { cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, CursorRenderInfo *cursor, bool inverted) {
struct CellRenderData { struct CellRenderData {
@@ -220,7 +228,7 @@ cell_update_uniform_block(ssize_t vao_idx, Screen *screen, int uniform_buffer, G
copy_color_table_to_buffer(screen->color_profile, (GLuint*)rd, cell_program_layouts[CELL_PROGRAM].color_table.offset / sizeof(GLuint), cell_program_layouts[CELL_PROGRAM].color_table.stride / sizeof(GLuint)); copy_color_table_to_buffer(screen->color_profile, (GLuint*)rd, cell_program_layouts[CELL_PROGRAM].color_table.offset / sizeof(GLuint), cell_program_layouts[CELL_PROGRAM].color_table.stride / sizeof(GLuint));
} }
// Cursor position // Cursor position
if (cursor->is_visible && cursor->shape == CURSOR_BLOCK) { if (cursor->is_visible && cursor->shape == CURSOR_BLOCK && cursor->is_focused) {
rd->cursor_x = screen->cursor->x, rd->cursor_y = screen->cursor->y; rd->cursor_x = screen->cursor->x, rd->cursor_y = screen->cursor->y;
} else { } else {
rd->cursor_x = screen->columns, rd->cursor_y = screen->lines; rd->cursor_x = screen->columns, rd->cursor_y = screen->lines;
@@ -284,12 +292,6 @@ static void
draw_graphics(int program, ssize_t vao_idx, ssize_t gvao_idx, ImageRenderData *data, GLuint start, GLuint count) { draw_graphics(int program, ssize_t vao_idx, ssize_t gvao_idx, ImageRenderData *data, GLuint start, GLuint count) {
bind_program(program); bind_program(program);
bind_vertex_array(gvao_idx); bind_vertex_array(gvao_idx);
static bool graphics_constants_set = false;
if (!graphics_constants_set) {
glUniform1i(glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "image"), GRAPHICS_UNIT);
glUniform1i(glGetUniformLocation(program_id(GRAPHICS_PREMULT_PROGRAM), "image"), GRAPHICS_UNIT);
graphics_constants_set = true;
}
glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT); glActiveTexture(GL_TEXTURE0 + GRAPHICS_UNIT);
GLuint base = 4 * start; GLuint base = 4 * start;
@@ -389,8 +391,29 @@ draw_cells_interleaved_premult(ssize_t vao_idx, ssize_t gvao_idx, Screen *screen
glDisable(GL_SCISSOR_TEST); glDisable(GL_SCISSOR_TEST);
} }
static inline void
set_cell_uniforms(float current_inactive_text_alpha) {
if (!cell_uniform_data.constants_set) {
cell_uniform_data.gploc = glGetUniformLocation(program_id(GRAPHICS_PROGRAM), "inactive_text_alpha");
cell_uniform_data.gpploc = glGetUniformLocation(program_id(GRAPHICS_PREMULT_PROGRAM), "inactive_text_alpha");
cell_uniform_data.cploc = glGetUniformLocation(program_id(CELL_PROGRAM), "inactive_text_alpha");
cell_uniform_data.cfploc = glGetUniformLocation(program_id(CELL_FG_PROGRAM), "inactive_text_alpha");
#define S(prog, name, val) { bind_program(prog); glUniform1i(glGetUniformLocation(program_id(prog), #name), val); }
S(GRAPHICS_PROGRAM, image, GRAPHICS_UNIT); S(GRAPHICS_PREMULT_PROGRAM, image, GRAPHICS_UNIT);
S(CELL_PROGRAM, sprites, SPRITE_MAP_UNIT); S(CELL_FG_PROGRAM, sprites, SPRITE_MAP_UNIT);
#undef S
cell_uniform_data.constants_set = true;
}
if (current_inactive_text_alpha != cell_uniform_data.prev_inactive_text_alpha) {
cell_uniform_data.prev_inactive_text_alpha = current_inactive_text_alpha;
#define S(prog, loc) { bind_program(prog); glUniform1f(cell_uniform_data.loc, current_inactive_text_alpha); }
S(CELL_PROGRAM, cploc); S(CELL_FG_PROGRAM, cfploc); S(GRAPHICS_PROGRAM, gploc); S(GRAPHICS_PREMULT_PROGRAM, gpploc);
#undef S
}
}
void void
draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window) { draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GLfloat dx, GLfloat dy, Screen *screen, OSWindow *os_window, bool is_active_window) {
if (os_window->clear_count < 2) { if (os_window->clear_count < 2) {
os_window->clear_count++; os_window->clear_count++;
#define C(shift) (((GLfloat)((OPT(background) >> shift) & 0xFF)) / 255.0f) #define C(shift) (((GLfloat)((OPT(background) >> shift) & 0xFF)) / 255.0f)
@@ -400,6 +423,8 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
} }
cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy); cell_prepare_to_render(vao_idx, gvao_idx, screen, xstart, ystart, dx, dy);
float current_inactive_text_alpha = screen->cursor_render_info.is_focused && is_active_window ? 1.0 : OPT(inactive_text_alpha);
set_cell_uniforms(current_inactive_text_alpha);
GLfloat w = (GLfloat)screen->columns * dx, h = (GLfloat)screen->lines * dy; GLfloat w = (GLfloat)screen->columns * dx, h = (GLfloat)screen->lines * dy;
#define SCALE(w, x) ((GLfloat)(os_window->viewport_##w) * (GLfloat)(x)) #define SCALE(w, x) ((GLfloat)(os_window->viewport_##w) * (GLfloat)(x))
glScissor( glScissor(
@@ -409,13 +434,6 @@ draw_cells(ssize_t vao_idx, ssize_t gvao_idx, GLfloat xstart, GLfloat ystart, GL
(GLsizei)(ceilf(SCALE(height, h / 2.0f))) (GLsizei)(ceilf(SCALE(height, h / 2.0f)))
); );
#undef SCALE #undef SCALE
static bool cell_constants_set = false;
if (!cell_constants_set) {
bind_program(CELL_PROGRAM);
glUniform1i(glGetUniformLocation(program_id(CELL_PROGRAM), "sprites"), SPRITE_MAP_UNIT);
glUniform1i(glGetUniformLocation(program_id(CELL_FG_PROGRAM), "sprites"), SPRITE_MAP_UNIT);
cell_constants_set = true;
}
if (os_window->is_semi_transparent) { if (os_window->is_semi_transparent) {
if (screen->grman->count) draw_cells_interleaved_premult(vao_idx, gvao_idx, screen, os_window); if (screen->grman->count) draw_cells_interleaved_premult(vao_idx, gvao_idx, screen, os_window);
else draw_cells_simple(vao_idx, gvao_idx, screen); else draw_cells_simple(vao_idx, gvao_idx, screen);

View File

@@ -242,6 +242,31 @@ add_borders_rect(id_type os_window_id, id_type tab_id, uint32_t left, uint32_t t
} }
void
os_window_regions(OSWindow *os_window, Region *central, Region *tab_bar) {
if (os_window->num_tabs > 1) {
switch(OPT(tab_bar_edge)) {
case TOP_EDGE:
central->left = 0; central->top = global_state.cell_height; central->right = os_window->viewport_width - 1;
central->bottom = os_window->viewport_height - 1;
tab_bar->left = central->left; tab_bar->right = central->right; tab_bar->top = 0;
tab_bar->bottom = central->top - 1;
break;
default:
central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1;
central->bottom = os_window->viewport_height - global_state.cell_height - 1;
tab_bar->left = central->left; tab_bar->right = central->right; tab_bar->top = central->bottom + 1;
tab_bar->bottom = os_window->viewport_height - 1;
break;
}
} else {
memset(tab_bar, 0, sizeof(Region));
central->left = 0; central->top = 0; central->right = os_window->viewport_width - 1;
central->bottom = os_window->viewport_height - 1;
}
}
// Python API {{{ // Python API {{{
#define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self) #define PYWRAP0(name) static PyObject* py##name(PyObject UNUSED *self)
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args) #define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
@@ -311,12 +336,15 @@ PYWRAP1(set_options) {
S(cursor_blink_interval, PyFloat_AsDouble); S(cursor_blink_interval, PyFloat_AsDouble);
S(cursor_stop_blinking_after, PyFloat_AsDouble); S(cursor_stop_blinking_after, PyFloat_AsDouble);
S(background_opacity, PyFloat_AsDouble); S(background_opacity, PyFloat_AsDouble);
S(inactive_text_alpha, PyFloat_AsDouble);
S(cursor_shape, PyLong_AsLong); S(cursor_shape, PyLong_AsLong);
S(url_style, PyLong_AsUnsignedLong); S(url_style, PyLong_AsUnsignedLong);
S(x11_bell_volume, PyLong_AsLong); S(x11_bell_volume, PyLong_AsLong);
S(tab_bar_edge, PyLong_AsLong);
S(mouse_hide_wait, PyFloat_AsDouble); S(mouse_hide_wait, PyFloat_AsDouble);
S(wheel_scroll_multiplier, PyFloat_AsDouble); S(wheel_scroll_multiplier, PyFloat_AsDouble);
S(open_url_modifiers, PyLong_AsUnsignedLong); S(open_url_modifiers, PyLong_AsUnsignedLong);
S(rectangle_select_modifiers, PyLong_AsUnsignedLong);
S(click_interval, PyFloat_AsDouble); S(click_interval, PyFloat_AsDouble);
S(url_color, color_as_int); S(url_color, color_as_int);
S(background, color_as_int); S(background, color_as_int);
@@ -362,15 +390,47 @@ PYWRAP1(set_tab_bar_render_data) {
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static PyTypeObject RegionType;
static PyStructSequence_Field region_fields[] = {
{"left", ""}, {"top", ""}, {"right", ""}, {"bottom", ""}, {"width", ""}, {"height", ""}, {NULL, NULL}
};
static PyStructSequence_Desc region_desc = {"Region", NULL, region_fields, 6};
static inline PyObject*
wrap_region(Region *r) {
PyObject *ans = PyStructSequence_New(&RegionType);
if (ans) {
PyStructSequence_SET_ITEM(ans, 0, PyLong_FromUnsignedLong(r->left));
PyStructSequence_SET_ITEM(ans, 1, PyLong_FromUnsignedLong(r->top));
PyStructSequence_SET_ITEM(ans, 2, PyLong_FromUnsignedLong(r->right));
PyStructSequence_SET_ITEM(ans, 3, PyLong_FromUnsignedLong(r->bottom));
PyStructSequence_SET_ITEM(ans, 4, PyLong_FromUnsignedLong(r->right - r->left + 1));
PyStructSequence_SET_ITEM(ans, 5, PyLong_FromUnsignedLong(r->bottom - r->top + 1));
}
return ans;
}
PYWRAP1(viewport_for_window) { PYWRAP1(viewport_for_window) {
id_type os_window_id = 0; id_type os_window_id = 0;
int vw = 100, vh = 100;
PA("|K", &os_window_id); PA("|K", &os_window_id);
Region central = {0}, tab_bar = {0};
WITH_OS_WINDOW(os_window_id) WITH_OS_WINDOW(os_window_id)
int available_height = os_window->viewport_height; os_window_regions(os_window, &central, &tab_bar);
if (os_window->num_tabs > 1) available_height -= global_state.cell_height; vw = os_window->viewport_width; vh = os_window->viewport_height;
return Py_BuildValue("iiiII", os_window->viewport_width, os_window->viewport_height, available_height, global_state.cell_width, global_state.cell_height); goto end;
END_WITH_OS_WINDOW END_WITH_OS_WINDOW
return Py_BuildValue("iiII", 400, 400, global_state.cell_width, global_state.cell_height); end:
return Py_BuildValue("NNiiII", wrap_region(&central), wrap_region(&tab_bar), vw, vh, global_state.cell_width, global_state.cell_height);
}
PYWRAP1(set_dpi_from_os_window) {
id_type os_window_id = PyLong_AsUnsignedLongLong(args);
WITH_OS_WINDOW(os_window_id)
set_dpi_from_os_window(os_window);
Py_RETURN_TRUE;
END_WITH_OS_WINDOW
Py_RETURN_FALSE;
} }
PYWRAP1(mark_os_window_for_close) { PYWRAP1(mark_os_window_for_close) {
@@ -476,6 +536,7 @@ static PyMethodDef module_methods[] = {
MW(set_logical_dpi, METH_VARARGS), MW(set_logical_dpi, METH_VARARGS),
MW(pt_to_px, METH_O), MW(pt_to_px, METH_O),
MW(pt_to_px_ceil, METH_O), MW(pt_to_px_ceil, METH_O),
MW(set_dpi_from_os_window, METH_O),
MW(add_tab, METH_O), MW(add_tab, METH_O),
MW(add_window, METH_VARARGS), MW(add_window, METH_VARARGS),
MW(update_window_title, METH_VARARGS), MW(update_window_title, METH_VARARGS),
@@ -502,6 +563,9 @@ bool
init_state(PyObject *module) { init_state(PyObject *module) {
global_state.cell_width = 1; global_state.cell_height = 1; global_state.cell_width = 1; global_state.cell_height = 1;
if (PyModule_AddFunctions(module, module_methods) != 0) return false; if (PyModule_AddFunctions(module, module_methods) != 0) return false;
if (PyStructSequence_InitType2(&RegionType, &region_desc) != 0) return false;
Py_INCREF((PyObject *) &RegionType);
PyModule_AddObject(module, "Region", (PyObject *) &RegionType);
return true; return true;
} }
// }}} // }}}

View File

@@ -10,11 +10,14 @@
#define OPT(name) global_state.opts.name #define OPT(name) global_state.opts.name
typedef enum { LEFT_EDGE, TOP_EDGE, RIGHT_EDGE, BOTTOM_EDGE } Edge;
typedef struct { typedef struct {
double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier; double visual_bell_duration, cursor_blink_interval, cursor_stop_blinking_after, mouse_hide_wait, click_interval, wheel_scroll_multiplier;
bool enable_audio_bell; bool enable_audio_bell;
CursorShape cursor_shape; CursorShape cursor_shape;
unsigned int open_url_modifiers; unsigned int open_url_modifiers;
unsigned int rectangle_select_modifiers;
unsigned int url_style; unsigned int url_style;
char_type select_by_word_characters[256]; size_t select_by_word_characters_count; char_type select_by_word_characters[256]; size_t select_by_word_characters_count;
color_type url_color, background; color_type url_color, background;
@@ -26,6 +29,8 @@ typedef struct {
int x11_bell_volume; int x11_bell_volume;
float adjust_line_height_frac; float adjust_line_height_frac;
float background_opacity; float background_opacity;
float inactive_text_alpha;
Edge tab_bar_edge;
} Options; } Options;
typedef struct { typedef struct {
@@ -105,6 +110,7 @@ typedef struct {
double last_resize_at; double last_resize_at;
bool has_pending_resizes; bool has_pending_resizes;
bool is_semi_transparent; bool is_semi_transparent;
bool shown_once;
uint32_t offscreen_texture_id; uint32_t offscreen_texture_id;
unsigned int clear_count; unsigned int clear_count;
} OSWindow; } OSWindow;
@@ -146,6 +152,7 @@ void mark_os_window_for_close(OSWindow* w, bool yes);
void update_os_window_viewport(OSWindow *window, bool); void update_os_window_viewport(OSWindow *window, bool);
bool should_os_window_close(OSWindow* w); bool should_os_window_close(OSWindow* w);
bool should_os_window_be_rendered(OSWindow* w); bool should_os_window_be_rendered(OSWindow* w);
void set_dpi_from_os_window(OSWindow *w);
void wakeup_main_loop(); void wakeup_main_loop();
void event_loop_wait(double timeout); void event_loop_wait(double timeout);
void swap_window_buffers(OSWindow *w); void swap_window_buffers(OSWindow *w);
@@ -156,12 +163,13 @@ void set_os_window_title(OSWindow *w, const char *title);
OSWindow* os_window_for_kitty_window(id_type); OSWindow* os_window_for_kitty_window(id_type);
OSWindow* add_os_window(); OSWindow* add_os_window();
OSWindow* current_os_window(); OSWindow* current_os_window();
void os_window_regions(OSWindow*, Region *main, Region *tab_bar);
bool drag_scroll(Window *, OSWindow*); bool drag_scroll(Window *, OSWindow*);
void draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height); void draw_borders(ssize_t vao_idx, unsigned int num_border_rects, BorderRect *rect_buf, bool rect_data_is_dirty, uint32_t viewport_width, uint32_t viewport_height);
ssize_t create_cell_vao(); ssize_t create_cell_vao();
ssize_t create_graphics_vao(); ssize_t create_graphics_vao();
ssize_t create_border_vao(); ssize_t create_border_vao();
void draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *); void draw_cells(ssize_t, ssize_t, float, float, float, float, Screen *, OSWindow *, bool);
void draw_cursor(CursorRenderInfo *, bool); void draw_cursor(CursorRenderInfo *, bool);
void update_surface_size(int, int, uint32_t); void update_surface_size(int, int, uint32_t);
void free_texture(uint32_t*); void free_texture(uint32_t*);

View File

@@ -9,29 +9,28 @@ from functools import partial
from .borders import Borders from .borders import Borders
from .child import Child from .child import Child
from .config import build_ansi_color_table from .config import build_ansi_color_table
from .constants import ( from .constants import WindowGeometry, appname, get_boss, is_macos, is_wayland
WindowGeometry, appname, get_boss, is_macos, is_wayland, shell_path
)
from .fast_data_types import ( from .fast_data_types import (
DECAWM, Screen, add_tab, glfw_post_empty_event, remove_tab, remove_window, DECAWM, Screen, add_tab, glfw_post_empty_event, remove_tab, remove_window,
set_active_tab, set_active_window, set_tab_bar_render_data, swap_tabs, set_active_tab, set_active_window, set_tab_bar_render_data, swap_tabs,
swap_windows, viewport_for_window, x11_window_id swap_windows, viewport_for_window, x11_window_id
) )
from .layout import Rect, all_layouts from .layout import Rect, all_layouts
from .session import resolved_shell
from .utils import color_as_int from .utils import color_as_int
from .window import Window, calculate_gl_geometry from .window import Window, calculate_gl_geometry
TabbarData = namedtuple('TabbarData', 'title is_active is_last') TabbarData = namedtuple('TabbarData', 'title is_active is_last')
SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title') SpecialWindowInstance = namedtuple('SpecialWindow', 'cmd stdin override_title cwd_from cwd')
def SpecialWindow(cmd, stdin=None, override_title=None): def SpecialWindow(cmd, stdin=None, override_title=None, cwd_from=None, cwd=None):
return SpecialWindowInstance(cmd, stdin, override_title) return SpecialWindowInstance(cmd, stdin, override_title, cwd_from, cwd)
class Tab: # {{{ class Tab: # {{{
def __init__(self, tab_manager, session_tab=None, special_window=None): def __init__(self, tab_manager, session_tab=None, special_window=None, cwd_from=None):
self.tab_manager_ref = weakref.ref(tab_manager) self.tab_manager_ref = weakref.ref(tab_manager)
self.os_window_id = tab_manager.os_window_id self.os_window_id = tab_manager.os_window_id
self.id = add_tab(self.os_window_id) self.id = add_tab(self.os_window_id)
@@ -50,7 +49,7 @@ class Tab: # {{{
sl = self.enabled_layouts[0] sl = self.enabled_layouts[0]
self.current_layout = all_layouts[sl](self.os_window_id, self.opts, self.borders.border_width, self.windows) self.current_layout = all_layouts[sl](self.os_window_id, self.opts, self.borders.border_width, self.windows)
if special_window is None: if special_window is None:
self.new_window() self.new_window(cwd_from=cwd_from)
else: else:
self.new_special_window(special_window) self.new_special_window(special_window)
else: else:
@@ -75,6 +74,12 @@ class Tab: # {{{
def title(self): def title(self):
return getattr(self.active_window, 'title', appname) return getattr(self.active_window, 'title', appname)
def set_title(self, title):
self.name = title or ''
tm = self.tab_manager_ref()
if tm is not None:
tm.title_changed(self.name)
def title_changed(self, window): def title_changed(self, window):
if window is self.active_window: if window is self.active_window:
tm = self.tab_manager_ref() tm = self.tab_manager_ref()
@@ -109,12 +114,12 @@ class Tab: # {{{
w.set_visible_in_layout(i, True) w.set_visible_in_layout(i, True)
self.relayout() self.relayout()
def launch_child(self, use_shell=False, cmd=None, stdin=None): def launch_child(self, use_shell=False, cmd=None, stdin=None, cwd_from=None, cwd=None):
if cmd is None: if cmd is None:
if use_shell: if use_shell:
cmd = [shell_path] cmd = resolved_shell(self.opts)
else: else:
cmd = self.args.args or [shell_path] cmd = self.args.args or resolved_shell(self.opts)
env = {} env = {}
if not is_macos and not is_wayland: if not is_macos and not is_wayland:
try: try:
@@ -122,12 +127,12 @@ class Tab: # {{{
except Exception: except Exception:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
ans = Child(cmd, self.cwd, self.opts, stdin, env) ans = Child(cmd, cwd or self.cwd, self.opts, stdin, env, cwd_from)
ans.fork() ans.fork()
return ans return ans
def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None): def new_window(self, use_shell=True, cmd=None, stdin=None, override_title=None, cwd_from=None, cwd=None):
child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin) child = self.launch_child(use_shell=use_shell, cmd=cmd, stdin=stdin, cwd_from=cwd_from, cwd=cwd)
window = Window(self, child, self.opts, self.args, override_title=override_title) window = Window(self, child, self.opts, self.args, override_title=override_title)
# Must add child before laying out so that resize_pty succeeds # Must add child before laying out so that resize_pty succeeds
get_boss().add_child(window) get_boss().add_child(window)
@@ -138,7 +143,7 @@ class Tab: # {{{
return window return window
def new_special_window(self, special_window): def new_special_window(self, special_window):
self.new_window(False, *special_window) return self.new_window(False, *special_window)
def close_window(self): def close_window(self):
if self.windows: if self.windows:
@@ -202,6 +207,17 @@ class Tab: # {{{
def move_window_backward(self): def move_window_backward(self):
self.move_window(-1) self.move_window(-1)
def list_windows(self):
for w in self:
yield w.as_dict()
def matches(self, field, pat):
if field == 'id':
return pat.pattern == str(self.id)
if field == 'title':
return pat.search(self.name or self.title) is not None
return False
def __iter__(self): def __iter__(self):
yield from iter(self.windows) yield from iter(self.windows)
@@ -228,7 +244,7 @@ class TabBar: # {{{
self.num_tabs = 1 self.num_tabs = 1
self.cell_width = 1 self.cell_width = 1
self.data_buffer_size = 0 self.data_buffer_size = 0
self.layout_changed = None self.laid_out_once = False
self.dirty = True self.dirty = True
self.screen = s = Screen(None, 1, 10) self.screen = s = Screen(None, 1, 10)
s.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) s.color_profile.update_ansi_color_table(build_ansi_color_table(opts))
@@ -255,25 +271,29 @@ class TabBar: # {{{
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))
def layout(self, viewport_width, viewport_height, cell_width, cell_height): def layout(self):
central, tab_bar, vw, vh, cell_width, cell_height = viewport_for_window(self.os_window_id)
if tab_bar.width < 2:
return
self.cell_width = cell_width self.cell_width = cell_width
s = self.screen s = self.screen
viewport_width = tab_bar.width
ncells = viewport_width // cell_width ncells = viewport_width // cell_width
s.resize(1, ncells) s.resize(1, ncells)
s.reset_mode(DECAWM) s.reset_mode(DECAWM)
self.layout_changed = True self.laid_out_once = True
margin = (viewport_width - ncells * cell_width) // 2 margin = (viewport_width - ncells * cell_width) // 2
self.window_geometry = g = WindowGeometry( self.window_geometry = g = WindowGeometry(
margin, viewport_height - cell_height, viewport_width - margin, viewport_height, s.columns, s.lines) margin, tab_bar.top, viewport_width - margin, tab_bar.bottom, s.columns, s.lines)
if margin > 0: if margin > 0:
self.tab_bar_blank_rects = (Rect(0, g.top, g.left, g.bottom), Rect(g.right - 1, g.top, viewport_width, g.bottom)) self.blank_rects = (Rect(0, g.top, g.left, g.bottom + 1), Rect(g.right - 1, g.top, viewport_width, g.bottom + 1))
else: else:
self.tab_bar_blank_rects = () self.blank_rects = ()
self.screen_geometry = sg = calculate_gl_geometry(g, viewport_width, viewport_height, cell_width, cell_height) self.screen_geometry = sg = calculate_gl_geometry(g, vw, vh, cell_width, cell_height)
set_tab_bar_render_data(self.os_window_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen) set_tab_bar_render_data(self.os_window_id, sg.xstart, sg.ystart, sg.dx, sg.dy, self.screen)
def update(self, data): def update(self, data):
if self.layout_changed is None: if not self.laid_out_once:
return return
s = self.screen s = self.screen
s.cursor.x = 0 s.cursor.x = 0
@@ -321,31 +341,35 @@ class TabManager: # {{{
self.opts, self.args = opts, args self.opts, self.args = opts, args
self.tabs = [] self.tabs = []
self.tab_bar = TabBar(self.os_window_id, opts) self.tab_bar = TabBar(self.os_window_id, opts)
self.tab_bar.layout(*self.tab_bar_layout_data)
self.active_tab_idx = 0 self.active_tab_idx = 0
for t in startup_session.tabs: for t in startup_session.tabs:
self._add_tab(Tab(self, session_tab=t)) self._add_tab(Tab(self, session_tab=t))
self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1))) self._set_active_tab(max(0, min(startup_session.active_tab_idx, len(self.tabs) - 1)))
if len(self.tabs) > 1:
self.tabbar_visibility_changed()
self.update_tab_bar() self.update_tab_bar()
def refresh_sprite_positions(self): def refresh_sprite_positions(self):
self.tab_bar.screen.refresh_sprite_positions() self.tab_bar.screen.refresh_sprite_positions()
def _add_tab(self, tab): def _add_tab(self, tab):
before = len(self.tabs)
self.tabs.append(tab) self.tabs.append(tab)
if len(self.tabs) > 1 and before < 2:
self.tabbar_visibility_changed()
def _remove_tab(self, tab): def _remove_tab(self, tab):
before = len(self.tabs)
remove_tab(self.os_window_id, tab.id) remove_tab(self.os_window_id, tab.id)
self.tabs.remove(tab) self.tabs.remove(tab)
if len(self.tabs) < 2 and before > 1:
self.tabbar_visibility_changed()
def _set_active_tab(self, idx): def _set_active_tab(self, idx):
self.active_tab_idx = idx self.active_tab_idx = idx
set_active_tab(self.os_window_id, idx) set_active_tab(self.os_window_id, idx)
def tabbar_visibility_changed(self): def tabbar_visibility_changed(self):
self.tab_bar.layout()
self.resize(only_tabs=True) self.resize(only_tabs=True)
glfw_post_empty_event() glfw_post_empty_event()
@@ -355,23 +379,30 @@ class TabManager: # {{{
def resize(self, only_tabs=False): def resize(self, only_tabs=False):
if not only_tabs: if not only_tabs:
self.tab_bar.layout(*self.tab_bar_layout_data) self.tab_bar.layout()
self.update_tab_bar() self.update_tab_bar()
for tab in self.tabs: for tab in self.tabs:
tab.relayout() tab.relayout()
def set_active_tab(self, idx): def set_active_tab_idx(self, idx):
self._set_active_tab(idx) self._set_active_tab(idx)
self.active_tab.relayout_borders() self.active_tab.relayout_borders()
self.update_tab_bar() self.update_tab_bar()
def set_active_tab(self, tab):
try:
idx = self.tabs.index(tab)
except Exception:
return
self.set_active_tab_idx(idx)
def next_tab(self, delta=1): def next_tab(self, delta=1):
if len(self.tabs) > 1: if len(self.tabs) > 1:
self.set_active_tab((self.active_tab_idx + len(self.tabs) + delta) % len(self.tabs)) self.set_active_tab_idx((self.active_tab_idx + len(self.tabs) + delta) % len(self.tabs))
def goto_tab(self, tab_num): def goto_tab(self, tab_num):
if tab_num < len(self.tabs) and 0 <= tab_num: if tab_num < len(self.tabs) and 0 <= tab_num:
self.set_active_tab(tab_num) self.set_active_tab_idx(tab_num)
def __iter__(self): def __iter__(self):
return iter(self.tabs) return iter(self.tabs)
@@ -379,6 +410,14 @@ class TabManager: # {{{
def __len__(self): def __len__(self):
return len(self.tabs) return len(self.tabs)
def list_tabs(self):
for tab in self:
yield {
'id': tab.id,
'title': tab.name or tab.title,
'windows': list(tab.list_windows()),
}
@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
@@ -401,28 +440,17 @@ class TabManager: # {{{
def title_changed(self, new_title): def title_changed(self, new_title):
self.update_tab_bar() self.update_tab_bar()
def new_tab(self, special_window=None): def new_tab(self, special_window=None, cwd_from=None):
needs_resize = len(self.tabs) == 1
idx = len(self.tabs) idx = len(self.tabs)
self._add_tab(Tab(self, special_window=special_window)) self._add_tab(Tab(self, special_window=special_window, cwd_from=cwd_from))
self._set_active_tab(idx) self._set_active_tab(idx)
self.update_tab_bar() self.update_tab_bar()
if needs_resize:
self.tabbar_visibility_changed()
def remove(self, tab): def remove(self, tab):
needs_resize = len(self.tabs) == 2
self._remove_tab(tab) self._remove_tab(tab)
self._set_active_tab(max(0, min(self.active_tab_idx, len(self.tabs) - 1))) self._set_active_tab(max(0, min(self.active_tab_idx, len(self.tabs) - 1)))
self.update_tab_bar() self.update_tab_bar()
tab.destroy() tab.destroy()
if needs_resize:
self.tabbar_visibility_changed()
@property
def tab_bar_layout_data(self):
vw, vh, ah, cw, ch = viewport_for_window(self.os_window_id)
return vw, vh, cw, ch
@property @property
def tab_bar_data(self): def tab_bar_data(self):
@@ -436,7 +464,7 @@ class TabManager: # {{{
def activate_tab_at(self, x): def activate_tab_at(self, x):
i = self.tab_bar.tab_at(x) i = self.tab_bar.tab_at(x)
if i is not None: if i is not None:
self.set_active_tab(i) self.set_active_tab_idx(i)
@property @property
def blank_rects(self): def blank_rects(self):

2606
kitty/unicode-data.c Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,41 +1,24 @@
#pragma once #pragma once
#include "data-types.h"
#include <unictype.h> bool is_combining_char(char_type ch);
#include <uninorm.h> bool is_ignored_char(char_type ch);
bool is_word_char(char_type ch);
static inline bool bool is_CZ_category(char_type);
is_combining_char(uint32_t ch) { bool is_P_category(char_type);
return uc_combining_class(ch) != UC_CCC_NR; char_type codepoint_for_mark(combining_type m);
} combining_type mark_for_codepoint(char_type c);
static inline bool
is_ignored_char(uint32_t ch) {
return uc_is_general_category_withtable(ch, UC_CATEGORY_MASK_Cc | UC_CATEGORY_MASK_Cf | UC_CATEGORY_MASK_Cs);
}
static inline bool
is_word_char(uint32_t ch) {
return uc_is_general_category_withtable(ch, UC_CATEGORY_MASK_L | UC_CATEGORY_MASK_N);
}
static inline bool static inline bool
is_url_char(uint32_t ch) { is_url_char(uint32_t ch) {
return ch && !uc_is_general_category_withtable(ch, UC_CATEGORY_MASK_C | UC_CATEGORY_MASK_Z); return ch && !is_CZ_category(ch);
}
static inline uint32_t
normalize(uint32_t ch, uint32_t cc1, uint32_t cc2) {
uint32_t ans = uc_composition(ch, cc1);
if (ans && cc2) ans = uc_composition(ans, cc2);
return ans;
} }
static inline bool static inline bool
can_strip_from_end_of_url(uint32_t ch) { can_strip_from_end_of_url(uint32_t ch) {
// remove trailing punctuation // remove trailing punctuation
return ( return (
(uc_is_general_category_withtable(ch, UC_CATEGORY_MASK_P) && ch != '/') || (is_P_category(ch) && ch != '/') ||
ch == '>' ch == '>'
) ? true : false; ) ? true : false;
} }

View File

@@ -12,6 +12,7 @@ import shlex
import socket import socket
import string import string
import subprocess import subprocess
import sys
import tempfile import tempfile
from contextlib import contextmanager from contextlib import contextmanager
from functools import lru_cache from functools import lru_cache
@@ -266,7 +267,34 @@ def single_instance(group_id=None):
return True return True
def encode_wm_class(name, cls, title=appname): @contextmanager
if is_macos: def non_blocking_read(src=sys.stdin):
return title import termios
return '\x01' + (name or cls) + '\x1e' + cls + '\x1e' + title import tty
import fcntl
fd = src.fileno()
if src.isatty():
old = termios.tcgetattr(fd)
tty.setraw(fd)
oldfl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
yield fd
if src.isatty():
termios.tcsetattr(fd, termios.TCSADRAIN, old)
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl)
def read_with_timeout(more_needed, timeout=10, src=sys.stdin):
import select
start_time = monotonic()
with non_blocking_read(src) as fd:
while timeout > monotonic() - start_time:
rd = select.select([fd], [], [], max(0, timeout - (monotonic() - start_time)))[0]
if rd:
data = sys.stdin.buffer.read()
if not data:
break # eof
if not more_needed(data):
break
else:
break

2619
kitty/wcwidth-std.h generated Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@
# 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 sys import sys
import weakref import weakref
from collections import deque from collections import deque
@@ -87,7 +88,7 @@ class Window:
def __init__(self, tab, child, opts, args, override_title=None): def __init__(self, tab, child, opts, args, override_title=None):
self.override_title = override_title self.override_title = override_title
self.title = self.override_title or appname self.child_title = appname
self.id = add_window(tab.os_window_id, tab.id, self.title) self.id = add_window(tab.os_window_id, tab.id, self.title)
if not self.id: if not self.id:
raise Exception('No tab with id: {} in OS Window: {} was found, or the window counter wrapped'.format(tab.id, tab.os_window_id)) raise Exception('No tab with id: {} in OS Window: {} was found, or the window counter wrapped'.format(tab.id, tab.os_window_id))
@@ -103,9 +104,37 @@ class Window:
self.screen = Screen(self, 24, 80, opts.scrollback_lines, self.id) self.screen = Screen(self, 24, 80, opts.scrollback_lines, self.id)
setup_colors(self.screen, opts) setup_colors(self.screen, opts)
@property
def title(self):
return self.override_title or self.child_title
def __repr__(self): def __repr__(self):
return 'Window(title={}, id={})'.format(self.title, self.id) return 'Window(title={}, id={})'.format(self.title, self.id)
def as_dict(self):
return dict(
id=self.id,
title=self.override_title or self.title,
pid=self.child.pid,
cwd=self.child.current_cwd or self.child.cwd, cmdline=self.child.cmdline
)
def matches(self, field, pat):
if field == 'id':
return pat.pattern == str(self.id)
if field == 'pid':
return pat.pattern == str(self.child.pid)
if field == 'title':
return pat.search(self.override_title or self.title) is not None
if field in 'cwd':
return pat.search(self.child.current_cwd or self.child.cwd) is not None
if field == 'cmdline':
for x in self.child.cmdline:
if pat.search(x) is not None:
return True
return False
return False
def set_visible_in_layout(self, window_idx, val): def set_visible_in_layout(self, window_idx, val):
val = bool(val) val = bool(val)
if val is not self.is_visible_in_layout: if val is not self.is_visible_in_layout:
@@ -119,7 +148,7 @@ class Window:
wakeup() wakeup()
def update_position(self, window_geometry): def update_position(self, window_geometry):
vw, vh, ah, cw, ch = viewport_for_window(self.os_window_id) central, tab_bar, vw, vh, cw, ch = viewport_for_window(self.os_window_id)
self.screen_geometry = sg = calculate_gl_geometry(window_geometry, vw, vh, cw, ch) self.screen_geometry = sg = calculate_gl_geometry(window_geometry, vw, vh, cw, ch)
return sg return sg
@@ -163,6 +192,17 @@ class Window:
if get_boss().child_monitor.needs_write(self.id, data) is not True: if get_boss().child_monitor.needs_write(self.id, data) is not True:
print('Failed to write to child %d as it does not exist' % self.id, file=sys.stderr) print('Failed to write to child %d as it does not exist' % self.id, file=sys.stderr)
def title_updated(self):
update_window_title(self.os_window_id, self.tab_id, self.id, self.title)
t = self.tabref()
if t is not None:
t.title_changed(self)
glfw_post_empty_event()
def set_title(self, title):
self.override_title = title or None
self.title_updated()
# screen callbacks {{{ # screen callbacks {{{
def use_utf8(self, on): def use_utf8(self, on):
get_boss().child_monitor.set_iutf8(self.window_id, on) get_boss().child_monitor.set_iutf8(self.window_id, on)
@@ -176,13 +216,9 @@ class Window:
self.screen.send_escape_code_to_child(CSI, 'O') self.screen.send_escape_code_to_child(CSI, 'O')
def title_changed(self, new_title): def title_changed(self, new_title):
self.child_title = sanitize_title(new_title or appname)
if self.override_title is None: if self.override_title is None:
self.title = sanitize_title(new_title or appname) self.title_updated()
update_window_title(self.os_window_id, self.tab_id, self.id, self.title)
t = self.tabref()
if t is not None:
t.title_changed(self)
glfw_post_empty_event()
def icon_changed(self, new_icon): def icon_changed(self, new_icon):
pass # TODO: Implement this pass # TODO: Implement this
@@ -258,6 +294,12 @@ class Window:
def request_capabilities(self, q): def request_capabilities(self, q):
self.screen.send_escape_code_to_child(DCS, get_capabilities(q)) self.screen.send_escape_code_to_child(DCS, get_capabilities(q))
def handle_remote_cmd(self, cmd):
get_boss().handle_remote_cmd(cmd, self)
def send_cmd_response(self, response):
self.screen.send_escape_code_to_child(DCS, '@kitty-cmd' + json.dumps(response))
# }}} # }}}
def text_for_selection(self): def text_for_selection(self):
@@ -270,14 +312,20 @@ class Window:
self.screen.reset_callbacks() self.screen.reset_callbacks()
self.screen = None self.screen = None
def buffer_as_ansi(self): def buffer_as_ansi(self, add_history=True):
data = [] data = []
self.screen.historybuf.as_ansi(data.append) if add_history:
self.screen.historybuf.as_ansi(data.append)
self.screen.linebuf.as_ansi(data.append) self.screen.linebuf.as_ansi(data.append)
return ''.join(data) return ''.join(data)
def buffer_as_text(self): def buffer_as_text(self, add_history=True):
return str(self.screen.historybuf) + '\n' + str(self.screen.linebuf) ans = str(self.screen.linebuf).rstrip('\n')
if add_history:
h = str(self.screen.historybuf)
if h.strip():
ans = h + '\n' + ans
return ans
# actions {{{ # actions {{{

View File

@@ -140,7 +140,7 @@ class TestDataTypes(BaseTest):
lb = filled_line_buf(5, 5, filled_cursor()) lb = filled_line_buf(5, 5, filled_cursor())
l0 = lb.line(0) l0 = lb.line(0)
l0.add_combining_char(1, 'a') l0.add_combining_char(1, '\u0300')
l0.clear_text(1, 2) l0.clear_text(1, 2)
self.ae(str(l0), '0 00') self.ae(str(l0), '0 00')
self.assertEqualAttributes(l0.cursor_from(1), l0.cursor_from(0)) self.assertEqualAttributes(l0.cursor_from(1), l0.cursor_from(0))
@@ -164,14 +164,14 @@ class TestDataTypes(BaseTest):
lb.line(0)[lb.xnum] lb.line(0)[lb.xnum]
l0 = lb.line(0) l0 = lb.line(0)
l0.set_text(' ', 0, len(' '), C()) l0.set_text(' ', 0, len(' '), C())
l0.add_combining_char(0, '1') l0.add_combining_char(0, '\u0300')
self.ae(l0[0], ' 1') self.ae(l0[0], ' \u0300')
l0.add_combining_char(0, '2') l0.add_combining_char(0, '\U000e0100')
self.ae(l0[0], ' 12') self.ae(l0[0], ' \u0300\U000e0100')
l0.add_combining_char(0, '3') l0.add_combining_char(0, '\u0302')
self.ae(l0[0], ' 13') self.ae(l0[0], ' \u0300\u0302')
self.ae(l0[1], '\0') self.ae(l0[1], '\0')
self.ae(str(l0), ' 13') self.ae(str(l0), ' \u0300\u0302')
t = 'Testing with simple text' t = 'Testing with simple text'
lb = LineBuf(2, len(t)) lb = LineBuf(2, len(t))
l0 = lb.line(0) l0 = lb.line(0)
@@ -269,6 +269,9 @@ class TestDataTypes(BaseTest):
no_url('http: //acme.com') no_url('http: //acme.com')
no_url('http:/ /acme.com') no_url('http:/ /acme.com')
l4 = create(' xxxxxtekljhgdkjgd')
self.ae(l4.url_end_at(0), 0)
def rewrap(self, lb, lb2): def rewrap(self, lb, lb2):
hb = HistoryBuf(lb2.ynum, lb2.xnum) hb = HistoryBuf(lb2.ynum, lb2.xnum)
cy = lb.rewrap(lb2, hb) cy = lb.rewrap(lb2, hb)

View File

@@ -80,6 +80,7 @@ class Rendering(BaseTest):
self.ae(groups('abcd'), [(1, 1) for i in range(4)]) self.ae(groups('abcd'), [(1, 1) for i in range(4)])
self.ae(groups('A=>>B!=C', path='kitty_tests/FiraCode-Medium.otf'), [(1, 1), (3, 3), (1, 1), (2, 2), (1, 1)]) self.ae(groups('A=>>B!=C', path='kitty_tests/FiraCode-Medium.otf'), [(1, 1), (3, 3), (1, 1), (2, 2), (1, 1)])
self.ae(groups('==!=<>==<><><>', path='kitty_tests/FiraCode-Medium.otf'), [(2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2), (2, 2)])
colon_glyph = shape_string('9:30', path='kitty_tests/FiraCode-Medium.otf')[1][2] colon_glyph = shape_string('9:30', path='kitty_tests/FiraCode-Medium.otf')[1][2]
self.assertNotEqual(colon_glyph, shape_string(':', path='kitty_tests/FiraCode-Medium.otf')[0][2]) self.assertNotEqual(colon_glyph, shape_string(':', path='kitty_tests/FiraCode-Medium.otf')[0][2])
self.ae(colon_glyph, 998) self.ae(colon_glyph, 998)

View File

@@ -78,3 +78,30 @@ class TestParser(BaseTest):
keycode = getattr(defines, 'GLFW_KEY_' + key) keycode = getattr(defines, 'GLFW_KEY_' + key)
base_key = smkx_key_map[keycode] base_key = smkx_key_map[keycode]
km(modify_key_bytes(base_key, num).decode('ascii')[1:], key) km(modify_key_bytes(base_key, num).decode('ascii')[1:], key)
def test_encode_mouse_event(self):
PRESS, RELEASE, DRAG, MOVE = range(4)
NORMAL_PROTOCOL, UTF8_PROTOCOL, SGR_PROTOCOL, URXVT_PROTOCOL = range(4)
L, M, R = defines.GLFW_MOUSE_BUTTON_LEFT, defines.GLFW_MOUSE_BUTTON_MIDDLE, defines.GLFW_MOUSE_BUTTON_RIGHT
protocol = SGR_PROTOCOL
def enc(button=L, action=PRESS, mods=0, x=1, y=1):
return defines.test_encode_mouse(x, y, protocol, button, action, mods)
self.ae(enc(), '<0;1;1M')
self.ae(enc(action=RELEASE), '<0;1;1m')
self.ae(enc(action=MOVE), '<35;1;1M')
self.ae(enc(action=DRAG), '<32;1;1M')
self.ae(enc(R), '<2;1;1M')
self.ae(enc(R, action=RELEASE), '<2;1;1m')
self.ae(enc(R, action=DRAG), '<34;1;1M')
self.ae(enc(M), '<1;1;1M')
self.ae(enc(M, action=RELEASE), '<1;1;1m')
self.ae(enc(M, action=DRAG), '<33;1;1M')
self.ae(enc(x=1234, y=5678), '<0;1234;5678M')
self.ae(enc(mods=defines.GLFW_MOD_SHIFT), '<4;1;1M')
self.ae(enc(mods=defines.GLFW_MOD_ALT), '<8;1;1M')
self.ae(enc(mods=defines.GLFW_MOD_CONTROL), '<16;1;1M')

View File

@@ -367,3 +367,16 @@ class TestScreen(BaseTest):
s.cursor.y = 2 s.cursor.y = 2
s.erase_characters(2) s.erase_characters(2)
self.ae(s.linebuf.dirty_lines(), [0, 1, 2]) self.ae(s.linebuf.dirty_lines(), [0, 1, 2])
def test_selection_as_text(self):
s = self.create_screen()
for i in range(2 * s.lines):
if i != 0:
s.carriage_return(), s.linefeed()
s.draw(str(i) * s.columns)
s.start_selection(0, 0, False)
s.update_selection(4, 4, True)
expected = ('55555', '\n66666', '\n77777', '\n88888', '\n99999')
self.ae(s.text_for_selection(), expected)
s.scroll(2, True)
self.ae(s.text_for_selection(), expected)

13
launcher/kitty Executable file
View File

@@ -0,0 +1,13 @@
#!/usr/bin/env python3
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
import os
import sys
base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, base)
with open(os.path.join(base, '__main__.py')) as f:
src = f.read()
code = compile(src, f.name, 'exec')
exec(code, {'__name__': '__main__'})

View File

@@ -53,8 +53,10 @@ static int run_embedded(const char* exe_dir_, int argc, wchar_t **argv) {
if (kitty == NULL) { fprintf(stderr, "Failed to allocate python kitty lib object\n"); goto end; } if (kitty == NULL) { fprintf(stderr, "Failed to allocate python kitty lib object\n"); goto end; }
PyObject *runpy = PyImport_ImportModule("runpy"); PyObject *runpy = PyImport_ImportModule("runpy");
if (runpy == NULL) { PyErr_Print(); fprintf(stderr, "Unable to import runpy\n"); Py_CLEAR(kitty); goto end; } if (runpy == NULL) { PyErr_Print(); fprintf(stderr, "Unable to import runpy\n"); Py_CLEAR(kitty); goto end; }
PyObject *res = PyObject_CallMethod(runpy, "run_path", "O", kitty); PyObject *run_name = PyUnicode_FromString("__main__");
Py_CLEAR(runpy); Py_CLEAR(kitty); if (run_name == NULL) { fprintf(stderr, "Failed to allocate run_name\n"); goto end; }
PyObject *res = PyObject_CallMethod(runpy, "run_path", "OOO", kitty, Py_None, run_name);
Py_CLEAR(runpy); Py_CLEAR(kitty); Py_CLEAR(run_name);
if (res == NULL) PyErr_Print(); if (res == NULL) PyErr_Print();
else { ret = 0; Py_CLEAR(res); } else { ret = 0; Py_CLEAR(res); }
end: end:
@@ -88,8 +90,12 @@ int main(int argc, char *argv[]) {
wchar_t *argvw[MAX_ARGC + 1] = {0}; wchar_t *argvw[MAX_ARGC + 1] = {0};
#ifdef WITH_PROFILER #ifdef WITH_PROFILER
num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/"); num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/");
#else
#ifdef FOR_LAUNCHER
num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../Frameworks/kitty");
#else #else
num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../lib/kitty"); num = snprintf(lib, PATH_MAX, "%s%s", exe_dir, "/../lib/kitty");
#endif
#endif #endif
if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; } if (num < 0 || num >= PATH_MAX) { fprintf(stderr, "Failed to create path to kitty lib\n"); return 1; }

View File

@@ -23,7 +23,7 @@ if os.path.exists(iconset):
shutil.rmtree(iconset) shutil.rmtree(iconset)
os.mkdir(iconset) os.mkdir(iconset)
os.chdir(iconset) os.chdir(iconset)
for sz in (16, 32, 128, 256, 512, 1024): for sz in (16, 32, 64, 128, 256, 512, 1024):
iname = 'icon_{0}x{0}.png'.format(sz) iname = 'icon_{0}x{0}.png'.format(sz)
iname2x = 'icon_{0}x{0}@2x.png'.format(sz // 2) iname2x = 'icon_{0}x{0}@2x.png'.format(sz // 2)
render(iname, sz) render(iname, sz)

77
remote-control.asciidoc Normal file
View File

@@ -0,0 +1,77 @@
= Controlling kitty from scripts or the shell
kitty can be controlled from scripts or the shell prompt. You can open new
windows, send arbitrary text input to any window, name windows and tabs, etc.
Let's walk through a few examples of controlling kitty.
Start by running kitty as:
kitty -o allow_remote_control=yes --window-layout tall
In order for control to work, `allow_remote_control` must be enabled in
kitty.conf. Here we turn it on explicitly at the command line.
Now, in the new kitty window, enter the command:
kitty @ new-window --title Output --keep-focus cat
This will open a new window, running the ``cat`` program that will appear next
to the current window.
Let's send some text to this new window:
kitty @ send-text --match cmdline:cat Hello, World
This will make `Hello, World` show up in the window running the `cat` program.
The `--match` option is very powerful, it allows selecting windows by their
titles, the command line of the program running in the window, the working
directory of the program running in the window, etc. See `kitty @ send-text
--help` for details.
More usefully, you can pipe the output of a command running in one window to
another window, for example:
ls | kitty @ send-text --match title:Output --stdin
This will show the output of ls in the output window instead of the current
window. You can use this technique to, for example, show the output of running
`make` in your editor in a different window. The possibilities are endless.
You can even have things you type show up in a different window. Run:
kitty @ send-text --match title:Output --stdin
And type some text, it will show up in the output window, instead of the current
window. Type `Ctrl+D` when you are ready to stop.
Now, let's open a new tab.
kitty @ new-window --new-tab --tab-title "My Tab" --keep-focus bash
This will open a new tab running the bash shell with the title "My Tab".
We can change the title of the tab with:
kitty @ set-tab-title --match title:My New Title
Let's change the title of the current tab:
kitty @ set-tab-title Master Tab
Now lets switch to the newly opened tab.
kitty @ focus-window --match title:New
You can get a listing of available tabs and windows, by running:
kitty @ ls
This outputs a tree of data in JSON format. The top level of the tree is all
operating system kitty windows. Each OS window has an id and a list of tabs.
Each tab has its own id, a title and a list of windows. Each window has an id,
title, current working directory, process id (PID) and command-line of the
process running in the window. You can use this information with `--match`
to control individual windows.
As you can see, it is very easy to control kitty using the
`kitty @` messaging system. This tutorial touches only the
surface of what is possible. See `kitty @ --help` for more details.

View File

@@ -12,6 +12,7 @@ import shutil
import subprocess import subprocess
import sys import sys
import sysconfig import sysconfig
import time
base = os.path.dirname(os.path.abspath(__file__)) base = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.join(base, 'glfw')) sys.path.insert(0, os.path.join(base, 'glfw'))
@@ -219,9 +220,7 @@ def kitty_env():
pylib = get_python_flags(cflags) pylib = get_python_flags(cflags)
gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs') gl_libs = ['-framework', 'OpenGL'] if is_macos else pkg_config('gl', '--libs')
libpng = pkg_config('libpng', '--libs') libpng = pkg_config('libpng', '--libs')
ans.ldpaths += pylib + font_libs + gl_libs + libpng + [ ans.ldpaths += pylib + font_libs + gl_libs + libpng
'-lunistring'
]
if is_macos: if is_macos:
ans.ldpaths.extend('-framework Cocoa'.split()) ans.ldpaths.extend('-framework Cocoa'.split())
if is_travis and 'SW' in os.environ: if is_travis and 'SW' in os.environ:
@@ -401,7 +400,12 @@ def compile_glfw(incremental, compilation_database, all_keys):
sources = [os.path.join('glfw', x) for x in genv.sources] sources = [os.path.join('glfw', x) for x in genv.sources]
all_headers = [os.path.join('glfw', x) for x in genv.all_headers] all_headers = [os.path.join('glfw', x) for x in genv.all_headers]
if module == 'wayland': if module == 'wayland':
glfw.build_wayland_protocols(genv, run_tool, emphasis, newer, os.path.join(base, 'glfw')) try:
glfw.build_wayland_protocols(genv, run_tool, emphasis, newer, os.path.join(base, 'glfw'))
except SystemExit as err:
print(err, file=sys.stderr)
print(error('Disabling building of wayland backend'), file=sys.stderr)
continue
compile_c_extension(genv, 'kitty/glfw-' + module, incremental, compilation_database, all_keys, sources, all_headers) compile_c_extension(genv, 'kitty/glfw-' + module, incremental, compilation_database, all_keys, sources, all_headers)
@@ -450,7 +454,7 @@ def build_asan_launcher(args):
run_tool(cmd, desc='Creating {} ...'.format(emphasis('asan-launcher'))) run_tool(cmd, desc='Creating {} ...'.format(emphasis('asan-launcher')))
def build_linux_launcher(args, launcher_dir='.', for_bundle=False): def build_linux_launcher(args, launcher_dir='.', for_bundle=False, sh_launcher=False):
cflags = '-Wall -Werror -fpie'.split() cflags = '-Wall -Werror -fpie'.split()
libs = [] libs = []
if args.profile: if args.profile:
@@ -461,6 +465,8 @@ def build_linux_launcher(args, launcher_dir='.', for_bundle=False):
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()))
elif sh_launcher:
cflags.append('-DFOR_LAUNCHER')
pylib = get_python_flags(cflags) pylib = get_python_flags(cflags)
exe = 'kitty-profile' if args.profile else 'kitty' exe = 'kitty-profile' if args.profile else 'kitty'
cmd = [env.cc] + cflags + [ cmd = [env.cc] + cflags + [
@@ -470,7 +476,7 @@ def build_linux_launcher(args, launcher_dir='.', for_bundle=False):
run_tool(cmd) run_tool(cmd)
def package(args, for_bundle=False): # {{{ def package(args, for_bundle=False, sh_launcher=False): # {{{
ddir = args.prefix ddir = args.prefix
libdir = os.path.join(ddir, 'lib', 'kitty') libdir = os.path.join(ddir, 'lib', 'kitty')
if os.path.exists(libdir): if os.path.exists(libdir):
@@ -499,7 +505,7 @@ 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)
build_linux_launcher(args, launcher_dir, for_bundle) build_linux_launcher(args, launcher_dir, for_bundle, sh_launcher)
if not is_macos: # {{{ linux desktop gunk if not is_macos: # {{{ linux desktop gunk
icdir = os.path.join(ddir, 'share', 'icons', 'hicolor', '256x256', 'apps') icdir = os.path.join(ddir, 'share', 'icons', 'hicolor', '256x256', 'apps')
safe_makedirs(icdir) safe_makedirs(icdir)
@@ -523,15 +529,47 @@ Categories=System;
) )
# }}} # }}}
if for_bundle: # OS X bundle gunk {{{ if for_bundle or sh_launcher: # OS X bundle gunk {{{
import plistlib
logo_dir = os.path.abspath(os.path.join('logo', appname + '.iconset'))
os.chdir(ddir) os.chdir(ddir)
os.mkdir('Contents') os.mkdir('Contents')
os.chdir('Contents') os.chdir('Contents')
VERSION = '.'.join(map(str, version))
pl = dict(
CFBundleDevelopmentRegion='English',
CFBundleDisplayName=appname,
CFBundleName=appname,
CFBundleIdentifier='net.kovidgoyal.' + appname,
CFBundleVersion=VERSION,
CFBundleShortVersionString=VERSION,
CFBundlePackageType='APPL',
CFBundleSignature='????',
CFBundleExecutable=appname,
LSMinimumSystemVersion='10.12.0',
LSRequiresNativeExecution=True,
NSAppleScriptEnabled=False,
NSHumanReadableCopyright=time.strftime(
'Copyright %Y, Kovid Goyal'),
CFBundleGetInfoString='kitty, an OpenGL based terminal emulator https://github.com/kovidgoyal/kitty',
CFBundleIconFile=appname + '.icns',
NSHighResolutionCapable=True,
NSSupportsAutomaticGraphicsSwitching=True,
LSApplicationCategoryType='public.app-category.utilities',
LSEnvironment={'KITTY_LAUNCHED_BY_LAUNCH_SERVICES': '1'},
)
plistlib.writePlist(pl, 'Info.plist')
os.rename('../share', 'Resources') os.rename('../share', 'Resources')
os.rename('../bin', 'MacOS') os.rename('../bin', 'MacOS')
os.rename('../lib', 'Frameworks') os.rename('../lib', 'Frameworks')
if not os.path.exists(logo_dir):
raise SystemExit('The kitty logo has not been generated, you need to run logo/make.py')
subprocess.check_call([
'iconutil', '-c', 'icns', logo_dir, '-o',
os.path.join('Resources', os.path.basename(logo_dir).partition('.')[0] + '.icns')
])
# }}} # }}}
# }}} # }}}
def clean(): def clean():
@@ -551,7 +589,7 @@ def option_parser():
'action', 'action',
nargs='?', nargs='?',
default='build', default='build',
choices='build test linux-package osx-bundle clean'.split(), choices='build test linux-package kitty.app osx-bundle clean'.split(),
help='Action to perform (default is build)' help='Action to perform (default is build)'
) )
p.add_argument( p.add_argument(
@@ -620,6 +658,13 @@ def main():
elif args.action == 'osx-bundle': elif args.action == 'osx-bundle':
build(args, native_optimizations=False) build(args, native_optimizations=False)
package(args, for_bundle=True) package(args, for_bundle=True)
elif args.action == 'kitty.app':
args.prefix = 'kitty.app'
if os.path.exists(args.prefix):
shutil.rmtree(args.prefix)
build(args)
package(args, for_bundle=False, sh_launcher=True)
print('kitty.app successfully built!')
elif args.action == 'clean': elif args.action == 'clean':
clean() clean()