Compare commits
437 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
361b23e3f1 | ||
|
|
8feccdf63f | ||
|
|
ed905fcf6d | ||
|
|
50a6abaac1 | ||
|
|
718f7e77a1 | ||
|
|
c4aeb1adba | ||
|
|
d9c69f0a54 | ||
|
|
5afdbbe6f1 | ||
|
|
4273cb35bf | ||
|
|
89be7a031d | ||
|
|
06c5127856 | ||
|
|
fd298256d7 | ||
|
|
e0246044b7 | ||
|
|
2f677990b2 | ||
|
|
da2e4c8503 | ||
|
|
a1eb236616 | ||
|
|
11186735c2 | ||
|
|
a038f62a5b | ||
|
|
f45acc055e | ||
|
|
43a25c49c4 | ||
|
|
e47e2b2560 | ||
|
|
7652065134 | ||
|
|
d0da418e49 | ||
|
|
77e72cfd9a | ||
|
|
7b2ecb0498 | ||
|
|
318c5bca67 | ||
|
|
d52da934ca | ||
|
|
21951e3668 | ||
|
|
64bef38813 | ||
|
|
fe67e3dde7 | ||
|
|
fc5e74e457 | ||
|
|
fdbd8df7b2 | ||
|
|
246972b59f | ||
|
|
6057296499 | ||
|
|
b4b2207b93 | ||
|
|
b881364c50 | ||
|
|
76059882c4 | ||
|
|
be71f5f1f4 | ||
|
|
021dfcad39 | ||
|
|
4e7498b854 | ||
|
|
494035c94b | ||
|
|
9c4230ad9c | ||
|
|
056c2c12bb | ||
|
|
a97c440872 | ||
|
|
653d823175 | ||
|
|
c2a2dbc88c | ||
|
|
77492d31cf | ||
|
|
aaa609109e | ||
|
|
2367fad90b | ||
|
|
aaa521bd24 | ||
|
|
d22686da10 | ||
|
|
48b93ab28f | ||
|
|
703a1b83a8 | ||
|
|
c9441bd3e2 | ||
|
|
9de6d619ee | ||
|
|
9aae31ef2f | ||
|
|
dca04c549b | ||
|
|
69e76d6eeb | ||
|
|
b4a93c67c9 | ||
|
|
f098e6c973 | ||
|
|
f8ebba3954 | ||
|
|
37ee693de2 | ||
|
|
f77ad2b41d | ||
|
|
e99eb2e72b | ||
|
|
86b89072f3 | ||
|
|
022bee0560 | ||
|
|
204ea44946 | ||
|
|
3f2489b306 | ||
|
|
291a2d6295 | ||
|
|
b244d13e8a | ||
|
|
b33324d07c | ||
|
|
ae2a641db5 | ||
|
|
1cac757365 | ||
|
|
992de77040 | ||
|
|
f6b5e54890 | ||
|
|
d20c65ef80 | ||
|
|
4a8562a85f | ||
|
|
1ba7b5f5a3 | ||
|
|
f895b7f8cb | ||
|
|
16542e96bf | ||
|
|
3005b8b9d6 | ||
|
|
35653ef4b4 | ||
|
|
36b3582825 | ||
|
|
c17c801a31 | ||
|
|
460c353898 | ||
|
|
3fa7007e91 | ||
|
|
4cf6373203 | ||
|
|
e0b3bb1830 | ||
|
|
47ec372c30 | ||
|
|
d5366a421f | ||
|
|
3d5977669e | ||
|
|
36da668e6f | ||
|
|
052913c656 | ||
|
|
0ef055a2b2 | ||
|
|
a7bd6922e5 | ||
|
|
8d9ba4a772 | ||
|
|
222beedc2a | ||
|
|
8ba8320189 | ||
|
|
3501a2a1da | ||
|
|
819ee94a70 | ||
|
|
7e7dbc5ecf | ||
|
|
3a6bc94d25 | ||
|
|
07a4cec616 | ||
|
|
60de840d69 | ||
|
|
928167c0b2 | ||
|
|
2aac220cdb | ||
|
|
d8a74e8a3c | ||
|
|
019e6efeda | ||
|
|
a7a7216b30 | ||
|
|
ae8ca5272b | ||
|
|
06c1236cc6 | ||
|
|
4d3982c5cf | ||
|
|
a0ff625f61 | ||
|
|
b44345d78e | ||
|
|
96229becd1 | ||
|
|
b269cab0b8 | ||
|
|
0b3c8210f3 | ||
|
|
074614bc8e | ||
|
|
4ceb4cfc22 | ||
|
|
0bb08d8bdd | ||
|
|
b9e6557f72 | ||
|
|
7ed268ea2f | ||
|
|
a91b3b9487 | ||
|
|
cdf236d01b | ||
|
|
f54efb7886 | ||
|
|
0ffee56170 | ||
|
|
ab97646576 | ||
|
|
33b62dcabc | ||
|
|
39e6e91d69 | ||
|
|
dedd80c6b5 | ||
|
|
df11da74e9 | ||
|
|
9991dd6ce4 | ||
|
|
e777364f91 | ||
|
|
a32ed5ae5f | ||
|
|
db1e48848d | ||
|
|
c02fa79591 | ||
|
|
6f0a59c696 | ||
|
|
bc715d4348 | ||
|
|
55e4761302 | ||
|
|
18fe2e8dfa | ||
|
|
18a07a78b5 | ||
|
|
aa7024b264 | ||
|
|
c67ab2a6ff | ||
|
|
d15eab31da | ||
|
|
a6900443d5 | ||
|
|
235bcacc7b | ||
|
|
3e26f96744 | ||
|
|
718111582a | ||
|
|
f5cdbe53ea | ||
|
|
bf761a7189 | ||
|
|
05ee01b4dd | ||
|
|
53166d659e | ||
|
|
34aea3bca1 | ||
|
|
f26519863f | ||
|
|
a57e96500a | ||
|
|
5e27c21856 | ||
|
|
4601cd13dc | ||
|
|
ad92541ff3 | ||
|
|
3f77a2044e | ||
|
|
75e57a48e0 | ||
|
|
160fbd2071 | ||
|
|
f5743f4610 | ||
|
|
82f9aecacb | ||
|
|
be6556c884 | ||
|
|
f941ffb1fe | ||
|
|
cbdba0b6aa | ||
|
|
ce04f102a7 | ||
|
|
fc3bd2d15f | ||
|
|
a958cabf01 | ||
|
|
d0a2eab79e | ||
|
|
b835afefee | ||
|
|
d0a7be90f9 | ||
|
|
1f3550b84c | ||
|
|
939212e1d4 | ||
|
|
7db6318806 | ||
|
|
e5d9182606 | ||
|
|
d519553581 | ||
|
|
e99b1be023 | ||
|
|
e460866ebc | ||
|
|
23aea68e32 | ||
|
|
65c2cbe93a | ||
|
|
ee31ba0c76 | ||
|
|
3ed9d9a069 | ||
|
|
721e51c117 | ||
|
|
09daaf1ec3 | ||
|
|
b2d62f10d2 | ||
|
|
0d542b2f60 | ||
|
|
04fd058e0f | ||
|
|
31a0a9427e | ||
|
|
04f10ba570 | ||
|
|
34b7b2e4e8 | ||
|
|
09183772d3 | ||
|
|
3e4eca5816 | ||
|
|
30c6c4697f | ||
|
|
b650275d45 | ||
|
|
174bd5c57f | ||
|
|
ec51a219a7 | ||
|
|
cca2fb967c | ||
|
|
5b547d9b06 | ||
|
|
facb2df3f6 | ||
|
|
8395076da5 | ||
|
|
e113e0cba7 | ||
|
|
407d19c2dc | ||
|
|
382367506a | ||
|
|
bd5a85a4e7 | ||
|
|
631f97eb26 | ||
|
|
0c0ce4844c | ||
|
|
6bf290149e | ||
|
|
60fa812b60 | ||
|
|
61f3a39aa0 | ||
|
|
a6949df727 | ||
|
|
d839a31d7f | ||
|
|
1de510f46f | ||
|
|
0359bbe4be | ||
|
|
40b355e593 | ||
|
|
adf9c4fc5f | ||
|
|
ae9d049011 | ||
|
|
d24977d164 | ||
|
|
3035d64127 | ||
|
|
70c9c058e2 | ||
|
|
1d9f73adec | ||
|
|
fca95af49b | ||
|
|
62fe392041 | ||
|
|
097d11819b | ||
|
|
691286f376 | ||
|
|
c910f6c832 | ||
|
|
65f9ac32ef | ||
|
|
27f2a2e3d5 | ||
|
|
88aab20376 | ||
|
|
a045ac3bd7 | ||
|
|
722b605407 | ||
|
|
7f53745077 | ||
|
|
7f902c4c03 | ||
|
|
1205c2055f | ||
|
|
f08f0f9dbd | ||
|
|
093c05e16a | ||
|
|
787100a4dc | ||
|
|
c493583f25 | ||
|
|
b4afbe47b3 | ||
|
|
6c4aeeeadd | ||
|
|
5b01519367 | ||
|
|
c494759606 | ||
|
|
2102c48b87 | ||
|
|
c498f4e8ab | ||
|
|
05a0166ebd | ||
|
|
0484365de6 | ||
|
|
cbab3b56de | ||
|
|
715f7985c7 | ||
|
|
2637018b53 | ||
|
|
1fa66ca45d | ||
|
|
13b10f691d | ||
|
|
3fe7d91713 | ||
|
|
98864091ff | ||
|
|
7750a461aa | ||
|
|
ac98b85157 | ||
|
|
21f6c6a5a8 | ||
|
|
b8d1491b99 | ||
|
|
d9c168d824 | ||
|
|
ee7f4ee1a4 | ||
|
|
ee3ce3c7d8 | ||
|
|
a57f38dbd5 | ||
|
|
3c4cb3c35b | ||
|
|
12cfe6d1d1 | ||
|
|
c64e8fc434 | ||
|
|
15f534a8f9 | ||
|
|
8e09e809aa | ||
|
|
fec5a80b89 | ||
|
|
1d50a188c4 | ||
|
|
b0d34b136f | ||
|
|
7eb234c250 | ||
|
|
d52434c0aa | ||
|
|
efc0b830cf | ||
|
|
4a8c4c4601 | ||
|
|
e498cedf56 | ||
|
|
6c8a52875e | ||
|
|
c1397fd366 | ||
|
|
3323ddcdef | ||
|
|
1be394cda7 | ||
|
|
f292092ffc | ||
|
|
b82e74f99a | ||
|
|
9b293ad66a | ||
|
|
3a4420f2d3 | ||
|
|
1eb8d6e845 | ||
|
|
e2f97dc44a | ||
|
|
ea9af96d2b | ||
|
|
e4033d997c | ||
|
|
bc38357193 | ||
|
|
618b486fd5 | ||
|
|
1986403e42 | ||
|
|
cf51f80b79 | ||
|
|
2fe17aa889 | ||
|
|
f76c8ad30b | ||
|
|
c421d3bb59 | ||
|
|
3d862da2be | ||
|
|
10b15caad9 | ||
|
|
e08238d5a0 | ||
|
|
d0104660c8 | ||
|
|
a4c2a9ef0d | ||
|
|
ee39d7ba14 | ||
|
|
62cb035afe | ||
|
|
7a8bc6b5d2 | ||
|
|
0d86ac23bb | ||
|
|
897fd9486e | ||
|
|
39d3d093c4 | ||
|
|
3f0b52bef4 | ||
|
|
4a04f27fde | ||
|
|
5fb02c0439 | ||
|
|
edfb4ef3f1 | ||
|
|
f037923b00 | ||
|
|
424aafb514 | ||
|
|
1b64167a3e | ||
|
|
a9a86e4604 | ||
|
|
1dc7c23b2f | ||
|
|
5bdcfbc2ce | ||
|
|
74e9bf4f31 | ||
|
|
0652529839 | ||
|
|
772d6597a9 | ||
|
|
4e99194022 | ||
|
|
f0ff2f4ea7 | ||
|
|
d583c533c2 | ||
|
|
ec1f219850 | ||
|
|
21d586cc86 | ||
|
|
8f7139d48f | ||
|
|
cfb6dae04d | ||
|
|
bceac3ee0b | ||
|
|
38d6edb0ca | ||
|
|
1b41a3bcee | ||
|
|
c54f934f6e | ||
|
|
94052bb712 | ||
|
|
7eb1dcc7ee | ||
|
|
626fbe827e | ||
|
|
28c154ecf0 | ||
|
|
8176f661ea | ||
|
|
0195e7f841 | ||
|
|
141b8a1481 | ||
|
|
be3ab90cff | ||
|
|
9782f16635 | ||
|
|
6930edfd8a | ||
|
|
6b1bec6550 | ||
|
|
dac0304815 | ||
|
|
cfcf81b5fb | ||
|
|
f072a02215 | ||
|
|
5f6e21da9b | ||
|
|
b9b38a4ec1 | ||
|
|
e5a7ba4f22 | ||
|
|
1dcf3f2afb | ||
|
|
2ec84969dd | ||
|
|
a08b945124 | ||
|
|
16f712fbaa | ||
|
|
1f63ff1d41 | ||
|
|
49d8f4689f | ||
|
|
e1f993938a | ||
|
|
ab65b9b2fb | ||
|
|
f7a621f89f | ||
|
|
b808a05e5e | ||
|
|
72c37c519d | ||
|
|
af4b072b29 | ||
|
|
739ed906f0 | ||
|
|
9fd462077a | ||
|
|
cb3671343e | ||
|
|
b18b705632 | ||
|
|
9963e78636 | ||
|
|
e39635ea16 | ||
|
|
9329b2c202 | ||
|
|
791f70e5a2 | ||
|
|
8272c4a429 | ||
|
|
f0357b061e | ||
|
|
7438e0e997 | ||
|
|
cca672e536 | ||
|
|
0fa12a687d | ||
|
|
d2b900c77f | ||
|
|
21242d9144 | ||
|
|
03fd138b63 | ||
|
|
1eeaef6dbb | ||
|
|
b9a93b68c4 | ||
|
|
e3728942dc | ||
|
|
7f0674ac27 | ||
|
|
60fcf7ab3d | ||
|
|
ee82a6ff18 | ||
|
|
bac5e402b7 | ||
|
|
732108109e | ||
|
|
ab3b86d65b | ||
|
|
d4d63506e1 | ||
|
|
3ad69f8ba3 | ||
|
|
9fd50a6ac0 | ||
|
|
9b5c8a18d4 | ||
|
|
9f3112ca56 | ||
|
|
aa9de47da3 | ||
|
|
5eaeda4402 | ||
|
|
ecb03d7dba | ||
|
|
88612d3691 | ||
|
|
2c0e70a423 | ||
|
|
d6b70f10d3 | ||
|
|
40b01fb702 | ||
|
|
89222d93f4 | ||
|
|
b3db3e5732 | ||
|
|
b45161ecc3 | ||
|
|
d4fc0af7de | ||
|
|
5ef8cce1d7 | ||
|
|
c98a147f94 | ||
|
|
e9975cb7eb | ||
|
|
33f0ac83c1 | ||
|
|
3bd45966c6 | ||
|
|
1b7244f3c8 | ||
|
|
1949f3c3d5 | ||
|
|
6a51ce5dc4 | ||
|
|
c0c8e1ce5e | ||
|
|
6a3d6191bd | ||
|
|
b2aa07d29f | ||
|
|
c84fb68bf8 | ||
|
|
128b9e1cd0 | ||
|
|
bbe8bcb28d | ||
|
|
6dc9c22543 | ||
|
|
59f5781f19 | ||
|
|
d61fb9cc42 | ||
|
|
ace052ee70 | ||
|
|
1451a8ccc0 | ||
|
|
547959c1ef | ||
|
|
b1a5e98630 | ||
|
|
f3a1ee7b9f | ||
|
|
b0a39b127d | ||
|
|
e36f1157cc | ||
|
|
053b9a786c | ||
|
|
2561d937da | ||
|
|
55f457caf4 | ||
|
|
f5cc2fd8ad | ||
|
|
e630b76fac | ||
|
|
0b6948cb4f | ||
|
|
b0a2dea747 | ||
|
|
f8395e36a1 | ||
|
|
6ec58f58ce | ||
|
|
96cbfe946c | ||
|
|
ac686a47c1 | ||
|
|
e05aaf77ce | ||
|
|
6b371eb8a8 | ||
|
|
f6660a0d55 | ||
|
|
3b9f213ea2 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ logo/*.iconset
|
||||
compile_commands.json
|
||||
glad/out
|
||||
asan-launcher
|
||||
asan-launcher.dSYM
|
||||
kitty-profile
|
||||
dev
|
||||
__pycache__
|
||||
|
||||
@@ -89,9 +89,10 @@ matrix:
|
||||
language: generic
|
||||
env: SWBASE=/Users/Shared/buildbot/sw SW=$SWBASE/sw PATH=$SW/bin:$PATH
|
||||
|
||||
- os: osx
|
||||
language: generic
|
||||
env: USE_BREW=1 BUILD_PKG=osx-bundle
|
||||
# Disabled because harfbuzz is currently broken in brew
|
||||
# - os: osx
|
||||
# language: generic
|
||||
# env: USE_BREW=1 BUILD_PKG=osx-bundle
|
||||
|
||||
env:
|
||||
global:
|
||||
@@ -106,7 +107,7 @@ install: |
|
||||
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
|
||||
if [[ "$USE_BREW" == "1" ]]; then
|
||||
brew update;
|
||||
brew upgrade python;
|
||||
# brew upgrade python;
|
||||
brew install harfbuzz --without-graphite2 --without-icu4c --without-freetype;
|
||||
brew install imagemagick
|
||||
brew install optipng
|
||||
|
||||
@@ -10,9 +10,9 @@ config to reproduce the issue with).
|
||||
|
||||
### Contributing code
|
||||
|
||||
Install [the dependencies](https://github.com/kovidgoyal/kitty#dependencies)
|
||||
Install [the dependencies](https://sw.kovidgoyal.net/kitty/build.html#dependencies)
|
||||
using your favorite package manager. Build and run kitty [from
|
||||
source](https://github.com/kovidgoyal/kitty#install-and-run-from-source).
|
||||
source](https://sw.kovidgoyal.net/kitty/build.html#install-and-run-from-source).
|
||||
|
||||
Make a fork, submit your Pull Request. If it's a large/controversial change, open an issue
|
||||
beforehand to discuss it, so that you don't waste your time making a pull
|
||||
|
||||
13
__main__.py
13
__main__.py
@@ -7,8 +7,9 @@ import os
|
||||
|
||||
|
||||
def icat(args):
|
||||
from kittens.icat.main import main
|
||||
main(args)
|
||||
from kittens.runner import run_kitten
|
||||
sys.argv = args
|
||||
run_kitten('icat')
|
||||
|
||||
|
||||
def list_fonts(args):
|
||||
@@ -41,7 +42,13 @@ def complete(args):
|
||||
def launch(args):
|
||||
import runpy
|
||||
sys.argv = args[1:]
|
||||
runpy.run_path(args[1], run_name='__main__')
|
||||
exe = args[1]
|
||||
if exe.startswith(':'):
|
||||
import shutil
|
||||
exe = shutil.which(exe[1:])
|
||||
if not exe:
|
||||
raise SystemExit('{} not found in PATH'.format(args[1][1:]))
|
||||
runpy.run_path(exe, run_name='__main__')
|
||||
|
||||
|
||||
def run_kitten(args):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS = -T $(FAIL_WARN)
|
||||
SPHINXBUILD = python3 .. +launch $(shell which sphinx-build)
|
||||
SPHINXBUILD = python3 .. +launch :sphinx-build
|
||||
SPHINXPROJ = kitty
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
@@ -16,7 +16,8 @@ the following simple command:
|
||||
|
||||
The binaries will be installed in the standard location for your OS,
|
||||
:file:`/Applications/kitty.app` on macOS and :file:`~/.local/kitty.app` on
|
||||
Linux. The installer only touches files in that directory.
|
||||
Linux. The installer only touches files in that directory. To update kitty,
|
||||
simply re-run the command.
|
||||
|
||||
|
||||
Manually installing
|
||||
@@ -29,6 +30,25 @@ the :file:`.dmg` and install as normal. If you are on Linux, download the tarbal
|
||||
and extract it into a directory. The |kitty| executable will be in the
|
||||
:file:`bin` sub-directory.
|
||||
|
||||
Desktop integration on Linux
|
||||
--------------------------------
|
||||
|
||||
If you want the kitty icon to appear in the taskbar and an entry for it to be
|
||||
present in the menus, you will need to install the :file:`kitty.desktop` file.
|
||||
The details of the following procedure may need to be adjusted for your
|
||||
particular desktop, but it should work for most major desktop environments.
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
# Create a symbolic link to add kitty to PATH (assuming ~/.local/bin is in
|
||||
# your PATH)
|
||||
ln -s ~/.local/kitty.app/bin/kitty ~/.local/bin/
|
||||
# Place the kitty.desktop file somewhere it can be found by the OS
|
||||
cp ~/.local/kitty.app/share/applications/kitty.desktop ~/.local/share/applications
|
||||
# Update the path to the kitty icon in the kitty.desktop file
|
||||
sed -i "s/Icon\=kitty/Icon\=\/home\/$USER\/.local\/kitty.app\/share\/icons\/hicolor\/256x256\/apps\/kitty.png/g" ~/.local/share/applications/kitty.desktop
|
||||
|
||||
|
||||
|
||||
Customizing the installation
|
||||
--------------------------------
|
||||
@@ -58,6 +78,12 @@ Customizing the installation
|
||||
|ins| \\
|
||||
installer=/path/to/dmg or tarball
|
||||
|
||||
Uninstalling
|
||||
----------------
|
||||
|
||||
All the installer does is copy the kitty files into the install directory. To
|
||||
uninstall, simply delete that directory.
|
||||
|
||||
|
||||
Building from source
|
||||
------------------------
|
||||
|
||||
@@ -28,7 +28,7 @@ Build-time dependencies:
|
||||
* gcc or clang
|
||||
* pkg-config
|
||||
* For building on Linux in addition to the above dependencies you might also need to install the ``-dev`` packages for:
|
||||
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev`` and ``libxkbcommon-x11-dev``,
|
||||
``libdbus-1-dev``, ``libxcursor-dev``, ``libxrandr-dev``, ``libxi-dev``, ``libxinerama-dev``, ``libgl1-mesa-dev``, ``libxkbcommon-x11-dev``, ``libfontconfig-dev`` and ``libpython-dev``.
|
||||
if they are not already installed by your distro.
|
||||
|
||||
Install and run from source
|
||||
@@ -57,6 +57,25 @@ If that works, you can create a script to launch |kitty|:
|
||||
And place it in :file:`~/bin` or :file:`/usr/bin` so that you can run |kitty| using
|
||||
just ``kitty``.
|
||||
|
||||
|
||||
Building kitty.app on macOS from source
|
||||
-------------------------------------------
|
||||
|
||||
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
|
||||
for the logo generation step). And run::
|
||||
|
||||
make app
|
||||
|
||||
This :file:`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 :file:`kitty.dmg` includes all dependencies, unlike the
|
||||
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
|
||||
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
|
||||
is designed to run on Linux and is not for the faint of heart.
|
||||
|
||||
|
||||
Note for Linux/macOS packagers
|
||||
----------------------------------
|
||||
|
||||
|
||||
@@ -3,6 +3,277 @@ Changelog
|
||||
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
0.13.2 [2019-01-04]
|
||||
------------------------------
|
||||
|
||||
- Add a new option :opt:`tab_title_template` to control how tab titles
|
||||
are formatted. In particular the template can be used to display
|
||||
the tab number next to the title (:iss:`1223`)
|
||||
|
||||
- Report the current foreground processes as well as the original child process,
|
||||
when using `kitty @ ls`
|
||||
|
||||
- Use the current working directory of the foreground process for the
|
||||
`*_with_cwd` actions that open a new window with the current working
|
||||
directory.
|
||||
|
||||
- Add a new ``copy_or_interrupt`` action that can be mapped to kbd:`ctrl+c`. It
|
||||
will copy if there is a selection and interrupt otherwise (:iss:`1286`)
|
||||
|
||||
- Fix setting :opt:`background_opacity` causing window margins/padding to be slightly
|
||||
different shade from background (:iss:`1221`)
|
||||
|
||||
- Handle keyboards with a "+" key (:iss:`1224`)
|
||||
|
||||
- Fix Private use Unicode area characters followed by spaces at the end of text
|
||||
not being rendered correctly (:iss:`1210`)
|
||||
|
||||
- macOS: Add an entry to the dock menu to open a new OS window (:iss:`1242`)
|
||||
|
||||
- macOS: Fix scrolling very slowly with wheel mice not working (:iss:`1238`)
|
||||
|
||||
- Fix changing :opt:`cursor_text_color` via remote control not working
|
||||
(:iss:`1229`)
|
||||
|
||||
- Add an action to resize windows that can be mapped to shortcuts in :file:`kitty.conf`
|
||||
(:pull:`1245`)
|
||||
|
||||
- Fix using the ``new_tab !neighbor`` action changing the order of the
|
||||
non-neighboring tabs (:iss:`1256`)
|
||||
|
||||
- macOS: Fix momentum scrolling continuing when changing the active window/tab
|
||||
(:iss:`1267`)
|
||||
|
||||
|
||||
0.13.1 [2018-12-06]
|
||||
------------------------------
|
||||
|
||||
- Fix passing input via the pipe action to a program without a window not
|
||||
working.
|
||||
|
||||
- Linux: Fix a regression in the previous release that caused automatic
|
||||
selection of bold/italic fonts when using aliases such as "monospace" to not
|
||||
work (:iss:`1209`)
|
||||
|
||||
- Fix resizing window smaller and then restoring causing some wrapped lines to not
|
||||
be properly unwrapped (:iss:`1206`)
|
||||
|
||||
0.13.0 [2018-12-05]
|
||||
------------------------------
|
||||
|
||||
- Add an option :opt:`scrollback_pager_history_size` to tell kitty to store
|
||||
extended scrollback to use when viewing the scrollback buffer in a pager
|
||||
(:iss:`970`)
|
||||
|
||||
- Modify the kittens sub-system to allow creating custom kittens without any
|
||||
user interface. This is useful for creating more complex actions that can
|
||||
be bound to key presses in :file:`kitty.conf`. See
|
||||
doc:`kittens/custom`. (:iss:`870`)
|
||||
|
||||
- Add a new ``nth_window`` action that can be used to go to the nth window and
|
||||
also previously active windows, using negative numbers. Similarly,
|
||||
``goto_tab`` now accepts negative numbers to go to previously active tabs
|
||||
(:iss:`1040`)
|
||||
|
||||
- Allow hiding the tab bar completely, by setting :opt:`tab_bar_style` to
|
||||
``hidden``. (:iss:`1014`)
|
||||
|
||||
- Allow private use unicode characters to stretch over more than a single
|
||||
neighboring space (:pull:`1036`)
|
||||
|
||||
- Add a new :opt:`touch_scroll_multiplier` option to modify the amount
|
||||
scrolled by high precision scrolling devices such as touchpads (:pull:`1129`)
|
||||
|
||||
- icat kitten: Implement reading image data from STDIN, if STDIN is not
|
||||
connected to a terminal (:iss:`1130`)
|
||||
|
||||
- hints kitten: Insert trailing spaces after matches when using the
|
||||
``--multiple`` option. Also add a separate ``--add-trailing-space``
|
||||
option to control this behavior (:pull:`1132`)
|
||||
|
||||
- Fix the ``*_with_cwd`` actions using the cwd of the overlay window rather
|
||||
than the underlying window's cwd (:iss:`1045`)
|
||||
|
||||
- Fix incorrect key repeat rate on wayland (:pull:`1055`)
|
||||
|
||||
- macOS: Fix drag and drop of files not working on Mojave (:iss:`1058`)
|
||||
|
||||
- macOS: Fix IME input for East Asian languages (:iss:`910`)
|
||||
|
||||
- macOS: Fix rendering frames-per-second very low when processing
|
||||
large amounts of input in small chunks (:pull:`1082`)
|
||||
|
||||
- macOS: Fix incorrect text sizes calculated when using an external display
|
||||
that is set to mirror the main display (:iss:`1056`)
|
||||
|
||||
- macOS: Use the system default double click interval (:pull:`1090`)
|
||||
|
||||
- macOS: Fix touch scrolling sensitivity low on retina screens (:iss:`1112`)
|
||||
|
||||
- Linux: Fix incorrect rendering of some fonts when hinting is disabled at
|
||||
small sizes (:iss:`1173`)
|
||||
|
||||
- Linux: Fix match rules used as aliases in Fontconfig configuration not being
|
||||
respected (:iss:`1085`)
|
||||
|
||||
- Linux: Fix a crash when using the GNU Unifont as a fallback font
|
||||
(:iss:`1087`)
|
||||
|
||||
- Wayland: Fix copying from hidden kitty windows hanging (:iss:`1051`)
|
||||
|
||||
- Wayland: Add support for the primary selection protocol
|
||||
implemented by some compositors (:pull:`1095`)
|
||||
|
||||
- Fix expansion of env vars not working in the :opt:`env` directive
|
||||
(:iss:`1075`)
|
||||
|
||||
- Fix :opt:`mouse_hide_wait` only taking effect after an event such as cursor
|
||||
blink or key press (:iss:`1073`)
|
||||
|
||||
- Fix the ``set_background_opacity`` action not working correctly
|
||||
(:pull:`1147`)
|
||||
|
||||
- Fix second cell of emoji created using variation selectors not having
|
||||
the same attributes as the first cell (:iss:`1109`)
|
||||
|
||||
- Fix focusing neighboring windows in the grid layout with less than 4 windows
|
||||
not working (:iss:`1115`)
|
||||
|
||||
- Fix :kbd:`ctrl+shift+special` key not working in normal and application keyboard
|
||||
modes (:iss:`1114`)
|
||||
|
||||
- Add a terminfo entry for full keyboard mode.
|
||||
|
||||
- Fix incorrect text-antialiasing when using very low background opacity
|
||||
(:iss:`1005`)
|
||||
|
||||
- When double or triple clicking ignore clicks if they are "far" from each
|
||||
other (:iss:`1093`)
|
||||
|
||||
- Follow xterm's behavior for the menu key (:iss:`597`)
|
||||
|
||||
- Fix hover detection of URLs not working when hovering over the first colon
|
||||
and slash characters in short URLs (:iss:`1201`)
|
||||
|
||||
0.12.3 [2018-09-29]
|
||||
------------------------------
|
||||
|
||||
- macOS: Fix kitty window not being rendered on macOS Mojave until the window is
|
||||
moved or resized at least once (:iss:`887`)
|
||||
|
||||
- Unicode input: Fix an error when searching for the string 'fir' (:iss:`1035`)
|
||||
|
||||
|
||||
0.12.2 [2018-09-24]
|
||||
------------------------------
|
||||
|
||||
- A new ``last_used_layout`` function that can be mapped to a shortcut to
|
||||
switch to the previously used window layout (:iss:`870`)
|
||||
|
||||
- New ``neighboring_window`` and ``move_window`` functions to switch to
|
||||
neighboring windows in the current layout, and move them around, similar to
|
||||
window movement in vim (:iss:`916`)
|
||||
|
||||
- A new ``pipe`` function that can be used to pipe the contents of the screen
|
||||
and scrollback buffer to any desired program running in a new window, tab or
|
||||
overlay window. (:iss:`933`)
|
||||
|
||||
- Add a new :option:`kitty --start-as` command line flag to start kitty
|
||||
full-screen/maximized/minimized. This replaces the ``--start-in-fullscreen``
|
||||
flag introduced in the previous release (:iss:`935`)
|
||||
|
||||
- When mapping the ``new_tab`` action allow specifying that the tab should open
|
||||
next to the current tab instead of at the end of the tabs list (:iss:`979`)
|
||||
|
||||
- macOS: Add a new :opt:`macos_thicken_font` to make text rendering
|
||||
on macs thicker, which makes it similar to the result of
|
||||
sub-pixel antialiasing (:pull:`950`)
|
||||
|
||||
- macOS: Add an option :opt:`macos_traditional_fullscreen` to make
|
||||
full-screening of kitty windows much faster, but less pretty. (:iss:`911`)
|
||||
|
||||
- Fix a bug causing incorrect line ordering when viewing the scrollback buffer
|
||||
if the scrollback buffer is full (:iss:`960`)
|
||||
|
||||
- Fix drag-scrolling not working when the mouse leaves the window confines
|
||||
(:iss:`917`)
|
||||
|
||||
- Workaround for broken editors like nano that cannot handle newlines in pasted text
|
||||
(:iss:`994`)
|
||||
|
||||
- Linux: Ensure that the python embedded in the kitty binary build uses
|
||||
UTF-8 mode to process command-line arguments (:iss:`924`)
|
||||
|
||||
- Linux: Handle fonts that contain monochrome bitmaps (such as the Terminus TTF
|
||||
font) (:pull:`934`)
|
||||
|
||||
- Have the :option:`kitty --title` flag apply to all windows created
|
||||
using :option:`kitty --session` (:iss:`921`)
|
||||
|
||||
- Revert change for backspacing of wide characters in the previous release,
|
||||
as it breaks backspacing in some wide character aware programs (:iss:`875`)
|
||||
|
||||
- Fix kitty @set-colors not working for tab backgrounds when using the `fade` tabbar style
|
||||
(:iss:`937`)
|
||||
|
||||
- macOS: Fix resizing semi-transparent windows causing the windows to be
|
||||
invisible during the resize (:iss:`941`)
|
||||
|
||||
- Linux: Fix window icon not set on X11 for the first OS window (:iss:`961`)
|
||||
|
||||
- macOS: Add an :opt:`macos_custom_beam_cursor` option to use a special
|
||||
mouse cursor image that can be seen on both light and dark backgrounds
|
||||
(:iss:`359`)
|
||||
|
||||
- Remote control: Fix the ``focus_window`` command not focusing the
|
||||
top-level OS window of the specified kitty window (:iss:`1003`)
|
||||
|
||||
- Fix using :opt:`focus_follows_mouse` causing text selection with the
|
||||
mouse to malfunction when using multiple kitty windows (:iss:`1002`)
|
||||
|
||||
0.12.1 [2018-09-08]
|
||||
------------------------------
|
||||
|
||||
- Add a new ``--start-in-fullscreen`` command line flag to start
|
||||
kitty in full screen mode (:iss:`856`)
|
||||
|
||||
- macOS: Fix a character that cannot be rendered in any font causing
|
||||
font fallback for all subsequent characters that cannot be rendered in the
|
||||
main font to fail (:iss:`799`)
|
||||
|
||||
- Linux: Do not enable IME input via ibus unless the ``GLFW_IM_MODULE=ibus``
|
||||
environment variable is set. IME causes key processing latency and even
|
||||
missed keystrokes for many people, so it is now off by default.
|
||||
|
||||
- Fix backspacing of wide characters in wide-character unaware programs not working (:iss:`875`)
|
||||
|
||||
- Linux: Fix number pad arrow keys not working when Numlock is off (:iss:`857`)
|
||||
|
||||
- Wayland: Implement support for clipboard copy/paste (:iss:`855`)
|
||||
|
||||
- Allow mapping shortcuts using the raw key code from the OS (:iss:`848`)
|
||||
|
||||
- Allow mapping of individual key-presses without modifiers as shortcuts
|
||||
|
||||
- Fix legacy invocation of icat as `kitty icat` not working (:iss:`850`)
|
||||
|
||||
- Improve rendering of wavy underline at small font sizes (:iss:`853`)
|
||||
|
||||
- Fix a regression in 0.12.0 that broke dynamic resizing of layouts (:iss:`860`)
|
||||
|
||||
- Wayland: Allow using the :option:`kitty --class` command line flag
|
||||
to set the app id (:iss:`862`)
|
||||
|
||||
- Add completion of the kitty command for the fish shell (:pull:`829`)
|
||||
|
||||
- Linux: Fix XCompose rules with no defined symbol not working (:iss:`880`)
|
||||
|
||||
- Linux: Fix crash with some Nvidia drivers when creating tabs in the first
|
||||
top level-window after creating a second top-level window. (:iss:`873`)
|
||||
|
||||
- macOS: Diff kitten: Fix syntax highlighting not working because of
|
||||
a bug in the 0.12.0 macOS package
|
||||
|
||||
0.12.0 [2018-09-01]
|
||||
------------------------------
|
||||
|
||||
@@ -23,6 +294,9 @@ Changelog
|
||||
- Add an option :opt:`env` to set environment variables in child processes
|
||||
from kitty.conf
|
||||
|
||||
- Add an action to the ``clear_terminal`` function to scroll the screen
|
||||
contents into the scrollback buffer (:iss:`1113`)
|
||||
|
||||
- Implement high precision scrolling with the trackpad on platforms such as
|
||||
macOS and Wayland that implement it. (:pull:`819`)
|
||||
|
||||
@@ -232,7 +506,7 @@ Changelog
|
||||
|
||||
- Add a config option (:opt:`editor`) to set the EDITOR kitty uses (:iss:`580`)
|
||||
|
||||
- Add a config option (:opt:`x11_hide_window_decorations`) to hide window
|
||||
- Add a config option (``x11_hide_window_decorations``) to hide window
|
||||
decorations under X11/Wayland (:iss:`607`)
|
||||
|
||||
- Add an option to @set-window-title to make the title change non-permanent
|
||||
|
||||
63
docs/faq.rst
63
docs/faq.rst
@@ -6,7 +6,7 @@ Frequently Asked Questions
|
||||
.. contents::
|
||||
|
||||
Some special symbols are rendered small/truncated in kitty?
|
||||
-----------------------------------------------------------------
|
||||
-----------------------------------------------------------
|
||||
|
||||
The number of cells a unicode character takes up are controlled by the unicode
|
||||
standard. All characters are rendered in a single cell unless the unicode
|
||||
@@ -25,24 +25,6 @@ these characters are followed by a space or empty cell in which case kitty
|
||||
makes use of the extra cell to render them in two cells.
|
||||
|
||||
|
||||
How do I build kitty.app on macOS?
|
||||
----------------------------------------
|
||||
|
||||
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed
|
||||
for the logo generation step). And run::
|
||||
|
||||
make app
|
||||
|
||||
This :file:`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 :file:`kitty.dmg` includes all dependencies, unlike the
|
||||
:file:`kitty.app` built above and is built automatically by using the :file:`kitty` branch of
|
||||
`build-calibre <https://github.com/kovidgoyal/build-calibre>`_ however, that
|
||||
is designed to run on Linux and is not for the faint of heart.
|
||||
|
||||
|
||||
Using a color theme with a background color does not work well in vim?
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
@@ -66,15 +48,43 @@ terminfo files to the server::
|
||||
kitty +kitten ssh myserver
|
||||
|
||||
If for some reason that does not work (typically because the server is using a
|
||||
a non POSIX compliant shell), you can use the following one-liner instead (it
|
||||
non POSIX compliant shell), you can use the following one-liner instead (it
|
||||
is slower as it needs to ssh into the server twice, but will work with most
|
||||
servers)::
|
||||
|
||||
infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||
|
||||
If you are behind a proxy (like Balabit) that prevents this, you must redirect the
|
||||
1st command to a file, copy that to the server and run ``tic`` manually. If you
|
||||
connect to a server, embedded or Android system that doesn't have ``tic``, copy over
|
||||
your local file terminfo to the other system as :file:`~/.terminfo/x/xterm-kitty`.
|
||||
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have ssh do this automatically when connecting to a server, so that all
|
||||
terminals work transparently.
|
||||
have ssh do this automatically, if possible, when connecting to a server, so that
|
||||
all terminals work transparently.
|
||||
|
||||
|
||||
Keys such as arrow keys, backspace, delete, home/end, etc. do not work when using su or sudo?
|
||||
-------------------------------------------------------------------------------------------------
|
||||
|
||||
Make sure the TERM environment variable, is ``xterm-kitty``. And either the
|
||||
TERMINFO environment variable points to a directory containing :file:`x/xterm-kitty`
|
||||
or that file is under :file:`~/.terminfo/x/`.
|
||||
|
||||
Note that ``sudo`` might remove TERMINFO. Then setting it at the shell prompt can
|
||||
be too late, because command line editing may not be reinitialized. In that case
|
||||
you can either ask ``sudo`` to set it or if that is not supported, insert an ``env``
|
||||
command before starting the shell, or, if not possible, after sudo start another
|
||||
Shell providing the right terminfo path::
|
||||
|
||||
sudo … TERMINFO=$HOME/.terminfo bash -i
|
||||
sudo … env TERMINFO=$HOME/.terminfo bash -i
|
||||
TERMINFO=/home/ORIGINALUSER/.terminfo exec bash -i
|
||||
|
||||
If you have double width characters in your prompt, you may also need to
|
||||
explicitly set a UTF-8 locale, like::
|
||||
|
||||
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
|
||||
|
||||
|
||||
How do I change the colors in a running kitty instance?
|
||||
@@ -113,3 +123,12 @@ your font is not listed in ``kitty list-fonts`` it means that it is not
|
||||
monospace. On Linux you can list all monospace fonts with::
|
||||
|
||||
fc-list : family spacing | grep spacing=100
|
||||
|
||||
|
||||
How can I assign a single global shortcut to bring up the kitty terminal?
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Bringing up applications on a single key press is the job of the window
|
||||
manager/desktop environment. For ways to do it with kitty (or indeed any
|
||||
terminal) in different environments,
|
||||
see `here <https://github.com/kovidgoyal/kitty/issues/45>`_.
|
||||
|
||||
@@ -19,7 +19,7 @@ For some discussion regarding the design choices, see `#33
|
||||
|
||||
To see a quick demo, inside a |kitty| terminal run::
|
||||
|
||||
kitty icat path/to/some/image.png
|
||||
kitty +kitten icat path/to/some/image.png
|
||||
|
||||
You can also see a screenshot with more sophisticated features such as
|
||||
alpha-blending and text over graphics.
|
||||
@@ -75,6 +75,47 @@ You can also use the *CSI t* escape code to get the screen size. Send
|
||||
``<ESC>[4;<height>;<width>t`` where *height* and *width* are the window size in
|
||||
pixels. This escape code is supported in many terminals, not just kitty.
|
||||
|
||||
A minimal example
|
||||
------------------
|
||||
|
||||
Some minimal python code to display PNG images in kitty, using the most basic
|
||||
features of the graphics protocol:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
from base64 import standard_b64encode
|
||||
|
||||
def serialize_gr_command(cmd, payload=None):
|
||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||
ans = []
|
||||
w = ans.append
|
||||
w(b'\033_G'), w(cmd.encode('ascii'))
|
||||
if payload:
|
||||
w(b';')
|
||||
w(payload)
|
||||
w(b'\033\\')
|
||||
return b''.join(ans)
|
||||
|
||||
def write_chunked(cmd, data):
|
||||
data = standard_b64encode(data)
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
cmd['m'] = m
|
||||
sys.stdout.buffer.write(serialize_gr_command(cmd, chunk))
|
||||
sys.stdout.flush()
|
||||
cmd.clear()
|
||||
|
||||
write_chunked({'a': 'T', 'f': 100}, open(sys.argv[-1], 'rb').read())
|
||||
|
||||
|
||||
Save this script as :file:`png.py`, then you can use it to display any PNG
|
||||
file in kitty as::
|
||||
|
||||
python png.py file.png
|
||||
|
||||
|
||||
The graphics escape code
|
||||
---------------------------
|
||||
|
||||
@@ -160,7 +201,11 @@ Value of `t` Meaning
|
||||
================== ============
|
||||
``d`` Direct (the data is transmitted within the escape code itself)
|
||||
``f`` A simple file
|
||||
``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data
|
||||
``t`` A temporary file, the terminal emulator will delete the file after reading the pixel data. For security reasons
|
||||
the terminal emulator should only delete the file if it
|
||||
is in a known temporary directory, such as :file:`/tmp`,
|
||||
:file:`/dev/shm`, :file:`TMPDIR env var if present` and any platform
|
||||
specific temporary directories.
|
||||
``s`` A `POSIX shared memory object <http://man7.org/linux/man-pages/man7/shm_overview.7.html>`_.
|
||||
The terminal emulator will delete it after reading the pixel data
|
||||
================== ============
|
||||
|
||||
@@ -51,7 +51,8 @@ Quickstart
|
||||
--------------
|
||||
|
||||
Pre-built binaries of |kitty| are available for both macOS and Linux.
|
||||
See the :doc:`binary install instructions </binary>`.
|
||||
See the :doc:`binary install instructions </binary>`. You can also
|
||||
:doc:`build from source </build>`.
|
||||
|
||||
If you are on Linux, you can also use your distribution's |kitty| package.
|
||||
|kitty| packages are available for:
|
||||
@@ -60,7 +61,8 @@ If you are on Linux, you can also use your distribution's |kitty| package.
|
||||
`Arch Linux <https://www.archlinux.org/packages/community/x86_64/kitty/>`_,
|
||||
`NixOS <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix>`_,
|
||||
`Gentoo <https://packages.gentoo.org/packages/x11-terms/kitty>`_,
|
||||
`Fedora <https://copr.fedorainfracloud.org/coprs/oleastre/kitty-terminal/>`_.
|
||||
`Fedora <https://copr.fedorainfracloud.org/coprs/gagbo/kitty-latest/>`_,
|
||||
`Void Linux <https://github.com/void-linux/void-packages/blob/master/srcpkgs/kitty/template>`_.
|
||||
|
||||
See :doc:`Configuring kitty <conf>` for help on configuring |kitty| and
|
||||
:doc:`Invocation <invocation>` for the command line arguments |kitty| supports.
|
||||
@@ -123,7 +125,7 @@ Action Shortcut
|
||||
New tab :sc:`new_tab`
|
||||
Close tab :sc:`close_tab`
|
||||
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
|
||||
Previous tab :sc:`previous_tab`
|
||||
Previous tab :sc:`previous_tab` (also :kbd:`control+shift+tab` on macOS)
|
||||
Next layout :sc:`next_layout`
|
||||
Move tab forward :sc:`move_tab_forward`
|
||||
Move tab backward :sc:`move_tab_backward`
|
||||
@@ -149,6 +151,22 @@ Focus specific window :sc:`first_window`, :sc:`second_window` ... :sc:`ten
|
||||
(clockwise from the top-left)
|
||||
======================== =======================
|
||||
|
||||
Additionally, you can define shortcuts in :file:`kitty.conf` to focus neighboring
|
||||
windows and move windows around (similar to window movement in vim)::
|
||||
|
||||
map ctrl+left neighboring_window left
|
||||
map shift+left move_window right
|
||||
map ctrl+down neighboring_window down
|
||||
map shift+down move_window up
|
||||
...
|
||||
|
||||
You can also define a shortcut to switch to the previously active window::
|
||||
|
||||
map ctrl+p nth_window -1
|
||||
|
||||
``nth_window`` will focus the nth window for positive numbers and the
|
||||
previously active windows for negative numbers.
|
||||
|
||||
|
||||
Other keyboard shortcuts
|
||||
----------------------------------
|
||||
@@ -162,7 +180,8 @@ Paste from selection :sc:`paste_from_selection`
|
||||
Increase font size :sc:`increase_font_size`
|
||||
Decrease font size :sc:`decrease_font_size`
|
||||
Restore font size :sc:`reset_font_size`
|
||||
Toggle fullscreen :sc:`toggle_fullscreen`
|
||||
Toggle fullscreen :sc:`toggle_fullscreen` (also :kbd:`^⌘+f` on macOS)
|
||||
on
|
||||
Input unicode character :sc:`input_unicode_character`
|
||||
Click URL using the keyboard :sc:`open_url`
|
||||
Reset the terminal :sc:`reset_terminal`
|
||||
@@ -181,6 +200,9 @@ Reset background opacity :sc:`reset_background_opacity`
|
||||
Layouts
|
||||
----------
|
||||
|
||||
A layout is an arrangement of multiple *windows*. You can create a new window
|
||||
using the :sc:`new_window` key combination.
|
||||
|
||||
Currently, there are five layouts available,
|
||||
|
||||
* **Stack** -- Only a single maximized window is shown at a time
|
||||
@@ -201,6 +223,19 @@ example, in the Tall layout you can make the first window wider/narrower, but
|
||||
not taller/shorter. Note that what you are resizing is actually not a window,
|
||||
but a row/column in the layout, all windows in that row/column will be resized.
|
||||
|
||||
You can also define shortcuts in :file:`kitty.conf` to make the active window
|
||||
wider, narrower, taller, or shorter by mapping to the ``resize_window``
|
||||
action, for example::
|
||||
|
||||
map ctrl+left resize_window narrower
|
||||
map ctrl+right resize_window wider
|
||||
map ctrl+up resize_window taller
|
||||
map ctrl+down resize_window shorter 3
|
||||
|
||||
The ``resize_window`` action has a second, optional argument to control
|
||||
the resizing increment (a positive integer that defaults to 1).
|
||||
|
||||
|
||||
Some layouts take options to control their behavior. For example, the ``fat``
|
||||
and ``tall`` layouts accept the ``bias`` option to control how the available
|
||||
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
|
||||
@@ -250,6 +285,8 @@ Some prominent kittens:
|
||||
:doc:`Clipboard <kittens/clipboard>`
|
||||
Copy/paste to the clipboard from shell scripts, even over SSH.
|
||||
|
||||
You can also :doc:`Learn to create your own kittens <kittens/custom>`.
|
||||
|
||||
|
||||
Configuring kitty
|
||||
-------------------
|
||||
@@ -258,7 +295,6 @@ Configuring kitty
|
||||
painting frames-per-second. For details and a sample :file:`kitty.conf`,
|
||||
see the :doc:`configuration docs <conf>`.
|
||||
|
||||
.. _sessions:
|
||||
|
||||
Remote control
|
||||
------------------
|
||||
@@ -269,6 +305,8 @@ fonts, open new windows, tabs, set their titles, change window layout, get text
|
||||
from one window and send text to another, etc, etc. The possibilities are
|
||||
endless. See the :doc:`tutorial <remote-control>` to get started.
|
||||
|
||||
.. _sessions:
|
||||
|
||||
Startup Sessions
|
||||
------------------
|
||||
|
||||
@@ -344,6 +382,17 @@ scrollback buffer in your favorite pager program (which is ``less`` by default).
|
||||
Colors and text formatting are preserved. You can explore the scrollback buffer
|
||||
comfortably within the pager.
|
||||
|
||||
Additionally, you can pipe the contents of the scrollback buffer to an
|
||||
arbitrary, command running in a new window, tab or overlay, for example::
|
||||
|
||||
map f1 pipe @ansi window less +G -R
|
||||
|
||||
Would open the scrollback buffer in a new window when you press the :kbd:`F1`
|
||||
key. See :sc:`show_scrollback` for details.
|
||||
|
||||
If you wish to store very large amounts of scrollback to view using the piping or
|
||||
:sc:`show_scrollback` features, you can use the :opt:`scrollback_pager_history_size`
|
||||
option.
|
||||
|
||||
Frequently Asked Questions
|
||||
---------------------------------
|
||||
@@ -384,6 +433,16 @@ The important thing above is to make sure the call to |kitty| to load the zsh
|
||||
completions happens after the call to :file:`compinit`.
|
||||
|
||||
|
||||
fish
|
||||
~~~~~~~~
|
||||
|
||||
Add the following to your :file:`~/.config/fish/config.fish`
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
kitty + complete setup fish | source
|
||||
|
||||
|
||||
Changelog
|
||||
------------------
|
||||
|
||||
|
||||
129
docs/kittens/custom.rst
Normal file
129
docs/kittens/custom.rst
Normal file
@@ -0,0 +1,129 @@
|
||||
Custom kittens
|
||||
=================
|
||||
|
||||
You can easily create your own kittens to extend kitty. They are just
|
||||
terminal programs written in Python. When launching a kitten, kitty will
|
||||
open an overlay window over the current window and optionally pass the
|
||||
contents of the current window/scrollback to the kitten over its :file:`STDIN`.
|
||||
The kitten can then perform whatever actions it likes, just as a normal
|
||||
terminal program. After execution of the kitten is complete, it has access
|
||||
to the running kitty instance so it can perform arbitrary actions
|
||||
such as closing windows, pasting text, etc.
|
||||
|
||||
Let's see a simple example of creating a kitten. It will ask the user for some
|
||||
input and paste it into the terminal window.
|
||||
|
||||
Create a file in the kitty config folder, :file:`~/.config/kitty/mykitten.py`
|
||||
(you might need to adjust the path to wherever the kitty config folder is on
|
||||
your machine).
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def main(args):
|
||||
# this is the main entry point of the kitten, it will be executed in
|
||||
# the overlay window when the kitten is launched
|
||||
answer = input('Enter some text: ')
|
||||
# whatever this function returns will be available in the
|
||||
# handle_result() function
|
||||
return answer
|
||||
|
||||
def handle_result(args, answer, target_window_id, boss):
|
||||
# get the kitty window into which to paste answer
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
w.paste(answer)
|
||||
|
||||
|
||||
Now in :file:`kitty.conf` add the lines::
|
||||
|
||||
map ctrl+k kitten mykitten.py
|
||||
|
||||
|
||||
Start kitty and press :kbd:`ctrl+k` and you should see the kitten running.
|
||||
The best way to develop your own kittens is to modify one of the built in
|
||||
kittens. Look in the kittens sub-directory of the kitty source code for those.
|
||||
|
||||
|
||||
Passing arguments to kittens
|
||||
------------------------------
|
||||
|
||||
You can pass arguments to kittens by defining them in the map directive in
|
||||
:file:`kitty.conf`. For example::
|
||||
|
||||
map ctrl+k kitten mykitten.py arg1 arg2
|
||||
|
||||
These will be available as the ``args`` parameter in the ``main()`` and
|
||||
``handle_result()`` functions. Note also that the current working directory
|
||||
of the kitten is set to the working directory of whatever program is
|
||||
running in the active kitty window.
|
||||
|
||||
|
||||
Passing the contents of the screen to the kitten
|
||||
---------------------------------------------------
|
||||
|
||||
If you would like your kitten to have access to the contents of the screen
|
||||
and/or the scrollback buffer, you just need to add an annotation to the ``handle_result()``
|
||||
function, telling kitty what kind of input your kitten would like. For example:
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
# in main, STDIN is for the kitten process and will contain
|
||||
# the contents of the screen
|
||||
def main(args):
|
||||
return sys.stdin.read()
|
||||
|
||||
# in handle_result, STDIN is for the kitty process itself, rather
|
||||
# than the kitten process and should not be read from.
|
||||
def handle_result(args, stdin_data, target_window_id, boss):
|
||||
pass
|
||||
|
||||
handle_result.type_of_input = 'text'
|
||||
|
||||
This will send the plain text of the active window to the kitten's
|
||||
:file:`STDIN`. For text with formatting escape codes, use ``ansi``
|
||||
instead. If you want line wrap markers as well, use ``screen-ansi``
|
||||
or just ``screen``. For the scrollback buffer as well, use
|
||||
``history``, ``ansi-history`` or ``screen-history``.
|
||||
|
||||
|
||||
Using kittens to script kitty, without any terminal UI
|
||||
-----------------------------------------------------------
|
||||
|
||||
If you would like your kitten to script kitty, without bothering to write a
|
||||
terminal program, you can tell the kittens system to run the
|
||||
``handle_result()`` function without first running the ``main()`` function.
|
||||
|
||||
For example, here is a kitten that "zooms/unzooms" the current terminal window
|
||||
by switching to the stack layout or back to the previous layout.
|
||||
|
||||
Create a file in the kitty config folder, :file:`~/.config/kitty/zoom_toggle.py`
|
||||
|
||||
.. code-block:: py
|
||||
|
||||
def main(args):
|
||||
pass
|
||||
|
||||
def handle_result(args, answer, target_window_id, boss):
|
||||
tab = boss.active_tab
|
||||
if tab is not None:
|
||||
if tab.current_layout.name == 'stack':
|
||||
tab.last_used_layout()
|
||||
else:
|
||||
tab.goto_layout('stack')
|
||||
|
||||
handle_result.no_ui = True
|
||||
|
||||
|
||||
Now in kitty.conf add::
|
||||
|
||||
map f11 kitten zoom_toggle.py
|
||||
|
||||
Pressing :kbd:`F11` will now act as a zoom toggle function. You can get even
|
||||
more fancy, switching the kitty OS window to fullscreen as well as changing the
|
||||
layout, by simply adding the line::
|
||||
|
||||
boss.toggle_fullscreen()
|
||||
|
||||
|
||||
to the ``handle_result()`` function, above.
|
||||
@@ -19,7 +19,9 @@ terminal, very useful for picking files from the output of a ``git`` or ``ls`` c
|
||||
adding them to the command line for the next command.
|
||||
|
||||
The hints kitten is very powerful to see more detailed help on its various
|
||||
options and modes of operation, see below.
|
||||
options and modes of operation, see below. You can use these options to
|
||||
create mappings in :file:`kitty.conf` to select various different text
|
||||
snippets. See :sc:`insert_selected_path` for examples.
|
||||
|
||||
Command Line Interface
|
||||
-------------------------
|
||||
|
||||
@@ -10,6 +10,12 @@ It supports all image types supported by `ImageMagick
|
||||
<https://www.imagemagick.org>`_. It even works over SSH. For details, see
|
||||
the :doc:`kitty graphics protocol </graphics-protocol>`.
|
||||
|
||||
You might want to create an alias in your shell's configuration files::
|
||||
|
||||
alias icat="kitty +kitten icat"
|
||||
|
||||
Then you can simply use ``icat image.png`` to view images.
|
||||
|
||||
.. note::
|
||||
|
||||
`ImageMagick <https://www.imagemagick.org>`_ must be installed for ``icat`` to
|
||||
|
||||
@@ -8,10 +8,10 @@ glyph in video RAM so that font rendering is not a bottleneck. Interaction
|
||||
with child programs takes place in a separate thread from rendering, to improve
|
||||
smoothness.
|
||||
|
||||
There are two parameters you can tune to adjust the performance. ``repaint_delay``
|
||||
and ``input_delay``. These control the artificial delays introduced into the
|
||||
There are two parameters you can tune to adjust the performance. :opt:`repaint_delay`
|
||||
and :opt:`input_delay`. These control the artificial delays introduced into the
|
||||
render loop to reduce CPU usage. See :ref:`conf-kitty-performance` for details.
|
||||
See also the ``sync_to_monitor`` option to further decrease latency at the cost
|
||||
See also the :opt:`sync_to_monitor` option to further decrease latency at the cost
|
||||
of some `tearing <https://en.wikipedia.org/wiki/Screen_tearing>`_ while scrolling.
|
||||
|
||||
You can generate detailed per-function performance data using
|
||||
|
||||
88
docs/pipe.rst
Normal file
88
docs/pipe.rst
Normal file
@@ -0,0 +1,88 @@
|
||||
Working with the screen and history buffer contents
|
||||
======================================================
|
||||
|
||||
|
||||
You can pipe the contents of the current screen and history buffer as
|
||||
:file:`STDIN` to an arbitrary program using the ``pipe`` function. The program
|
||||
can be displayed in a kitty window or overlay.
|
||||
|
||||
For example, the following in :file:`kitty.conf` will open the scrollback
|
||||
buffer in less in an overlay window, when you press :kbd:`F1`::
|
||||
|
||||
map f1 pipe @ansi overlay less +G -R
|
||||
|
||||
The syntax of the ``pipe`` function is::
|
||||
|
||||
pipe <input placeholder> <destination window type> <command line to run>
|
||||
|
||||
|
||||
The piping environment
|
||||
--------------------------
|
||||
|
||||
The program to which the data is piped has a special environment variable
|
||||
declared, ``KITTY_PIPE_DATA`` whose contents are::
|
||||
|
||||
KITTY_PIPE_DATA={scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}
|
||||
|
||||
where ``scrolled_by`` is the number of lines kitty is currently scrolled by,
|
||||
``cursor_(x|y)`` is the position of the cursor on the screen with ``(1,1)``
|
||||
being the top left corner and ``{lines},{columns}`` being the number of rows
|
||||
and columns of the screen.
|
||||
|
||||
You can choose where to run the pipe program:
|
||||
|
||||
``overlay``
|
||||
An overlay window over the current kitty window
|
||||
|
||||
``window``
|
||||
A new kitty window
|
||||
|
||||
``os_window``
|
||||
A new top-level window
|
||||
|
||||
``tab``
|
||||
A new window in a new tab
|
||||
|
||||
``none``
|
||||
Run it in the background
|
||||
|
||||
|
||||
Input placeholders
|
||||
--------------------
|
||||
|
||||
There are various different kinds of placeholders
|
||||
|
||||
``@text``
|
||||
Plain text, current screen + scrollback buffer
|
||||
|
||||
``@ansi``
|
||||
Text with formatting, current screen + scrollback buffer
|
||||
|
||||
``@screen``
|
||||
Plain text, only current screen
|
||||
|
||||
``@ansi_screen``
|
||||
Text with formatting, only current screen
|
||||
|
||||
``@alternate``
|
||||
Plain text, secondary screen. The secondary screen is the screen not currently displayed. For
|
||||
example if you run a fullscreen terminal application, the secondary screen will
|
||||
be the screen you return to when quitting the application.
|
||||
|
||||
``@ansi_alternate``
|
||||
Text with formatting, secondary screen.
|
||||
|
||||
``@alternate_scrollback``
|
||||
Plain text, secondary screen + scrollback, if any.
|
||||
|
||||
``@ansi_alternate_scrollback``
|
||||
Text with formatting, secondary screen + scrollback, if any.
|
||||
|
||||
``none``
|
||||
No input
|
||||
|
||||
|
||||
You can also add the suffix ``_wrap`` to the placeholder, in which case kitty
|
||||
will insert the carriage return at every line wrap location (where long lines
|
||||
are wrapped at screen edges). This is useful if you want to pipe to program
|
||||
that wants to duplicate the screen layout of the screen.
|
||||
@@ -6,7 +6,7 @@ These are typically in the form of new or re-purposed escape codes. While these
|
||||
extensions are currently |kitty| specific, it would be nice to get some of them
|
||||
adopted more broadly, to push the state of terminal emulators forward.
|
||||
|
||||
The goal of these extensions is to be as small an unobtrusive as possible,
|
||||
The goal of these extensions is to be as small and unobtrusive as possible,
|
||||
while filling in some gaps in the existing xterm protocol. In particular, one
|
||||
of the goals of this specification is explicitly not to "re-imagine" the tty.
|
||||
The tty should remain what it is -- a device for efficiently processing text
|
||||
@@ -141,6 +141,8 @@ For example::
|
||||
This encoding means each key event is represented by 8 or 9 printable ascii
|
||||
only bytes, for maximum robustness.
|
||||
|
||||
Support for this mode is indicated by the ``fullkbd`` boolean capability
|
||||
in the terminfo database, in case querying for it via DECQRM is inconvenient.
|
||||
|
||||
.. _ext_styles:
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Let's walk through a few examples of controlling |kitty|.
|
||||
|
||||
Start by running |kitty| as::
|
||||
|
||||
kitty -o allow_remote_control=yes --window-layout tall
|
||||
kitty -o allow_remote_control=yes -o enabled_layouts=tall
|
||||
|
||||
In order for control to work, :opt:`allow_remote_control` must be enabled in
|
||||
:file:`kitty.conf`. Here we turn it on explicitly at the command line.
|
||||
|
||||
@@ -87,6 +87,11 @@ def parse_ucd():
|
||||
if category.startswith('M'):
|
||||
marks.add(codepoint)
|
||||
|
||||
# Some common synonyms
|
||||
word_search_map['bee'] |= word_search_map['honeybee']
|
||||
word_search_map['lambda'] |= word_search_map['lamda']
|
||||
word_search_map['lamda'] |= word_search_map['lambda']
|
||||
|
||||
|
||||
def split_two(line):
|
||||
spec, rest = line.split(';', 1)
|
||||
@@ -360,16 +365,15 @@ def gen_names():
|
||||
root = TrieNode()
|
||||
all_trie_nodes.append(root)
|
||||
|
||||
def add_word(word_idx):
|
||||
word = word_map[word_idx]
|
||||
def add_word(word_idx, word):
|
||||
parent = root
|
||||
for letter in map(ord, word):
|
||||
idx = parent.add_letter(letter)
|
||||
parent = all_trie_nodes[idx]
|
||||
parent.match_offset = offsets_array[word_idx]
|
||||
|
||||
for i in range(len(word_map)):
|
||||
add_word(i)
|
||||
for i, word in enumerate(word_map):
|
||||
add_word(i, word)
|
||||
children_array = [0]
|
||||
for node in all_trie_nodes:
|
||||
if node.children:
|
||||
|
||||
51
glfw/backend_utils.c
vendored
51
glfw/backend_utils.c
vendored
@@ -261,3 +261,54 @@ closeFds(int *fds, size_t count) {
|
||||
fds++;
|
||||
}
|
||||
}
|
||||
|
||||
// Splits and translates a text/uri-list into separate file paths
|
||||
// NOTE: This function destroys the provided string
|
||||
//
|
||||
char** parseUriList(char* text, int* count)
|
||||
{
|
||||
const char* prefix = "file://";
|
||||
char** paths = NULL;
|
||||
char* line;
|
||||
|
||||
*count = 0;
|
||||
|
||||
while ((line = strtok(text, "\r\n")))
|
||||
{
|
||||
text = NULL;
|
||||
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
||||
{
|
||||
line += strlen(prefix);
|
||||
// TODO: Validate hostname
|
||||
while (*line != '/')
|
||||
line++;
|
||||
}
|
||||
|
||||
(*count)++;
|
||||
|
||||
char* path = calloc(strlen(line) + 1, 1);
|
||||
paths = realloc(paths, *count * sizeof(char*));
|
||||
paths[*count - 1] = path;
|
||||
|
||||
while (*line)
|
||||
{
|
||||
if (line[0] == '%' && line[1] && line[2])
|
||||
{
|
||||
const char digits[3] = { line[1], line[2], '\0' };
|
||||
*path = strtol(digits, NULL, 16);
|
||||
line += 2;
|
||||
}
|
||||
else
|
||||
*path = *line;
|
||||
|
||||
path++;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
1
glfw/backend_utils.h
vendored
1
glfw/backend_utils.h
vendored
@@ -71,3 +71,4 @@ int pollForEvents(EventLoopData *eld, double timeout);
|
||||
unsigned dispatchTimers(EventLoopData *eld);
|
||||
void closeFds(int *fds, size_t count);
|
||||
void initPollData(EventLoopData *eld, int wakeup_fd, int display_fd);
|
||||
char** parseUriList(char* text, int* count);
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
|
||||
#include "internal.h"
|
||||
#include <sys/param.h> // For MAXPATHLEN
|
||||
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200
|
||||
#define NSEventMaskKeyUp NSKeyUpMask
|
||||
#define NSEventMaskKeyDown NSKeyDownMask
|
||||
#define NSEventModifierFlagCommand NSCommandKeyMask
|
||||
#endif
|
||||
|
||||
// Change to our application bundle's resources directory, if present
|
||||
//
|
||||
@@ -274,17 +278,21 @@ static GLFWbool initializeTIS(void)
|
||||
return updateUnicodeDataNS();
|
||||
}
|
||||
|
||||
@interface GLFWLayoutListener : NSObject
|
||||
@interface GLFWHelper : NSObject
|
||||
@end
|
||||
|
||||
@implementation GLFWLayoutListener
|
||||
@implementation GLFWHelper
|
||||
|
||||
- (void)selectedKeyboardInputSourceChanged:(NSObject* )object
|
||||
{
|
||||
updateUnicodeDataNS();
|
||||
}
|
||||
|
||||
@end
|
||||
- (void)doNothing:(id)object
|
||||
{
|
||||
}
|
||||
|
||||
@end // GLFWHelper
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -294,13 +302,53 @@ static GLFWbool initializeTIS(void)
|
||||
int _glfwPlatformInit(void)
|
||||
{
|
||||
_glfw.ns.autoreleasePool = [[NSAutoreleasePool alloc] init];
|
||||
_glfw.ns.helper = [[GLFWHelper alloc] init];
|
||||
|
||||
[NSThread detachNewThreadSelector:@selector(doNothing:)
|
||||
toTarget:_glfw.ns.helper
|
||||
withObject:nil];
|
||||
|
||||
[NSApplication sharedApplication];
|
||||
|
||||
NSEvent* (^keydown_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||
{
|
||||
NSEventModifierFlags modifierFlags = [event modifierFlags];
|
||||
if (event.keyCode == kVK_Tab && (modifierFlags == NSEventModifierFlagControl || modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[NSApp keyWindow].contentView keyDown:event];
|
||||
}
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
NSEvent* (^keyup_block)(NSEvent*) = ^ NSEvent* (NSEvent* event)
|
||||
{
|
||||
NSEventModifierFlags modifierFlags = [event modifierFlags];
|
||||
if ([event modifierFlags] & NSEventModifierFlagCommand) {
|
||||
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
||||
// This works around an AppKit bug, where key up events while holding
|
||||
// down the command key don't get sent to the key window.
|
||||
[[NSApp keyWindow] sendEvent:event];
|
||||
}
|
||||
if (event.keyCode == kVK_Tab && (modifierFlags == NSEventModifierFlagControl || modifierFlags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[NSApp keyWindow].contentView keyUp:event];
|
||||
}
|
||||
|
||||
return event;
|
||||
};
|
||||
|
||||
_glfw.ns.keyUpMonitor =
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyUp
|
||||
handler:keyup_block];
|
||||
_glfw.ns.keyDownMonitor =
|
||||
[NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskKeyDown
|
||||
handler:keydown_block];
|
||||
if (_glfw.hints.init.ns.chdir)
|
||||
changeToResourcesDirectory();
|
||||
|
||||
_glfw.ns.listener = [[GLFWLayoutListener alloc] init];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
addObserver:_glfw.ns.listener
|
||||
addObserver:_glfw.ns.helper
|
||||
selector:@selector(selectedKeyboardInputSourceChanged:)
|
||||
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
||||
object:nil];
|
||||
@@ -345,17 +393,21 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.ns.delegate = nil;
|
||||
}
|
||||
|
||||
if (_glfw.ns.listener)
|
||||
if (_glfw.ns.helper)
|
||||
{
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
removeObserver:_glfw.ns.listener
|
||||
removeObserver:_glfw.ns.helper
|
||||
name:NSTextInputContextKeyboardSelectionDidChangeNotification
|
||||
object:nil];
|
||||
[[NSNotificationCenter defaultCenter]
|
||||
removeObserver:_glfw.ns.listener];
|
||||
[_glfw.ns.listener release];
|
||||
_glfw.ns.listener = nil;
|
||||
removeObserver:_glfw.ns.helper];
|
||||
[_glfw.ns.helper release];
|
||||
_glfw.ns.helper = nil;
|
||||
}
|
||||
if (_glfw.ns.keyUpMonitor)
|
||||
[NSEvent removeMonitor:_glfw.ns.keyUpMonitor];
|
||||
if (_glfw.ns.keyDownMonitor)
|
||||
[NSEvent removeMonitor:_glfw.ns.keyDownMonitor];
|
||||
|
||||
free(_glfw.ns.clipboardString);
|
||||
|
||||
|
||||
@@ -467,7 +467,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode *mode)
|
||||
CVDisplayLinkRelease(link);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
uint32_t i, size = CGDisplayGammaTableCapacity(monitor->ns.displayID);
|
||||
CGGammaValue* values = calloc(size * 3, sizeof(CGGammaValue));
|
||||
@@ -489,6 +489,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
}
|
||||
|
||||
free(values);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
@@ -523,4 +524,3 @@ GLFWAPI CGDirectDisplayID glfwGetCocoaMonitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(kCGNullDirectDisplay);
|
||||
return monitor->ns.displayID;
|
||||
}
|
||||
|
||||
|
||||
11
glfw/cocoa_platform.h
vendored
11
glfw/cocoa_platform.h
vendored
@@ -27,18 +27,17 @@
|
||||
#include <stdint.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include <Carbon/Carbon.h>
|
||||
#if defined(__OBJC__)
|
||||
#import <Carbon/Carbon.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#include <Carbon/Carbon.h>
|
||||
#include <ApplicationServices/ApplicationServices.h>
|
||||
typedef void* id;
|
||||
#endif
|
||||
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
|
||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
{
|
||||
@@ -102,6 +101,8 @@ typedef struct _GLFWwindowNS
|
||||
|
||||
// The text input filter callback
|
||||
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
||||
// The toggle fullscreen intercept callback
|
||||
GLFWcocoatogglefullscreenfun toggleFullscreenCallback;
|
||||
// Dead key state
|
||||
UInt32 deadKeyState;
|
||||
} _GLFWwindowNS;
|
||||
@@ -117,7 +118,9 @@ typedef struct _GLFWlibraryNS
|
||||
TISInputSourceRef inputSource;
|
||||
IOHIDManagerRef hidManager;
|
||||
id unicodeData;
|
||||
id listener;
|
||||
id helper;
|
||||
id keyUpMonitor;
|
||||
id keyDownMonitor;
|
||||
|
||||
char keyName[64];
|
||||
char text[256];
|
||||
|
||||
@@ -47,9 +47,13 @@
|
||||
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
|
||||
#define NSEventMaskAny NSAnyEventMask
|
||||
#define NSEventTypeApplicationDefined NSApplicationDefined
|
||||
#define NSEventTypeKeyUp NSKeyUp
|
||||
#endif
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
|
||||
#define NSPasteboardTypeFileURL NSFilenamesPboardType
|
||||
#define NSBitmapFormatAlphaNonpremultiplied NSAlphaNonpremultipliedBitmapFormat
|
||||
#define NSPasteboardTypeString NSStringPboardType
|
||||
#endif
|
||||
|
||||
// Returns the style mask corresponding to the window settings
|
||||
//
|
||||
@@ -307,6 +311,7 @@ static int translateKey(unsigned int key, GLFWbool apply_keymap)
|
||||
K(';', SEMICOLON);
|
||||
K('[', LEFT_BRACKET);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('+', PLUS);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K('\\', BACKSLASH);
|
||||
#undef K
|
||||
@@ -339,6 +344,8 @@ static NSUInteger translateKeyToModifierFlag(int key)
|
||||
case GLFW_KEY_LEFT_SUPER:
|
||||
case GLFW_KEY_RIGHT_SUPER:
|
||||
return NSEventModifierFlagCommand;
|
||||
case GLFW_KEY_CAPS_LOCK:
|
||||
return NSEventModifierFlagCapsLock;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -450,6 +457,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
|
||||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||
updateCursorMode(window);
|
||||
if (_glfw.ns.disabledCursorWindow != window && cursorInClientArea(window))
|
||||
{
|
||||
double x = 0, y = 0;
|
||||
_glfwPlatformGetCursorPos(window, &x, &y);
|
||||
_glfwInputCursorPos(window, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
@@ -549,8 +562,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
markedText = [[NSMutableAttributedString alloc] init];
|
||||
|
||||
[self updateTrackingAreas];
|
||||
[self registerForDraggedTypes:[NSArray arrayWithObjects:
|
||||
NSFilenamesPboardType, nil]];
|
||||
[self registerForDraggedTypes:@[NSPasteboardTypeFileURL]];
|
||||
}
|
||||
|
||||
return self;
|
||||
@@ -563,6 +575,10 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (_GLFWwindow*)glfwWindow {
|
||||
return window;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return [window->ns.object isOpaque];
|
||||
@@ -583,6 +599,21 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)updateLayer
|
||||
{
|
||||
if (window->context.client != GLFW_NO_API) {
|
||||
@try {
|
||||
[window->context.nsgl.object update];
|
||||
} @catch (NSException *e) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Failed to update NSGL Context object with error: %s (%s)",
|
||||
[[e name] UTF8String], [[e reason] UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
_glfwInputWindowDamage(window);
|
||||
}
|
||||
|
||||
- (id)makeBackingLayer
|
||||
{
|
||||
if (window->ns.layer)
|
||||
@@ -596,6 +627,11 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
- (BOOL)acceptsFirstMouse:(NSEvent *)event
|
||||
{
|
||||
return NO; // changed by Kovid, to follow cocoa platform conventions
|
||||
}
|
||||
|
||||
- (void)mouseDown:(NSEvent *)event
|
||||
{
|
||||
_glfwInputMouseClick(window,
|
||||
@@ -824,28 +860,22 @@ is_ascii_control_char(char x) {
|
||||
window->ns.deadKeyState = 0;
|
||||
return;
|
||||
}
|
||||
debug_key(@"scancode: 0x%x (%s) %schar_count: %lu deadKeyState: %u ", scancode, safe_name_for_scancode(scancode), format_mods(mods), char_count, window->ns.deadKeyState);
|
||||
if (process_text) {
|
||||
// We check if cocoa wants to insert text, as UCKeyTranslate
|
||||
// inserts text even when the cmd key is pressed. For instance,
|
||||
// cmd+a will result in the text a.
|
||||
// this will call insertText which will fill up _glfw.ns.text
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
|
||||
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
|
||||
_glfw.ns.text[0] = 0;
|
||||
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
|
||||
} else {
|
||||
window->ns.deadKeyState = 0;
|
||||
}
|
||||
if (window->ns.deadKeyState && char_count == 0) {
|
||||
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
|
||||
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
|
||||
if (window->ns.deadKeyState && (char_count == 0 || scancode == 0x75)) {
|
||||
// 0x75 is the delete key which needs to be ignored during a compose sequence
|
||||
debug_key(@"Ignoring dead key.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
|
||||
scancode, safe_name_for_scancode(scancode), format_mods(mods),
|
||||
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
||||
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
|
||||
debug_key(@"text: %s glfw_key: %s\n",
|
||||
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
||||
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
||||
}
|
||||
|
||||
@@ -885,52 +915,64 @@ is_ascii_control_char(char x) {
|
||||
deltaX = [event scrollingDeltaX];
|
||||
deltaY = [event scrollingDeltaY];
|
||||
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
|
||||
if (flags) {
|
||||
float xscale = 1, yscale = 1;
|
||||
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
||||
if (xscale > 0) deltaX *= xscale;
|
||||
if (yscale > 0) deltaY *= yscale;
|
||||
}
|
||||
|
||||
if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
switch([event momentumPhase]) {
|
||||
case NSEventPhaseBegan:
|
||||
flags |= (1 << 1); break;
|
||||
case NSEventPhaseStationary:
|
||||
flags |= (2 << 1); break;
|
||||
case NSEventPhaseChanged:
|
||||
flags |= (3 << 1); break;
|
||||
case NSEventPhaseEnded:
|
||||
flags |= (4 << 1); break;
|
||||
case NSEventPhaseCancelled:
|
||||
flags |= (5 << 1); break;
|
||||
case NSEventPhaseMayBegin:
|
||||
flags |= (6 << 1); break;
|
||||
case NSEventPhaseNone:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
|
||||
== NSDragOperationGeneric)
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
return NSDragOperationGeneric;
|
||||
}
|
||||
|
||||
return NSDragOperationNone;
|
||||
}
|
||||
|
||||
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
return YES;
|
||||
// HACK: We don't know what to say here because we don't know what the
|
||||
// application wants to do with the paths
|
||||
return NSDragOperationGeneric;
|
||||
}
|
||||
|
||||
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
||||
NSArray* files = [pasteboard propertyListForType:NSFilenamesPboardType];
|
||||
|
||||
const NSRect contentRect = [window->ns.view frame];
|
||||
_glfwInputCursorPos(window,
|
||||
[sender draggingLocation].x,
|
||||
contentRect.size.height - [sender draggingLocation].y);
|
||||
NSPasteboard* pasteboard = [sender draggingPasteboard];
|
||||
NSDictionary* options = @{NSPasteboardURLReadingFileURLsOnlyKey:@YES};
|
||||
NSArray* urls = [pasteboard readObjectsForClasses:@[[NSURL class]]
|
||||
options:options];
|
||||
if (!urls) return NO;
|
||||
const NSUInteger count = [urls count];
|
||||
|
||||
const NSUInteger count = [files count];
|
||||
if (count)
|
||||
{
|
||||
NSEnumerator* e = [files objectEnumerator];
|
||||
char** paths = calloc(count, sizeof(char*));
|
||||
NSUInteger i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
paths[i] = _glfw_strdup([[e nextObject] UTF8String]);
|
||||
for (NSUInteger i = 0; i < count; i++)
|
||||
paths[i] = _glfw_strdup([[urls objectAtIndex:i] fileSystemRepresentation]);
|
||||
|
||||
_glfwInputDrop(window, (int) count, (const char**) paths);
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
for (NSUInteger i = 0; i < count; i++)
|
||||
free(paths[i]);
|
||||
free(paths);
|
||||
}
|
||||
@@ -938,11 +980,6 @@ is_ascii_control_char(char x) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)concludeDragOperation:(id <NSDraggingInfo>)sender
|
||||
{
|
||||
[self setNeedsDisplay:YES];
|
||||
}
|
||||
|
||||
- (BOOL)hasMarkedText
|
||||
{
|
||||
return [markedText length] > 0;
|
||||
@@ -1040,70 +1077,21 @@ is_ascii_control_char(char x) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// GLFW application class
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
@interface GLFWApplication : NSApplication
|
||||
- (void)toggleFullScreen:(nullable id)sender
|
||||
{
|
||||
NSArray* nibObjects;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GLFWApplication
|
||||
|
||||
- (void)sendEvent:(NSEvent *)event
|
||||
{
|
||||
NSEventType etype = [event type];
|
||||
NSEventModifierFlags flags;
|
||||
switch(etype) {
|
||||
case NSEventTypeKeyUp:
|
||||
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
if (flags & NSEventModifierFlagCommand) {
|
||||
// From http://cocoadev.com/index.pl?GameKeyboardHandlingAlmost
|
||||
// This works around an AppKit bug, where key up events while holding
|
||||
// down the command key don't get sent to the key window.
|
||||
[[self keyWindow] sendEvent:event];
|
||||
return;
|
||||
}
|
||||
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[self keyWindow].contentView keyUp:event];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case NSEventTypeKeyDown:
|
||||
flags = [event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
if (event.keyCode == kVK_Tab && (flags == NSEventModifierFlagControl || flags == (NSEventModifierFlagControl | NSEventModifierFlagShift))) {
|
||||
// Cocoa swallows Ctrl+Tab to cycle between views
|
||||
[[self keyWindow].contentView keyDown:event];
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
GLFWContentView *view = [self contentView];
|
||||
if (view)
|
||||
{
|
||||
_GLFWwindow *window = [view glfwWindow];
|
||||
if (window && window->ns.toggleFullscreenCallback && window->ns.toggleFullscreenCallback((GLFWwindow*)window) == 1)
|
||||
return;
|
||||
}
|
||||
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
|
||||
// No-op thread entry point
|
||||
//
|
||||
- (void)doNothing:(id)object
|
||||
{
|
||||
}
|
||||
|
||||
- (void)loadMainMenu
|
||||
{ // removed by Kovid as it generated compiler warnings
|
||||
[super toggleFullScreen:sender];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// Set up the menu bar (manually)
|
||||
// This is nasty, nasty stuff -- calls to undocumented semi-private APIs that
|
||||
// could go away at any moment, lots of stuff that really should be
|
||||
@@ -1213,32 +1201,9 @@ static void createMenuBar(void)
|
||||
//
|
||||
static GLFWbool initializeAppKit(void)
|
||||
{
|
||||
if (NSApp)
|
||||
if (_glfw.ns.delegate)
|
||||
return GLFW_TRUE;
|
||||
|
||||
// Implicitly create shared NSApplication instance
|
||||
[GLFWApplication sharedApplication];
|
||||
|
||||
// Make Cocoa enter multi-threaded mode
|
||||
[NSThread detachNewThreadSelector:@selector(doNothing:)
|
||||
toTarget:NSApp
|
||||
withObject:nil];
|
||||
|
||||
if (_glfw.hints.init.ns.menubar)
|
||||
{
|
||||
// In case we are unbundled, make us a proper UI application
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
// Menu bar setup must go between sharedApplication above and
|
||||
// finishLaunching below, in order to properly emulate the behavior
|
||||
// of NSApplicationMain
|
||||
|
||||
if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"])
|
||||
[NSApp loadMainMenu];
|
||||
else
|
||||
createMenuBar();
|
||||
}
|
||||
|
||||
// There can only be one application delegate, but we allocate it the
|
||||
// first time a window is created to keep all window code in this file
|
||||
_glfw.ns.delegate = [[GLFWApplicationDelegate alloc] init];
|
||||
@@ -1248,15 +1213,29 @@ static GLFWbool initializeAppKit(void)
|
||||
"Cocoa: Failed to create application delegate");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
[NSApp setDelegate:_glfw.ns.delegate];
|
||||
|
||||
if (_glfw.hints.init.ns.menubar)
|
||||
{
|
||||
// In case we are unbundled, make us a proper UI application
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||
|
||||
// Menu bar setup must go between sharedApplication above and
|
||||
// finishLaunching below, in order to properly emulate the behavior
|
||||
// of NSApplicationMain
|
||||
|
||||
// disabled by Kovid
|
||||
/* if ([[NSBundle mainBundle] pathForResource:@"MainMenu" ofType:@"nib"]) */
|
||||
/* [NSApp loadMainMenu]; */
|
||||
/* else */
|
||||
createMenuBar();
|
||||
}
|
||||
|
||||
[NSApp run];
|
||||
|
||||
// Press and Hold prevents some keys from emitting repeated characters
|
||||
NSDictionary* defaults =
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],
|
||||
@"ApplePressAndHoldEnabled",
|
||||
nil];
|
||||
NSDictionary* defaults = @{@"ApplePressAndHoldEnabled":@NO};
|
||||
|
||||
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
|
||||
|
||||
return GLFW_TRUE;
|
||||
@@ -1553,6 +1532,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = (float) (pixels.size.height / points.size.height);
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return [NSEvent doubleClickInterval];
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
[window->ns.object miniaturize:nil];
|
||||
@@ -1763,9 +1747,6 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
if (!initializeAppKit())
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
@@ -1775,7 +1756,7 @@ void _glfwPlatformPollEvents(void)
|
||||
if (event == nil)
|
||||
break;
|
||||
|
||||
[NSApp sendEvent:event];
|
||||
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
}
|
||||
|
||||
[_glfw.ns.autoreleasePool drain];
|
||||
@@ -1791,7 +1772,7 @@ void _glfwPlatformWaitEvents(void)
|
||||
untilDate:[NSDate distantFuture]
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
[NSApp sendEvent:event];
|
||||
if ([event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
}
|
||||
@@ -1803,8 +1784,7 @@ void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||
untilDate:date
|
||||
inMode:NSDefaultRunLoopMode
|
||||
dequeue:YES];
|
||||
if (event)
|
||||
[NSApp sendEvent:event];
|
||||
if (event && [event type] != NSEventTypeApplicationDefined) [NSApp sendEvent:event];
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
}
|
||||
@@ -1907,8 +1887,6 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
NSImage* native;
|
||||
NSBitmapImageRep* rep;
|
||||
|
||||
if (!initializeAppKit())
|
||||
return GLFW_FALSE;
|
||||
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
||||
if (native == nil)
|
||||
return GLFW_FALSE;
|
||||
@@ -1924,7 +1902,7 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSCalibratedRGBColorSpace
|
||||
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
||||
bitmapFormat:NSBitmapFormatAlphaNonpremultiplied
|
||||
bytesPerRow:src->width * 4
|
||||
bitsPerPixel:32];
|
||||
if (rep == nil)
|
||||
@@ -1945,8 +1923,6 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||
{
|
||||
if (!initializeAppKit())
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (shape == GLFW_ARROW_CURSOR)
|
||||
cursor->ns.object = [NSCursor arrowCursor];
|
||||
@@ -1986,26 +1962,24 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
NSArray* types = [NSArray arrayWithObjects:NSStringPboardType, nil];
|
||||
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
[pasteboard declareTypes:types owner:nil];
|
||||
[pasteboard declareTypes:@[NSPasteboardTypeString] owner:nil];
|
||||
[pasteboard setString:[NSString stringWithUTF8String:string]
|
||||
forType:NSStringPboardType];
|
||||
forType:NSPasteboardTypeString];
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetClipboardString(void)
|
||||
{
|
||||
NSPasteboard* pasteboard = [NSPasteboard generalPasteboard];
|
||||
|
||||
if (![[pasteboard types] containsObject:NSStringPboardType])
|
||||
if (![[pasteboard types] containsObject:NSPasteboardTypeString])
|
||||
{
|
||||
_glfwInputError(GLFW_FORMAT_UNAVAILABLE,
|
||||
"Cocoa: Failed to retrieve string from pasteboard");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
NSString* object = [pasteboard stringForType:NSStringPboardType];
|
||||
NSString* object = [pasteboard stringForType:NSPasteboardTypeString];
|
||||
if (!object)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -2114,6 +2088,14 @@ GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *hand
|
||||
return previous;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *handle, GLFWcocoatogglefullscreenfun callback) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
GLFWcocoatogglefullscreenfun previous = window->ns.toggleFullscreenCallback;
|
||||
window->ns.toggleFullscreenCallback = callback;
|
||||
return previous;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback) {
|
||||
GLFWapplicationshouldhandlereopenfun previous = handle_reopen_callback;
|
||||
handle_reopen_callback = callback;
|
||||
@@ -2181,6 +2163,7 @@ GLFWAPI void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, unsigned sho
|
||||
K(';', SEMICOLON);
|
||||
K('[', LEFT_BRACKET);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('+', PLUS);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K('\\', BACKSLASH);
|
||||
|
||||
|
||||
5
glfw/egl_context.h
vendored
5
glfw/egl_context.h
vendored
@@ -43,10 +43,6 @@ typedef Window EGLNativeWindowType;
|
||||
#define EGLAPIENTRY
|
||||
typedef struct wl_display* EGLNativeDisplayType;
|
||||
typedef struct wl_egl_window* EGLNativeWindowType;
|
||||
#elif defined(_GLFW_MIR)
|
||||
#define EGLAPIENTRY
|
||||
typedef MirEGLNativeDisplayType EGLNativeDisplayType;
|
||||
typedef MirEGLNativeWindowType EGLNativeWindowType;
|
||||
#else
|
||||
#error "No supported EGL platform selected"
|
||||
#endif
|
||||
@@ -216,4 +212,3 @@ GLFWbool _glfwChooseVisualEGL(const _GLFWwndconfig* wndconfig,
|
||||
const _GLFWfbconfig* fbconfig,
|
||||
Visual** visual, int* depth);
|
||||
#endif /*_GLFW_X11*/
|
||||
|
||||
|
||||
91
glfw/glfw.py
91
glfw/glfw.py
@@ -5,7 +5,6 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
@@ -22,7 +21,7 @@ def wayland_protocol_file_name(base, ext='c'):
|
||||
return 'wayland-{}-client-protocol.{}'.format(base, ext)
|
||||
|
||||
|
||||
def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
def init_env(env, pkg_config, at_least_version, test_compile, module='x11'):
|
||||
ans = env.copy()
|
||||
ans.cflags = [
|
||||
x for x in ans.cflags
|
||||
@@ -32,10 +31,11 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
ans.cflags.append('-pthread')
|
||||
ans.ldpaths.append('-pthread')
|
||||
ans.cflags.append('-fpic')
|
||||
ans.cflags.append('-D_GLFW_' + module.upper())
|
||||
ans.cflags.append('-D_GLFW_BUILD_DLL')
|
||||
ans.cppflags.append('-D_GLFW_' + module.upper())
|
||||
ans.cppflags.append('-D_GLFW_BUILD_DLL')
|
||||
|
||||
if is_macos:
|
||||
ans.cppflags.append('-DGL_SILENCE_DEPRECATION')
|
||||
ans.ldpaths.extend(
|
||||
"-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo".
|
||||
split()
|
||||
@@ -76,6 +76,14 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon dbus-1'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
has_memfd_create = test_compile(env.cc, '-Werror', src='''#define _GNU_SOURCE
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
int main(void) {
|
||||
return syscall(__NR_memfd_create, "test", 0);
|
||||
}''')
|
||||
if has_memfd_create:
|
||||
ans.cppflags.append('-DHAS_MEMFD_CREATE')
|
||||
|
||||
return ans
|
||||
|
||||
@@ -128,16 +136,6 @@ def collect_source_information():
|
||||
return ans
|
||||
|
||||
|
||||
def patch_in_file(path, pfunc):
|
||||
with open(path, 'r+') as f:
|
||||
raw = f.read()
|
||||
nraw = pfunc(raw)
|
||||
if raw == nraw:
|
||||
raise SystemExit('Patching of {} failed'.format(path))
|
||||
f.seek(0), f.truncate()
|
||||
f.write(nraw)
|
||||
|
||||
|
||||
class Arg:
|
||||
|
||||
def __init__(self, decl):
|
||||
@@ -189,7 +187,7 @@ class Function:
|
||||
return ans
|
||||
|
||||
|
||||
def generate_wrappers(glfw_header, glfw_native_header):
|
||||
def generate_wrappers(glfw_header):
|
||||
src = open(glfw_header).read()
|
||||
functions = []
|
||||
first = None
|
||||
@@ -202,15 +200,18 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
functions.append(Function(decl))
|
||||
for line in '''\
|
||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
||||
void* glfwGetNSGLContext(GLFWwindow *window)
|
||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||
GLFWcocoatogglefullscreenfun glfwSetCocoaToggleFullscreenIntercept(GLFWwindow *window, GLFWcocoatogglefullscreenfun callback)
|
||||
GLFWapplicationshouldhandlereopenfun glfwSetApplicationShouldHandleReopen(GLFWapplicationshouldhandlereopenfun callback)
|
||||
void glfwGetCocoaKeyEquivalent(int glfw_key, int glfw_mods, void* cocoa_key, void* cocoa_mods)
|
||||
void* glfwGetX11Display(void)
|
||||
int32_t glfwGetX11Window(GLFWwindow* window)
|
||||
void glfwSetX11SelectionString(const char* string)
|
||||
const char* glfwGetX11SelectionString(void)
|
||||
void glfwSetPrimarySelectionString(GLFWwindow* window, const char* string)
|
||||
const char* glfwGetPrimarySelectionString(GLFWwindow* window, void)
|
||||
int glfwGetXKBScancode(const char* key_name, int case_sensitive)
|
||||
void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, GLFWwaylandframecallbackfunc callback)
|
||||
'''.splitlines():
|
||||
if line:
|
||||
functions.append(Function(line.strip(), check_fail=False))
|
||||
@@ -223,11 +224,13 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
|
||||
{}
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
|
||||
{}
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
@@ -265,55 +268,9 @@ unload_glfw() {
|
||||
f.write(code)
|
||||
|
||||
|
||||
def from_glfw(glfw_dir):
|
||||
os.chdir(glfw_dir)
|
||||
sinfo = collect_source_information()
|
||||
files_to_copy = set()
|
||||
for x in sinfo.values():
|
||||
if isinstance(x, dict):
|
||||
headers, sources = x['headers'], x['sources']
|
||||
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_native_header = os.path.abspath('include/GLFW/glfw3native.h')
|
||||
os.chdir(base)
|
||||
for x in os.listdir('.'):
|
||||
if x.rpartition('.') in ('c', 'h'):
|
||||
os.unlink(x)
|
||||
for src in files_to_copy:
|
||||
shutil.copy2(src, '.')
|
||||
shutil.copy2(glfw_header, '.')
|
||||
patch_in_file('cocoa_window.m', lambda x: re.sub(
|
||||
r'[(]void[)]loadMainMenu.+?}', '(void)loadMainMenu\n{ // removed by Kovid as it generated compiler warnings \n}\n', x, flags=re.DOTALL))
|
||||
json.dump(
|
||||
sinfo,
|
||||
open('source-info.json', 'w'),
|
||||
indent=2,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True
|
||||
)
|
||||
generate_wrappers(glfw_header, glfw_native_header)
|
||||
|
||||
|
||||
def to_glfw(glfw_dir):
|
||||
src = base
|
||||
for x in os.listdir(src):
|
||||
if x in ('glfw.py', 'glfw3.h', '__pycache__', 'source-info.json') or x.startswith('wayland-'):
|
||||
continue
|
||||
xp = os.path.join(src, x)
|
||||
shutil.copyfile(xp, os.path.join(glfw_dir, 'src', x))
|
||||
shutil.copyfile(os.path.join(src, 'glfw3.h'), os.path.join(glfw_dir, 'include/GLFW/glfw3.h'))
|
||||
|
||||
|
||||
def main():
|
||||
glfw_dir = os.path.abspath(os.path.join(base, '../../glfw'))
|
||||
q = sys.argv[1].lower().replace('_', '-')
|
||||
if q == 'from-glfw':
|
||||
from_glfw(glfw_dir)
|
||||
elif q == 'to-glfw':
|
||||
to_glfw(glfw_dir)
|
||||
else:
|
||||
raise SystemExit('First argument must be one of to-glfw or from-glfw')
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
generate_wrappers('glfw3.h')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
51
glfw/glfw3.h
vendored
51
glfw/glfw3.h
vendored
@@ -407,6 +407,7 @@ extern "C" {
|
||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
|
||||
#define GLFW_KEY_PLUS 163
|
||||
|
||||
/* Function keys */
|
||||
#define GLFW_KEY_ESCAPE 256
|
||||
@@ -973,6 +974,10 @@ extern "C" {
|
||||
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
||||
*/
|
||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||
/*! @brief Window content area scaling window
|
||||
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
|
||||
*/
|
||||
#define GLFW_SCALE_TO_MONITOR 0x0002200C
|
||||
|
||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||
@@ -980,6 +985,8 @@ extern "C" {
|
||||
|
||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||
|
||||
#define GLFW_WAYLAND_APP_ID 0x00025001
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_NO_API 0
|
||||
@@ -1373,7 +1380,13 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] xoffset The scroll offset along the x-axis.
|
||||
* @param[in] yoffset The scroll offset along the y-axis.
|
||||
* @param[in] flags A bit-mask providing extra data about the event. flags & 1 will be true if and only if the offset values are "high-precision". Typically pixel values. Otherwise the offset values are number of lines.
|
||||
* @param[in] flags A bit-mask providing extra data about the event.
|
||||
* flags & 1 will be true if and only if the offset values are "high-precision".
|
||||
* Typically pixel values. Otherwise the offset values are number of lines.
|
||||
* (flags >> 1) & 7 will have value 1 for the start of momentum scrolling,
|
||||
* value 2 for stationary momentum scrolling, value 3 for momentum scrolling
|
||||
* in progress, value 4 for momentum scrolling ended, value 5 for momentum
|
||||
* scrolling cancelled and value 6 if scrolling may begin soon.
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
@@ -2115,9 +2128,9 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* monitor);
|
||||
|
||||
/*! @brief Generates a gamma ramp and sets it for the specified monitor.
|
||||
*
|
||||
* This function generates a 256-element gamma ramp from the specified exponent
|
||||
* and then calls @ref glfwSetGammaRamp with it. The value must be a finite
|
||||
* number greater than zero.
|
||||
* This function generates an appropriately sized gamma ramp from the
|
||||
* specified exponent and then calls @ref glfwSetGammaRamp with it. The value
|
||||
* must be a finite number greater than zero.
|
||||
*
|
||||
* The software controlled gamma ramp is applied _in addition_ to the hardware
|
||||
* gamma correction, which today is usually an approximation of sRGB gamma.
|
||||
@@ -2196,8 +2209,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* monitor);
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark Gamma ramp sizes other than 256 are not supported by all platforms
|
||||
* or graphics hardware.
|
||||
* @remark The size of the specified gamma ramp should match the size of the
|
||||
* current ramp for that monitor.
|
||||
*
|
||||
* @remark @win32 The gamma ramp size must be 256.
|
||||
*
|
||||
@@ -2924,6 +2937,32 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
|
||||
*/
|
||||
GLFWAPI void glfwGetWindowContentScale(GLFWwindow* window, float* xscale, float* yscale);
|
||||
|
||||
/*! @brief Returns the double click time interval.
|
||||
*
|
||||
* This function returns the maximum time between clicks to count as a
|
||||
* double click.
|
||||
*
|
||||
* The double click interval is a positive finite number greater than zero,
|
||||
* where zero means that no click is ever recognized as a double click. If the
|
||||
* system does not support a double click interval, this function always returns one half.
|
||||
*
|
||||
* @return The double click interval.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref double_click
|
||||
* @sa @ref click_interval
|
||||
* @sa @ref double_click_interval
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* window);
|
||||
|
||||
/*! @brief Returns the opacity of the whole window.
|
||||
*
|
||||
* This function returns the opacity of the window, including any decorations.
|
||||
|
||||
3
glfw/ibus_glfw.c
vendored
3
glfw/ibus_glfw.c
vendored
@@ -280,8 +280,7 @@ setup_connection(_GLFWIBUSData *ibus) {
|
||||
void
|
||||
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
|
||||
if (ibus->inited) return;
|
||||
if (!test_env_var("XMODIFIERS", "@im=ibus") && !test_env_var("GTK_IM_MODULE", "ibus") && !test_env_var("QT_IM_MODULE", "ibus") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
if (getenv("GLFW_IM_MODULE") && !test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
if (!test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
ibus->inited = GLFW_TRUE;
|
||||
setup_connection(ibus);
|
||||
}
|
||||
|
||||
17
glfw/input.c
vendored
17
glfw/input.c
vendored
@@ -487,6 +487,7 @@ const char* _glfwGetKeyName(int key)
|
||||
case GLFW_KEY_SLASH: return "SLASH";
|
||||
case GLFW_KEY_WORLD_1: return "WORLD 1";
|
||||
case GLFW_KEY_WORLD_2: return "WORLD 2";
|
||||
case GLFW_KEY_PLUS: return "PLUS";
|
||||
|
||||
// Function keys
|
||||
case GLFW_KEY_ESCAPE: return "ESCAPE";
|
||||
@@ -1375,6 +1376,22 @@ GLFWAPI const char* glfwGetClipboardString(GLFWwindow* handle)
|
||||
return _glfwPlatformGetClipboardString();
|
||||
}
|
||||
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
GLFWAPI void glfwSetPrimarySelectionString(GLFWwindow* handle, const char* string)
|
||||
{
|
||||
assert(string != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
_glfwPlatformSetPrimarySelectionString(string);
|
||||
}
|
||||
|
||||
GLFWAPI const char* glfwGetPrimarySelectionString(GLFWwindow* handle)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return _glfwPlatformGetPrimarySelectionString();
|
||||
}
|
||||
#endif
|
||||
|
||||
GLFWAPI double glfwGetTime(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0.0);
|
||||
|
||||
18
glfw/internal.h
vendored
18
glfw/internal.h
vendored
@@ -31,6 +31,8 @@
|
||||
#include "glfw_config.h"
|
||||
#endif
|
||||
|
||||
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||
|
||||
#if defined(GLFW_INCLUDE_GLCOREARB) || \
|
||||
defined(GLFW_INCLUDE_ES1) || \
|
||||
defined(GLFW_INCLUDE_ES2) || \
|
||||
@@ -127,7 +129,6 @@ typedef enum VkStructureType
|
||||
VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR = 1000004000,
|
||||
VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR = 1000005000,
|
||||
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
|
||||
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
|
||||
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
|
||||
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
|
||||
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
@@ -189,8 +190,6 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
|
||||
#include "x11_platform.h"
|
||||
#elif defined(_GLFW_WAYLAND)
|
||||
#include "wl_platform.h"
|
||||
#elif defined(_GLFW_MIR)
|
||||
#include "mir_platform.h"
|
||||
#elif defined(_GLFW_OSMESA)
|
||||
#include "null_platform.h"
|
||||
#else
|
||||
@@ -271,6 +270,7 @@ struct _GLFWwndconfig
|
||||
GLFWbool maximized;
|
||||
GLFWbool centerCursor;
|
||||
GLFWbool focusOnShow;
|
||||
GLFWbool scaleToMonitor;
|
||||
struct {
|
||||
GLFWbool retina;
|
||||
char frameName[256];
|
||||
@@ -279,6 +279,9 @@ struct _GLFWwndconfig
|
||||
char className[256];
|
||||
char instanceName[256];
|
||||
} x11;
|
||||
struct {
|
||||
char appId[256];
|
||||
} wl;
|
||||
};
|
||||
|
||||
// Context configuration
|
||||
@@ -565,8 +568,6 @@ struct _GLFWlibrary
|
||||
GLFWbool KHR_xcb_surface;
|
||||
#elif defined(_GLFW_WAYLAND)
|
||||
GLFWbool KHR_wayland_surface;
|
||||
#elif defined(_GLFW_MIR)
|
||||
GLFWbool KHR_mir_surface;
|
||||
#endif
|
||||
} vk;
|
||||
|
||||
@@ -618,11 +619,15 @@ void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
|
||||
float* xscale, float* yscale);
|
||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp);
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string);
|
||||
const char* _glfwPlatformGetClipboardString(void);
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string);
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void);
|
||||
#endif
|
||||
|
||||
int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode);
|
||||
void _glfwPlatformUpdateGamepadGUID(char* guid);
|
||||
@@ -652,6 +657,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
int* right, int* bottom);
|
||||
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
float* xscale, float* yscale);
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window);
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
|
||||
|
||||
3
glfw/linux_joystick.c
vendored
3
glfw/linux_joystick.c
vendored
@@ -228,7 +228,7 @@ static GLFWbool openJoystickDevice(const char* path)
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
strncpy(linjs.path, path, sizeof(linjs.path));
|
||||
strncpy(linjs.path, path, sizeof(linjs.path) - 1);
|
||||
memcpy(&js->linjs, &linjs, sizeof(linjs));
|
||||
|
||||
pollAbsState(js);
|
||||
@@ -431,4 +431,3 @@ int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
|
||||
void _glfwPlatformUpdateGamepadGUID(char* guid)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
54
glfw/memfd.h
vendored
Normal file
54
glfw/memfd.h
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef HAS_MEMFD_CREATE
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/syscall.h>
|
||||
static inline int memfd_create(const char *name, unsigned int flags) {
|
||||
return syscall(__NR_memfd_create, name, flags);
|
||||
}
|
||||
|
||||
#ifndef F_LINUX_SPECIFIC_BASE
|
||||
#define F_LINUX_SPECIFIC_BASE 1024
|
||||
#endif
|
||||
|
||||
#ifndef F_ADD_SEALS
|
||||
#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
|
||||
#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
|
||||
|
||||
#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */
|
||||
#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */
|
||||
#define F_SEAL_GROW 0x0004 /* prevent file from growing */
|
||||
#define F_SEAL_WRITE 0x0008 /* prevent writes */
|
||||
#endif
|
||||
|
||||
#ifndef MFD_CLOEXEC
|
||||
#define MFD_CLOEXEC 0x0001U
|
||||
#define MFD_ALLOW_SEALING 0x0002U
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
static inline int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink(tmpname);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
#endif
|
||||
27
glfw/monitor.c
vendored
27
glfw/monitor.c
vendored
@@ -427,11 +427,11 @@ GLFWAPI const GLFWvidmode* glfwGetVideoMode(GLFWmonitor* handle)
|
||||
|
||||
GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||
{
|
||||
int i;
|
||||
unsigned short values[256];
|
||||
unsigned int i;
|
||||
unsigned short* values;
|
||||
GLFWgammaramp ramp;
|
||||
const GLFWgammaramp* original;
|
||||
assert(handle != NULL);
|
||||
assert(gamma == gamma);
|
||||
assert(gamma >= 0.f);
|
||||
assert(gamma <= FLT_MAX);
|
||||
|
||||
@@ -442,13 +442,18 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||
_glfwInputError(GLFW_INVALID_VALUE, "Invalid gamma value %f", gamma);
|
||||
return;
|
||||
}
|
||||
original = glfwGetGammaRamp(handle);
|
||||
if (!original)
|
||||
return;
|
||||
|
||||
for (i = 0; i < 256; i++)
|
||||
values = calloc(original->size, sizeof(unsigned short));
|
||||
|
||||
for (i = 0; i < original->size; i++)
|
||||
{
|
||||
float value;
|
||||
|
||||
// Calculate intensity
|
||||
value = i / 255.f;
|
||||
value = i / (float) (original->size - 1);
|
||||
// Apply gamma curve
|
||||
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
|
||||
|
||||
@@ -462,9 +467,10 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||
ramp.red = values;
|
||||
ramp.green = values;
|
||||
ramp.blue = values;
|
||||
ramp.size = 256;
|
||||
ramp.size = original->size;
|
||||
|
||||
glfwSetGammaRamp(handle, &ramp);
|
||||
free(values);
|
||||
}
|
||||
|
||||
GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
|
||||
@@ -475,7 +481,8 @@ GLFWAPI const GLFWgammaramp* glfwGetGammaRamp(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
|
||||
_glfwFreeGammaArrays(&monitor->currentRamp);
|
||||
_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp);
|
||||
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->currentRamp))
|
||||
return NULL;
|
||||
|
||||
return &monitor->currentRamp;
|
||||
}
|
||||
@@ -501,8 +508,10 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* handle, const GLFWgammaramp* ramp)
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
if (!monitor->originalRamp.size)
|
||||
_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp);
|
||||
{
|
||||
if (!_glfwPlatformGetGammaRamp(monitor, &monitor->originalRamp))
|
||||
return;
|
||||
}
|
||||
|
||||
_glfwPlatformSetGammaRamp(monitor, ramp);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
#include "internal.h"
|
||||
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101400)
|
||||
#define NSOpenGLContextParameterSwapInterval NSOpenGLCPSwapInterval
|
||||
#define NSOpenGLContextParameterSurfaceOpacity NSOpenGLCPSurfaceOpacity
|
||||
#endif
|
||||
|
||||
static void makeContextCurrentNSGL(_GLFWwindow* window)
|
||||
{
|
||||
if (window)
|
||||
@@ -49,7 +54,7 @@ static void swapIntervalNSGL(int interval)
|
||||
|
||||
GLint sync = interval;
|
||||
[window->context.nsgl.object setValues:&sync
|
||||
forParameter:NSOpenGLCPSwapInterval];
|
||||
forParameter:NSOpenGLContextParameterSwapInterval];
|
||||
}
|
||||
|
||||
static int extensionSupportedNSGL(const char* extension)
|
||||
@@ -299,7 +304,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
|
||||
if (fbconfig->transparent)
|
||||
{
|
||||
GLint opaque = 0;
|
||||
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity];
|
||||
[window->context.nsgl.object setValues:&opaque forParameter:NSOpenGLContextParameterSurfaceOpacity];
|
||||
}
|
||||
|
||||
[window->context.nsgl.object setView:window->ns.view];
|
||||
@@ -332,4 +337,3 @@ GLFWAPI id glfwGetNSGLContext(GLFWwindow* handle)
|
||||
|
||||
return window->context.nsgl.object;
|
||||
}
|
||||
|
||||
|
||||
4
glfw/null_monitor.c
vendored
4
glfw/null_monitor.c
vendored
@@ -58,11 +58,11 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
5
glfw/null_window.c
vendored
5
glfw/null_window.c
vendored
@@ -148,6 +148,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = 1.f;
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
}
|
||||
|
||||
3
glfw/osmesa_context.c
vendored
3
glfw/osmesa_context.c
vendored
@@ -240,7 +240,7 @@ GLFWbool _glfwCreateContextOSMesa(_GLFWwindow* window,
|
||||
if (ctxconfig->forward)
|
||||
{
|
||||
_glfwInputError(GLFW_VERSION_UNAVAILABLE,
|
||||
"OSMesa: Foward-compatible contexts not supported");
|
||||
"OSMesa: Forward-compatible contexts not supported");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -367,4 +367,3 @@ GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle)
|
||||
|
||||
return window->context.osmesa.handle;
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,9 @@
|
||||
"stable/viewporter/viewporter.xml",
|
||||
"unstable/relative-pointer/relative-pointer-unstable-v1.xml",
|
||||
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
|
||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml",
|
||||
"unstable/xdg-decoration/xdg-decoration-unstable-v1.xml",
|
||||
"unstable/primary-selection/primary-selection-unstable-v1.xml"
|
||||
],
|
||||
"sources": [
|
||||
"wl_init.c",
|
||||
@@ -91,7 +93,7 @@
|
||||
},
|
||||
"wayland_protocols": [
|
||||
1,
|
||||
12
|
||||
17
|
||||
],
|
||||
"win32": {
|
||||
"headers": [
|
||||
@@ -145,4 +147,4 @@
|
||||
"null_joystick.c"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
glfw/vulkan.c
vendored
4
glfw/vulkan.c
vendored
@@ -136,9 +136,6 @@ GLFWbool _glfwInitVulkan(int mode)
|
||||
#elif defined(_GLFW_WAYLAND)
|
||||
else if (strcmp(ep[i].extensionName, "VK_KHR_wayland_surface") == 0)
|
||||
_glfw.vk.KHR_wayland_surface = GLFW_TRUE;
|
||||
#elif defined(_GLFW_MIR)
|
||||
else if (strcmp(ep[i].extensionName, "VK_KHR_mir_surface") == 0)
|
||||
_glfw.vk.KHR_mir_surface = GLFW_TRUE;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -326,4 +323,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance,
|
||||
|
||||
return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
|
||||
}
|
||||
|
||||
|
||||
34
glfw/wgl_context.c
vendored
34
glfw/wgl_context.c
vendored
@@ -256,12 +256,20 @@ static void makeContextCurrentWGL(_GLFWwindow* window)
|
||||
|
||||
static void swapBuffersWGL(_GLFWwindow* window)
|
||||
{
|
||||
// HACK: Use DwmFlush when desktop composition is enabled
|
||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
||||
if (!window->monitor)
|
||||
{
|
||||
int count = abs(window->context.wgl.interval);
|
||||
while (count--)
|
||||
DwmFlush();
|
||||
if (IsWindowsVistaOrGreater())
|
||||
{
|
||||
BOOL enabled;
|
||||
|
||||
// HACK: Use DwmFlush when desktop composition is enabled
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||
{
|
||||
int count = abs(window->context.wgl.interval);
|
||||
while (count--)
|
||||
DwmFlush();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwapBuffers(window->context.wgl.dc);
|
||||
@@ -273,10 +281,18 @@ static void swapIntervalWGL(int interval)
|
||||
|
||||
window->context.wgl.interval = interval;
|
||||
|
||||
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
||||
// avoid interfering with DWM vsync
|
||||
if (_glfwIsCompositionEnabledWin32() && !window->monitor)
|
||||
interval = 0;
|
||||
if (!window->monitor)
|
||||
{
|
||||
if (IsWindowsVistaOrGreater())
|
||||
{
|
||||
BOOL enabled;
|
||||
|
||||
// HACK: Disable WGL swap interval when desktop composition is enabled to
|
||||
// avoid interfering with DWM vsync
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||
interval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (_glfw.wgl.EXT_swap_control)
|
||||
_glfw.wgl.SwapIntervalEXT(interval);
|
||||
|
||||
68
glfw/win32_init.c
vendored
68
glfw/win32_init.c
vendored
@@ -62,17 +62,6 @@ BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, LPVOID reserved)
|
||||
|
||||
#endif // _GLFW_BUILD_DLL
|
||||
|
||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp)
|
||||
{
|
||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||
return VerifyVersionInfoW(&osvi, mask, cond);
|
||||
}
|
||||
|
||||
// Load necessary libraries (DLLs)
|
||||
//
|
||||
static GLFWbool loadLibraries(void)
|
||||
@@ -100,6 +89,14 @@ static GLFWbool loadLibraries(void)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDPIAware");
|
||||
_glfw.win32.user32.ChangeWindowMessageFilterEx_ = (PFN_ChangeWindowMessageFilterEx)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "ChangeWindowMessageFilterEx");
|
||||
_glfw.win32.user32.EnableNonClientDpiScaling_ = (PFN_EnableNonClientDpiScaling)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "EnableNonClientDpiScaling");
|
||||
_glfw.win32.user32.SetProcessDpiAwarenessContext_ = (PFN_SetProcessDpiAwarenessContext)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "SetProcessDpiAwarenessContext");
|
||||
_glfw.win32.user32.GetDpiForWindow_ = (PFN_GetDpiForWindow)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "GetDpiForWindow");
|
||||
_glfw.win32.user32.AdjustWindowRectExForDpi_ = (PFN_AdjustWindowRectExForDpi)
|
||||
GetProcAddress(_glfw.win32.user32.instance, "AdjustWindowRectExForDpi");
|
||||
|
||||
_glfw.win32.dinput8.instance = LoadLibraryA("dinput8.dll");
|
||||
if (_glfw.win32.dinput8.instance)
|
||||
@@ -155,6 +152,13 @@ static GLFWbool loadLibraries(void)
|
||||
GetProcAddress(_glfw.win32.shcore.instance, "GetDpiForMonitor");
|
||||
}
|
||||
|
||||
_glfw.win32.ntdll.instance = LoadLibraryA("ntdll.dll");
|
||||
if (_glfw.win32.ntdll.instance)
|
||||
{
|
||||
_glfw.win32.ntdll.RtlVerifyVersionInfo_ = (PFN_RtlVerifyVersionInfo)
|
||||
GetProcAddress(_glfw.win32.ntdll.instance, "RtlVerifyVersionInfo");
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
@@ -179,6 +183,9 @@ static void freeLibraries(void)
|
||||
|
||||
if (_glfw.win32.shcore.instance)
|
||||
FreeLibrary(_glfw.win32.shcore.instance);
|
||||
|
||||
if (_glfw.win32.ntdll.instance)
|
||||
FreeLibrary(_glfw.win32.ntdll.instance);
|
||||
}
|
||||
|
||||
// Create key code translation tables
|
||||
@@ -309,6 +316,7 @@ static void createKeyTables(void)
|
||||
_glfw.win32.keycodes[0x053] = GLFW_KEY_KP_DECIMAL;
|
||||
_glfw.win32.keycodes[0x135] = GLFW_KEY_KP_DIVIDE;
|
||||
_glfw.win32.keycodes[0x11C] = GLFW_KEY_KP_ENTER;
|
||||
_glfw.win32.keycodes[0x059] = GLFW_KEY_KP_EQUAL;
|
||||
_glfw.win32.keycodes[0x037] = GLFW_KEY_KP_MULTIPLY;
|
||||
_glfw.win32.keycodes[0x04A] = GLFW_KEY_KP_SUBTRACT;
|
||||
|
||||
@@ -339,8 +347,8 @@ static HWND createHelperWindow(void)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// HACK: The first call to ShowWindow is ignored if the parent process
|
||||
// passed along a STARTUPINFO, so clear that flag with a no-op call
|
||||
// HACK: The command to the first ShowWindow call is ignored if the parent
|
||||
// process passed along a STARTUPINFO, so clear that with a no-op call
|
||||
ShowWindow(window, SW_HIDE);
|
||||
|
||||
// Register for HID device notifications
|
||||
@@ -502,6 +510,36 @@ void _glfwUpdateKeyNamesWin32(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Replacement for IsWindowsVersionOrGreater as MinGW lacks versionhelpers.h
|
||||
//
|
||||
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp)
|
||||
{
|
||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), major, minor, 0, 0, {0}, sp };
|
||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR;
|
||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
|
||||
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||
// latter lies unless the user knew to embedd a non-default manifest
|
||||
// announcing support for Windows 10 via supportedOS GUID
|
||||
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||
}
|
||||
|
||||
// Checks whether we are on at least the specified build of Windows 10
|
||||
//
|
||||
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build)
|
||||
{
|
||||
OSVERSIONINFOEXW osvi = { sizeof(osvi), 10, 0, build };
|
||||
DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER;
|
||||
ULONGLONG cond = VerSetConditionMask(0, VER_MAJORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_MINORVERSION, VER_GREATER_EQUAL);
|
||||
cond = VerSetConditionMask(cond, VER_BUILDNUMBER, VER_GREATER_EQUAL);
|
||||
// HACK: Use RtlVerifyVersionInfo instead of VerifyVersionInfoW as the
|
||||
// latter lies unless the user knew to embedd a non-default manifest
|
||||
// announcing support for Windows 10 via supportedOS GUID
|
||||
return RtlVerifyVersionInfo(&osvi, mask, cond) == 0;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
@@ -523,7 +561,9 @@ int _glfwPlatformInit(void)
|
||||
createKeyTables();
|
||||
_glfwUpdateKeyNamesWin32();
|
||||
|
||||
if (IsWindows8Point1OrGreater())
|
||||
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
|
||||
else if (IsWindows8Point1OrGreater())
|
||||
SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
|
||||
else if (IsWindowsVistaOrGreater())
|
||||
SetProcessDPIAware();
|
||||
|
||||
9
glfw/win32_monitor.c
vendored
9
glfw/win32_monitor.c
vendored
@@ -324,9 +324,9 @@ void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* ysc
|
||||
}
|
||||
|
||||
if (xscale)
|
||||
*xscale = xdpi / 96.f;
|
||||
*xscale = xdpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
if (yscale)
|
||||
*yscale = ydpi / 96.f;
|
||||
*yscale = ydpi / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
}
|
||||
|
||||
|
||||
@@ -455,7 +455,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
&mode->blueBits);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
HDC dc;
|
||||
WORD values[768];
|
||||
@@ -469,6 +469,8 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
memcpy(ramp->red, values + 0, 256 * sizeof(unsigned short));
|
||||
memcpy(ramp->green, values + 256, 256 * sizeof(unsigned short));
|
||||
memcpy(ramp->blue, values + 512, 256 * sizeof(unsigned short));
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
@@ -510,4 +512,3 @@ GLFWAPI const char* glfwGetWin32Monitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return monitor->win32.publicDisplayName;
|
||||
}
|
||||
|
||||
|
||||
71
glfw/win32_platform.h
vendored
71
glfw/win32_platform.h
vendored
@@ -98,6 +98,12 @@
|
||||
#ifndef _WIN32_WINNT_WINBLUE
|
||||
#define _WIN32_WINNT_WINBLUE 0x0602
|
||||
#endif
|
||||
#ifndef WM_GETDPISCALEDSIZE
|
||||
#define WM_GETDPISCALEDSIZE 0x02e4
|
||||
#endif
|
||||
#ifndef USER_DEFAULT_SCREEN_DPI
|
||||
#define USER_DEFAULT_SCREEN_DPI 96
|
||||
#endif
|
||||
|
||||
#if WINVER < 0x0601
|
||||
typedef struct
|
||||
@@ -140,23 +146,32 @@ typedef enum
|
||||
} MONITOR_DPI_TYPE;
|
||||
#endif /*DPI_ENUMS_DECLARED*/
|
||||
|
||||
#ifndef DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
|
||||
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
|
||||
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT) -4)
|
||||
#endif /*DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2*/
|
||||
|
||||
// HACK: Define versionhelpers.h functions manually as MinGW lacks the header
|
||||
BOOL IsWindowsVersionOrGreater(WORD major, WORD minor, WORD sp);
|
||||
#define IsWindowsXPOrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINXP), \
|
||||
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
||||
#define IsWindowsVistaOrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_VISTA), \
|
||||
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
||||
#define IsWindows7OrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN7), \
|
||||
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
||||
#define IsWindows8OrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WIN8), \
|
||||
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
||||
#define IsWindows8Point1OrGreater() \
|
||||
IsWindowsVersionOrGreater(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
||||
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINXP), \
|
||||
LOBYTE(_WIN32_WINNT_WINXP), 0)
|
||||
#define IsWindowsVistaOrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_VISTA), \
|
||||
LOBYTE(_WIN32_WINNT_VISTA), 0)
|
||||
#define IsWindows7OrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN7), \
|
||||
LOBYTE(_WIN32_WINNT_WIN7), 0)
|
||||
#define IsWindows8OrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WIN8), \
|
||||
LOBYTE(_WIN32_WINNT_WIN8), 0)
|
||||
#define IsWindows8Point1OrGreater() \
|
||||
_glfwIsWindowsVersionOrGreaterWin32(HIBYTE(_WIN32_WINNT_WINBLUE), \
|
||||
LOBYTE(_WIN32_WINNT_WINBLUE), 0)
|
||||
|
||||
#define _glfwIsWindows10AnniversaryUpdateOrGreaterWin32() \
|
||||
_glfwIsWindows10BuildOrGreaterWin32(14393)
|
||||
#define _glfwIsWindows10CreatorsUpdateOrGreaterWin32() \
|
||||
_glfwIsWindows10BuildOrGreaterWin32(15063)
|
||||
|
||||
// HACK: Define macros that some xinput.h variants don't
|
||||
#ifndef XINPUT_CAPS_WIRELESS
|
||||
@@ -209,8 +224,16 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
|
||||
// user32.dll function pointer typedefs
|
||||
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
|
||||
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
|
||||
typedef BOOL (WINAPI * PFN_EnableNonClientDpiScaling)(HWND);
|
||||
typedef BOOL (WINAPI * PFN_SetProcessDpiAwarenessContext)(DPI_AWARENESS_CONTEXT);
|
||||
typedef UINT (WINAPI * PFN_GetDpiForWindow)(HWND);
|
||||
typedef BOOL (WINAPI * PFN_AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UINT);
|
||||
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
|
||||
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
|
||||
#define EnableNonClientDpiScaling _glfw.win32.user32.EnableNonClientDpiScaling_
|
||||
#define SetProcessDpiAwarenessContext _glfw.win32.user32.SetProcessDpiAwarenessContext_
|
||||
#define GetDpiForWindow _glfw.win32.user32.GetDpiForWindow_
|
||||
#define AdjustWindowRectExForDpi _glfw.win32.user32.AdjustWindowRectExForDpi_
|
||||
|
||||
// dwmapi.dll function pointer typedefs
|
||||
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
|
||||
@@ -226,6 +249,10 @@ typedef HRESULT (WINAPI * PFN_GetDpiForMonitor)(HMONITOR,MONITOR_DPI_TYPE,UINT*,
|
||||
#define SetProcessDpiAwareness _glfw.win32.shcore.SetProcessDpiAwareness_
|
||||
#define GetDpiForMonitor _glfw.win32.shcore.GetDpiForMonitor_
|
||||
|
||||
// ntdll.dll function pointer typedefs
|
||||
typedef LONG (WINAPI * PFN_RtlVerifyVersionInfo)(OSVERSIONINFOEXW*,ULONG,ULONGLONG);
|
||||
#define RtlVerifyVersionInfo _glfw.win32.ntdll.RtlVerifyVersionInfo_
|
||||
|
||||
typedef VkFlags VkWin32SurfaceCreateFlagsKHR;
|
||||
|
||||
typedef struct VkWin32SurfaceCreateInfoKHR
|
||||
@@ -279,6 +306,7 @@ typedef struct _GLFWwindowWin32
|
||||
GLFWbool maximized;
|
||||
// Whether to enable framebuffer transparency on DWM
|
||||
GLFWbool transparent;
|
||||
GLFWbool scaleToMonitor;
|
||||
|
||||
// The last received cursor position, regardless of source
|
||||
int lastCursorPosX, lastCursorPosY;
|
||||
@@ -326,6 +354,10 @@ typedef struct _GLFWlibraryWin32
|
||||
HINSTANCE instance;
|
||||
PFN_SetProcessDPIAware SetProcessDPIAware_;
|
||||
PFN_ChangeWindowMessageFilterEx ChangeWindowMessageFilterEx_;
|
||||
PFN_EnableNonClientDpiScaling EnableNonClientDpiScaling_;
|
||||
PFN_SetProcessDpiAwarenessContext SetProcessDpiAwarenessContext_;
|
||||
PFN_GetDpiForWindow GetDpiForWindow_;
|
||||
PFN_AdjustWindowRectExForDpi AdjustWindowRectExForDpi_;
|
||||
} user32;
|
||||
|
||||
struct {
|
||||
@@ -341,6 +373,11 @@ typedef struct _GLFWlibraryWin32
|
||||
PFN_GetDpiForMonitor GetDpiForMonitor_;
|
||||
} shcore;
|
||||
|
||||
struct {
|
||||
HINSTANCE instance;
|
||||
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
|
||||
} ntdll;
|
||||
|
||||
} _GLFWlibraryWin32;
|
||||
|
||||
// Win32-specific per-monitor data
|
||||
@@ -396,10 +433,11 @@ typedef struct _GLFWmutexWin32
|
||||
|
||||
GLFWbool _glfwRegisterWindowClassWin32(void);
|
||||
void _glfwUnregisterWindowClassWin32(void);
|
||||
GLFWbool _glfwIsCompositionEnabledWin32(void);
|
||||
|
||||
WCHAR* _glfwCreateWideStringFromUTF8Win32(const char* source);
|
||||
char* _glfwCreateUTF8FromWideStringWin32(const WCHAR* source);
|
||||
BOOL _glfwIsWindowsVersionOrGreaterWin32(WORD major, WORD minor, WORD sp);
|
||||
BOOL _glfwIsWindows10BuildOrGreaterWin32(WORD build);
|
||||
void _glfwInputErrorWin32(int error, const char* description);
|
||||
void _glfwUpdateKeyNamesWin32(void);
|
||||
|
||||
@@ -409,4 +447,3 @@ void _glfwPollMonitorsWin32(void);
|
||||
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
|
||||
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);
|
||||
|
||||
|
||||
270
glfw/win32_window.c
vendored
270
glfw/win32_window.c
vendored
@@ -186,14 +186,20 @@ static HICON createIcon(const GLFWimage* image,
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Translate client window size to full window size according to styles
|
||||
// Translate client window size to full window size according to styles and DPI
|
||||
//
|
||||
static void getFullWindowSize(DWORD style, DWORD exStyle,
|
||||
int clientWidth, int clientHeight,
|
||||
int* fullWidth, int* fullHeight)
|
||||
int* fullWidth, int* fullHeight,
|
||||
UINT dpi)
|
||||
{
|
||||
RECT rect = { 0, 0, clientWidth, clientHeight };
|
||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle, dpi);
|
||||
else
|
||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||
|
||||
*fullWidth = rect.right - rect.left;
|
||||
*fullHeight = rect.bottom - rect.top;
|
||||
}
|
||||
@@ -206,7 +212,8 @@ static void applyAspectRatio(_GLFWwindow* window, int edge, RECT* area)
|
||||
const float ratio = (float) window->numer / (float) window->denom;
|
||||
|
||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||
0, 0, &xoff, &yoff);
|
||||
0, 0, &xoff, &yoff,
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
|
||||
if (edge == WMSZ_LEFT || edge == WMSZ_BOTTOMLEFT ||
|
||||
edge == WMSZ_RIGHT || edge == WMSZ_BOTTOMRIGHT)
|
||||
@@ -337,7 +344,16 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
||||
style |= getWindowStyle(window);
|
||||
|
||||
GetClientRect(window->win32.handle, &rect);
|
||||
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, style, FALSE,
|
||||
getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
AdjustWindowRectEx(&rect, style, FALSE, getWindowExStyle(window));
|
||||
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
|
||||
@@ -351,10 +367,12 @@ static void updateWindowStyles(const _GLFWwindow* window)
|
||||
//
|
||||
static void updateFramebufferTransparency(const _GLFWwindow* window)
|
||||
{
|
||||
BOOL enabled;
|
||||
|
||||
if (!IsWindowsVistaOrGreater())
|
||||
return;
|
||||
|
||||
if (_glfwIsCompositionEnabledWin32())
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
|
||||
{
|
||||
HRGN region = CreateRectRgn(0, 0, -1, -1);
|
||||
DWM_BLURBEHIND bb = {0};
|
||||
@@ -396,29 +414,6 @@ static void updateFramebufferTransparency(const _GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a resource ID
|
||||
//
|
||||
static LPWSTR translateCursorShape(int shape)
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
case GLFW_ARROW_CURSOR:
|
||||
return IDC_ARROW;
|
||||
case GLFW_IBEAM_CURSOR:
|
||||
return IDC_IBEAM;
|
||||
case GLFW_CROSSHAIR_CURSOR:
|
||||
return IDC_CROSS;
|
||||
case GLFW_HAND_CURSOR:
|
||||
return IDC_HAND;
|
||||
case GLFW_HRESIZE_CURSOR:
|
||||
return IDC_SIZEWE;
|
||||
case GLFW_VRESIZE_CURSOR:
|
||||
return IDC_SIZENS;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Retrieves and translates modifier keys
|
||||
//
|
||||
static int getKeyMods(void)
|
||||
@@ -578,9 +573,18 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
if (!window)
|
||||
{
|
||||
// This is the message handling for the hidden helper window
|
||||
// and for a regular window during its initial creation
|
||||
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_NCCREATE:
|
||||
{
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
EnableNonClientDpiScaling(hWnd);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_DISPLAYCHANGE:
|
||||
_glfwPollMonitorsWin32();
|
||||
break;
|
||||
@@ -1003,7 +1007,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
break;
|
||||
|
||||
getFullWindowSize(getWindowStyle(window), getWindowExStyle(window),
|
||||
0, 0, &xoff, &yoff);
|
||||
0, 0, &xoff, &yoff,
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
|
||||
if (window->minwidth != GLFW_DONT_CARE &&
|
||||
window->minheight != GLFW_DONT_CARE)
|
||||
@@ -1056,10 +1061,52 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_GETDPISCALEDSIZE:
|
||||
{
|
||||
if (window->win32.scaleToMonitor)
|
||||
break;
|
||||
|
||||
// Adjust the window size to keep the client area size constant
|
||||
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||
{
|
||||
RECT source = {0}, target = {0};
|
||||
SIZE* size = (SIZE*) lParam;
|
||||
|
||||
AdjustWindowRectExForDpi(&source, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
AdjustWindowRectExForDpi(&target, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
LOWORD(wParam));
|
||||
|
||||
size->cx += (target.right - target.left) -
|
||||
(source.right - source.left);
|
||||
size->cy += (target.bottom - target.top) -
|
||||
(source.bottom - source.top);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_DPICHANGED:
|
||||
{
|
||||
const float xscale = HIWORD(wParam) / 96.f;
|
||||
const float yscale = LOWORD(wParam) / 96.f;
|
||||
const float xscale = HIWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
const float yscale = LOWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
|
||||
|
||||
// Only apply the suggested size if the OS is new enough to have
|
||||
// sent a WM_GETDPISCALEDSIZE before this
|
||||
if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
|
||||
{
|
||||
RECT* suggested = (RECT*) lParam;
|
||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||
suggested->left,
|
||||
suggested->top,
|
||||
suggested->right - suggested->left,
|
||||
suggested->bottom - suggested->top,
|
||||
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
_glfwInputWindowContentScale(window, xscale, yscale);
|
||||
break;
|
||||
}
|
||||
@@ -1146,7 +1193,8 @@ static int createNativeWindow(_GLFWwindow* window,
|
||||
|
||||
getFullWindowSize(style, exStyle,
|
||||
wndconfig->width, wndconfig->height,
|
||||
&fullWidth, &fullHeight);
|
||||
&fullWidth, &fullHeight,
|
||||
USER_DEFAULT_SCREEN_DPI);
|
||||
}
|
||||
|
||||
wideTitle = _glfwCreateWideStringFromUTF8Win32(wndconfig->title);
|
||||
@@ -1185,6 +1233,40 @@ static int createNativeWindow(_GLFWwindow* window,
|
||||
WM_COPYGLOBALDATA, MSGFLT_ALLOW, NULL);
|
||||
}
|
||||
|
||||
window->win32.scaleToMonitor = wndconfig->scaleToMonitor;
|
||||
|
||||
// Adjust window size to account for DPI scaling of the window frame and
|
||||
// optionally DPI scaling of the client area
|
||||
// This cannot be done until we know what monitor it was placed on
|
||||
if (!window->monitor)
|
||||
{
|
||||
RECT rect = { 0, 0, wndconfig->width, wndconfig->height };
|
||||
|
||||
if (wndconfig->scaleToMonitor)
|
||||
{
|
||||
float xscale, yscale;
|
||||
_glfwPlatformGetWindowContentScale(window, &xscale, &yscale);
|
||||
rect.right = (int) (rect.right * xscale);
|
||||
rect.bottom = (int) (rect.bottom * yscale);
|
||||
}
|
||||
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &rect.right);
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, style, FALSE, exStyle,
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
AdjustWindowRectEx(&rect, style, FALSE, exStyle);
|
||||
|
||||
SetWindowPos(window->win32.handle, NULL,
|
||||
rect.left, rect.top,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
SWP_NOACTIVATE | SWP_NOZORDER);
|
||||
}
|
||||
|
||||
DragAcceptFiles(window->win32.handle, TRUE);
|
||||
|
||||
if (fbconfig->transparent)
|
||||
@@ -1244,20 +1326,6 @@ void _glfwUnregisterWindowClassWin32(void)
|
||||
UnregisterClassW(_GLFW_WNDCLASSNAME, GetModuleHandleW(NULL));
|
||||
}
|
||||
|
||||
// Returns whether desktop compositing is enabled
|
||||
//
|
||||
GLFWbool _glfwIsCompositionEnabledWin32(void)
|
||||
{
|
||||
if (IsWindowsVistaOrGreater())
|
||||
{
|
||||
BOOL enabled;
|
||||
if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)))
|
||||
return enabled;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
@@ -1395,8 +1463,19 @@ void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos)
|
||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos)
|
||||
{
|
||||
RECT rect = { xpos, ypos, xpos, ypos };
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, NULL, rect.left, rect.top, 0, 0,
|
||||
SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE);
|
||||
}
|
||||
@@ -1425,8 +1504,19 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
else
|
||||
{
|
||||
RECT rect = { 0, 0, width, height };
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||
0, 0, rect.right - rect.left, rect.bottom - rect.top,
|
||||
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOZORDER);
|
||||
@@ -1481,8 +1571,18 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
|
||||
_glfwPlatformGetWindowSize(window, &width, &height);
|
||||
SetRect(&rect, 0, 0, width, height);
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
if (left)
|
||||
*left = -rect.left;
|
||||
@@ -1502,6 +1602,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
_glfwGetMonitorContentScaleWin32(handle, xscale, yscale);
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
ShowWindow(window->win32.handle, SW_MINIMIZE);
|
||||
@@ -1563,8 +1668,19 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
else
|
||||
{
|
||||
RECT rect = { xpos, ypos, xpos + width, ypos + height };
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, HWND_TOP,
|
||||
rect.left, rect.top,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
@@ -1624,8 +1740,18 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
else
|
||||
after = HWND_NOTOPMOST;
|
||||
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
|
||||
{
|
||||
AdjustWindowRectExForDpi(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window),
|
||||
GetDpiForWindow(window->win32.handle));
|
||||
}
|
||||
else
|
||||
{
|
||||
AdjustWindowRectEx(&rect, getWindowStyle(window),
|
||||
FALSE, getWindowExStyle(window));
|
||||
}
|
||||
|
||||
SetWindowPos(window->win32.handle, after,
|
||||
rect.left, rect.top,
|
||||
rect.right - rect.left, rect.bottom - rect.top,
|
||||
@@ -1660,7 +1786,15 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
return window->win32.transparent && _glfwIsCompositionEnabledWin32();
|
||||
BOOL enabled;
|
||||
|
||||
if (!window->win32.transparent)
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (!IsWindowsVistaOrGreater())
|
||||
return GLFW_FALSE;
|
||||
|
||||
return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
|
||||
@@ -1867,8 +2001,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||
{
|
||||
cursor->win32.handle =
|
||||
CopyCursor(LoadCursorW(NULL, translateCursorShape(shape)));
|
||||
LPCWSTR name = NULL;
|
||||
|
||||
if (shape == GLFW_ARROW_CURSOR)
|
||||
name = IDC_ARROW;
|
||||
else if (shape == GLFW_IBEAM_CURSOR)
|
||||
name = IDC_IBEAM;
|
||||
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||
name = IDC_CROSS;
|
||||
else if (shape == GLFW_HAND_CURSOR)
|
||||
name = IDC_HAND;
|
||||
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||
name = IDC_SIZEWE;
|
||||
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||
name = IDC_SIZENS;
|
||||
else
|
||||
return GLFW_FALSE;
|
||||
|
||||
cursor->win32.handle = CopyCursor(LoadCursorW(NULL, name));
|
||||
if (!cursor->win32.handle)
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
|
||||
16
glfw/window.c
vendored
16
glfw/window.c
vendored
@@ -393,6 +393,9 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
||||
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
case GLFW_SCALE_TO_MONITOR:
|
||||
_glfw.hints.window.scaleToMonitor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
case GLFW_CENTER_CURSOR:
|
||||
_glfw.hints.window.centerCursor = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
@@ -457,6 +460,10 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value)
|
||||
strncpy(_glfw.hints.window.x11.instanceName, value,
|
||||
sizeof(_glfw.hints.window.x11.instanceName) - 1);
|
||||
return;
|
||||
case GLFW_WAYLAND_APP_ID:
|
||||
strncpy(_glfw.hints.window.wl.appId, value,
|
||||
sizeof(_glfw.hints.window.wl.appId) - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
|
||||
@@ -714,6 +721,15 @@ GLFWAPI void glfwGetWindowContentScale(GLFWwindow* handle,
|
||||
_glfwPlatformGetWindowContentScale(window, xscale, yscale);
|
||||
}
|
||||
|
||||
GLFWAPI double glfwGetDoubleClickInterval(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0.5f);
|
||||
return _glfwPlatformGetDoubleClickInterval(window);
|
||||
}
|
||||
|
||||
GLFWAPI float glfwGetWindowOpacity(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
|
||||
75
glfw/wl_init.c
vendored
75
glfw/wl_init.c
vendored
@@ -29,6 +29,8 @@
|
||||
#include "backend_utils.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <linux/input.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -139,6 +141,7 @@ static void setCursor(const char* name)
|
||||
"Wayland: Standard cursor not found");
|
||||
return;
|
||||
}
|
||||
// TODO: handle animated cursors too.
|
||||
image = cursor->images[0];
|
||||
|
||||
if (!image)
|
||||
@@ -406,7 +409,7 @@ static void
|
||||
dispatchPendingKeyRepeats(id_type timer_id, void *data) {
|
||||
if (_glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
|
||||
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, ((double)_glfw.wl.keyboardRepeatRate) / 1000.0);
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (1.0 / (double)_glfw.wl.keyboardRepeatRate));
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
|
||||
}
|
||||
|
||||
@@ -560,6 +563,12 @@ static void registryHandleGlobal(void* data,
|
||||
_glfw.wl.seatVersion);
|
||||
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
||||
}
|
||||
if (_glfw.wl.seat) {
|
||||
if (_glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) _glfwSetupWaylandDataDevice();
|
||||
if (_glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
|
||||
_glfwSetupWaylandPrimarySelectionDevice();
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||
{
|
||||
@@ -567,6 +576,12 @@ static void registryHandleGlobal(void* data,
|
||||
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
||||
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
|
||||
}
|
||||
else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
|
||||
{
|
||||
_glfw.wl.decorationManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&zxdg_decoration_manager_v1_interface, 1);
|
||||
}
|
||||
else if (strcmp(interface, "wp_viewporter") == 0)
|
||||
{
|
||||
_glfw.wl.viewporter =
|
||||
@@ -593,6 +608,28 @@ static void registryHandleGlobal(void* data,
|
||||
&zwp_idle_inhibit_manager_v1_interface,
|
||||
1);
|
||||
}
|
||||
else if (strcmp(interface, "wl_data_device_manager") == 0)
|
||||
{
|
||||
_glfw.wl.dataDeviceManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&wl_data_device_manager_interface,
|
||||
1);
|
||||
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||
_glfwSetupWaylandDataDevice();
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") == 0 ||
|
||||
strcmp(interface, "gtk_primary_selection_device_manager") == 0)
|
||||
{
|
||||
_glfw.wl.primarySelectionDeviceManager =
|
||||
wl_registry_bind(registry, name,
|
||||
&zwp_primary_selection_device_manager_v1_interface,
|
||||
1);
|
||||
if (_glfw.wl.seat && _glfw.wl.primarySelectionDeviceManager && !_glfw.wl.primarySelectionDevice) {
|
||||
_glfwSetupWaylandPrimarySelectionDevice();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void registryHandleGlobalRemove(void *data,
|
||||
@@ -674,7 +711,8 @@ int _glfwPlatformInit(void)
|
||||
}
|
||||
initPollData(&_glfw.wl.eventLoopData, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
|
||||
glfw_dbus_init(&_glfw.wl.dbus, &_glfw.wl.eventLoopData);
|
||||
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-keyrepeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
|
||||
_glfw.wl.keyRepeatInfo.keyRepeatTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-key-repeat", 0.5, 0, dispatchPendingKeyRepeats, NULL);
|
||||
_glfw.wl.cursorAnimationTimer = addTimer(&_glfw.wl.eventLoopData, "wayland-cursor-animation", 0.5, 0, animateCursorImage, NULL);
|
||||
|
||||
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
|
||||
wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL);
|
||||
@@ -698,7 +736,17 @@ int _glfwPlatformInit(void)
|
||||
|
||||
if (_glfw.wl.pointer && _glfw.wl.shm)
|
||||
{
|
||||
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
|
||||
const char *cursorTheme = getenv("XCURSOR_THEME"), *cursorSizeStr = getenv("XCURSOR_SIZE");
|
||||
char *cursorSizeEnd;
|
||||
int cursorSize = 32;
|
||||
if (cursorSizeStr)
|
||||
{
|
||||
errno = 0;
|
||||
long cursorSizeLong = strtol(cursorSizeStr, &cursorSizeEnd, 10);
|
||||
if (!*cursorSizeEnd && !errno && cursorSizeLong > 0 && cursorSizeLong <= INT_MAX)
|
||||
cursorSize = (int)cursorSizeLong;
|
||||
}
|
||||
_glfw.wl.cursorTheme = wl_cursor_theme_load(cursorTheme, cursorSize, _glfw.wl.shm);
|
||||
if (!_glfw.wl.cursorTheme)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -747,6 +795,8 @@ void _glfwPlatformTerminate(void)
|
||||
wl_shell_destroy(_glfw.wl.shell);
|
||||
if (_glfw.wl.viewporter)
|
||||
wp_viewporter_destroy(_glfw.wl.viewporter);
|
||||
if (_glfw.wl.decorationManager)
|
||||
zxdg_decoration_manager_v1_destroy(_glfw.wl.decorationManager);
|
||||
if (_glfw.wl.wmBase)
|
||||
xdg_wm_base_destroy(_glfw.wl.wmBase);
|
||||
if (_glfw.wl.pointer)
|
||||
@@ -761,6 +811,21 @@ void _glfwPlatformTerminate(void)
|
||||
zwp_pointer_constraints_v1_destroy(_glfw.wl.pointerConstraints);
|
||||
if (_glfw.wl.idleInhibitManager)
|
||||
zwp_idle_inhibit_manager_v1_destroy(_glfw.wl.idleInhibitManager);
|
||||
if (_glfw.wl.dataSourceForClipboard)
|
||||
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||
for (size_t doi=0; doi < arraysz(_glfw.wl.dataOffers); doi++) {
|
||||
if (_glfw.wl.dataOffers[doi].id) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[doi].id);
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.dataDevice)
|
||||
wl_data_device_destroy(_glfw.wl.dataDevice);
|
||||
if (_glfw.wl.dataDeviceManager)
|
||||
wl_data_device_manager_destroy(_glfw.wl.dataDeviceManager);
|
||||
if (_glfw.wl.primarySelectionDevice)
|
||||
zwp_primary_selection_device_v1_destroy(_glfw.wl.primarySelectionDevice);
|
||||
if (_glfw.wl.primarySelectionDeviceManager)
|
||||
zwp_primary_selection_device_manager_v1_destroy(_glfw.wl.primarySelectionDeviceManager);
|
||||
if (_glfw.wl.registry)
|
||||
wl_registry_destroy(_glfw.wl.registry);
|
||||
if (_glfw.wl.display)
|
||||
@@ -769,6 +834,10 @@ void _glfwPlatformTerminate(void)
|
||||
wl_display_disconnect(_glfw.wl.display);
|
||||
}
|
||||
closeFds(_glfw.wl.eventLoopData.wakeupFds, sizeof(_glfw.wl.eventLoopData.wakeupFds)/sizeof(_glfw.wl.eventLoopData.wakeupFds[0]));
|
||||
free(_glfw.wl.clipboardString); _glfw.wl.clipboardString = NULL;
|
||||
free(_glfw.wl.primarySelectionString); _glfw.wl.primarySelectionString = NULL;
|
||||
free(_glfw.wl.pasteString); _glfw.wl.pasteString = NULL;
|
||||
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetVersionString(void)
|
||||
|
||||
58
glfw/wl_monitor.c
vendored
58
glfw/wl_monitor.c
vendored
@@ -32,16 +32,16 @@
|
||||
#include <errno.h>
|
||||
|
||||
|
||||
static void geometry(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
int32_t physicalWidth,
|
||||
int32_t physicalHeight,
|
||||
int32_t subpixel,
|
||||
const char* make,
|
||||
const char* model,
|
||||
int32_t transform)
|
||||
static void outputHandleGeometry(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
int32_t physicalWidth,
|
||||
int32_t physicalHeight,
|
||||
int32_t subpixel,
|
||||
const char* make,
|
||||
const char* model,
|
||||
int32_t transform)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
char name[1024];
|
||||
@@ -55,12 +55,12 @@ static void geometry(void* data,
|
||||
monitor->name = _glfw_strdup(name);
|
||||
}
|
||||
|
||||
static void mode(void* data,
|
||||
struct wl_output* output,
|
||||
uint32_t flags,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t refresh)
|
||||
static void outputHandleMode(void* data,
|
||||
struct wl_output* output,
|
||||
uint32_t flags,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
int32_t refresh)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
GLFWvidmode mode;
|
||||
@@ -81,16 +81,16 @@ static void mode(void* data,
|
||||
monitor->wl.currentMode = monitor->modeCount - 1;
|
||||
}
|
||||
|
||||
static void done(void* data, struct wl_output* output)
|
||||
static void outputHandleDone(void* data, struct wl_output* output)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
|
||||
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
|
||||
}
|
||||
|
||||
static void scale(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t factor)
|
||||
static void outputHandleScale(void* data,
|
||||
struct wl_output* output,
|
||||
int32_t factor)
|
||||
{
|
||||
struct _GLFWmonitor *monitor = data;
|
||||
|
||||
@@ -98,10 +98,10 @@ static void scale(void* data,
|
||||
}
|
||||
|
||||
static const struct wl_output_listener outputListener = {
|
||||
geometry,
|
||||
mode,
|
||||
done,
|
||||
scale,
|
||||
outputHandleGeometry,
|
||||
outputHandleMode,
|
||||
outputHandleDone,
|
||||
outputHandleScale,
|
||||
};
|
||||
|
||||
|
||||
@@ -180,18 +180,17 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
*mode = monitor->modes[monitor->wl.currentMode];
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Gamma ramp getting not supported yet");
|
||||
"Wayland: Gamma ramp access is not available");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Gamma ramp setting not supported yet");
|
||||
"Wayland: Gamma ramp access is not available");
|
||||
}
|
||||
|
||||
|
||||
@@ -205,4 +204,3 @@ GLFWAPI struct wl_output* glfwGetWaylandMonitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return monitor->wl.output;
|
||||
}
|
||||
|
||||
|
||||
66
glfw/wl_platform.h
vendored
66
glfw/wl_platform.h
vendored
@@ -56,9 +56,11 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-viewporter-client-protocol.h"
|
||||
#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
#include "wayland-primary-selection-unstable-v1-client-protocol.h"
|
||||
|
||||
#define _glfw_dlopen(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
|
||||
#define _glfw_dlclose(handle) dlclose(handle)
|
||||
@@ -143,12 +145,14 @@ typedef struct _GLFWwindowWayland
|
||||
struct {
|
||||
struct xdg_surface* surface;
|
||||
struct xdg_toplevel* toplevel;
|
||||
struct zxdg_toplevel_decoration_v1* decoration;
|
||||
} xdg;
|
||||
|
||||
_GLFWcursor* currentCursor;
|
||||
double cursorPosX, cursorPosY;
|
||||
|
||||
char* title;
|
||||
char appId[256];
|
||||
|
||||
// We need to track the monitors the window spans on to calculate the
|
||||
// optimal scaling factor.
|
||||
@@ -168,13 +172,53 @@ typedef struct _GLFWwindowWayland
|
||||
GLFWbool justCreated;
|
||||
|
||||
struct {
|
||||
GLFWbool serverSide;
|
||||
struct wl_buffer* buffer;
|
||||
_GLFWdecorationWayland top, left, right, bottom;
|
||||
int focus;
|
||||
} decorations;
|
||||
|
||||
struct {
|
||||
unsigned long long id;
|
||||
void(*callback)(unsigned long long id);
|
||||
struct wl_callback *current_wl_callback;
|
||||
} frameCallbackData;
|
||||
|
||||
|
||||
} _GLFWwindowWayland;
|
||||
|
||||
typedef enum _GLFWWaylandOfferType
|
||||
{
|
||||
EXPIRED,
|
||||
CLIPBOARD,
|
||||
DRAG_AND_DROP,
|
||||
PRIMARY_SELECTION
|
||||
}_GLFWWaylandOfferType ;
|
||||
|
||||
typedef struct _GLFWWaylandDataOffer
|
||||
{
|
||||
struct wl_data_offer *id;
|
||||
const char *mime;
|
||||
_GLFWWaylandOfferType offer_type;
|
||||
size_t idx;
|
||||
int is_self_offer;
|
||||
int has_uri_list;
|
||||
uint32_t source_actions;
|
||||
uint32_t dnd_action;
|
||||
struct wl_surface *surface;
|
||||
} _GLFWWaylandDataOffer;
|
||||
|
||||
typedef struct _GLFWWaylandPrimaryOffer
|
||||
{
|
||||
struct zwp_primary_selection_offer_v1 *id;
|
||||
const char *mime;
|
||||
_GLFWWaylandOfferType offer_type;
|
||||
size_t idx;
|
||||
int is_self_offer;
|
||||
int has_uri_list;
|
||||
struct wl_surface *surface;
|
||||
} _GLFWWaylandPrimaryOffer;
|
||||
|
||||
// Wayland-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryWayland
|
||||
@@ -189,10 +233,17 @@ typedef struct _GLFWlibraryWayland
|
||||
struct wl_pointer* pointer;
|
||||
struct wl_keyboard* keyboard;
|
||||
struct xdg_wm_base* wmBase;
|
||||
struct zxdg_decoration_manager_v1* decorationManager;
|
||||
struct wp_viewporter* viewporter;
|
||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||
struct wl_data_device_manager* dataDeviceManager;
|
||||
struct wl_data_device* dataDevice;
|
||||
struct wl_data_source* dataSourceForClipboard;
|
||||
struct zwp_primary_selection_device_manager_v1* primarySelectionDeviceManager;
|
||||
struct zwp_primary_selection_device_v1* primarySelectionDevice;
|
||||
struct zwp_primary_selection_source_v1* dataSourceForPrimarySelection;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
@@ -208,6 +259,7 @@ typedef struct _GLFWlibraryWayland
|
||||
id_type keyRepeatTimer;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
} keyRepeatInfo;
|
||||
id_type cursorAnimationTimer;
|
||||
_GLFWXKBData xkb;
|
||||
_GLFWDBUSData dbus;
|
||||
|
||||
@@ -232,7 +284,13 @@ typedef struct _GLFWlibraryWayland
|
||||
} egl;
|
||||
|
||||
EventLoopData eventLoopData;
|
||||
|
||||
char* pasteString;
|
||||
char* clipboardString;
|
||||
size_t dataOffersCounter;
|
||||
_GLFWWaylandDataOffer dataOffers[8];
|
||||
char* primarySelectionString;
|
||||
size_t primarySelectionOffersCounter;
|
||||
_GLFWWaylandPrimaryOffer primarySelectionOffers[8];
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -253,11 +311,15 @@ typedef struct _GLFWmonitorWayland
|
||||
//
|
||||
typedef struct _GLFWcursorWayland
|
||||
{
|
||||
struct wl_cursor_image* image;
|
||||
struct wl_cursor* cursor;
|
||||
struct wl_buffer* buffer;
|
||||
int width, height;
|
||||
int xhot, yhot;
|
||||
int currentImage;
|
||||
} _GLFWcursorWayland;
|
||||
|
||||
|
||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||
void _glfwSetupWaylandDataDevice();
|
||||
void _glfwSetupWaylandPrimarySelectionDevice();
|
||||
void animateCursorImage(id_type timer_id, void *data);
|
||||
|
||||
715
glfw/wl_window.c
vendored
715
glfw/wl_window.c
vendored
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "internal.h"
|
||||
#include "backend_utils.h"
|
||||
#include "memfd.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -37,6 +38,18 @@
|
||||
#include <sys/mman.h>
|
||||
|
||||
|
||||
#define URI_LIST_MIME "text/uri-list"
|
||||
|
||||
|
||||
static const char*
|
||||
clipboard_mime() {
|
||||
static char buf[128] = {0};
|
||||
if (buf[0] == 0) {
|
||||
snprintf(buf, sizeof(buf), "application/glfw+clipboard-%d", getpid());
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void handlePing(void* data,
|
||||
struct wl_shell_surface* shellSurface,
|
||||
uint32_t serial)
|
||||
@@ -103,18 +116,6 @@ static const struct wl_shell_surface_listener shellSurfaceListener = {
|
||||
handlePopupDone
|
||||
};
|
||||
|
||||
static int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink(tmpname);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new, unique, anonymous file of the given size, and
|
||||
* return the file descriptor for it. The file descriptor is set
|
||||
@@ -138,11 +139,21 @@ createTmpfileCloexec(char* tmpname)
|
||||
static int
|
||||
createAnonymousFile(off_t size)
|
||||
{
|
||||
int ret;
|
||||
#ifdef HAS_MEMFD_CREATE
|
||||
int fd = memfd_create("glfw-shared", MFD_CLOEXEC | MFD_ALLOW_SEALING);
|
||||
if (fd < 0) return -1;
|
||||
// We can add this seal before calling posix_fallocate(), as the file
|
||||
// is currently zero-sized anyway.
|
||||
//
|
||||
// There is also no need to check for the return value, we couldn’t do
|
||||
// anything with it anyway.
|
||||
fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL);
|
||||
#else
|
||||
static const char template[] = "/glfw-shared-XXXXXX";
|
||||
const char* path;
|
||||
char* name;
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
path = getenv("XDG_RUNTIME_DIR");
|
||||
if (!path)
|
||||
@@ -161,6 +172,7 @@ createAnonymousFile(off_t size)
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
#endif
|
||||
ret = posix_fallocate(fd, 0, size);
|
||||
if (ret != 0)
|
||||
{
|
||||
@@ -186,7 +198,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Creating a buffer file for %d B failed: %m",
|
||||
length);
|
||||
return GLFW_FALSE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
@@ -195,7 +207,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: mmap failed: %m");
|
||||
close(fd);
|
||||
return GLFW_FALSE;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
|
||||
@@ -260,11 +272,13 @@ static void createDecorations(_GLFWwindow* window)
|
||||
const GLFWimage image = { 1, 1, data };
|
||||
GLFWbool opaque = (data[3] == 255);
|
||||
|
||||
if (!_glfw.wl.viewporter)
|
||||
if (!_glfw.wl.viewporter || !window->decorated || window->wl.decorations.serverSide)
|
||||
return;
|
||||
|
||||
if (!window->wl.decorations.buffer)
|
||||
window->wl.decorations.buffer = createShmBuffer(&image);
|
||||
if (!window->wl.decorations.buffer)
|
||||
return;
|
||||
|
||||
createDecoration(&window->wl.decorations.top, window->wl.surface,
|
||||
window->wl.decorations.buffer, opaque,
|
||||
@@ -305,6 +319,20 @@ static void destroyDecorations(_GLFWwindow* window)
|
||||
destroyDecoration(&window->wl.decorations.bottom);
|
||||
}
|
||||
|
||||
static void xdgDecorationHandleConfigure(void* data,
|
||||
struct zxdg_toplevel_decoration_v1* decoration,
|
||||
uint32_t mode)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
window->wl.decorations.serverSide = (mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
if (!window->wl.decorations.serverSide)
|
||||
createDecorations(window);
|
||||
}
|
||||
|
||||
static const struct zxdg_toplevel_decoration_v1_listener xdgDecorationListener = {
|
||||
xdgDecorationHandleConfigure,
|
||||
};
|
||||
|
||||
// Makes the surface considered as XRGB instead of ARGB.
|
||||
static void setOpaqueRegion(_GLFWwindow* window)
|
||||
{
|
||||
@@ -477,9 +505,6 @@ static GLFWbool createSurface(_GLFWwindow* window,
|
||||
if (!window->wl.transparent)
|
||||
setOpaqueRegion(window);
|
||||
|
||||
if (window->decorated && !window->monitor)
|
||||
createDecorations(window);
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
@@ -500,7 +525,8 @@ static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refres
|
||||
monitor->wl.output);
|
||||
}
|
||||
setIdleInhibitor(window, GLFW_TRUE);
|
||||
destroyDecorations(window);
|
||||
if (!window->wl.decorations.serverSide)
|
||||
destroyDecorations(window);
|
||||
}
|
||||
|
||||
static GLFWbool createShellSurface(_GLFWwindow* window)
|
||||
@@ -536,11 +562,13 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
|
||||
{
|
||||
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
createDecorations(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
createDecorations(window);
|
||||
}
|
||||
|
||||
wl_surface_commit(window->wl.surface);
|
||||
@@ -629,6 +657,27 @@ static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
xdgSurfaceHandleConfigure
|
||||
};
|
||||
|
||||
static void setXdgDecorations(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.wl.decorationManager)
|
||||
{
|
||||
window->wl.xdg.decoration =
|
||||
zxdg_decoration_manager_v1_get_toplevel_decoration(
|
||||
_glfw.wl.decorationManager, window->wl.xdg.toplevel);
|
||||
zxdg_toplevel_decoration_v1_add_listener(window->wl.xdg.decoration,
|
||||
&xdgDecorationListener,
|
||||
window);
|
||||
zxdg_toplevel_decoration_v1_set_mode(
|
||||
window->wl.xdg.decoration,
|
||||
ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE);
|
||||
}
|
||||
else
|
||||
{
|
||||
window->wl.decorations.serverSide = GLFW_FALSE;
|
||||
createDecorations(window);
|
||||
}
|
||||
}
|
||||
|
||||
static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
|
||||
@@ -676,11 +725,15 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
setXdgDecorations(window);
|
||||
}
|
||||
else
|
||||
{
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
setXdgDecorations(window);
|
||||
}
|
||||
if (strlen(window->wl.appId))
|
||||
xdg_toplevel_set_app_id(window->wl.xdg.toplevel, window->wl.appId);
|
||||
|
||||
wl_surface_commit(window->wl.surface);
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
@@ -688,6 +741,69 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
setCursorImage(_GLFWcursorWayland* cursorWayland)
|
||||
{
|
||||
struct wl_cursor_image* image;
|
||||
struct wl_buffer* buffer;
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
|
||||
if (!cursorWayland->cursor) {
|
||||
buffer = cursorWayland->buffer;
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
} else
|
||||
{
|
||||
image = cursorWayland->cursor->images[cursorWayland->currentImage];
|
||||
buffer = wl_cursor_image_get_buffer(image);
|
||||
if (image->delay) {
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, ((double)image->delay) / 1000.0);
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
|
||||
} else {
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 0);
|
||||
}
|
||||
|
||||
if (!buffer)
|
||||
return;
|
||||
|
||||
cursorWayland->width = image->width;
|
||||
cursorWayland->height = image->height;
|
||||
cursorWayland->xhot = image->hotspot_x;
|
||||
cursorWayland->yhot = image->hotspot_y;
|
||||
}
|
||||
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
|
||||
surface,
|
||||
cursorWayland->xhot,
|
||||
cursorWayland->yhot);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage(surface, 0, 0,
|
||||
cursorWayland->width, cursorWayland->height);
|
||||
wl_surface_commit(surface);
|
||||
}
|
||||
|
||||
static void
|
||||
incrementCursorImage(_GLFWwindow* window)
|
||||
{
|
||||
if (window && window->wl.decorations.focus == mainWindow) {
|
||||
_GLFWcursor* cursor = window->wl.currentCursor;
|
||||
if (cursor && cursor->wl.cursor)
|
||||
{
|
||||
cursor->wl.currentImage += 1;
|
||||
cursor->wl.currentImage %= cursor->wl.cursor->image_count;
|
||||
setCursorImage(&cursor->wl);
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, cursor->wl.cursor->image_count > 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.cursorAnimationTimer, 1);
|
||||
}
|
||||
|
||||
void
|
||||
animateCursorImage(id_type timer_id, void *data) {
|
||||
incrementCursorImage(_glfw.wl.pointerFocus);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
handleEvents(double timeout)
|
||||
{
|
||||
@@ -757,6 +873,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
{
|
||||
window->wl.justCreated = GLFW_TRUE;
|
||||
window->wl.transparent = fbconfig->transparent;
|
||||
strncpy(window->wl.appId, wndconfig->wl.appId, sizeof(window->wl.appId));
|
||||
|
||||
if (!createSurface(window, wndconfig))
|
||||
return GLFW_FALSE;
|
||||
@@ -835,6 +952,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
window->context.destroy(window);
|
||||
|
||||
destroyDecorations(window);
|
||||
if (window->wl.xdg.decoration)
|
||||
zxdg_toplevel_decoration_v1_destroy(window->wl.xdg.decoration);
|
||||
if (window->wl.decorations.buffer)
|
||||
wl_buffer_destroy(window->wl.decorations.buffer);
|
||||
|
||||
@@ -855,6 +974,8 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
|
||||
free(window->wl.title);
|
||||
free(window->wl.monitors);
|
||||
if (window->wl.frameCallbackData.current_wl_callback)
|
||||
wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
|
||||
@@ -948,7 +1069,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
int* left, int* top,
|
||||
int* right, int* bottom)
|
||||
{
|
||||
if (window->decorated && !window->monitor)
|
||||
if (window->decorated && !window->monitor && !window->wl.decorations.serverSide)
|
||||
{
|
||||
if (top)
|
||||
*top = _GLFW_DECORATION_TOP;
|
||||
@@ -970,6 +1091,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = (float) window->wl.scale;
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.wl.wmBase)
|
||||
@@ -1090,7 +1216,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
else if (window->wl.shellSurface)
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
if (window->decorated)
|
||||
if (!_glfw.wl.decorationManager)
|
||||
createDecorations(window);
|
||||
}
|
||||
_glfwInputWindowMonitor(window, monitor);
|
||||
@@ -1227,6 +1353,8 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
int xhot, int yhot, int count)
|
||||
{
|
||||
cursor->wl.buffer = createShmBuffer(image);
|
||||
if (!cursor->wl.buffer)
|
||||
return GLFW_FALSE;
|
||||
cursor->wl.width = image->width;
|
||||
cursor->wl.height = image->height;
|
||||
cursor->wl.xhot = xhot;
|
||||
@@ -1248,14 +1376,15 @@ int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
cursor->wl.image = standardCursor->images[0];
|
||||
cursor->wl.cursor = standardCursor;
|
||||
cursor->wl.currentImage = 0;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor)
|
||||
{
|
||||
// If it's a standard cursor we don't need to do anything here
|
||||
if (cursor->wl.image)
|
||||
if (cursor->wl.cursor)
|
||||
return;
|
||||
|
||||
if (cursor->wl.buffer)
|
||||
@@ -1361,10 +1490,7 @@ static GLFWbool isPointerLocked(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
{
|
||||
struct wl_buffer* buffer;
|
||||
struct wl_cursor* defaultCursor;
|
||||
struct wl_cursor_image* image;
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
|
||||
if (!_glfw.wl.pointer)
|
||||
return;
|
||||
@@ -1373,7 +1499,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
|
||||
// If we're not in the correct window just save the cursor
|
||||
// the next time the pointer enters the window the cursor will change
|
||||
if (window != _glfw.wl.pointerFocus)
|
||||
if (window != _glfw.wl.pointerFocus || window->wl.decorations.focus != mainWindow)
|
||||
return;
|
||||
|
||||
// Unlock possible pointer lock if no longer disabled.
|
||||
@@ -1383,7 +1509,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
if (window->cursorMode == GLFW_CURSOR_NORMAL)
|
||||
{
|
||||
if (cursor)
|
||||
image = cursor->wl.image;
|
||||
setCursorImage(&cursor->wl);
|
||||
else
|
||||
{
|
||||
defaultCursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
|
||||
@@ -1394,33 +1520,14 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
"Wayland: Standard cursor not found");
|
||||
return;
|
||||
}
|
||||
image = defaultCursor->images[0];
|
||||
}
|
||||
|
||||
if (image)
|
||||
{
|
||||
buffer = wl_cursor_image_get_buffer(image);
|
||||
if (!buffer)
|
||||
return;
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
|
||||
surface,
|
||||
image->hotspot_x,
|
||||
image->hotspot_y);
|
||||
wl_surface_attach(surface, buffer, 0, 0);
|
||||
wl_surface_damage(surface, 0, 0,
|
||||
image->width, image->height);
|
||||
wl_surface_commit(surface);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_pointer_set_cursor(_glfw.wl.pointer, _glfw.wl.pointerSerial,
|
||||
surface,
|
||||
cursor->wl.xhot,
|
||||
cursor->wl.yhot);
|
||||
wl_surface_attach(surface, cursor->wl.buffer, 0, 0);
|
||||
wl_surface_damage(surface, 0, 0,
|
||||
cursor->wl.width, cursor->wl.height);
|
||||
wl_surface_commit(surface);
|
||||
_GLFWcursorWayland cursorWayland = {
|
||||
defaultCursor,
|
||||
NULL,
|
||||
0, 0,
|
||||
0, 0,
|
||||
0
|
||||
};
|
||||
setCursorImage(&cursorWayland);
|
||||
}
|
||||
}
|
||||
else if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
@@ -1435,18 +1542,480 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
}
|
||||
}
|
||||
|
||||
static void send_text(char *text, int fd)
|
||||
{
|
||||
if (text) {
|
||||
size_t len = strlen(text), pos = 0;
|
||||
double start = glfwGetTime();
|
||||
while (pos < len && glfwGetTime() - start < 2.0) {
|
||||
ssize_t ret = write(fd, text + pos, len - pos);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) continue;
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Could not copy writing to destination fd failed with error: %s", strerror(errno));
|
||||
break;
|
||||
}
|
||||
if (ret > 0) {
|
||||
start = glfwGetTime();
|
||||
pos += ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
|
||||
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd) {
|
||||
send_text(_glfw.wl.clipboardString, fd);
|
||||
}
|
||||
|
||||
static void _glfwSendPrimarySelectionText(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source,
|
||||
const char *mime_type, int fd) {
|
||||
send_text(_glfw.wl.primarySelectionString, fd);
|
||||
}
|
||||
|
||||
static char* read_offer_string(int data_pipe) {
|
||||
wl_display_flush(_glfw.wl.display);
|
||||
size_t sz = 0, capacity = 0;
|
||||
char *buf = NULL;
|
||||
struct pollfd fds;
|
||||
fds.fd = data_pipe;
|
||||
fds.events = POLLIN;
|
||||
double start = glfwGetTime();
|
||||
#define bail(...) { \
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
||||
free(buf); buf = NULL; \
|
||||
close(data_pipe); \
|
||||
return NULL; \
|
||||
}
|
||||
|
||||
while (glfwGetTime() - start < 2) {
|
||||
int ret = poll(&fds, 1, 2000);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
bail("Wayland: Failed to poll clipboard data from pipe with error: %s", strerror(errno));
|
||||
}
|
||||
if (!ret) {
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
}
|
||||
if (capacity <= sz || capacity - sz <= 64) {
|
||||
capacity += 4096;
|
||||
buf = realloc(buf, capacity);
|
||||
if (!buf) {
|
||||
bail("Wayland: Failed to allocate memory to read clipboard data");
|
||||
}
|
||||
}
|
||||
ret = read(data_pipe, buf + sz, capacity - sz - 1);
|
||||
if (ret == -1) {
|
||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) continue;
|
||||
bail("Wayland: Failed to read clipboard data from pipe with error: %s", strerror(errno));
|
||||
}
|
||||
if (ret == 0) { close(data_pipe); buf[sz] = 0; return buf; }
|
||||
sz += ret;
|
||||
start = glfwGetTime();
|
||||
}
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
#undef bail
|
||||
|
||||
}
|
||||
|
||||
static char* read_primary_selection_offer(struct zwp_primary_selection_offer_v1 *primary_selection_offer, const char *mime) {
|
||||
int pipefd[2];
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
zwp_primary_selection_offer_v1_receive(primary_selection_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
return read_offer_string(pipefd[0]);
|
||||
}
|
||||
|
||||
static char* read_data_offer(struct wl_data_offer *data_offer, const char *mime) {
|
||||
int pipefd[2];
|
||||
if (pipe2(pipefd, O_CLOEXEC) != 0) return NULL;
|
||||
wl_data_offer_receive(data_offer, mime, pipefd[1]);
|
||||
close(pipefd[1]);
|
||||
return read_offer_string(pipefd[0]);
|
||||
}
|
||||
|
||||
static void data_source_canceled(void *data, struct wl_data_source *wl_data_source) {
|
||||
if (_glfw.wl.dataSourceForClipboard == wl_data_source)
|
||||
_glfw.wl.dataSourceForClipboard = NULL;
|
||||
wl_data_source_destroy(wl_data_source);
|
||||
}
|
||||
|
||||
static void primary_selection_source_canceled(void *data, struct zwp_primary_selection_source_v1 *primary_selection_source) {
|
||||
if (_glfw.wl.dataSourceForPrimarySelection == primary_selection_source)
|
||||
_glfw.wl.dataSourceForPrimarySelection = NULL;
|
||||
zwp_primary_selection_source_v1_destroy(primary_selection_source);
|
||||
}
|
||||
|
||||
static void data_source_target(void *data, struct wl_data_source *wl_data_source, const char* mime) {
|
||||
}
|
||||
|
||||
const static struct wl_data_source_listener data_source_listener = {
|
||||
.send = _glfwSendClipboardText,
|
||||
.cancelled = data_source_canceled,
|
||||
.target = data_source_target,
|
||||
};
|
||||
|
||||
const static struct zwp_primary_selection_source_v1_listener primary_selection_source_listener = {
|
||||
.send = _glfwSendPrimarySelectionText,
|
||||
.cancelled = primary_selection_source_canceled,
|
||||
};
|
||||
|
||||
static void prune_unclaimed_data_offers() {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void prune_unclaimed_primary_selection_offers() {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id && !_glfw.wl.dataOffers[i].offer_type) {
|
||||
zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[i].id);
|
||||
memset(_glfw.wl.primarySelectionOffers + i, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_selection_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *data_offer)
|
||||
{
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == data_offer) {
|
||||
_glfw.wl.dataOffers[i].offer_type = CLIPBOARD;
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
|
||||
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous selection offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_data_offers();
|
||||
}
|
||||
|
||||
static void mark_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1* primary_selection_device,
|
||||
struct zwp_primary_selection_offer_v1 *primary_selection_offer) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == primary_selection_offer) {
|
||||
_glfw.wl.primarySelectionOffers[i].offer_type = PRIMARY_SELECTION;
|
||||
} else if (_glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
|
||||
_glfw.wl.primarySelectionOffers[i].offer_type = EXPIRED; // previous selection offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_primary_selection_offers();
|
||||
}
|
||||
|
||||
static void set_offer_mimetype(struct _GLFWWaylandDataOffer* offer, const char* mime) {
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
|
||||
offer->mime = "text/plain;charset=utf-8";
|
||||
else if (!offer->mime && strcmp(mime, "text/plain") == 0)
|
||||
offer->mime = "text/plain";
|
||||
else if (strcmp(mime, clipboard_mime()) == 0)
|
||||
offer->is_self_offer = 1;
|
||||
else if (strcmp(mime, URI_LIST_MIME) == 0)
|
||||
offer->has_uri_list = 1;
|
||||
}
|
||||
|
||||
static void handle_offer_mimetype(void *data, struct wl_data_offer* id, const char *mime) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
set_offer_mimetype(&_glfw.wl.dataOffers[i], mime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_primary_selection_offer_mimetype(void *data, struct zwp_primary_selection_offer_v1* id, const char *mime) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == id) {
|
||||
set_offer_mimetype((struct _GLFWWaylandDataOffer*)&_glfw.wl.primarySelectionOffers[i], mime);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void data_offer_source_actions(void *data, struct wl_data_offer* id, uint32_t actions) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].source_actions = actions;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void data_offer_action(void *data, struct wl_data_offer* id, uint32_t action) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].dnd_action = action;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const struct wl_data_offer_listener data_offer_listener = {
|
||||
.offer = handle_offer_mimetype,
|
||||
.source_actions = data_offer_source_actions,
|
||||
.action = data_offer_action,
|
||||
};
|
||||
|
||||
static const struct zwp_primary_selection_offer_v1_listener primary_selection_offer_listener = {
|
||||
.offer = handle_primary_selection_offer_mimetype,
|
||||
};
|
||||
|
||||
static void handle_data_offer(void *data, struct wl_data_device *wl_data_device, struct wl_data_offer *id) {
|
||||
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].idx && _glfw.wl.dataOffers[i].idx < smallest_idx) {
|
||||
smallest_idx = _glfw.wl.dataOffers[i].idx;
|
||||
pos = i;
|
||||
}
|
||||
if (_glfw.wl.dataOffers[i].id == NULL) {
|
||||
_glfw.wl.dataOffers[i].id = id;
|
||||
_glfw.wl.dataOffers[i].idx = ++_glfw.wl.dataOffersCounter;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.dataOffers[pos].id) wl_data_offer_destroy(_glfw.wl.dataOffers[pos].id);
|
||||
memset(_glfw.wl.dataOffers + pos, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
_glfw.wl.dataOffers[pos].id = id;
|
||||
_glfw.wl.dataOffers[pos].idx = ++_glfw.wl.dataOffersCounter;
|
||||
end:
|
||||
wl_data_offer_add_listener(id, &data_offer_listener, NULL);
|
||||
}
|
||||
|
||||
static void handle_primary_selection_offer(void *data, struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, struct zwp_primary_selection_offer_v1 *id) {
|
||||
size_t smallest_idx = SIZE_MAX, pos = 0;
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].idx && _glfw.wl.primarySelectionOffers[i].idx < smallest_idx) {
|
||||
smallest_idx = _glfw.wl.primarySelectionOffers[i].idx;
|
||||
pos = i;
|
||||
}
|
||||
if (_glfw.wl.primarySelectionOffers[i].id == NULL) {
|
||||
_glfw.wl.primarySelectionOffers[i].id = id;
|
||||
_glfw.wl.primarySelectionOffers[i].idx = ++_glfw.wl.primarySelectionOffersCounter;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
if (_glfw.wl.primarySelectionOffers[pos].id) zwp_primary_selection_offer_v1_destroy(_glfw.wl.primarySelectionOffers[pos].id);
|
||||
memset(_glfw.wl.primarySelectionOffers + pos, 0, sizeof(_glfw.wl.primarySelectionOffers[0]));
|
||||
_glfw.wl.primarySelectionOffers[pos].id = id;
|
||||
_glfw.wl.primarySelectionOffers[pos].idx = ++_glfw.wl.primarySelectionOffersCounter;
|
||||
end:
|
||||
zwp_primary_selection_offer_v1_add_listener(id, &primary_selection_offer_listener, NULL);
|
||||
}
|
||||
|
||||
static void drag_enter(void *data, struct wl_data_device *wl_data_device, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *id) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id == id) {
|
||||
_glfw.wl.dataOffers[i].offer_type = DRAG_AND_DROP;
|
||||
_glfw.wl.dataOffers[i].surface = surface;
|
||||
const char *mime = _glfw.wl.dataOffers[i].has_uri_list ? URI_LIST_MIME : NULL;
|
||||
wl_data_offer_accept(id, serial, mime);
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
_glfw.wl.dataOffers[i].offer_type = EXPIRED; // previous drag offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_data_offers();
|
||||
}
|
||||
|
||||
static void drag_leave(void *data, struct wl_data_device *wl_data_device) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drop(void *data, struct wl_data_device *wl_data_device) {
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].offer_type == DRAG_AND_DROP) {
|
||||
char *uri_list = read_data_offer(_glfw.wl.dataOffers[i].id, URI_LIST_MIME);
|
||||
if (uri_list) {
|
||||
wl_data_offer_finish(_glfw.wl.dataOffers[i].id);
|
||||
int count;
|
||||
char** paths = parseUriList(data, &count);
|
||||
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
while (window)
|
||||
{
|
||||
if (window->wl.surface == _glfw.wl.dataOffers[i].surface) {
|
||||
_glfwInputDrop(window, count, (const char**) paths);
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
}
|
||||
|
||||
|
||||
for (int k = 0; k < count; k++)
|
||||
free(paths[k]);
|
||||
free(paths);
|
||||
free(uri_list);
|
||||
}
|
||||
wl_data_offer_destroy(_glfw.wl.dataOffers[i].id);
|
||||
memset(_glfw.wl.dataOffers + i, 0, sizeof(_glfw.wl.dataOffers[0]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void motion(void *data, struct wl_data_device *wl_data_device, uint32_t time, wl_fixed_t x, wl_fixed_t y) {
|
||||
}
|
||||
|
||||
const static struct wl_data_device_listener data_device_listener = {
|
||||
.data_offer = handle_data_offer,
|
||||
.selection = mark_selection_offer,
|
||||
.enter = drag_enter,
|
||||
.motion = motion,
|
||||
.drop = drop,
|
||||
.leave = drag_leave,
|
||||
};
|
||||
|
||||
const static struct zwp_primary_selection_device_v1_listener primary_selection_device_listener = {
|
||||
.data_offer = handle_primary_selection_offer,
|
||||
.selection = mark_primary_selection_offer,
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
clipboard_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (_glfw.wl.dataDevice && data == (void*)_glfw.wl.dataSourceForClipboard) {
|
||||
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
static void
|
||||
primary_selection_copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (_glfw.wl.primarySelectionDevice && data == (void*)_glfw.wl.dataSourceForPrimarySelection) {
|
||||
zwp_primary_selection_device_v1_set_selection(_glfw.wl.primarySelectionDevice, data, serial);
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
void _glfwSetupWaylandDataDevice() {
|
||||
_glfw.wl.dataDevice = wl_data_device_manager_get_data_device(_glfw.wl.dataDeviceManager, _glfw.wl.seat);
|
||||
if (_glfw.wl.dataDevice) wl_data_device_add_listener(_glfw.wl.dataDevice, &data_device_listener, NULL);
|
||||
}
|
||||
|
||||
void _glfwSetupWaylandPrimarySelectionDevice() {
|
||||
_glfw.wl.primarySelectionDevice = zwp_primary_selection_device_manager_v1_get_device(_glfw.wl.primarySelectionDeviceManager, _glfw.wl.seat);
|
||||
if (_glfw.wl.primarySelectionDevice) zwp_primary_selection_device_v1_add_listener(_glfw.wl.primarySelectionDevice, &primary_selection_device_listener, NULL);
|
||||
}
|
||||
|
||||
static inline GLFWbool _glfwEnsureDataDevice() {
|
||||
if (!_glfw.wl.dataDeviceManager)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot use clipboard, data device manager is not ready");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (!_glfw.wl.dataDevice)
|
||||
{
|
||||
if (!_glfw.wl.seat)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot use clipboard, seat is not ready");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
if (!_glfw.wl.dataDevice)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot use clipboard, failed to create data device");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Clipboard setting not implemented yet");
|
||||
if (!_glfwEnsureDataDevice()) return;
|
||||
free(_glfw.wl.clipboardString);
|
||||
_glfw.wl.clipboardString = _glfw_strdup(string);
|
||||
if (_glfw.wl.dataSourceForClipboard)
|
||||
wl_data_source_destroy(_glfw.wl.dataSourceForClipboard);
|
||||
_glfw.wl.dataSourceForClipboard = wl_data_device_manager_create_data_source(_glfw.wl.dataDeviceManager);
|
||||
if (!_glfw.wl.dataSourceForClipboard)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot copy failed to create data source");
|
||||
return;
|
||||
}
|
||||
wl_data_source_add_listener(_glfw.wl.dataSourceForClipboard, &data_source_listener, NULL);
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, clipboard_mime());
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "text/plain;charset=utf-8");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "TEXT");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "STRING");
|
||||
wl_data_source_offer(_glfw.wl.dataSourceForClipboard, "UTF8_STRING");
|
||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||
const static struct wl_callback_listener clipboard_copy_callback_listener = {.done = clipboard_copy_callback_done};
|
||||
wl_callback_add_listener(callback, &clipboard_copy_callback_listener, _glfw.wl.dataSourceForClipboard);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetClipboardString(void)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Clipboard getting not implemented yet");
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.dataOffers); i++) {
|
||||
if (_glfw.wl.dataOffers[i].id && _glfw.wl.dataOffers[i].mime && _glfw.wl.dataOffers[i].offer_type == CLIPBOARD) {
|
||||
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
|
||||
free(_glfw.wl.pasteString);
|
||||
_glfw.wl.pasteString = read_data_offer(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
|
||||
return _glfw.wl.pasteString;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
||||
{
|
||||
if (!_glfw.wl.primarySelectionDevice) {
|
||||
static GLFWbool warned_about_primary_selection_device = GLFW_FALSE;
|
||||
if (!warned_about_primary_selection_device) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot copy no primary selection device available");
|
||||
warned_about_primary_selection_device = GLFW_TRUE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (_glfw.wl.primarySelectionString == string) return;
|
||||
free(_glfw.wl.primarySelectionString);
|
||||
_glfw.wl.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
if (_glfw.wl.dataSourceForPrimarySelection)
|
||||
zwp_primary_selection_source_v1_destroy(_glfw.wl.dataSourceForPrimarySelection);
|
||||
_glfw.wl.dataSourceForPrimarySelection = zwp_primary_selection_device_manager_v1_create_source(_glfw.wl.primarySelectionDeviceManager);
|
||||
if (!_glfw.wl.dataSourceForPrimarySelection)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cannot copy failed to create primary selection source");
|
||||
return;
|
||||
}
|
||||
zwp_primary_selection_source_v1_add_listener(_glfw.wl.dataSourceForPrimarySelection, &primary_selection_source_listener, NULL);
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, clipboard_mime());
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "text/plain;charset=utf-8");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "TEXT");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "STRING");
|
||||
zwp_primary_selection_source_v1_offer(_glfw.wl.dataSourceForPrimarySelection, "UTF8_STRING");
|
||||
struct wl_callback *callback = wl_display_sync(_glfw.wl.display);
|
||||
const static struct wl_callback_listener primary_selection_copy_callback_listener = {.done = primary_selection_copy_callback_done};
|
||||
wl_callback_add_listener(callback, &primary_selection_copy_callback_listener, _glfw.wl.dataSourceForPrimarySelection);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||
{
|
||||
if (_glfw.wl.dataSourceForPrimarySelection != NULL) return _glfw.wl.primarySelectionString;
|
||||
|
||||
for (size_t i = 0; i < arraysz(_glfw.wl.primarySelectionOffers); i++) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].id && _glfw.wl.primarySelectionOffers[i].mime && _glfw.wl.primarySelectionOffers[i].offer_type == PRIMARY_SELECTION) {
|
||||
if (_glfw.wl.primarySelectionOffers[i].is_self_offer) {
|
||||
return _glfw.wl.primarySelectionString;
|
||||
}
|
||||
free(_glfw.wl.pasteString);
|
||||
_glfw.wl.pasteString = read_primary_selection_offer(_glfw.wl.primarySelectionOffers[i].id, _glfw.wl.primarySelectionOffers[i].mime);
|
||||
return _glfw.wl.pasteString;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1518,6 +2087,15 @@ _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int
|
||||
glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d);
|
||||
}
|
||||
|
||||
static void
|
||||
frame_handle_redraw(void *data, struct wl_callback *callback, uint32_t time) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) data;
|
||||
if (callback == window->wl.frameCallbackData.current_wl_callback) {
|
||||
window->wl.frameCallbackData.callback(window->wl.frameCallbackData.id);
|
||||
window->wl.frameCallbackData.current_wl_callback = NULL;
|
||||
}
|
||||
wl_callback_destroy(callback);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
@@ -1539,3 +2117,16 @@ GLFWAPI struct wl_surface* glfwGetWaylandWindow(GLFWwindow* handle)
|
||||
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
|
||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwRequestWaylandFrameEvent(GLFWwindow *handle, unsigned long long id, void(*callback)(unsigned long long id)) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
static const struct wl_callback_listener frame_listener = { .done = frame_handle_redraw };
|
||||
if (window->wl.frameCallbackData.current_wl_callback) wl_callback_destroy(window->wl.frameCallbackData.current_wl_callback);
|
||||
window->wl.frameCallbackData.id = id;
|
||||
window->wl.frameCallbackData.callback = callback;
|
||||
window->wl.frameCallbackData.current_wl_callback = wl_surface_frame(window->wl.surface);
|
||||
if (window->wl.frameCallbackData.current_wl_callback) {
|
||||
wl_callback_add_listener(window->wl.frameCallbackData.current_wl_callback, &frame_listener, window);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
}
|
||||
}
|
||||
|
||||
14
glfw/x11_monitor.c
vendored
14
glfw/x11_monitor.c
vendored
@@ -433,7 +433,7 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
GLFWbool _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.gammaBroken)
|
||||
{
|
||||
@@ -449,6 +449,7 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
memcpy(ramp->blue, gamma->blue, size * sizeof(unsigned short));
|
||||
|
||||
XRRFreeGamma(gamma);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
else if (_glfw.x11.vidmode.available)
|
||||
{
|
||||
@@ -460,6 +461,13 @@ void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp)
|
||||
XF86VidModeGetGammaRamp(_glfw.x11.display,
|
||||
_glfw.x11.screen,
|
||||
ramp->size, ramp->red, ramp->green, ramp->blue);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Gamma ramp access not supported by server");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,6 +500,10 @@ void _glfwPlatformSetGammaRamp(_GLFWmonitor* monitor, const GLFWgammaramp* ramp)
|
||||
(unsigned short*) ramp->green,
|
||||
(unsigned short*) ramp->blue);
|
||||
}
|
||||
else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Gamma ramp access not supported by server");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
207
glfw/x11_window.c
vendored
207
glfw/x11_window.c
vendored
@@ -161,29 +161,6 @@ static Bool isSelPropNewValueNotify(Display* display, XEvent* event, XPointer po
|
||||
event->xproperty.atom == notification->xselection.property;
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a font cursor shape
|
||||
//
|
||||
static int translateCursorShape(int shape)
|
||||
{
|
||||
switch (shape)
|
||||
{
|
||||
case GLFW_ARROW_CURSOR:
|
||||
return XC_left_ptr;
|
||||
case GLFW_IBEAM_CURSOR:
|
||||
return XC_xterm;
|
||||
case GLFW_CROSSHAIR_CURSOR:
|
||||
return XC_crosshair;
|
||||
case GLFW_HAND_CURSOR:
|
||||
return XC_hand1;
|
||||
case GLFW_HRESIZE_CURSOR:
|
||||
return XC_sb_h_double_arrow;
|
||||
case GLFW_VRESIZE_CURSOR:
|
||||
return XC_sb_v_double_arrow;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Translates an X event modifier state mask
|
||||
//
|
||||
static int translateState(int state)
|
||||
@@ -206,23 +183,6 @@ static int translateState(int state)
|
||||
return mods;
|
||||
}
|
||||
|
||||
// Return the GLFW window corresponding to the specified X11 window
|
||||
//
|
||||
static _GLFWwindow* findWindowByHandle(Window handle)
|
||||
{
|
||||
_GLFWwindow* window;
|
||||
|
||||
if (XFindContext(_glfw.x11.display,
|
||||
handle,
|
||||
_glfw.x11.context,
|
||||
(XPointer*) &window) != 0)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
// Sends an EWMH or ICCCM event to the window manager
|
||||
//
|
||||
static void sendEventToWM(_GLFWwindow* window, Atom type,
|
||||
@@ -390,56 +350,6 @@ static void updateWindowMode(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Splits and translates a text/uri-list into separate file paths
|
||||
// NOTE: This function destroys the provided string
|
||||
//
|
||||
static char** parseUriList(char* text, int* count)
|
||||
{
|
||||
const char* prefix = "file://";
|
||||
char** paths = NULL;
|
||||
char* line;
|
||||
|
||||
*count = 0;
|
||||
|
||||
while ((line = strtok(text, "\r\n")))
|
||||
{
|
||||
text = NULL;
|
||||
|
||||
if (line[0] == '#')
|
||||
continue;
|
||||
|
||||
if (strncmp(line, prefix, strlen(prefix)) == 0)
|
||||
{
|
||||
line += strlen(prefix);
|
||||
// TODO: Validate hostname
|
||||
while (*line != '/')
|
||||
line++;
|
||||
}
|
||||
|
||||
(*count)++;
|
||||
|
||||
char* path = calloc(strlen(line) + 1, 1);
|
||||
paths = realloc(paths, *count * sizeof(char*));
|
||||
paths[*count - 1] = path;
|
||||
|
||||
while (*line)
|
||||
{
|
||||
if (line[0] == '%' && line[1] && line[2])
|
||||
{
|
||||
const char digits[3] = { line[1], line[2], '\0' };
|
||||
*path = strtol(digits, NULL, 16);
|
||||
line += 2;
|
||||
}
|
||||
else
|
||||
*path = *line;
|
||||
|
||||
path++;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
// Encode a Unicode code point to a UTF-8 stream
|
||||
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
||||
@@ -582,6 +492,15 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
const _GLFWwndconfig* wndconfig,
|
||||
Visual* visual, int depth)
|
||||
{
|
||||
int width = wndconfig->width;
|
||||
int height = wndconfig->height;
|
||||
|
||||
if (wndconfig->scaleToMonitor)
|
||||
{
|
||||
width *= _glfw.x11.contentScaleX;
|
||||
height *= _glfw.x11.contentScaleY;
|
||||
}
|
||||
|
||||
// Create a colormap based on the visual used by the current context
|
||||
window->x11.colormap = XCreateColormap(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
@@ -607,7 +526,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
window->x11.handle = XCreateWindow(_glfw.x11.display,
|
||||
_glfw.x11.root,
|
||||
0, 0,
|
||||
wndconfig->width, wndconfig->height,
|
||||
width, height,
|
||||
0, // Border width
|
||||
depth, // Color depth
|
||||
InputOutput,
|
||||
@@ -710,7 +629,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
XFree(hints);
|
||||
}
|
||||
|
||||
updateNormalHints(window, wndconfig->width, wndconfig->height);
|
||||
updateNormalHints(window, width, height);
|
||||
|
||||
// Set ICCCM WM_CLASS property
|
||||
{
|
||||
@@ -954,6 +873,7 @@ static const char* getSelectionString(Atom selection)
|
||||
Atom actualType;
|
||||
int actualFormat;
|
||||
unsigned long itemCount, bytesAfter;
|
||||
double start = glfwGetTime();
|
||||
XEvent notification, dummy;
|
||||
|
||||
XConvertSelection(_glfw.x11.display,
|
||||
@@ -968,7 +888,10 @@ static const char* getSelectionString(Atom selection)
|
||||
SelectionNotify,
|
||||
¬ification))
|
||||
{
|
||||
waitForX11Event(-1);
|
||||
double time = glfwGetTime();
|
||||
if (time - start > 2)
|
||||
return "";
|
||||
waitForX11Event(2.0 - (time - start));
|
||||
}
|
||||
|
||||
if (notification.xselection.property == None)
|
||||
@@ -999,12 +922,16 @@ static const char* getSelectionString(Atom selection)
|
||||
|
||||
for (;;)
|
||||
{
|
||||
start = glfwGetTime();
|
||||
while (!XCheckIfEvent(_glfw.x11.display,
|
||||
&dummy,
|
||||
isSelPropNewValueNotify,
|
||||
(XPointer) ¬ification))
|
||||
{
|
||||
waitForX11Event(-1);
|
||||
double time = glfwGetTime();
|
||||
if (time - start > 2)
|
||||
return "";
|
||||
waitForX11Event(2.0 - (time - start));
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
@@ -1243,8 +1170,10 @@ static void processEvent(XEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
window = findWindowByHandle(event->xany.window);
|
||||
if (window == NULL)
|
||||
if (XFindContext(_glfw.x11.display,
|
||||
event->xany.window,
|
||||
_glfw.x11.context,
|
||||
(XPointer*) &window) != 0)
|
||||
{
|
||||
// This is an event for a window that has already been destroyed
|
||||
return;
|
||||
@@ -1370,12 +1299,20 @@ static void processEvent(XEvent *event)
|
||||
|
||||
case EnterNotify:
|
||||
{
|
||||
// XEnterWindowEvent is XCrossingEvent
|
||||
const int x = event->xcrossing.x;
|
||||
const int y = event->xcrossing.y;
|
||||
|
||||
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
|
||||
// ignore the defined cursor for hidden cursor mode
|
||||
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
||||
updateCursorImage(window);
|
||||
|
||||
_glfwInputCursorEnter(window, GLFW_TRUE);
|
||||
_glfwInputCursorPos(window, x, y);
|
||||
|
||||
window->x11.lastCursorPosX = x;
|
||||
window->x11.lastCursorPosY = y;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2179,6 +2116,11 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
*yscale = _glfw.x11.contentScaleY;
|
||||
}
|
||||
|
||||
double _glfwPlatformGetDoubleClickInterval(_GLFWwindow* window)
|
||||
{
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (window->x11.overrideRedirect)
|
||||
@@ -2357,6 +2299,10 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
Atom* states;
|
||||
unsigned long i;
|
||||
GLFWbool maximized = GLFW_FALSE;
|
||||
if (!_glfw.x11.NET_WM_STATE ||
|
||||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT ||
|
||||
!_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ)
|
||||
return maximized;
|
||||
const unsigned long count =
|
||||
_glfwGetWindowPropertyX11(window->x11.handle,
|
||||
_glfw.x11.NET_WM_STATE,
|
||||
@@ -2680,8 +2626,24 @@ int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape)
|
||||
{
|
||||
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display,
|
||||
translateCursorShape(shape));
|
||||
int native = 0;
|
||||
|
||||
if (shape == GLFW_ARROW_CURSOR)
|
||||
native = XC_left_ptr;
|
||||
else if (shape == GLFW_IBEAM_CURSOR)
|
||||
native = XC_xterm;
|
||||
else if (shape == GLFW_CROSSHAIR_CURSOR)
|
||||
native = XC_crosshair;
|
||||
else if (shape == GLFW_HAND_CURSOR)
|
||||
native = XC_hand1;
|
||||
else if (shape == GLFW_HRESIZE_CURSOR)
|
||||
native = XC_sb_h_double_arrow;
|
||||
else if (shape == GLFW_VRESIZE_CURSOR)
|
||||
native = XC_sb_v_double_arrow;
|
||||
else
|
||||
return GLFW_FALSE;
|
||||
|
||||
cursor->x11.handle = XCreateFontCursor(_glfw.x11.display, native);
|
||||
if (!cursor->x11.handle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
@@ -2730,6 +2692,29 @@ const char* _glfwPlatformGetClipboardString(void)
|
||||
return getSelectionString(_glfw.x11.CLIPBOARD);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetPrimarySelectionString(const char* string)
|
||||
{
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.PRIMARY,
|
||||
_glfw.x11.helperWindowHandle,
|
||||
CurrentTime);
|
||||
|
||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
||||
_glfw.x11.helperWindowHandle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to become owner of primary selection");
|
||||
}
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetPrimarySelectionString(void)
|
||||
{
|
||||
return getSelectionString(_glfw.x11.PRIMARY);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
{
|
||||
if (!_glfw.vk.KHR_surface)
|
||||
@@ -2901,32 +2886,6 @@ GLFWAPI Window glfwGetX11Window(GLFWwindow* handle)
|
||||
return window->x11.handle;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetX11SelectionString(const char* string)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.PRIMARY,
|
||||
_glfw.x11.helperWindowHandle,
|
||||
CurrentTime);
|
||||
|
||||
if (XGetSelectionOwner(_glfw.x11.display, _glfw.x11.PRIMARY) !=
|
||||
_glfw.x11.helperWindowHandle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Failed to become owner of primary selection");
|
||||
}
|
||||
}
|
||||
|
||||
GLFWAPI const char* glfwGetX11SelectionString(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return getSelectionString(_glfw.x11.PRIMARY);
|
||||
}
|
||||
|
||||
GLFWAPI int glfwGetXKBScancode(const char* keyName, GLFWbool caseSensitive) {
|
||||
return glfw_xkb_keysym_from_name(keyName, caseSensitive);
|
||||
}
|
||||
|
||||
31
glfw/xkb_glfw.c
vendored
31
glfw/xkb_glfw.c
vendored
@@ -33,7 +33,7 @@
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
|
||||
|
||||
#define map_key(key) { \
|
||||
#define map_key(key) \
|
||||
switch(key) { \
|
||||
S(space, SPACE); \
|
||||
S(apostrophe, APOSTROPHE); \
|
||||
@@ -46,6 +46,7 @@
|
||||
S(bracketleft, LEFT_BRACKET); \
|
||||
S(backslash, BACKSLASH); \
|
||||
S(bracketright, RIGHT_BRACKET); \
|
||||
S(plus, PLUS); \
|
||||
S(grave, GRAVE_ACCENT); \
|
||||
S(Escape, ESCAPE); \
|
||||
S(Return, ENTER); \
|
||||
@@ -93,10 +94,6 @@
|
||||
D(A, Z, A, Z); \
|
||||
R(F1, F25, F1, F25); \
|
||||
R(KP_0, KP_9, KP_0, KP_9); \
|
||||
default: \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
|
||||
static int
|
||||
glfw_key_for_sym(xkb_keysym_t key) {
|
||||
@@ -105,6 +102,13 @@ glfw_key_for_sym(xkb_keysym_t key) {
|
||||
#define R(s, e, gs, ...) case XKB_KEY_##s ... XKB_KEY_##e: return GLFW_KEY_##gs + key - XKB_KEY_##s
|
||||
#define D(s, e, gs, ...) R(s, e, gs)
|
||||
map_key(key)
|
||||
S(KP_Up, UP);
|
||||
S(KP_Down, DOWN);
|
||||
S(KP_Left, LEFT);
|
||||
S(KP_Right, RIGHT);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
#undef F
|
||||
#undef D
|
||||
@@ -119,6 +123,9 @@ glfw_xkb_sym_for_key(int key) {
|
||||
#define R(s, e, gs, ge) case GLFW_KEY_##gs ... GLFW_KEY_##ge: return XKB_KEY_##s + key - GLFW_KEY_##gs
|
||||
#define D(...)
|
||||
map_key(key)
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
#undef F
|
||||
#undef D
|
||||
@@ -249,6 +256,7 @@ load_compose_tables(_GLFWXKBData *xkb) {
|
||||
if (!xkb->states.composeState) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
||||
}
|
||||
xkb_compose_table_unref(compose_table);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
@@ -329,12 +337,14 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
|
||||
static KeyEvent key_event = {};
|
||||
|
||||
static inline xkb_keysym_t
|
||||
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym) {
|
||||
compose_symbol(struct xkb_compose_state *composeState, xkb_keysym_t sym, int *compose_completed) {
|
||||
*compose_completed = 0;
|
||||
if (sym == XKB_KEY_NoSymbol || !composeState) return sym;
|
||||
if (xkb_compose_state_feed(composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
||||
switch (xkb_compose_state_get_status(composeState)) {
|
||||
case XKB_COMPOSE_COMPOSED:
|
||||
xkb_compose_state_get_utf8(composeState, key_event.text, sizeof(key_event.text));
|
||||
*compose_completed = 1;
|
||||
return xkb_compose_state_get_one_sym(composeState);
|
||||
case XKB_COMPOSE_COMPOSING:
|
||||
case XKB_COMPOSE_CANCELLED:
|
||||
@@ -432,9 +442,9 @@ glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed) {
|
||||
debug("↳ to application: glfw_keycode: 0x%x (%s) keysym: 0x%x (%s) action: %s %s text: %s\n",
|
||||
ev->glfw_keycode, _glfwGetKeyName(ev->glfw_keycode), ev->keysym, glfw_xkb_keysym_name(ev->keysym),
|
||||
(ev->action == GLFW_RELEASE ? "RELEASE" : (ev->action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
||||
format_mods(ev->glfw_modifiers), key_event.text
|
||||
format_mods(ev->glfw_modifiers), ev->text
|
||||
);
|
||||
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, key_event.text, 0);
|
||||
_glfwInputKeyboard(window, ev->glfw_keycode, ev->keysym, ev->action, ev->glfw_modifiers, ev->text, 0);
|
||||
} else debug("↳ discarded\n");
|
||||
if (!is_release && handled_by_ime) last_handled_press_keycode = ev->keycode;
|
||||
}
|
||||
@@ -465,8 +475,9 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
||||
debug("clean_sym: %s ", glfw_xkb_keysym_name(clean_syms[0]));
|
||||
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
|
||||
const char *text_type = "composed_text";
|
||||
glfw_sym = compose_symbol(sg->composeState, syms[0]);
|
||||
if (glfw_sym == XKB_KEY_NoSymbol) {
|
||||
int compose_completed;
|
||||
glfw_sym = compose_symbol(sg->composeState, syms[0], &compose_completed);
|
||||
if (glfw_sym == XKB_KEY_NoSymbol && !compose_completed) {
|
||||
debug("compose not complete, ignoring.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ from .search import BadRegex, Search
|
||||
try:
|
||||
from .highlight import initialize_highlighter, highlight_collection
|
||||
except ImportError:
|
||||
initialize_highlighter = None
|
||||
initialize_highlighter = highlight_collection = None
|
||||
|
||||
|
||||
INITIALIZING, COLLECTED, DIFFED, COMMAND, MESSAGE = range(5)
|
||||
|
||||
@@ -84,9 +84,17 @@ class Hints(Handler):
|
||||
self.args = args
|
||||
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
|
||||
self.multiple = args.multiple
|
||||
self.match_suffix = self.get_match_suffix(args)
|
||||
self.chosen = []
|
||||
self.reset()
|
||||
|
||||
def get_match_suffix(self, args):
|
||||
if args.add_trailing_space == 'always':
|
||||
return ' '
|
||||
if args.add_trailing_space == 'never':
|
||||
return ''
|
||||
return ' ' if args.multiple else ''
|
||||
|
||||
def reset(self):
|
||||
self.current_input = ''
|
||||
self.current_text = None
|
||||
@@ -112,7 +120,7 @@ class Hints(Handler):
|
||||
if encode_hint(idx).startswith(self.current_input)
|
||||
]
|
||||
if len(matches) == 1:
|
||||
self.chosen.append(matches[0].text)
|
||||
self.chosen.append(matches[0].text + self.match_suffix)
|
||||
if self.multiple:
|
||||
self.ignore_mark_indices.add(matches[0].index)
|
||||
self.reset()
|
||||
@@ -130,7 +138,7 @@ class Hints(Handler):
|
||||
elif key_event is enter_key and self.current_input:
|
||||
try:
|
||||
idx = decode_hint(self.current_input)
|
||||
self.chosen.append(self.index_map[idx].text)
|
||||
self.chosen.append(self.index_map[idx].text + self.match_suffix)
|
||||
self.ignore_mark_indices.add(idx)
|
||||
except Exception:
|
||||
self.current_input = ''
|
||||
@@ -363,6 +371,13 @@ The minimum number of characters to consider a match.
|
||||
type=bool-set
|
||||
Select multiple matches and perform the action on all of them together at the end.
|
||||
In this mode, press :kbd:`Esc` to finish selecting.
|
||||
|
||||
|
||||
--add-trailing-space
|
||||
default=auto
|
||||
choices=auto,always,never
|
||||
Add trailing space after matched text. Defaults to auto, which adds the space
|
||||
when used together with --multiple.
|
||||
'''.format(','.join(sorted(URL_PREFIXES))).format
|
||||
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||
usage = ''
|
||||
|
||||
@@ -34,9 +34,9 @@ Horizontal alignment for the displayed image.
|
||||
--place
|
||||
Choose where on the screen to display the image. The image will
|
||||
be scaled to fit into the specified rectangle. The syntax for
|
||||
specifying rectanges is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>. All measurements
|
||||
are in cells (i.e. cursor positions) with the origin :italic:`(0, 0)` at
|
||||
the top-left corner of the screen.
|
||||
specifying rectangles is <:italic:`width`>x<:italic:`height`>@<:italic:`left`>x<:italic:`top`>.
|
||||
All measurements are in cells (i.e. cursor positions) with the
|
||||
origin :italic:`(0, 0)` at the top-left corner of the screen.
|
||||
|
||||
|
||||
--scale-up
|
||||
@@ -174,7 +174,7 @@ def show(outfile, width, height, fmt, transmit_mode='t', align='center', place=N
|
||||
write_chunked(cmd, data)
|
||||
|
||||
|
||||
def process(path, args):
|
||||
def process(path, args, is_tempfile):
|
||||
m = identify(path)
|
||||
ss = screen_size()
|
||||
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
|
||||
@@ -183,7 +183,7 @@ def process(path, args):
|
||||
needs_scaling = needs_scaling or args.scale_up
|
||||
if m.fmt == 'png' and not needs_scaling:
|
||||
outfile = path
|
||||
transmit_mode = 'f'
|
||||
transmit_mode = 't' if is_tempfile else 'f'
|
||||
fmt = 100
|
||||
width, height = m.width, m.height
|
||||
else:
|
||||
@@ -249,7 +249,8 @@ def parse_place(raw):
|
||||
help_text = (
|
||||
'A cat like utility to display images in the terminal.'
|
||||
' You can specify multiple image files and/or directories.'
|
||||
' Directories are scanned recursively for image files.'
|
||||
' Directories are scanned recursively for image files. If STDIN'
|
||||
' is not a terminal, image data will be read from it as well.'
|
||||
)
|
||||
usage = 'image-file ...'
|
||||
|
||||
@@ -267,6 +268,13 @@ def main(args=sys.argv):
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
sys.stdout = open(os.ctermid(), 'w')
|
||||
stdin_data = None
|
||||
if not sys.stdin.isatty():
|
||||
stdin_data = sys.stdin.buffer.read()
|
||||
items.insert(0, stdin_data)
|
||||
sys.stdin.close()
|
||||
sys.stdin = open(os.ctermid(), 'r')
|
||||
|
||||
screen_size = screen_size_function()
|
||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||
if screen_size().width == 0:
|
||||
@@ -298,16 +306,22 @@ def main(args=sys.argv):
|
||||
if not items:
|
||||
raise SystemExit('You must specify at least one file to cat')
|
||||
if args.place:
|
||||
if len(items) > 1 or os.path.isdir(items[0]):
|
||||
if len(items) > 1 or (isinstance(items[0], str) and os.path.isdir(items[0])):
|
||||
raise SystemExit('The --place option can only be used with a single image')
|
||||
sys.stdout.buffer.write(b'\0337') # save cursor
|
||||
for item in items:
|
||||
is_tempfile = False
|
||||
try:
|
||||
if isinstance(item, bytes):
|
||||
tf = NamedTemporaryFile(prefix='stdin-image-data-', delete=False)
|
||||
tf.write(item), tf.close()
|
||||
item = tf.name
|
||||
is_tempfile = True
|
||||
if os.path.isdir(item):
|
||||
for x in scan(item):
|
||||
process(item, args)
|
||||
else:
|
||||
process(item, args)
|
||||
process(item, args, is_tempfile)
|
||||
except NoImageMagick as e:
|
||||
raise SystemExit(str(e))
|
||||
except ConvertFailed as e:
|
||||
|
||||
@@ -43,7 +43,7 @@ class Resize(Handler):
|
||||
cmdline = [resize_window.name, '--self', '--increment={}'.format(increment), '--axis=' + axis]
|
||||
opts, items = parse_subcommand_cli(resize_window, cmdline)
|
||||
payload = resize_window(global_opts, opts, items)
|
||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload}
|
||||
send = {'cmd': resize_window.name, 'version': version, 'payload': payload, 'no_response': False}
|
||||
self.write(encode_send(send))
|
||||
|
||||
def on_kitty_cmd_response(self, response):
|
||||
|
||||
@@ -46,6 +46,7 @@ def create_kitten_handler(kitten, orig_args):
|
||||
m = import_kitten_main_module(config_dir, kitten)
|
||||
ans = partial(m['end'], [kitten] + orig_args)
|
||||
ans.type_of_input = getattr(m['end'], 'type_of_input', None)
|
||||
ans.no_ui = getattr(m['end'], 'no_ui', False)
|
||||
return ans
|
||||
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from ..tui.line_edit import LineEdit
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, color_code, colored, cursor, faint, set_line_wrapping,
|
||||
clear_screen, colored, cursor, faint, set_line_wrapping,
|
||||
set_window_title, sgr, styled
|
||||
)
|
||||
|
||||
@@ -159,30 +159,32 @@ class Table:
|
||||
self.last_cols, self.last_rows = cols, rows
|
||||
self.layout_dirty = False
|
||||
|
||||
def safe_chr(codepoint):
|
||||
return chr(codepoint).encode('utf-8', 'replace').decode('utf-8')
|
||||
|
||||
if self.mode is NAME:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), name(codepoint)
|
||||
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), name(codepoint)
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
is_current = i == self.current_idx
|
||||
if is_current:
|
||||
yield sgr(color_code('gray', base=40))
|
||||
yield colored(idx, 'green') + ' '
|
||||
yield colored(c, 'black' if is_current else 'gray', True) + ' '
|
||||
text = colored(idx, 'green') + ' ' + sgr('49') + c + ' '
|
||||
w = wcswidth(c)
|
||||
if w < 2:
|
||||
yield ' ' * (2 - w)
|
||||
text += ' ' * (2 - w)
|
||||
if len(desc) > space_for_desc:
|
||||
desc = desc[:space_for_desc - 1] + '…'
|
||||
yield faint(desc)
|
||||
text += desc[:space_for_desc - 1] + '…'
|
||||
else:
|
||||
text += desc
|
||||
extra = space_for_desc - len(desc)
|
||||
if extra > 0:
|
||||
yield ' ' * extra
|
||||
if is_current:
|
||||
yield sgr('49')
|
||||
text += ' ' * extra
|
||||
|
||||
yield styled(text, reverse=True if is_current else None)
|
||||
|
||||
else:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), ''
|
||||
return encode_hint(i).ljust(idx_size), safe_chr(codepoint), ''
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
yield colored(idx, 'green') + ' '
|
||||
|
||||
30346
kittens/unicode_input/names.h
generated
30346
kittens/unicode_input/names.h
generated
File diff suppressed because one or more lines are too long
208
kitty/boss.py
208
kitty/boss.py
@@ -10,6 +10,7 @@ from functools import partial
|
||||
from gettext import gettext as _
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from .child import cached_process_data
|
||||
from .cli import create_opts, parse_args
|
||||
from .conf.utils import to_cmdline
|
||||
from .config import initial_window_size_func, prepare_config_file_for_editing
|
||||
@@ -19,10 +20,11 @@ from .constants import (
|
||||
)
|
||||
from .fast_data_types import (
|
||||
ChildMonitor, background_opacity_of, change_background_opacity,
|
||||
create_os_window, current_os_window, destroy_global_data,
|
||||
get_clipboard_string, glfw_post_empty_event, global_font_size,
|
||||
mark_os_window_for_close, os_window_font_size, patch_global_colors,
|
||||
set_clipboard_string, set_in_sequence_mode, toggle_fullscreen
|
||||
change_os_window_state, create_os_window, current_os_window,
|
||||
destroy_global_data, get_clipboard_string, glfw_post_empty_event,
|
||||
global_font_size, mark_os_window_for_close, os_window_font_size,
|
||||
patch_global_colors, set_clipboard_string, set_in_sequence_mode,
|
||||
toggle_fullscreen
|
||||
)
|
||||
from .keys import get_shortcut, shortcut_matches
|
||||
from .layout import set_draw_minimal_borders
|
||||
@@ -31,9 +33,9 @@ from .rgb import Color, color_from_int
|
||||
from .session import create_session
|
||||
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
|
||||
from .utils import (
|
||||
get_editor, get_primary_selection, log_error, open_url, parse_address_spec,
|
||||
remove_socket_file, safe_print, set_primary_selection, single_instance,
|
||||
startup_notification_handler
|
||||
get_editor, get_primary_selection, is_path_in_temp_dir, log_error,
|
||||
open_url, parse_address_spec, remove_socket_file, safe_print,
|
||||
set_primary_selection, single_instance, startup_notification_handler
|
||||
)
|
||||
|
||||
|
||||
@@ -47,6 +49,31 @@ def listen_on(spec):
|
||||
return s.fileno()
|
||||
|
||||
|
||||
def data_for_at(w, arg, add_wrap_markers=False):
|
||||
def as_text(**kw):
|
||||
kw['add_wrap_markers'] = add_wrap_markers
|
||||
return w.as_text(**kw)
|
||||
|
||||
if arg == '@selection':
|
||||
return w.text_for_selection()
|
||||
if arg == '@ansi':
|
||||
return as_text(as_ansi=True, add_history=True)
|
||||
if arg == '@text':
|
||||
return as_text(add_history=True)
|
||||
if arg == '@screen':
|
||||
return as_text()
|
||||
if arg == '@ansi_screen':
|
||||
return as_text(as_ansi=True)
|
||||
if arg == '@alternate':
|
||||
return as_text(alternate_screen=True)
|
||||
if arg == '@alternate_scrollback':
|
||||
return as_text(alternate_screen=True, add_history=True)
|
||||
if arg == '@ansi_alternate':
|
||||
return as_text(as_ansi=True, alternate_screen=True)
|
||||
if arg == '@ansi_alternate_scrollback':
|
||||
return as_text(as_ansi=True, alternate_screen=True, add_history=True)
|
||||
|
||||
|
||||
class DumpCommands: # {{{
|
||||
|
||||
def __init__(self, args):
|
||||
@@ -80,6 +107,7 @@ class Boss:
|
||||
set_draw_minimal_borders(opts)
|
||||
self.window_id_map = WeakValueDictionary()
|
||||
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
|
||||
self.startup_cursor_text_color = opts.cursor_text_color
|
||||
self.pending_sequences = None
|
||||
self.cached_values = cached_values
|
||||
self.os_window_map = {}
|
||||
@@ -103,6 +131,11 @@ class Boss:
|
||||
if new_os_window_trigger is not None:
|
||||
self.keymap.pop(new_os_window_trigger, None)
|
||||
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||
if args.start_as != 'normal':
|
||||
if args.start_as == 'fullscreen':
|
||||
self.toggle_fullscreen()
|
||||
else:
|
||||
change_os_window_state(args.start_as)
|
||||
|
||||
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, opts_for_size=None, startup_id=None):
|
||||
if os_window_id is None:
|
||||
@@ -118,14 +151,15 @@ class Boss:
|
||||
return os_window_id
|
||||
|
||||
def list_os_windows(self):
|
||||
active_tab, active_window = self.active_tab, self.active_window
|
||||
active_tab_manager = self.active_tab_manager
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||
}
|
||||
with cached_process_data():
|
||||
active_tab, active_window = self.active_tab, self.active_window
|
||||
active_tab_manager = self.active_tab_manager
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||
}
|
||||
|
||||
@property
|
||||
def all_tab_managers(self):
|
||||
@@ -213,9 +247,16 @@ class Boss:
|
||||
def new_os_window(self, *args):
|
||||
self._new_os_window(args)
|
||||
|
||||
def new_os_window_with_cwd(self, *args):
|
||||
@property
|
||||
def active_window_for_cwd(self):
|
||||
w = self.active_window
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
if w is not None and w.overlay_for is not None and w.overlay_for in self.window_id_map:
|
||||
w = self.window_id_map[w.overlay_for]
|
||||
return w
|
||||
|
||||
def new_os_window_with_cwd(self, *args):
|
||||
w = self.active_window_for_cwd
|
||||
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||
self._new_os_window(args, cwd_from)
|
||||
|
||||
def add_child(self, window):
|
||||
@@ -339,6 +380,9 @@ class Boss:
|
||||
reset = action == 'reset'
|
||||
how = 3 if action == 'scrollback' else 2
|
||||
for w in windows:
|
||||
if action == 'scroll':
|
||||
w.screen.scroll_until_cursor()
|
||||
continue
|
||||
w.screen.cursor.x = w.screen.cursor.y = 0
|
||||
if reset:
|
||||
w.screen.reset()
|
||||
@@ -422,10 +466,10 @@ class Boss:
|
||||
_('You must set the dynamic_background_opacity option in kitty.conf to be able to change background opacity'))
|
||||
os_window_id = window.os_window_id
|
||||
if opacity[0] in '+-':
|
||||
opacity = background_opacity_of(os_window_id)
|
||||
if opacity is None:
|
||||
old_opacity = background_opacity_of(os_window_id)
|
||||
if old_opacity is None:
|
||||
return
|
||||
opacity += float(opacity)
|
||||
opacity = old_opacity + float(opacity)
|
||||
elif opacity == 'default':
|
||||
opacity = self.opts.background_opacity
|
||||
else:
|
||||
@@ -518,6 +562,8 @@ class Boss:
|
||||
if key_action is not None:
|
||||
f = getattr(self, key_action.func, None)
|
||||
if f is not None:
|
||||
if self.args.debug_keyboard:
|
||||
print('Keypress matched action:', f.__name__)
|
||||
passthrough = f(*key_action.args)
|
||||
if passthrough is not True:
|
||||
return True
|
||||
@@ -531,6 +577,8 @@ class Boss:
|
||||
f = getattr(tab, key_action.func, getattr(window, key_action.func, None))
|
||||
if f is not None:
|
||||
passthrough = f(*key_action.args)
|
||||
if self.args.debug_keyboard:
|
||||
print('Keypress matched action:', f.__name__)
|
||||
if passthrough is not True:
|
||||
return True
|
||||
return False
|
||||
@@ -608,17 +656,20 @@ class Boss:
|
||||
return output
|
||||
|
||||
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
|
||||
orig_args, args = list(args), list(args)
|
||||
from kittens.runner import create_kitten_handler
|
||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||
if window is None:
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
else:
|
||||
w = window
|
||||
tab = w.tabref()
|
||||
if end_kitten.no_ui:
|
||||
end_kitten(None, getattr(w, 'id', None), self)
|
||||
return
|
||||
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
orig_args, args = list(args), list(args)
|
||||
from kittens.runner import create_kitten_handler
|
||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||
args[0:0] = [config_dir, kitten]
|
||||
if input_data is None:
|
||||
type_of_input = end_kitten.type_of_input
|
||||
@@ -768,25 +819,74 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.next_tab(-1)
|
||||
|
||||
prev_tab = previous_tab
|
||||
|
||||
def process_stdin_source(self, window=None, stdin=None):
|
||||
w = window or self.active_window
|
||||
env = None
|
||||
if stdin:
|
||||
add_wrap_markers = stdin.endswith('_wrap')
|
||||
if add_wrap_markers:
|
||||
stdin = stdin[:-len('_wrap')]
|
||||
stdin = data_for_at(w, stdin, add_wrap_markers=add_wrap_markers)
|
||||
if stdin is not None:
|
||||
pipe_data = w.pipe_data(stdin, has_wrap_markers=add_wrap_markers) if w else {}
|
||||
if pipe_data:
|
||||
env = {
|
||||
'KITTY_PIPE_DATA':
|
||||
'{scrolled_by}:{cursor_x},{cursor_y}:{lines},{columns}'.format(**pipe_data)
|
||||
}
|
||||
stdin = stdin.encode('utf-8')
|
||||
return env, stdin
|
||||
|
||||
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
|
||||
w = window or self.active_window
|
||||
env, stdin = self.process_stdin_source(w, stdin)
|
||||
cmdline = []
|
||||
for arg in cmd:
|
||||
if arg == '@selection':
|
||||
arg = data_for_at(w, arg)
|
||||
if not arg:
|
||||
continue
|
||||
cmdline.append(arg)
|
||||
overlay_for = w.id if as_overlay and w.overlay_for is None else None
|
||||
return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for, env=env)
|
||||
|
||||
def pipe(self, source, dest, exe, *args):
|
||||
cmd = [exe] + list(args)
|
||||
window = self.active_window
|
||||
cwd_from = window.child.pid_for_cwd if window else None
|
||||
|
||||
def create_window():
|
||||
return self.special_window_for_cmd(
|
||||
cmd, stdin=source, as_overlay=dest == 'overlay', cwd_from=cwd_from)
|
||||
|
||||
if dest == 'overlay' or dest == 'window':
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
return tab.new_special_window(create_window())
|
||||
elif dest == 'tab':
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.new_tab(special_window=create_window(), cwd_from=cwd_from)
|
||||
elif dest == 'os_window':
|
||||
self._new_os_window(create_window(), cwd_from=cwd_from)
|
||||
else:
|
||||
import subprocess
|
||||
env, stdin = self.process_stdin_source(stdin=source, window=window)
|
||||
if stdin:
|
||||
p = subprocess.Popen(cmd, env=env, stdin=subprocess.PIPE)
|
||||
p.communicate(stdin)
|
||||
else:
|
||||
subprocess.Popen(cmd)
|
||||
|
||||
def args_to_special_window(self, args, cwd_from=None):
|
||||
args = list(args)
|
||||
stdin = None
|
||||
w = self.active_window
|
||||
|
||||
def data_for_at(arg):
|
||||
if arg == '@selection':
|
||||
return w.text_for_selection()
|
||||
if arg == '@ansi':
|
||||
return w.as_text(as_ansi=True, add_history=True)
|
||||
if arg == '@text':
|
||||
return w.as_text(add_history=True)
|
||||
if arg == '@screen':
|
||||
return w.as_text()
|
||||
if arg == '@ansi_screen':
|
||||
return w.as_text(as_ansi=True)
|
||||
|
||||
if args[0].startswith('@') and args[0] != '@':
|
||||
stdin = data_for_at(args[0]) or None
|
||||
stdin = data_for_at(w, args[0]) or None
|
||||
if stdin is not None:
|
||||
stdin = stdin.encode('utf-8')
|
||||
del args[0]
|
||||
@@ -794,13 +894,13 @@ class Boss:
|
||||
cmd = []
|
||||
for arg in args:
|
||||
if arg == '@selection':
|
||||
arg = data_for_at(arg)
|
||||
arg = data_for_at(w, arg)
|
||||
if not arg:
|
||||
continue
|
||||
cmd.append(arg)
|
||||
return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
|
||||
|
||||
def _new_tab(self, args, cwd_from=None):
|
||||
def _new_tab(self, args, cwd_from=None, as_neighbor=False):
|
||||
special_window = None
|
||||
if args:
|
||||
if isinstance(args, SpecialWindowInstance):
|
||||
@@ -809,15 +909,22 @@ class Boss:
|
||||
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
|
||||
return tm.new_tab(special_window=special_window, cwd_from=cwd_from, as_neighbor=as_neighbor)
|
||||
|
||||
def _create_tab(self, args, cwd_from=None):
|
||||
as_neighbor = False
|
||||
if args and args[0].startswith('!'):
|
||||
as_neighbor = 'neighbor' in args[0][1:].split(',')
|
||||
args = args[1:]
|
||||
self._new_tab(args, as_neighbor=as_neighbor, cwd_from=cwd_from)
|
||||
|
||||
def new_tab(self, *args):
|
||||
self._new_tab(args)
|
||||
self._create_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)
|
||||
w = self.active_window_for_cwd
|
||||
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||
self._create_tab(args, cwd_from=cwd_from)
|
||||
|
||||
def _new_window(self, args, cwd_from=None):
|
||||
tab = self.active_tab
|
||||
@@ -831,10 +938,10 @@ class Boss:
|
||||
self._new_window(args)
|
||||
|
||||
def new_window_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
w = self.active_window_for_cwd
|
||||
if w is None:
|
||||
return self.new_window(*args)
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
cwd_from = w.child.pid_for_cwd if w is not None else None
|
||||
self._new_window(args, cwd_from=cwd_from)
|
||||
|
||||
def move_tab_forward(self):
|
||||
@@ -847,11 +954,20 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.move_tab(-1)
|
||||
|
||||
def patch_colors(self, spec, configured=False):
|
||||
def patch_colors(self, spec, cursor_text_color, configured=False):
|
||||
if configured:
|
||||
for k, v in spec.items():
|
||||
if hasattr(self.opts, k):
|
||||
setattr(self.opts, k, color_from_int(v))
|
||||
if cursor_text_color is not False:
|
||||
self.opts.cursor_text_color = cursor_text_color
|
||||
for tm in self.all_tab_managers:
|
||||
tm.tab_bar.patch_colors(spec)
|
||||
patch_global_colors(spec, configured)
|
||||
|
||||
def safe_delete_temp_file(self, path):
|
||||
if is_path_in_temp_dir(path):
|
||||
try:
|
||||
os.remove(path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
@@ -64,7 +64,7 @@ vec4 calculate_foreground() {
|
||||
// Since strike and text are the same color, we simply add the alpha values
|
||||
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
||||
// Underline color might be different, so alpha blend
|
||||
vec4 ans = alpha_blend(decoration_fg, underline_alpha * effective_text_alpha, fg, combined_alpha * effective_text_alpha);
|
||||
vec4 ans = alpha_blend(fg, combined_alpha * effective_text_alpha, decoration_fg, underline_alpha * effective_text_alpha);
|
||||
return mix(ans, cursor_color_vec, cursor_alpha);
|
||||
}
|
||||
#endif
|
||||
@@ -101,12 +101,12 @@ void main() {
|
||||
#else
|
||||
// SIMPLE
|
||||
#ifdef TRANSPARENT
|
||||
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 = alpha_blend_premul(fg.rgb, fg.a, background, bg_alpha);
|
||||
final_color = vec4(final_color.rgb, final_color.a);
|
||||
#else
|
||||
// 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(final_color.rgb / final_color.a, final_color.a);
|
||||
final_color = vec4(final_color.rgb, final_color.a);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -5,13 +5,12 @@
|
||||
#define REVERSE_SHIFT {REVERSE_SHIFT}
|
||||
#define STRIKE_SHIFT {STRIKE_SHIFT}
|
||||
#define DIM_SHIFT {DIM_SHIFT}
|
||||
#define CURSOR_TEXT_COLOR {CURSOR_TEXT_COLOR}
|
||||
|
||||
// Inputs {{{
|
||||
layout(std140) uniform CellRenderData {
|
||||
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
|
||||
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity, cursor_text_uses_bg;
|
||||
|
||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
|
||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, cursor_text_color, url_color, url_style, inverted;
|
||||
|
||||
uint xnum, ynum, cursor_fg_sprite_idx;
|
||||
float cursor_x, cursor_y, cursor_w;
|
||||
@@ -182,8 +181,9 @@ void main() {
|
||||
|
||||
// Cursor
|
||||
cursor_color_vec = vec4(color_to_vec(cursor_color), 1.0);
|
||||
foreground = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, foreground);
|
||||
decoration_fg = choose_color(cell_has_block_cursor, CURSOR_TEXT_COLOR, decoration_fg);
|
||||
vec3 final_cursor_text_color = mix(color_to_vec(cursor_text_color), bg, cursor_text_uses_bg);
|
||||
foreground = choose_color(cell_has_block_cursor, final_cursor_text_color, foreground);
|
||||
decoration_fg = choose_color(cell_has_block_cursor, final_cursor_text_color, decoration_fg);
|
||||
cursor_pos = to_sprite_pos(pos, cursor_fg_sprite_idx * uint(cell_has_cursor), ZERO, ZERO);
|
||||
#endif
|
||||
// }}}
|
||||
|
||||
@@ -372,7 +372,7 @@ parse_input(ChildMonitor *self) {
|
||||
}
|
||||
|
||||
if (UNLIKELY(kill_signal_received)) {
|
||||
global_state.close_all_windows = true;
|
||||
global_state.terminate = true;
|
||||
} else {
|
||||
count = self->count;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
@@ -565,7 +565,10 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
|
||||
}
|
||||
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
|
||||
}
|
||||
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
|
||||
if (OPT(mouse_hide_wait) > 0 && !is_mouse_hidden(os_window)) {
|
||||
if (now - os_window->last_mouse_activity_at >= OPT(mouse_hide_wait)) hide_mouse(os_window);
|
||||
else set_maximum_wait(OPT(mouse_hide_wait) - now + os_window->last_mouse_activity_at);
|
||||
}
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
*active_window_bg = OPT(background);
|
||||
for (unsigned int i = 0; i < tab->num_windows; i++) {
|
||||
@@ -599,6 +602,8 @@ prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *activ
|
||||
|
||||
static inline void
|
||||
render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows) {
|
||||
// ensure all pixels are cleared to background color at least once in every buffer
|
||||
if (os_window->clear_count++ < 3) blank_os_window(os_window);
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
BorderRects *br = &tab->border_rects;
|
||||
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows, os_window);
|
||||
@@ -620,6 +625,7 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
|
||||
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;
|
||||
os_window->focused_at_last_render = os_window->is_focused;
|
||||
os_window->is_damaged = false;
|
||||
if (global_state.is_wayland && OPT(sync_to_monitor)) wayland_request_frame_render(os_window);
|
||||
#undef WD
|
||||
#undef TD
|
||||
}
|
||||
@@ -635,6 +641,10 @@ render(double now) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
|
||||
if (global_state.is_wayland && w->wayland_render_state != RENDER_FRAME_READY && OPT(sync_to_monitor)) {
|
||||
if (w->wayland_render_state == RENDER_FRAME_NOT_REQUESTED) wayland_request_frame_render(w);
|
||||
continue;
|
||||
}
|
||||
bool needs_render = w->is_damaged;
|
||||
make_os_window_context_current(w);
|
||||
if (w->viewport_size_dirty) {
|
||||
@@ -727,7 +737,6 @@ process_pending_resizes(double now) {
|
||||
static inline void
|
||||
close_all_windows() {
|
||||
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
|
||||
global_state.close_all_windows = false;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -783,7 +792,13 @@ main_loop(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
}
|
||||
#endif
|
||||
parse_input(self);
|
||||
if (global_state.close_all_windows) close_all_windows();
|
||||
if (global_state.terminate) {
|
||||
global_state.terminate = false;
|
||||
close_all_windows();
|
||||
#ifdef __APPLE__
|
||||
request_application_quit();
|
||||
#endif
|
||||
}
|
||||
has_open_windows = process_pending_closes(self);
|
||||
}
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
@@ -864,7 +879,7 @@ read_bytes(int fd, Screen *screen) {
|
||||
while(true) {
|
||||
len = read(fd, screen->read_buf + orig_sz, available_buffer_space);
|
||||
if (len < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EINTR || errno == EAGAIN) continue;
|
||||
if (errno != EIO) perror("Call to read() from child fd failed");
|
||||
return false;
|
||||
}
|
||||
@@ -981,7 +996,8 @@ io_loop(void *data) {
|
||||
// The I/O thread loop
|
||||
size_t i;
|
||||
int ret;
|
||||
bool has_more, data_received;
|
||||
bool has_more, data_received, has_pending_wakeups = false;
|
||||
double last_main_loop_wakeup_at = -1, now = -1;
|
||||
Screen *screen;
|
||||
ChildMonitor *self = (ChildMonitor*)data;
|
||||
set_thread_name("KittyChildMon");
|
||||
@@ -1000,7 +1016,14 @@ io_loop(void *data) {
|
||||
fds[EXTRA_FDS + i].events = (screen->read_buf_sz < READ_BUF_SZ ? POLLIN : 0) | (screen->write_buf_used ? POLLOUT : 0);
|
||||
screen_mutex(unlock, read); screen_mutex(unlock, write);
|
||||
}
|
||||
ret = poll(fds, self->count + EXTRA_FDS, -1);
|
||||
if (has_pending_wakeups) {
|
||||
now = monotonic();
|
||||
double time_delta = OPT(input_delay) - (now - last_main_loop_wakeup_at);
|
||||
if (time_delta >= 0) ret = poll(fds, self->count + EXTRA_FDS, (int)ceil(1000 * time_delta));
|
||||
else ret = 0;
|
||||
} else {
|
||||
ret = poll(fds, self->count + EXTRA_FDS, -1);
|
||||
}
|
||||
if (ret > 0) {
|
||||
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
|
||||
if (fds[1].revents && POLLIN) {
|
||||
@@ -1044,8 +1067,17 @@ io_loop(void *data) {
|
||||
perror("Call to poll() failed");
|
||||
}
|
||||
}
|
||||
if (data_received) wakeup_main_loop();
|
||||
#define WAKEUP { wakeup_main_loop(); last_main_loop_wakeup_at = now; has_pending_wakeups = false; }
|
||||
// we only wakeup the main loop after input_delay as wakeup is an expensive operation
|
||||
// on some platforms, such as cocoa
|
||||
if (data_received) {
|
||||
if ((now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
|
||||
else has_pending_wakeups = true;
|
||||
} else {
|
||||
if (has_pending_wakeups && (now = monotonic()) - last_main_loop_wakeup_at > OPT(input_delay)) WAKEUP
|
||||
}
|
||||
}
|
||||
#undef WAKEUP
|
||||
children_mutex(lock);
|
||||
for (i = 0; i < self->count; i++) children[i].needs_removal = true;
|
||||
remove_children(self);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
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++) {
|
||||
@@ -28,6 +29,13 @@ serialize_string_tuple(PyObject *src) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline void
|
||||
free_string_tuple(char** data) {
|
||||
size_t i = 0;
|
||||
while(data[i]) free(data[i++]);
|
||||
free(data);
|
||||
}
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static inline void
|
||||
@@ -107,6 +115,9 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
for (int c = 3; c < 201; c++) close(c);
|
||||
|
||||
environ = env;
|
||||
// for some reason SIGPIPE is set to SIG_IGN, so reset it, needed by bash,
|
||||
// which does not reset signal handlers on its own
|
||||
signal(SIGPIPE, SIG_DFL);
|
||||
execvp(exe, argv);
|
||||
// Report the failure and exec a shell instead, so that we are not left
|
||||
// with a forked but not exec'ed process
|
||||
@@ -125,8 +136,8 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
break;
|
||||
}
|
||||
#undef exit_on_err
|
||||
free(argv);
|
||||
free(env);
|
||||
free_string_tuple(argv);
|
||||
free_string_tuple(env);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
return PyLong_FromLong(pid);
|
||||
}
|
||||
|
||||
@@ -4,17 +4,28 @@
|
||||
|
||||
import fcntl
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
|
||||
import kitty.fast_data_types as fast_data_types
|
||||
|
||||
from .constants import is_macos, shell_path, terminfo_dir
|
||||
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process
|
||||
from kitty.fast_data_types import (
|
||||
cmdline_of_process, cwd_of_process as _cwd, environ_of_process as _environ_of_process,
|
||||
process_group_map as _process_group_map
|
||||
)
|
||||
|
||||
def cwd_of_process(pid):
|
||||
return os.path.realpath(_cwd(pid))
|
||||
|
||||
def process_group_map():
|
||||
ans = defaultdict(list)
|
||||
for pid, pgid in _process_group_map():
|
||||
ans[pgid].append(pid)
|
||||
return ans
|
||||
|
||||
else:
|
||||
|
||||
def cmdline_of_process(pid):
|
||||
@@ -27,6 +38,46 @@ else:
|
||||
def _environ_of_process(pid):
|
||||
return open('/proc/{}/environ'.format(pid), 'rb').read().decode('utf-8')
|
||||
|
||||
def process_group_map():
|
||||
ans = defaultdict(list)
|
||||
for x in os.listdir('/proc'):
|
||||
try:
|
||||
pid = int(x)
|
||||
except Exception:
|
||||
continue
|
||||
try:
|
||||
raw = open('/proc/' + x + '/stat', 'rb').read().decode('utf-8')
|
||||
except EnvironmentError:
|
||||
continue
|
||||
try:
|
||||
q = int(raw.split(' ', 5)[4])
|
||||
except Exception:
|
||||
continue
|
||||
ans[q].append(pid)
|
||||
return ans
|
||||
|
||||
|
||||
def processes_in_group(grp):
|
||||
gmap = getattr(process_group_map, 'cached_map', None)
|
||||
if gmap is None:
|
||||
try:
|
||||
gmap = process_group_map()
|
||||
except Exception:
|
||||
gmap = {}
|
||||
return gmap.get(grp, [])
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cached_process_data():
|
||||
try:
|
||||
process_group_map.cached_map = process_group_map()
|
||||
except Exception:
|
||||
process_group_map.cached_map = {}
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
process_group_map.cached_map = None
|
||||
|
||||
|
||||
def parse_environ_block(data):
|
||||
"""Parse a C environ block of environment variables into a dictionary."""
|
||||
@@ -142,6 +193,28 @@ class Child:
|
||||
os.close(self.terminal_ready_fd)
|
||||
self.terminal_ready_fd = -1
|
||||
|
||||
@property
|
||||
def foreground_processes(self):
|
||||
try:
|
||||
pgrp = os.tcgetpgrp(self.child_fd)
|
||||
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
||||
|
||||
def process_desc(pid):
|
||||
ans = {'pid': pid}
|
||||
try:
|
||||
ans['cmdline'] = cmdline_of_process(pid)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
ans['cwd'] = cwd_of_process(pid) or None
|
||||
except Exception:
|
||||
pass
|
||||
return ans
|
||||
|
||||
return list(map(process_desc, foreground_processes))
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
@property
|
||||
def cmdline(self):
|
||||
try:
|
||||
@@ -162,3 +235,21 @@ class Child:
|
||||
return cwd_of_process(self.pid)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@property
|
||||
def pid_for_cwd(self):
|
||||
try:
|
||||
pgrp = os.tcgetpgrp(self.child_fd)
|
||||
foreground_processes = processes_in_group(pgrp) if pgrp >= 0 else []
|
||||
if len(foreground_processes) == 1:
|
||||
return foreground_processes[0]
|
||||
except Exception:
|
||||
pass
|
||||
return self.pid
|
||||
|
||||
@property
|
||||
def foreground_cwd(self):
|
||||
try:
|
||||
return cwd_of_process(self.pid_for_cwd) or None
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
18
kitty/cli.py
18
kitty/cli.py
@@ -275,7 +275,10 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
if print_help_for_seq.allow_pager and sys.stdout.isatty():
|
||||
import subprocess
|
||||
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
|
||||
p.communicate(text.encode('utf-8'))
|
||||
try:
|
||||
p.communicate(text.encode('utf-8'))
|
||||
except KeyboardInterrupt:
|
||||
raise SystemExit(1)
|
||||
raise SystemExit(p.wait())
|
||||
else:
|
||||
print(text)
|
||||
@@ -464,7 +467,7 @@ def options_spec():
|
||||
dest=cls
|
||||
default={appname}
|
||||
condition=not is_macos
|
||||
Set the class part of the :italic:`WM_CLASS` window property
|
||||
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
|
||||
|
||||
|
||||
--name
|
||||
@@ -474,7 +477,9 @@ Set the name part of the :italic:`WM_CLASS` property (defaults to using the valu
|
||||
|
||||
--title -T
|
||||
Set the window title. This will override any title set by the program running inside kitty. So
|
||||
only use this if you are running a program that does not set titles.
|
||||
only use this if you are running a program that does not set titles. If combined
|
||||
with :option:`{appname} --session` the title will be used for all windows created by the
|
||||
session, that do not set their own titles.
|
||||
|
||||
|
||||
--config -c
|
||||
@@ -546,6 +551,13 @@ specify this address. This option will be ignored, unless you set
|
||||
option as it is read automatically from the environment.
|
||||
|
||||
|
||||
--start-as
|
||||
type=choices
|
||||
default=normal
|
||||
choices=normal,fullscreen,maximized,minimized
|
||||
Control how the initial kitty window is created.
|
||||
|
||||
|
||||
# Debugging options
|
||||
|
||||
--version -v
|
||||
|
||||
@@ -32,6 +32,9 @@ class UnknownLayout(ValueError):
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
cmap = {}
|
||||
|
||||
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...', string_return_is_error=False, args_count=None):
|
||||
|
||||
def w(func):
|
||||
@@ -45,6 +48,7 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
||||
func.no_response = no_response
|
||||
func.string_return_is_error = string_return_is_error
|
||||
func.args_count = 0 if not argspec else args_count
|
||||
cmap[func.name] = func
|
||||
return func
|
||||
return w
|
||||
|
||||
@@ -122,7 +126,7 @@ def set_font_size(boss, window, payload):
|
||||
' and :italic:`\\u21fa` to send unicode characters. If you use the :option:`kitty @ send-text --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
|
||||
options_spec=MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t') + '''\n
|
||||
--stdin
|
||||
type=bool-set
|
||||
Read the text to be sent from :italic:`stdin`. Note that in this case the text is sent as is,
|
||||
@@ -138,7 +142,7 @@ are sent as is, not interpreted for escapes.
|
||||
)
|
||||
def cmd_send_text(global_opts, opts, args):
|
||||
limit = 1024
|
||||
ret = {'match': opts.match, 'is_binary': False}
|
||||
ret = {'match': opts.match, 'is_binary': False, 'match_tab': opts.match_tab}
|
||||
|
||||
def pipe():
|
||||
ret['is_binary'] = True
|
||||
@@ -205,6 +209,13 @@ def send_text(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if payload['match_tab']:
|
||||
windows = []
|
||||
tabs = tuple(boss.match_tabs(payload['match_tab']))
|
||||
if not tabs:
|
||||
raise MatchError(payload['match_tab'], 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
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:
|
||||
@@ -278,7 +289,7 @@ def set_tab_title(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
# set_layout {{{
|
||||
# goto_layout {{{
|
||||
@cmd(
|
||||
'Set the window layout',
|
||||
'Set the window layout in the specified tab (or the active tab if not specified).'
|
||||
@@ -313,6 +324,34 @@ def goto_layout(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
# last_used_layout {{{
|
||||
@cmd(
|
||||
'Switch to the last used layout',
|
||||
'Switch to the last used window layout in the specified tab (or the active tab if not specified).'
|
||||
' You can use special match value :italic:`all` to set the layout in all tabs.',
|
||||
options_spec=MATCH_TAB_OPTION,
|
||||
)
|
||||
def cmd_last_used_layout(global_opts, opts, args):
|
||||
return {'match': opts.match}
|
||||
|
||||
|
||||
def last_used_layout(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
if match == 'all':
|
||||
tabs = tuple(boss.all_tabs)
|
||||
else:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
tab.last_used_layout()
|
||||
# }}}
|
||||
|
||||
|
||||
# close_window {{{
|
||||
@cmd(
|
||||
'Close the specified window(s)',
|
||||
@@ -363,7 +402,7 @@ reset the layout to its default configuration.
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the window this command is run in, rather than the active window.
|
||||
If specified resize the window this command is run in, rather than the active window.
|
||||
''',
|
||||
argspec='',
|
||||
string_return_is_error=True
|
||||
@@ -541,7 +580,9 @@ def focus_window(boss, window, payload):
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
boss.set_active_window(window)
|
||||
os_window_id = boss.set_active_window(window)
|
||||
if os_window_id:
|
||||
focus_os_window(os_window_id, True)
|
||||
break
|
||||
# }}}
|
||||
|
||||
@@ -550,10 +591,20 @@ def focus_window(boss, window, payload):
|
||||
@cmd(
|
||||
'Focus the specified tab',
|
||||
'The active window in the specified tab will be focused.',
|
||||
options_spec=MATCH_TAB_OPTION,
|
||||
options_spec=MATCH_TAB_OPTION + '''
|
||||
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response indicating the success of the action. Note that
|
||||
using this option means that you will not be notified of failures.
|
||||
''',
|
||||
argspec='',
|
||||
no_response=True,
|
||||
)
|
||||
def cmd_focus_tab(global_opts, opts, args):
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match}
|
||||
|
||||
|
||||
@@ -640,7 +691,7 @@ this option, any color arguments are ignored and --configured and --all are impl
|
||||
)
|
||||
def cmd_set_colors(global_opts, opts, args):
|
||||
from .rgb import color_as_int, Color
|
||||
colors = {}
|
||||
colors, cursor_text_color = {}, False
|
||||
if not opts.reset:
|
||||
for spec in args:
|
||||
if '=' in spec:
|
||||
@@ -648,15 +699,17 @@ def cmd_set_colors(global_opts, opts, args):
|
||||
else:
|
||||
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
|
||||
colors.update(parse_config(f))
|
||||
cursor_text_color = colors.pop('cursor_text_color', False)
|
||||
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
|
||||
return {
|
||||
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, 'colors': colors, 'reset': opts.reset
|
||||
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset,
|
||||
'colors': colors, 'reset': opts.reset, 'cursor_text_color': cursor_text_color
|
||||
}
|
||||
|
||||
|
||||
def set_colors(boss, window, payload):
|
||||
from .rgb import color_as_int
|
||||
from .rgb import color_as_int, Color
|
||||
if payload['all']:
|
||||
windows = tuple(boss.all_windows)
|
||||
else:
|
||||
@@ -673,10 +726,14 @@ def set_colors(boss, window, payload):
|
||||
windows += tuple(tab)
|
||||
if payload['reset']:
|
||||
payload['colors'] = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
|
||||
payload['cursor_text_color'] = boss.startup_cursor_text_color
|
||||
profiles = tuple(w.screen.color_profile for w in windows)
|
||||
from .fast_data_types import patch_color_profiles
|
||||
patch_color_profiles(payload['colors'], profiles, payload['configured'])
|
||||
boss.patch_colors(payload['colors'], payload['configured'])
|
||||
cursor_text_color = payload.get('cursor_text_color', False)
|
||||
if isinstance(cursor_text_color, (tuple, list, Color)):
|
||||
cursor_text_color = color_as_int(Color(*cursor_text_color))
|
||||
patch_color_profiles(payload['colors'], cursor_text_color, profiles, payload['configured'])
|
||||
boss.patch_colors(payload['colors'], cursor_text_color, payload['configured'])
|
||||
default_bg_changed = 'background' in payload['colors']
|
||||
for w in windows:
|
||||
if default_bg_changed:
|
||||
@@ -787,9 +844,6 @@ def kitten(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
||||
|
||||
|
||||
def cli_params_for(func):
|
||||
return (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
// Needed for _NSGetProgname
|
||||
#include <crt_externs.h>
|
||||
#include <objc/runtime.h>
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||
@@ -99,6 +100,21 @@ cocoa_set_new_window_trigger(PyObject *self UNUSED, PyObject *args) {
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
// Implementation of applicationDockMenu: for the app delegate
|
||||
static NSMenu *dockMenu = nil;
|
||||
static NSMenu *
|
||||
get_dock_menu(id self UNUSED, SEL _cmd UNUSED, NSApplication *sender UNUSED) {
|
||||
if (!dockMenu) {
|
||||
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
|
||||
dockMenu = [[NSMenu alloc] init];
|
||||
NSMenuItem *newWindowItem = [dockMenu addItemWithTitle:@"New OS window"
|
||||
action:@selector(new_os_window:)
|
||||
keyEquivalent:@""];
|
||||
[newWindowItem setTarget:global_menu_target];
|
||||
}
|
||||
return dockMenu;
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_create_global_menu(void) {
|
||||
NSString* app_name = find_app_name();
|
||||
@@ -183,6 +199,12 @@ cocoa_create_global_menu(void) {
|
||||
}
|
||||
|
||||
[bar release];
|
||||
|
||||
class_addMethod(
|
||||
object_getClass([NSApp delegate]),
|
||||
@selector(applicationDockMenu:),
|
||||
(IMP)get_dock_menu,
|
||||
"@@:@");
|
||||
}
|
||||
|
||||
void
|
||||
@@ -224,6 +246,29 @@ cocoa_focus_window(void *w) {
|
||||
[window makeKeyWindow];
|
||||
}
|
||||
|
||||
bool
|
||||
cocoa_toggle_fullscreen(void *w, bool traditional) {
|
||||
NSWindow *window = (NSWindow*)w;
|
||||
bool made_fullscreen = true;
|
||||
NSWindowStyleMask sm = [window styleMask];
|
||||
bool in_fullscreen = sm & NSWindowStyleMaskFullScreen;
|
||||
if (traditional) {
|
||||
if (!(in_fullscreen)) {
|
||||
sm |= NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen;
|
||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock];
|
||||
} else {
|
||||
made_fullscreen = false;
|
||||
sm &= ~(NSWindowStyleMaskBorderless | NSWindowStyleMaskFullScreen);
|
||||
[[NSApplication sharedApplication] setPresentationOptions: NSApplicationPresentationDefault];
|
||||
}
|
||||
[window setStyleMask: sm];
|
||||
} else {
|
||||
if (in_fullscreen) made_fullscreen = false;
|
||||
[window toggleFullScreen: nil];
|
||||
}
|
||||
return made_fullscreen;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cocoa_get_lang(PyObject UNUSED *self) {
|
||||
NSString* locale = nil;
|
||||
@@ -269,6 +314,12 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup() {
|
||||
if (dockMenu) [dockMenu release];
|
||||
dockMenu = nil;
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
||||
@@ -278,5 +329,9 @@ static PyMethodDef module_methods[] = {
|
||||
bool
|
||||
init_cocoa(PyObject *module) {
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
if (Py_AtExit(cleanup) != 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to register the cocoa_window at exit handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -100,8 +100,8 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
||||
|
||||
static PyObject*
|
||||
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
|
||||
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
||||
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured; PyObject *cursor_text_color;
|
||||
if (!PyArg_ParseTuple(args, "O!OO!p", &PyDict_Type, &spec, &cursor_text_color, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
||||
char key[32] = {0};
|
||||
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
||||
snprintf(key, sizeof(key) - 1, "color%zu", i);
|
||||
@@ -131,6 +131,22 @@ patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
|
||||
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
|
||||
#undef S
|
||||
if (cursor_text_color != Py_False) {
|
||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
|
||||
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
|
||||
self->overridden.cursor_text_color = 0x111111;
|
||||
self->overridden.cursor_text_uses_bg = 3;
|
||||
if (cursor_text_color != Py_None) {
|
||||
self->overridden.cursor_text_color = (PyLong_AsUnsignedLong(cursor_text_color) << 8) | 2;
|
||||
self->overridden.cursor_text_uses_bg = 1;
|
||||
}
|
||||
if (change_configured) {
|
||||
self->configured.cursor_text_color = self->overridden.cursor_text_color;
|
||||
self->configured.cursor_text_uses_bg = self->overridden.cursor_text_uses_bg;
|
||||
}
|
||||
self->dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -149,6 +165,14 @@ colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
||||
}
|
||||
}
|
||||
|
||||
float
|
||||
cursor_text_as_bg(ColorProfile *self) {
|
||||
if (self->overridden.cursor_text_uses_bg & 1) {
|
||||
return self->overridden.cursor_text_uses_bg & 2 ? 1.f : 0.f;
|
||||
}
|
||||
return self->configured.cursor_text_uses_bg & 2 ? 1.f : 0.f;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
as_dict(ColorProfile *self, PyObject *args UNUSED) {
|
||||
@@ -174,7 +198,7 @@ as_dict(ColorProfile *self, PyObject *args UNUSED) {
|
||||
if (ret != 0) { Py_CLEAR(ans); return NULL; } \
|
||||
}}
|
||||
D(default_fg, foreground); D(default_bg, background);
|
||||
D(cursor_color, cursor); D(highlight_fg, selection_foreground);
|
||||
D(cursor_color, cursor); D(cursor_text_color, cursor_text); D(highlight_fg, selection_foreground);
|
||||
D(highlight_bg, selection_background);
|
||||
|
||||
#undef D
|
||||
@@ -236,7 +260,11 @@ set_color(ColorProfile *self, PyObject *args) {
|
||||
static PyObject*
|
||||
set_configured_colors(ColorProfile *self, PyObject *args) {
|
||||
#define set_configured_colors_doc "Set the configured colors"
|
||||
if (!PyArg_ParseTuple(args, "II|III", &(self->configured.default_fg), &(self->configured.default_bg), &(self->configured.cursor_color), &(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
|
||||
if (!PyArg_ParseTuple(
|
||||
args, "II|IIIII",
|
||||
&(self->configured.default_fg), &(self->configured.default_bg),
|
||||
&(self->configured.cursor_color), &(self->configured.cursor_text_color), &(self->configured.cursor_text_uses_bg),
|
||||
&(self->configured.highlight_fg), &(self->configured.highlight_bg))) return NULL;
|
||||
self->dirty = true;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -286,6 +314,7 @@ default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
CGETSET(default_fg)
|
||||
CGETSET(default_bg)
|
||||
CGETSET(cursor_color)
|
||||
CGETSET(cursor_text_color)
|
||||
CGETSET(highlight_fg)
|
||||
CGETSET(highlight_bg)
|
||||
|
||||
@@ -293,6 +322,7 @@ static PyGetSetDef getsetters[] = {
|
||||
GETSET(default_fg)
|
||||
GETSET(default_bg)
|
||||
GETSET(cursor_color)
|
||||
GETSET(cursor_text_color)
|
||||
GETSET(highlight_fg)
|
||||
GETSET(highlight_bg)
|
||||
{NULL} /* Sentinel */
|
||||
|
||||
@@ -35,7 +35,7 @@ completion_scripts = {
|
||||
'zsh': '''
|
||||
_kitty() {
|
||||
local src
|
||||
# Send all words upto the word the cursor is currently on
|
||||
# Send all words up to the word the cursor is currently on
|
||||
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
|
||||
if [[ $? == 0 ]]; then
|
||||
eval ${src}
|
||||
@@ -44,7 +44,7 @@ _kitty() {
|
||||
compdef _kitty kitty
|
||||
''',
|
||||
'bash': '''
|
||||
kitty_completions() {
|
||||
_kitty_completions() {
|
||||
local src
|
||||
local limit
|
||||
# Send all words up to the word the cursor is currently on
|
||||
@@ -55,7 +55,15 @@ kitty_completions() {
|
||||
fi
|
||||
}
|
||||
|
||||
complete -o nospace -F kitty_completions kitty
|
||||
complete -o nospace -F _kitty_completions kitty
|
||||
''',
|
||||
'fish': '''
|
||||
function __kitty_completions
|
||||
# Send all words up to the one before the cursor
|
||||
commandline -cop | kitty +complete fish
|
||||
end
|
||||
|
||||
complete -f -c kitty -a "(__kitty_completions)"
|
||||
''',
|
||||
}
|
||||
|
||||
@@ -86,6 +94,11 @@ def bash_input_parser(data):
|
||||
return words, new_word
|
||||
|
||||
|
||||
@input_parser
|
||||
def fish_input_parser(data):
|
||||
return data.rstrip().splitlines(), True
|
||||
|
||||
|
||||
@output_serializer
|
||||
def zsh_output_serializer(ans):
|
||||
lines = []
|
||||
@@ -95,6 +108,10 @@ def zsh_output_serializer(ans):
|
||||
cmd += ['-S', '""']
|
||||
if description in ans.files_groups:
|
||||
cmd.append('-f')
|
||||
common_prefix = os.path.commonprefix(tuple(matches))
|
||||
if common_prefix:
|
||||
cmd.extend(('-p', shlex.quote(common_prefix)))
|
||||
matches = {k[len(common_prefix):]: v for k, v in matches.items()}
|
||||
cmd.append('--')
|
||||
for word, description in matches.items():
|
||||
cmd.append(shlex.quote(word))
|
||||
@@ -117,6 +134,16 @@ def bash_output_serializer(ans):
|
||||
# }}}
|
||||
|
||||
|
||||
@output_serializer
|
||||
def fish_output_serializer(ans):
|
||||
lines = []
|
||||
for matches in ans.match_groups.values():
|
||||
for word in matches:
|
||||
lines.append(shlex.quote(word))
|
||||
# debug('\n'.join(lines))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points):
|
||||
cmds = ['@' + c for c in cmap]
|
||||
ans.match_groups['Entry points'] = {
|
||||
@@ -197,21 +224,40 @@ def complete_remote_command(ans, cmd_name, words, new_word):
|
||||
complete_alias_map(ans, words, new_word, alias_map)
|
||||
|
||||
|
||||
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=lambda filename: True):
|
||||
def path_completion(prefix=''):
|
||||
prefix = prefix.replace(r'\ ', ' ')
|
||||
dirs, files = [], []
|
||||
base = '.'
|
||||
if prefix.endswith('/'):
|
||||
base = prefix
|
||||
elif '/' in prefix:
|
||||
base = os.path.dirname(prefix)
|
||||
for x in os.scandir(base):
|
||||
q = os.path.relpath(x.path)
|
||||
if x.is_dir():
|
||||
if q.startswith(prefix):
|
||||
dirs.append(q.rstrip(os.sep) + os.sep)
|
||||
src = os.path.expandvars(os.path.expanduser(base))
|
||||
src_prefix = os.path.abspath(os.path.expandvars(os.path.expanduser(prefix))) if prefix else ''
|
||||
try:
|
||||
items = os.scandir(src)
|
||||
except FileNotFoundError:
|
||||
items = ()
|
||||
for x in items:
|
||||
abspath = os.path.abspath(x.path)
|
||||
if prefix and not abspath.startswith(src_prefix):
|
||||
continue
|
||||
if prefix:
|
||||
q = prefix + abspath[len(src_prefix):].lstrip(os.sep)
|
||||
q = os.path.expandvars(os.path.expanduser(q))
|
||||
else:
|
||||
if q.startswith(prefix) and predicate(q):
|
||||
files.append(q)
|
||||
q = os.path.relpath(abspath)
|
||||
if x.is_dir():
|
||||
dirs.append(q.rstrip(os.sep) + os.sep)
|
||||
else:
|
||||
files.append(q)
|
||||
return dirs, files
|
||||
|
||||
|
||||
def complete_files_and_dirs(ans, prefix, files_group_name='Files', predicate=None):
|
||||
dirs, files = path_completion(prefix or '')
|
||||
files = filter(predicate, files)
|
||||
|
||||
if dirs:
|
||||
ans.match_groups['Directories'] = dict.fromkeys(dirs)
|
||||
ans.files_groups.add('Directories'), ans.no_space_groups.add('Directories')
|
||||
@@ -232,6 +278,17 @@ def complete_icat_args(ans, opt, prefix):
|
||||
complete_files_and_dirs(ans, prefix, 'Images', icat_file_predicate)
|
||||
|
||||
|
||||
def config_file_predicate(filename):
|
||||
return filename.endswith('.conf')
|
||||
|
||||
|
||||
def complete_diff_args(ans, opt, prefix):
|
||||
if opt is None:
|
||||
complete_files_and_dirs(ans, prefix, 'Files')
|
||||
elif opt['dest'] == 'config':
|
||||
complete_files_and_dirs(ans, prefix, 'Config Files', config_file_predicate)
|
||||
|
||||
|
||||
def complete_kitten(ans, kitten, words, new_word):
|
||||
try:
|
||||
cd = get_kitten_cli_docs(kitten)
|
||||
@@ -247,7 +304,8 @@ def complete_kitten(ans, kitten, words, new_word):
|
||||
for alias in opt['aliases']:
|
||||
option_map[alias] = opt
|
||||
complete_alias_map(ans, words, new_word, option_map, {
|
||||
'icat': complete_icat_args
|
||||
'icat': complete_icat_args,
|
||||
'diff': complete_diff_args,
|
||||
}.get(kitten))
|
||||
|
||||
|
||||
|
||||
115
kitty/config.py
115
kitty/config.py
@@ -16,7 +16,7 @@ from .conf.utils import (
|
||||
parse_config_base, python_string, to_bool, to_cmdline
|
||||
)
|
||||
from .config_data import all_options, parse_mods, type_map
|
||||
from .constants import cache_dir, defconf
|
||||
from .constants import cache_dir, defconf, is_macos
|
||||
from .utils import log_error
|
||||
|
||||
named_keys = {
|
||||
@@ -35,14 +35,22 @@ named_keys = {
|
||||
|
||||
def parse_shortcut(sc):
|
||||
parts = sc.split('+')
|
||||
mods = parse_mods(parts[:-1], sc)
|
||||
if mods is None:
|
||||
return None, None, None
|
||||
mods = 0
|
||||
if len(parts) > 1:
|
||||
mods = parse_mods(parts[:-1], sc)
|
||||
if mods is None:
|
||||
return None, None, None
|
||||
key = parts[-1].upper()
|
||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||
is_native = False
|
||||
if key is None:
|
||||
key = defines.key_for_native_key_name(parts[-1])
|
||||
if parts[-1].startswith('0x'):
|
||||
try:
|
||||
key = int(parts[-1], 16)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
key = defines.key_for_native_key_name(parts[-1])
|
||||
is_native = key is not None
|
||||
return mods, is_native, key
|
||||
|
||||
@@ -107,7 +115,7 @@ def float_parse(func, rest):
|
||||
|
||||
@func_with_args('change_font_size')
|
||||
def parse_change_font_size(func, rest):
|
||||
vals = rest.split(' ', 1)
|
||||
vals = rest.strip().split(' ', 1)
|
||||
if len(vals) != 2:
|
||||
log_error('Invalid change_font_size specification: {}, treating it as default'.format(rest))
|
||||
args = [True, None, 0]
|
||||
@@ -117,13 +125,13 @@ def parse_change_font_size(func, rest):
|
||||
if amt[0] in '+-':
|
||||
args[1] = amt[0]
|
||||
amt = amt[1:]
|
||||
args[2] = float(amt)
|
||||
args[2] = float(amt.strip())
|
||||
return func, args
|
||||
|
||||
|
||||
@func_with_args('clear_terminal')
|
||||
def clear_terminal(func, rest):
|
||||
vals = rest.split(' ', 1)
|
||||
vals = rest.strip().split(' ', 1)
|
||||
if len(vals) != 2:
|
||||
log_error('clear_terminal needs two arguments, using defaults')
|
||||
args = ['reset', 'active']
|
||||
@@ -132,8 +140,72 @@ def clear_terminal(func, rest):
|
||||
return func, args
|
||||
|
||||
|
||||
@func_with_args('neighboring_window')
|
||||
def neighboring_window(func, rest):
|
||||
rest = rest.lower()
|
||||
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
|
||||
if rest not in ('left', 'right', 'top', 'bottom'):
|
||||
log_error('Invalid neighbor specification: {}'.format(rest))
|
||||
rest = 'right'
|
||||
return func, [rest]
|
||||
|
||||
|
||||
@func_with_args('resize_window')
|
||||
def resize_window(func, rest):
|
||||
vals = rest.strip().split(' ', 1)
|
||||
if len(vals) > 2:
|
||||
log_error('resize_window needs one or two arguments, using defaults')
|
||||
args = ['wider', 1]
|
||||
else:
|
||||
quality = vals[0].lower()
|
||||
if quality not in ('taller', 'shorter', 'wider', 'narrower'):
|
||||
log_error('Invalid quality specification: {}'.format(quality))
|
||||
quality = 'wider'
|
||||
increment = 1
|
||||
if len(vals) == 2:
|
||||
try:
|
||||
increment = int(vals[1])
|
||||
except Exception:
|
||||
log_error('Invalid increment specification: {}'.format(vals[1]))
|
||||
args = [quality, increment]
|
||||
return func, args
|
||||
|
||||
|
||||
@func_with_args('move_window')
|
||||
def move_window(func, rest):
|
||||
rest = rest.lower()
|
||||
rest = {'up': 'top', 'down': 'bottom'}.get(rest, rest)
|
||||
try:
|
||||
rest = int(rest)
|
||||
except Exception:
|
||||
if rest not in ('left', 'right', 'top', 'bottom'):
|
||||
log_error('Invalid move_window specification: {}'.format(rest))
|
||||
rest = 0
|
||||
return func, [rest]
|
||||
|
||||
|
||||
@func_with_args('pipe')
|
||||
def pipe(func, rest):
|
||||
import shlex
|
||||
rest = shlex.split(rest)
|
||||
if len(rest) < 3:
|
||||
log_error('Too few arguments to pipe function')
|
||||
rest = ['none', 'none', 'true']
|
||||
return func, rest
|
||||
|
||||
|
||||
@func_with_args('nth_window')
|
||||
def nth_window(func, rest):
|
||||
try:
|
||||
num = int(rest)
|
||||
except Exception:
|
||||
log_error('Invalid nth_window number: {}'.format(rest))
|
||||
num = 1
|
||||
return func, [num]
|
||||
|
||||
|
||||
def parse_key_action(action):
|
||||
parts = action.split(' ', 1)
|
||||
parts = action.strip().split(' ', 1)
|
||||
func = parts[0]
|
||||
if len(parts) == 1:
|
||||
return KeyAction(func, ())
|
||||
@@ -144,7 +216,8 @@ def parse_key_action(action):
|
||||
func, args = parser(func, rest)
|
||||
except Exception:
|
||||
log_error('Ignoring invalid key action: {}'.format(action))
|
||||
return KeyAction(func, args)
|
||||
else:
|
||||
return KeyAction(func, args)
|
||||
|
||||
|
||||
all_key_actions = set()
|
||||
@@ -261,6 +334,15 @@ special_handlers = {}
|
||||
|
||||
def special_handler(func):
|
||||
special_handlers[func.__name__.partition('_')[2]] = func
|
||||
return func
|
||||
|
||||
|
||||
def deprecated_handler(*names):
|
||||
def special_handler(func):
|
||||
for name in names:
|
||||
special_handlers[name] = func
|
||||
return func
|
||||
return special_handler
|
||||
|
||||
|
||||
@special_handler
|
||||
@@ -285,6 +367,15 @@ def handle_clear_all_shortcuts(key, val, ans):
|
||||
ans['key_definitions'] = [None]
|
||||
|
||||
|
||||
@deprecated_handler('x11_hide_window_decorations', 'macos_hide_titlebar')
|
||||
def handle_deprecated_hide_window_decorations_aliases(key, val, ans):
|
||||
if not hasattr(handle_deprecated_hide_window_decorations_aliases, key):
|
||||
handle_deprecated_hide_window_decorations_aliases.key = True
|
||||
log_error('The option {} is deprecated. Use hide_window_decorations instead.'.format(key))
|
||||
if to_bool(val):
|
||||
ans['hide_window_decorations'] = True
|
||||
|
||||
|
||||
def expandvars(val, env):
|
||||
|
||||
def sub(m):
|
||||
@@ -296,7 +387,7 @@ def expandvars(val, env):
|
||||
result = m.group()
|
||||
return result
|
||||
|
||||
return re.sub(r'$\{(\S+)\}', sub, val)
|
||||
return re.sub(r'\$\{(\S+?)\}', sub, val)
|
||||
|
||||
|
||||
@special_handler
|
||||
@@ -501,4 +592,6 @@ def load_config(*paths, overrides=None):
|
||||
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
|
||||
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
||||
opts.macos_titlebar_color = 0
|
||||
if (is_macos and getattr(opts, 'macos_hide_titlebar', False)) or (not is_macos and getattr(opts, 'x11_hide_window_decorations', False)):
|
||||
opts.hide_window_decorations = True
|
||||
return opts
|
||||
|
||||
@@ -95,6 +95,21 @@ for a list of key names. The name to use is the part after the :code:`XKB_KEY_`
|
||||
prefix. Note that you should only use an XKB key name for keys that are not present
|
||||
in the list of GLFW keys.
|
||||
|
||||
Finally, you can use raw system key codes to map keys. To see the system key code
|
||||
for a key, start kitty with the :option:`kitty --debug-keyboard` option. Then kitty will
|
||||
output some debug text for every key event. In that text look for ``native_code``
|
||||
the value of that becomes the key name in the shortcut. For example:
|
||||
|
||||
.. code-block:: none
|
||||
|
||||
on_key_input: glfw key: 65 native_code: 0x61 action: PRESS mods: 0x0 text: 'a'
|
||||
|
||||
Here, the key name for the :kbd:`A` key is :kbd:`0x61` and you can use it with::
|
||||
|
||||
map ctrl+0x61 something
|
||||
|
||||
to map :kbd:`ctrl+a` to something.
|
||||
|
||||
You can use the special action :code:`no_op` to unmap a keyboard shortcut that is
|
||||
assigned in the default configuration.
|
||||
|
||||
@@ -125,13 +140,19 @@ For example::
|
||||
'shortcuts.tab': [
|
||||
_('Tab management'), '',
|
||||
_('''\
|
||||
You can also create shortcuts to go to specific tabs, with 1 being the first tab::
|
||||
You can also create shortcuts to go to specific tabs, with 1 being the first
|
||||
tab, 2 the second tab and -1 being the previously active tab::
|
||||
|
||||
map ctrl+alt+1 goto_tab 1
|
||||
map ctrl+alt+2 goto_tab 2
|
||||
|
||||
Just as with :code:`new_window` above, you can also pass the name of arbitrary
|
||||
commands to run when using new_tab and use :code:`new_tab_with_cwd`.
|
||||
commands to run when using new_tab and use :code:`new_tab_with_cwd`. Finally,
|
||||
if you want the new tab to open next to the current tab rather than at the
|
||||
end of the tabs list, use::
|
||||
|
||||
map ctrl+t new_tab !neighbor [optional cmd to run]
|
||||
|
||||
''')],
|
||||
'shortcuts.layout': [
|
||||
_('Layout management'), '',
|
||||
@@ -140,17 +161,22 @@ You can also create shortcuts to switch to specific layouts::
|
||||
|
||||
map ctrl+alt+t goto_layout tall
|
||||
map ctrl+alt+s goto_layout stack
|
||||
|
||||
Similarly, to switch back to the previous layout::
|
||||
|
||||
map ctrl+alt+p last_used_layout
|
||||
|
||||
''')],
|
||||
'shortcuts.fonts': [
|
||||
_('Font sizes'), _('''\
|
||||
You can change the font size for all top-level kitty windows at a time
|
||||
You can change the font size for all top-level kitty OS windows at a time
|
||||
or only the current one.
|
||||
'''), _('''\
|
||||
To setup shortcuts for specific font sizes::
|
||||
|
||||
map kitty_mod+f6 change_font_size all 10.0
|
||||
|
||||
To setup shortcuts to change only the current window's font size::
|
||||
To setup shortcuts to change only the current OS window's font size::
|
||||
|
||||
map kitty_mod+f6 change_font_size current 10.0
|
||||
''')],
|
||||
@@ -290,9 +316,24 @@ o('cursor_stop_blinking_after', 15.0, option_type=positive_float)
|
||||
|
||||
g('scrollback') # {{{
|
||||
|
||||
o('scrollback_lines', 2000, option_type=positive_int, long_text=_('''
|
||||
|
||||
def scrollback_lines(x):
|
||||
x = int(x)
|
||||
if x < 0:
|
||||
x = 2 ** 32 - 1
|
||||
return x
|
||||
|
||||
|
||||
def scrollback_pager_history_size(x):
|
||||
ans = int(max(0, float(x)) * 1024 * 1024)
|
||||
return min(ans, 4096 * 1024 * 1024 - 1)
|
||||
|
||||
|
||||
o('scrollback_lines', 2000, option_type=scrollback_lines, long_text=_('''
|
||||
Number of lines of history to keep in memory for scrolling back. Memory is allocated
|
||||
on demand.'''))
|
||||
on demand. Negative numbers are (effectively) infinite scrollback. Note that using
|
||||
very large scrollback is not recommended as it can slow down resizing of the terminal
|
||||
and also use large amounts of RAM.'''))
|
||||
|
||||
o('scrollback_pager', 'less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER', option_type=to_cmdline, long_text=_('''
|
||||
Program with which to view scrollback in a new window. The scrollback buffer is
|
||||
@@ -301,10 +342,26 @@ use can handle ANSI escape sequences for colors and text formatting.
|
||||
INPUT_LINE_NUMBER in the command line above will be replaced by an integer
|
||||
representing which line should be at the top of the screen.'''))
|
||||
|
||||
o('scrollback_pager_history_size', 0, option_type=scrollback_pager_history_size, long_text=_('''
|
||||
Separate scrollback history size, used only for browsing the scrollback buffer (in MB).
|
||||
This separate buffer is not available for interactive scrolling but will be
|
||||
piped to the pager program when viewing scrollback buffer in a separate window.
|
||||
The current implementation stores one character in 4 bytes, so approximatively
|
||||
2500 lines per megabyte at 100 chars per line. A value of zero or less disables
|
||||
this feature. The maximum allowed size is 4GB.'''))
|
||||
|
||||
o('wheel_scroll_multiplier', 5.0, long_text=_('''
|
||||
Modify the amount scrolled by the mouse wheel. Note this is only used for low
|
||||
precision scrolling devices, not for high precision scrolling on platforms such
|
||||
as macOS and Wayland. Use negative numbers to change scroll direction.'''))
|
||||
|
||||
o('touch_scroll_multiplier', 1.0, long_text=_('''
|
||||
Modify the amount scrolled by a touchpad. Note this is only used for high
|
||||
precision scrolling devices on platforms such as macOS and Wayland.
|
||||
Use negative numbers to change scroll direction.'''))
|
||||
|
||||
# }}}
|
||||
|
||||
# }}}
|
||||
|
||||
g('mouse') # {{{
|
||||
@@ -337,7 +394,7 @@ operating system's default URL handler.'''))
|
||||
o('copy_on_select', False, long_text=_('''
|
||||
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
|
||||
such as macOS 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.'''))
|
||||
|
||||
@@ -350,9 +407,9 @@ Characters considered part of a word when double clicking. In addition to these
|
||||
any character that is marked as an alpha-numeric character in the unicode
|
||||
database will be matched.'''))
|
||||
|
||||
o('click_interval', 0.5, option_type=positive_float, long_text=_('''
|
||||
The interval between successive clicks to detect
|
||||
double/triple clicks (in seconds)'''))
|
||||
o('click_interval', -1.0, option_type=float, long_text=_('''
|
||||
The interval between successive clicks to detect double/triple clicks (in seconds).
|
||||
Negative numbers will use the system default instead, if available, or fallback to 0.5.'''))
|
||||
|
||||
o('mouse_hide_wait', 3.0, option_type=positive_float, long_text=_('''
|
||||
Hide mouse cursor after the specified number of seconds
|
||||
@@ -432,7 +489,7 @@ def to_layout_names(raw):
|
||||
parts = [x.strip().lower() for x in raw.split(',')]
|
||||
ans = []
|
||||
for p in parts:
|
||||
if p == '*':
|
||||
if p in ('*', 'all'):
|
||||
ans.extend(sorted(all_layouts))
|
||||
continue
|
||||
name = p.partition(':')[0]
|
||||
@@ -444,7 +501,7 @@ def to_layout_names(raw):
|
||||
|
||||
o('enabled_layouts', '*', option_type=to_layout_names, long_text=_('''
|
||||
The enabled window layouts. A comma separated list of layout names. The special
|
||||
value :code:`*` means all layouts. The first listed layout will be used as the
|
||||
value :code:`all` means all layouts. The first listed layout will be used as the
|
||||
startup layout. For a list of available layouts, see the :ref:`layouts`.
|
||||
'''))
|
||||
|
||||
@@ -489,6 +546,12 @@ o('inactive_text_alpha', 1.0, option_type=unit_float, long_text=_('''
|
||||
Fade the text in inactive windows by the specified amount (a number between
|
||||
zero and one, with zero being fully faded).
|
||||
'''))
|
||||
|
||||
o('hide_window_decorations', False, long_text=_('''
|
||||
Hide the window decorations (title-bar and window borders).
|
||||
Whether this works and exactly what effect it has depends on the
|
||||
window manager/operating system.
|
||||
'''))
|
||||
# }}}
|
||||
|
||||
g('tabbar') # {{{
|
||||
@@ -523,8 +586,8 @@ Which edge to show the tab bar on, top or bottom'''))
|
||||
o('tab_bar_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
||||
The margin to the left and right of the tab bar (in pts)'''))
|
||||
|
||||
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator'), long_text=_('''
|
||||
The tab bar style, can be one of: :code:`fade` or :code:`separator`. In the fade style,
|
||||
o('tab_bar_style', 'fade', option_type=choices('fade', 'separator', 'hidden'), long_text=_('''
|
||||
The tab bar style, can be one of: :code:`fade`, :code:`separator` or :code:`hidden`. In the fade style,
|
||||
each tab's edges fade into the background color, in the separator style, tabs are
|
||||
separated by a configurable separator.
|
||||
'''))
|
||||
@@ -545,6 +608,13 @@ entries to this list.
|
||||
o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separator, long_text=_('''
|
||||
The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.'''))
|
||||
|
||||
o('tab_title_template', '{title}', long_text=_('''
|
||||
A template to render the tab title. The default just renders
|
||||
the title. If you wish to include the tab-index as well,
|
||||
use something like: :code:`{index}: {title}`. Useful
|
||||
if you have shortcuts mapped for :code:`goto_tab N`.
|
||||
'''))
|
||||
|
||||
o('active_tab_foreground', '#000', option_type=to_color, long_text=_('''
|
||||
Tab bar colors and styles'''))
|
||||
o('active_tab_background', '#eee', option_type=to_color)
|
||||
@@ -655,7 +725,7 @@ Specify environment variables to set in all child processes. Note that
|
||||
environment variables are expanded recursively, so if you use::
|
||||
|
||||
env MYVAR1=a
|
||||
env MYVAR2=${MYVAR}/${HOME}/b
|
||||
env MYVAR2=${MYVAR1}/${HOME}/b
|
||||
|
||||
The value of MYVAR2 will be :code:`a/<path to home directory>/b`.
|
||||
'''))
|
||||
@@ -689,9 +759,13 @@ program, even one running on a remote server via SSH can read your clipboard.
|
||||
'''))
|
||||
|
||||
o('term', 'xterm-kitty', long_text=_('''
|
||||
The value of the TERM environment variable to set. Changing this can break
|
||||
many terminal programs, only change it if you know what you are doing, not
|
||||
because you read some advice on Stack Overflow to change it.
|
||||
The value of the TERM environment variable to set. Changing this can break many
|
||||
terminal programs, only change it if you know what you are doing, not because
|
||||
you read some advice on Stack Overflow to change it. The TERM variable if used
|
||||
by various programs to get information about the capabilities and behavior of
|
||||
the terminal. If you change it, depending on what programs you run, and how
|
||||
different the terminal you are changing it to is, various things from
|
||||
key-presses, to colors, to various advanced features may not work.
|
||||
'''))
|
||||
|
||||
# }}}
|
||||
@@ -716,18 +790,9 @@ an arbitrary color, such as :code:`#12af59` or :code:`red`. WARNING: This option
|
||||
using a hack, as there is no proper Cocoa API for it. It sets the background
|
||||
color of the entire window and makes the titlebar transparent. As such it is
|
||||
incompatible with :opt:`background_opacity`. If you want to use both, you are
|
||||
probably better off just hiding the titlebar with :opt:`macos_hide_titlebar`.
|
||||
probably better off just hiding the titlebar with :opt:`hide_window_decorations`.
|
||||
'''))
|
||||
|
||||
o('macos_hide_titlebar', False, long_text=_('''
|
||||
Hide the kitty window's title bar on macOS.'''))
|
||||
|
||||
o('x11_hide_window_decorations', False, long_text=_('''
|
||||
Hide the window decorations (title bar and window borders) on X11 and Wayland.
|
||||
Whether this works and exactly what effect it has depends on the window
|
||||
manager, as it is the job of the window manager/compositor to draw window
|
||||
decorations.'''))
|
||||
|
||||
o('macos_option_as_alt', True, long_text=_('''
|
||||
Use the option key as an alt key. With this set to no, kitty will use
|
||||
the macOS native :kbd:`Option+Key` = unicode character behavior. This will
|
||||
@@ -749,6 +814,23 @@ o('macos_window_resizable', True, long_text=_('''
|
||||
Disable this if you want kitty top-level (OS) windows to not be resizable
|
||||
on macOS.
|
||||
'''))
|
||||
|
||||
o('macos_thicken_font', 0, option_type=positive_float, long_text=_('''
|
||||
Draw an extra border around the font with the given width, to increase
|
||||
legibility at small font sizes. For example, a value of 0.75 will
|
||||
result in rendering that looks similar to sub-pixel antialiasing at
|
||||
common font sizes.
|
||||
'''))
|
||||
|
||||
o('macos_traditional_fullscreen', False, long_text=_('''
|
||||
Use the traditional full-screen transition, that is faster, but less pretty.
|
||||
'''))
|
||||
|
||||
# Disabled by default because of https://github.com/kovidgoyal/kitty/issues/794
|
||||
o('macos_custom_beam_cursor', False, long_text=_('''
|
||||
Enable/disable custom mouse cursor for macOS that is easier to see on both
|
||||
light and dark backgrounds. WARNING: this might make your mouse cursor
|
||||
invisible on dual GPU machines.'''))
|
||||
# }}}
|
||||
|
||||
g('shortcuts') # {{{
|
||||
@@ -766,7 +848,9 @@ g('shortcuts.clipboard') # {{{
|
||||
if is_macos:
|
||||
k('copy_to_clipboard', 'cmd+c', 'copy_to_clipboard', _('Copy to clipboard'), add_to_docs=False)
|
||||
k('paste_from_clipboard', 'cmd+v', 'paste_from_clipboard', _('Paste from clipboard'), add_to_docs=False)
|
||||
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'))
|
||||
k('copy_to_clipboard', 'kitty_mod+c', 'copy_to_clipboard', _('Copy to clipboard'), long_text=_('''
|
||||
There is also a :code:`copy_or_interrupt` action that can be optionally mapped to :kbd:`Ctrl+c`.
|
||||
It will copy only if there is a selection and send an interrupt otherwise.'''))
|
||||
k('paste_from_clipboard', 'kitty_mod+v', 'paste_from_clipboard', _('Paste from clipboard'))
|
||||
k('paste_from_selection', 'kitty_mod+s', 'paste_from_selection', _('Paste from selection'))
|
||||
k('paste_from_selection', 'shift+insert', 'paste_from_selection', _('Paste from selection'))
|
||||
@@ -795,12 +879,15 @@ k('scroll_page_down', 'kitty_mod+page_down', 'scroll_page_down', _('Scroll page
|
||||
k('scroll_home', 'kitty_mod+home', 'scroll_home', _('Scroll to top'))
|
||||
k('scroll_end', 'kitty_mod+end', 'scroll_end', _('Scroll to bottom'))
|
||||
k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_('''
|
||||
You can send the contents of the current screen + history buffer as stdin to an arbitrary program using
|
||||
the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes).
|
||||
For only the current screen, use @screen or @ansi_screen.
|
||||
For example, the following command opens the scrollback buffer in less in a new window::
|
||||
|
||||
map kitty_mod+y new_window @ansi less +G -R
|
||||
You can pipe the contents of the current screen + history buffer as
|
||||
:file:`STDIN` to an arbitrary program using the ``pipe`` function. For example,
|
||||
the following opens the scrollback buffer in less in an overlay window::
|
||||
|
||||
map f1 pipe @ansi overlay less +G -R
|
||||
|
||||
For more details on piping screen and buffer contents to external programs,
|
||||
see :doc:`pipe`.
|
||||
'''))
|
||||
|
||||
|
||||
@@ -850,6 +937,8 @@ g('shortcuts.tab') # {{{
|
||||
if is_macos:
|
||||
k('next_tab', 'ctrl+tab', 'next_tab', _('Next tab'))
|
||||
k('next_tab', 'kitty_mod+right', 'next_tab', _('Next tab'))
|
||||
if is_macos:
|
||||
k('previous_tab', 'ctrl+shift+tab', 'previous_tab', _('Previous tab'))
|
||||
k('previous_tab', 'kitty_mod+left', 'previous_tab', _('Previous tab'))
|
||||
k('new_tab', 'kitty_mod+t', 'new_tab', _('New tab'))
|
||||
k('close_tab', 'kitty_mod+q', 'close_tab', _('Close tab'))
|
||||
@@ -907,12 +996,21 @@ k('reset_terminal', 'kitty_mod+delete', 'clear_terminal reset active', _('Reset
|
||||
long_text=_('''
|
||||
You can create shortcuts to clear/reset the terminal. For example::
|
||||
|
||||
# Reset the terminal
|
||||
map kitty_mod+f9 clear_terminal reset active
|
||||
# Clear the terminal screen by erasing all contents
|
||||
map kitty_mod+f10 clear_terminal clear active
|
||||
# Clear the terminal scrollback by erasing it
|
||||
map kitty_mod+f11 clear_terminal scrollback active
|
||||
# Scroll the contents of the screen into the scrollback
|
||||
map kitty_mod+f12 clear_terminal scroll active
|
||||
|
||||
These will reset screen/clear screen/clear screen+scrollback respectively. If you want to
|
||||
operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
|
||||
If you want to operate on all windows instead of just the current one, use :italic:`all` instead of :italic`active`.
|
||||
|
||||
It is also possible to remap Ctrl+L to both scroll the current screen contents into the scrollback buffer
|
||||
and clear the screen, instead of just clearing the screen::
|
||||
|
||||
map ctrl+l combine : clear_terminal scroll active : send_text normal,application \x0c
|
||||
'''))
|
||||
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
|
||||
add_to_default=False, long_text=_('''
|
||||
|
||||
@@ -7,9 +7,8 @@ import pwd
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 12, 0)
|
||||
version = (0, 13, 2)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
@@ -61,6 +60,18 @@ def _get_config_dir():
|
||||
os.makedirs(ans, exist_ok=True)
|
||||
except FileExistsError:
|
||||
raise SystemExit('A file {} already exists. It must be a directory, not a file.'.format(ans))
|
||||
except PermissionError:
|
||||
import tempfile
|
||||
import atexit
|
||||
ans = tempfile.mkdtemp(prefix='kitty-conf-')
|
||||
|
||||
def cleanup():
|
||||
import shutil
|
||||
try:
|
||||
shutil.rmtree(ans)
|
||||
except Exception:
|
||||
pass
|
||||
atexit.register(cleanup)
|
||||
return ans
|
||||
|
||||
|
||||
@@ -125,4 +136,4 @@ if os.environ.get('WAYLAND_DISPLAY') and 'KITTY_ENABLE_WAYLAND' in os.environ an
|
||||
is_wayland = True
|
||||
|
||||
|
||||
supports_primary_selection = not is_macos and not is_wayland
|
||||
supports_primary_selection = not is_macos
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// *Bell*: Beeps.
|
||||
#define BEL 0x07
|
||||
|
||||
// *Backspace*: Backspace one column, but not past the begining of the
|
||||
// *Backspace*: Backspace one column, but not past the beginning of the
|
||||
// line.
|
||||
#define BS 0x08
|
||||
|
||||
|
||||
@@ -171,6 +171,14 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
|
||||
else start++;
|
||||
if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); return NULL; }
|
||||
if (new_font == old_font) { CFRelease(new_font); continue; }
|
||||
CFStringRef name = CTFontCopyPostScriptName(new_font);
|
||||
CFComparisonResult cr = CFStringCompare(name, CFSTR("LastResort"), 0);
|
||||
CFRelease(name);
|
||||
if (cr == kCFCompareEqualTo) {
|
||||
CFRelease(new_font);
|
||||
PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont other than the LastResort font");
|
||||
return NULL;
|
||||
}
|
||||
return new_font;
|
||||
}
|
||||
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
|
||||
@@ -213,6 +221,15 @@ is_glyph_empty(PyObject *s, glyph_index g) {
|
||||
return bounds.size.width <= 0;
|
||||
}
|
||||
|
||||
int
|
||||
get_glyph_width(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;
|
||||
}
|
||||
|
||||
static inline float
|
||||
scaled_point_sz(FONTS_DATA_HANDLE fg) {
|
||||
return ((fg->logical_dpi_x + fg->logical_dpi_y) / 144.0) * fg->font_sz_in_pts;
|
||||
@@ -364,14 +381,18 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
|
||||
CGContextSetShouldAntialias(render_ctx, true);
|
||||
CGContextSetShouldSmoothFonts(render_ctx, true);
|
||||
CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs
|
||||
CGContextSetTextDrawingMode(render_ctx, kCGTextFill);
|
||||
CGContextSetGrayStrokeColor(render_ctx, 1, 1);
|
||||
CGContextSetLineWidth(render_ctx, global_state.opts.macos_thicken_font);
|
||||
CGContextSetTextDrawingMode(render_ctx, kCGTextFillStroke);
|
||||
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
|
||||
CGContextSetTextPosition(render_ctx, 0, height - baseline);
|
||||
CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx);
|
||||
CGContextRelease(render_ctx);
|
||||
CGColorSpaceRelease(gray_color_space);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg) {
|
||||
do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
unsigned int canvas_width = cell_width * num_cells;
|
||||
CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs);
|
||||
if (allow_resize) {
|
||||
@@ -382,7 +403,7 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
|
||||
CGFloat sz = CTFontGetSize(ct_font);
|
||||
sz *= canvas_width / right;
|
||||
CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL);
|
||||
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg);
|
||||
bool ret = do_render(new_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph);
|
||||
CFRelease(new_font);
|
||||
return ret;
|
||||
}
|
||||
@@ -408,10 +429,10 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g
|
||||
}
|
||||
|
||||
bool
|
||||
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) {
|
||||
render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint;
|
||||
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg);
|
||||
return do_render(self->ct_font, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -211,55 +211,15 @@ END_ALLOW_CASE_RANGE
|
||||
#undef RANGE
|
||||
}
|
||||
|
||||
static inline int
|
||||
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
|
||||
switch(val & 0xff) {
|
||||
case 1:
|
||||
val >>= 8;
|
||||
if (val < 16 && simple_code) {
|
||||
return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8));
|
||||
}
|
||||
return snprintf(buf, sz, "%u:5:%lu;", complex_code, val);
|
||||
case 2:
|
||||
return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff);
|
||||
default:
|
||||
return snprintf(buf, sz, "%u;", complex_code + 1); // reset
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
decoration_as_sgr(uint8_t decoration) {
|
||||
switch(decoration) {
|
||||
case 1: return "4";
|
||||
case 2: return "4:2";
|
||||
case 3: return "4:3";
|
||||
default: return "24";
|
||||
}
|
||||
}
|
||||
|
||||
const char*
|
||||
cursor_as_sgr(Cursor *self, Cursor *prev) {
|
||||
static char buf[128];
|
||||
#define SZ sizeof(buf) - (p - buf) - 2
|
||||
#define P(fmt, ...) { p += snprintf(p, SZ, fmt ";", __VA_ARGS__); }
|
||||
char *p = buf;
|
||||
bool intensity_differs = self->bold != prev->bold || self->dim != prev->dim;
|
||||
if (intensity_differs) {
|
||||
if (!self->bold || !self->dim) { P("%d", 22); }
|
||||
else P("%d;%d", 1, 2);
|
||||
}
|
||||
if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23);
|
||||
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
|
||||
if (self->strikethrough != prev->strikethrough) P("%d", self->strikethrough ? 9 : 29);
|
||||
if (self->decoration != prev->decoration) P("%s", decoration_as_sgr(self->decoration));
|
||||
if (self->fg != prev->fg) p += color_as_sgr(p, SZ, self->fg, 30, 90, 38);
|
||||
if (self->bg != prev->bg) p += color_as_sgr(p, SZ, self->bg, 40, 100, 48);
|
||||
if (self->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, self->decoration_fg, 0, 0, DECORATION_FG_CODE);
|
||||
#undef P
|
||||
#undef SZ
|
||||
if (p > buf) *(p - 1) = 0; // remove trailing semi-colon
|
||||
*p = 0; // ensure string is null-terminated
|
||||
return buf;
|
||||
cursor_as_sgr(const Cursor *self) {
|
||||
GPUCell blank_cell = { 0 }, cursor_cell = {
|
||||
.attrs = CURSOR_TO_ATTRS(self, 1),
|
||||
.fg = self->fg & COL_MASK,
|
||||
.bg = self->bg & COL_MASK,
|
||||
.decoration_fg = self->decoration_fg & COL_MASK,
|
||||
};
|
||||
return cell_as_sgr(&cursor_cell, &blank_cell);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
#define NS_TO_US (1000)
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <libproc.h>
|
||||
#include <mach/mach_time.h>
|
||||
static mach_timebase_info_data_t timebase = {0};
|
||||
|
||||
@@ -54,6 +55,25 @@ user_cache_dir() {
|
||||
return PyUnicode_FromString(buf);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
process_group_map() {
|
||||
int num_of_processes = proc_listallpids(NULL, 0);
|
||||
size_t bufsize = sizeof(pid_t) * (num_of_processes + 1024);
|
||||
pid_t *buf = malloc(bufsize);
|
||||
if (!buf) return PyErr_NoMemory();
|
||||
num_of_processes = proc_listallpids(buf, (int)bufsize);
|
||||
PyObject *ans = PyTuple_New(num_of_processes);
|
||||
if (ans == NULL) { free(buf); return PyErr_NoMemory(); }
|
||||
for (int i = 0; i < num_of_processes; i++) {
|
||||
long pid = buf[i], pgid = getpgid(buf[i]);
|
||||
PyObject *t = Py_BuildValue("ll", pid, pgid);
|
||||
if (t == NULL) { free(buf); Py_DECREF(ans); return NULL; }
|
||||
PyTuple_SET_ITEM(ans, i, t);
|
||||
}
|
||||
free(buf);
|
||||
return ans;
|
||||
}
|
||||
|
||||
#else
|
||||
#include <time.h>
|
||||
static inline double monotonic_() {
|
||||
@@ -126,6 +146,7 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||
static char ctty[L_ctermid+1];
|
||||
int fd = open(ctermid(ctty), flags);
|
||||
if (fd == -1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
struct termios *termios_p = calloc(1, sizeof(struct termios));
|
||||
if (!termios_p) return PyErr_NoMemory();
|
||||
if (tcgetattr(fd, termios_p) != 0) { free(termios_p); PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
@@ -176,6 +197,7 @@ static PyMethodDef module_methods[] = {
|
||||
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||
#ifdef __APPLE__
|
||||
METHODB(user_cache_dir, METH_NOARGS),
|
||||
METHODB(process_group_map, METH_NOARGS),
|
||||
#endif
|
||||
#ifdef WITH_PROFILER
|
||||
{"start_profiler", (PyCFunction)start_profiler, METH_VARARGS, ""},
|
||||
|
||||
@@ -173,11 +173,20 @@ typedef struct {
|
||||
line_attrs_type *line_attrs;
|
||||
} HistoryBufSegment;
|
||||
|
||||
typedef struct {
|
||||
index_type bufsize, maxsz;
|
||||
Py_UCS4 *buffer;
|
||||
index_type start, end;
|
||||
index_type bufend;
|
||||
bool rewrap_needed;
|
||||
} PagerHistoryBuf;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
index_type xnum, ynum, num_segments;
|
||||
HistoryBufSegment* segments;
|
||||
HistoryBufSegment *segments;
|
||||
PagerHistoryBuf *pagerhist;
|
||||
Line *line;
|
||||
index_type start_of_data, count;
|
||||
} HistoryBuf;
|
||||
@@ -201,7 +210,7 @@ typedef struct {
|
||||
} CursorRenderInfo;
|
||||
|
||||
typedef struct {
|
||||
color_type default_fg, default_bg, cursor_color, highlight_fg, highlight_bg;
|
||||
color_type default_fg, default_bg, cursor_color, cursor_text_color, cursor_text_uses_bg, highlight_fg, highlight_bg;
|
||||
} DynamicColor;
|
||||
|
||||
typedef struct {
|
||||
@@ -254,7 +263,7 @@ const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, siz
|
||||
Line* alloc_line();
|
||||
Cursor* alloc_cursor();
|
||||
LineBuf* alloc_linebuf(unsigned int, unsigned int);
|
||||
HistoryBuf* alloc_historybuf(unsigned int, unsigned int);
|
||||
HistoryBuf* alloc_historybuf(unsigned int, unsigned int, unsigned int);
|
||||
ColorProfile* alloc_color_profile();
|
||||
PyObject* create_256_color_table();
|
||||
PyObject* parse_bytes_dump(PyObject UNUSED *, PyObject *);
|
||||
@@ -267,7 +276,8 @@ void cursor_copy_to(Cursor *src, Cursor *dest);
|
||||
void cursor_reset_display_attrs(Cursor*);
|
||||
void cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count);
|
||||
void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count);
|
||||
const char* cursor_as_sgr(Cursor*, Cursor*);
|
||||
const char* cell_as_sgr(GPUCell *, GPUCell *);
|
||||
const char* cursor_as_sgr(const Cursor *);
|
||||
|
||||
double monotonic();
|
||||
PyObject* cm_thread_write(PyObject *self, PyObject *args);
|
||||
@@ -275,13 +285,14 @@ bool schedule_write_to_child(unsigned long id, unsigned int num, ...);
|
||||
bool set_iutf8(int, bool);
|
||||
|
||||
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
|
||||
float cursor_text_as_bg(ColorProfile *self);
|
||||
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
|
||||
void colorprofile_push_dynamic_colors(ColorProfile*);
|
||||
void colorprofile_pop_dynamic_colors(ColorProfile*);
|
||||
|
||||
void set_mouse_cursor(MouseShape);
|
||||
void enter_event();
|
||||
void mouse_event(int, int);
|
||||
void mouse_event(int, int, int);
|
||||
void focus_in_event();
|
||||
void wakeup_io_loop(bool);
|
||||
void scroll_event(double, double, int);
|
||||
|
||||
@@ -31,10 +31,15 @@ static PyObject*
|
||||
init_x11_startup_notification(PyObject UNUSED *self, PyObject *args) {
|
||||
static bool done = false;
|
||||
static const char* libname = "libstartup-notification-1.so";
|
||||
// some installs are missing the .so symlink, so try the full name
|
||||
static const char* libname2 = "libstartup-notification-1.so.0";
|
||||
static const char* libname3 = "libstartup-notification-1.so.0.0.0";
|
||||
if (!done) {
|
||||
done = true;
|
||||
|
||||
libsn_handle = dlopen(libname, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) libsn_handle = dlopen(libname2, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) libsn_handle = dlopen(libname3, RTLD_LAZY);
|
||||
if (libsn_handle == NULL) {
|
||||
PyErr_Format(PyExc_OSError, "Failed to load %s with error: %s", libname, dlerror());
|
||||
return NULL;
|
||||
|
||||
@@ -24,11 +24,12 @@ pyspacing(int val) {
|
||||
#undef S
|
||||
}
|
||||
|
||||
|
||||
static inline PyObject*
|
||||
pattern_as_dict(FcPattern *pat) {
|
||||
PyObject *ans = PyDict_New();
|
||||
if (ans == NULL) return NULL;
|
||||
#define PS(x) PyUnicode_FromString((char*)x)
|
||||
#define PS(x) PyUnicode_Decode((const char*)x, strlen((const char*)x), "UTF-8", "replace")
|
||||
#define G(type, get, which, conv, name) { \
|
||||
type out; PyObject *p; \
|
||||
if (get(pat, which, 0, &out) == FcResultMatch) { \
|
||||
@@ -114,6 +115,7 @@ _fc_match(FcPattern *pat) {
|
||||
FcResult result;
|
||||
FcConfigSubstitute(NULL, pat, FcMatchPattern);
|
||||
FcDefaultSubstitute(pat);
|
||||
/* printf("fc_match = %s\n", FcNameUnparse(pat)); */
|
||||
match = FcFontMatch(NULL, pat, &result);
|
||||
if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; }
|
||||
ans = pattern_as_dict(match);
|
||||
@@ -145,16 +147,22 @@ end:
|
||||
static PyObject*
|
||||
fc_match(PyObject UNUSED *self, PyObject *args) {
|
||||
char *family = NULL;
|
||||
int bold = 0, italic = 0, allow_bitmapped_fonts = 0;
|
||||
int bold = 0, italic = 0, allow_bitmapped_fonts = 0, monospaced = 0;
|
||||
double size_in_pts = 0, dpi = 0;
|
||||
FcPattern *pat = NULL;
|
||||
PyObject *ans = NULL;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "|zpppdd", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "|zppppdd", &family, &bold, &italic, &monospaced, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
|
||||
pat = FcPatternCreate();
|
||||
if (pat == NULL) return PyErr_NoMemory();
|
||||
|
||||
if (family && strlen(family) > 0) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family");
|
||||
if (monospaced) {
|
||||
// pass the family,monospace as the family parameter to fc-match,
|
||||
// which will fallback to using monospace if the family does not match.
|
||||
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family");
|
||||
AP(FcPatternAddInteger, FC_SPACING, FC_MONO, "spacing");
|
||||
}
|
||||
if (!allow_bitmapped_fonts) {
|
||||
AP(FcPatternAddBool, FC_OUTLINE, true, "outline");
|
||||
AP(FcPatternAddBool, FC_SCALABLE, true, "scalable");
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#define MISSING_GLYPH 4
|
||||
#define MAX_NUM_EXTRA_GLYPHS 8
|
||||
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
|
||||
#define MAX_NUM_EXTRA_GLYPHS_PUA 4
|
||||
|
||||
typedef void (*send_sprite_to_gpu_func)(FONTS_DATA_HANDLE fg, unsigned int, unsigned int, unsigned int, pixel*);
|
||||
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
|
||||
@@ -395,7 +396,7 @@ calc_cell_metrics(FontGroup *fg) {
|
||||
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
|
||||
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
|
||||
if (OPT(adjust_column_width_px != 0)) cell_width += OPT(adjust_column_width_px);
|
||||
if (OPT(adjust_column_width_frac) != 0.f) cell_height *= OPT(adjust_column_width_frac);
|
||||
if (OPT(adjust_column_width_frac) != 0.f) cell_width *= OPT(adjust_column_width_frac);
|
||||
int line_height_adjustment = cell_height - before_cell_height;
|
||||
if (cell_height < 4) fatal("line height too small after adjustment");
|
||||
if (cell_height > 1000) fatal("line height too large after adjustment");
|
||||
@@ -635,8 +636,13 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells)
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_private_use(char_type ch) {
|
||||
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs) {
|
||||
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, bool center_glyph) {
|
||||
static SpritePosition* sprite_position[16];
|
||||
int error = 0;
|
||||
num_cells = MIN(sizeof(sprite_position)/sizeof(sprite_position[0]), num_cells);
|
||||
@@ -651,7 +657,7 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
|
||||
|
||||
clear_canvas(fg);
|
||||
bool was_colored = (gpu_cells->attrs & WIDTH_MASK) == 2 && is_emoji(cpu_cells->ch);
|
||||
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg);
|
||||
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas, fg->cell_width, fg->cell_height, num_cells, fg->baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph);
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
|
||||
for (unsigned int i = 0; i < num_cells; i++) {
|
||||
@@ -901,17 +907,17 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
|
||||
|
||||
static inline void
|
||||
merge_groups_for_pua_space_ligature() {
|
||||
if (G(group_idx) == 1) {
|
||||
while (G(group_idx) > 0) {
|
||||
Group *g = G(groups), *g1 = G(groups) + 1;
|
||||
g->num_cells += g1->num_cells;
|
||||
g->num_glyphs += g1->num_glyphs;
|
||||
g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1);
|
||||
G(group_idx) = 0;
|
||||
G(group_idx)--;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_groups(FontGroup *fg, Font *font) {
|
||||
render_groups(FontGroup *fg, Font *font, bool center_glyph) {
|
||||
unsigned idx = 0;
|
||||
ExtraGlyphs ed;
|
||||
while (idx <= G(group_idx)) {
|
||||
@@ -924,7 +930,7 @@ render_groups(FontGroup *fg, Font *font) {
|
||||
int last = -1;
|
||||
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
|
||||
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
|
||||
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
|
||||
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed, center_glyph);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
@@ -970,12 +976,12 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
#undef G
|
||||
|
||||
static inline void
|
||||
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
|
||||
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature, bool center_glyph) {
|
||||
switch(font_idx) {
|
||||
default:
|
||||
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[font_idx]);
|
||||
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
|
||||
render_groups(fg, &fg->fonts[font_idx]);
|
||||
render_groups(fg, &fg->fonts[font_idx], center_glyph);
|
||||
break;
|
||||
case BLANK_FONT:
|
||||
while(num_cells--) { set_sprite(first_gpu_cell, 0, 0, 0); first_cpu_cell++; first_gpu_cell++; }
|
||||
@@ -989,16 +995,12 @@ render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, inde
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_private_use(char_type ch) {
|
||||
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
||||
}
|
||||
|
||||
void
|
||||
render_line(FONTS_DATA_HANDLE fg_, Line *line) {
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(fg, line->cpu_cells + first_cell_in_run, line->gpu_cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false, center_glyph);
|
||||
FontGroup *fg = (FontGroup*)fg_;
|
||||
ssize_t run_font_idx = NO_FONT;
|
||||
bool center_glyph = false;
|
||||
index_type first_cell_in_run, i;
|
||||
attrs_type prev_width = 0;
|
||||
for (i=0, first_cell_in_run=0; i < line->xnum; i++) {
|
||||
@@ -1006,21 +1008,46 @@ render_line(FONTS_DATA_HANDLE fg_, Line *line) {
|
||||
CPUCell *cpu_cell = line->cpu_cells + i;
|
||||
GPUCell *gpu_cell = line->gpu_cells + i;
|
||||
ssize_t cell_font_idx = font_for_cell(fg, cpu_cell, gpu_cell);
|
||||
if (is_private_use(cpu_cell->ch) && i + 1 < line->xnum && (line->cpu_cells[i+1].ch == ' ' || line->cpu_cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
|
||||
// We have a private use char followed by a space char, render it as a two cell ligature.
|
||||
GPUCell *space_cell = line->gpu_cells + i+1;
|
||||
// Ensure the space cell uses the foreground colors from the PUA cell
|
||||
// This is needed because there are stupid applications like
|
||||
// powerline that use PUA+space with different foreground colors
|
||||
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
|
||||
space_cell->fg = gpu_cell->fg; space_cell->decoration_fg = gpu_cell->decoration_fg;
|
||||
RENDER;
|
||||
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, 2, cell_font_idx, true);
|
||||
run_font_idx = NO_FONT;
|
||||
first_cell_in_run = i + 2;
|
||||
prev_width = line->gpu_cells[i+1].attrs & WIDTH_MASK;
|
||||
i++;
|
||||
continue;
|
||||
|
||||
if (is_private_use(cpu_cell->ch)
|
||||
&& cell_font_idx != BOX_FONT
|
||||
&& cell_font_idx != MISSING_FONT) {
|
||||
int desired_cells = 1;
|
||||
if (cell_font_idx > 0) {
|
||||
Font *font = (fg->fonts + cell_font_idx);
|
||||
glyph_index glyph_id = glyph_id_for_codepoint(font->face, cpu_cell->ch);
|
||||
|
||||
int width = get_glyph_width(font->face, glyph_id);
|
||||
desired_cells = ceilf((float)width / fg->cell_width);
|
||||
}
|
||||
|
||||
int num_spaces = 0;
|
||||
while ((line->cpu_cells[i+num_spaces+1].ch == ' ')
|
||||
&& num_spaces < MAX_NUM_EXTRA_GLYPHS_PUA
|
||||
&& num_spaces < desired_cells
|
||||
&& i + num_spaces + 1 < line->xnum) {
|
||||
num_spaces++;
|
||||
// We have a private use char followed by space(s), render it as a multi-cell ligature.
|
||||
GPUCell *space_cell = line->gpu_cells + i + num_spaces;
|
||||
// Ensure the space cell uses the foreground/background colors from the PUA cell.
|
||||
// This is needed because there are stupid applications like
|
||||
// powerline that use PUA+space with different foreground colors
|
||||
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
|
||||
space_cell->fg = gpu_cell->fg;
|
||||
space_cell->bg = gpu_cell->bg;
|
||||
space_cell->decoration_fg = gpu_cell->decoration_fg;
|
||||
}
|
||||
if (num_spaces) {
|
||||
center_glyph = true;
|
||||
RENDER;
|
||||
center_glyph = false;
|
||||
render_run(fg, line->cpu_cells + i, line->gpu_cells + i, num_spaces + 1, cell_font_idx, true, center_glyph);
|
||||
run_font_idx = NO_FONT;
|
||||
first_cell_in_run = i + num_spaces + 1;
|
||||
prev_width = line->gpu_cells[i+num_spaces].attrs & WIDTH_MASK;
|
||||
i += num_spaces;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
prev_width = gpu_cell->attrs & WIDTH_MASK;
|
||||
if (run_font_idx == NO_FONT) run_font_idx = cell_font_idx;
|
||||
@@ -1077,10 +1104,10 @@ send_prerendered_sprites(FontGroup *fg) {
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
PyObject *args = PyObject_CallFunction(prerender_function, "IIIIIdd", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness, fg->logical_dpi_x, fg->logical_dpi_y);
|
||||
if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); }
|
||||
if (args == NULL) { PyErr_Print(); fatal("Failed to pre-render cells"); }
|
||||
for (ssize_t i = 0; i < PyTuple_GET_SIZE(args) - 1; i++) {
|
||||
x = fg->sprite_tracker.x; y = fg->sprite_tracker.y; z = fg->sprite_tracker.z;
|
||||
if (y > 0) { fatal("too many prerendered sprites for your GPU"); }
|
||||
if (y > 0) { fatal("too many pre-rendered sprites for your GPU or the font size is too large"); }
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
|
||||
@@ -1280,6 +1307,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
||||
if (italic) gpu_cell.attrs |= 1 << ITALIC_SHIFT;
|
||||
FontGroup *fg = font_groups;
|
||||
ssize_t ans = fallback_font(fg, &cpu_cell, &gpu_cell);
|
||||
if (ans == MISSING_FONT) { PyErr_SetString(PyExc_ValueError, "No fallback font found"); return NULL; }
|
||||
if (ans < 0) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
|
||||
return fg->fonts[ans].face;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
// API that font backends need to implement
|
||||
typedef uint16_t glyph_index;
|
||||
unsigned int glyph_id_for_codepoint(PyObject *, char_type);
|
||||
int get_glyph_width(PyObject *, glyph_index);
|
||||
bool is_glyph_empty(PyObject *, glyph_index);
|
||||
hb_font_t* harfbuzz_font_for_face(PyObject*);
|
||||
bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE);
|
||||
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
|
||||
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE);
|
||||
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph);
|
||||
PyObject* create_fallback_face(PyObject *base_face, CPUCell* cell, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg);
|
||||
PyObject* specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE);
|
||||
PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE);
|
||||
|
||||
@@ -44,8 +44,12 @@ def list_fonts():
|
||||
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
|
||||
|
||||
|
||||
def family_name_to_key(family):
|
||||
return re.sub(r'\s+', ' ', family.lower())
|
||||
|
||||
|
||||
def find_best_match(family, bold=False, italic=False, monospaced=True):
|
||||
q = re.sub(r'\s+', ' ', family.lower())
|
||||
q = family_name_to_key(family)
|
||||
font_map = all_fonts_map(monospaced)
|
||||
|
||||
def score(candidate):
|
||||
@@ -61,6 +65,22 @@ def find_best_match(family, bold=False, italic=False, monospaced=True):
|
||||
candidates.sort(key=score)
|
||||
return candidates[0]
|
||||
|
||||
# Use fc-match to see if we can find a monospaced font that matches family
|
||||
possibility = fc_match(family, False, False)
|
||||
for key, map_key in (('postscript_name', 'ps_map'), ('full_name', 'full_map'), ('family', 'family_map')):
|
||||
val = possibility.get(key)
|
||||
if val:
|
||||
candidates = font_map[map_key].get(family_name_to_key(val))
|
||||
if candidates:
|
||||
if len(candidates) == 1:
|
||||
# happens if the family name is an alias, so we search with
|
||||
# the actual family name to see if we can find all the
|
||||
# fonts in the family.
|
||||
family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family']))
|
||||
if family_name_candidates and len(family_name_candidates) > 1:
|
||||
candidates = family_name_candidates
|
||||
return sorted(candidates, key=score)[0]
|
||||
|
||||
# Use fc-match with a generic family
|
||||
family = 'monospace' if monospaced else 'sans-serif'
|
||||
return fc_match(family, bold, italic)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import ctypes
|
||||
import sys
|
||||
from functools import partial
|
||||
from math import ceil, floor, pi, sin, sqrt
|
||||
from math import ceil, pi, cos, floor
|
||||
|
||||
from kitty.config import defaults
|
||||
from kitty.constants import is_macos
|
||||
@@ -115,29 +115,30 @@ def add_dline(buf, cell_width, position, thickness, cell_height):
|
||||
|
||||
|
||||
def add_curl(buf, cell_width, position, thickness, cell_height):
|
||||
xfactor = 2.0 * pi / cell_width
|
||||
yfactor = thickness
|
||||
max_x, max_y = cell_width - 1, cell_height - 1
|
||||
xfactor = 2.0 * pi / max_x
|
||||
half_height = max(thickness // 2, 1)
|
||||
|
||||
def clamp_y(y):
|
||||
return max(0, min(int(y), cell_height - 1))
|
||||
def add_intensity(x, y, val):
|
||||
y += position
|
||||
y = min(y, max_y)
|
||||
idx = cell_width * y + x
|
||||
buf[idx] = min(255, buf[idx] + val)
|
||||
|
||||
def clamp_x(x):
|
||||
return max(0, min(int(x), cell_width - 1))
|
||||
# Ensure all space at bottom of cell is used
|
||||
if position + half_height < max_y:
|
||||
position += max_y - (position + half_height)
|
||||
if position + half_height > max_y:
|
||||
position -= position + half_height - max_y
|
||||
|
||||
def add_intensity(x, y, distance):
|
||||
buf[cell_width * y + x] = min(
|
||||
255, buf[cell_width * y + x] + int(255 * (1 - distance))
|
||||
)
|
||||
|
||||
for x_exact in range(cell_width):
|
||||
y_exact = yfactor * sin(x_exact * xfactor) + position
|
||||
y_below = clamp_y(floor(y_exact))
|
||||
y_above = clamp_y(ceil(y_exact))
|
||||
x_before, x_after = map(clamp_x, (x_exact - 1, x_exact + 1))
|
||||
for x in {x_before, x_exact, x_after}:
|
||||
for y in {y_below, y_above}:
|
||||
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
|
||||
add_intensity(x, y, dist)
|
||||
# Use the Wu antialias algorithm to draw the curve
|
||||
# cosine waves always have slope <= 1 so are never steep
|
||||
for x in range(cell_width):
|
||||
y = half_height * cos(x * xfactor)
|
||||
y1, y2 = floor(y), ceil(y)
|
||||
i1 = int(255 * abs(y - y1))
|
||||
add_intensity(x, y1, 255 - i1)
|
||||
add_intensity(x, y2, i1)
|
||||
|
||||
|
||||
def render_special(
|
||||
|
||||
107
kitty/freetype.c
107
kitty/freetype.c
@@ -16,7 +16,12 @@
|
||||
#define HARFBUZZ_HAS_CHANGE_FONT
|
||||
#endif
|
||||
|
||||
#if FREETYPE_MAJOR == 2 && FREETYPE_MINOR < 7
|
||||
#define FT_Bitmap_Init FT_Bitmap_New
|
||||
#endif
|
||||
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_BITMAP_H
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
@@ -272,6 +277,20 @@ is_glyph_empty(PyObject *s, glyph_index g) {
|
||||
#undef M
|
||||
}
|
||||
|
||||
int
|
||||
get_glyph_width(PyObject *s, glyph_index g) {
|
||||
Face *self = (Face*)s;
|
||||
if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; }
|
||||
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
|
||||
#define M self->face->glyph->metrics
|
||||
#define B self->face->glyph->bitmap
|
||||
/* printf("glyph: %u bitmap.width: %d bitmap.rows: %d horiAdvance: %ld horiBearingX: %ld horiBearingY: %ld vertBearingX: %ld vertBearingY: %ld vertAdvance: %ld width: %ld height: %ld\n", */
|
||||
/* g, B.width, B.rows, M.horiAdvance, M.horiBearingX, M.horiBearingY, M.vertBearingX, M.vertBearingY, M.vertAdvance, M.width, M.height); */
|
||||
return bitmap->width;
|
||||
#undef M
|
||||
#undef B
|
||||
}
|
||||
|
||||
hb_font_t*
|
||||
harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; }
|
||||
|
||||
@@ -283,8 +302,17 @@ typedef struct {
|
||||
FT_Pixel_Mode pixel_mode;
|
||||
bool needs_free;
|
||||
unsigned int factor, right_edge;
|
||||
int bitmap_left, bitmap_top;
|
||||
} ProcessedBitmap;
|
||||
|
||||
static inline void
|
||||
free_processed_bitmap(ProcessedBitmap *bm) {
|
||||
if (bm->needs_free) {
|
||||
bm->needs_free = false;
|
||||
free(bm->buf); bm->buf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
trim_borders(ProcessedBitmap *ans, size_t extra) {
|
||||
bool column_has_text = false;
|
||||
@@ -302,17 +330,48 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
|
||||
ans->width -= extra;
|
||||
}
|
||||
|
||||
static inline void
|
||||
populate_processed_bitmap(FT_GlyphSlotRec *slot, FT_Bitmap *bitmap, ProcessedBitmap *ans, bool copy_buf) {
|
||||
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
|
||||
ans->rows = bitmap->rows;
|
||||
if (copy_buf) {
|
||||
ans->buf = calloc(ans->rows, ans->stride);
|
||||
if (!ans->buf) fatal("Out of memory");
|
||||
ans->needs_free = true;
|
||||
memcpy(ans->buf, bitmap->buffer, ans->rows * ans->stride);
|
||||
} else ans->buf = bitmap->buffer;
|
||||
ans->start_x = 0; ans->width = bitmap->width;
|
||||
ans->pixel_mode = bitmap->pixel_mode;
|
||||
ans->bitmap_top = slot->bitmap_top; ans->bitmap_left = slot->bitmap_left;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale, FONTS_DATA_HANDLE fg) {
|
||||
if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false;
|
||||
unsigned int max_width = cell_width * num_cells;
|
||||
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
|
||||
ans->buf = bitmap->buffer;
|
||||
ans->start_x = 0; ans->width = bitmap->width;
|
||||
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
|
||||
ans->rows = bitmap->rows;
|
||||
ans->pixel_mode = bitmap->pixel_mode;
|
||||
|
||||
// Embedded bitmap glyph?
|
||||
if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
|
||||
FT_Bitmap bitmap;
|
||||
FT_Bitmap_Init(&bitmap);
|
||||
|
||||
// This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to
|
||||
int error = FT_Bitmap_Convert(library, &self->face->glyph->bitmap, &bitmap, 1);
|
||||
if (error) { set_freetype_error("Failed to convert bitmap, with error:", error); return false; }
|
||||
|
||||
// Normalize gray levels to the range [0..255]
|
||||
bitmap.num_grays = 256;
|
||||
unsigned int stride = bitmap.pitch < 0 ? -bitmap.pitch : bitmap.pitch;
|
||||
for (unsigned int i = 0; i < bitmap.rows; ++i) {
|
||||
// We only have 2 levels
|
||||
for (unsigned int j = 0; j < bitmap.width; ++j) bitmap.buffer[i * stride + j] *= 255;
|
||||
}
|
||||
populate_processed_bitmap(self->face->glyph, &bitmap, ans, true);
|
||||
FT_Bitmap_Done(library, &bitmap);
|
||||
} else {
|
||||
populate_processed_bitmap(self->face->glyph, &self->face->glyph->bitmap, ans, false);
|
||||
}
|
||||
|
||||
if (ans->width > max_width) {
|
||||
size_t extra = ans->width - max_width;
|
||||
if (italic && extra < cell_width / 2) {
|
||||
@@ -323,8 +382,9 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
|
||||
// bad, we just crop the bitmap on the right. See https://github.com/kovidgoyal/kitty/issues/352
|
||||
} else if (rescale && self->is_scalable && extra > 1) {
|
||||
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height;
|
||||
float ar = (float)max_width / (float)bitmap->width;
|
||||
float ar = (float)max_width / (float)ans->width;
|
||||
if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0, fg->cell_height)) {
|
||||
free_processed_bitmap(ans);
|
||||
if (!render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, false, fg)) return false;
|
||||
if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi, 0, fg->cell_height)) return false;
|
||||
} else return false;
|
||||
@@ -400,6 +460,8 @@ render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int
|
||||
ans->rows = bitmap->rows;
|
||||
ans->pixel_mode = bitmap->pixel_mode;
|
||||
if (ans->width > num_cells * cell_width + 2) downsample_bitmap(ans, num_cells * cell_width, cell_height);
|
||||
ans->bitmap_top = (float)self->face->glyph->bitmap_top / ans->factor;
|
||||
ans->bitmap_left = (float)self->face->glyph->bitmap_left / ans->factor;
|
||||
detect_right_edge(ans);
|
||||
return true;
|
||||
}
|
||||
@@ -422,16 +484,14 @@ copy_color_bitmap(uint8_t *src, pixel* dest, Region *src_rect, Region *dest_rect
|
||||
}
|
||||
|
||||
static inline void
|
||||
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) {
|
||||
place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, size_t baseline) {
|
||||
// We want the glyph to be positioned inside the cell based on the bearingX
|
||||
// and bearingY values, making sure that it does not overflow the cell.
|
||||
|
||||
Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width + bm->start_x }, dest = { .bottom = cell_height, .right = cell_width };
|
||||
|
||||
// Calculate column bounds
|
||||
float bearing_x = (float)metrics->horiBearingX / 64.f;
|
||||
bearing_x /= bm->factor;
|
||||
int32_t xoff = (ssize_t)(x_offset + bearing_x);
|
||||
int32_t xoff = (ssize_t)(x_offset + bm->bitmap_left);
|
||||
uint32_t extra;
|
||||
if (xoff < 0) src.left += -xoff;
|
||||
else dest.left = xoff;
|
||||
@@ -442,9 +502,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
}
|
||||
|
||||
// Calculate row bounds
|
||||
float bearing_y = (float)metrics->horiBearingY / 64.f;
|
||||
bearing_y /= bm->factor;
|
||||
int32_t yoff = (ssize_t)(y_offset + bearing_y);
|
||||
int32_t yoff = (ssize_t)(y_offset + bm->bitmap_top);
|
||||
if ((yoff > 0 && (size_t)yoff > baseline)) {
|
||||
dest.top = 0;
|
||||
} else {
|
||||
@@ -461,7 +519,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
static const ProcessedBitmap EMPTY_PBM = {.factor = 1};
|
||||
|
||||
bool
|
||||
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg) {
|
||||
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) {
|
||||
Face *self = (Face*)f;
|
||||
bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color;
|
||||
float x = 0.f, y = 0.f, x_offset = 0.f;
|
||||
@@ -480,17 +538,20 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
|
||||
}
|
||||
x_offset = x + (float)positions[i].x_offset / 64.0f;
|
||||
y = (float)positions[i].y_offset / 64.0f;
|
||||
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, &self->face->glyph->metrics, baseline);
|
||||
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) {
|
||||
place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, baseline);
|
||||
}
|
||||
x += (float)positions[i].x_advance / 64.0f;
|
||||
if (bm.needs_free) free(bm.buf);
|
||||
free_processed_bitmap(&bm);
|
||||
}
|
||||
|
||||
// center the glyphs in the canvas
|
||||
unsigned int right_edge = (unsigned int)x, delta;
|
||||
// x_advance is wrong for colored bitmaps that have been downsampled
|
||||
if (*was_colored) right_edge = num_glyphs == 1 ? bm.right_edge : canvas_width;
|
||||
if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
|
||||
right_shift_canvas(canvas, canvas_width, cell_height, delta);
|
||||
if (center_glyph && num_glyphs) {
|
||||
unsigned int right_edge = (unsigned int)x, delta;
|
||||
// x_advance is wrong for colored bitmaps that have been downsampled
|
||||
if (*was_colored) right_edge = num_glyphs == 1 ? bm.right_edge : canvas_width;
|
||||
if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {
|
||||
right_shift_canvas(canvas, canvas_width, cell_height, delta);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
5606
kitty/gl-wrapper.c
generated
5606
kitty/gl-wrapper.c
generated
File diff suppressed because it is too large
Load Diff
87
kitty/gl-wrapper.h
generated
87
kitty/gl-wrapper.h
generated
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
OpenGL loader generated by glad 0.1.24a0 on Wed Jun 20 12:32:33 2018.
|
||||
OpenGL loader generated by glad 0.1.28 on Tue Dec 4 16:30:01 2018.
|
||||
Language/Generator: C/C++ Debug
|
||||
Specification: gl
|
||||
APIs: gl=3.3
|
||||
@@ -13,10 +13,11 @@
|
||||
Loader: True
|
||||
Local files: False
|
||||
Omit khrplatform: False
|
||||
Reproducible: False
|
||||
Commandline:
|
||||
--profile="core" --api="gl=3.3" --generator="c-debug" --spec="gl" --extensions="GL_ARB_copy_image,GL_ARB_multisample,GL_ARB_robustness,GL_ARB_texture_storage,GL_KHR_debug"
|
||||
Online:
|
||||
http://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
|
||||
https://glad.dav1d.de/#profile=core&language=c-debug&specification=gl&loader=on&api=gl%3D3.3&extensions=GL_ARB_copy_image&extensions=GL_ARB_multisample&extensions=GL_ARB_robustness&extensions=GL_ARB_texture_storage&extensions=GL_KHR_debug
|
||||
*/
|
||||
#ifndef __glad_h_
|
||||
#define __glad_h_
|
||||
@@ -39,6 +40,9 @@
|
||||
#ifndef APIENTRYP
|
||||
#define APIENTRYP APIENTRY *
|
||||
#endif
|
||||
#ifndef GLAPIENTRY
|
||||
#define GLAPIENTRY APIENTRY
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -79,59 +83,21 @@ GLAPI int gladLoadGLLoader(GLADloadproc);
|
||||
typedef void (* GLADcallback)(const char *name, void *funcptr, int len_args, ...);
|
||||
GLAPI void glad_set_pre_callback(GLADcallback cb);
|
||||
GLAPI void glad_set_post_callback(GLADcallback cb);
|
||||
#include <stddef.h>
|
||||
#include "khrplatform.h"
|
||||
#ifndef GLEXT_64_TYPES_DEFINED
|
||||
/* This code block is duplicated in glxext.h, so must be protected */
|
||||
#define GLEXT_64_TYPES_DEFINED
|
||||
/* Define int32_t, int64_t, and uint64_t types for UST/MSC */
|
||||
/* (as used in the GL_EXT_timer_query extension). */
|
||||
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
|
||||
#include <inttypes.h>
|
||||
#elif defined(__sun__) || defined(__digital__)
|
||||
#include <inttypes.h>
|
||||
#if defined(__STDC__)
|
||||
#if defined(__arch64__) || defined(_LP64)
|
||||
typedef long int int64_t;
|
||||
typedef unsigned long int uint64_t;
|
||||
#else
|
||||
typedef long long int int64_t;
|
||||
typedef unsigned long long int uint64_t;
|
||||
#endif /* __arch64__ */
|
||||
#endif /* __STDC__ */
|
||||
#elif defined( __VMS ) || defined(__sgi)
|
||||
#include <inttypes.h>
|
||||
#elif defined(__SCO__) || defined(__USLC__)
|
||||
#include <stdint.h>
|
||||
#elif defined(__UNIXOS2__) || defined(__SOL64__)
|
||||
typedef long int int32_t;
|
||||
typedef long long int int64_t;
|
||||
typedef unsigned long long int uint64_t;
|
||||
#elif defined(_WIN32) && defined(__GNUC__)
|
||||
#include <stdint.h>
|
||||
#elif defined(_WIN32)
|
||||
typedef __int32 int32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
/* Fallback if nothing above works */
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
#endif
|
||||
typedef unsigned int GLenum;
|
||||
typedef unsigned char GLboolean;
|
||||
typedef unsigned int GLbitfield;
|
||||
typedef void GLvoid;
|
||||
typedef signed char GLbyte;
|
||||
typedef short GLshort;
|
||||
typedef khronos_int8_t GLbyte;
|
||||
typedef khronos_uint8_t GLubyte;
|
||||
typedef khronos_int16_t GLshort;
|
||||
typedef khronos_uint16_t GLushort;
|
||||
typedef int GLint;
|
||||
typedef int GLclampx;
|
||||
typedef unsigned char GLubyte;
|
||||
typedef unsigned short GLushort;
|
||||
typedef unsigned int GLuint;
|
||||
typedef khronos_int32_t GLclampx;
|
||||
typedef int GLsizei;
|
||||
typedef float GLfloat;
|
||||
typedef float GLclampf;
|
||||
typedef khronos_float_t GLfloat;
|
||||
typedef khronos_float_t GLclampf;
|
||||
typedef double GLdouble;
|
||||
typedef double GLclampd;
|
||||
typedef void *GLeglClientBufferEXT;
|
||||
@@ -143,25 +109,17 @@ typedef void *GLhandleARB;
|
||||
#else
|
||||
typedef unsigned int GLhandleARB;
|
||||
#endif
|
||||
typedef unsigned short GLhalfARB;
|
||||
typedef unsigned short GLhalf;
|
||||
typedef GLint GLfixed;
|
||||
typedef khronos_uint16_t GLhalf;
|
||||
typedef khronos_uint16_t GLhalfARB;
|
||||
typedef khronos_int32_t GLfixed;
|
||||
typedef khronos_intptr_t GLintptr;
|
||||
typedef khronos_intptr_t GLintptrARB;
|
||||
typedef khronos_ssize_t GLsizeiptr;
|
||||
typedef int64_t GLint64;
|
||||
typedef uint64_t GLuint64;
|
||||
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
|
||||
typedef long GLintptrARB;
|
||||
#else
|
||||
typedef ptrdiff_t GLintptrARB;
|
||||
#endif
|
||||
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060)
|
||||
typedef long GLsizeiptrARB;
|
||||
#else
|
||||
typedef ptrdiff_t GLsizeiptrARB;
|
||||
#endif
|
||||
typedef int64_t GLint64EXT;
|
||||
typedef uint64_t GLuint64EXT;
|
||||
typedef khronos_ssize_t GLsizeiptrARB;
|
||||
typedef khronos_int64_t GLint64;
|
||||
typedef khronos_int64_t GLint64EXT;
|
||||
typedef khronos_uint64_t GLuint64;
|
||||
typedef khronos_uint64_t GLuint64EXT;
|
||||
typedef struct __GLsync *GLsync;
|
||||
struct _cl_context;
|
||||
struct _cl_event;
|
||||
@@ -814,7 +772,6 @@ typedef void (APIENTRY *GLVULKANPROCNV)(void);
|
||||
#define GL_RENDERBUFFER_STENCIL_SIZE 0x8D55
|
||||
#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE 0x8D56
|
||||
#define GL_MAX_SAMPLES 0x8D57
|
||||
#define GL_INDEX 0x8222
|
||||
#define GL_FRAMEBUFFER_SRGB 0x8DB9
|
||||
#define GL_HALF_FLOAT 0x140B
|
||||
#define GL_MAP_READ_BIT 0x0001
|
||||
|
||||
13
kitty/glfw-wrapper.c
generated
13
kitty/glfw-wrapper.c
generated
@@ -131,6 +131,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwGetWindowContentScale_impl) = dlsym(handle, "glfwGetWindowContentScale");
|
||||
if (glfwGetWindowContentScale_impl == NULL) fail("Failed to load glfw function glfwGetWindowContentScale with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwGetDoubleClickInterval_impl) = dlsym(handle, "glfwGetDoubleClickInterval");
|
||||
if (glfwGetDoubleClickInterval_impl == NULL) fail("Failed to load glfw function glfwGetDoubleClickInterval with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwGetWindowOpacity_impl) = dlsym(handle, "glfwGetWindowOpacity");
|
||||
if (glfwGetWindowOpacity_impl == NULL) fail("Failed to load glfw function glfwGetWindowOpacity with error: %s", dlerror());
|
||||
|
||||
@@ -358,10 +361,14 @@ load_glfw(const char* path) {
|
||||
|
||||
*(void **) (&glfwGetCocoaWindow_impl) = dlsym(handle, "glfwGetCocoaWindow");
|
||||
|
||||
*(void **) (&glfwGetNSGLContext_impl) = dlsym(handle, "glfwGetNSGLContext");
|
||||
|
||||
*(void **) (&glfwGetCocoaMonitor_impl) = dlsym(handle, "glfwGetCocoaMonitor");
|
||||
|
||||
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
|
||||
|
||||
*(void **) (&glfwSetCocoaToggleFullscreenIntercept_impl) = dlsym(handle, "glfwSetCocoaToggleFullscreenIntercept");
|
||||
|
||||
*(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen");
|
||||
|
||||
*(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent");
|
||||
@@ -370,12 +377,14 @@ load_glfw(const char* path) {
|
||||
|
||||
*(void **) (&glfwGetX11Window_impl) = dlsym(handle, "glfwGetX11Window");
|
||||
|
||||
*(void **) (&glfwSetX11SelectionString_impl) = dlsym(handle, "glfwSetX11SelectionString");
|
||||
*(void **) (&glfwSetPrimarySelectionString_impl) = dlsym(handle, "glfwSetPrimarySelectionString");
|
||||
|
||||
*(void **) (&glfwGetX11SelectionString_impl) = dlsym(handle, "glfwGetX11SelectionString");
|
||||
*(void **) (&glfwGetPrimarySelectionString_impl) = dlsym(handle, "glfwGetPrimarySelectionString");
|
||||
|
||||
*(void **) (&glfwGetXKBScancode_impl) = dlsym(handle, "glfwGetXKBScancode");
|
||||
|
||||
*(void **) (&glfwRequestWaylandFrameEvent_impl) = dlsym(handle, "glfwRequestWaylandFrameEvent");
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
41
kitty/glfw-wrapper.h
generated
41
kitty/glfw-wrapper.h
generated
@@ -1,8 +1,6 @@
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
|
||||
|
||||
|
||||
@@ -168,6 +166,7 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
#define GLFW_KEY_GRAVE_ACCENT 96 /* ` */
|
||||
#define GLFW_KEY_WORLD_1 161 /* non-US #1 */
|
||||
#define GLFW_KEY_WORLD_2 162 /* non-US #2 */
|
||||
#define GLFW_KEY_PLUS 163 /* non-US #2 */
|
||||
|
||||
/* Function keys */
|
||||
#define GLFW_KEY_ESCAPE 256
|
||||
@@ -734,6 +733,10 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
* [attribute](@ref GLFW_CLIENT_API_attrib).
|
||||
*/
|
||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||
/*! @brief Window content area scaling window
|
||||
* [window hint](@ref GLFW_SCALE_TO_MONITOR).
|
||||
*/
|
||||
#define GLFW_SCALE_TO_MONITOR 0x0002200C
|
||||
|
||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||
@@ -741,6 +744,8 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
|
||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||
|
||||
#define GLFW_WAYLAND_APP_ID 0x00025001
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_NO_API 0
|
||||
@@ -1380,6 +1385,10 @@ typedef struct GLFWgamepadstate
|
||||
*/
|
||||
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
typedef void (*GLFWwaylandframecallbackfunc)(unsigned long long id);
|
||||
typedef int (*glfwInit_func)();
|
||||
glfwInit_func glfwInit_impl;
|
||||
#define glfwInit glfwInit_impl
|
||||
@@ -1536,6 +1545,10 @@ typedef void (*glfwGetWindowContentScale_func)(GLFWwindow*, float*, float*);
|
||||
glfwGetWindowContentScale_func glfwGetWindowContentScale_impl;
|
||||
#define glfwGetWindowContentScale glfwGetWindowContentScale_impl
|
||||
|
||||
typedef double (*glfwGetDoubleClickInterval_func)(GLFWwindow*);
|
||||
glfwGetDoubleClickInterval_func glfwGetDoubleClickInterval_impl;
|
||||
#define glfwGetDoubleClickInterval glfwGetDoubleClickInterval_impl
|
||||
|
||||
typedef float (*glfwGetWindowOpacity_func)(GLFWwindow*);
|
||||
glfwGetWindowOpacity_func glfwGetWindowOpacity_impl;
|
||||
#define glfwGetWindowOpacity glfwGetWindowOpacity_impl
|
||||
@@ -1840,6 +1853,10 @@ typedef void* (*glfwGetCocoaWindow_func)(GLFWwindow*);
|
||||
glfwGetCocoaWindow_func glfwGetCocoaWindow_impl;
|
||||
#define glfwGetCocoaWindow glfwGetCocoaWindow_impl
|
||||
|
||||
typedef void* (*glfwGetNSGLContext_func)(GLFWwindow*);
|
||||
glfwGetNSGLContext_func glfwGetNSGLContext_impl;
|
||||
#define glfwGetNSGLContext glfwGetNSGLContext_impl
|
||||
|
||||
typedef uint32_t (*glfwGetCocoaMonitor_func)(GLFWmonitor*);
|
||||
glfwGetCocoaMonitor_func glfwGetCocoaMonitor_impl;
|
||||
#define glfwGetCocoaMonitor glfwGetCocoaMonitor_impl
|
||||
@@ -1848,6 +1865,10 @@ typedef GLFWcocoatextinputfilterfun (*glfwSetCocoaTextInputFilter_func)(GLFWwind
|
||||
glfwSetCocoaTextInputFilter_func glfwSetCocoaTextInputFilter_impl;
|
||||
#define glfwSetCocoaTextInputFilter glfwSetCocoaTextInputFilter_impl
|
||||
|
||||
typedef GLFWcocoatogglefullscreenfun (*glfwSetCocoaToggleFullscreenIntercept_func)(GLFWwindow*, GLFWcocoatogglefullscreenfun);
|
||||
glfwSetCocoaToggleFullscreenIntercept_func glfwSetCocoaToggleFullscreenIntercept_impl;
|
||||
#define glfwSetCocoaToggleFullscreenIntercept glfwSetCocoaToggleFullscreenIntercept_impl
|
||||
|
||||
typedef GLFWapplicationshouldhandlereopenfun (*glfwSetApplicationShouldHandleReopen_func)(GLFWapplicationshouldhandlereopenfun);
|
||||
glfwSetApplicationShouldHandleReopen_func glfwSetApplicationShouldHandleReopen_impl;
|
||||
#define glfwSetApplicationShouldHandleReopen glfwSetApplicationShouldHandleReopen_impl
|
||||
@@ -1864,16 +1885,20 @@ typedef int32_t (*glfwGetX11Window_func)(GLFWwindow*);
|
||||
glfwGetX11Window_func glfwGetX11Window_impl;
|
||||
#define glfwGetX11Window glfwGetX11Window_impl
|
||||
|
||||
typedef void (*glfwSetX11SelectionString_func)(const char*);
|
||||
glfwSetX11SelectionString_func glfwSetX11SelectionString_impl;
|
||||
#define glfwSetX11SelectionString glfwSetX11SelectionString_impl
|
||||
typedef void (*glfwSetPrimarySelectionString_func)(GLFWwindow*, const char*);
|
||||
glfwSetPrimarySelectionString_func glfwSetPrimarySelectionString_impl;
|
||||
#define glfwSetPrimarySelectionString glfwSetPrimarySelectionString_impl
|
||||
|
||||
typedef const char* (*glfwGetX11SelectionString_func)();
|
||||
glfwGetX11SelectionString_func glfwGetX11SelectionString_impl;
|
||||
#define glfwGetX11SelectionString glfwGetX11SelectionString_impl
|
||||
typedef const char* (*glfwGetPrimarySelectionString_func)(GLFWwindow*);
|
||||
glfwGetPrimarySelectionString_func glfwGetPrimarySelectionString_impl;
|
||||
#define glfwGetPrimarySelectionString glfwGetPrimarySelectionString_impl
|
||||
|
||||
typedef int (*glfwGetXKBScancode_func)(const char*, int);
|
||||
glfwGetXKBScancode_func glfwGetXKBScancode_impl;
|
||||
#define glfwGetXKBScancode glfwGetXKBScancode_impl
|
||||
|
||||
typedef void (*glfwRequestWaylandFrameEvent_func)(GLFWwindow*, unsigned long long, GLFWwaylandframecallbackfunc);
|
||||
glfwRequestWaylandFrameEvent_func glfwRequestWaylandFrameEvent_impl;
|
||||
#define glfwRequestWaylandFrameEvent glfwRequestWaylandFrameEvent_impl
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
|
||||
223
kitty/glfw.c
223
kitty/glfw.c
@@ -10,10 +10,12 @@
|
||||
#include "glfw-wrapper.h"
|
||||
extern bool cocoa_make_window_resizable(void *w, bool);
|
||||
extern void cocoa_focus_window(void *w);
|
||||
extern bool cocoa_toggle_fullscreen(void *w, bool);
|
||||
extern void cocoa_create_global_menu(void);
|
||||
extern void cocoa_set_hide_from_tasks(void);
|
||||
extern void cocoa_set_titlebar_color(void *w, color_type color);
|
||||
|
||||
|
||||
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
|
||||
#error "glfw has too many keys, you should increase MAX_KEY_COUNT"
|
||||
#endif
|
||||
@@ -25,9 +27,13 @@ static void set_os_window_dpi(OSWindow *w);
|
||||
|
||||
void
|
||||
update_os_window_viewport(OSWindow *window, bool notify_boss) {
|
||||
int w, h;
|
||||
glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height);
|
||||
int w, h, fw, fh;
|
||||
glfwGetFramebufferSize(window->handle, &fw, &fh);
|
||||
glfwGetWindowSize(window->handle, &w, &h);
|
||||
if (fw == window->viewport_width && fh == window->viewport_height && w == window->window_width && h == window->window_height) {
|
||||
return; // no change, ignore
|
||||
}
|
||||
window->viewport_width = fw; window->viewport_height = fh;
|
||||
double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio;
|
||||
window->viewport_x_ratio = (double)window->viewport_width / (double)w;
|
||||
window->viewport_y_ratio = (double)window->viewport_height / (double)h;
|
||||
@@ -43,6 +49,11 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
|
||||
window->window_height = MAX(h, 100);
|
||||
if (notify_boss) {
|
||||
call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
|
||||
if (dpi_changed && global_state.is_wayland) {
|
||||
// Fake resize event needed on weston to ensure surface is
|
||||
// positioned correctly after DPI change
|
||||
glfwSetWindowSize(window->handle, window->window_width, window->window_height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +117,17 @@ framebuffer_size_callback(GLFWwindow *w, int width, int height) {
|
||||
window->has_pending_resizes = true; global_state.has_pending_resizes = true;
|
||||
window->last_resize_event_at = monotonic();
|
||||
unjam_event_loop();
|
||||
#ifdef __APPLE__
|
||||
// Cocoa starts a sub-loop inside wait events which means main_loop
|
||||
// stays stuck and no rendering happens. This causes the window to be
|
||||
// blank. This is particularly bad for semi-transparent windows since
|
||||
// they are rendered as invisible, so for that case we manually render.
|
||||
if (global_state.callback_os_window->is_semi_transparent) {
|
||||
make_os_window_context_current(global_state.callback_os_window);
|
||||
blank_os_window(global_state.callback_os_window);
|
||||
swap_window_buffers(global_state.callback_os_window);
|
||||
}
|
||||
#endif
|
||||
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
@@ -157,9 +179,9 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
|
||||
show_mouse_cursor(w);
|
||||
double now = monotonic();
|
||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
||||
if (button >= 0 && (unsigned int)button < sizeof(global_state.callback_os_window->mouse_button_pressed)/sizeof(global_state.callback_os_window->mouse_button_pressed[0])) {
|
||||
if (button >= 0 && (unsigned int)button < arraysz(global_state.callback_os_window->mouse_button_pressed)) {
|
||||
global_state.callback_os_window->mouse_button_pressed[button] = action == GLFW_PRESS ? true : false;
|
||||
if (is_window_ready_for_callbacks()) mouse_event(button, mods);
|
||||
if (is_window_ready_for_callbacks()) mouse_event(button, mods, action);
|
||||
}
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
@@ -173,7 +195,7 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
|
||||
global_state.callback_os_window->cursor_blink_zero_time = now;
|
||||
global_state.callback_os_window->mouse_x = x * global_state.callback_os_window->viewport_x_ratio;
|
||||
global_state.callback_os_window->mouse_y = y * global_state.callback_os_window->viewport_y_ratio;
|
||||
if (is_window_ready_for_callbacks()) mouse_event(-1, 0);
|
||||
if (is_window_ready_for_callbacks()) mouse_event(-1, 0, -1);
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
@@ -278,17 +300,17 @@ set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static GLFWwindow *current_os_window_ctx = NULL;
|
||||
|
||||
void
|
||||
make_os_window_context_current(OSWindow *w) {
|
||||
if (w->handle != current_os_window_ctx) {
|
||||
current_os_window_ctx = w->handle;
|
||||
GLFWwindow *current_context = glfwGetCurrentContext();
|
||||
if (w->handle != current_context) {
|
||||
glfwMakeContextCurrent(w->handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifndef __APPLE__
|
||||
static GLFWmonitor*
|
||||
current_monitor(GLFWwindow *window) {
|
||||
// Find the monitor that has the maximum overlap with this window
|
||||
@@ -323,14 +345,15 @@ current_monitor(GLFWwindow *window) {
|
||||
|
||||
return bestmonitor;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
get_window_dpi(GLFWwindow *w, double *x, double *y) {
|
||||
GLFWmonitor *monitor = NULL;
|
||||
if (w) monitor = current_monitor(w);
|
||||
if (monitor == NULL) monitor = glfwGetPrimaryMonitor();
|
||||
float xscale = 1, yscale = 1;
|
||||
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||
if (w) glfwGetWindowContentScale(w, &xscale, &yscale);
|
||||
else glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, &yscale);
|
||||
if (!xscale) xscale = 1.0;
|
||||
if (!yscale) yscale = 1.0;
|
||||
#ifdef __APPLE__
|
||||
double factor = 72.0;
|
||||
#else
|
||||
@@ -345,11 +368,56 @@ set_os_window_dpi(OSWindow *w) {
|
||||
get_window_dpi(w->handle, &w->logical_dpi_x, &w->logical_dpi_y);
|
||||
}
|
||||
|
||||
static bool
|
||||
toggle_fullscreen_for_os_window(OSWindow *w) {
|
||||
int width, height, x, y;
|
||||
glfwGetWindowSize(w->handle, &width, &height);
|
||||
glfwGetWindowPos(w->handle, &x, &y);
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_traditional_fullscreen)) {
|
||||
if (cocoa_toggle_fullscreen(glfwGetCocoaWindow(w->handle), true)) {
|
||||
w->before_fullscreen.is_set = true;
|
||||
w->before_fullscreen.w = width; w->before_fullscreen.h = height; w->before_fullscreen.x = x; w->before_fullscreen.y = y;
|
||||
return true;
|
||||
}
|
||||
if (w->before_fullscreen.is_set) {
|
||||
glfwSetWindowSize(w->handle, w->before_fullscreen.w, w->before_fullscreen.h);
|
||||
glfwSetWindowPos(w->handle, w->before_fullscreen.x, w->before_fullscreen.y);
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return cocoa_toggle_fullscreen(glfwGetCocoaWindow(w->handle), false);
|
||||
}
|
||||
#else
|
||||
GLFWmonitor *monitor;
|
||||
if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) {
|
||||
// make fullscreen
|
||||
monitor = current_monitor(w->handle);
|
||||
if (monitor == NULL) { PyErr_Print(); return false; }
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
w->before_fullscreen.is_set = true;
|
||||
w->before_fullscreen.w = width; w->before_fullscreen.h = height; w->before_fullscreen.x = x; w->before_fullscreen.y = y;
|
||||
glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h);
|
||||
glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y);
|
||||
glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
return true;
|
||||
} else {
|
||||
// make windowed
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate);
|
||||
else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef __APPLE__
|
||||
static int
|
||||
filter_option(int key UNUSED, int mods, unsigned int scancode UNUSED) {
|
||||
return ((mods == GLFW_MOD_ALT) || (mods == (GLFW_MOD_ALT | GLFW_MOD_SHIFT))) ? 1 : 0;
|
||||
}
|
||||
|
||||
static GLFWwindow *application_quit_canary = NULL;
|
||||
|
||||
static int
|
||||
@@ -360,6 +428,14 @@ on_application_reopen(int has_visible_windows) {
|
||||
unjam_event_loop();
|
||||
return false;
|
||||
}
|
||||
|
||||
static int
|
||||
intercept_cocoa_fullscreen(GLFWwindow *w) {
|
||||
if (!OPT(macos_traditional_fullscreen) || !set_callback_window(w)) return 0;
|
||||
toggle_fullscreen_for_os_window(global_state.callback_os_window);
|
||||
global_state.callback_os_window = NULL;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
@@ -399,9 +475,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwWindowHint(GLFW_DEPTH_BITS, 0);
|
||||
glfwWindowHint(GLFW_STENCIL_BITS, 0);
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_hide_titlebar)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
glfwWindowHint(GLFW_COCOA_GRAPHICS_SWITCHING, true);
|
||||
glfwSetApplicationShouldHandleReopen(on_application_reopen);
|
||||
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
#endif
|
||||
|
||||
}
|
||||
@@ -409,9 +485,8 @@ 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);
|
||||
if (OPT(x11_hide_window_decorations)) {
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||
}
|
||||
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
|
||||
if (OPT(hide_window_decorations)) glfwWindowHint(GLFW_DECORATED, false);
|
||||
#endif
|
||||
|
||||
if (global_state.num_os_windows >= MAX_CHILDREN) {
|
||||
@@ -476,6 +551,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
}}
|
||||
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
|
||||
#undef CC
|
||||
if (OPT(click_interval) < 0) OPT(click_interval) = glfwGetDoubleClickInterval(glfw_window);
|
||||
is_first_window = false;
|
||||
}
|
||||
OSWindow *w = add_os_window();
|
||||
@@ -488,12 +564,12 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
w->logical_dpi_x = dpi_x; w->logical_dpi_y = dpi_y;
|
||||
w->fonts_data = fonts_data;
|
||||
current_os_window_ctx = glfw_window;
|
||||
w->shown_once = true;
|
||||
push_focus_history(w);
|
||||
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0);
|
||||
glfwSwapInterval(OPT(sync_to_monitor) && !global_state.is_wayland ? 1 : 0);
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_option_as_alt)) glfwSetCocoaTextInputFilter(glfw_window, filter_option);
|
||||
glfwSetCocoaToggleFullscreenIntercept(glfw_window, intercept_cocoa_fullscreen);
|
||||
#endif
|
||||
send_prerendered_sprites_for_window(w);
|
||||
if (logo.pixels && logo.width && logo.height) glfwSetWindowIcon(glfw_window, 1, &logo);
|
||||
@@ -535,7 +611,6 @@ destroy_os_window(OSWindow *w) {
|
||||
show_mouse_cursor(w->handle);
|
||||
glfwSetCursor(w->handle, NULL);
|
||||
glfwDestroyWindow(w->handle);
|
||||
if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL;
|
||||
}
|
||||
w->handle = NULL;
|
||||
#ifdef __APPLE__
|
||||
@@ -575,6 +650,12 @@ bool
|
||||
application_quit_requested() {
|
||||
return !application_quit_canary || glfwWindowShouldClose(application_quit_canary);
|
||||
}
|
||||
|
||||
void
|
||||
request_application_quit() {
|
||||
if (application_quit_canary)
|
||||
glfwSetWindowShouldClose(application_quit_canary, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Global functions {{{
|
||||
@@ -674,6 +755,24 @@ get_clipboard_string(PYNOARG) {
|
||||
return Py_BuildValue("s", "");
|
||||
}
|
||||
|
||||
void
|
||||
ring_audio_bell(OSWindow *w) {
|
||||
static double last_bell_at = -1;
|
||||
double now = monotonic();
|
||||
if (now - last_bell_at <= 0.1) return;
|
||||
last_bell_at = now;
|
||||
if (w->handle) {
|
||||
glfwWindowBell(w->handle);
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
ring_bell(PYNOARG) {
|
||||
OSWindow *w = current_os_window();
|
||||
ring_audio_bell(w);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_content_scale_for_window(PYNOARG) {
|
||||
OSWindow *w = global_state.callback_os_window ? global_state.callback_os_window : global_state.os_windows;
|
||||
@@ -693,40 +792,22 @@ set_clipboard_string(PyObject UNUSED *self, PyObject *args) {
|
||||
|
||||
static PyObject*
|
||||
toggle_fullscreen(PYNOARG) {
|
||||
GLFWmonitor *monitor;
|
||||
OSWindow *w = current_os_window();
|
||||
if (!w) Py_RETURN_NONE;
|
||||
if ((monitor = glfwGetWindowMonitor(w->handle)) == NULL) {
|
||||
// make fullscreen
|
||||
monitor = current_monitor(w->handle);
|
||||
if (monitor == NULL) return NULL;
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
w->before_fullscreen.is_set = true;
|
||||
glfwGetWindowSize(w->handle, &w->before_fullscreen.w, &w->before_fullscreen.h);
|
||||
glfwGetWindowPos(w->handle, &w->before_fullscreen.x, &w->before_fullscreen.y);
|
||||
glfwSetWindowMonitor(w->handle, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||
Py_RETURN_TRUE;
|
||||
} else {
|
||||
// make windowed
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
if (w->before_fullscreen.is_set) glfwSetWindowMonitor(w->handle, NULL, w->before_fullscreen.x, w->before_fullscreen.y, w->before_fullscreen.w, w->before_fullscreen.h, mode->refreshRate);
|
||||
else glfwSetWindowMonitor(w->handle, NULL, 0, 0, 600, 400, mode->refreshRate);
|
||||
#ifdef __APPLE__
|
||||
if (glfwGetCocoaWindow) cocoa_make_window_resizable(glfwGetCocoaWindow(w->handle), OPT(macos_window_resizable));
|
||||
#endif
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
if (toggle_fullscreen_for_os_window(w)) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
ring_audio_bell(OSWindow *w) {
|
||||
static double last_bell_at = -1;
|
||||
double now = monotonic();
|
||||
if (now - last_bell_at <= 0.1) return;
|
||||
last_bell_at = now;
|
||||
if (w->handle) {
|
||||
glfwWindowBell(w->handle);
|
||||
}
|
||||
static PyObject*
|
||||
change_os_window_state(PyObject *self UNUSED, PyObject *args) {
|
||||
char *state;
|
||||
if (!PyArg_ParseTuple(args, "s", &state)) return NULL;
|
||||
OSWindow *w = current_os_window();
|
||||
if (!w || !w->handle) Py_RETURN_NONE;
|
||||
if (strcmp(state, "maximized") == 0) glfwMaximizeWindow(w->handle);
|
||||
else if (strcmp(state, "minimized") == 0) glfwIconifyWindow(w->handle);
|
||||
else { PyErr_SetString(PyExc_ValueError, "Unknown window state"); return NULL; }
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
@@ -749,9 +830,15 @@ hide_mouse(OSWindow *w) {
|
||||
glfwSetInputMode(w->handle, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||
}
|
||||
|
||||
bool
|
||||
is_mouse_hidden(OSWindow *w) {
|
||||
return w->handle && glfwGetInputMode(w->handle, GLFW_CURSOR) == GLFW_CURSOR_HIDDEN;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
swap_window_buffers(OSWindow *w) {
|
||||
glfwSwapBuffers(w->handle);
|
||||
swap_window_buffers(OSWindow *os_window) {
|
||||
glfwSwapBuffers(os_window->handle);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -821,9 +908,10 @@ x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||
|
||||
static PyObject*
|
||||
get_primary_selection(PYNOARG) {
|
||||
if (glfwGetX11SelectionString) {
|
||||
return Py_BuildValue("y", glfwGetX11SelectionString());
|
||||
} else log_error("Failed to load glfwGetX11SelectionString");
|
||||
if (glfwGetPrimarySelectionString) {
|
||||
OSWindow *w = current_os_window();
|
||||
if (w) return Py_BuildValue("y", glfwGetPrimarySelectionString(w->handle));
|
||||
} else log_error("Failed to load glfwGetPrimarySelectionString");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -831,8 +919,11 @@ static PyObject*
|
||||
set_primary_selection(PyObject UNUSED *self, PyObject *args) {
|
||||
char *text;
|
||||
if (!PyArg_ParseTuple(args, "s", &text)) return NULL;
|
||||
if (glfwSetX11SelectionString) glfwSetX11SelectionString(text);
|
||||
else log_error("Failed to load glfwSetX11SelectionString");
|
||||
if (glfwSetPrimarySelectionString) {
|
||||
OSWindow *w = current_os_window();
|
||||
if (w) glfwSetPrimarySelectionString(w->handle, text);
|
||||
}
|
||||
else log_error("Failed to load glfwSetPrimarySelectionString");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -915,6 +1006,23 @@ get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *coco
|
||||
glfwGetCocoaKeyEquivalent(key, mods, cocoa_key, cocoa_mods);
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
wayland_frame_request_callback(id_type os_window_id) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
if (global_state.os_windows[i].id == os_window_id) {
|
||||
global_state.os_windows[i].wayland_render_state = RENDER_FRAME_READY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
wayland_request_frame_render(OSWindow *w) {
|
||||
glfwRequestWaylandFrameEvent(w->handle, w->id, wayland_frame_request_callback);
|
||||
w->wayland_render_state = RENDER_FRAME_REQUESTED;
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
@@ -924,8 +1032,10 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(set_default_window_icon, METH_VARARGS),
|
||||
METHODB(get_clipboard_string, METH_NOARGS),
|
||||
METHODB(get_content_scale_for_window, METH_NOARGS),
|
||||
METHODB(ring_bell, METH_NOARGS),
|
||||
METHODB(set_clipboard_string, METH_VARARGS),
|
||||
METHODB(toggle_fullscreen, METH_NOARGS),
|
||||
METHODB(change_os_window_state, METH_VARARGS),
|
||||
METHODB(glfw_window_hint, METH_VARARGS),
|
||||
METHODB(os_window_should_close, METH_VARARGS),
|
||||
METHODB(os_window_swap_buffers, METH_VARARGS),
|
||||
@@ -1020,6 +1130,7 @@ init_glfw(PyObject *m) {
|
||||
ADDC(GLFW_KEY_GRAVE_ACCENT);
|
||||
ADDC(GLFW_KEY_WORLD_1);
|
||||
ADDC(GLFW_KEY_WORLD_2);
|
||||
ADDC(GLFW_KEY_PLUS);
|
||||
|
||||
// --- Function keys -----------------------------------------------------------
|
||||
ADDC(GLFW_KEY_ESCAPE);
|
||||
|
||||
@@ -368,7 +368,10 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
if (fd == -1) ABRT(EBADF, "Failed to open file %s for graphics transmission with error: [%d] %s", fname, errno, strerror(errno));
|
||||
img->data_loaded = mmap_img_file(self, img, fd, g->data_sz, g->data_offset);
|
||||
close(fd);
|
||||
if (tt == 't') unlink(fname);
|
||||
if (tt == 't') {
|
||||
if (global_state.boss) { call_boss(safe_delete_temp_file, "s", fname); }
|
||||
else unlink(fname);
|
||||
}
|
||||
else if (tt == 's') shm_unlink(fname);
|
||||
break;
|
||||
default:
|
||||
|
||||
180
kitty/history.c
180
kitty/history.c
@@ -54,14 +54,37 @@ attrptr(HistoryBuf *self, index_type y) {
|
||||
seg_ptr(line_attrs, 1);
|
||||
}
|
||||
|
||||
static inline PagerHistoryBuf*
|
||||
alloc_pagerhist(unsigned int pagerhist_sz) {
|
||||
PagerHistoryBuf *ph;
|
||||
if (!pagerhist_sz) return NULL;
|
||||
ph = PyMem_Calloc(1, sizeof(PagerHistoryBuf));
|
||||
ph->maxsz = pagerhist_sz / sizeof(Py_UCS4);
|
||||
ph->bufsize = 1024*1024 / sizeof(Py_UCS4);
|
||||
ph->buffer = PyMem_RawMalloc(1024*1024);
|
||||
if (!ph->buffer) { PyMem_Free(ph); return NULL; }
|
||||
return ph;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
pagerhist_extend(PagerHistoryBuf *ph, size_t minsz) {
|
||||
if (ph->bufsize >= ph->maxsz) return false;
|
||||
size_t newsz = ph->bufsize + MAX(1024 * 1024, minsz);
|
||||
void *newbuf = PyMem_Realloc(ph->buffer, newsz * sizeof(Py_UCS4));
|
||||
if (!newbuf) return false;
|
||||
ph->buffer = newbuf;
|
||||
ph->bufsize = newsz;
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
HistoryBuf *self;
|
||||
unsigned int xnum = 1, ynum = 1;
|
||||
unsigned int xnum = 1, ynum = 1, pagerhist_sz = 0;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "II|I", &ynum, &xnum, &pagerhist_sz)) return NULL;
|
||||
|
||||
if (xnum * ynum == 0) {
|
||||
if (xnum == 0 || ynum == 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "Cannot create an empty history buffer");
|
||||
return NULL;
|
||||
}
|
||||
@@ -74,9 +97,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
add_segment(self);
|
||||
self->line = alloc_line();
|
||||
self->line->xnum = xnum;
|
||||
for(index_type y = 0; y < self->ynum; y++) {
|
||||
clear_chars_in_line(cpu_lineptr(self, y), gpu_lineptr(self, y), self->xnum, BLANK_CHAR);
|
||||
}
|
||||
self->pagerhist = alloc_pagerhist(pagerhist_sz);
|
||||
}
|
||||
|
||||
return (PyObject*)self;
|
||||
@@ -91,6 +112,8 @@ dealloc(HistoryBuf* self) {
|
||||
PyMem_Free(self->segments[i].line_attrs);
|
||||
}
|
||||
PyMem_Free(self->segments);
|
||||
if (self->pagerhist) PyMem_Free(self->pagerhist->buffer);
|
||||
PyMem_Free(self->pagerhist);
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
}
|
||||
|
||||
@@ -135,12 +158,50 @@ historybuf_clear(HistoryBuf *self) {
|
||||
self->start_of_data = 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
pagerhist_push(HistoryBuf *self) {
|
||||
PagerHistoryBuf *ph = self->pagerhist;
|
||||
if (!ph) return;
|
||||
bool truncated;
|
||||
Line l = {.xnum=self->xnum};
|
||||
init_line(self, self->start_of_data, &l);
|
||||
#define EXPAND_IF_FULL(sz) { \
|
||||
if (ph->bufsize - ph->end < sz && !pagerhist_extend(ph, sz)) { \
|
||||
ph->bufend = ph->end; ph->end = 0; \
|
||||
} \
|
||||
}
|
||||
size_t sz = MAX(1024, ph->bufsize - ph->end);
|
||||
sz = MAX(sz, self->xnum + self->xnum);
|
||||
EXPAND_IF_FULL(sz);
|
||||
if (ph->start != ph->end && !l.continued) {
|
||||
ph->buffer[ph->end++] = '\n';
|
||||
}
|
||||
while(sz < ph->maxsz - 2) {
|
||||
size_t num = line_as_ansi(&l, ph->buffer + ph->end, ph->bufsize - ph->end - 2, &truncated);
|
||||
if (!truncated) {
|
||||
ph->end += num;
|
||||
ph->buffer[ph->end++] = '\r';
|
||||
if (ph->bufend) {
|
||||
ph->start = ph->end + 1 < ph->bufend ? ph->end + 1 : 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// check if sz is too large too fit in buffer
|
||||
if (ph->bufsize > ph->maxsz && !ph->end) break;
|
||||
sz *= 2;
|
||||
EXPAND_IF_FULL(sz);
|
||||
}
|
||||
#undef EXPAND_IF_FULL
|
||||
}
|
||||
|
||||
static inline index_type
|
||||
historybuf_push(HistoryBuf *self) {
|
||||
index_type idx = (self->start_of_data + self->count) % self->ynum;
|
||||
init_line(self, idx, self->line);
|
||||
if (self->count == self->ynum) self->start_of_data = (self->start_of_data + 1) % self->ynum;
|
||||
else self->count++;
|
||||
if (self->count == self->ynum) {
|
||||
pagerhist_push(self);
|
||||
self->start_of_data = (self->start_of_data + 1) % self->ynum;
|
||||
} else self->count++;
|
||||
return idx;
|
||||
}
|
||||
|
||||
@@ -164,7 +225,7 @@ line(HistoryBuf *self, PyObject *val) {
|
||||
|
||||
static PyObject*
|
||||
__str__(HistoryBuf *self) {
|
||||
PyObject *lines = PyTuple_New(self->ynum);
|
||||
PyObject *lines = PyTuple_New(self->count);
|
||||
if (lines == NULL) return PyErr_NoMemory();
|
||||
for (index_type i = 0; i < self->count; i++) {
|
||||
init_line(self, index_of(self, i), self->line);
|
||||
@@ -192,12 +253,13 @@ as_ansi(HistoryBuf *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."
|
||||
static Py_UCS4 t[5120];
|
||||
Line l = {.xnum=self->xnum};
|
||||
bool truncated;
|
||||
for(unsigned int i = 0; i < self->count; i++) {
|
||||
init_line(self, i, &l);
|
||||
if (i < self->count - 1) {
|
||||
l.continued = *attrptr(self, index_of(self, i + 1)) & CONTINUED_MASK;
|
||||
} else l.continued = false;
|
||||
index_type num = line_as_ansi(&l, t, 5120);
|
||||
index_type num = line_as_ansi(&l, t, 5120, &truncated);
|
||||
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
@@ -210,7 +272,94 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
|
||||
}
|
||||
|
||||
static inline Line*
|
||||
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, y, l); return l; }
|
||||
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, index_of(self, self->count - y - 1), l); return l; }
|
||||
|
||||
static void
|
||||
pagerhist_rewrap(PagerHistoryBuf *ph, index_type xnum) {
|
||||
Py_UCS4 *buf = PyMem_RawMalloc(ph->bufsize * sizeof(Py_UCS4));
|
||||
if (!buf) return;
|
||||
index_type s = ph->start, i = s, dest = 0, dest_bufend = 0, x = 0;
|
||||
index_type end = ph->bufend ? ph->bufend : ph->end;
|
||||
index_type lastmod_s = 0, lastmod_len = 0;
|
||||
#define CPY(_s, _l) { if (dest + (_l) >= ph->bufsize - 1) { dest_bufend = dest; dest = 0; } \
|
||||
memcpy(buf + dest, ph->buffer + (_s), (_l) * sizeof(Py_UCS4)); dest += (_l); }
|
||||
while (i < end) {
|
||||
switch (ph->buffer[i]) {
|
||||
case '\n':
|
||||
CPY(s, i - s + 1);
|
||||
x = 0; s = i + 1; lastmod_len = 0;
|
||||
break;
|
||||
case '\r':
|
||||
CPY(s, i - s);
|
||||
if (!memcmp(ph->buffer + lastmod_s, ph->buffer + i + 1, lastmod_len * sizeof(Py_UCS4)))
|
||||
i += lastmod_len;
|
||||
s = i + 1;
|
||||
break;
|
||||
case '\x1b':
|
||||
if (ph->buffer[i+1] != '[') break;
|
||||
lastmod_s = i;
|
||||
while (ph->buffer[++i] != 'm');
|
||||
lastmod_len = i - lastmod_s + 1;
|
||||
break;
|
||||
default:
|
||||
x++; break;
|
||||
}
|
||||
i++;
|
||||
if (ph->bufend && i == ph->bufend) {
|
||||
if (s != i) CPY(s, i - s);
|
||||
end = ph->end; i = s = 0;
|
||||
}
|
||||
if (x == xnum) {
|
||||
CPY(s, i - s); buf[dest++] = '\r'; s = i; x = 0;
|
||||
if (!(ph->buffer[i] == '\x1b' && ph->buffer[i+1] == '[') && lastmod_len)
|
||||
CPY(lastmod_s, lastmod_len);
|
||||
}
|
||||
}
|
||||
#undef CPY
|
||||
PyMem_Free(ph->buffer);
|
||||
ph->buffer = buf;
|
||||
ph->end = dest; ph->bufend = dest_bufend;
|
||||
ph->start = dest_bufend ? dest + 1 : 0;
|
||||
ph->rewrap_needed = false;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
pagerhist_as_text(HistoryBuf *self, PyObject *callback) {
|
||||
PagerHistoryBuf *ph = self->pagerhist;
|
||||
PyObject *ret = NULL, *t = NULL;
|
||||
Py_UCS4 *buf = NULL;
|
||||
index_type num;
|
||||
if (!ph) Py_RETURN_NONE;
|
||||
|
||||
if (ph->rewrap_needed) pagerhist_rewrap(ph, self->xnum);
|
||||
|
||||
#define CALLBACK { \
|
||||
if (t == NULL) goto end; \
|
||||
ret = PyObject_CallFunctionObjArgs(callback, t, NULL); \
|
||||
Py_DECREF(t); \
|
||||
if (ret == NULL) goto end; \
|
||||
Py_DECREF(ret); \
|
||||
}
|
||||
|
||||
num = (ph->bufend ? ph->bufend : ph->end) - ph->start;
|
||||
buf = ph->buffer + ph->start;
|
||||
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
|
||||
CALLBACK;
|
||||
if (ph->bufend) {
|
||||
num = ph->end; buf = ph->buffer;
|
||||
t = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, num);
|
||||
CALLBACK;
|
||||
}
|
||||
Line l = {.xnum=self->xnum}; get_line(self, 0, &l);
|
||||
if (!l.continued) {
|
||||
t = PyUnicode_FromString("\n");
|
||||
CALLBACK;
|
||||
}
|
||||
#undef CALLBACK
|
||||
end:
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_text(HistoryBuf *self, PyObject *args) {
|
||||
@@ -225,7 +374,7 @@ static PyObject*
|
||||
dirty_lines(HistoryBuf *self, PyObject *a UNUSED) {
|
||||
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
||||
PyObject *ans = PyList_New(0);
|
||||
for (index_type i = 0; i < self->ynum; i++) {
|
||||
for (index_type i = 0; i < self->count; i++) {
|
||||
if (*attrptr(self, i) & TEXT_DIRTY_MASK) {
|
||||
PyList_Append(ans, PyLong_FromUnsignedLong(i));
|
||||
}
|
||||
@@ -241,6 +390,7 @@ static PyObject* rewrap(HistoryBuf *self, PyObject *args);
|
||||
static PyMethodDef methods[] = {
|
||||
METHOD(line, METH_O)
|
||||
METHOD(as_ansi, METH_O)
|
||||
METHODB(pagerhist_as_text, METH_O),
|
||||
METHODB(as_text, METH_VARARGS),
|
||||
METHOD(dirty_lines, METH_NOARGS)
|
||||
METHOD(push, METH_VARARGS)
|
||||
@@ -270,8 +420,8 @@ PyTypeObject HistoryBuf_Type = {
|
||||
|
||||
INIT_TYPE(HistoryBuf)
|
||||
|
||||
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
|
||||
return (HistoryBuf*)new(&HistoryBuf_Type, Py_BuildValue("II", lines, columns), NULL);
|
||||
HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns, unsigned int pagerhist_sz) {
|
||||
return (HistoryBuf*)new(&HistoryBuf_Type, Py_BuildValue("III", lines, columns, pagerhist_sz), NULL);
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -301,6 +451,8 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
|
||||
other->count = self->count; other->start_of_data = self->start_of_data;
|
||||
return;
|
||||
}
|
||||
if (other->pagerhist && other->xnum != self->xnum && other->pagerhist->end != other->pagerhist->start)
|
||||
other->pagerhist->rewrap_needed = true;
|
||||
other->count = 0; other->start_of_data = 0;
|
||||
index_type x = 0, y = 0;
|
||||
if (self->count > 0) {
|
||||
|
||||
47
kitty/key_encoding.py
generated
47
kitty/key_encoding.py
generated
@@ -127,7 +127,8 @@ ENCODING = {
|
||||
'WORLD 2': 'x',
|
||||
'X': 'p',
|
||||
'Y': 'q',
|
||||
'Z': 'r'
|
||||
'Z': 'r',
|
||||
'PLUS': 'Bi'
|
||||
}
|
||||
KEY_MAP = {
|
||||
32: 'A',
|
||||
@@ -180,6 +181,7 @@ KEY_MAP = {
|
||||
96: 'v',
|
||||
161: 'w',
|
||||
162: 'x',
|
||||
163: 'Bi',
|
||||
256: 'y',
|
||||
257: 'z',
|
||||
258: '0',
|
||||
@@ -250,10 +252,8 @@ KEY_MAP = {
|
||||
346: 'Bg',
|
||||
347: 'Bh'
|
||||
}
|
||||
|
||||
# END_ENCODING }}}
|
||||
|
||||
|
||||
text_keys = string.ascii_uppercase + string.ascii_lowercase + string.digits + '`~!@#$%^&*()_-+=[{]}\\|<,>./?;:\'" '
|
||||
|
||||
|
||||
@@ -324,28 +324,35 @@ mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
|
||||
key_rmap = {}
|
||||
g = globals()
|
||||
config_key_map = {}
|
||||
config_mod_map = {'SHIFT': SHIFT, 'ALT': ALT, 'OPTION': ALT, '⌥': ALT, '⌘': SUPER, 'CMD': SUPER, 'SUPER': SUPER, 'CTRL': CTRL, 'CONTROL': CTRL}
|
||||
config_mod_map = {
|
||||
'SHIFT': SHIFT,
|
||||
'ALT': ALT,
|
||||
'OPTION': ALT,
|
||||
'⌥': ALT,
|
||||
'⌘': SUPER,
|
||||
'CMD': SUPER,
|
||||
'SUPER': SUPER,
|
||||
'CTRL': CTRL,
|
||||
'CONTROL': CTRL
|
||||
}
|
||||
for key_name, enc in ENCODING.items():
|
||||
key_name = key_name.replace(' ', '_')
|
||||
g[key_name] = config_key_map[key_name] = key_name
|
||||
key_rmap[enc] = key_name
|
||||
config_key_map.update({
|
||||
'`': g['GRAVE_ACCENT'],
|
||||
'-': g['MINUS'],
|
||||
'=': g['EQUAL'],
|
||||
|
||||
'[': g['LEFT_BRACKET'],
|
||||
']': g['RIGHT_BRACKET'],
|
||||
'\\': g['BACKSLASH'],
|
||||
|
||||
';': g['SEMICOLON'],
|
||||
"'": g['APOSTROPHE'],
|
||||
|
||||
',': g['COMMA'],
|
||||
'.': g['PERIOD'],
|
||||
'/': g['SLASH'],
|
||||
|
||||
'ESC': g['ESCAPE'],
|
||||
'`': g['GRAVE_ACCENT'],
|
||||
'-': g['MINUS'],
|
||||
'=': g['EQUAL'],
|
||||
'[': g['LEFT_BRACKET'],
|
||||
']': g['RIGHT_BRACKET'],
|
||||
'\\': g['BACKSLASH'],
|
||||
';': g['SEMICOLON'],
|
||||
"'": g['APOSTROPHE'],
|
||||
',': g['COMMA'],
|
||||
'.': g['PERIOD'],
|
||||
'/': g['SLASH'],
|
||||
'ESC': g['ESCAPE'],
|
||||
'+': g['PLUS'],
|
||||
})
|
||||
|
||||
enter_key = KeyEvent(PRESS, 0, g['ENTER'])
|
||||
|
||||
18
kitty/keys.c
18
kitty/keys.c
@@ -103,13 +103,9 @@ check_if_special(int key, int mods, int scancode) {
|
||||
qkey = SPECIAL_INDEX(qkey);
|
||||
special = needs_special_handling[qkey];
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
(void)scancode;
|
||||
#else
|
||||
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
|
||||
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
|
||||
}
|
||||
#endif
|
||||
for (size_t i = 0; !special && i < native_special_keys_count; i++) {
|
||||
if (scancode == native_special_keys[i].scancode && mods == native_special_keys[i].mods) special = true;
|
||||
}
|
||||
return special;
|
||||
}
|
||||
|
||||
@@ -127,7 +123,7 @@ update_ime_position(OSWindow *os_window, Window* w, Screen *screen) {
|
||||
void
|
||||
on_key_input(int key, int scancode, int action, int mods, const char* text, int state) {
|
||||
Window *w = active_window();
|
||||
debug("on_key_input: glfw key: %d native_code: %d action: %s mods: 0x%x text: '%s' state: %d ",
|
||||
debug("on_key_input: glfw key: %d native_code: 0x%x action: %s mods: 0x%x text: '%s' state: %d ",
|
||||
key, scancode,
|
||||
(action == GLFW_RELEASE ? "RELEASE" : (action == GLFW_PRESS ? "PRESS" : "REPEAT")),
|
||||
mods, text, state);
|
||||
@@ -222,15 +218,13 @@ PYWRAP1(key_for_native_key_name) {
|
||||
const char *name;
|
||||
int case_sensitive = 0;
|
||||
PA("s|p", &name, case_sensitive);
|
||||
#ifdef __APPLE__
|
||||
Py_RETURN_NONE;
|
||||
#else
|
||||
#ifndef __APPLE__
|
||||
if (glfwGetXKBScancode) { // if this function is called before GLFW is initialized glfwGetXKBScancode will be NULL
|
||||
int scancode = glfwGetXKBScancode(name, case_sensitive);
|
||||
if (scancode) return Py_BuildValue("i", scancode);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
#endif
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
|
||||
12043
kitty/keys.h
generated
12043
kitty/keys.h
generated
File diff suppressed because it is too large
Load Diff
@@ -6,23 +6,14 @@ import string
|
||||
|
||||
from . import fast_data_types as defines
|
||||
from .key_encoding import KEY_MAP
|
||||
from .terminfo import key_as_bytes
|
||||
from .terminfo import key_as_bytes, modify_key_bytes
|
||||
from .utils import base64_encode
|
||||
from .constants import is_macos
|
||||
|
||||
|
||||
def modify_key_bytes(keybytes, amt):
|
||||
ans = bytearray(keybytes)
|
||||
amt = str(amt).encode('ascii')
|
||||
if ans[-1] == ord('~'):
|
||||
return bytes(ans[:-1] + bytearray(b';' + amt + b'~'))
|
||||
if ans[1] == ord('O'):
|
||||
return bytes(ans[:1] + bytearray(b'[1;' + amt) + ans[-1:])
|
||||
raise ValueError('Unknown key type')
|
||||
|
||||
|
||||
def modify_complex_key(name, amt):
|
||||
return modify_key_bytes(key_as_bytes(name), amt)
|
||||
if not isinstance(name, bytes):
|
||||
name = key_as_bytes(name)
|
||||
return modify_key_bytes(name, amt)
|
||||
|
||||
|
||||
control_codes = {
|
||||
@@ -40,6 +31,7 @@ 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)
|
||||
ctrl_shift_mod = defines.GLFW_MOD_SHIFT | defines.GLFW_MOD_CONTROL
|
||||
ctrl_alt_mod = defines.GLFW_MOD_ALT | defines.GLFW_MOD_CONTROL
|
||||
ctrl_alt_shift_mod = ctrl_alt_mod | defines.GLFW_MOD_SHIFT
|
||||
SHIFTED_KEYS = {
|
||||
defines.GLFW_KEY_TAB: key_as_bytes('kcbt'),
|
||||
defines.GLFW_KEY_HOME: key_as_bytes('kHOM'),
|
||||
@@ -55,6 +47,32 @@ SHIFTED_KEYS = {
|
||||
control_alt_codes = {
|
||||
defines.GLFW_KEY_SPACE: b'\x1b\0',
|
||||
}
|
||||
control_alt_shift_codes = {}
|
||||
ASCII_C0_SHIFTED = {
|
||||
# ^@
|
||||
'2': b'\x00',
|
||||
# ^^
|
||||
'6': b'\x1e',
|
||||
# ^_
|
||||
'MINUS': b'\x1f',
|
||||
# ^?
|
||||
'SLASH': b'\x7f',
|
||||
}
|
||||
control_shift_keys = {getattr(defines, 'GLFW_KEY_' + k): v for k, v in ASCII_C0_SHIFTED.items()}
|
||||
|
||||
|
||||
def create_modifier_variants(keycode, terminfo_name_or_bytes, add_shifted_key=True):
|
||||
kn = terminfo_name_or_bytes
|
||||
smkx_key_map[keycode] = kn if isinstance(kn, bytes) else key_as_bytes(kn)
|
||||
if add_shifted_key:
|
||||
SHIFTED_KEYS[keycode] = modify_complex_key(kn, 2)
|
||||
alt_codes[keycode] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[keycode] = modify_complex_key(kn, 4)
|
||||
control_codes[keycode] = modify_complex_key(kn, 5)
|
||||
control_shift_keys[keycode] = modify_complex_key(kn, 6)
|
||||
control_alt_codes[keycode] = modify_complex_key(kn, 7)
|
||||
control_alt_shift_codes[keycode] = modify_complex_key(kn, 8)
|
||||
|
||||
|
||||
for kf, kn in {
|
||||
defines.GLFW_KEY_UP: 'kcuu1',
|
||||
@@ -68,24 +86,16 @@ for kf, kn in {
|
||||
defines.GLFW_KEY_PAGE_UP: 'kpp',
|
||||
defines.GLFW_KEY_PAGE_DOWN: 'knp',
|
||||
}.items():
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
control_alt_codes[kf] = modify_complex_key(kn, 7)
|
||||
create_modifier_variants(kf, kn, add_shifted_key=False)
|
||||
for f in range(1, 13):
|
||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||
kn = 'kf{}'.format(f)
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
SHIFTED_KEYS[kf] = modify_complex_key(kn, 2)
|
||||
alt_codes[kf] = modify_complex_key(kn, 3)
|
||||
shift_alt_codes[kf] = modify_complex_key(kn, 4)
|
||||
control_codes[kf] = modify_complex_key(kn, 5)
|
||||
control_alt_codes[kf] = modify_complex_key(kn, 7)
|
||||
create_modifier_variants(kf, kn)
|
||||
for f in range(13, 26):
|
||||
kf = getattr(defines, 'GLFW_KEY_F{}'.format(f))
|
||||
kn = 'kf{}'.format(f)
|
||||
smkx_key_map[kf] = key_as_bytes(kn)
|
||||
create_modifier_variants(defines.GLFW_KEY_MENU, b'\x1b[29~')
|
||||
f = {k: k for k in '0123456789'}
|
||||
f.update({
|
||||
'COMMA': ',',
|
||||
@@ -216,17 +226,6 @@ 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()}
|
||||
CTRL_ALT_KEYS = {getattr(defines, 'GLFW_KEY_' + k) for k in string.ascii_uppercase}
|
||||
all_control_alt_keys = set(CTRL_ALT_KEYS) | set(control_alt_codes)
|
||||
|
||||
@@ -238,8 +237,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
|
||||
if mods == defines.GLFW_MOD_CONTROL and key in control_codes:
|
||||
# Map Ctrl-key to ascii control code
|
||||
data.extend(control_codes[key])
|
||||
elif mods == ctrl_shift_mod and key in CTRL_SHIFT_KEYS:
|
||||
data.extend(CTRL_SHIFT_KEYS[key])
|
||||
elif mods == ctrl_shift_mod and key in control_shift_keys:
|
||||
data.extend(control_shift_keys[key])
|
||||
elif mods in alt_mods:
|
||||
if key in alt_codes:
|
||||
data.extend((alt_codes if mods == defines.GLFW_MOD_ALT else shift_alt_codes)[key])
|
||||
@@ -252,6 +251,8 @@ def key_to_bytes(key, smkx, extended, mods, action):
|
||||
data.append(0x1b), data.extend(control_codes[key])
|
||||
else:
|
||||
data.extend(control_alt_codes[key])
|
||||
elif mods == ctrl_alt_shift_mod and key in control_alt_shift_codes:
|
||||
data.extend(control_alt_shift_codes[key])
|
||||
else:
|
||||
key_map = cursor_key_mode_map[smkx]
|
||||
x = key_map.get(key)
|
||||
@@ -276,7 +277,7 @@ def interpret_key_event(key, scancode, mods, window, action):
|
||||
def get_shortcut(keymap, mods, key, scancode):
|
||||
mods &= 0b1111
|
||||
ans = keymap.get((mods, False, key))
|
||||
if ans is None and not is_macos:
|
||||
if ans is None:
|
||||
ans = keymap.get((mods, True, scancode))
|
||||
return ans
|
||||
|
||||
|
||||
103
kitty/layout.py
103
kitty/layout.py
@@ -228,15 +228,34 @@ class Layout: # {{{
|
||||
active_window_idx = idx_for_id(windows[active_window_idx].id, all_windows)
|
||||
return self.set_active_window(all_windows, active_window_idx)
|
||||
|
||||
def move_window(self, all_windows, active_window_idx, delta=1):
|
||||
def neighbors(self, all_windows, active_window_idx):
|
||||
w = all_windows[active_window_idx]
|
||||
windows = process_overlaid_windows(all_windows)[1]
|
||||
if len(windows) < 2 or abs(delta) == 0:
|
||||
ans = self.neighbors_for_window(w, windows)
|
||||
for values in ans.values():
|
||||
values[:] = [idx_for_id(w.id, all_windows) for w in values]
|
||||
return ans
|
||||
|
||||
def move_window(self, all_windows, active_window_idx, delta=1):
|
||||
# delta can be either a number or a string such as 'left', 'top', etc
|
||||
# for neighborhood moves
|
||||
w = all_windows[active_window_idx]
|
||||
windows = process_overlaid_windows(all_windows)[1]
|
||||
if len(windows) < 2 or not delta:
|
||||
return active_window_idx
|
||||
idx = idx_for_id(w.id, windows)
|
||||
if idx is None:
|
||||
idx = idx_for_id(w.overlay_window_id, windows)
|
||||
nidx = (idx + len(windows) + delta) % len(windows)
|
||||
if isinstance(delta, int):
|
||||
nidx = (idx + len(windows) + delta) % len(windows)
|
||||
else:
|
||||
delta = delta.lower()
|
||||
delta = {'up': 'top', 'down': 'bottom'}.get(delta, delta)
|
||||
neighbors = self.neighbors_for_window(w, windows)
|
||||
if not neighbors.get(delta):
|
||||
return active_window_idx
|
||||
nidx = idx_for_id(neighbors[delta][0].id, windows)
|
||||
|
||||
nw = windows[nidx]
|
||||
nidx = idx_for_id(nw.id, all_windows)
|
||||
idx = active_window_idx
|
||||
@@ -368,6 +387,9 @@ class Layout: # {{{
|
||||
def do_layout(self, windows, active_window_idx):
|
||||
raise NotImplementedError()
|
||||
|
||||
def neighbors_for_window(self, window, windows):
|
||||
return {'left': [], 'right': [], 'top': [], 'bottom': []}
|
||||
|
||||
def resolve_borders(self, windows, active_window):
|
||||
if draw_minimal_borders:
|
||||
needs_borders_map = {w.id: (w is active_window or w.needs_attention) for w in windows}
|
||||
@@ -469,6 +491,13 @@ class Tall(Layout): # {{{
|
||||
# left bottom blank rect
|
||||
self.bottom_blank_rect(windows[0])
|
||||
|
||||
def neighbors_for_window(self, window, windows):
|
||||
if window is windows[0]:
|
||||
return {'left': [], 'right': windows[1:], 'top': [], 'bottom': []}
|
||||
idx = windows.index(window)
|
||||
return {'left': [windows[0]], 'right': [], 'top': [] if idx <= 1 else [windows[idx-1]],
|
||||
'bottom': [] if window is windows[-1] else [windows[idx+1]]}
|
||||
|
||||
def minimal_borders(self, windows, active_window, needs_borders_map):
|
||||
last_i = len(windows) - 1
|
||||
for i, w in enumerate(windows):
|
||||
@@ -520,6 +549,14 @@ class Fat(Tall): # {{{
|
||||
self.bottom_blank_rect(windows[0])
|
||||
# bottom blank rect
|
||||
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
|
||||
|
||||
def neighbors_for_window(self, window, windows):
|
||||
if window is windows[0]:
|
||||
return {'left': [], 'bottom': windows[1:], 'top': [], 'right': []}
|
||||
idx = windows.index(window)
|
||||
return {'top': [windows[0]], 'bottom': [], 'left': [] if idx <= 1 else [windows[idx-1]],
|
||||
'right': [] if window is windows[-1] else [windows[idx+1]]}
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -680,6 +717,56 @@ class Grid(Layout): # {{{
|
||||
(right_neighbor_id is not None and not needs_borders_map[right_neighbor_id]) or next_col_has_different_count,
|
||||
bottom_neighbor_id is not None and not needs_borders_map[bottom_neighbor_id]
|
||||
)
|
||||
|
||||
def neighbors_for_window(self, window, windows):
|
||||
n = len(windows)
|
||||
if n < 4:
|
||||
return Tall.neighbors_for_window(self, window, windows)
|
||||
try:
|
||||
n, ncols, nrows, special_rows, special_col = windows[0].layout_data
|
||||
except Exception:
|
||||
n = -1
|
||||
if n != len(windows):
|
||||
# Something bad happened
|
||||
return Layout.neighbors_for_window(self, window, windows)
|
||||
|
||||
blank_row = [None for i in range(ncols)]
|
||||
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
|
||||
wi = iter(windows)
|
||||
pos_map = {}
|
||||
col_counts = []
|
||||
id_map = {}
|
||||
for col in range(ncols):
|
||||
rows = special_rows if col == special_col else nrows
|
||||
for row in range(rows):
|
||||
w = next(wi)
|
||||
matrix[row][col] = wid = w.id
|
||||
pos_map[wid] = row, col
|
||||
id_map[wid] = w
|
||||
col_counts.append(rows)
|
||||
row, col = pos_map[window.id]
|
||||
|
||||
def neighbors(row, col):
|
||||
try:
|
||||
ans = matrix[row][col]
|
||||
except IndexError:
|
||||
ans = None
|
||||
return [] if ans is None else [id_map[ans]]
|
||||
|
||||
def side(row, col, delta):
|
||||
neighbor_col = col + delta
|
||||
if col_counts[neighbor_col] == col_counts[col]:
|
||||
return neighbors(row, neighbor_col)
|
||||
return neighbors(min(row, col_counts[neighbor_col] - 1), neighbor_col)
|
||||
|
||||
return {
|
||||
'top': neighbors(row-1, col) if row else [],
|
||||
'bottom': neighbors(row + 1, col),
|
||||
'left': side(row, col, -1) if col else [],
|
||||
'right': side(row, col, 1) if col < ncols - 1 else [],
|
||||
}
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -740,6 +827,15 @@ class Vertical(Layout): # {{{
|
||||
yield no_borders
|
||||
else:
|
||||
yield self.only_between_border
|
||||
|
||||
def neighbors_for_window(self, window, windows):
|
||||
idx = windows.index(window)
|
||||
before = [] if window is windows[0] else [windows[idx-1]]
|
||||
after = [] if window is windows[-1] else [windows[idx+1]]
|
||||
if self.main_is_horizontal:
|
||||
return {'left': before, 'right': after, 'top': [], 'bottom': []}
|
||||
return {'top': before, 'bottom': after, 'left': [], 'right': []}
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -768,6 +864,7 @@ class Horizontal(Vertical): # {{{
|
||||
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))
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
|
||||
@@ -397,16 +397,17 @@ as_ansi(LineBuf *self, PyObject *callback) {
|
||||
Line l = {.xnum=self->xnum};
|
||||
// remove trailing empty lines
|
||||
index_type ylimit = self->ynum - 1;
|
||||
bool truncated;
|
||||
do {
|
||||
init_line(self, (&l), self->line_map[ylimit]);
|
||||
if (line_as_ansi(&l, t, 5120) != 0) break;
|
||||
if (line_as_ansi(&l, t, 5120, &truncated) != 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;
|
||||
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, &truncated);
|
||||
if (!(l.continued) && num < 5119) t[num++] = 10; // 10 = \n
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
|
||||
117
kitty/line.c
117
kitty/line.c
@@ -50,18 +50,25 @@ cell_text(CPUCell *cell) {
|
||||
|
||||
static const char* url_prefixes[4] = {"https", "http", "file", "ftp"};
|
||||
static size_t url_prefix_lengths[sizeof(url_prefixes)/sizeof(url_prefixes[0])] = {0};
|
||||
typedef enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH} URL_PARSER_STATE;
|
||||
|
||||
static inline index_type
|
||||
find_colon_slash(Line *self, index_type x, index_type limit) {
|
||||
// Find :// at or before x
|
||||
index_type pos = x;
|
||||
URL_PARSER_STATE state = ANY;
|
||||
enum URL_PARSER_STATES {ANY, FIRST_SLASH, SECOND_SLASH};
|
||||
enum URL_PARSER_STATES state = ANY;
|
||||
limit = MAX(2, limit);
|
||||
if (pos < limit) return 0;
|
||||
do {
|
||||
char_type ch = self->cpu_cells[pos].ch;
|
||||
if (!is_url_char(ch)) return false;
|
||||
if (pos == x) {
|
||||
if (ch == ':') {
|
||||
if (pos + 2 < self->xnum && self->cpu_cells[pos+1].ch == '/' && self->cpu_cells[pos + 2].ch == '/') state = SECOND_SLASH;
|
||||
} else if (ch == '/') {
|
||||
if (pos + 1 < self->xnum && self->cpu_cells[pos+1].ch == '/') state = FIRST_SLASH;
|
||||
}
|
||||
}
|
||||
switch(state) {
|
||||
case ANY:
|
||||
if (ch == '/') state = FIRST_SLASH;
|
||||
@@ -227,50 +234,53 @@ write_sgr(const char *val, Py_UCS4 *buf, index_type buflen, index_type *i) {
|
||||
}
|
||||
|
||||
index_type
|
||||
line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen) {
|
||||
#define WRITE_SGR(val) { if (!write_sgr(val, buf, buflen, &i)) return i; }
|
||||
#define WRITE_CH(val) if (i > buflen - 1) return i; buf[i++] = val;
|
||||
line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen, bool *truncated) {
|
||||
#define WRITE_SGR(val) { if (!write_sgr(val, buf, buflen, &i)) { *truncated = true; return i; } }
|
||||
#define WRITE_CH(val) if (i > buflen - 1) { *truncated = true; return i; } buf[i++] = val;
|
||||
|
||||
index_type limit = xlimit_for_line(self), i=0;
|
||||
*truncated = false;
|
||||
if (limit == 0) return 0;
|
||||
char_type previous_width = 0;
|
||||
|
||||
WRITE_SGR("0");
|
||||
Cursor c1 = {{0}}, c2 = {{0}};
|
||||
Cursor *cursor = &c1, *prev_cursor = &c2, *t;
|
||||
GPUCell blank_cell = { 0 };
|
||||
GPUCell *cell, *prev_cell = &blank_cell;
|
||||
|
||||
for (index_type pos=0; pos < limit; pos++) {
|
||||
char_type attrs = self->gpu_cells[pos].attrs, ch = self->cpu_cells[pos].ch;
|
||||
char_type ch = self->cpu_cells[pos].ch;
|
||||
if (ch == 0) {
|
||||
if (previous_width == 2) { previous_width = 0; continue; }
|
||||
ch = ' ';
|
||||
}
|
||||
ATTRS_TO_CURSOR(attrs, cursor);
|
||||
cursor->fg = self->gpu_cells[pos].fg; cursor->bg = self->gpu_cells[pos].bg;
|
||||
cursor->decoration_fg = self->gpu_cells[pos].decoration_fg & COL_MASK;
|
||||
|
||||
const char *sgr = cursor_as_sgr(cursor, prev_cursor);
|
||||
t = prev_cursor; prev_cursor = cursor; cursor = t;
|
||||
if (*sgr) WRITE_SGR(sgr);
|
||||
cell = &self->gpu_cells[pos];
|
||||
|
||||
#define CMP_ATTRS (cell->attrs & ATTRS_MASK_WITHOUT_WIDTH) != (prev_cell->attrs & ATTRS_MASK_WITHOUT_WIDTH)
|
||||
#define CMP(x) cell->x != prev_cell->x
|
||||
if (CMP_ATTRS || CMP(fg) || CMP(bg) || CMP(decoration_fg)) {
|
||||
const char *sgr = cell_as_sgr(cell, prev_cell);
|
||||
if (*sgr) WRITE_SGR(sgr);
|
||||
}
|
||||
prev_cell = cell;
|
||||
WRITE_CH(ch);
|
||||
for(unsigned c = 0; c < arraysz(self->cpu_cells[pos].cc_idx) && self->cpu_cells[pos].cc_idx[c]; c++) {
|
||||
WRITE_CH(codepoint_for_mark(self->cpu_cells[pos].cc_idx[c]));
|
||||
}
|
||||
previous_width = attrs & WIDTH_MASK;
|
||||
previous_width = cell->attrs & WIDTH_MASK;
|
||||
}
|
||||
return i;
|
||||
#undef CHECK_BOOL
|
||||
#undef CHECK_COLOR
|
||||
#undef CMP_ATTRS
|
||||
#undef CMP
|
||||
#undef WRITE_SGR
|
||||
#undef WRITE_CH
|
||||
#undef WRITE_COLOR
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
as_ansi(Line* self, PyObject *a UNUSED) {
|
||||
#define as_ansi_doc "Return the line's contents with ANSI (SGR) escape codes for formatting"
|
||||
static Py_UCS4 t[5120] = {0};
|
||||
index_type num = line_as_ansi(self, t, 5120);
|
||||
bool truncated;
|
||||
index_type num = line_as_ansi(self, t, 5120, &truncated);
|
||||
PyObject *ans = PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, t, num);
|
||||
return ans;
|
||||
}
|
||||
@@ -527,6 +537,73 @@ set_attribute(Line *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline int
|
||||
color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsigned aix_code, unsigned complex_code) {
|
||||
switch(val & 0xff) {
|
||||
case 1:
|
||||
val >>= 8;
|
||||
if (val < 16 && simple_code) {
|
||||
return snprintf(buf, sz, "%lu;", (val < 8) ? simple_code + val : aix_code + (val - 8));
|
||||
}
|
||||
return snprintf(buf, sz, "%u:5:%lu;", complex_code, val);
|
||||
case 2:
|
||||
return snprintf(buf, sz, "%u:2:%lu:%lu:%lu;", complex_code, (val >> 24) & 0xff, (val >> 16) & 0xff, (val >> 8) & 0xff);
|
||||
default:
|
||||
return snprintf(buf, sz, "%u;", complex_code + 1); // reset
|
||||
}
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
decoration_as_sgr(uint8_t decoration) {
|
||||
switch(decoration) {
|
||||
case 1: return "4;";
|
||||
case 2: return "4:2;";
|
||||
case 3: return "4:3;";
|
||||
default: return "24;";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
cell_as_sgr(GPUCell *cell, GPUCell *prev) {
|
||||
static char buf[128];
|
||||
#define SZ sizeof(buf) - (p - buf) - 2
|
||||
#define P(s) { size_t len = strlen(s); if (SZ > len) { memcpy(p, s, len); p += len; } }
|
||||
char *p = buf;
|
||||
#define CMP(attr) (attr(cell) != attr(prev))
|
||||
#define BOLD(cell) (cell->attrs & (1 << BOLD_SHIFT))
|
||||
#define DIM(cell) (cell->attrs & (1 << DIM_SHIFT))
|
||||
#define ITALIC(cell) (cell->attrs & (1 << ITALIC_SHIFT))
|
||||
#define REVERSE(cell) (cell->attrs & (1 << REVERSE_SHIFT))
|
||||
#define STRIKETHROUGH(cell) (cell->attrs & (1 << STRIKE_SHIFT))
|
||||
#define DECORATION(cell) (cell->attrs & (DECORATION_MASK << DECORATION_SHIFT))
|
||||
bool intensity_differs = CMP(BOLD) || CMP(DIM);
|
||||
if (intensity_differs) {
|
||||
if (!BOLD(cell) && !DIM(cell)) { P("22;"); }
|
||||
else { if (BOLD(cell)) P("1;"); if (DIM(cell)) P("2;"); }
|
||||
}
|
||||
if (CMP(ITALIC)) P(ITALIC(cell) ? "3;" : "23;");
|
||||
if (CMP(REVERSE)) P(REVERSE(cell) ? "7;" : "27;");
|
||||
if (CMP(STRIKETHROUGH)) P(STRIKETHROUGH(cell) ? "9;" : "29;");
|
||||
if (cell->fg != prev->fg) p += color_as_sgr(p, SZ, cell->fg, 30, 90, 38);
|
||||
if (cell->bg != prev->bg) p += color_as_sgr(p, SZ, cell->bg, 40, 100, 48);
|
||||
if (cell->decoration_fg != prev->decoration_fg) p += color_as_sgr(p, SZ, cell->decoration_fg, 0, 0, DECORATION_FG_CODE);
|
||||
if (CMP(DECORATION)) P(decoration_as_sgr((cell->attrs >> DECORATION_SHIFT) & DECORATION_MASK));
|
||||
#undef CMP
|
||||
#undef BOLD
|
||||
#undef DIM
|
||||
#undef ITALIC
|
||||
#undef REVERSE
|
||||
#undef STRIKETHROUGH
|
||||
#undef DECORATION
|
||||
#undef P
|
||||
#undef SZ
|
||||
if (p > buf) *(p - 1) = 0; // remove trailing semi-colon
|
||||
*p = 0; // ensure string is null-terminated
|
||||
return buf;
|
||||
}
|
||||
|
||||
|
||||
static Py_ssize_t
|
||||
__len__(PyObject *self) {
|
||||
return (Py_ssize_t)(((Line*)self)->xnum);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user