Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627b91ffba | ||
|
|
9ad65f2e1d | ||
|
|
a6a606ff3d | ||
|
|
63317f9c78 | ||
|
|
46d9dfb614 | ||
|
|
be3b33af06 | ||
|
|
04e1b8b1ae | ||
|
|
213768b6e0 | ||
|
|
aff1abdb26 | ||
|
|
44a413df81 | ||
|
|
4f12c91fff | ||
|
|
b6188bf436 | ||
|
|
b9a14e0e4a | ||
|
|
6882c23f43 | ||
|
|
80301d465b | ||
|
|
32632264ee | ||
|
|
409bd37db5 | ||
|
|
5fd4bc965f | ||
|
|
e86e5ce1d8 | ||
|
|
5faa649452 | ||
|
|
2ddc7e8c80 | ||
|
|
ed700ff830 | ||
|
|
33ed873997 | ||
|
|
9125c00f96 | ||
|
|
73fe3293be | ||
|
|
804c4fbe19 | ||
|
|
d6c82ba21c | ||
|
|
67308f2ca1 | ||
|
|
4a17a946fb | ||
|
|
1ae7ae4a1d | ||
|
|
404ca23de3 | ||
|
|
d385952fef | ||
|
|
f03cac8a9d | ||
|
|
4b501fd694 | ||
|
|
872d39eb2c | ||
|
|
0341cae922 | ||
|
|
5357a9a1cc | ||
|
|
3695538a11 | ||
|
|
7780d52930 | ||
|
|
6a23b4ee47 | ||
|
|
f48b770036 | ||
|
|
81b5d2c4f2 | ||
|
|
d43d6ab0a9 | ||
|
|
5a4e883360 | ||
|
|
eb93cd4048 | ||
|
|
d83e3fddb0 | ||
|
|
80ca25e879 | ||
|
|
c86b34aacd | ||
|
|
8da426039b | ||
|
|
9184e9a28f | ||
|
|
578d537d5f | ||
|
|
236c92b2b4 | ||
|
|
66a46279f4 | ||
|
|
eb35edbe20 | ||
|
|
7cf2404331 | ||
|
|
9ccc6bf835 | ||
|
|
025a46bb35 | ||
|
|
677e8a1ae9 | ||
|
|
03702772d3 | ||
|
|
3cd744b247 | ||
|
|
88523b2692 | ||
|
|
dcb39eb210 | ||
|
|
102823809b | ||
|
|
23f2b29069 | ||
|
|
7b33a87725 | ||
|
|
6dde573ed9 | ||
|
|
566e641be1 | ||
|
|
08d5448523 | ||
|
|
c11a20f1ec | ||
|
|
e5b9bd2e5a | ||
|
|
6ad49bd7fb | ||
|
|
5d082d8d5a | ||
|
|
977575da73 | ||
|
|
bced6f31b1 | ||
|
|
6bf04e22cc | ||
|
|
2f9784809d | ||
|
|
3e85497c2c | ||
|
|
3b551a1dee | ||
|
|
d2fdb1c1eb | ||
|
|
db92af68e9 | ||
|
|
815087460c | ||
|
|
b4cb9d7c4d | ||
|
|
c6d10c6a23 | ||
|
|
464c7a80e2 | ||
|
|
4f56cce727 | ||
|
|
7dea6e0ef4 | ||
|
|
8c143db381 | ||
|
|
0cea6d5a1b | ||
|
|
eff881f4bb | ||
|
|
c64fd1760c | ||
|
|
9e361700d2 | ||
|
|
1f976644bf | ||
|
|
f3cb68ee40 | ||
|
|
85a3da057f | ||
|
|
c551384369 | ||
|
|
ce230b071b | ||
|
|
13d5f04ad9 | ||
|
|
3b861de3d2 | ||
|
|
1ef010689d | ||
|
|
3523ab283f | ||
|
|
5b2a9b101d | ||
|
|
fdcfcfa1b9 | ||
|
|
88e9c21a3b | ||
|
|
973352a98d | ||
|
|
f06f871dfc | ||
|
|
e03c713294 | ||
|
|
aa107956f9 | ||
|
|
d725578e1b | ||
|
|
239eb8202b | ||
|
|
58d7439719 | ||
|
|
cc145ec3dd | ||
|
|
847ce37160 | ||
|
|
0305009766 | ||
|
|
fe1b056371 | ||
|
|
0c4804db79 | ||
|
|
f7e84df332 | ||
|
|
bd654d22f4 | ||
|
|
115de36599 | ||
|
|
b01c3483de | ||
|
|
db7f566a0c | ||
|
|
9f806e76e7 | ||
|
|
12bdd4dddd | ||
|
|
72a1eb237d | ||
|
|
6a89289907 | ||
|
|
7ed835cf13 | ||
|
|
4621ff41d9 | ||
|
|
60e17d5224 | ||
|
|
1e2f7c705d | ||
|
|
1ebf0b95c4 | ||
|
|
c5acd94456 | ||
|
|
448ffe01c3 | ||
|
|
5c882f3290 | ||
|
|
70a4720bf9 | ||
|
|
c8aee8c881 | ||
|
|
c2ad0607b8 | ||
|
|
d1282b9f55 | ||
|
|
11ee317884 | ||
|
|
e69de2f968 | ||
|
|
2f4f3e3331 | ||
|
|
020f8a2047 | ||
|
|
b8faba2a16 | ||
|
|
0fcce6ec58 | ||
|
|
15f5ea92b8 | ||
|
|
2bbb5d25eb | ||
|
|
8d94285a5d | ||
|
|
eed11361c5 |
3
.gitattributes
vendored
3
.gitattributes
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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]
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
|||||||
8
Makefile
8
Makefile
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
52
__main__.py
52
__main__.py
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
75
gen-emoji.py
75
gen-emoji.py
@@ -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
243
gen-wcwidth.py
Executable 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()
|
||||||
3
glfw/cocoa_platform.h
vendored
3
glfw/cocoa_platform.h
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
16
glfw/context.c
vendored
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
glfw/glfw.py
15
glfw/glfw.py
@@ -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
307
glfw/glfw3.h
vendored
@@ -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
26
glfw/init.c
vendored
@@ -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
109
glfw/input.c
vendored
@@ -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
26
glfw/internal.h
vendored
@@ -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
28
glfw/monitor.c
vendored
@@ -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
5
glfw/null_window.c
vendored
@@ -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;
|
||||||
|
|||||||
@@ -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
91
glfw/wgl_context.c
vendored
@@ -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
5
glfw/wgl_context.h
vendored
@@ -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
10
glfw/win32_init.c
vendored
@@ -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
12
glfw/win32_platform.h
vendored
@@ -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
39
glfw/win32_window.c
vendored
@@ -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
63
glfw/window.c
vendored
@@ -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
40
glfw/wl_init.c
vendored
@@ -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
3
glfw/wl_platform.h
vendored
@@ -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
6
glfw/wl_window.c
vendored
@@ -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
76
glfw/x11_window.c
vendored
@@ -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)
|
||||||
|
|||||||
177
kitty/boss.py
177
kitty/boss.py
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
113
kitty/child.c
Normal 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;
|
||||||
|
}
|
||||||
103
kitty/child.py
103
kitty/child.py
@@ -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
|
|
||||||
|
|||||||
15
kitty/cli.py
15
kitty/cli.py
@@ -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)))
|
||||||
|
|||||||
@@ -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 */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
108
kitty/config.py
108
kitty/config.py
@@ -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
67
kitty/config_utils.py
Normal 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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
14
kitty/emoji.h
generated
@@ -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
|
||||||
|
|||||||
381
kitty/fonts.c
381
kitty/fonts.c
@@ -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);
|
||||||
|
|||||||
@@ -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*);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|
||||||
|
|||||||
@@ -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
21
kitty/glfw-wrapper.c
generated
@@ -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
78
kitty/glfw-wrapper.h
generated
@@ -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
|
||||||
|
|||||||
87
kitty/glfw.c
87
kitty/glfw.c
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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*);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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'
|
||||||
|
|||||||
14
kitty/keys.c
14
kitty/keys.c
@@ -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
48
kitty/keys.h
generated
@@ -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
|
||||||
|
|||||||
@@ -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 = {}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
118
kitty/layout.py
118
kitty/layout.py
@@ -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}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
66
kitty/line.c
66
kitty/line.c
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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('~'))
|
||||||
|
|||||||
@@ -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, ¢ral, &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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
488
kitty/remote_control.py
Normal 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'])
|
||||||
154
kitty/screen.c
154
kitty/screen.c
@@ -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)
|
||||||
|
|||||||
@@ -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*);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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, ¢ral, &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(¢ral), 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, ®ion_desc) != 0) return false;
|
||||||
|
Py_INCREF((PyObject *) &RegionType);
|
||||||
|
PyModule_AddObject(module, "Region", (PyObject *) &RegionType);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// }}}
|
// }}}
|
||||||
|
|||||||
@@ -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*);
|
||||||
|
|||||||
116
kitty/tabs.py
116
kitty/tabs.py
@@ -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
2606
kitty/unicode-data.c
Normal file
File diff suppressed because one or more lines are too long
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
2619
kitty/wcwidth-std.h
generated
Normal file
File diff suppressed because it is too large
Load Diff
1321
kitty/wcwidth9.h
1321
kitty/wcwidth9.h
File diff suppressed because it is too large
Load Diff
@@ -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 {{{
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
13
launcher/kitty
Executable 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__'})
|
||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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
77
remote-control.asciidoc
Normal 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.
|
||||||
65
setup.py
65
setup.py
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user