Compare commits
382 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a4420f2d3 | ||
|
|
1eb8d6e845 | ||
|
|
e2f97dc44a | ||
|
|
ea9af96d2b | ||
|
|
e4033d997c | ||
|
|
bc38357193 | ||
|
|
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 | ||
|
|
4f05024550 | ||
|
|
4d2d9d5574 | ||
|
|
14f44e79f7 | ||
|
|
ab960ea12d | ||
|
|
d276317e3a | ||
|
|
8b3b0064a9 | ||
|
|
66b890bfd3 | ||
|
|
2454ecd586 | ||
|
|
b891f4f874 | ||
|
|
92d06b3088 | ||
|
|
3067103b18 | ||
|
|
0e248b3faa | ||
|
|
d1a76bbcd9 | ||
|
|
1d91edfb00 | ||
|
|
cf7ecd5e66 | ||
|
|
63a2ecb89c | ||
|
|
8e6b58b1db | ||
|
|
0f594ef950 | ||
|
|
0c1b76c17a | ||
|
|
1c11ba8dbd | ||
|
|
529d5b2226 | ||
|
|
9fe16867c6 | ||
|
|
1d275c6b35 | ||
|
|
d19b29ced0 | ||
|
|
bbefcb2cd2 | ||
|
|
c864cba4a2 | ||
|
|
42546f4371 | ||
|
|
ff09bdcbf1 | ||
|
|
f36a9fffa3 | ||
|
|
933cced76c | ||
|
|
c51e7d3bde | ||
|
|
d3b31224cd | ||
|
|
ec9bb2302c | ||
|
|
009ef54de7 | ||
|
|
0e0e25f986 | ||
|
|
8b02b89bd6 | ||
|
|
8ba3c4ba50 | ||
|
|
16764c79b2 | ||
|
|
96c444d041 | ||
|
|
ec1c72e9a3 | ||
|
|
f9b1f6be5c | ||
|
|
9201d27875 | ||
|
|
ecbe79e04f | ||
|
|
6e34a7559b | ||
|
|
ea8b17565d | ||
|
|
a143faaf05 | ||
|
|
acdb56ad5d | ||
|
|
1f90238b18 | ||
|
|
89b636376e | ||
|
|
59f39ae3ab | ||
|
|
a92b20f85a | ||
|
|
e71c432b7c | ||
|
|
b6a5d09d4f | ||
|
|
9235d2b8e4 | ||
|
|
605847fd75 | ||
|
|
6222273adc | ||
|
|
6a759ea7d5 | ||
|
|
d8a7114dba | ||
|
|
ab818ed89f | ||
|
|
1bb6540b64 | ||
|
|
ddd8209c45 | ||
|
|
1dc3dfe58f | ||
|
|
634d262b64 | ||
|
|
d964146f8c | ||
|
|
05b817f5f5 | ||
|
|
eb2ec1833c | ||
|
|
094ddd9333 | ||
|
|
e05d48a574 | ||
|
|
927bfcd37a | ||
|
|
42cabace47 | ||
|
|
9a1f14d05c | ||
|
|
4189aa8f14 | ||
|
|
5fa5bf8142 | ||
|
|
000c1cf306 | ||
|
|
28447d3389 | ||
|
|
ab7f3310c4 | ||
|
|
e5a720c6fa | ||
|
|
8d20dbe81d | ||
|
|
1fc666d82c | ||
|
|
1b2a1a1e77 | ||
|
|
9af3081f45 | ||
|
|
78def6e6bf | ||
|
|
b4269199b4 | ||
|
|
cb289125ab | ||
|
|
a4e3ce31dd | ||
|
|
8a725d3d3e | ||
|
|
c48fa0f078 | ||
|
|
31aa782362 | ||
|
|
892637683c | ||
|
|
1cf6f28fe8 | ||
|
|
29f9394257 | ||
|
|
8fd92df7ae | ||
|
|
374f7abb5d | ||
|
|
3d1e604a6c | ||
|
|
1ca49b9380 | ||
|
|
9bd9cf8052 | ||
|
|
ed934a0206 | ||
|
|
dc891760ec | ||
|
|
97401f843a | ||
|
|
833865cb28 | ||
|
|
02635d506e | ||
|
|
f018e1a075 | ||
|
|
c79cde43ae | ||
|
|
8a812c9d02 | ||
|
|
3df78de3f8 | ||
|
|
bd4831e2ee | ||
|
|
341a34ea9e | ||
|
|
011866a101 | ||
|
|
1486af8770 | ||
|
|
f06e04ffc5 | ||
|
|
d72098bdf8 | ||
|
|
57871e1983 | ||
|
|
21dbb0e820 | ||
|
|
007f377f16 | ||
|
|
da15477aaf | ||
|
|
4f34ad0a8d | ||
|
|
6ed9dccde2 | ||
|
|
ce2b3265be | ||
|
|
14ea69c9c7 | ||
|
|
72e2307c16 | ||
|
|
0dd3334811 | ||
|
|
5485436ea1 | ||
|
|
9e044750ce | ||
|
|
f84cb2f07a | ||
|
|
e0ff6bcc5d | ||
|
|
1695f6800c | ||
|
|
810f149501 | ||
|
|
05d51d8693 | ||
|
|
ac4c819f57 | ||
|
|
e7cc654bb4 | ||
|
|
bf37e3f77f | ||
|
|
af9f7bb0d2 | ||
|
|
0b7abf5690 | ||
|
|
490ae248f8 | ||
|
|
e552bcb0c6 | ||
|
|
2c27316c4a | ||
|
|
c49dd12855 | ||
|
|
544f97a3fe | ||
|
|
6a909e0700 | ||
|
|
40a6ad9a1f | ||
|
|
039f2b49e8 | ||
|
|
5ae2406a03 | ||
|
|
2c3235d452 | ||
|
|
d2ea71a403 | ||
|
|
feb97b2313 | ||
|
|
0b662ecb9a | ||
|
|
3d0da77c80 | ||
|
|
9918397190 | ||
|
|
b3e2562c00 | ||
|
|
255cacf912 | ||
|
|
8268f9f612 | ||
|
|
92b8798cad | ||
|
|
fbb4db8a83 | ||
|
|
19a672caf2 | ||
|
|
5a85c890ba | ||
|
|
55556aaac4 | ||
|
|
31853ee20c | ||
|
|
037bfeff82 | ||
|
|
bf1c919547 | ||
|
|
202ec17a08 | ||
|
|
98ede457a0 | ||
|
|
3c772c3576 | ||
|
|
5b1eaa741c | ||
|
|
1a1aa2d6aa | ||
|
|
6e9aecdff3 | ||
|
|
95187632bf | ||
|
|
e3e28a023c | ||
|
|
65bb71779a | ||
|
|
29dd766a67 | ||
|
|
851145e69e | ||
|
|
b2e300c440 | ||
|
|
38998a4869 | ||
|
|
21f1fd55f8 | ||
|
|
c6c6a18375 | ||
|
|
56063b283e | ||
|
|
d27e7ead49 | ||
|
|
342164ede1 | ||
|
|
1061e40b98 | ||
|
|
377115a594 | ||
|
|
e405b68da1 | ||
|
|
d875d7baed | ||
|
|
9c48e4965c | ||
|
|
8675659fcb | ||
|
|
3b226618ad | ||
|
|
6879a492dc | ||
|
|
4070255dde | ||
|
|
dd9e484336 | ||
|
|
78cda7aa74 | ||
|
|
9ced447fa3 | ||
|
|
8e665ffedc | ||
|
|
99ea6c08a7 | ||
|
|
b0bfa2b2fc | ||
|
|
3797294838 | ||
|
|
3d6995d1e6 | ||
|
|
e91eb27e56 | ||
|
|
3a4b614ae0 | ||
|
|
26a6d6bef9 | ||
|
|
dca81589a7 | ||
|
|
5b7c697dfe | ||
|
|
18d6b8a0ad | ||
|
|
cdee601137 | ||
|
|
e74c08ff39 | ||
|
|
5bfe2fb8d3 | ||
|
|
55fc5c4358 | ||
|
|
d6a603d536 | ||
|
|
5c85d915cc | ||
|
|
57f8519a52 | ||
|
|
056be45dd5 | ||
|
|
3ee22e545e | ||
|
|
db5aa36f44 | ||
|
|
cc767061cc | ||
|
|
1d22b75090 | ||
|
|
4313531432 | ||
|
|
1faddeb402 | ||
|
|
2b035739f8 | ||
|
|
aeed20087e | ||
|
|
a239f41495 | ||
|
|
b36d6967a5 | ||
|
|
7c1d13b7db | ||
|
|
3e99949db8 | ||
|
|
aa12a65a8f | ||
|
|
18804efb7e | ||
|
|
a5d27196ea | ||
|
|
5af6d88ffb | ||
|
|
472a3bc69f | ||
|
|
f0f1c911ea | ||
|
|
fcb0033d25 | ||
|
|
c8b1e76783 | ||
|
|
b50f621e61 | ||
|
|
77c4f5fecc | ||
|
|
e27ea3337a | ||
|
|
23fbf6f157 | ||
|
|
321e5081ed | ||
|
|
33cac29f87 | ||
|
|
12be438dea | ||
|
|
00092db6ad | ||
|
|
619c3a0025 | ||
|
|
f12697c897 |
@@ -17,6 +17,7 @@ matrix:
|
||||
- libxinerama-dev
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libdbus-1-dev
|
||||
|
||||
- os: linux
|
||||
dist: trusty
|
||||
@@ -37,6 +38,7 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libpng-dev
|
||||
- libdbus-1-dev
|
||||
- python3-pil
|
||||
|
||||
- os: linux
|
||||
@@ -58,6 +60,7 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libpng-dev
|
||||
- libdbus-1-dev
|
||||
- python3-pil
|
||||
|
||||
- os: linux
|
||||
@@ -79,6 +82,7 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libxcb-xkb-dev
|
||||
- libpng-dev
|
||||
- libdbus-1-dev
|
||||
- python3-pil
|
||||
- clang
|
||||
- os: osx
|
||||
|
||||
@@ -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
|
||||
|
||||
19
__main__.py
19
__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):
|
||||
@@ -33,10 +34,21 @@ def hold(args):
|
||||
raise SystemExit(ret)
|
||||
|
||||
|
||||
def complete(args):
|
||||
from kitty.complete import main
|
||||
main(args[1:], entry_points, namespaced_entry_points)
|
||||
|
||||
|
||||
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):
|
||||
@@ -69,6 +81,7 @@ entry_points = {
|
||||
}
|
||||
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
|
||||
namespaced_entry_points['hold'] = hold
|
||||
namespaced_entry_points['complete'] = complete
|
||||
|
||||
|
||||
def setup_openssl_environment():
|
||||
|
||||
@@ -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:
|
||||
``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`` and ``libxkbcommon-x11-dev``,
|
||||
if they are not already installed by your distro.
|
||||
|
||||
Install and run from source
|
||||
@@ -85,7 +85,7 @@ without needing to install all of |kitty|.
|
||||
.. note::
|
||||
You need a couple of extra dependencies to build linux-package.
|
||||
:file:`tic` to compile terminfo files, usually found in the
|
||||
development package of :file:`ncurses`. Also, if you are building form
|
||||
development package of :file:`ncurses`. Also, if you are building from
|
||||
a git checkout instead of the released source code tarball, you will
|
||||
need :file:`sphinx-build` from the `Sphinx documentation generator
|
||||
<http://www.sphinx-doc.org/>`_.
|
||||
|
||||
@@ -3,6 +3,222 @@ Changelog
|
||||
|
||||
|kitty| is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
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]
|
||||
------------------------------
|
||||
|
||||
- Preserve the mouse selection even when the contents of the screen are
|
||||
scrolled or overwritten provided the new text does not intersect the
|
||||
selected lines.
|
||||
|
||||
- Linux: Implement support for Input Method Extensions (multilingual input
|
||||
using standard keyboards) via `IBus
|
||||
<https://github.com/ibus/ibus/wiki/ReadMe>`_ (:iss:`469`)
|
||||
|
||||
- Implement completion for the kitty command in bash and zsh. See
|
||||
:ref:`completion`.
|
||||
|
||||
- Render the text under the cursor in a fixed color, configurable via
|
||||
the option :opt:`cursor_text_color` (:iss:`126`)
|
||||
|
||||
- Add an option :opt:`env` to set environment variables in child processes
|
||||
from kitty.conf
|
||||
|
||||
- Implement high precision scrolling with the trackpad on platforms such as
|
||||
macOS and Wayland that implement it. (:pull:`819`)
|
||||
|
||||
- macOS: Allow scrolling window contents using mouse wheel/trackpad even when the
|
||||
window is not the active window (:iss:`729`)
|
||||
|
||||
- Remote control: Allow changing the current window layout with a new
|
||||
:ref:`at_goto-layout` command (:iss:`845`)
|
||||
|
||||
- Remote control: Allow matching windows by the environment variables of their
|
||||
child process as well
|
||||
|
||||
- Allow running kittens via the remote control system (:iss:`738`)
|
||||
|
||||
- Allow enabling remote control in only some kitty windows
|
||||
|
||||
- Add a keyboard shortcut to reset the terminal (:sc:`reset_terminal`). It
|
||||
takes parameters so you can define your own shortcuts to clear the
|
||||
screen/scrollback also (:iss:`747`)
|
||||
|
||||
- Fix one-pixel line appearing at window edges at some window sizes when
|
||||
displaying images with background opacity enabled (:iss:`741`)
|
||||
|
||||
- diff kitten: Fix error when right hand side file is binary and left hand side
|
||||
file is text (:pull:`752`)
|
||||
|
||||
- kitty @ new-window: Add a new option :option:`kitty @ new-window --window-type`
|
||||
to create top-level OS windows (:iss:`770`)
|
||||
|
||||
- macOS: The :opt:`focus_follows_mouse` option now also works across top-level kitty OS windows
|
||||
(:iss:`754`)
|
||||
|
||||
- Fix detection of URLs in HTML source code (URLs inside quotes) (:iss:`785`)
|
||||
|
||||
- Implement support for emoji skin tone modifiers (:iss:`787`)
|
||||
|
||||
- Round-trip the zwj unicode character. Rendering of sequences containing zwj
|
||||
is still not implemented, since it can cause the collapse of an unbounded
|
||||
number of characters into a single cell. However, kitty at least preserves
|
||||
the zwj by storing it as a combining character.
|
||||
|
||||
- macOS: Disable the custom mouse cursor. Using a custom cursor fails on dual
|
||||
GPU machines. I give up, Apple users will just have to live with the
|
||||
limitations of their choice of OS. (:iss:`794`)
|
||||
|
||||
- macOS: Fix control+tab key combination not working (:iss:`801`)
|
||||
|
||||
- Linux: Fix slow startup on some systems caused by GLFW searching for
|
||||
joysticks. Since kitty does not use joysticks, disable joystick support.
|
||||
(:iss:`830`)
|
||||
|
||||
|
||||
0.11.3 [2018-07-10]
|
||||
------------------------------
|
||||
|
||||
- Draw only the minimum borders needed for inactive windows. That is only the borders
|
||||
that separate the inactive window from a neighbor. Note that setting
|
||||
a non-zero window margin overrides this and causes all borders to be drawn.
|
||||
The old behavior of drawing all borders can be restored via the
|
||||
:opt:`draw_minimal_borders` setting in kitty.conf. (:iss:`699`)
|
||||
|
||||
- macOS: Add an option :opt:`macos_window_resizable` to control if kitty
|
||||
top-level windows are resizable using the mouse or not (:iss:`698`)
|
||||
|
||||
- macOS: Use a custom mouse cursor that shows up well on both light and dark backgrounds
|
||||
(:iss:`359`)
|
||||
|
||||
- macOS: Workaround for switching from fullscreen to windowed mode with the
|
||||
titlebar hidden causing window resizing to not work. (:iss:`711`)
|
||||
|
||||
- Fix triple-click to select line not working when the entire line is filled
|
||||
(:iss:`703`)
|
||||
|
||||
- When dragging to select with the mouse "grab" the mouse so that if it strays
|
||||
into neighboring windows, the selection is still updated (:pull:`624`)
|
||||
|
||||
- When clicking in the margin/border area of a window, map the click to the
|
||||
nearest cell in the window. Avoids selection with the mouse failing when
|
||||
starting the selection just outside the window.
|
||||
|
||||
- When drag-scrolling stop the scroll when the mouse button is released.
|
||||
|
||||
- Fix a regression in the previous release that caused pasting large amounts
|
||||
of text to be duplicated (:iss:`709`)
|
||||
|
||||
|
||||
0.11.2 [2018-07-01]
|
||||
------------------------------
|
||||
|
||||
@@ -261,7 +477,7 @@ Changelog
|
||||
|
||||
- When triple-clicking select all wrapped lines belonging to a single logical line.
|
||||
|
||||
- hints kitten: Detect bracketed URLs and dont include the closing bracket in the URL.
|
||||
- hints kitten: Detect bracketed URLs and don't include the closing bracket in the URL.
|
||||
|
||||
- When calling pass_selection_to_program use the current directory of the child
|
||||
process as the cwd of the program.
|
||||
|
||||
@@ -113,3 +113,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.
|
||||
@@ -372,9 +372,9 @@ Key Value Default Description
|
||||
**Keys for deleting images**
|
||||
-----------------------------------------------------------
|
||||
``d`` Single character. ``a`` What to delete.
|
||||
``(a, A, c, C, p,
|
||||
P, q, Q, x, X, y,
|
||||
Y, z, Z)``.
|
||||
``(a, A, c, C, i,
|
||||
I, p, P, q, Q, x, X,
|
||||
y, Y, z, Z)``.
|
||||
======= ==================== ========= =================
|
||||
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -122,8 +124,8 @@ Action Shortcut
|
||||
======================== =======================
|
||||
New tab :sc:`new_tab`
|
||||
Close tab :sc:`close_tab`
|
||||
Next tab :sc:`next_tab`
|
||||
Previous tab :sc:`previous_tab`
|
||||
Next tab :sc:`next_tab` (also :kbd:`control+tab` on macOS)
|
||||
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,15 @@ 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
|
||||
...
|
||||
|
||||
|
||||
Other keyboard shortcuts
|
||||
----------------------------------
|
||||
@@ -162,9 +173,11 @@ 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`
|
||||
Pass current selection to program :sc:`pass_selection_to_program`
|
||||
Edit |kitty| config file :sc:`edit_config_file`
|
||||
Open a |kitty| shell :sc:`kitty_shell`
|
||||
@@ -180,6 +193,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
|
||||
@@ -249,6 +265,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
|
||||
-------------------
|
||||
@@ -257,6 +275,16 @@ Configuring kitty
|
||||
painting frames-per-second. For details and a sample :file:`kitty.conf`,
|
||||
see the :doc:`configuration docs <conf>`.
|
||||
|
||||
|
||||
Remote control
|
||||
------------------
|
||||
|
||||
|kitty| has a very powerful system that allows you to control it from the
|
||||
:doc:`shell prompt, even over SSH <remote-control>`. You can change colors,
|
||||
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
|
||||
@@ -303,6 +331,8 @@ Mouse features
|
||||
* You can also hold down :kbd:`ctrl+shift` and click on a URL to open it in a browser.
|
||||
* You can double click to select a word and triple click to select a line.
|
||||
* You can right click to extend a previous selection
|
||||
* You can hold down :kbd:`ctrl+alt` and drag with the mouse to select in
|
||||
columns
|
||||
|
||||
|
||||
Font control
|
||||
@@ -332,12 +362,63 @@ 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.
|
||||
|
||||
|
||||
Frequently Asked Questions
|
||||
---------------------------------
|
||||
|
||||
The list of Frequently Asked Questions (*FAQ*) is :doc:`available here <faq>`.
|
||||
|
||||
.. _completion:
|
||||
|
||||
Completion for kitty
|
||||
---------------------------------
|
||||
|
||||
|kitty| comes with completion for the ``kitty`` command for popular shells.
|
||||
|
||||
|
||||
bash
|
||||
~~~~~~~~
|
||||
|
||||
Add the following to your :file:`~/.bashrc`
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
source <(kitty + complete setup bash)
|
||||
|
||||
|
||||
zsh
|
||||
~~~~~~~~~
|
||||
|
||||
Add the following to your :file:`~/.zshrc`
|
||||
|
||||
.. code-block:: sh
|
||||
|
||||
autoload -Uz compinit
|
||||
compinit
|
||||
# Completion for kitty
|
||||
kitty + complete setup zsh | source /dev/stdin
|
||||
|
||||
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
|
||||
------------------
|
||||
|
||||
80
docs/kittens/custom.rst
Normal file
80
docs/kittens/custom.rst
Normal file
@@ -0,0 +1,80 @@
|
||||
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
|
||||
|
||||
def handle_result(...):
|
||||
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``.
|
||||
@@ -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
|
||||
|
||||
@@ -18,5 +18,5 @@ typing a leading period and then the two character index and pressing Enter.
|
||||
|
||||
In :guilabel:`Name` mode you instead type words from the character name and use the arrow
|
||||
keys/tab to select the character from the displayed matches. You can also type
|
||||
a leading period and the index for the match if you dont like to use arrow
|
||||
a leading period and the index for the match if you don't like to use arrow
|
||||
keys.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -184,3 +184,25 @@ changes even after it exits. To avoid this, kitty introduces a new pair of
|
||||
These escape codes save/restore the so called *dynamic colors*, default
|
||||
background, default foreground, selection background, selection foreground and
|
||||
cursor color.
|
||||
|
||||
|
||||
Pasting to clipboard
|
||||
----------------------
|
||||
|
||||
|kitty| implements the OSC 52 escape code protocol to get/set the clipboard
|
||||
contents (controlled via the :opt:`clipboard_control` setting). There is one
|
||||
difference in kitty's implementation compared to some other terminal emulators.
|
||||
|kitty| allows sending arbitrary amounts of text to the clipboard. It does so
|
||||
by modifying the protocol slightly. Successive OSC 52 escape codes to set the
|
||||
clipboard will concatenate, so::
|
||||
|
||||
<ESC>]52;c;<payload1><ESC>\
|
||||
<ESC>]52;c;<payload2><ESC>\
|
||||
|
||||
will result in the clipboard having the contents ``payload1 + payload2``. To
|
||||
send a new string to the clipboard send an OSC 52 sequence with an invalid payload
|
||||
first, for example::
|
||||
|
||||
<ESC>]52;c;!<ESC>\
|
||||
|
||||
Here ``!`` is not valid base64 encoded text, so it clears the clipboard.
|
||||
|
||||
@@ -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.
|
||||
@@ -103,7 +103,7 @@ instance of |kitty| using the :option:`kitty @ --to` command line argument to ``
|
||||
kitty @ --to unix:/tmp/mykitty ls
|
||||
|
||||
|
||||
Note that is all you want to do is run a single |kitty| "daemon" and have subsequent
|
||||
Note that if all you want to do is run a single |kitty| "daemon" and have subsequent
|
||||
|kitty| invocations appear as new top-level windows, you can use the simpler :option:`kitty --single-instance`
|
||||
option, see ``kitty --help`` for that.
|
||||
|
||||
@@ -116,7 +116,23 @@ shell with completion for |kitty| command names and options.
|
||||
|
||||
You can even open the |kitty| shell inside a running |kitty| using a simple
|
||||
keyboard shortcut (:sc:`kitty_shell` by default). This has the added
|
||||
advantage that you dont need to use ``allow_remote_control`` to make it work.
|
||||
advantage that you don't need to use ``allow_remote_control`` to make it work.
|
||||
|
||||
|
||||
Allowing only some windows to control kitty
|
||||
----------------------------------------------
|
||||
|
||||
If you do not want to allow all programs running in |kitty| to control it, you can selectively
|
||||
enable remote control for only some |kitty| windows. Simply create a shortcut
|
||||
such as::
|
||||
|
||||
map ctrl+k new_window @ some_program
|
||||
|
||||
Then programs running in windows created with that shortcut can use ``kitty @``
|
||||
to control kitty. Note that any program with the right level of permissions can
|
||||
still write to the pipes of any other program on the same computer and
|
||||
therefore can control |kitty|. It can, however, be useful to block programs
|
||||
running on other computers (for example, over ssh) or as other users.
|
||||
|
||||
|
||||
.. include:: generated/cli-kitty-at.rst
|
||||
|
||||
264
gen-apc-parsers.py
Executable file
264
gen-apc-parsers.py
Executable file
@@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def resolve_keys(keymap):
|
||||
ans = defaultdict(list)
|
||||
for ch, (attr, atype) in keymap.items():
|
||||
if atype not in ('int', 'uint'):
|
||||
atype = 'flag'
|
||||
ans[atype].append(ch)
|
||||
return ans
|
||||
|
||||
|
||||
def enum(keymap):
|
||||
lines = []
|
||||
for ch, (attr, atype) in keymap.items():
|
||||
lines.append(f"{attr}='{ch}'")
|
||||
return '''
|
||||
enum KEYS {{
|
||||
{}
|
||||
}};
|
||||
'''.format(',\n'.join(lines))
|
||||
|
||||
|
||||
def parse_key(keymap):
|
||||
lines = []
|
||||
for attr, atype in keymap.values():
|
||||
vs = atype.upper() if atype in ('uint', 'int') else 'FLAG'
|
||||
lines.append(f'case {attr}: value_state = {vs}; break;')
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_flag(keymap, type_map, command_class):
|
||||
lines = []
|
||||
for ch in type_map['flag']:
|
||||
attr, allowed_values = keymap[ch]
|
||||
q = ' && '.join(f"g.{attr} != '{x}'" for x in allowed_values)
|
||||
lines.append(f'''
|
||||
case {attr}: {{
|
||||
g.{attr} = screen->parser_buf[pos++] & 0xff;
|
||||
if ({q}) {{
|
||||
REPORT_ERROR("Malformed {command_class} control block, unknown flag value for {attr}: 0x%x", g.{attr});
|
||||
return;
|
||||
}};
|
||||
}}
|
||||
break;
|
||||
''')
|
||||
return ' \n'.join(lines)
|
||||
|
||||
|
||||
def parse_number(keymap):
|
||||
int_keys = [f'I({attr})' for attr, atype in keymap.values() if atype == 'int']
|
||||
uint_keys = [f'U({attr})' for attr, atype in keymap.values() if atype == 'uint']
|
||||
return '; '.join(int_keys), '; '.join(uint_keys)
|
||||
|
||||
|
||||
def cmd_for_report(report_name, keymap, type_map, payload_allowed):
|
||||
def group(atype, conv):
|
||||
flag_fmt, flag_attrs = [], []
|
||||
cv = {'flag': 'c', 'int': 'i', 'uint': 'I'}[atype]
|
||||
for ch in type_map[atype]:
|
||||
flag_fmt.append('s' + cv)
|
||||
attr = keymap[ch][0]
|
||||
flag_attrs.append(f'"{attr}", {conv}g.{attr}')
|
||||
return ' '.join(flag_fmt), ', '.join(flag_attrs)
|
||||
|
||||
flag_fmt, flag_attrs = group('flag', '')
|
||||
int_fmt, int_attrs = group('int', '(int)')
|
||||
uint_fmt, uint_attrs = group('uint', '(unsigned int)')
|
||||
|
||||
fmt = f'{flag_fmt} {uint_fmt} {int_fmt}'
|
||||
if payload_allowed:
|
||||
ans = [f'REPORT_VA_COMMAND("s {{{fmt} sI}} y#", "{report_name}",']
|
||||
else:
|
||||
ans = [f'REPORT_VA_COMMAND("s {{{fmt}}}", "{report_name}",']
|
||||
ans.append(',\n '.join((flag_attrs, uint_attrs, int_attrs)))
|
||||
if payload_allowed:
|
||||
ans.append(', "payload_sz", g.payload_sz, payload, g.payload_sz')
|
||||
ans.append(');')
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def generate(function_name, callback_name, report_name, keymap, command_class, initial_key='a', payload_allowed=True):
|
||||
type_map = resolve_keys(keymap)
|
||||
keys_enum = enum(keymap)
|
||||
handle_key = parse_key(keymap)
|
||||
flag_keys = parse_flag(keymap, type_map, command_class)
|
||||
int_keys, uint_keys = parse_number(keymap)
|
||||
report_cmd = cmd_for_report(report_name, keymap, type_map, payload_allowed)
|
||||
if payload_allowed:
|
||||
payload_after_value = "case ';': state = PAYLOAD; break;"
|
||||
payload = payload = ', PAYLOAD'
|
||||
parr = 'static uint8_t payload[4096];'
|
||||
payload_case = f'''
|
||||
case PAYLOAD: {{
|
||||
sz = screen->parser_buf_pos - pos;
|
||||
const char *err = base64_decode(screen->parser_buf + pos, sz, payload, sizeof(payload), &g.payload_sz);
|
||||
if (err != NULL) {{ REPORT_ERROR("Failed to parse {command_class} command payload with error: %s", err); return; }}
|
||||
pos = screen->parser_buf_pos;
|
||||
}}
|
||||
break;
|
||||
'''
|
||||
callback = f'{callback_name}(screen, &g, payload)'
|
||||
else:
|
||||
payload_after_value = payload = parr = payload_case = ''
|
||||
callback = f'{callback_name}(screen, &g)'
|
||||
|
||||
return f'''
|
||||
static inline void
|
||||
{function_name}(Screen *screen, PyObject UNUSED *dump_callback) {{
|
||||
unsigned int pos = 1;
|
||||
enum PARSER_STATES {{ KEY, EQUAL, UINT, INT, FLAG, AFTER_VALUE {payload} }};
|
||||
enum PARSER_STATES state = KEY, value_state = FLAG;
|
||||
static {command_class} g;
|
||||
unsigned int i, code;
|
||||
uint64_t lcode;
|
||||
bool is_negative;
|
||||
memset(&g, 0, sizeof(g));
|
||||
size_t sz;
|
||||
{parr}
|
||||
{keys_enum}
|
||||
enum KEYS key = '{initial_key}';
|
||||
|
||||
while (pos < screen->parser_buf_pos) {{
|
||||
switch(state) {{
|
||||
case KEY:
|
||||
key = screen->parser_buf[pos++];
|
||||
state = EQUAL;
|
||||
switch(key) {{
|
||||
{handle_key}
|
||||
default:
|
||||
REPORT_ERROR("Malformed {command_class} control block, invalid key character: 0x%x", key);
|
||||
return;
|
||||
}}
|
||||
break;
|
||||
|
||||
case EQUAL:
|
||||
if (screen->parser_buf[pos++] != '=') {{
|
||||
REPORT_ERROR("Malformed {command_class} control block, no = after key, found: 0x%x instead", screen->parser_buf[pos-1]);
|
||||
return;
|
||||
}}
|
||||
state = value_state;
|
||||
break;
|
||||
|
||||
case FLAG:
|
||||
switch(key) {{
|
||||
{flag_keys}
|
||||
default:
|
||||
break;
|
||||
}}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
|
||||
case INT:
|
||||
#define READ_UINT \\
|
||||
for (i = pos; i < MIN(screen->parser_buf_pos, pos + 10); i++) {{ \\
|
||||
if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; \\
|
||||
}} \\
|
||||
if (i == pos) {{ REPORT_ERROR("Malformed {command_class} control block, expecting an integer value for key: %c", key & 0xFF); return; }} \\
|
||||
lcode = utoi(screen->parser_buf + pos, i - pos); pos = i; \\
|
||||
if (lcode > UINT32_MAX) {{ REPORT_ERROR("Malformed {command_class} control block, number is too large"); return; }} \\
|
||||
code = lcode;
|
||||
|
||||
is_negative = false;
|
||||
if(screen->parser_buf[pos] == '-') {{ is_negative = true; pos++; }}
|
||||
#define I(x) case x: g.x = is_negative ? 0 - (int32_t)code : (int32_t)code; break
|
||||
READ_UINT;
|
||||
switch(key) {{
|
||||
{int_keys};
|
||||
default: break;
|
||||
}}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef I
|
||||
case UINT:
|
||||
READ_UINT;
|
||||
#define U(x) case x: g.x = code; break
|
||||
switch(key) {{
|
||||
{uint_keys};
|
||||
default: break;
|
||||
}}
|
||||
state = AFTER_VALUE;
|
||||
break;
|
||||
#undef U
|
||||
#undef READ_UINT
|
||||
|
||||
case AFTER_VALUE:
|
||||
switch (screen->parser_buf[pos++]) {{
|
||||
default:
|
||||
REPORT_ERROR("Malformed {command_class} control block, expecting a comma or semi-colon after a value, found: 0x%x",
|
||||
screen->parser_buf[pos - 1]);
|
||||
return;
|
||||
case ',':
|
||||
state = KEY;
|
||||
break;
|
||||
{payload_after_value}
|
||||
}}
|
||||
break;
|
||||
|
||||
{payload_case}
|
||||
|
||||
}} // end switch
|
||||
}} // end while
|
||||
|
||||
switch(state) {{
|
||||
case EQUAL:
|
||||
REPORT_ERROR("Malformed {command_class} control block, no = after key"); return;
|
||||
case INT:
|
||||
case UINT:
|
||||
REPORT_ERROR("Malformed {command_class} control block, expecting an integer value"); return;
|
||||
case FLAG:
|
||||
REPORT_ERROR("Malformed {command_class} control block, expecting a flag value"); return;
|
||||
default:
|
||||
break;
|
||||
}}
|
||||
|
||||
{report_cmd}
|
||||
|
||||
{callback};
|
||||
}}
|
||||
'''
|
||||
|
||||
|
||||
def write_header(text, path):
|
||||
with open(path, 'w') as f:
|
||||
print(f'// This file is generated by {__file__} do not edit!', file=f, end='\n\n')
|
||||
print('#pragma once', file=f)
|
||||
print(text, file=f)
|
||||
subprocess.check_call(['clang-format', '-i', path])
|
||||
|
||||
|
||||
def graphics_parser():
|
||||
flag = frozenset
|
||||
keymap = {
|
||||
'a': ('action', flag('tTqpd')),
|
||||
'd': ('delete_action', flag('aAiIcCpPqQxXyYzZ')),
|
||||
't': ('transmission_type', flag('dfts')),
|
||||
'o': ('compressed', flag('z')),
|
||||
'f': ('format', 'uint'),
|
||||
'm': ('more', 'uint'),
|
||||
'i': ('id', 'uint'),
|
||||
'w': ('width', 'uint'),
|
||||
'h': ('height', 'uint'),
|
||||
'x': ('x_offset', 'uint'),
|
||||
'y': ('y_offset', 'uint'),
|
||||
'v': ('data_height', 'uint'),
|
||||
's': ('data_width', 'uint'),
|
||||
'S': ('data_sz', 'uint'),
|
||||
'O': ('data_offset', 'uint'),
|
||||
'c': ('num_cells', 'uint'),
|
||||
'r': ('num_lines', 'uint'),
|
||||
'X': ('cell_x_offset', 'uint'),
|
||||
'Y': ('cell_y_offset', 'uint'),
|
||||
'z': ('z_index', 'int'),
|
||||
}
|
||||
text = generate('parse_graphics_code', 'screen_handle_graphics_command', 'graphics_command', keymap, 'GraphicsCommand')
|
||||
write_header(text, 'kitty/parse-graphics-command.h')
|
||||
|
||||
|
||||
graphics_parser()
|
||||
@@ -20,6 +20,7 @@ non_characters |= frozenset(range(0xffff, 0x10ffff + 1, 0x10000))
|
||||
non_characters |= frozenset(range(0xfdd0, 0xfdf0))
|
||||
if len(non_characters) != 66:
|
||||
raise SystemExit('non_characters table incorrect')
|
||||
emoji_skin_tone_modifiers = frozenset(range(0x1f3fb, 0x1F3FF + 1))
|
||||
|
||||
|
||||
def get_data(fname, folder='UCD'):
|
||||
@@ -41,7 +42,8 @@ def get_data(fname, folder='UCD'):
|
||||
class_maps = {}
|
||||
name_map = {}
|
||||
word_search_map = defaultdict(set)
|
||||
marks = set()
|
||||
zwj = 0x200d
|
||||
marks = set(emoji_skin_tone_modifiers) | {zwj}
|
||||
not_assigned = set(range(0, sys.maxunicode))
|
||||
|
||||
|
||||
@@ -195,12 +197,13 @@ def gen_emoji():
|
||||
p('\treturn false;\n}')
|
||||
|
||||
|
||||
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset()):
|
||||
def category_test(name, p, classes, comment, static=False, extra_chars=frozenset(), exclude=frozenset()):
|
||||
static = 'static inline ' if static else ''
|
||||
chars = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
chars |= extra_chars
|
||||
chars -= exclude
|
||||
p(f'{static}bool\n{name}(char_type code) {{')
|
||||
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||
p('\tswitch(code) {')
|
||||
@@ -251,8 +254,16 @@ def gen_ucd():
|
||||
cz = {c for c in class_maps if c[0] in 'CZ'}
|
||||
with create_header('kitty/unicode-data.c') as p:
|
||||
p('#include "unicode-data.h"')
|
||||
category_test('is_combining_char', p, {c for c in class_maps if c.startswith('M')}, 'M category (marks)')
|
||||
category_test('is_ignored_char', p, 'Cc Cf Cs'.split(), 'Control characters and non-characters', extra_chars=non_characters)
|
||||
category_test(
|
||||
'is_combining_char', p,
|
||||
{c for c in class_maps if c.startswith('M')},
|
||||
'M category (marks)',
|
||||
# See https://github.com/harfbuzz/harfbuzz/issues/169
|
||||
extra_chars=emoji_skin_tone_modifiers | {zwj}
|
||||
)
|
||||
category_test(
|
||||
'is_ignored_char', p, 'Cc Cf Cs'.split(),
|
||||
'Control characters and non-characters', extra_chars=non_characters, exclude={zwj})
|
||||
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
||||
category_test('is_CZ_category', p, cz, 'C and Z categories')
|
||||
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
||||
@@ -265,7 +276,7 @@ def gen_ucd():
|
||||
p('combining_type mark_for_codepoint(char_type c) {')
|
||||
rmap = codepoint_to_mark_map(p, mark_map)
|
||||
p('}\n')
|
||||
if rmap[0xfe0e] != 1280:
|
||||
if rmap[0xfe0e] != 1281:
|
||||
raise ValueError('The mark for 0xfe0e has changed, you have to update VS15 to {} and VS16 to {} in unicode-data.h'.format(
|
||||
rmap[0xfe0e], rmap[0xfe0f]
|
||||
))
|
||||
|
||||
@@ -43,8 +43,6 @@ def export():
|
||||
switch = ['glad_debug_{0} = glad_{0}'.format(f) for f in functions]
|
||||
c = c.replace('<glad/glad.h>', '"gl-wrapper.h"', 1)
|
||||
c = '#pragma GCC diagnostic ignored "-Wpedantic"\n' + c
|
||||
# See https://github.com/kovidgoyal/kitty/issues/654
|
||||
c = c.replace('strncpy(local_str, gl_str_tmp, len+1)', 'memcpy(local_str, gl_str_tmp, len+1)', 1)
|
||||
c += '''
|
||||
int
|
||||
init_glad(GLADloadproc load, int debug) {
|
||||
|
||||
314
glfw/backend_utils.c
vendored
Normal file
314
glfw/backend_utils.c
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* backend_utils.c
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include "backend_utils.h"
|
||||
#include "internal.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <float.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#define ppoll pollts
|
||||
#endif
|
||||
|
||||
static inline double
|
||||
monotonic() {
|
||||
struct timespec ts = {0};
|
||||
#ifdef CLOCK_HIGHRES
|
||||
clock_gettime(CLOCK_HIGHRES, &ts);
|
||||
#elif CLOCK_MONOTONIC_RAW
|
||||
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
||||
#else
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
#endif
|
||||
return (((double)ts.tv_nsec) / 1e9) + (double)ts.tv_sec;
|
||||
}
|
||||
|
||||
void
|
||||
update_fds(EventLoopData *eld) {
|
||||
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||
Watch *w = eld->watches + i;
|
||||
eld->fds[i].fd = w->fd;
|
||||
eld->fds[i].events = w->enabled ? w->events : 0;
|
||||
}
|
||||
}
|
||||
|
||||
static id_type watch_counter = 0;
|
||||
|
||||
id_type
|
||||
addWatch(EventLoopData *eld, const char* name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data) {
|
||||
if (eld->watches_count >= sizeof(eld->watches)/sizeof(eld->watches[0])) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many watches added");
|
||||
return 0;
|
||||
}
|
||||
Watch *w = eld->watches + eld->watches_count++;
|
||||
w->name = name;
|
||||
w->fd = fd; w->events = events; w->enabled = enabled;
|
||||
w->callback = cb;
|
||||
w->callback_data = cb_data;
|
||||
w->id = ++watch_counter;
|
||||
update_fds(eld);
|
||||
return w->id;
|
||||
}
|
||||
|
||||
#define removeX(which, item_id, update_func) {\
|
||||
for (nfds_t i = 0; i < eld->which##_count; i++) { \
|
||||
if (eld->which[i].id == item_id) { \
|
||||
eld->which##_count--; \
|
||||
if (i < eld->which##_count) { \
|
||||
memmove(eld->which + i, eld->which + i + 1, sizeof(eld->which[0]) * (eld->which##_count - i)); \
|
||||
} \
|
||||
update_func(eld); break; \
|
||||
}}}
|
||||
|
||||
void
|
||||
removeWatch(EventLoopData *eld, id_type watch_id) {
|
||||
removeX(watches, watch_id, update_fds);
|
||||
}
|
||||
|
||||
void
|
||||
toggleWatch(EventLoopData *eld, id_type watch_id, int enabled) {
|
||||
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||
if (eld->watches[i].id == watch_id) {
|
||||
if (eld->watches[i].enabled != enabled) {
|
||||
eld->watches[i].enabled = enabled;
|
||||
update_fds(eld);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static id_type timer_counter = 0;
|
||||
|
||||
static int
|
||||
compare_timers(const void *a_, const void *b_) {
|
||||
const Timer *a = (const Timer*)a_, *b = (const Timer*)b_;
|
||||
return (a->trigger_at > b->trigger_at) ? 1 : (a->trigger_at < b->trigger_at) ? -1 : 0;
|
||||
}
|
||||
|
||||
static inline void
|
||||
update_timers(EventLoopData *eld) {
|
||||
if (eld->timers_count > 1) qsort(eld->timers, eld->timers_count, sizeof(eld->timers[0]), compare_timers);
|
||||
}
|
||||
|
||||
id_type
|
||||
addTimer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data) {
|
||||
if (eld->timers_count >= sizeof(eld->timers)/sizeof(eld->timers[0])) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Too many timers added");
|
||||
return 0;
|
||||
}
|
||||
Timer *t = eld->timers + eld->timers_count++;
|
||||
t->interval = interval;
|
||||
t->name = name;
|
||||
t->trigger_at = enabled ? monotonic() + interval : DBL_MAX;
|
||||
t->callback = cb;
|
||||
t->callback_data = cb_data;
|
||||
t->id = ++timer_counter;
|
||||
update_timers(eld);
|
||||
return t->id;
|
||||
}
|
||||
|
||||
void
|
||||
removeTimer(EventLoopData *eld, id_type timer_id) {
|
||||
removeX(timers, timer_id, update_timers);
|
||||
}
|
||||
|
||||
void
|
||||
toggleTimer(EventLoopData *eld, id_type timer_id, int enabled) {
|
||||
for (nfds_t i = 0; i < eld->timers_count; i++) {
|
||||
if (eld->timers[i].id == timer_id) {
|
||||
double trigger_at = enabled ? (monotonic() + eld->timers[i].interval) : DBL_MAX;
|
||||
if (trigger_at != eld->timers[i].trigger_at) {
|
||||
eld->timers[i].trigger_at = trigger_at;
|
||||
update_timers(eld);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval) {
|
||||
for (nfds_t i = 0; i < eld->timers_count; i++) {
|
||||
if (eld->timers[i].id == timer_id) {
|
||||
eld->timers[i].interval = interval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
double
|
||||
prepareForPoll(EventLoopData *eld, double timeout) {
|
||||
for (nfds_t i = 0; i < eld->watches_count; i++) eld->fds[i].revents = 0;
|
||||
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return timeout;
|
||||
double now = monotonic(), next_repeat_at = eld->timers[0].trigger_at;
|
||||
if (timeout < 0 || now + timeout > next_repeat_at) {
|
||||
timeout = next_repeat_at <= now ? 0 : next_repeat_at - now;
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
int
|
||||
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
|
||||
const long seconds = (long) timeout;
|
||||
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
|
||||
struct timespec tv = { seconds, nanoseconds };
|
||||
return ppoll(fds, nfds, &tv, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
dispatchEvents(EventLoopData *eld) {
|
||||
for (nfds_t i = 0; i < eld->watches_count; i++) {
|
||||
Watch *ww = eld->watches + i;
|
||||
struct pollfd *pfd = eld->fds + i;
|
||||
if (pfd->revents & ww->events) {
|
||||
ww->ready = 1;
|
||||
if (ww->callback) ww->callback(ww->fd, pfd->revents, ww->callback_data);
|
||||
} else ww->ready = 0;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned
|
||||
dispatchTimers(EventLoopData *eld) {
|
||||
if (!eld->timers_count || eld->timers[0].trigger_at == DBL_MAX) return 0;
|
||||
static struct { timer_callback_func func; id_type id; void* data; } dispatches[sizeof(eld->timers)/sizeof(eld->timers[0])];
|
||||
unsigned num_dispatches = 0;
|
||||
double now = monotonic();
|
||||
for (nfds_t i = 0; i < eld->timers_count && eld->timers[i].trigger_at <= now; i++) {
|
||||
eld->timers[i].trigger_at = now + eld->timers[i].interval;
|
||||
dispatches[num_dispatches].func = eld->timers[i].callback;
|
||||
dispatches[num_dispatches].id = eld->timers[i].id;
|
||||
dispatches[num_dispatches].data = eld->timers[i].callback_data;
|
||||
num_dispatches++;
|
||||
}
|
||||
// we dispatch separately so that the callbacks can modify timers
|
||||
for (unsigned i = 0; i < num_dispatches; i++) {
|
||||
dispatches[i].func(dispatches[i].id, dispatches[i].data);
|
||||
}
|
||||
if (num_dispatches) update_timers(eld);
|
||||
return num_dispatches;
|
||||
}
|
||||
|
||||
static void
|
||||
drain_wakeup_fd(int fd, int events, void* data) {
|
||||
static char drain_buf[64];
|
||||
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
|
||||
}
|
||||
|
||||
void
|
||||
initPollData(EventLoopData *eld, int wakeup_fd, int display_fd) {
|
||||
addWatch(eld, "display", display_fd, POLLIN, 1, NULL, NULL);
|
||||
addWatch(eld, "wakeup", wakeup_fd, POLLIN, 1, drain_wakeup_fd, NULL);
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
pollForEvents(EventLoopData *eld, double timeout) {
|
||||
int read_ok = 0;
|
||||
timeout = prepareForPoll(eld, timeout);
|
||||
int result;
|
||||
double end_time = monotonic() + timeout;
|
||||
|
||||
while(1) {
|
||||
if (timeout >= 0) {
|
||||
errno = 0;
|
||||
result = pollWithTimeout(eld->fds, eld->watches_count, timeout);
|
||||
int saved_errno = errno;
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
read_ok = eld->watches[0].ready;
|
||||
break;
|
||||
}
|
||||
timeout = end_time - monotonic();
|
||||
if (timeout <= 0) break;
|
||||
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
|
||||
break;
|
||||
} else {
|
||||
errno = 0;
|
||||
result = poll(eld->fds, eld->watches_count, -1);
|
||||
int saved_errno = errno;
|
||||
dispatchTimers(eld);
|
||||
if (result > 0) {
|
||||
dispatchEvents(eld);
|
||||
read_ok = eld->watches[0].ready;
|
||||
}
|
||||
if (result < 0 && (saved_errno == EINTR || saved_errno == EAGAIN)) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return read_ok;
|
||||
}
|
||||
|
||||
void
|
||||
closeFds(int *fds, size_t count) {
|
||||
while(count--) {
|
||||
if (*fds > 0) {
|
||||
close(*fds);
|
||||
*fds = -1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
72
glfw/backend_utils.h
vendored
72
glfw/backend_utils.h
vendored
@@ -25,42 +25,50 @@
|
||||
//========================================================================
|
||||
|
||||
#pragma once
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef __NetBSD__
|
||||
#define ppoll pollts
|
||||
#endif
|
||||
typedef unsigned long long id_type;
|
||||
typedef void(*watch_callback_func)(int, int, void*);
|
||||
typedef void(*timer_callback_func)(id_type, void*);
|
||||
|
||||
static inline void
|
||||
drainFd(int fd) {
|
||||
static char drain_buf[64];
|
||||
while(read(fd, drain_buf, sizeof(drain_buf)) < 0 && errno == EINTR);
|
||||
}
|
||||
typedef struct {
|
||||
int fd, events, enabled, ready;
|
||||
watch_callback_func callback;
|
||||
void *callback_data;
|
||||
id_type id;
|
||||
const char *name;
|
||||
} Watch;
|
||||
|
||||
typedef struct {
|
||||
id_type id;
|
||||
double interval, trigger_at;
|
||||
timer_callback_func callback;
|
||||
void *callback_data;
|
||||
const char *name;
|
||||
} Timer;
|
||||
|
||||
|
||||
static inline int
|
||||
pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout) {
|
||||
const long seconds = (long) timeout;
|
||||
const long nanoseconds = (long) ((timeout - seconds) * 1e9);
|
||||
struct timespec tv = { seconds, nanoseconds };
|
||||
return ppoll(fds, nfds, &tv, NULL);
|
||||
}
|
||||
typedef struct {
|
||||
struct pollfd fds[32];
|
||||
int wakeupFds[2];
|
||||
nfds_t watches_count, timers_count;
|
||||
Watch watches[32];
|
||||
Timer timers[128];
|
||||
} EventLoopData;
|
||||
|
||||
static inline void
|
||||
initPollData(struct pollfd *fds, int wakeup_fd, int display_fd) {
|
||||
fds[0].fd = wakeup_fd; fds[1].fd = display_fd;
|
||||
fds[0].events = POLLIN; fds[1].events = POLLIN;
|
||||
}
|
||||
|
||||
static inline void
|
||||
closeFds(int *fds, size_t count) {
|
||||
while(count--) {
|
||||
if (*fds > 0) {
|
||||
close(*fds);
|
||||
*fds = -1;
|
||||
}
|
||||
fds++;
|
||||
}
|
||||
}
|
||||
id_type addWatch(EventLoopData *eld, const char *name, int fd, int events, int enabled, watch_callback_func cb, void *cb_data);
|
||||
void removeWatch(EventLoopData *eld, id_type watch_id);
|
||||
void toggleWatch(EventLoopData *eld, id_type watch_id, int enabled);
|
||||
id_type addTimer(EventLoopData *eld, const char *name, double interval, int enabled, timer_callback_func cb, void *cb_data);
|
||||
void removeTimer(EventLoopData *eld, id_type timer_id);
|
||||
void toggleTimer(EventLoopData *eld, id_type timer_id, int enabled);
|
||||
void changeTimerInterval(EventLoopData *eld, id_type timer_id, double interval);
|
||||
double prepareForPoll(EventLoopData *eld, double timeout);
|
||||
int pollWithTimeout(struct pollfd *fds, nfds_t nfds, double timeout);
|
||||
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);
|
||||
|
||||
3
glfw/cocoa_platform.h
vendored
3
glfw/cocoa_platform.h
vendored
@@ -39,6 +39,7 @@ typedef void* id;
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
|
||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
{
|
||||
@@ -102,6 +103,8 @@ typedef struct _GLFWwindowNS
|
||||
|
||||
// The text input filter callback
|
||||
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
||||
// The toggle fullscreen intercept callback
|
||||
GLFWcocoatogglefullscreenfun toggleFullscreenCallback;
|
||||
// Dead key state
|
||||
UInt32 deadKeyState;
|
||||
} _GLFWwindowNS;
|
||||
|
||||
@@ -563,6 +563,10 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (_GLFWwindow*)glfwWindow {
|
||||
return window;
|
||||
}
|
||||
|
||||
- (BOOL)isOpaque
|
||||
{
|
||||
return [window->ns.object isOpaque];
|
||||
@@ -739,7 +743,7 @@ static GLFWapplicationshouldhandlereopenfun handle_reopen_callback = NULL;
|
||||
}
|
||||
|
||||
const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
|
||||
NSTrackingActiveInKeyWindow |
|
||||
NSTrackingActiveAlways |
|
||||
NSTrackingEnabledDuringMouseDrag |
|
||||
NSTrackingCursorUpdate |
|
||||
NSTrackingInVisibleRect |
|
||||
@@ -845,7 +849,7 @@ is_ascii_control_char(char x) {
|
||||
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; // dont send text for ascii control codes
|
||||
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // don't send text for ascii control codes
|
||||
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
||||
}
|
||||
|
||||
@@ -884,15 +888,10 @@ is_ascii_control_char(char x) {
|
||||
|
||||
deltaX = [event scrollingDeltaX];
|
||||
deltaY = [event scrollingDeltaY];
|
||||
|
||||
if ([event hasPreciseScrollingDeltas])
|
||||
{
|
||||
deltaX *= 0.1;
|
||||
deltaY *= 0.1;
|
||||
}
|
||||
int flags = [event hasPreciseScrollingDeltas] ? 1 : 0;
|
||||
|
||||
if (fabs(deltaX) > 0.0 || fabs(deltaY) > 0.0)
|
||||
_glfwInputScroll(window, deltaX, deltaY);
|
||||
_glfwInputScroll(window, deltaX, deltaY, flags);
|
||||
}
|
||||
|
||||
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
|
||||
@@ -1045,6 +1044,18 @@ is_ascii_control_char(char x) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)toggleFullScreen:(nullable id)sender
|
||||
{
|
||||
GLFWContentView *view = [self contentView];
|
||||
if (view)
|
||||
{
|
||||
_GLFWwindow *window = [view glfwWindow];
|
||||
if (window && window->ns.toggleFullscreenCallback && window->ns.toggleFullscreenCallback((GLFWwindow*)window) == 1)
|
||||
return;
|
||||
}
|
||||
[super toggleFullScreen:sender];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -1061,18 +1072,39 @@ is_ascii_control_char(char x) {
|
||||
|
||||
@implementation GLFWApplication
|
||||
|
||||
// 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.
|
||||
- (void)sendEvent:(NSEvent *)event
|
||||
{
|
||||
if ([event type] == NSEventTypeKeyUp &&
|
||||
([event modifierFlags] & NSEventModifierFlagCommand))
|
||||
{
|
||||
[[self keyWindow] sendEvent: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;
|
||||
}
|
||||
else
|
||||
[super sendEvent:event];
|
||||
|
||||
[super sendEvent:event];
|
||||
}
|
||||
|
||||
|
||||
@@ -1083,7 +1115,7 @@ is_ascii_control_char(char x) {
|
||||
}
|
||||
|
||||
- (void)loadMainMenu
|
||||
{ // removed by Kovid as it generated compiler warnings
|
||||
{ // removed by Kovid as it generated compiler warnings
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1886,44 +1918,44 @@ int _glfwPlatformGetKeyScancode(int key)
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
int xhot, int yhot, int count)
|
||||
{
|
||||
NSImage* native;
|
||||
NSBitmapImageRep* rep;
|
||||
|
||||
if (!initializeAppKit())
|
||||
return GLFW_FALSE;
|
||||
|
||||
rep = [[NSBitmapImageRep alloc]
|
||||
initWithBitmapDataPlanes:NULL
|
||||
pixelsWide:image->width
|
||||
pixelsHigh:image->height
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:4
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSCalibratedRGBColorSpace
|
||||
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
||||
bytesPerRow:image->width * 4
|
||||
bitsPerPixel:32];
|
||||
|
||||
if (rep == nil)
|
||||
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
||||
if (native == nil)
|
||||
return GLFW_FALSE;
|
||||
|
||||
memcpy([rep bitmapData], image->pixels, image->width * image->height * 4);
|
||||
for (int i = 0; i < count; i++) {
|
||||
const GLFWimage *src = image + i;
|
||||
rep = [[NSBitmapImageRep alloc]
|
||||
initWithBitmapDataPlanes:NULL
|
||||
pixelsWide:src->width
|
||||
pixelsHigh:src->height
|
||||
bitsPerSample:8
|
||||
samplesPerPixel:4
|
||||
hasAlpha:YES
|
||||
isPlanar:NO
|
||||
colorSpaceName:NSCalibratedRGBColorSpace
|
||||
bitmapFormat:NSAlphaNonpremultipliedBitmapFormat
|
||||
bytesPerRow:src->width * 4
|
||||
bitsPerPixel:32];
|
||||
if (rep == nil)
|
||||
return GLFW_FALSE;
|
||||
|
||||
native = [[NSImage alloc] initWithSize:NSMakeSize(image->width, image->height)];
|
||||
[native addRepresentation:rep];
|
||||
memcpy([rep bitmapData], src->pixels, src->width * src->height * 4);
|
||||
[native addRepresentation:rep];
|
||||
[rep release];
|
||||
}
|
||||
|
||||
cursor->ns.object = [[NSCursor alloc] initWithImage:native
|
||||
hotSpot:NSMakePoint(xhot, yhot)];
|
||||
|
||||
[native release];
|
||||
[rep release];
|
||||
|
||||
if (cursor->ns.object == nil)
|
||||
return GLFW_FALSE;
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
@@ -2098,6 +2130,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;
|
||||
|
||||
295
glfw/dbus_glfw.c
vendored
Normal file
295
glfw/dbus_glfw.c
vendored
Normal file
@@ -0,0 +1,295 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
|
||||
#include "internal.h"
|
||||
#include "dbus_glfw.h"
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline void
|
||||
report_error(DBusError *err, const char *fmt, ...) {
|
||||
static char buf[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
int n = vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
snprintf(buf + n, sizeof(buf), ". DBUS error: %s", err->message);
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", buf);
|
||||
dbus_error_free(err);
|
||||
}
|
||||
|
||||
static _GLFWDBUSData *dbus_data = NULL;
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld) {
|
||||
dbus->eld = eld;
|
||||
dbus_data = dbus;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_watch_ready(int fd, int events, void *data) {
|
||||
DBusWatch *watch = (DBusWatch*)data;
|
||||
unsigned int flags = 0;
|
||||
if (events & POLLERR) flags |= DBUS_WATCH_ERROR;
|
||||
if (events & POLLHUP) flags |= DBUS_WATCH_HANGUP;
|
||||
if (events & POLLIN) flags |= DBUS_WATCH_READABLE;
|
||||
if (events & POLLOUT) flags |= DBUS_WATCH_WRITABLE;
|
||||
dbus_watch_handle(watch, flags);
|
||||
}
|
||||
|
||||
static inline int
|
||||
events_for_watch(DBusWatch *watch) {
|
||||
int events = 0;
|
||||
unsigned int flags = dbus_watch_get_flags(watch);
|
||||
if (flags & DBUS_WATCH_READABLE) events |= POLLIN;
|
||||
if (flags & DBUS_WATCH_WRITABLE) events |= POLLOUT;
|
||||
return events;
|
||||
}
|
||||
|
||||
static dbus_bool_t
|
||||
add_dbus_watch(DBusWatch *watch, void *data) {
|
||||
id_type watch_id = addWatch(dbus_data->eld, data, dbus_watch_get_unix_fd(watch), events_for_watch(watch), dbus_watch_get_enabled(watch), on_dbus_watch_ready, watch);
|
||||
if (!watch_id) return FALSE;
|
||||
id_type *idp = malloc(sizeof(id_type));
|
||||
if (!idp) return FALSE;
|
||||
*idp = watch_id;
|
||||
dbus_watch_set_data(watch, idp, free);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
remove_dbus_watch(DBusWatch *watch, void *data) {
|
||||
id_type *idp = dbus_watch_get_data(watch);
|
||||
if (idp) removeWatch(dbus_data->eld, *idp);
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_dbus_watch(DBusWatch *watch, void *data) {
|
||||
id_type *idp = dbus_watch_get_data(watch);
|
||||
if (idp) toggleWatch(dbus_data->eld, *idp, dbus_watch_get_enabled(watch));
|
||||
}
|
||||
|
||||
static void
|
||||
on_dbus_timer_ready(id_type timer_id, void *data) {
|
||||
if (data) {
|
||||
DBusTimeout *t = (DBusTimeout*)data;
|
||||
dbus_timeout_handle(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static dbus_bool_t
|
||||
add_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||
int enabled = dbus_timeout_get_enabled(timeout) ? 1 : 0;
|
||||
double interval = ((double)dbus_timeout_get_interval(timeout)) / 1000.0;
|
||||
if (interval < 0) return FALSE;
|
||||
id_type timer_id = addTimer(dbus_data->eld, data, interval, enabled, on_dbus_timer_ready, timeout);
|
||||
if (!timer_id) return FALSE;
|
||||
id_type *idp = malloc(sizeof(id_type));
|
||||
if (!idp) {
|
||||
removeTimer(dbus_data->eld, timer_id);
|
||||
return FALSE;
|
||||
}
|
||||
*idp = timer_id;
|
||||
dbus_timeout_set_data(timeout, idp, free);
|
||||
return TRUE;
|
||||
|
||||
}
|
||||
|
||||
static void
|
||||
remove_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||
id_type *idp = dbus_timeout_get_data(timeout);
|
||||
if (idp) removeTimer(dbus_data->eld, *idp);
|
||||
}
|
||||
|
||||
static void
|
||||
toggle_dbus_timeout(DBusTimeout *timeout, void *data) {
|
||||
id_type *idp = dbus_timeout_get_data(timeout);
|
||||
if (idp) toggleTimer(dbus_data->eld, *idp, dbus_timeout_get_enabled(timeout));
|
||||
}
|
||||
|
||||
|
||||
DBusConnection*
|
||||
glfw_dbus_connect_to(const char *path, const char* err_msg, const char *name, GLFWbool register_on_bus) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
DBusConnection *ans = dbus_connection_open_private(path, &err);
|
||||
if (!ans) {
|
||||
report_error(&err, err_msg);
|
||||
return NULL;
|
||||
}
|
||||
dbus_connection_set_exit_on_disconnect(ans, FALSE);
|
||||
dbus_error_free(&err);
|
||||
if (register_on_bus) {
|
||||
if (!dbus_bus_register(ans, &err)) {
|
||||
report_error(&err, err_msg);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (!dbus_connection_set_watch_functions(ans, add_dbus_watch, remove_dbus_watch, toggle_dbus_watch, (void*)name, NULL)) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS watches on connection to: %s", path);
|
||||
dbus_connection_close(ans);
|
||||
dbus_connection_unref(ans);
|
||||
return NULL;
|
||||
}
|
||||
if (!dbus_connection_set_timeout_functions(ans, add_dbus_timeout, remove_dbus_timeout, toggle_dbus_timeout, (void*)name, NULL)) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set DBUS timeout functions on connection to: %s", path);
|
||||
dbus_connection_close(ans);
|
||||
dbus_connection_unref(ans);
|
||||
return NULL;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_dbus_dispatch(DBusConnection *conn) {
|
||||
while(dbus_connection_dispatch(conn) == DBUS_DISPATCH_DATA_REMAINS);
|
||||
}
|
||||
|
||||
void
|
||||
glfw_dbus_terminate(_GLFWDBUSData *dbus) {
|
||||
if (dbus_data) {
|
||||
dbus_data->eld = NULL;
|
||||
dbus_data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
glfw_dbus_close_connection(DBusConnection *conn) {
|
||||
dbus_connection_close(conn);
|
||||
dbus_connection_unref(conn);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
va_list ap;
|
||||
va_start(ap, failmsg);
|
||||
int firstarg = va_arg(ap, int);
|
||||
GLFWbool ret = dbus_message_get_args_valist(msg, &err, firstarg, ap) ? GLFW_TRUE : GLFW_FALSE;
|
||||
va_end(ap);
|
||||
if (!ret) report_error(&err, failmsg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
dbus_pending_callback callback;
|
||||
void *user_data;
|
||||
} MethodResponse;
|
||||
|
||||
static const char*
|
||||
format_message_error(DBusError *err) {
|
||||
static char buf[1024];
|
||||
snprintf(buf, sizeof(buf), "[%s] %s", err->name ? err->name : "", err->message);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
method_reply_received(DBusPendingCall *pending, void *user_data) {
|
||||
MethodResponse *res = (MethodResponse*)user_data;
|
||||
DBusMessage *msg = dbus_pending_call_steal_reply(pending);
|
||||
if (msg) {
|
||||
DBusError err;
|
||||
dbus_error_init(&err);
|
||||
if (dbus_set_error_from_message(&err, msg)) res->callback(NULL, format_message_error(&err), res->user_data);
|
||||
else res->callback(msg, NULL, res->user_data);
|
||||
dbus_message_unref(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static GLFWbool
|
||||
call_method(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void *user_data, va_list ap) {
|
||||
if (!conn) return GLFW_FALSE;
|
||||
DBusMessage *msg = dbus_message_new_method_call(node, path, interface, method);
|
||||
if (!msg) return GLFW_FALSE;
|
||||
GLFWbool retval = GLFW_FALSE;
|
||||
MethodResponse *res = malloc(sizeof(MethodResponse));
|
||||
if (!res) { dbus_message_unref(msg); return GLFW_FALSE; }
|
||||
res->callback = callback;
|
||||
res->user_data = user_data;
|
||||
|
||||
int firstarg = va_arg(ap, int);
|
||||
if ((firstarg == DBUS_TYPE_INVALID) || dbus_message_append_args_valist(msg, firstarg, ap)) {
|
||||
if (callback) {
|
||||
DBusPendingCall *pending = NULL;
|
||||
if (dbus_connection_send_with_reply(conn, msg, &pending, timeout)) {
|
||||
dbus_pending_call_set_notify(pending, method_reply_received, res, free);
|
||||
retval = GLFW_TRUE;
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
|
||||
}
|
||||
} else {
|
||||
if (dbus_connection_send(conn, msg, NULL)) {
|
||||
retval = GLFW_TRUE;
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s out of memory", method, node, interface);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to call DBUS method: %s on node: %s and interface: %s could not add arguments", method, node, interface);
|
||||
}
|
||||
dbus_message_unref(msg);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout, dbus_pending_callback callback, void* user_data, ...) {
|
||||
GLFWbool retval;
|
||||
va_list ap;
|
||||
va_start(ap, user_data);
|
||||
retval = call_method(conn, node, path, interface, method, timeout, callback, user_data, ap);
|
||||
va_end(ap);
|
||||
return retval;
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...) {
|
||||
GLFWbool retval;
|
||||
va_list ap;
|
||||
va_start(ap, method);
|
||||
retval = call_method(conn, node, path, interface, method, DBUS_TIMEOUT_USE_DEFAULT, NULL, NULL, ap);
|
||||
va_end(ap);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, interface);
|
||||
int ans = -1, num = -1;
|
||||
while(1) {
|
||||
num++;
|
||||
const char *name = va_arg(ap, const char*);
|
||||
if (!name) break;
|
||||
if (dbus_message_is_signal(msg, interface, name)) { ans = num; break; }
|
||||
}
|
||||
va_end(ap);
|
||||
return ans;
|
||||
}
|
||||
50
glfw/dbus_glfw.h
vendored
Normal file
50
glfw/dbus_glfw.h
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dbus/dbus.h>
|
||||
#include "backend_utils.h"
|
||||
|
||||
typedef void(*dbus_pending_callback)(DBusMessage *msg, const char* err, void* data);
|
||||
|
||||
typedef struct {
|
||||
EventLoopData* eld;
|
||||
} _GLFWDBUSData;
|
||||
|
||||
|
||||
GLFWbool glfw_dbus_init(_GLFWDBUSData *dbus, EventLoopData *eld);
|
||||
void glfw_dbus_terminate(_GLFWDBUSData *dbus);
|
||||
DBusConnection* glfw_dbus_connect_to(const char *path, const char* err_msg, const char* name, GLFWbool register_on_bus);
|
||||
void glfw_dbus_close_connection(DBusConnection *conn);
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_no_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, ...);
|
||||
GLFWbool
|
||||
glfw_dbus_call_method_with_reply(DBusConnection *conn, const char *node, const char *path, const char *interface, const char *method, int timeout_ms, dbus_pending_callback callback, void *user_data, ...);
|
||||
void glfw_dbus_dispatch(DBusConnection *);
|
||||
GLFWbool glfw_dbus_get_args(DBusMessage *msg, const char *failmsg, ...);
|
||||
int glfw_dbus_match_signal(DBusMessage *msg, const char *interface, ...);
|
||||
53
glfw/glfw.py
53
glfw/glfw.py
@@ -55,7 +55,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
at_least_version('xkbcommon', 0, 5)
|
||||
|
||||
if module == 'x11':
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb'.split():
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb dbus-1'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
|
||||
@@ -67,11 +67,13 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
|
||||
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
|
||||
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
|
||||
scanner_version = tuple(map(int, pkg_config('wayland-scanner', '--modversion')[0].strip().split('.')))
|
||||
ans.wayland_scanner_code = 'private-code' if scanner_version >= (1, 14, 91) else 'code'
|
||||
ans.wayland_protocols = tuple(sinfo[module]['protocols'])
|
||||
for p in ans.wayland_protocols:
|
||||
ans.sources.append(wayland_protocol_file_name(p))
|
||||
ans.all_headers.append(wayland_protocol_file_name(p, 'h'))
|
||||
for dep in 'wayland-egl wayland-client wayland-cursor xkbcommon'.split():
|
||||
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'))
|
||||
|
||||
@@ -87,7 +89,7 @@ def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
|
||||
dest = wayland_protocol_file_name(src, ext)
|
||||
dest = os.path.join(dest_dir, dest)
|
||||
if newer(dest, src):
|
||||
q = 'client-header' if ext == 'h' else 'code'
|
||||
q = 'client-header' if ext == 'h' else env.wayland_scanner_code
|
||||
run_tool([env.wayland_scanner, q, src, dest],
|
||||
desc='Generating {} ...'.format(emphasis(os.path.basename(dest))))
|
||||
|
||||
@@ -126,16 +128,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):
|
||||
@@ -202,6 +194,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
void* glfwGetCocoaWindow(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)
|
||||
@@ -221,11 +214,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*);
|
||||
|
||||
{}
|
||||
|
||||
const char* load_glfw(const char* path);
|
||||
@@ -263,8 +258,8 @@ unload_glfw() {
|
||||
f.write(code)
|
||||
|
||||
|
||||
def main():
|
||||
os.chdir(sys.argv[-1])
|
||||
def from_glfw(glfw_dir):
|
||||
os.chdir(glfw_dir)
|
||||
sinfo = collect_source_information()
|
||||
files_to_copy = set()
|
||||
for x in sinfo.values():
|
||||
@@ -281,9 +276,6 @@ def main():
|
||||
for src in files_to_copy:
|
||||
shutil.copy2(src, '.')
|
||||
shutil.copy2(glfw_header, '.')
|
||||
patch_in_file('internal.h', lambda x: x.replace('../include/GLFW/', ''))
|
||||
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'),
|
||||
@@ -294,5 +286,26 @@ def main():
|
||||
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')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
43
glfw/glfw3.h
vendored
43
glfw/glfw3.h
vendored
@@ -973,6 +973,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 +984,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
|
||||
@@ -1058,6 +1064,7 @@ extern "C" {
|
||||
* @{ */
|
||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
||||
|
||||
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
||||
#define GLFW_COCOA_MENUBAR 0x00051002
|
||||
@@ -1372,15 +1379,17 @@ 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.
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
*
|
||||
* @since Added in version 3.0. Replaces `GLFWmousewheelfun`.
|
||||
* @since Changed in version 4.0. Added `flags` parameter.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
|
||||
/*! @brief The function signature for key callbacks.
|
||||
*
|
||||
@@ -1399,7 +1408,9 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
|
||||
* held down.
|
||||
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
|
||||
* @param[in] reserved Reserved for future use.
|
||||
* @param[in] Used for Input Method events. Zero for normal key events.
|
||||
* A value of 1 means the pre-edit text for the input event has been changed.
|
||||
* A value of 2 means the text should be committed.
|
||||
*
|
||||
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
|
||||
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
|
||||
@@ -4007,8 +4018,8 @@ GLFWAPI int glfwGetKey(GLFWwindow* window, int key);
|
||||
* `GLFW_RELEASE`.
|
||||
*
|
||||
* If the @ref GLFW_STICKY_MOUSE_BUTTONS input mode is enabled, this function
|
||||
* `GLFW_PRESS` the first time you call it for a mouse button that was pressed,
|
||||
* even if that mouse button has already been released.
|
||||
* returns `GLFW_PRESS` the first time you call it for a mouse button that was
|
||||
* pressed, even if that mouse button has already been released.
|
||||
*
|
||||
* @param[in] window The desired window.
|
||||
* @param[in] button The desired [mouse button](@ref buttons).
|
||||
@@ -4123,6 +4134,7 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
|
||||
* @param[in] image The desired cursor image.
|
||||
* @param[in] xhot The desired x-coordinate, in pixels, of the cursor hotspot.
|
||||
* @param[in] yhot The desired y-coordinate, in pixels, of the cursor hotspot.
|
||||
* @param[in] count The number of images. Used on Cocoa for retina cursors. The first image should be the 1:1 scale image.
|
||||
* @return The handle of the created cursor, or `NULL` if an
|
||||
* [error](@ref error_handling) occurred.
|
||||
*
|
||||
@@ -4138,11 +4150,11 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* window, double xpos, double ypos);
|
||||
* @sa @ref glfwDestroyCursor
|
||||
* @sa @ref glfwCreateStandardCursor
|
||||
*
|
||||
* @since Added in version 3.1.
|
||||
* @since Added in version 3.1. Changed in 4.0 to add the count parameter.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot);
|
||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count);
|
||||
|
||||
/*! @brief Creates a cursor with a standard shape.
|
||||
*
|
||||
@@ -4227,6 +4239,23 @@ GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
|
||||
*/
|
||||
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun);
|
||||
|
||||
/*! @brief Notifies the OS Input Method Event system of changes to application input state
|
||||
*
|
||||
* Used to notify the IME system of changes in state such as focus gained/lost
|
||||
* and text cursor position.
|
||||
*
|
||||
* @param which: What data to notify. 1 means focus and 2 means cursor position.
|
||||
* @param a, b, c, d: Interpreted based on the value of which. When which is 1
|
||||
* a is interpreted as a boolean indicating focus gained/lost. When which is 2
|
||||
* a, b, c, d are the cursor x, y, width and height values (in the window co-ordinate
|
||||
* system).
|
||||
*
|
||||
* @ingroup input
|
||||
* @since Added in version 4.0
|
||||
*/
|
||||
GLFWAPI void glfwUpdateIMEState(GLFWwindow* window, int which, int a, int b, int c, int d);
|
||||
|
||||
|
||||
/*! @brief Sets the mouse button callback.
|
||||
*
|
||||
* This function sets the mouse button callback of the specified window, which
|
||||
@@ -4787,6 +4816,8 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_INVALID_ENUM.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref gamepad
|
||||
* @sa @ref glfwUpdateGamepadMappings
|
||||
* @sa @ref glfwJoystickIsGamepad
|
||||
|
||||
426
glfw/ibus_glfw.c
vendored
Normal file
426
glfw/ibus_glfw.c
vendored
Normal file
@@ -0,0 +1,426 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "internal.h"
|
||||
#include "ibus_glfw.h"
|
||||
|
||||
#define debug(...) if (_glfw.hints.init.debugKeyboard) printf(__VA_ARGS__);
|
||||
static const char IBUS_SERVICE[] = "org.freedesktop.IBus";
|
||||
static const char IBUS_PATH[] = "/org/freedesktop/IBus";
|
||||
static const char IBUS_INTERFACE[] = "org.freedesktop.IBus";
|
||||
static const char IBUS_INPUT_INTERFACE[] = "org.freedesktop.IBus.InputContext";
|
||||
enum Capabilities {
|
||||
IBUS_CAP_PREEDIT_TEXT = 1 << 0,
|
||||
IBUS_CAP_AUXILIARY_TEXT = 1 << 1,
|
||||
IBUS_CAP_LOOKUP_TABLE = 1 << 2,
|
||||
IBUS_CAP_FOCUS = 1 << 3,
|
||||
IBUS_CAP_PROPERTY = 1 << 4,
|
||||
IBUS_CAP_SURROUNDING_TEXT = 1 << 5
|
||||
};
|
||||
|
||||
|
||||
static inline GLFWbool
|
||||
test_env_var(const char *name, const char *val) {
|
||||
const char *q = getenv(name);
|
||||
return (q && strcmp(q, val) == 0) ? GLFW_TRUE : GLFW_FALSE;
|
||||
}
|
||||
|
||||
static inline GLFWbool
|
||||
MIN(size_t a, size_t b) {
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
static const char*
|
||||
get_ibus_text_from_message(DBusMessage *msg) {
|
||||
/* The message structure is (from dbus-monitor)
|
||||
variant struct {
|
||||
string "IBusText"
|
||||
array [
|
||||
]
|
||||
string "ash "
|
||||
variant struct {
|
||||
string "IBusAttrList"
|
||||
array [
|
||||
]
|
||||
array [
|
||||
]
|
||||
}
|
||||
}
|
||||
*/
|
||||
const char *text = NULL;
|
||||
const char *struct_id = NULL;
|
||||
DBusMessageIter iter, sub1, sub2;
|
||||
dbus_message_iter_init(msg, &iter);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) return NULL;
|
||||
|
||||
dbus_message_iter_recurse(&iter, &sub1);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&sub1) != DBUS_TYPE_STRUCT) return NULL;
|
||||
|
||||
dbus_message_iter_recurse(&sub1, &sub2);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
|
||||
|
||||
dbus_message_iter_get_basic(&sub2, &struct_id);
|
||||
if (!struct_id || strncmp(struct_id, "IBusText", sizeof("IBusText")) != 0) return NULL;
|
||||
|
||||
dbus_message_iter_next(&sub2);
|
||||
dbus_message_iter_next(&sub2);
|
||||
|
||||
if (dbus_message_iter_get_arg_type(&sub2) != DBUS_TYPE_STRING) return NULL;
|
||||
|
||||
dbus_message_iter_get_basic(&sub2, &text);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
static inline void
|
||||
send_text(const char *text, int state) {
|
||||
_GLFWwindow *w = _glfwFocusedWindow();
|
||||
if (w && w->callbacks.keyboard) {
|
||||
w->callbacks.keyboard((GLFWwindow*) w, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, text, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Connection handling {{{
|
||||
|
||||
static DBusHandlerResult
|
||||
message_handler(DBusConnection *conn, DBusMessage *msg, void *user_data) {
|
||||
// To monitor signals from IBUS, use
|
||||
// dbus-monitor --address `ibus address` "type='signal',interface='org.freedesktop.IBus.InputContext'"
|
||||
_GLFWIBUSData *ibus = (_GLFWIBUSData*)user_data;
|
||||
(void)ibus;
|
||||
const char *text;
|
||||
switch(glfw_dbus_match_signal(msg, IBUS_INPUT_INTERFACE, "CommitText", "UpdatePreeditText", "HidePreeditText", "ShowPreeditText", NULL)) {
|
||||
case 0:
|
||||
text = get_ibus_text_from_message(msg);
|
||||
debug("IBUS: CommitText: '%s'\n", text ? text : "(nil)");
|
||||
send_text(text, 2);
|
||||
break;
|
||||
case 1:
|
||||
text = get_ibus_text_from_message(msg);
|
||||
send_text(text, 1);
|
||||
debug("IBUS: UpdatePreeditText: '%s'\n", text ? text : "(nil)");
|
||||
break;
|
||||
case 2:
|
||||
debug("IBUS: HidePreeditText\n");
|
||||
break;
|
||||
case 3:
|
||||
debug("IBUS: ShowPreeditText\n");
|
||||
break;
|
||||
}
|
||||
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
get_ibus_address_file_name(void) {
|
||||
const char *addr;
|
||||
static char ans[PATH_MAX];
|
||||
addr = getenv("IBUS_ADDRESS");
|
||||
int offset = 0;
|
||||
if (addr && addr[0]) {
|
||||
memcpy(ans, addr, MIN(strlen(addr), sizeof(ans)));
|
||||
return ans;
|
||||
}
|
||||
|
||||
const char *de = getenv("DISPLAY");
|
||||
if (!de || !de[0]) de = ":0.0";
|
||||
char *display = _glfw_strdup(de);
|
||||
const char *host = display;
|
||||
char *disp_num = strrchr(display, ':');
|
||||
char *screen_num = strrchr(display, '.');
|
||||
|
||||
if (!disp_num) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as DISPLAY env var has no colon");
|
||||
free(display);
|
||||
return NULL;
|
||||
}
|
||||
*disp_num = 0;
|
||||
disp_num++;
|
||||
if (screen_num) *screen_num = 0;
|
||||
if (!*host) host = "unix";
|
||||
|
||||
memset(ans, 0, sizeof(ans));
|
||||
const char *conf_env = getenv("XDG_CONFIG_HOME");
|
||||
if (conf_env && conf_env[0]) {
|
||||
offset = snprintf(ans, sizeof(ans), "%s", conf_env);
|
||||
} else {
|
||||
conf_env = getenv("HOME");
|
||||
if (!conf_env || !conf_env[0]) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not get IBUS address file name as no HOME env var is set");
|
||||
free(display);
|
||||
return NULL;
|
||||
}
|
||||
offset = snprintf(ans, sizeof(ans), "%s/.config", conf_env);
|
||||
}
|
||||
char *key = dbus_get_local_machine_id();
|
||||
snprintf(ans + offset, sizeof(ans) - offset, "/ibus/bus/%s-%s-%s", key, host, disp_num);
|
||||
dbus_free(key);
|
||||
free(display);
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
static inline GLFWbool
|
||||
read_ibus_address(_GLFWIBUSData *ibus) {
|
||||
static char buf[1024];
|
||||
struct stat s;
|
||||
FILE *addr_file = fopen(ibus->address_file_name, "r");
|
||||
if (!addr_file) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to open IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
int stat_result = fstat(fileno(addr_file), &s);
|
||||
GLFWbool found = GLFW_FALSE;
|
||||
while (fgets(buf, sizeof(buf), addr_file)) {
|
||||
if (strncmp(buf, "IBUS_ADDRESS=", sizeof("IBUS_ADDRESS=")-1) == 0) {
|
||||
size_t sz = strlen(buf);
|
||||
if (buf[sz-1] == '\n') buf[sz-1] = 0;
|
||||
if (buf[sz-2] == '\r') buf[sz-2] = 0;
|
||||
found = GLFW_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(addr_file); addr_file = NULL;
|
||||
if (stat_result != 0) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to stat IBUS address file: %s with error: %s", ibus->address_file_name, strerror(errno));
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
ibus->address_file_mtime = s.st_mtime;
|
||||
if (found) {
|
||||
free((void*)ibus->address);
|
||||
ibus->address = _glfw_strdup(buf + sizeof("IBUS_ADDRESS=") - 1);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Could not find IBUS_ADDRESS in %s", ibus->address_file_name);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
input_context_created(DBusMessage *msg, const char* errmsg, void *data) {
|
||||
if (errmsg) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to create input context with error: %s", errmsg);
|
||||
return;
|
||||
}
|
||||
const char *path = NULL;
|
||||
if (!glfw_dbus_get_args(msg, "Failed to get IBUS context path from reply", DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) return;
|
||||
_GLFWIBUSData *ibus = (_GLFWIBUSData*)data;
|
||||
free((void*)ibus->input_ctx_path);
|
||||
ibus->input_ctx_path = _glfw_strdup(path);
|
||||
if (!ibus->input_ctx_path) return;
|
||||
dbus_bus_add_match(ibus->conn, "type='signal',interface='org.freedesktop.IBus.InputContext'", NULL);
|
||||
DBusObjectPathVTable ibus_vtable = {.message_function = message_handler};
|
||||
dbus_connection_try_register_object_path(ibus->conn, ibus->input_ctx_path, &ibus_vtable, ibus, NULL);
|
||||
enum Capabilities caps = IBUS_CAP_FOCUS | IBUS_CAP_PREEDIT_TEXT;
|
||||
if (!glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCapabilities", DBUS_TYPE_UINT32, &caps, DBUS_TYPE_INVALID)) return;
|
||||
ibus->ok = GLFW_TRUE;
|
||||
glfw_ibus_set_focused(ibus, GLFW_FALSE);
|
||||
glfw_ibus_set_cursor_geometry(ibus, 0, 0, 0, 0);
|
||||
debug("Connected to IBUS daemon for IME input management\n");
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
setup_connection(_GLFWIBUSData *ibus) {
|
||||
const char *client_name = "GLFW_Application";
|
||||
const char *address_file_name = get_ibus_address_file_name();
|
||||
ibus->ok = GLFW_FALSE;
|
||||
if (!address_file_name) return GLFW_FALSE;
|
||||
free((void*)ibus->address_file_name);
|
||||
ibus->address_file_name = _glfw_strdup(address_file_name);
|
||||
if (!read_ibus_address(ibus)) return GLFW_FALSE;
|
||||
if (ibus->conn) {
|
||||
glfw_dbus_close_connection(ibus->conn);
|
||||
ibus->conn = NULL;
|
||||
}
|
||||
debug("Connecting to IBUS daemon @ %s for IME input management\n", ibus->address);
|
||||
ibus->conn = glfw_dbus_connect_to(ibus->address, "Failed to connect to the IBUS daemon, with error", "ibus", GLFW_FALSE);
|
||||
if (!ibus->conn) return GLFW_FALSE;
|
||||
free((void*)ibus->input_ctx_path); ibus->input_ctx_path = NULL;
|
||||
if (!glfw_dbus_call_method_with_reply(
|
||||
ibus->conn, IBUS_SERVICE, IBUS_PATH, IBUS_INTERFACE, "CreateInputContext", DBUS_TIMEOUT_USE_DEFAULT, input_context_created, ibus,
|
||||
DBUS_TYPE_STRING, &client_name, DBUS_TYPE_INVALID)) {
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
glfw_connect_to_ibus(_GLFWIBUSData *ibus) {
|
||||
if (ibus->inited) return;
|
||||
if (!test_env_var("GLFW_IM_MODULE", "ibus")) return;
|
||||
ibus->inited = GLFW_TRUE;
|
||||
setup_connection(ibus);
|
||||
}
|
||||
|
||||
void
|
||||
glfw_ibus_terminate(_GLFWIBUSData *ibus) {
|
||||
if (ibus->conn) {
|
||||
glfw_dbus_close_connection(ibus->conn);
|
||||
ibus->conn = NULL;
|
||||
}
|
||||
#define F(x) if (ibus->x) { free((void*)ibus->x); ibus->x = NULL; }
|
||||
F(input_ctx_path);
|
||||
F(address);
|
||||
F(address_file_name);
|
||||
#undef F
|
||||
|
||||
ibus->ok = GLFW_FALSE;
|
||||
}
|
||||
|
||||
static GLFWbool
|
||||
check_connection(_GLFWIBUSData *ibus) {
|
||||
if (!ibus->inited) return GLFW_FALSE;
|
||||
if (ibus->conn && dbus_connection_get_is_connected(ibus->conn)) {
|
||||
return ibus->ok;
|
||||
}
|
||||
struct stat s;
|
||||
if (stat(ibus->address_file_name, &s) != 0 || s.st_mtime != ibus->address_file_mtime) {
|
||||
if (!read_ibus_address(ibus)) return GLFW_FALSE;
|
||||
setup_connection(ibus);
|
||||
}
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
glfw_ibus_dispatch(_GLFWIBUSData *ibus) {
|
||||
if (ibus->conn) glfw_dbus_dispatch(ibus->conn);
|
||||
}
|
||||
// }}}
|
||||
|
||||
static void
|
||||
simple_message(_GLFWIBUSData *ibus, const char *method) {
|
||||
if (check_connection(ibus)) {
|
||||
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, method, DBUS_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused) {
|
||||
simple_message(ibus, focused ? "FocusIn" : "FocusOut");
|
||||
}
|
||||
|
||||
void
|
||||
glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h) {
|
||||
if (check_connection(ibus)) {
|
||||
glfw_dbus_call_method_no_reply(ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "SetCursorLocation",
|
||||
DBUS_TYPE_INT32, &x, DBUS_TYPE_INT32, &y, DBUS_TYPE_INT32, &w, DBUS_TYPE_INT32, &h, DBUS_TYPE_INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum
|
||||
{
|
||||
IBUS_SHIFT_MASK = 1 << 0,
|
||||
IBUS_LOCK_MASK = 1 << 1,
|
||||
IBUS_CONTROL_MASK = 1 << 2,
|
||||
IBUS_MOD1_MASK = 1 << 3,
|
||||
IBUS_MOD2_MASK = 1 << 4,
|
||||
IBUS_MOD3_MASK = 1 << 5,
|
||||
IBUS_MOD4_MASK = 1 << 6,
|
||||
IBUS_MOD5_MASK = 1 << 7,
|
||||
IBUS_BUTTON1_MASK = 1 << 8,
|
||||
IBUS_BUTTON2_MASK = 1 << 9,
|
||||
IBUS_BUTTON3_MASK = 1 << 10,
|
||||
IBUS_BUTTON4_MASK = 1 << 11,
|
||||
IBUS_BUTTON5_MASK = 1 << 12,
|
||||
|
||||
/* The next few modifiers are used by XKB, so we skip to the end.
|
||||
* Bits 15 - 23 are currently unused. Bit 29 is used internally.
|
||||
*/
|
||||
|
||||
/* ibus mask */
|
||||
IBUS_HANDLED_MASK = 1 << 24,
|
||||
IBUS_FORWARD_MASK = 1 << 25,
|
||||
IBUS_IGNORED_MASK = IBUS_FORWARD_MASK,
|
||||
|
||||
IBUS_SUPER_MASK = 1 << 26,
|
||||
IBUS_HYPER_MASK = 1 << 27,
|
||||
IBUS_META_MASK = 1 << 28,
|
||||
|
||||
IBUS_RELEASE_MASK = 1 << 30,
|
||||
|
||||
IBUS_MODIFIER_MASK = 0x5f001fff
|
||||
} IBusModifierType;
|
||||
|
||||
|
||||
static inline uint32_t
|
||||
ibus_key_state(unsigned int glfw_modifiers, int action) {
|
||||
uint32_t ans = action == GLFW_RELEASE ? IBUS_RELEASE_MASK : 0;
|
||||
#define M(g, i) if(glfw_modifiers & GLFW_MOD_##g) ans |= i
|
||||
M(SHIFT, IBUS_SHIFT_MASK);
|
||||
M(CAPS_LOCK, IBUS_LOCK_MASK);
|
||||
M(CONTROL, IBUS_CONTROL_MASK);
|
||||
M(ALT, IBUS_MOD1_MASK);
|
||||
M(NUM_LOCK, IBUS_MOD2_MASK);
|
||||
M(SUPER, IBUS_MOD4_MASK);
|
||||
#undef M
|
||||
return ans;
|
||||
}
|
||||
|
||||
void
|
||||
key_event_processed(DBusMessage *msg, const char* errmsg, void *data) {
|
||||
uint32_t handled = 0;
|
||||
KeyEvent *ev = (KeyEvent*)data;
|
||||
GLFWbool is_release = ev->action == GLFW_RELEASE;
|
||||
GLFWbool failed = GLFW_FALSE;
|
||||
if (errmsg) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "IBUS: Failed to process key with error: %s", errmsg);
|
||||
failed = GLFW_TRUE;
|
||||
} else {
|
||||
glfw_dbus_get_args(msg, "Failed to get IBUS handled key from reply", DBUS_TYPE_BOOLEAN, &handled, DBUS_TYPE_INVALID);
|
||||
debug("IBUS processed scancode: 0x%x release: %d handled: %u\n", ev->keycode, is_release, handled);
|
||||
}
|
||||
glfw_xkb_key_from_ime(ev, handled ? GLFW_TRUE : GLFW_FALSE, failed);
|
||||
free(ev);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus) {
|
||||
if (!check_connection(ibus)) return GLFW_FALSE;
|
||||
KeyEvent *ev = malloc(sizeof(KeyEvent));
|
||||
if (!ev) return GLFW_FALSE;
|
||||
memcpy(ev, ev_, sizeof(KeyEvent));
|
||||
uint32_t state = ibus_key_state(ev->glfw_modifiers, ev->action);
|
||||
if (!glfw_dbus_call_method_with_reply(
|
||||
ibus->conn, IBUS_SERVICE, ibus->input_ctx_path, IBUS_INPUT_INTERFACE, "ProcessKeyEvent",
|
||||
3000, key_event_processed, ev,
|
||||
DBUS_TYPE_UINT32, &ev->ibus_sym, DBUS_TYPE_UINT32, &ev->ibus_keycode, DBUS_TYPE_UINT32,
|
||||
&state, DBUS_TYPE_INVALID)) {
|
||||
free(ev);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
55
glfw/ibus_glfw.h
vendored
Normal file
55
glfw/ibus_glfw.h
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 XKB - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2018 Kovid Goyal <kovid@kovidgoyal.net>
|
||||
//
|
||||
// This software is provided 'as-is', without any express or implied
|
||||
// warranty. In no event will the authors be held liable for any damages
|
||||
// arising from the use of this software.
|
||||
//
|
||||
// Permission is granted to anyone to use this software for any purpose,
|
||||
// including commercial applications, and to alter it and redistribute it
|
||||
// freely, subject to the following restrictions:
|
||||
//
|
||||
// 1. The origin of this software must not be misrepresented; you must not
|
||||
// claim that you wrote the original software. If you use this software
|
||||
// in a product, an acknowledgment in the product documentation would
|
||||
// be appreciated but is not required.
|
||||
//
|
||||
// 2. Altered source versions must be plainly marked as such, and must not
|
||||
// be misrepresented as being the original software.
|
||||
//
|
||||
// 3. This notice may not be removed or altered from any source
|
||||
// distribution.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dbus_glfw.h"
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
|
||||
typedef struct {
|
||||
GLFWbool ok, inited;
|
||||
time_t address_file_mtime;
|
||||
DBusConnection *conn;
|
||||
const char *input_ctx_path, *address_file_name, *address;
|
||||
} _GLFWIBUSData;
|
||||
|
||||
typedef struct {
|
||||
xkb_keycode_t keycode, ibus_keycode;
|
||||
xkb_keysym_t keysym, ibus_sym;
|
||||
unsigned int glfw_modifiers;
|
||||
int action;
|
||||
GLFWid window_id;
|
||||
int glfw_keycode;
|
||||
char text[64];
|
||||
} KeyEvent;
|
||||
|
||||
void glfw_connect_to_ibus(_GLFWIBUSData *ibus);
|
||||
void glfw_ibus_terminate(_GLFWIBUSData *ibus);
|
||||
void glfw_ibus_set_focused(_GLFWIBUSData *ibus, GLFWbool focused);
|
||||
void glfw_ibus_dispatch(_GLFWIBUSData *ibus);
|
||||
GLFWbool ibus_process_key(const KeyEvent *ev_, _GLFWIBUSData *ibus);
|
||||
void glfw_ibus_set_cursor_geometry(_GLFWIBUSData *ibus, int x, int y, int w, int h);
|
||||
9
glfw/init.c
vendored
9
glfw/init.c
vendored
@@ -52,6 +52,7 @@ static _GLFWinitconfig _glfwInitHints =
|
||||
{
|
||||
GLFW_TRUE, // hat buttons
|
||||
GLFW_FALSE, // debug keyboard
|
||||
GLFW_TRUE, // enable joystick
|
||||
{
|
||||
GLFW_TRUE, // macOS menu bar
|
||||
GLFW_TRUE // macOS bundle chdir
|
||||
@@ -115,8 +116,9 @@ static void terminate(void)
|
||||
char* _glfw_strdup(const char* source)
|
||||
{
|
||||
const size_t length = strlen(source);
|
||||
char* result = calloc(length + 1, 1);
|
||||
strcpy(result, source);
|
||||
char* result = malloc(length + 1);
|
||||
memcpy(result, source, length);
|
||||
result[length] = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -253,6 +255,9 @@ GLFWAPI void glfwInitHint(int hint, int value)
|
||||
{
|
||||
switch (hint)
|
||||
{
|
||||
case GLFW_ENABLE_JOYSTICKS:
|
||||
_glfwInitHints.enableJoysticks = value;
|
||||
return;
|
||||
case GLFW_JOYSTICK_HAT_BUTTONS:
|
||||
_glfwInitHints.hatButtons = value;
|
||||
return;
|
||||
|
||||
21
glfw/input.c
vendored
21
glfw/input.c
vendored
@@ -286,10 +286,10 @@ void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action,
|
||||
|
||||
// Notifies shared code of a scroll event
|
||||
//
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags)
|
||||
{
|
||||
if (window->callbacks.scroll)
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset);
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset, flags);
|
||||
}
|
||||
|
||||
// Notifies shared code of a mouse button click event
|
||||
@@ -799,11 +799,12 @@ GLFWAPI void glfwSetCursorPos(GLFWwindow* handle, double xpos, double ypos)
|
||||
}
|
||||
}
|
||||
|
||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
|
||||
GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot, int count)
|
||||
{
|
||||
_GLFWcursor* cursor;
|
||||
|
||||
assert(image != NULL);
|
||||
assert(count > 0);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
|
||||
@@ -811,7 +812,7 @@ GLFWAPI GLFWcursor* glfwCreateCursor(const GLFWimage* image, int xhot, int yhot)
|
||||
cursor->next = _glfw.cursorListHead;
|
||||
_glfw.cursorListHead = cursor;
|
||||
|
||||
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot))
|
||||
if (!_glfwPlatformCreateCursor(cursor, image, xhot, yhot, count))
|
||||
{
|
||||
glfwDestroyCursor((GLFWcursor*) cursor);
|
||||
return NULL;
|
||||
@@ -908,6 +909,18 @@ GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboard
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwUpdateIMEState(GLFWwindow* handle, int which, int a, int b, int c, int d) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
#if defined(_GLFW_X11) || defined(_GLFW_WAYLAND)
|
||||
_glfwPlatformUpdateIMEState(window, which, a, b, c, d);
|
||||
#else
|
||||
(void)window; (void)which; (void)a; (void)b; (void)c; (void)d;
|
||||
#endif
|
||||
}
|
||||
|
||||
GLFWAPI GLFWmousebuttonfun glfwSetMouseButtonCallback(GLFWwindow* handle,
|
||||
GLFWmousebuttonfun cbfun)
|
||||
{
|
||||
|
||||
17
glfw/internal.h
vendored
17
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) || \
|
||||
@@ -59,6 +61,7 @@
|
||||
#define _GLFW_MESSAGE_SIZE 1024
|
||||
|
||||
typedef int GLFWbool;
|
||||
typedef unsigned long long GLFWid;
|
||||
|
||||
typedef struct _GLFWerror _GLFWerror;
|
||||
typedef struct _GLFWinitconfig _GLFWinitconfig;
|
||||
@@ -243,6 +246,7 @@ struct _GLFWinitconfig
|
||||
{
|
||||
GLFWbool hatButtons;
|
||||
GLFWbool debugKeyboard;
|
||||
GLFWbool enableJoysticks;
|
||||
struct {
|
||||
GLFWbool menubar;
|
||||
GLFWbool chdir;
|
||||
@@ -269,6 +273,7 @@ struct _GLFWwndconfig
|
||||
GLFWbool maximized;
|
||||
GLFWbool centerCursor;
|
||||
GLFWbool focusOnShow;
|
||||
GLFWbool scaleToMonitor;
|
||||
struct {
|
||||
GLFWbool retina;
|
||||
char frameName[256];
|
||||
@@ -277,6 +282,9 @@ struct _GLFWwndconfig
|
||||
char className[256];
|
||||
char instanceName[256];
|
||||
} x11;
|
||||
struct {
|
||||
char appId[256];
|
||||
} wl;
|
||||
};
|
||||
|
||||
// Context configuration
|
||||
@@ -377,6 +385,7 @@ struct _GLFWwindow
|
||||
GLFWbool focusOnShow;
|
||||
GLFWbool shouldClose;
|
||||
void* userPointer;
|
||||
GLFWid id;
|
||||
GLFWvidmode videoMode;
|
||||
_GLFWmonitor* monitor;
|
||||
_GLFWcursor* cursor;
|
||||
@@ -525,6 +534,7 @@ struct _GLFWlibrary
|
||||
_GLFWerror* errorListHead;
|
||||
_GLFWcursor* cursorListHead;
|
||||
_GLFWwindow* windowListHead;
|
||||
GLFWid focusedWindowId;
|
||||
|
||||
_GLFWmonitor** monitors;
|
||||
int monitorCount;
|
||||
@@ -600,7 +610,7 @@ void _glfwPlatformGetCursorPos(_GLFWwindow* window, double* xpos, double* ypos);
|
||||
void _glfwPlatformSetCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode);
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image, int xhot, int yhot);
|
||||
const GLFWimage* image, int xhot, int yhot, int count);
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
|
||||
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
|
||||
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
|
||||
@@ -670,6 +680,7 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
|
||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled);
|
||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled);
|
||||
void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity);
|
||||
void _glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d);
|
||||
|
||||
void _glfwPlatformPollEvents(void);
|
||||
void _glfwPlatformWaitEvents(void);
|
||||
@@ -713,7 +724,7 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window);
|
||||
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
|
||||
|
||||
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state);
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset, int flags);
|
||||
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
|
||||
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
|
||||
@@ -766,5 +777,7 @@ const char* _glfwGetKeyName(int key);
|
||||
GLFWbool _glfwInitVulkan(int mode);
|
||||
void _glfwTerminateVulkan(void);
|
||||
const char* _glfwGetVulkanResultString(VkResult result);
|
||||
_GLFWwindow* _glfwFocusedWindow();
|
||||
_GLFWwindow* _glfwWindowForId(GLFWid id);
|
||||
|
||||
char* _glfw_strdup(const char* source);
|
||||
|
||||
2
glfw/null_window.c
vendored
2
glfw/null_window.c
vendored
@@ -267,7 +267,7 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
int xhot, int yhot, int count)
|
||||
{
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
"xkb_glfw.h",
|
||||
"dbus_glfw.h",
|
||||
"ibus_glfw.h",
|
||||
"backend_utils.h",
|
||||
"egl_context.h",
|
||||
"osmesa_context.h",
|
||||
@@ -78,8 +80,11 @@
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"xkb_glfw.c",
|
||||
"dbus_glfw.c",
|
||||
"ibus_glfw.c",
|
||||
"egl_context.c",
|
||||
"osmesa_context.c",
|
||||
"backend_utils.c",
|
||||
"linux_joystick.c",
|
||||
"null_joystick.c"
|
||||
]
|
||||
@@ -112,6 +117,8 @@
|
||||
"headers": [
|
||||
"x11_platform.h",
|
||||
"xkb_glfw.h",
|
||||
"dbus_glfw.h",
|
||||
"ibus_glfw.h",
|
||||
"backend_utils.h",
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
@@ -126,11 +133,14 @@
|
||||
"x11_monitor.c",
|
||||
"x11_window.c",
|
||||
"xkb_glfw.c",
|
||||
"dbus_glfw.c",
|
||||
"ibus_glfw.c",
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"glx_context.c",
|
||||
"egl_context.c",
|
||||
"osmesa_context.c",
|
||||
"backend_utils.c",
|
||||
"linux_joystick.c",
|
||||
"null_joystick.c"
|
||||
]
|
||||
|
||||
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();
|
||||
|
||||
4
glfw/win32_monitor.c
vendored
4
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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,20 +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 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)
|
||||
#define IsWindowsXPOrGreater() \
|
||||
_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
|
||||
@@ -206,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*);
|
||||
@@ -223,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
|
||||
@@ -276,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;
|
||||
@@ -300,6 +331,7 @@ typedef struct _GLFWlibraryWin32
|
||||
_GLFWwindow* disabledCursorWindow;
|
||||
RAWINPUT* rawInput;
|
||||
int rawInputSize;
|
||||
UINT mouseTrailSize;
|
||||
|
||||
struct {
|
||||
HINSTANCE instance;
|
||||
@@ -322,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 {
|
||||
@@ -337,6 +373,11 @@ typedef struct _GLFWlibraryWin32
|
||||
PFN_GetDpiForMonitor GetDpiForMonitor_;
|
||||
} shcore;
|
||||
|
||||
struct {
|
||||
HINSTANCE instance;
|
||||
PFN_RtlVerifyVersionInfo RtlVerifyVersionInfo_;
|
||||
} ntdll;
|
||||
|
||||
} _GLFWlibraryWin32;
|
||||
|
||||
// Win32-specific per-monitor data
|
||||
@@ -392,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);
|
||||
|
||||
@@ -405,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);
|
||||
|
||||
|
||||
284
glfw/win32_window.c
vendored
284
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)
|
||||
@@ -529,7 +524,18 @@ static void fitToMonitor(_GLFWwindow* window)
|
||||
static void acquireMonitor(_GLFWwindow* window)
|
||||
{
|
||||
if (!_glfw.win32.acquiredMonitorCount)
|
||||
{
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
||||
|
||||
// HACK: When mouse trails are enabled the cursor becomes invisible when
|
||||
// the OpenGL ICD switches to page flipping
|
||||
if (IsWindowsXPOrGreater())
|
||||
{
|
||||
SystemParametersInfo(SPI_GETMOUSETRAILS, 0, &_glfw.win32.mouseTrailSize, 0);
|
||||
SystemParametersInfo(SPI_SETMOUSETRAILS, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (!window->monitor->window)
|
||||
_glfw.win32.acquiredMonitorCount++;
|
||||
|
||||
@@ -546,8 +552,14 @@ static void releaseMonitor(_GLFWwindow* window)
|
||||
|
||||
_glfw.win32.acquiredMonitorCount--;
|
||||
if (!_glfw.win32.acquiredMonitorCount)
|
||||
{
|
||||
SetThreadExecutionState(ES_CONTINUOUS);
|
||||
|
||||
// HACK: Restore mouse trail length saved in acquireMonitor
|
||||
if (IsWindowsXPOrGreater())
|
||||
SystemParametersInfo(SPI_SETMOUSETRAILS, _glfw.win32.mouseTrailSize, 0, 0);
|
||||
}
|
||||
|
||||
_glfwInputMonitorWindow(window->monitor, NULL);
|
||||
_glfwRestoreVideoModeWin32(window->monitor);
|
||||
}
|
||||
@@ -561,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;
|
||||
@@ -986,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)
|
||||
@@ -1039,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;
|
||||
}
|
||||
@@ -1129,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);
|
||||
@@ -1168,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)
|
||||
@@ -1227,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 //////
|
||||
@@ -1378,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);
|
||||
}
|
||||
@@ -1408,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);
|
||||
@@ -1464,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;
|
||||
@@ -1546,8 +1663,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,
|
||||
@@ -1607,8 +1735,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,
|
||||
@@ -1643,7 +1781,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)
|
||||
@@ -1839,7 +1985,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
int xhot, int yhot, int count)
|
||||
{
|
||||
cursor->win32.handle = (HCURSOR) createIcon(image, xhot, yhot, GLFW_FALSE);
|
||||
if (!cursor->win32.handle)
|
||||
@@ -1850,8 +1996,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,
|
||||
|
||||
31
glfw/window.c
vendored
31
glfw/window.c
vendored
@@ -48,6 +48,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
||||
if (!focused)
|
||||
{
|
||||
int key, button;
|
||||
_glfw.focusedWindowId = 0;
|
||||
|
||||
for (key = 0; key <= GLFW_KEY_LAST; key++)
|
||||
{
|
||||
@@ -63,7 +64,28 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
||||
if (window->mouseButtons[button] == GLFW_PRESS)
|
||||
_glfwInputMouseClick(window, button, GLFW_RELEASE, 0);
|
||||
}
|
||||
} else
|
||||
_glfw.focusedWindowId = window->id;
|
||||
}
|
||||
|
||||
_GLFWwindow* _glfwFocusedWindow() {
|
||||
if (_glfw.focusedWindowId) {
|
||||
_GLFWwindow *w = _glfw.windowListHead;
|
||||
while (w) {
|
||||
if (w->id == _glfw.focusedWindowId) return w;
|
||||
w = w->next;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
_GLFWwindow* _glfwWindowForId(GLFWid id) {
|
||||
_GLFWwindow *w = _glfw.windowListHead;
|
||||
while (w) {
|
||||
if (w->id == _glfw.focusedWindowId) return w;
|
||||
w = w->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Notifies shared code that a window has moved
|
||||
@@ -185,8 +207,10 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
if (!_glfwIsValidContextConfig(&ctxconfig))
|
||||
return NULL;
|
||||
|
||||
static GLFWid windowIdCounter = 0;
|
||||
window = calloc(1, sizeof(_GLFWwindow));
|
||||
window->next = _glfw.windowListHead;
|
||||
window->id = ++windowIdCounter;
|
||||
_glfw.windowListHead = window;
|
||||
|
||||
window->videoMode.width = width;
|
||||
@@ -369,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;
|
||||
@@ -433,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);
|
||||
|
||||
71
glfw/wl_init.c
vendored
71
glfw/wl_init.c
vendored
@@ -139,6 +139,7 @@ static void setCursor(const char* name)
|
||||
"Wayland: Standard cursor not found");
|
||||
return;
|
||||
}
|
||||
// TODO: handle animated cursors too.
|
||||
image = cursor->images[0];
|
||||
|
||||
if (!image)
|
||||
@@ -308,7 +309,7 @@ static void pointerHandleButton(void* data,
|
||||
state == WL_POINTER_BUTTON_STATE_PRESSED
|
||||
? GLFW_PRESS
|
||||
: GLFW_RELEASE,
|
||||
_glfw.wl.xkb.modifiers);
|
||||
_glfw.wl.xkb.states.modifiers);
|
||||
}
|
||||
|
||||
static void pointerHandleAxis(void* data,
|
||||
@@ -319,11 +320,6 @@ static void pointerHandleAxis(void* data,
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
double x = 0.0, y = 0.0;
|
||||
// Wayland scroll events are in pointer motion coordinate space (think two
|
||||
// finger scroll). The factor 10 is commonly used to convert to "scroll
|
||||
// step means 1.0.
|
||||
const double scrollFactor = 1.0 / 10.0;
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
@@ -331,11 +327,11 @@ static void pointerHandleAxis(void* data,
|
||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||
|
||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
||||
x = wl_fixed_to_double(value) * scrollFactor;
|
||||
x = wl_fixed_to_double(value) * -1;
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
y = wl_fixed_to_double(value) * scrollFactor;
|
||||
y = wl_fixed_to_double(value) * -1;
|
||||
|
||||
_glfwInputScroll(window, x, y);
|
||||
_glfwInputScroll(window, x, y, 1);
|
||||
}
|
||||
|
||||
static const struct wl_pointer_listener pointerListener = {
|
||||
@@ -407,6 +403,15 @@ static void keyboardHandleLeave(void* data,
|
||||
_glfwInputWindowFocus(window, GLFW_FALSE);
|
||||
}
|
||||
|
||||
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);
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, 1);
|
||||
}
|
||||
|
||||
|
||||
static void keyboardHandleKey(void* data,
|
||||
struct wl_keyboard* keyboard,
|
||||
uint32_t serial,
|
||||
@@ -419,14 +424,18 @@ static void keyboardHandleKey(void* data,
|
||||
return;
|
||||
int action = state == WL_KEYBOARD_KEY_STATE_PRESSED ? GLFW_PRESS : GLFW_RELEASE;
|
||||
glfw_xkb_handle_key_event(window, &_glfw.wl.xkb, key, action);
|
||||
_glfw.wl.keyRepeatInfo.nextRepeatAt = 0;
|
||||
GLFWbool repeatable = GLFW_FALSE;
|
||||
|
||||
if (action == GLFW_PRESS && _glfw.wl.keyboardRepeatRate > 0 && glfw_xkb_should_repeat(&_glfw.wl.xkb, key))
|
||||
{
|
||||
_glfw.wl.keyRepeatInfo.key = key;
|
||||
_glfw.wl.keyRepeatInfo.nextRepeatAt = glfwGetTime() + (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0;
|
||||
repeatable = GLFW_TRUE;
|
||||
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
||||
}
|
||||
if (repeatable) {
|
||||
changeTimerInterval(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, (double)(_glfw.wl.keyboardRepeatDelay) / 1000.0);
|
||||
}
|
||||
toggleTimer(&_glfw.wl.eventLoopData, _glfw.wl.keyRepeatInfo.keyRepeatTimer, repeatable ? 1 : 0);
|
||||
}
|
||||
|
||||
static void keyboardHandleModifiers(void* data,
|
||||
@@ -552,6 +561,9 @@ static void registryHandleGlobal(void* data,
|
||||
_glfw.wl.seatVersion);
|
||||
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
||||
}
|
||||
if (_glfw.wl.seat && _glfw.wl.dataDeviceManager && !_glfw.wl.dataDevice) {
|
||||
_glfwSetupWaylandDataDevice();
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||
{
|
||||
@@ -585,6 +597,17 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void registryHandleGlobalRemove(void *data,
|
||||
@@ -664,7 +687,10 @@ int _glfwPlatformInit(void)
|
||||
"Wayland: Failed to connect to display");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
initPollData(_glfw.wl.eventLoopData.fds, _glfw.wl.eventLoopData.wakeupFds[0], wl_display_get_fd(_glfw.wl.display));
|
||||
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-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);
|
||||
@@ -678,8 +704,10 @@ int _glfwPlatformInit(void)
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
|
||||
#ifdef __linux__
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
if (_glfw.hints.init.enableJoysticks) {
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
@@ -713,6 +741,7 @@ void _glfwPlatformTerminate(void)
|
||||
}
|
||||
|
||||
glfw_xkb_release(&_glfw.wl.xkb);
|
||||
glfw_dbus_terminate(&_glfw.wl.dbus);
|
||||
|
||||
if (_glfw.wl.cursorTheme)
|
||||
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
|
||||
@@ -748,6 +777,17 @@ 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.registry)
|
||||
wl_registry_destroy(_glfw.wl.registry);
|
||||
if (_glfw.wl.display)
|
||||
@@ -756,6 +796,9 @@ 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.clipboardSourceString); _glfw.wl.clipboardSourceString = NULL;
|
||||
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetVersionString(void)
|
||||
|
||||
38
glfw/wl_platform.h
vendored
38
glfw/wl_platform.h
vendored
@@ -49,6 +49,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#else
|
||||
#include "null_joystick.h"
|
||||
#endif
|
||||
#include "backend_utils.h"
|
||||
#include "xkb_glfw.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
@@ -148,6 +149,7 @@ typedef struct _GLFWwindowWayland
|
||||
double cursorPosX, cursorPosY;
|
||||
|
||||
char* title;
|
||||
char appId[256];
|
||||
|
||||
// We need to track the monitors the window spans on to calculate the
|
||||
// optimal scaling factor.
|
||||
@@ -174,6 +176,19 @@ typedef struct _GLFWwindowWayland
|
||||
|
||||
} _GLFWwindowWayland;
|
||||
|
||||
typedef struct _GLFWWaylandDataOffer
|
||||
{
|
||||
struct wl_data_offer *id;
|
||||
const char *mime;
|
||||
int 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;
|
||||
|
||||
// Wayland-specific global data
|
||||
//
|
||||
typedef struct _GLFWlibraryWayland
|
||||
@@ -192,6 +207,9 @@ typedef struct _GLFWlibraryWayland
|
||||
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;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
@@ -204,10 +222,12 @@ typedef struct _GLFWlibraryWayland
|
||||
int32_t keyboardRepeatDelay;
|
||||
struct {
|
||||
uint32_t key;
|
||||
double nextRepeatAt;
|
||||
id_type keyRepeatTimer;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
} keyRepeatInfo;
|
||||
id_type cursorAnimationTimer;
|
||||
_GLFWXKBData xkb;
|
||||
_GLFWDBUSData dbus;
|
||||
|
||||
_GLFWwindow* pointerFocus;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
@@ -229,11 +249,12 @@ typedef struct _GLFWlibraryWayland
|
||||
PFN_wl_egl_window_resize window_resize;
|
||||
} egl;
|
||||
|
||||
struct {
|
||||
struct pollfd fds[2];
|
||||
int wakeupFds[2];
|
||||
} eventLoopData;
|
||||
|
||||
EventLoopData eventLoopData;
|
||||
char* clipboardString;
|
||||
char* clipboardSourceString;
|
||||
struct wl_data_offer* clipboardSourceOffer;
|
||||
size_t dataOffersCounter;
|
||||
_GLFWWaylandDataOffer dataOffers[8];
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -254,11 +275,14 @@ 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 animateCursorImage(id_type timer_id, void *data);
|
||||
|
||||
496
glfw/wl_window.c
vendored
496
glfw/wl_window.c
vendored
@@ -37,6 +37,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)
|
||||
@@ -681,6 +693,8 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
}
|
||||
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);
|
||||
@@ -689,33 +703,75 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
}
|
||||
|
||||
static void
|
||||
dispatchPendingKeyRepeats() {
|
||||
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return;
|
||||
double now = glfwGetTime();
|
||||
while (_glfw.wl.keyRepeatInfo.nextRepeatAt <= now) {
|
||||
glfw_xkb_handle_key_event(_glfw.wl.keyRepeatInfo.keyboardFocus, &_glfw.wl.xkb, _glfw.wl.keyRepeatInfo.key, GLFW_REPEAT);
|
||||
_glfw.wl.keyRepeatInfo.nextRepeatAt += 1.0 / _glfw.wl.keyboardRepeatRate;
|
||||
now = glfwGetTime();
|
||||
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 double
|
||||
adjustTimeoutForKeyRepeat(double timeout) {
|
||||
if (_glfw.wl.keyRepeatInfo.nextRepeatAt <= 0 || _glfw.wl.keyRepeatInfo.keyboardFocus != _glfw.wl.keyboardFocus || _glfw.wl.keyboardRepeatRate == 0) return timeout;
|
||||
double now = glfwGetTime();
|
||||
if (timeout < 0 || now + timeout > _glfw.wl.keyRepeatInfo.nextRepeatAt) {
|
||||
timeout = _glfw.wl.keyRepeatInfo.nextRepeatAt <= now ? 0 : ( (_glfw.wl.keyRepeatInfo.nextRepeatAt - now) + 0.001 );
|
||||
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);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return timeout;
|
||||
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)
|
||||
{
|
||||
struct wl_display* display = _glfw.wl.display;
|
||||
|
||||
while (wl_display_prepare_read(display) != 0)
|
||||
while (wl_display_prepare_read(display) != 0) {
|
||||
wl_display_dispatch_pending(display);
|
||||
}
|
||||
|
||||
// If an error different from EAGAIN happens, we have likely been
|
||||
// disconnected from the Wayland session, try to handle that the best we
|
||||
@@ -732,25 +788,8 @@ handleEvents(double timeout)
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchPendingKeyRepeats();
|
||||
timeout = adjustTimeoutForKeyRepeat(timeout);
|
||||
GLFWbool read_ok = GLFW_FALSE;
|
||||
for (nfds_t i = 0; i < 2; i++) _glfw.wl.eventLoopData.fds[i].revents = 0;
|
||||
|
||||
if (timeout >= 0) {
|
||||
const int result = pollWithTimeout(_glfw.wl.eventLoopData.fds, 2, timeout);
|
||||
if (result > 0)
|
||||
{
|
||||
if (_glfw.wl.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.wl.eventLoopData.fds[0].fd);
|
||||
read_ok = _glfw.wl.eventLoopData.fds[1].revents & POLLIN;
|
||||
}
|
||||
} else {
|
||||
if (poll(_glfw.wl.eventLoopData.fds, 2, -1) > 0) {
|
||||
if (_glfw.wl.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.wl.eventLoopData.fds[0].fd);
|
||||
read_ok = _glfw.wl.eventLoopData.fds[1].revents & POLLIN;
|
||||
}
|
||||
}
|
||||
if (read_ok) {
|
||||
GLFWbool display_read_ok = pollForEvents(&_glfw.wl.eventLoopData, timeout);
|
||||
if (display_read_ok) {
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
}
|
||||
@@ -758,7 +797,7 @@ handleEvents(double timeout)
|
||||
{
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
dispatchPendingKeyRepeats();
|
||||
glfw_ibus_dispatch(&_glfw.wl.xkb.ibus);
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a theme cursor name
|
||||
@@ -794,6 +833,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;
|
||||
@@ -1094,7 +1134,8 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window)
|
||||
{
|
||||
// TODO: Use an actual Wayland API to implement this when one becomes available
|
||||
int fd = open("/dev/tty", O_WRONLY | O_CLOEXEC);
|
||||
static char tty[L_ctermid + 1];
|
||||
int fd = open(ctermid(tty), O_WRONLY | O_CLOEXEC);
|
||||
if (fd > -1) {
|
||||
int ret = write(fd, "\x07", 1) == 1 ? GLFW_TRUE : GLFW_FALSE;
|
||||
close(fd);
|
||||
@@ -1200,16 +1241,19 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
wl_display_dispatch_pending(_glfw.wl.display);
|
||||
handleEvents(0);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEvents(void)
|
||||
{
|
||||
handleEvents(-1);
|
||||
double timeout = wl_display_dispatch_pending(_glfw.wl.display) > 0 ? 0 : -1;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||
{
|
||||
if (wl_display_dispatch_pending(_glfw.wl.display) > 0) timeout = 0;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
@@ -1257,7 +1301,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
int xhot, int yhot, int count)
|
||||
{
|
||||
cursor->wl.buffer = createShmBuffer(image);
|
||||
cursor->wl.width = image->width;
|
||||
@@ -1281,14 +1325,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)
|
||||
@@ -1394,10 +1439,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;
|
||||
@@ -1406,7 +1448,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.
|
||||
@@ -1416,7 +1458,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,
|
||||
@@ -1427,33 +1469,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)
|
||||
@@ -1468,18 +1491,330 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
}
|
||||
}
|
||||
|
||||
static void _glfwSendClipboardText(void *data, struct wl_data_source *data_source, const char *mime_type, int fd)
|
||||
{
|
||||
if (_glfw.wl.clipboardString) {
|
||||
size_t len = strlen(_glfw.wl.clipboardString), pos = 0;
|
||||
double start = glfwGetTime();
|
||||
while (pos < len && glfwGetTime() - start < 2.0) {
|
||||
ssize_t ret = write(fd, _glfw.wl.clipboardString + 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 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]);
|
||||
wl_display_flush(_glfw.wl.display);
|
||||
size_t sz = 0, capacity = 0;
|
||||
char *buf = NULL;
|
||||
struct pollfd fds;
|
||||
fds.fd = pipefd[0];
|
||||
fds.events = POLLIN;
|
||||
double start = glfwGetTime();
|
||||
#define bail(...) { \
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, __VA_ARGS__); \
|
||||
free(buf); buf = NULL; \
|
||||
close(pipefd[0]); \
|
||||
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(pipefd[0], 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(pipefd[0]); buf[sz] = 0; return buf; }
|
||||
sz += ret;
|
||||
start = glfwGetTime();
|
||||
}
|
||||
bail("Wayland: Failed to read clipboard data from pipe (timed out)");
|
||||
#undef bail
|
||||
|
||||
}
|
||||
|
||||
static const char* _glfwReceiveClipboardText(struct wl_data_offer *data_offer, const char *mime)
|
||||
{
|
||||
if (_glfw.wl.clipboardSourceOffer == data_offer && _glfw.wl.clipboardSourceString)
|
||||
return _glfw.wl.clipboardSourceString;
|
||||
free(_glfw.wl.clipboardSourceString);
|
||||
_glfw.wl.clipboardSourceString = read_data_offer(data_offer, mime);
|
||||
return _glfw.wl.clipboardSourceString;
|
||||
}
|
||||
|
||||
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 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,
|
||||
};
|
||||
|
||||
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 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 = 1;
|
||||
} else if (_glfw.wl.dataOffers[i].offer_type == 1) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 0; // previous selection offer
|
||||
}
|
||||
}
|
||||
prune_unclaimed_data_offers();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (strcmp(mime, "text/plain;charset=utf-8") == 0)
|
||||
_glfw.wl.dataOffers[i].mime = "text/plain;charset=utf-8";
|
||||
else if (!_glfw.wl.dataOffers[i].mime && strcmp(mime, "text/plain") == 0)
|
||||
_glfw.wl.dataOffers[i].mime = "text/plain";
|
||||
else if (strcmp(mime, clipboard_mime()) == 0)
|
||||
_glfw.wl.dataOffers[i].is_self_offer = 1;
|
||||
else if (strcmp(mime, URI_LIST_MIME) == 0)
|
||||
_glfw.wl.dataOffers[i].has_uri_list = 1;
|
||||
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 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 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 = 2;
|
||||
_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 == 2) {
|
||||
_glfw.wl.dataOffers[i].offer_type = 0; // 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 == 2) {
|
||||
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 == 2) {
|
||||
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,
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
copy_callback_done(void *data, struct wl_callback *callback, uint32_t serial) {
|
||||
if (!_glfw.wl.dataDevice) return;
|
||||
if (data == (void*)_glfw.wl.dataSourceForClipboard) {
|
||||
wl_data_device_set_selection(_glfw.wl.dataDevice, data, serial);
|
||||
}
|
||||
}
|
||||
|
||||
const static struct wl_callback_listener copy_callback_listener = {
|
||||
.done = copy_callback_done
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
wl_callback_add_listener(callback, ©_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 == 1) {
|
||||
if (_glfw.wl.dataOffers[i].is_self_offer) return _glfw.wl.clipboardString;
|
||||
return _glfwReceiveClipboardText(_glfw.wl.dataOffers[i].id, _glfw.wl.dataOffers[i].mime);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1546,6 +1881,11 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
return err;
|
||||
}
|
||||
|
||||
void
|
||||
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
|
||||
glfw_xkb_update_ime_state(w, &_glfw.wl.xkb, which, a, b, c, d);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
|
||||
14
glfw/x11_init.c
vendored
14
glfw/x11_init.c
vendored
@@ -37,6 +37,7 @@
|
||||
#include <stdio.h>
|
||||
#include <locale.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
// Check whether the specified atom is supported
|
||||
@@ -640,8 +641,8 @@ int _glfwPlatformInit(void)
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
initPollData(_glfw.x11.eventLoopData.fds, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
|
||||
_glfw.x11.eventLoopData.fds[2].events = POLLIN;
|
||||
initPollData(&_glfw.x11.eventLoopData, _glfw.x11.eventLoopData.wakeupFds[0], ConnectionNumber(_glfw.x11.display));
|
||||
glfw_dbus_init(&_glfw.x11.dbus, &_glfw.x11.eventLoopData);
|
||||
|
||||
_glfw.x11.screen = DefaultScreen(_glfw.x11.display);
|
||||
_glfw.x11.root = RootWindow(_glfw.x11.display, _glfw.x11.screen);
|
||||
@@ -658,8 +659,12 @@ int _glfwPlatformInit(void)
|
||||
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
||||
|
||||
#if defined(__linux__)
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
if (_glfw.hints.init.enableJoysticks) {
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
if (_glfw.linjs.inotify > 0)
|
||||
addWatch(&_glfw.x11.eventLoopData, "joystick", _glfw.linjs.inotify, POLLIN, 1, NULL, NULL);
|
||||
}
|
||||
#endif
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
@@ -689,6 +694,7 @@ void _glfwPlatformTerminate(void)
|
||||
}
|
||||
|
||||
glfw_xkb_release(&_glfw.x11.xkb);
|
||||
glfw_dbus_terminate(&_glfw.x11.dbus);
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
free(_glfw.x11.clipboardString);
|
||||
|
||||
|
||||
28
glfw/x11_monitor.c
vendored
28
glfw/x11_monitor.c
vendored
@@ -152,6 +152,11 @@ void _glfwPollMonitorsX11(void)
|
||||
}
|
||||
|
||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, oi->crtc);
|
||||
if (!ci)
|
||||
{
|
||||
XRRFreeOutputInfo(oi);
|
||||
continue;
|
||||
}
|
||||
if (ci->rotation == RR_Rotate_90 || ci->rotation == RR_Rotate_270)
|
||||
{
|
||||
widthMM = oi->mm_height;
|
||||
@@ -321,13 +326,15 @@ void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
|
||||
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
if (ci)
|
||||
{
|
||||
if (xpos)
|
||||
*xpos = ci->x;
|
||||
if (ypos)
|
||||
*ypos = ci->y;
|
||||
|
||||
if (xpos)
|
||||
*xpos = ci->x;
|
||||
if (ypos)
|
||||
*ypos = ci->y;
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
}
|
||||
@@ -406,9 +413,13 @@ void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode)
|
||||
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
|
||||
*mode = vidmodeFromModeInfo(getModeInfo(sr, ci->mode), ci);
|
||||
if (ci) {
|
||||
const XRRModeInfo* mi = getModeInfo(sr, ci->mode);
|
||||
if (mi) // mi can be NULL if the monitor has been disconnected
|
||||
*mode = vidmodeFromModeInfo(mi, ci);
|
||||
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
}
|
||||
XRRFreeScreenResources(sr);
|
||||
}
|
||||
else
|
||||
@@ -501,4 +512,3 @@ GLFWAPI RROutput glfwGetX11Monitor(GLFWmonitor* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(None);
|
||||
return monitor->x11.output;
|
||||
}
|
||||
|
||||
|
||||
9
glfw/x11_platform.h
vendored
9
glfw/x11_platform.h
vendored
@@ -53,6 +53,7 @@
|
||||
|
||||
// The libxkb library is used for improved keyboard support
|
||||
#include "xkb_glfw.h"
|
||||
#include "backend_utils.h"
|
||||
|
||||
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
|
||||
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
|
||||
@@ -103,8 +104,6 @@ typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
|
||||
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
|
||||
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
|
||||
|
||||
typedef struct xcb_connection_t xcb_connection_t;
|
||||
|
||||
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
|
||||
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
typedef Bool (* PFN_XF86VidModeSetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
@@ -315,6 +314,7 @@ typedef struct _GLFWlibraryX11
|
||||
} randr;
|
||||
|
||||
_GLFWXKBData xkb;
|
||||
_GLFWDBUSData dbus;
|
||||
|
||||
struct {
|
||||
int count;
|
||||
@@ -382,10 +382,7 @@ typedef struct _GLFWlibraryX11
|
||||
PFN_XRenderFindVisualFormat FindVisualFormat;
|
||||
} xrender;
|
||||
|
||||
struct {
|
||||
struct pollfd fds[3];
|
||||
int wakeupFds[2];
|
||||
} eventLoopData;
|
||||
EventLoopData eventLoopData;
|
||||
|
||||
} _GLFWlibraryX11;
|
||||
|
||||
|
||||
244
glfw/x11_window.c
vendored
244
glfw/x11_window.c
vendored
@@ -54,47 +54,32 @@
|
||||
// This avoids blocking other threads via the per-display Xlib lock that also
|
||||
// covers GLX functions
|
||||
//
|
||||
static GLFWbool waitForEvent(double* timeout)
|
||||
{
|
||||
nfds_t count = 2;
|
||||
GLFWbool _glfwDispatchX11Events(void);
|
||||
|
||||
#if defined(__linux__)
|
||||
if (_glfw.linjs.inotify > 0)
|
||||
{
|
||||
count = 3;
|
||||
_glfw.x11.eventLoopData.fds[2].fd = _glfw.linjs.inotify;
|
||||
}
|
||||
#endif
|
||||
for (;;)
|
||||
{
|
||||
for (nfds_t i = 0; i < count; i++) _glfw.x11.eventLoopData.fds[i].revents = 0;
|
||||
if (timeout)
|
||||
{
|
||||
const uint64_t base = _glfwPlatformGetTimerValue();
|
||||
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, count, *timeout);
|
||||
*timeout -= (_glfwPlatformGetTimerValue() - base) /
|
||||
(double) _glfwPlatformGetTimerFrequency();
|
||||
static void
|
||||
handleEvents(double timeout) {
|
||||
int display_read_ok = pollForEvents(&_glfw.x11.eventLoopData, timeout);
|
||||
if (display_read_ok) _glfwDispatchX11Events();
|
||||
glfw_ibus_dispatch(&_glfw.x11.xkb.ibus);
|
||||
}
|
||||
|
||||
if (result > 0)
|
||||
{
|
||||
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
if (result == 0)
|
||||
return GLFW_FALSE;
|
||||
if (*timeout > 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
static GLFWbool
|
||||
waitForX11Event(double timeout) {
|
||||
// returns true iff there is X11 data waiting to be read, does not run watches and timers
|
||||
double end_time = glfwGetTime() + timeout;
|
||||
while(GLFW_TRUE) {
|
||||
if (timeout >= 0) {
|
||||
const int result = pollWithTimeout(_glfw.x11.eventLoopData.fds, 1, timeout);
|
||||
if (result > 0) return GLFW_TRUE;
|
||||
timeout = end_time - glfwGetTime();
|
||||
if (timeout <= 0) return GLFW_FALSE;
|
||||
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
return GLFW_FALSE;
|
||||
} else {
|
||||
const int result = poll(_glfw.x11.eventLoopData.fds, 1, -1);
|
||||
if (result > 0) return GLFW_TRUE;
|
||||
if (result < 0 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
else {
|
||||
const int result = poll(_glfw.x11.eventLoopData.fds, count, -1);
|
||||
if (result > 0)
|
||||
{
|
||||
if (_glfw.x11.eventLoopData.fds[0].revents & POLLIN) drainFd(_glfw.x11.eventLoopData.fds[0].fd);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
if (result == 0)
|
||||
return GLFW_FALSE;
|
||||
if (errno != EINTR && errno != EAGAIN) return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,14 +90,13 @@ static GLFWbool waitForEvent(double* timeout)
|
||||
static GLFWbool waitForVisibilityNotify(_GLFWwindow* window)
|
||||
{
|
||||
XEvent dummy;
|
||||
double timeout = 0.1;
|
||||
|
||||
while (!XCheckTypedWindowEvent(_glfw.x11.display,
|
||||
window->x11.handle,
|
||||
VisibilityNotify,
|
||||
&dummy))
|
||||
{
|
||||
if (!waitForEvent(&timeout))
|
||||
if (!waitForX11Event(0.1))
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -177,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)
|
||||
@@ -222,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,
|
||||
@@ -406,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)
|
||||
@@ -598,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,
|
||||
@@ -623,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,
|
||||
@@ -726,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
|
||||
{
|
||||
@@ -984,7 +887,7 @@ static const char* getSelectionString(Atom selection)
|
||||
SelectionNotify,
|
||||
¬ification))
|
||||
{
|
||||
waitForEvent(NULL);
|
||||
waitForX11Event(-1);
|
||||
}
|
||||
|
||||
if (notification.xselection.property == None)
|
||||
@@ -1020,7 +923,7 @@ static const char* getSelectionString(Atom selection)
|
||||
isSelPropNewValueNotify,
|
||||
(XPointer) ¬ification))
|
||||
{
|
||||
waitForEvent(NULL);
|
||||
waitForX11Event(-1);
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
@@ -1259,8 +1162,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;
|
||||
@@ -1325,13 +1230,13 @@ static void processEvent(XEvent *event)
|
||||
|
||||
// Modern X provides scroll events as mouse button presses
|
||||
else if (event->xbutton.button == Button4)
|
||||
_glfwInputScroll(window, 0.0, 1.0);
|
||||
_glfwInputScroll(window, 0.0, 1.0, 0);
|
||||
else if (event->xbutton.button == Button5)
|
||||
_glfwInputScroll(window, 0.0, -1.0);
|
||||
_glfwInputScroll(window, 0.0, -1.0, 0);
|
||||
else if (event->xbutton.button == Button6)
|
||||
_glfwInputScroll(window, 1.0, 0.0);
|
||||
_glfwInputScroll(window, 1.0, 0.0, 0);
|
||||
else if (event->xbutton.button == Button7)
|
||||
_glfwInputScroll(window, -1.0, 0.0);
|
||||
_glfwInputScroll(window, -1.0, 0.0, 0);
|
||||
|
||||
else
|
||||
{
|
||||
@@ -1852,7 +1757,7 @@ void _glfwPushSelectionToManagerX11(void)
|
||||
}
|
||||
}
|
||||
|
||||
waitForEvent(NULL);
|
||||
waitForX11Event(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2142,7 +2047,6 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
_glfw.x11.NET_REQUEST_FRAME_EXTENTS)
|
||||
{
|
||||
XEvent event;
|
||||
double timeout = 0.5;
|
||||
|
||||
// Ensure _NET_FRAME_EXTENTS is set, allowing glfwGetWindowFrameSize to
|
||||
// function before the window is mapped
|
||||
@@ -2159,7 +2063,7 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
isFrameExtentsEvent,
|
||||
(XPointer) window))
|
||||
{
|
||||
if (!waitForEvent(&timeout))
|
||||
if (!waitForX11Event(0.5))
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: The window manager has a broken _NET_REQUEST_FRAME_EXTENTS implementation; please report this issue");
|
||||
@@ -2560,9 +2464,10 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
PropModeReplace, (unsigned char*) &value, 1);
|
||||
}
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
GLFWbool
|
||||
_glfwDispatchX11Events(void) {
|
||||
_GLFWwindow* window;
|
||||
GLFWbool dispatched = GLFW_FALSE;
|
||||
|
||||
#if defined(__linux__)
|
||||
_glfwDetectJoystickConnectionLinux();
|
||||
@@ -2574,6 +2479,7 @@ void _glfwPlatformPollEvents(void)
|
||||
XEvent event;
|
||||
XNextEvent(_glfw.x11.display, &event);
|
||||
processEvent(&event);
|
||||
dispatched = GLFW_TRUE;
|
||||
}
|
||||
|
||||
window = _glfw.x11.disabledCursorWindow;
|
||||
@@ -2592,25 +2498,25 @@ void _glfwPlatformPollEvents(void)
|
||||
}
|
||||
|
||||
XFlush(_glfw.x11.display);
|
||||
return dispatched;
|
||||
}
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
_glfwDispatchX11Events();
|
||||
handleEvents(0);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEvents(void)
|
||||
{
|
||||
while (!XPending(_glfw.x11.display))
|
||||
waitForEvent(NULL);
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
double timeout = _glfwDispatchX11Events() ? 0 : -1;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
void _glfwPlatformWaitEventsTimeout(double timeout)
|
||||
{
|
||||
while (!XPending(_glfw.x11.display))
|
||||
{
|
||||
if (!waitForEvent(&timeout))
|
||||
break;
|
||||
}
|
||||
|
||||
_glfwPlatformPollEvents();
|
||||
if (_glfwDispatchX11Events()) timeout = 0;
|
||||
handleEvents(timeout);
|
||||
}
|
||||
|
||||
void _glfwPlatformPostEmptyEvent(void)
|
||||
@@ -2684,7 +2590,7 @@ int _glfwPlatformGetKeyScancode(int key)
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
int xhot, int yhot, int count)
|
||||
{
|
||||
cursor->x11.handle = _glfwCreateCursorX11(image, xhot, yhot);
|
||||
if (!cursor->x11.handle)
|
||||
@@ -2695,8 +2601,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,
|
||||
@@ -2894,6 +2816,10 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_glfwPlatformUpdateIMEState(_GLFWwindow *w, int which, int a, int b, int c, int d) {
|
||||
glfw_xkb_update_ime_state(w, &_glfw.x11.xkb, which, a, b, c, d);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW native API //////
|
||||
|
||||
332
glfw/xkb_glfw.c
vendored
332
glfw/xkb_glfw.c
vendored
@@ -32,7 +32,8 @@
|
||||
|
||||
#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); \
|
||||
@@ -92,10 +93,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) {
|
||||
@@ -104,6 +101,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
|
||||
@@ -118,6 +122,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
|
||||
@@ -170,36 +177,29 @@ glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) {
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
release_keyboard_data(_GLFWXKBData *xkb) {
|
||||
#define US(group, state, unref) if (xkb->group.state) { unref(xkb->group.state); xkb->group.state = NULL; }
|
||||
#define UK(keymap) if(xkb->keymap) { xkb_keymap_unref(xkb->keymap); xkb->keymap = NULL; }
|
||||
US(states, composeState, xkb_compose_state_unref);
|
||||
UK(keymap);
|
||||
UK(default_keymap);
|
||||
US(states, state, xkb_state_unref);
|
||||
US(states, clean_state, xkb_state_unref);
|
||||
US(states, default_state, xkb_state_unref);
|
||||
#undef US
|
||||
#undef UK
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_release(_GLFWXKBData *xkb) {
|
||||
if (xkb->composeState) {
|
||||
xkb_compose_state_unref(xkb->composeState);
|
||||
xkb->composeState = NULL;
|
||||
}
|
||||
if (xkb->keymap) {
|
||||
xkb_keymap_unref(xkb->keymap);
|
||||
xkb->keymap = NULL;
|
||||
}
|
||||
if (xkb->default_keymap) {
|
||||
xkb_keymap_unref(xkb->default_keymap);
|
||||
xkb->default_keymap = NULL;
|
||||
}
|
||||
if (xkb->state) {
|
||||
xkb_state_unref(xkb->state);
|
||||
xkb->state = NULL;
|
||||
}
|
||||
if (xkb->clean_state) {
|
||||
xkb_state_unref(xkb->clean_state);
|
||||
xkb->clean_state = NULL;
|
||||
}
|
||||
if (xkb->default_state) {
|
||||
xkb_state_unref(xkb->default_state);
|
||||
xkb->default_state = NULL;
|
||||
}
|
||||
release_keyboard_data(xkb);
|
||||
if (xkb->context) {
|
||||
xkb_context_unref(xkb->context);
|
||||
xkb->context = NULL;
|
||||
}
|
||||
glfw_ibus_terminate(&xkb->ibus);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
@@ -211,115 +211,116 @@ glfw_xkb_create_context(_GLFWXKBData *xkb) {
|
||||
"Failed to initialize XKB context");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
glfw_connect_to_ibus(&xkb->ibus);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static const char*
|
||||
load_keymaps(_GLFWXKBData *xkb, const char *map_str) {
|
||||
(void)(map_str); // not needed on X11
|
||||
xkb_glfw_load_keymap(xkb->keymap, map_str);
|
||||
if (!xkb->keymap) return "Failed to compile XKB keymap";
|
||||
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
|
||||
// env var, see
|
||||
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
|
||||
static struct xkb_rule_names default_rule_names = {0};
|
||||
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
if (!xkb->default_keymap) return "Failed to create default XKB keymap";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char*
|
||||
load_states(_GLFWXKBData *xkb) {
|
||||
xkb_glfw_load_state(xkb->keymap, xkb->states.state);
|
||||
xkb->states.clean_state = xkb_state_new(xkb->keymap);
|
||||
xkb->states.default_state = xkb_state_new(xkb->default_keymap);
|
||||
if (!xkb->states.state || !xkb->states.clean_state || !xkb->states.default_state) return "Failed to create XKB state";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
load_compose_tables(_GLFWXKBData *xkb) {
|
||||
/* Look up the preferred locale, falling back to "C" as default. */
|
||||
struct xkb_compose_table* compose_table = NULL;
|
||||
const char *locale = getenv("LC_ALL");
|
||||
if (!locale) locale = getenv("LC_CTYPE");
|
||||
if (!locale) locale = getenv("LANG");
|
||||
if (!locale) locale = "C";
|
||||
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||
if (!compose_table) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table for locale %s", locale);
|
||||
return;
|
||||
}
|
||||
xkb->states.composeState = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||
if (!xkb->states.composeState) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
||||
}
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str) {
|
||||
const char* locale = NULL;
|
||||
struct xkb_state* state = NULL, *clean_state = NULL;
|
||||
struct xkb_keymap* keymap = NULL;
|
||||
struct xkb_compose_table* compose_table = NULL;
|
||||
struct xkb_compose_state* compose_state = NULL;
|
||||
(void)(map_str); // not needed on X11
|
||||
GLFWbool ok = GLFW_FALSE;
|
||||
|
||||
xkb_glfw_load_keymap(keymap, map_str);
|
||||
if (!keymap) _glfwInputError(GLFW_PLATFORM_ERROR, "Failed to compile XKB keymap");
|
||||
else {
|
||||
xkb_glfw_load_state(keymap, state);
|
||||
clean_state = xkb_state_new(keymap);
|
||||
if (!state || !clean_state) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB state");
|
||||
xkb_keymap_unref(keymap); keymap = NULL;
|
||||
} else {
|
||||
ok = GLFW_TRUE;
|
||||
/* Look up the preferred locale, falling back to "C" as default. */
|
||||
locale = getenv("LC_ALL");
|
||||
if (!locale) locale = getenv("LC_CTYPE");
|
||||
if (!locale) locale = getenv("LANG");
|
||||
if (!locale) locale = "C";
|
||||
compose_table = xkb_compose_table_new_from_locale(xkb->context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||
if (!compose_table) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose table");
|
||||
} else {
|
||||
compose_state = xkb_compose_state_new(compose_table, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||
xkb_compose_table_unref(compose_table); compose_table = NULL;
|
||||
if (!compose_state) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create XKB compose state");
|
||||
}
|
||||
}
|
||||
}
|
||||
const char *err;
|
||||
release_keyboard_data(xkb);
|
||||
err = load_keymaps(xkb, map_str);
|
||||
if (err) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
|
||||
release_keyboard_data(xkb);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
if (keymap && state && clean_state) {
|
||||
if (xkb->composeState) xkb_compose_state_unref(xkb->composeState);
|
||||
xkb->composeState = compose_state;
|
||||
if (xkb->keymap) xkb_keymap_unref(xkb->keymap);
|
||||
xkb->keymap = keymap;
|
||||
if (xkb->state) xkb_state_unref(xkb->state);
|
||||
xkb->state = state;
|
||||
if (xkb->clean_state) xkb_state_unref(xkb->clean_state);
|
||||
xkb->clean_state = clean_state;
|
||||
err = load_states(xkb);
|
||||
if (err) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "%s", err);
|
||||
release_keyboard_data(xkb);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
if (xkb->keymap) {
|
||||
load_compose_tables(xkb);
|
||||
#define S(a, n) xkb->a##Idx = xkb_keymap_mod_get_index(xkb->keymap, n); xkb->a##Mask = 1 << xkb->a##Idx;
|
||||
S(control, XKB_MOD_NAME_CTRL);
|
||||
S(alt, XKB_MOD_NAME_ALT);
|
||||
S(shift, XKB_MOD_NAME_SHIFT);
|
||||
S(super, XKB_MOD_NAME_LOGO);
|
||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||
S(numLock, XKB_MOD_NAME_NUM);
|
||||
S(control, XKB_MOD_NAME_CTRL);
|
||||
S(alt, XKB_MOD_NAME_ALT);
|
||||
S(shift, XKB_MOD_NAME_SHIFT);
|
||||
S(super, XKB_MOD_NAME_LOGO);
|
||||
S(capsLock, XKB_MOD_NAME_CAPS);
|
||||
S(numLock, XKB_MOD_NAME_NUM);
|
||||
#undef S
|
||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||
}
|
||||
xkb->modifiers = 0;
|
||||
xkb->activeUnknownModifiers = 0;
|
||||
size_t capacity = sizeof(xkb->unknownModifiers)/sizeof(xkb->unknownModifiers[0]), j = 0;
|
||||
for (xkb_mod_index_t i = 0; i < capacity; i++) xkb->unknownModifiers[i] = XKB_MOD_INVALID;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap) && j < capacity - 1; i++) {
|
||||
if (i != xkb->controlIdx && i != xkb->altIdx && i != xkb->shiftIdx && i != xkb->superIdx && i != xkb->capsLockIdx && i != xkb->numLockIdx) xkb->unknownModifiers[j++] = i;
|
||||
}
|
||||
if (!xkb->default_keymap && xkb->context) {
|
||||
// The system default keymap, can be overridden by the XKB_DEFAULT_RULES
|
||||
// env var, see
|
||||
// https://xkbcommon.org/doc/current/structxkb__rule__names.html
|
||||
static struct xkb_rule_names default_rule_names = {0};
|
||||
xkb->default_keymap = xkb_keymap_new_from_names(xkb->context, &default_rule_names, XKB_KEYMAP_COMPILE_NO_FLAGS);
|
||||
if (xkb->default_keymap) {
|
||||
xkb->default_state = xkb_state_new(keymap);
|
||||
if (!xkb->default_state) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB state, fallback key processing is unavailable");
|
||||
}
|
||||
} else {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to create default XKB keymap, fallback key processing is unavailable");
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
xkb->states.modifiers = 0;
|
||||
xkb->states.activeUnknownModifiers = 0;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static inline xkb_mod_mask_t
|
||||
active_unknown_modifiers(_GLFWXKBData *xkb) {
|
||||
active_unknown_modifiers(_GLFWXKBData *xkb, struct xkb_state *state) {
|
||||
size_t i = 0;
|
||||
xkb_mod_mask_t ans = 0;
|
||||
while (xkb->unknownModifiers[i] != XKB_MOD_INVALID) {
|
||||
if (xkb_state_mod_index_is_active(xkb->state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||
if (xkb_state_mod_index_is_active(state, xkb->unknownModifiers[i], XKB_STATE_MODS_EFFECTIVE)) ans |= (1 << xkb->unknownModifiers[i]);
|
||||
i++;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static void
|
||||
update_modifiers(_GLFWXKBData *xkb, XKBStateGroup *group) {
|
||||
#define S(attr, name) if (xkb_state_mod_index_is_active(group->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) group->modifiers |= GLFW_MOD_##name
|
||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||
#undef S
|
||||
xkb->states.activeUnknownModifiers = active_unknown_modifiers(xkb, xkb->states.state);
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_update_modifiers(_GLFWXKBData *xkb, xkb_mod_mask_t depressed, xkb_mod_mask_t latched, xkb_mod_mask_t locked, xkb_layout_index_t base_group, xkb_layout_index_t latched_group, xkb_layout_index_t locked_group) {
|
||||
if (!xkb->keymap) return;
|
||||
xkb->modifiers = 0;
|
||||
xkb_state_update_mask(xkb->state, depressed, latched, locked, base_group, latched_group, locked_group);
|
||||
xkb->states.modifiers = 0;
|
||||
xkb_state_update_mask(xkb->states.state, depressed, latched, locked, base_group, latched_group, locked_group);
|
||||
// We have to update the groups in clean_state, as they change for
|
||||
// different keyboard layouts, see https://github.com/kovidgoyal/kitty/issues/488
|
||||
xkb_state_update_mask(xkb->clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||
#define S(attr, name) if (xkb_state_mod_index_is_active(xkb->state, xkb->attr##Idx, XKB_STATE_MODS_EFFECTIVE)) xkb->modifiers |= GLFW_MOD_##name
|
||||
S(control, CONTROL); S(alt, ALT); S(shift, SHIFT); S(super, SUPER); S(capsLock, CAPS_LOCK); S(numLock, NUM_LOCK);
|
||||
#undef S
|
||||
xkb->activeUnknownModifiers = active_unknown_modifiers(xkb);
|
||||
xkb_state_update_mask(xkb->states.clean_state, 0, 0, 0, base_group, latched_group, locked_group);
|
||||
update_modifiers(xkb, &xkb->states);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
@@ -331,16 +332,18 @@ glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
|
||||
}
|
||||
|
||||
|
||||
static char text[256];
|
||||
static KeyEvent key_event = {};
|
||||
|
||||
static inline xkb_keysym_t
|
||||
compose_symbol(_GLFWXKBData *xkb, xkb_keysym_t sym) {
|
||||
if (sym == XKB_KEY_NoSymbol || !xkb->composeState) return sym;
|
||||
if (xkb_compose_state_feed(xkb->composeState, sym) != XKB_COMPOSE_FEED_ACCEPTED) return sym;
|
||||
switch (xkb_compose_state_get_status(xkb->composeState)) {
|
||||
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(xkb->composeState, text, sizeof(text));
|
||||
return xkb_compose_state_get_one_sym(xkb->composeState);
|
||||
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:
|
||||
return XKB_KEY_NoSymbol;
|
||||
@@ -402,30 +405,77 @@ format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
|
||||
return buf;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d) {
|
||||
int x = 0, y = 0;
|
||||
switch(which) {
|
||||
case 1:
|
||||
glfw_ibus_set_focused(&xkb->ibus, a ? GLFW_TRUE : GLFW_FALSE);
|
||||
break;
|
||||
case 2:
|
||||
_glfwPlatformGetWindowPos(w, &x, &y);
|
||||
x += a; y += b;
|
||||
glfw_ibus_set_cursor_geometry(&xkb->ibus, x, y, c, d);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed) {
|
||||
_GLFWwindow *window = _glfwWindowForId(ev->window_id);
|
||||
if (failed && window && window->callbacks.keyboard) {
|
||||
// notify application to remove any existing pre-edit text
|
||||
window->callbacks.keyboard((GLFWwindow*) window, GLFW_KEY_UNKNOWN, 0, GLFW_PRESS, 0, "", 1);
|
||||
}
|
||||
static xkb_keycode_t last_handled_press_keycode = 0;
|
||||
// We filter out release events that correspond to the last press event
|
||||
// handled by the IME system. This wont fix the case of multiple key
|
||||
// presses before a release, but is better than nothing. For that case
|
||||
// you'd need to implement a ring buffer to store pending key presses.
|
||||
xkb_keycode_t prev_handled_press = last_handled_press_keycode;
|
||||
last_handled_press_keycode = 0;
|
||||
GLFWbool is_release = ev->action == GLFW_RELEASE;
|
||||
debug("From IBUS: scancode: 0x%x name: %s is_release: %d\n", ev->keycode, glfw_xkb_keysym_name(ev->keysym), is_release);
|
||||
if (window && !handled_by_ime && !(is_release && ev->keycode == prev_handled_press)) {
|
||||
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), ev->text
|
||||
);
|
||||
_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;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
|
||||
const xkb_keysym_t *syms, *clean_syms, *default_syms;
|
||||
xkb_keysym_t glfw_sym;
|
||||
xkb_keycode_t code_for_sym = scancode;
|
||||
key_event.ibus_keycode = scancode;
|
||||
#ifdef _GLFW_WAYLAND
|
||||
code_for_sym += 8;
|
||||
#else
|
||||
key_event.ibus_keycode -= 8;
|
||||
#endif
|
||||
debug("scancode: 0x%x release: %d ", scancode, action == GLFW_RELEASE);
|
||||
int num_syms = xkb_state_key_get_syms(xkb->state, code_for_sym, &syms);
|
||||
int num_clean_syms = xkb_state_key_get_syms(xkb->clean_state, code_for_sym, &clean_syms);
|
||||
text[0] = 0;
|
||||
debug("%s scancode: 0x%x ", action == GLFW_RELEASE ? "Release" : "Press", scancode);
|
||||
XKBStateGroup *sg = &xkb->states;
|
||||
int num_syms = xkb_state_key_get_syms(sg->state, code_for_sym, &syms);
|
||||
int num_clean_syms = xkb_state_key_get_syms(sg->clean_state, code_for_sym, &clean_syms);
|
||||
key_event.text[0] = 0;
|
||||
// According to the documentation of xkb_compose_state_feed it does not
|
||||
// support multi-sym events, so we ignore them
|
||||
if (num_syms != 1 || num_clean_syms != 1) {
|
||||
debug("scancode: 0x%x num_syms: %d num_clean_syms: %d ignoring event\n", scancode, num_syms, num_clean_syms);
|
||||
debug("num_syms: %d num_clean_syms: %d ignoring event\n", num_syms, num_clean_syms);
|
||||
return;
|
||||
}
|
||||
glfw_sym = clean_syms[0];
|
||||
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(xkb, 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;
|
||||
}
|
||||
@@ -435,21 +485,21 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
||||
// are active (for example if ISO_Shift_Level_* mods are active
|
||||
// they are not reported by GLFW so the key should be the shifted
|
||||
// key). See https://github.com/kovidgoyal/kitty/issues/171#issuecomment-377557053
|
||||
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(xkb->state, code_for_sym) & xkb->activeUnknownModifiers;
|
||||
if (xkb->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", xkb->activeUnknownModifiers));
|
||||
xkb_mod_mask_t consumed_unknown_mods = xkb_state_key_get_consumed_mods(sg->state, code_for_sym) & sg->activeUnknownModifiers;
|
||||
if (sg->activeUnknownModifiers) debug("%s", format_xkb_mods(xkb, "active_unknown_mods", sg->activeUnknownModifiers));
|
||||
if (consumed_unknown_mods) { debug("%s", format_xkb_mods(xkb, "consumed_unknown_mods", consumed_unknown_mods)); }
|
||||
else glfw_sym = clean_syms[0];
|
||||
// xkb returns text even if alt and/or super are pressed
|
||||
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & xkb->modifiers) == 0) xkb_state_key_get_utf8(xkb->state, code_for_sym, text, sizeof(text));
|
||||
if ( ((GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER) & sg->modifiers) == 0) xkb_state_key_get_utf8(sg->state, code_for_sym, key_event.text, sizeof(key_event.text));
|
||||
text_type = "text";
|
||||
}
|
||||
if ((1 <= text[0] && text[0] <= 31) || text[0] == 127) text[0] = 0; // dont send text for ascii control codes
|
||||
if (text[0]) { debug("%s: %s ", text_type, text); }
|
||||
if ((1 <= key_event.text[0] && key_event.text[0] <= 31) || key_event.text[0] == 127) key_event.text[0] = 0; // don't send text for ascii control codes
|
||||
if (key_event.text[0]) { debug("%s: %s ", text_type, key_event.text); }
|
||||
}
|
||||
int glfw_keycode = glfw_key_for_sym(glfw_sym);
|
||||
GLFWbool is_fallback = GLFW_FALSE;
|
||||
if (glfw_keycode == GLFW_KEY_UNKNOWN && !text[0]) {
|
||||
int num_default_syms = xkb_state_key_get_syms(xkb->default_state, code_for_sym, &default_syms);
|
||||
if (glfw_keycode == GLFW_KEY_UNKNOWN && !key_event.text[0]) {
|
||||
int num_default_syms = xkb_state_key_get_syms(sg->default_state, code_for_sym, &default_syms);
|
||||
if (num_default_syms > 0) {
|
||||
glfw_sym = default_syms[0];
|
||||
glfw_keycode = glfw_key_for_sym(glfw_sym);
|
||||
@@ -457,10 +507,18 @@ glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t
|
||||
}
|
||||
}
|
||||
debug(
|
||||
"%s%s: %s xkb_key_name: %s\n",
|
||||
format_mods(xkb->modifiers),
|
||||
is_fallback ? "glfw_fallback_key" : "glfw_key", _glfwGetKeyName(glfw_keycode),
|
||||
glfw_xkb_keysym_name(glfw_sym)
|
||||
"%s%s: %d (%s) xkb_key: %d (%s)\n",
|
||||
format_mods(sg->modifiers),
|
||||
is_fallback ? "glfw_fallback_key" : "glfw_key", glfw_keycode, _glfwGetKeyName(glfw_keycode),
|
||||
glfw_sym, glfw_xkb_keysym_name(glfw_sym)
|
||||
);
|
||||
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, xkb->modifiers, text, 0);
|
||||
key_event.action = action; key_event.glfw_modifiers = sg->modifiers;
|
||||
key_event.keycode = scancode; key_event.keysym = glfw_sym;
|
||||
key_event.window_id = window->id; key_event.glfw_keycode = glfw_keycode;
|
||||
key_event.ibus_sym = syms[0];
|
||||
if (ibus_process_key(&key_event, &xkb->ibus)) {
|
||||
debug("↳ to IBUS: keycode: 0x%x keysym: 0x%x (%s) %s\n", key_event.ibus_keycode, key_event.ibus_sym, glfw_xkb_keysym_name(key_event.ibus_sym), format_mods(key_event.glfw_modifiers));
|
||||
} else {
|
||||
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, sg->modifiers, key_event.text, 0);
|
||||
}
|
||||
}
|
||||
|
||||
20
glfw/xkb_glfw.h
vendored
20
glfw/xkb_glfw.h
vendored
@@ -32,14 +32,23 @@
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
#endif
|
||||
|
||||
#include "ibus_glfw.h"
|
||||
|
||||
typedef struct {
|
||||
struct xkb_context* context;
|
||||
struct xkb_keymap* keymap;
|
||||
struct xkb_keymap* default_keymap;
|
||||
struct xkb_state* state;
|
||||
struct xkb_state* clean_state;
|
||||
struct xkb_state* default_state;
|
||||
struct xkb_compose_state* composeState;
|
||||
xkb_mod_mask_t activeUnknownModifiers;
|
||||
unsigned int modifiers;
|
||||
} XKBStateGroup;
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct xkb_context* context;
|
||||
struct xkb_keymap* keymap;
|
||||
struct xkb_keymap* default_keymap;
|
||||
XKBStateGroup states;
|
||||
|
||||
xkb_mod_index_t controlIdx;
|
||||
xkb_mod_index_t altIdx;
|
||||
@@ -53,9 +62,8 @@ typedef struct {
|
||||
xkb_mod_mask_t superMask;
|
||||
xkb_mod_mask_t capsLockMask;
|
||||
xkb_mod_mask_t numLockMask;
|
||||
xkb_mod_mask_t activeUnknownModifiers;
|
||||
unsigned int modifiers;
|
||||
xkb_mod_index_t unknownModifiers[256];
|
||||
_GLFWIBUSData ibus;
|
||||
|
||||
#ifdef _GLFW_X11
|
||||
int32_t keyboard_device_id;
|
||||
@@ -84,3 +92,5 @@ const char* glfw_xkb_keysym_name(xkb_keysym_t sym);
|
||||
xkb_keysym_t glfw_xkb_sym_for_key(int key);
|
||||
void glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action);
|
||||
int glfw_xkb_keysym_from_name(const char *name, GLFWbool case_sensitive);
|
||||
void glfw_xkb_update_ime_state(_GLFWwindow *w, _GLFWXKBData *xkb, int which, int a, int b, int c, int d);
|
||||
void glfw_xkb_key_from_ime(KeyEvent *ev, GLFWbool handled_by_ime, GLFWbool failed);
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
|
||||
from ..tui.handler import Handler
|
||||
@@ -13,7 +15,6 @@ class Clipboard(Handler):
|
||||
|
||||
def __init__(self, data_to_send, args):
|
||||
self.args = args
|
||||
self.print_on_fail = None
|
||||
self.clipboard_contents = None
|
||||
self.data_to_send = data_to_send
|
||||
|
||||
@@ -63,16 +64,13 @@ def main(args):
|
||||
data = None
|
||||
if not sys.stdin.isatty():
|
||||
data = sys.stdin.buffer.read()
|
||||
sys.stdin = open('/dev/tty', 'r')
|
||||
sys.stdin = open(os.ctermid(), 'r')
|
||||
loop = Loop()
|
||||
handler = Clipboard(data, args)
|
||||
loop.loop(handler)
|
||||
if loop.return_code == 0 and handler.clipboard_contents:
|
||||
sys.stdout.write(handler.clipboard_contents)
|
||||
sys.stdout.flush()
|
||||
if handler.print_on_fail:
|
||||
print(handler.print_on_fail, file=sys.stderr)
|
||||
input('Press Enter to quit')
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ def highlight_collection(collection, aliases=None):
|
||||
jobs = {}
|
||||
ans = {}
|
||||
with concurrent.futures.ProcessPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
highlight_collection.processes = executor._processes
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type != 'rename':
|
||||
for p in (path, other_path):
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
@@ -24,14 +25,14 @@ from .collect import (
|
||||
set_highlight_data
|
||||
)
|
||||
from .config import init_config
|
||||
from .patch import Differ, set_diff_command
|
||||
from .patch import Differ, set_diff_command, worker_processes
|
||||
from .render import ImageSupportWarning, LineRef, render_diff
|
||||
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)
|
||||
@@ -42,7 +43,7 @@ def generate_diff(collection, context):
|
||||
|
||||
for path, item_type, changed_path in collection:
|
||||
if item_type == 'diff':
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
is_binary = isinstance(data_for_path(path), bytes) or isinstance(data_for_path(changed_path), bytes)
|
||||
if not is_binary:
|
||||
d.add_diff(path, changed_path)
|
||||
|
||||
@@ -106,10 +107,67 @@ class DiffHandler(Handler):
|
||||
return
|
||||
|
||||
def create_collection(self):
|
||||
self.start_job('collect', create_collection, self.left, self.right)
|
||||
|
||||
def collect_done(collection):
|
||||
self.collection = collection
|
||||
self.state = COLLECTED
|
||||
self.generate_diff()
|
||||
|
||||
def collect(left, right):
|
||||
collection = create_collection(left, right)
|
||||
self.asyncio_loop.call_soon_threadsafe(collect_done, collection)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, collect, self.left, self.right)
|
||||
|
||||
def generate_diff(self):
|
||||
self.start_job('diff', generate_diff, self.collection, self.current_context_count)
|
||||
|
||||
def diff_done(diff_map):
|
||||
if isinstance(diff_map, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.state = DIFFED
|
||||
self.diff_map = diff_map
|
||||
self.calculate_statistics()
|
||||
self.render_diff()
|
||||
self.scroll_pos = 0
|
||||
if self.restore_position is not None:
|
||||
self.current_position = self.restore_position
|
||||
self.restore_position = None
|
||||
self.draw_screen()
|
||||
if initialize_highlighter is not None and not self.highlighting_done:
|
||||
from .highlight import StyleNotFound
|
||||
self.highlighting_done = True
|
||||
try:
|
||||
initialize_highlighter(self.opts.pygments_style)
|
||||
except StyleNotFound as e:
|
||||
self.report_traceback_on_exit = str(e)
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.syntax_highlight()
|
||||
|
||||
def diff(collection, current_context_count):
|
||||
diff_map = generate_diff(collection, current_context_count)
|
||||
self.asyncio_loop.call_soon_threadsafe(diff_done, diff_map)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, diff, self.collection, self.current_context_count)
|
||||
|
||||
def syntax_highlight(self):
|
||||
|
||||
def highlighting_done(hdata):
|
||||
if isinstance(hdata, str):
|
||||
self.report_traceback_on_exit = hdata
|
||||
self.quit_loop(1)
|
||||
return
|
||||
set_highlight_data(hdata)
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def highlight(*a):
|
||||
result = highlight_collection(*a)
|
||||
self.asyncio_loop.call_soon_threadsafe(highlighting_done, result)
|
||||
|
||||
self.asyncio_loop.run_in_executor(None, highlight, self.collection, self.opts.syntax_aliases)
|
||||
|
||||
def calculate_statistics(self):
|
||||
self.added_count = self.collection.added_count
|
||||
@@ -413,50 +471,6 @@ class DiffHandler(Handler):
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_job_done(self, job_id, job_result):
|
||||
if 'tb' in job_result:
|
||||
self.report_traceback_on_exit = job_result['tb']
|
||||
self.quit_loop(1)
|
||||
return
|
||||
if job_id == 'collect':
|
||||
self.collection = job_result['result']
|
||||
self.state = COLLECTED
|
||||
self.generate_diff()
|
||||
elif job_id == 'diff':
|
||||
diff_map = job_result['result']
|
||||
if isinstance(diff_map, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.state = DIFFED
|
||||
self.diff_map = diff_map
|
||||
self.calculate_statistics()
|
||||
self.render_diff()
|
||||
self.scroll_pos = 0
|
||||
if self.restore_position is not None:
|
||||
self.current_position = self.restore_position
|
||||
self.restore_position = None
|
||||
self.draw_screen()
|
||||
if initialize_highlighter is not None and not self.highlighting_done:
|
||||
from .highlight import StyleNotFound
|
||||
self.highlighting_done = True
|
||||
try:
|
||||
initialize_highlighter(self.opts.pygments_style)
|
||||
except StyleNotFound as e:
|
||||
self.report_traceback_on_exit = str(e)
|
||||
self.quit_loop(1)
|
||||
return
|
||||
self.start_job('highlight', highlight_collection, self.collection, self.opts.syntax_aliases)
|
||||
elif job_id == 'highlight':
|
||||
hdata = job_result['result']
|
||||
if isinstance(hdata, str):
|
||||
self.report_traceback_on_exit = diff_map
|
||||
self.quit_loop(1)
|
||||
return
|
||||
set_highlight_data(hdata)
|
||||
self.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
@@ -495,6 +509,14 @@ help_text = 'Show a side-by-side diff of the specified files/directories'
|
||||
usage = 'file_or_directory_left file_or_directory_right'
|
||||
|
||||
|
||||
def terminate_processes(processes):
|
||||
for pid in processes:
|
||||
try:
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main(args):
|
||||
warnings.showwarning = showwarning
|
||||
args, items = parse_args(args[1:], OPTIONS, usage, help_text, 'kitty +kitten diff')
|
||||
@@ -517,6 +539,9 @@ def main(args):
|
||||
for message in showwarning.warnings:
|
||||
from kitty.utils import safe_print
|
||||
safe_print(message, file=sys.stderr)
|
||||
highlight_processes = getattr(highlight_collection, 'processes', ())
|
||||
terminate_processes(tuple(highlight_processes))
|
||||
terminate_processes(tuple(worker_processes))
|
||||
if loop.return_code != 0:
|
||||
if handler.report_traceback_on_exit:
|
||||
print(handler.report_traceback_on_exit, file=sys.stderr)
|
||||
|
||||
@@ -14,6 +14,7 @@ from .diff_speedup import changed_center
|
||||
left_lines = right_lines = None
|
||||
GIT_DIFF = 'git diff --no-color --no-ext-diff --exit-code -U_CONTEXT_ --no-index --'
|
||||
DIFF_DIFF = 'diff -p -U _CONTEXT_ --'
|
||||
worker_processes = []
|
||||
|
||||
|
||||
def find_differ():
|
||||
@@ -43,8 +44,10 @@ def run_diff(file1, file2, context=3):
|
||||
p = subprocess.Popen(
|
||||
cmd + [path1, path2],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
||||
worker_processes.append(p.pid)
|
||||
stdout, stderr = p.communicate()
|
||||
returncode = p.wait()
|
||||
worker_processes.remove(p.pid)
|
||||
if returncode in (0, 1):
|
||||
return True, returncode == 1, stdout.decode('utf-8')
|
||||
return False, returncode, stderr.decode('utf-8')
|
||||
@@ -205,9 +208,13 @@ def parse_patch(raw):
|
||||
|
||||
class Differ:
|
||||
|
||||
diff_executor = None
|
||||
|
||||
def __init__(self):
|
||||
self.jmap = {}
|
||||
self.jobs = []
|
||||
if Differ.diff_executor is None:
|
||||
Differ.diff_executor = self.diff_executor = concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count())
|
||||
|
||||
def add_diff(self, file1, file2):
|
||||
self.jmap[file1] = file2
|
||||
@@ -216,26 +223,26 @@ class Differ:
|
||||
def __call__(self, context=3):
|
||||
global left_lines, right_lines
|
||||
ans = {}
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
|
||||
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
key = jobs[future]
|
||||
left_path, right_path = key, self.jmap[key]
|
||||
try:
|
||||
ok, returncode, output = future.result()
|
||||
except FileNotFoundError as err:
|
||||
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
|
||||
except Exception as e:
|
||||
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
|
||||
if not ok:
|
||||
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
left_lines = lines_for_path(left_path)
|
||||
right_lines = lines_for_path(right_path)
|
||||
try:
|
||||
patch = parse_patch(output)
|
||||
except Exception:
|
||||
import traceback
|
||||
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
else:
|
||||
ans[key] = patch
|
||||
executor = Differ.diff_executor
|
||||
jobs = {executor.submit(run_diff, key, self.jmap[key], context): key for key in self.jobs}
|
||||
for future in concurrent.futures.as_completed(jobs):
|
||||
key = jobs[future]
|
||||
left_path, right_path = key, self.jmap[key]
|
||||
try:
|
||||
ok, returncode, output = future.result()
|
||||
except FileNotFoundError as err:
|
||||
return 'Could not find the {} executable. Is it in your PATH?'.format(err.filename)
|
||||
except Exception as e:
|
||||
return 'Running git diff for {} vs. {} generated an exception: {}'.format(left_path, right_path, e)
|
||||
if not ok:
|
||||
return output + '\nRunning git diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
left_lines = lines_for_path(left_path)
|
||||
right_lines = lines_for_path(right_path)
|
||||
try:
|
||||
patch = parse_patch(output)
|
||||
except Exception:
|
||||
import traceback
|
||||
return traceback.format_exc() + '\nParsing diff for {} vs. {} failed'.format(left_path, right_path)
|
||||
else:
|
||||
ans[key] = patch
|
||||
return ans
|
||||
|
||||
@@ -453,7 +453,9 @@ def render_diff(collection, diff_map, args, columns, image_manager):
|
||||
for i, (path, item_type, other_path) in enumerate(collection):
|
||||
item_ref = Reference(path)
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
is_img = is_binary and is_image(path) and images_supported()
|
||||
if not is_binary and item_type == 'diff' and isinstance(data_for_path(other_path), bytes):
|
||||
is_binary = True
|
||||
is_img = is_binary and (is_image(path) or is_image(other_path)) and images_supported()
|
||||
yield from yield_lines_from(title_lines(path, other_path, args, columns, margin_size), item_ref, False)
|
||||
if item_type == 'diff':
|
||||
if is_binary:
|
||||
|
||||
@@ -152,7 +152,6 @@ class Hints(Handler):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
@@ -171,7 +170,7 @@ def regex_finditer(pat, minimum_match_length, text):
|
||||
yield s, e
|
||||
|
||||
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>'}
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>', '*': '*', '"': '"', "'": "'"}
|
||||
opening_brackets = ''.join(closing_bracket_map)
|
||||
postprocessor_map = {}
|
||||
|
||||
@@ -190,15 +189,15 @@ def url(text, s, e):
|
||||
e -= len(url) - idx
|
||||
while text[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
|
||||
e -= 1
|
||||
# truncate url at closing bracket/quote
|
||||
if s > 0 and e <= len(text) and text[s-1] in opening_brackets:
|
||||
q = closing_bracket_map[text[s-1]]
|
||||
idx = text.find(q, s)
|
||||
if idx > s:
|
||||
e = idx
|
||||
# Restructured Text URLs
|
||||
if e > 3 and text[e-2:e] == '`_':
|
||||
e -= 2
|
||||
# Remove trailing bracket if matched by leading bracket
|
||||
if s > 0 and e < len(text) and text[s-1] in opening_brackets and text[e-1] == closing_bracket_map[text[s-1]]:
|
||||
e -= 1
|
||||
# Remove trailing quote if matched by leading quote
|
||||
if s > 0 and e < len(text) and text[s-1] in '\'"' and text[e-1] == text[s-1]:
|
||||
e -= 1
|
||||
|
||||
return s, e
|
||||
|
||||
@@ -256,7 +255,7 @@ def functions_for(args):
|
||||
)
|
||||
post_processors.append(url)
|
||||
elif args.type == 'path':
|
||||
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'
|
||||
pattern = r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})'
|
||||
post_processors.extend((brackets, quotes))
|
||||
elif args.type == 'line':
|
||||
pattern = '(?m)^\\s*(.+)[\\s\0]*$'
|
||||
@@ -382,7 +381,7 @@ def main(args):
|
||||
return
|
||||
else:
|
||||
text = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open('/dev/tty')
|
||||
sys.stdin = open(os.ctermid())
|
||||
try:
|
||||
args, items = parse_hints_args(args[1:])
|
||||
except SystemExit as e:
|
||||
|
||||
@@ -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
|
||||
@@ -260,13 +260,13 @@ def main(args=sys.argv):
|
||||
|
||||
if args.print_window_size:
|
||||
screen_size_function.ans = None
|
||||
with open('/dev/tty') as tty:
|
||||
with open(os.ctermid()) as tty:
|
||||
ss = screen_size_function(tty)()
|
||||
print('{}x{}'.format(ss.width, ss.height), end='')
|
||||
raise SystemExit(0)
|
||||
|
||||
if not sys.stdout.isatty():
|
||||
sys.stdout = open('/dev/tty', 'w')
|
||||
sys.stdout = open(os.ctermid(), 'w')
|
||||
screen_size = screen_size_function()
|
||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||
if screen_size().width == 0:
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from kitty.constants import is_macos, is_wayland
|
||||
from kitty.cli import parse_args
|
||||
|
||||
from kitty.constants import is_macos, is_wayland
|
||||
|
||||
OPTIONS = r'''
|
||||
--lines
|
||||
@@ -123,7 +123,8 @@ def main(sys_args):
|
||||
global args
|
||||
if is_macos or is_wayland:
|
||||
raise SystemExit('Currently the panel kitten is supported only on X11 desktops')
|
||||
call_xprop('-version', silent=True) # ensure xprop is available
|
||||
if not shutil.which('xprop'):
|
||||
raise SystemExit('The xprop program is required for the panel kitten')
|
||||
args, items = parse_panel_args(sys_args[1:])
|
||||
if not items:
|
||||
raise SystemExit('You must specify the program to run')
|
||||
|
||||
@@ -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):
|
||||
@@ -74,7 +74,6 @@ class Resize(Handler):
|
||||
self.do_window_resize(is_decrease=key_event.key in (N, S), is_horizontal=key_event.key in (W, N), multiplier=2)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
|
||||
@@ -17,23 +17,27 @@ def resolved_kitten(k):
|
||||
|
||||
def import_kitten_main_module(config_dir, kitten):
|
||||
if kitten.endswith('.py'):
|
||||
path_modified = False
|
||||
path = os.path.expanduser(kitten)
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(config_dir, path)
|
||||
path = os.path.abspath(path)
|
||||
if os.path.dirname(path):
|
||||
sys.path.insert(0, os.path.dirname(path))
|
||||
path_modified = True
|
||||
with open(path) as f:
|
||||
src = f.read()
|
||||
code = compile(src, path, 'exec')
|
||||
g = {'__name__': 'kitten'}
|
||||
exec(code, g)
|
||||
hr = g.get('handle_result', lambda *a, **kw: None)
|
||||
if path_modified:
|
||||
del sys.path[0]
|
||||
return {'start': g['main'], 'end': hr}
|
||||
else:
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
||||
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||
return {'start': m.main, 'end': getattr(m, 'handle_result', lambda *a, **k: None)}
|
||||
|
||||
|
||||
def create_kitten_handler(kitten, orig_args):
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from ..tui.operations import styled
|
||||
|
||||
from ..tui.operations import styled
|
||||
|
||||
OPTIONS = '''\
|
||||
--title
|
||||
@@ -17,7 +18,7 @@ The title for the error message.
|
||||
|
||||
def real_main(args):
|
||||
error_message = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open('/dev/tty')
|
||||
sys.stdin = open(os.ctermid())
|
||||
msg = 'Show an error message'
|
||||
args, items = parse_args(args, OPTIONS, '', msg, 'hints')
|
||||
print(styled(args.title, fg_intense=True, fg='red', bold=True))
|
||||
|
||||
@@ -102,7 +102,7 @@ def main(args):
|
||||
sh_script = SHELL_SCRIPT.replace('TERMINFO', terminfo, 1)
|
||||
if len(server_args) > 1:
|
||||
command_to_execute = ["'{}'".format(c.replace("'", """'"'"'""")) for c in server_args[1:]]
|
||||
command_to_execute = 'cmd=({}); exec "$cmd"'.format(' '.join(command_to_execute))
|
||||
command_to_execute = 'exec ' + ' '.join(command_to_execute)
|
||||
else:
|
||||
command_to_execute = ''
|
||||
sh_script = sh_script.replace('EXEC_CMD', command_to_execute)
|
||||
|
||||
@@ -10,14 +10,19 @@ class Handler:
|
||||
|
||||
image_manager_class = None
|
||||
|
||||
def _initialize(self, screen_size, quit_loop, wakeup, start_job, debug, image_manager=None):
|
||||
self.screen_size, self.quit_loop = screen_size, quit_loop
|
||||
self.wakeup = wakeup
|
||||
def _initialize(self, screen_size, term_manager, schedule_write, tui_loop, debug, image_manager=None):
|
||||
self.screen_size = screen_size
|
||||
self._term_manager = term_manager
|
||||
self._tui_loop = tui_loop
|
||||
self._schedule_write = schedule_write
|
||||
self.debug = debug
|
||||
self.start_job = start_job
|
||||
self.cmd = commander(self)
|
||||
self.image_manager = image_manager
|
||||
|
||||
@property
|
||||
def asyncio_loop(self):
|
||||
return self._tui_loop.asycio_loop
|
||||
|
||||
def add_shortcut(self, action, key, mods=None, is_text=False):
|
||||
if not hasattr(self, '_text_shortcuts'):
|
||||
self._text_shortcuts, self._key_shortcuts = {}, {}
|
||||
@@ -38,7 +43,6 @@ class Handler:
|
||||
self.initialize()
|
||||
|
||||
def __exit__(self, *a):
|
||||
del self.write_buf[:]
|
||||
del self.debug.fobj
|
||||
self.finalize()
|
||||
if self.image_manager is not None:
|
||||
@@ -53,8 +57,11 @@ class Handler:
|
||||
def on_resize(self, screen_size):
|
||||
self.screen_size = screen_size
|
||||
|
||||
def quit_loop(self, return_code=None):
|
||||
self._tui_loop.quit(return_code)
|
||||
|
||||
def on_term(self):
|
||||
self.quit_loop(1)
|
||||
self._tui_loop.quit(1)
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
pass
|
||||
@@ -71,12 +78,6 @@ class Handler:
|
||||
def on_eot(self):
|
||||
pass
|
||||
|
||||
def on_wakeup(self):
|
||||
pass
|
||||
|
||||
def on_job_done(self, job_id, job_result):
|
||||
pass
|
||||
|
||||
def on_kitty_cmd_response(self, response):
|
||||
pass
|
||||
|
||||
@@ -86,7 +87,7 @@ class Handler:
|
||||
def write(self, data):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
self.write_buf.append(data)
|
||||
self._schedule_write(data)
|
||||
|
||||
def print(self, *args, sep=' ', end='\r\n'):
|
||||
data = sep.join(map(str, args)) + end
|
||||
|
||||
@@ -71,13 +71,15 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
|
||||
from tempfile import NamedTemporaryFile
|
||||
width, height = m.width, m.height
|
||||
cmd = ['convert', '-background', 'none', path]
|
||||
scaled = False
|
||||
if scale_up:
|
||||
if width < available_width:
|
||||
r = available_width / width
|
||||
width, height = available_width, int(height * r)
|
||||
if width > available_width or height > available_height:
|
||||
scaled = True
|
||||
if scaled or width > available_width or height > available_height:
|
||||
width, height = fit_image(width, height, available_width, available_height)
|
||||
cmd += ['-resize', '{}x{}'.format(width, height)]
|
||||
cmd += ['-resize', '{}x{}!'.format(width, height)]
|
||||
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False, dir=tdir) as outfile:
|
||||
run_imagemagick(path, cmd + [outfile.name])
|
||||
# ImageMagick sometimes generated rgba images smaller than the specified
|
||||
@@ -88,7 +90,10 @@ def convert(path, m, available_width, available_height, scale_up, tdir=None):
|
||||
if sz < expected_size:
|
||||
missing = expected_size - sz
|
||||
if missing % (bytes_per_pixel * width) != 0:
|
||||
raise ConvertFailed(path, 'ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
|
||||
raise ConvertFailed(
|
||||
path, 'ImageMagick failed to convert {} correctly,'
|
||||
' it generated {} < {} of data (w={}, h={}, bpp={})'.format(
|
||||
path, sz, expected_size, width, height, bytes_per_pixel))
|
||||
height -= missing // (bytes_per_pixel * width)
|
||||
|
||||
return outfile.name, width, height
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import asyncio
|
||||
import codecs
|
||||
import io
|
||||
import os
|
||||
@@ -10,13 +11,12 @@ import selectors
|
||||
import signal
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from contextlib import closing, contextmanager
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from queue import Empty, Queue
|
||||
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import (
|
||||
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty,
|
||||
safe_pipe
|
||||
close_tty, normal_tty, open_tty, parse_input_from_terminal, raw_tty
|
||||
)
|
||||
from kitty.key_encoding import (
|
||||
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
|
||||
@@ -74,29 +74,6 @@ class TermManager:
|
||||
del self.tty_fd, self.original_termios
|
||||
|
||||
|
||||
class SignalManager:
|
||||
|
||||
def __init__(self, on_sigwinch, on_sigterm, on_sigint):
|
||||
self.on_sigwinch = on_sigwinch
|
||||
self.on_sigterm = on_sigterm
|
||||
self.on_sigint = on_sigint
|
||||
|
||||
def __enter__(self):
|
||||
for x in ('winch', 'term', 'int'):
|
||||
attr = 'on_sig' + x
|
||||
handler = getattr(self, attr)
|
||||
old_handler = signal.signal(getattr(signal, 'SIG' + x.upper()), handler)
|
||||
setattr(self, attr, old_handler)
|
||||
|
||||
def __exit__(self, *a):
|
||||
for x in ('winch', 'term', 'int'):
|
||||
attr = 'on_sig' + x
|
||||
val = getattr(self, attr)
|
||||
if val is None:
|
||||
val = signal.SIG_DFL
|
||||
signal.signal(getattr(signal, 'SIG' + x.upper()), val)
|
||||
|
||||
|
||||
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
|
||||
DRAG = REPEAT
|
||||
MouseEvent = namedtuple('MouseEvent', 'x y type buttons mods')
|
||||
@@ -150,22 +127,38 @@ class UnhandledException(Handler):
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
on_eot = on_term = on_interrupt
|
||||
|
||||
def on_eot(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
class SignalManager:
|
||||
|
||||
def __init__(self, loop, on_winch, on_interrupt, on_term):
|
||||
self.asycio_loop = loop
|
||||
self.on_winch, self.on_interrupt, self.on_term = on_winch, on_interrupt, on_term
|
||||
|
||||
def __enter__(self):
|
||||
tuple(map(lambda x: self.asycio_loop.add_signal_handler(*x), (
|
||||
(signal.SIGWINCH, self.on_winch),
|
||||
(signal.SIGINT, self.on_interrupt),
|
||||
(signal.SIGTERM, self.on_term)
|
||||
)))
|
||||
|
||||
def __exit__(self, *a):
|
||||
tuple(map(self.asycio_loop.remove_signal_handler, (
|
||||
signal.SIGWINCH, signal.SIGINT, signal.SIGTERM)))
|
||||
|
||||
|
||||
class Loop:
|
||||
|
||||
def __init__(self,
|
||||
sanitize_bracketed_paste='[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'):
|
||||
self.wakeup_read_fd, self.wakeup_write_fd = safe_pipe()
|
||||
# For some reason on macOS the DefaultSelector fails when tty_fd is
|
||||
# open('/dev/tty')
|
||||
self.sel = s = selectors.PollSelector()
|
||||
s.register(self.wakeup_read_fd, selectors.EVENT_READ)
|
||||
if is_macos:
|
||||
# On macOS PTY devices are not supported by the KqueueSelector
|
||||
self.asycio_loop = asyncio.SelectorEventLoop(selectors.PollSelector())
|
||||
asyncio.set_event_loop(self.asycio_loop)
|
||||
else:
|
||||
self.asycio_loop = asyncio.get_event_loop()
|
||||
self.return_code = 0
|
||||
self.read_allowed = True
|
||||
self.read_buf = ''
|
||||
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||
try:
|
||||
@@ -178,28 +171,8 @@ class Loop:
|
||||
self.sanitize_bracketed_paste = bool(sanitize_bracketed_paste)
|
||||
if self.sanitize_bracketed_paste:
|
||||
self.sanitize_ibp_pat = re.compile(sanitize_bracketed_paste)
|
||||
self.jobs_queue = Queue()
|
||||
|
||||
def start_job(self, job_id, func, *args, **kw):
|
||||
from threading import Thread
|
||||
t = Thread(target=partial(self._run_job, job_id, func), args=args, kwargs=kw, name='LoopJob')
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
def _run_job(self, job_id, func, *args, **kw):
|
||||
try:
|
||||
result = func(*args, **kw)
|
||||
except Exception as err:
|
||||
import traceback
|
||||
entry = {'id': job_id, 'exception': err, 'tb': traceback.format_exc()}
|
||||
else:
|
||||
entry = {'id': job_id, 'result': result}
|
||||
self.jobs_queue.put(entry)
|
||||
self._wakeup_write(b'j')
|
||||
|
||||
def _read_ready(self, handler, fd):
|
||||
if not self.read_allowed:
|
||||
return
|
||||
try:
|
||||
data = os.read(fd, io.DEFAULT_BUFFER_SIZE)
|
||||
except BlockingIOError:
|
||||
@@ -219,6 +192,7 @@ class Loop:
|
||||
finally:
|
||||
del self.handler
|
||||
|
||||
# terminal input callbacks {{{
|
||||
def _on_text(self, text):
|
||||
if self.in_bracketed_paste and self.sanitize_bracketed_paste:
|
||||
text = self.sanitize_ibp_pat.sub('', text)
|
||||
@@ -292,127 +266,99 @@ class Loop:
|
||||
elif apc.startswith('G'):
|
||||
if self.handler.image_manager is not None:
|
||||
self.handler.image_manager.handle_response(apc)
|
||||
# }}}
|
||||
|
||||
def _write_ready(self, handler, fd):
|
||||
if len(handler.write_buf) > self.iov_limit:
|
||||
handler.write_buf[self.iov_limit - 1] = b''.join(handler.write_buf[self.iov_limit - 1:])
|
||||
del handler.write_buf[self.iov_limit:]
|
||||
sizes = tuple(map(len, handler.write_buf))
|
||||
if len(self.write_buf) > self.iov_limit:
|
||||
self.write_buf[self.iov_limit - 1] = b''.join(self.write_buf[self.iov_limit - 1:])
|
||||
del self.write_buf[self.iov_limit:]
|
||||
sizes = tuple(map(len, self.write_buf))
|
||||
try:
|
||||
written = os.writev(fd, handler.write_buf)
|
||||
written = os.writev(fd, self.write_buf)
|
||||
except BlockingIOError:
|
||||
return
|
||||
if not written:
|
||||
raise EOFError('The output stream is closed')
|
||||
if written >= sum(sizes):
|
||||
handler.write_buf = []
|
||||
self.write_buf = []
|
||||
self.asycio_loop.remove_writer(fd)
|
||||
self.waiting_for_writes = False
|
||||
else:
|
||||
consumed = 0
|
||||
for i, buf in enumerate(handler.write_buf):
|
||||
for i, buf in enumerate(self.write_buf):
|
||||
if not written:
|
||||
break
|
||||
if len(buf) <= written:
|
||||
written -= len(buf)
|
||||
consumed += 1
|
||||
continue
|
||||
handler.write_buf[i] = buf[written:]
|
||||
self.write_buf[i] = buf[written:]
|
||||
break
|
||||
del handler.write_buf[:consumed]
|
||||
|
||||
def _wakeup_ready(self, handler, fd):
|
||||
data = os.read(fd, 1024)
|
||||
if b'r' in data:
|
||||
self._get_screen_size.changed = True
|
||||
handler.on_resize(self._get_screen_size())
|
||||
if b't' in data:
|
||||
handler.on_term()
|
||||
if b'i' in data:
|
||||
handler.on_interrupt()
|
||||
if b'j' in data:
|
||||
while True:
|
||||
try:
|
||||
entry = self.jobs_queue.get_nowait()
|
||||
except Empty:
|
||||
break
|
||||
else:
|
||||
job_id = entry.pop('id')
|
||||
handler.on_job_done(job_id, entry)
|
||||
if b'1' in data:
|
||||
handler.on_wakeup()
|
||||
|
||||
def _wakeup_write(self, val):
|
||||
while not os.write(self.wakeup_write_fd, val):
|
||||
pass
|
||||
|
||||
def _on_sigwinch(self, signum, frame):
|
||||
self._wakeup_write(b'r')
|
||||
|
||||
def _on_sigterm(self, signum, frame):
|
||||
self._wakeup_write(b't')
|
||||
|
||||
def _on_sigint(self, signum, frame):
|
||||
self._wakeup_write(b'i')
|
||||
del self.write_buf[:consumed]
|
||||
|
||||
def quit(self, return_code=None):
|
||||
self.read_allowed = False
|
||||
if return_code is not None:
|
||||
self.return_code = return_code
|
||||
self.asycio_loop.stop()
|
||||
|
||||
def wakeup(self):
|
||||
self._wakeup_write(b'1')
|
||||
def loop_impl(self, handler, term_manager, image_manager=None):
|
||||
self.write_buf = []
|
||||
tty_fd = term_manager.tty_fd
|
||||
tb = None
|
||||
self.waiting_for_writes = True
|
||||
|
||||
def _modify_output_selector(self, tty_fd, waiting_for_write):
|
||||
events = selectors.EVENT_READ
|
||||
if waiting_for_write:
|
||||
events |= selectors.EVENT_WRITE
|
||||
self.sel.modify(tty_fd, events)
|
||||
def schedule_write(data):
|
||||
self.write_buf.append(data)
|
||||
if not self.waiting_for_writes:
|
||||
self.asycio_loop.add_writer(tty_fd, self._write_ready, handler, tty_fd)
|
||||
self.waiting_for_writes = True
|
||||
|
||||
def loop_impl(self, handler, tty_fd, image_manager=None, waiting_for_write=True):
|
||||
read_ready, write_ready, wakeup_ready = self._read_ready, self._write_ready, self._wakeup_ready
|
||||
select = self.sel.select
|
||||
handler._initialize(self._get_screen_size(), self.quit, self.wakeup, self.start_job, debug, image_manager)
|
||||
def handle_exception(loop, context):
|
||||
nonlocal tb
|
||||
loop.stop()
|
||||
tb = context['message']
|
||||
exc = context.get('exception')
|
||||
if exc is not None:
|
||||
import traceback
|
||||
tb += '\n' + ''.join(traceback.format_exception(exc.__class__, exc, exc.__traceback__))
|
||||
|
||||
self.asycio_loop.set_exception_handler(handle_exception)
|
||||
handler._initialize(self._get_screen_size(), term_manager, schedule_write, self, debug, image_manager)
|
||||
with handler:
|
||||
while True:
|
||||
has_data_to_write = bool(handler.write_buf)
|
||||
if not has_data_to_write and not self.read_allowed:
|
||||
break
|
||||
if has_data_to_write != waiting_for_write:
|
||||
waiting_for_write = has_data_to_write
|
||||
self._modify_output_selector(tty_fd, waiting_for_write)
|
||||
for key, mask in select():
|
||||
fd = key.fd
|
||||
if fd == tty_fd:
|
||||
if mask & selectors.EVENT_READ:
|
||||
read_ready(handler, fd)
|
||||
if mask & selectors.EVENT_WRITE:
|
||||
write_ready(handler, fd)
|
||||
else:
|
||||
wakeup_ready(handler, fd)
|
||||
self.asycio_loop.add_reader(
|
||||
tty_fd, self._read_ready, handler, tty_fd)
|
||||
self.asycio_loop.add_writer(
|
||||
tty_fd, self._write_ready, handler, tty_fd)
|
||||
self.asycio_loop.run_forever()
|
||||
self.asycio_loop.remove_reader(tty_fd)
|
||||
if self.waiting_for_writes:
|
||||
self.asycio_loop.remove_writer(tty_fd)
|
||||
return tb
|
||||
|
||||
def loop(self, handler):
|
||||
tb = None
|
||||
signal_manager = SignalManager(self._on_sigwinch, self._on_sigterm, self._on_sigint)
|
||||
with closing(self.sel), TermManager() as term_manager, signal_manager:
|
||||
self.sel.register(term_manager.tty_fd, selectors.EVENT_READ | selectors.EVENT_WRITE)
|
||||
|
||||
def _on_sigwinch():
|
||||
self._get_screen_size.changed = True
|
||||
handler.screen_size = self._get_screen_size()
|
||||
handler.on_resize(handler.screen_size)
|
||||
|
||||
signal_manager = SignalManager(self.asycio_loop, _on_sigwinch, handler.on_interrupt, handler.on_term)
|
||||
with TermManager() as term_manager, signal_manager:
|
||||
self._get_screen_size = screen_size_function(term_manager.tty_fd)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
image_manager = None
|
||||
if handler.image_manager_class is not None:
|
||||
image_manager = handler.image_manager_class(handler)
|
||||
try:
|
||||
self.loop_impl(handler, term_manager.tty_fd, image_manager)
|
||||
tb = self.loop_impl(handler, term_manager, image_manager)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
self.return_code = 1
|
||||
|
||||
term_manager.extra_finalize = b''.join(handler.write_buf).decode('utf-8')
|
||||
term_manager.extra_finalize = b''.join(self.write_buf).decode('utf-8')
|
||||
if tb is not None:
|
||||
self.return_code = 1
|
||||
self._report_error_loop(tb, term_manager)
|
||||
|
||||
def _report_error_loop(self, tb, term_manager):
|
||||
handler = UnhandledException(tb)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
self.loop_impl(handler, term_manager.tty_fd, waiting_for_write=False)
|
||||
self.loop_impl(UnhandledException(tb), term_manager)
|
||||
|
||||
@@ -455,7 +455,6 @@ class UnicodeInput(Handler):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.refresh()
|
||||
|
||||
|
||||
|
||||
2
kittens/unicode_input/names.h
generated
2
kittens/unicode_input/names.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-06-14
|
||||
// unicode data, built from the unicode standard on: 2018-08-04
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from functools import partial
|
||||
from itertools import chain
|
||||
|
||||
from .fast_data_types import (
|
||||
@@ -10,6 +9,16 @@ from .fast_data_types import (
|
||||
)
|
||||
from .utils import load_shaders
|
||||
|
||||
try:
|
||||
from enum import IntFlag
|
||||
except Exception:
|
||||
from enum import IntEnum as IntFlag
|
||||
|
||||
|
||||
class BorderColor(IntFlag):
|
||||
# See the border vertex shader for how these flags become actual colors
|
||||
default_bg, active, inactive, window_bg, bell = ((1 << i) for i in range(5))
|
||||
|
||||
|
||||
def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left):
|
||||
add_borders_rect(os_window_id, tab_id, left, top, left + width, bottom, color)
|
||||
@@ -19,15 +28,15 @@ def horizontal_edge(os_window_id, tab_id, color, height, left, right, top):
|
||||
add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color)
|
||||
|
||||
|
||||
def edge(func, os_window_id, tab_id, color, sz, a, b):
|
||||
return partial(func, os_window_id, tab_id, color, sz, a, b)
|
||||
|
||||
|
||||
def border(os_window_id, tab_id, color, sz, left, top, right, bottom):
|
||||
horz = edge(horizontal_edge, os_window_id, tab_id, color, sz, left, right)
|
||||
horz(top), horz(bottom - sz) # top, bottom edges
|
||||
vert = edge(vertical_edge, os_window_id, tab_id, color, sz, top, bottom)
|
||||
vert(left), vert(right - sz) # left, right edges
|
||||
def draw_edges(os_window_id, tab_id, colors, width, geometry, base_width=0):
|
||||
left = geometry.left - (width + base_width)
|
||||
top = geometry.top - (width + base_width)
|
||||
right = geometry.right + (width + base_width)
|
||||
bottom = geometry.bottom + (width + base_width)
|
||||
horizontal_edge(os_window_id, tab_id, colors[1], width, left, right, top)
|
||||
horizontal_edge(os_window_id, tab_id, colors[3], width, left, right, geometry.bottom + base_width)
|
||||
vertical_edge(os_window_id, tab_id, colors[0], width, top, bottom, left)
|
||||
vertical_edge(os_window_id, tab_id, colors[2], width, top, bottom, geometry.right + base_width)
|
||||
|
||||
|
||||
def load_borders_program():
|
||||
@@ -52,28 +61,31 @@ class Borders:
|
||||
extra_blank_rects,
|
||||
draw_window_borders=True
|
||||
):
|
||||
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 1)
|
||||
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, BorderColor.default_bg)
|
||||
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
||||
add_borders_rect(self.os_window_id, self.tab_id, *br, 1)
|
||||
add_borders_rect(self.os_window_id, self.tab_id, *br, BorderColor.default_bg)
|
||||
bw, pw = self.border_width, self.padding_width
|
||||
fw = bw + pw
|
||||
if bw + pw <= 0:
|
||||
return
|
||||
draw_borders = bw > 0 and draw_window_borders and len(windows) > 1
|
||||
if draw_borders:
|
||||
border_data = current_layout.resolve_borders(windows, active_window)
|
||||
|
||||
if fw > 0:
|
||||
for w in windows:
|
||||
g = w.geometry
|
||||
if bw > 0 and draw_window_borders:
|
||||
# Draw the border rectangles
|
||||
color = 2 if w is active_window else (16 if w.needs_attention else 4)
|
||||
border(
|
||||
self.os_window_id, self.tab_id,
|
||||
color, bw, g.left - fw, g.top - fw, g.right + fw,
|
||||
g.bottom + fw
|
||||
)
|
||||
if pw > 0:
|
||||
# Draw the background rectangles over the padding region
|
||||
color = w.screen.color_profile.default_bg
|
||||
border(
|
||||
self.os_window_id, self.tab_id,
|
||||
(color << 8) | 8, pw, g.left - pw, g.top - pw, g.right + pw,
|
||||
g.bottom + pw
|
||||
)
|
||||
for i, w in enumerate(windows):
|
||||
g = w.geometry
|
||||
window_bg = w.screen.color_profile.default_bg
|
||||
window_bg = (window_bg << 8) | BorderColor.window_bg
|
||||
if draw_borders:
|
||||
# Draw the border rectangles
|
||||
if w is active_window:
|
||||
color = BorderColor.active
|
||||
else:
|
||||
color = BorderColor.bell if w.needs_attention else BorderColor.inactive
|
||||
colors = tuple(color if needed else window_bg for needed in next(border_data))
|
||||
draw_edges(
|
||||
self.os_window_id, self.tab_id, colors, bw, g, base_width=pw)
|
||||
if pw > 0:
|
||||
# Draw the background rectangles over the padding region
|
||||
colors = (window_bg, window_bg, window_bg, window_bg)
|
||||
draw_edges(
|
||||
self.os_window_id, self.tab_id, colors, pw, g)
|
||||
|
||||
183
kitty/boss.py
183
kitty/boss.py
@@ -19,12 +19,14 @@ 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
|
||||
from .remote_control import handle_cmd
|
||||
from .rgb import Color, color_from_int
|
||||
from .session import create_session
|
||||
@@ -46,6 +48,23 @@ def listen_on(spec):
|
||||
return s.fileno()
|
||||
|
||||
|
||||
def data_for_at(w, 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 arg == '@alternate':
|
||||
return w.as_text(alternate_screen=True)
|
||||
if arg == '@ansi_alternate':
|
||||
return w.as_text(as_ansi=True, alternate_screen=True)
|
||||
|
||||
|
||||
class DumpCommands: # {{{
|
||||
|
||||
def __init__(self, args):
|
||||
@@ -76,6 +95,7 @@ class DumpCommands: # {{{
|
||||
class Boss:
|
||||
|
||||
def __init__(self, os_window_id, opts, args, cached_values, new_os_window_trigger):
|
||||
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.pending_sequences = None
|
||||
@@ -101,6 +121,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:
|
||||
@@ -116,10 +141,13 @@ 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,
|
||||
'tabs': list(tm.list_tabs()),
|
||||
'is_focused': tm is active_tab_manager,
|
||||
'tabs': list(tm.list_tabs(active_tab, active_window)),
|
||||
}
|
||||
|
||||
@property
|
||||
@@ -150,11 +178,18 @@ class Boss:
|
||||
return
|
||||
if w is not None:
|
||||
yield w
|
||||
return
|
||||
if field == 'env':
|
||||
kp, vp = exp.partition('=')[::2]
|
||||
if vp:
|
||||
pat = tuple(map(re.compile, (kp, vp)))
|
||||
else:
|
||||
pat = re.compile(kp), None
|
||||
else:
|
||||
pat = re.compile(exp)
|
||||
for window in self.all_windows:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
for window in self.all_windows:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
|
||||
def tab_for_window(self, window):
|
||||
for tab in self.all_tabs:
|
||||
@@ -181,17 +216,20 @@ class Boss:
|
||||
yield tab
|
||||
|
||||
def set_active_window(self, window):
|
||||
for tm in self.os_window_map.values():
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
for tab in tm:
|
||||
for w in tab:
|
||||
if w.id == window.id:
|
||||
if tab is not self.active_tab:
|
||||
tm.set_active_tab(tab)
|
||||
tab.set_active_window(w)
|
||||
return
|
||||
return os_window_id
|
||||
|
||||
def _new_os_window(self, args, cwd_from=None):
|
||||
sw = self.args_to_special_window(args, cwd_from) if args else None
|
||||
if isinstance(args, SpecialWindowInstance):
|
||||
sw = args
|
||||
else:
|
||||
sw = self.args_to_special_window(args, cwd_from) if args else None
|
||||
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
|
||||
return self.add_os_window(startup_session)
|
||||
|
||||
@@ -313,6 +351,23 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.resize()
|
||||
|
||||
def clear_terminal(self, action, only_active):
|
||||
if only_active:
|
||||
windows = []
|
||||
w = self.active_window
|
||||
if w is not None:
|
||||
windows.append(w)
|
||||
else:
|
||||
windows = self.all_windows
|
||||
reset = action == 'reset'
|
||||
how = 3 if action == 'scrollback' else 2
|
||||
for w in windows:
|
||||
w.screen.cursor.x = w.screen.cursor.y = 0
|
||||
if reset:
|
||||
w.screen.reset()
|
||||
else:
|
||||
w.screen.erase_in_display(how, False)
|
||||
|
||||
def increase_font_size(self): # legacy
|
||||
cfs = global_font_size()
|
||||
self.set_font_size(min(self.opts.font_size * 5, cfs + 2.0))
|
||||
@@ -476,6 +531,8 @@ class Boss:
|
||||
if w is not None:
|
||||
tm = self.os_window_map.get(w.os_window_id)
|
||||
if tm is not None:
|
||||
tm.update_tab_bar_data()
|
||||
tm.mark_tab_bar_dirty()
|
||||
t = tm.tab_for_id(w.tab_id)
|
||||
if t is not None:
|
||||
t.relayout_borders()
|
||||
@@ -573,9 +630,14 @@ class Boss:
|
||||
output += str(s.linebuf.line(i))
|
||||
return output
|
||||
|
||||
def _run_kitten(self, kitten, args=(), input_data=None):
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
def _run_kitten(self, kitten, args=(), input_data=None, window=None):
|
||||
if window is None:
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
else:
|
||||
w = window
|
||||
tab = w.tabref()
|
||||
|
||||
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
|
||||
@@ -604,11 +666,14 @@ class Boss:
|
||||
stdin=data,
|
||||
env={
|
||||
'KITTY_COMMON_OPTS': json.dumps(copts),
|
||||
'KITTY_CHILD_PID': w.child.pid,
|
||||
'PYTHONWARNINGS': 'ignore',
|
||||
'OVERLAID_WINDOW_LINES': str(w.screen.lines),
|
||||
'OVERLAID_WINDOW_COLS': str(w.screen.columns),
|
||||
},
|
||||
overlay_for=w.id))
|
||||
cwd=w.cwd_of_child,
|
||||
overlay_for=w.id
|
||||
))
|
||||
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
|
||||
return overlay_window
|
||||
|
||||
@@ -647,23 +712,19 @@ class Boss:
|
||||
break
|
||||
|
||||
def kitty_shell(self, window_type):
|
||||
cmd = [kitty_exe(), '@']
|
||||
cmd = ['@', kitty_exe(), '@']
|
||||
if window_type == 'tab':
|
||||
window = self._new_tab(cmd).active_window
|
||||
self._new_tab(cmd).active_window
|
||||
elif window_type == 'os_window':
|
||||
os_window_id = self._new_os_window(cmd)
|
||||
window = self.os_window_map[os_window_id].active_window
|
||||
self.os_window_map[os_window_id].active_window
|
||||
elif window_type == 'overlay':
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
window = tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
|
||||
else:
|
||||
window = None
|
||||
tab.new_special_window(SpecialWindow(cmd, overlay_for=w.id))
|
||||
else:
|
||||
window = self._new_window(cmd)
|
||||
if window is not None:
|
||||
window.allow_remote_control = True
|
||||
self._new_window(cmd)
|
||||
|
||||
def switch_focus_to(self, window_idx):
|
||||
tab = self.active_tab
|
||||
@@ -730,25 +791,54 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.next_tab(-1)
|
||||
|
||||
prev_tab = previous_tab
|
||||
|
||||
def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False):
|
||||
w = window or self.active_window
|
||||
if stdin:
|
||||
stdin = data_for_at(w, stdin)
|
||||
if stdin is not None:
|
||||
stdin = stdin.encode('utf-8')
|
||||
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)
|
||||
|
||||
def pipe(self, source, dest, exe, *args):
|
||||
cmd = [exe] + list(args)
|
||||
window = self.active_window
|
||||
cwd_from = window.child.pid 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
|
||||
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('@'):
|
||||
stdin = data_for_at(args[0]) or None
|
||||
if args[0].startswith('@') and args[0] != '@':
|
||||
stdin = data_for_at(w, args[0]) or None
|
||||
if stdin is not None:
|
||||
stdin = stdin.encode('utf-8')
|
||||
del args[0]
|
||||
@@ -756,13 +846,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):
|
||||
@@ -771,15 +861,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)
|
||||
self._create_tab(args, cwd_from=cwd_from)
|
||||
|
||||
def _new_window(self, args, cwd_from=None):
|
||||
tab = self.active_tab
|
||||
|
||||
@@ -22,8 +22,10 @@ uniform sampler2DArray sprites;
|
||||
in float effective_text_alpha;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
in vec3 cursor_pos;
|
||||
in vec3 strike_pos;
|
||||
in vec3 foreground;
|
||||
in vec4 cursor_color_vec;
|
||||
in vec3 decoration_fg;
|
||||
in float colored_sprite;
|
||||
#endif
|
||||
@@ -58,10 +60,12 @@ vec4 calculate_foreground() {
|
||||
float text_alpha = text_fg.a;
|
||||
float underline_alpha = texture(sprites, underline_pos).a;
|
||||
float strike_alpha = texture(sprites, strike_pos).a;
|
||||
float cursor_alpha = texture(sprites, cursor_pos).a;
|
||||
// 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
|
||||
return 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
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#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 {
|
||||
@@ -12,7 +13,8 @@ layout(std140) uniform CellRenderData {
|
||||
|
||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style, inverted;
|
||||
|
||||
uint xnum, ynum, cursor_x, cursor_y, cursor_w;
|
||||
uint xnum, ynum, cursor_fg_sprite_idx;
|
||||
float cursor_x, cursor_y, cursor_w;
|
||||
|
||||
uint color_table[256];
|
||||
};
|
||||
@@ -53,6 +55,8 @@ uniform float inactive_text_alpha;
|
||||
uniform float dim_opacity;
|
||||
out vec3 sprite_pos;
|
||||
out vec3 underline_pos;
|
||||
out vec3 cursor_pos;
|
||||
out vec4 cursor_color_vec;
|
||||
out vec3 strike_pos;
|
||||
out vec3 foreground;
|
||||
out vec3 decoration_fg;
|
||||
@@ -110,9 +114,18 @@ vec3 choose_color(float q, vec3 a, vec3 b) {
|
||||
return mix(b, a, q);
|
||||
}
|
||||
|
||||
float is_cursor(uint x, uint y) {
|
||||
if (y == cursor_y && (x == cursor_x || x == cursor_w)) return 1.0;
|
||||
return 0.0;
|
||||
float are_integers_equal(float a, float b) { // return 1 iff equal otherwise 0
|
||||
float delta = abs(a - b); // delta can be 0, 1 or larger
|
||||
return step(delta, 0.5); // 0 if 0.5 < delta else 1
|
||||
}
|
||||
|
||||
float is_cursor(uint xi, uint y) {
|
||||
float x = float(xi);
|
||||
float y_equal = are_integers_equal(float(y), cursor_y);
|
||||
float x1_equal = are_integers_equal(x, cursor_x);
|
||||
float x2_equal = are_integers_equal(x, cursor_w);
|
||||
float x_equal = step(0.5, x1_equal + x2_equal);
|
||||
return step(2.0, x_equal + y_equal);
|
||||
}
|
||||
// }}}
|
||||
|
||||
@@ -141,7 +154,9 @@ void main() {
|
||||
uint is_inverted = ((text_attrs >> REVERSE_SHIFT) & ONE) + inverted;
|
||||
int fg_index = fg_index_map[is_inverted];
|
||||
int bg_index = 1 - fg_index;
|
||||
float cursor = is_cursor(c, r);
|
||||
float cell_has_cursor = is_cursor(c, r);
|
||||
float is_block_cursor = step(float(cursor_fg_sprite_idx), 0.5);
|
||||
float cell_has_block_cursor = cell_has_cursor * is_block_cursor;
|
||||
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
|
||||
// }}}
|
||||
|
||||
@@ -166,8 +181,10 @@ void main() {
|
||||
strike_pos = to_sprite_pos(pos, ((text_attrs >> STRIKE_SHIFT) & ONE) * FOUR, ZERO, ZERO);
|
||||
|
||||
// Cursor
|
||||
foreground = choose_color(cursor, bg, foreground);
|
||||
decoration_fg = choose_color(cursor, bg, decoration_fg);
|
||||
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);
|
||||
cursor_pos = to_sprite_pos(pos, cursor_fg_sprite_idx * uint(cell_has_cursor), ZERO, ZERO);
|
||||
#endif
|
||||
// }}}
|
||||
|
||||
@@ -182,17 +199,17 @@ void main() {
|
||||
// If the background color is default, set its opacity to background_opacity, otherwise it should be opaque
|
||||
bg_alpha = step(0.5, float(colors[bg_index] & BYTE_MASK));
|
||||
// Cursor must not be affected by background_opacity
|
||||
bg_alpha = mix(bg_alpha, 1.0, cursor);
|
||||
bg_alpha = mix(bg_alpha, 1.0, cell_has_block_cursor);
|
||||
bg_alpha = bg_alpha + (1.0f - bg_alpha) * background_opacity;
|
||||
#endif
|
||||
|
||||
#if defined(SPECIAL) || defined(SIMPLE)
|
||||
// Selection and cursor
|
||||
bg = choose_color(float(is_selected & ONE), color_to_vec(highlight_bg), bg);
|
||||
background = choose_color(cursor, color_to_vec(cursor_color), bg);
|
||||
background = choose_color(cell_has_block_cursor, color_to_vec(cursor_color), bg);
|
||||
#ifdef SPECIAL
|
||||
// bg_alpha should be 1 if cursor/selection otherwise 0
|
||||
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cursor));
|
||||
bg_alpha = mix(0.0, 1.0, step(0.5, float(is_selected & ONE) + cell_has_block_cursor));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ extern PyTypeObject Screen_Type;
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
static void (*parse_func)(Screen*, PyObject*);
|
||||
static void (*parse_func)(Screen*, PyObject*, double);
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
@@ -246,9 +246,18 @@ add_child(ChildMonitor *self, PyObject *args) {
|
||||
}
|
||||
|
||||
bool
|
||||
schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
||||
schedule_write_to_child(unsigned long id, unsigned int num, ...) {
|
||||
ChildMonitor *self = the_monitor;
|
||||
bool found = false;
|
||||
const char *data;
|
||||
size_t sz = 0;
|
||||
va_list ap;
|
||||
va_start(ap, num);
|
||||
for (unsigned int i = 0; i < num; i++) {
|
||||
data = va_arg(ap, const char*);
|
||||
sz += va_arg(ap, size_t);
|
||||
}
|
||||
va_end(ap);
|
||||
children_mutex(lock);
|
||||
for (size_t i = 0; i < self->count; i++) {
|
||||
if (children[i].id == id) {
|
||||
@@ -266,8 +275,14 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||
if (screen->write_buf == NULL) { fatal("Out of memory."); }
|
||||
}
|
||||
memcpy(screen->write_buf + screen->write_buf_used, data, sz);
|
||||
screen->write_buf_used += sz;
|
||||
va_start(ap, num);
|
||||
for (unsigned int i = 0; i < num; i++) {
|
||||
data = va_arg(ap, const char*);
|
||||
size_t dsz = va_arg(ap, size_t);
|
||||
memcpy(screen->write_buf + screen->write_buf_used, data, dsz);
|
||||
screen->write_buf_used += dsz;
|
||||
}
|
||||
va_end(ap);
|
||||
if (screen->write_buf_sz > BUFSIZ && screen->write_buf_used < BUFSIZ) {
|
||||
screen->write_buf_sz = BUFSIZ;
|
||||
screen->write_buf = PyMem_RawRealloc(screen->write_buf, screen->write_buf_sz);
|
||||
@@ -288,7 +303,7 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
|
||||
unsigned long id, sz;
|
||||
const char *data;
|
||||
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
|
||||
if (schedule_write_to_child(id, data, sz)) { Py_RETURN_TRUE; }
|
||||
if (schedule_write_to_child(id, 1, data, (size_t)sz)) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
@@ -314,13 +329,17 @@ shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
static inline void
|
||||
do_parse(ChildMonitor *self, Screen *screen, double now) {
|
||||
screen_mutex(lock, read);
|
||||
if (screen->read_buf_sz) {
|
||||
if (screen->read_buf_sz || screen->pending_mode.used) {
|
||||
double time_since_new_input = now - screen->new_input_at;
|
||||
if (time_since_new_input >= OPT(input_delay)) {
|
||||
parse_func(screen, self->dump_callback);
|
||||
if (screen->read_buf_sz >= READ_BUF_SZ) wakeup_io_loop(false); // Ensure the read fd has POLLIN set
|
||||
screen->read_buf_sz = 0;
|
||||
bool read_buf_full = screen->read_buf_sz >= READ_BUF_SZ;
|
||||
parse_func(screen, self->dump_callback, now);
|
||||
if (read_buf_full) wakeup_io_loop(false); // Ensure the read fd has POLLIN set
|
||||
screen->new_input_at = 0;
|
||||
if (screen->pending_mode.activated_at) {
|
||||
double time_since_pending = MAX(0, now - screen->pending_mode.activated_at);
|
||||
set_maximum_wait(screen->pending_mode.wait_time - time_since_pending);
|
||||
}
|
||||
} else set_maximum_wait(OPT(input_delay) - time_since_new_input);
|
||||
}
|
||||
screen_mutex(unlock, read);
|
||||
@@ -491,14 +510,6 @@ pyset_iutf8(ChildMonitor *self, PyObject *args) {
|
||||
|
||||
static double last_render_at = -DBL_MAX;
|
||||
|
||||
static inline double
|
||||
cursor_width(double w, bool vert, OSWindow *os_window) {
|
||||
double dpi = vert ? os_window->fonts_data->logical_dpi_x : os_window->fonts_data->logical_dpi_y;
|
||||
double ans = w * dpi / 72.0; // as pixels
|
||||
double factor = 2.0 / (vert ? os_window->viewport_width : os_window->viewport_height);
|
||||
return ans * factor;
|
||||
}
|
||||
|
||||
extern void cocoa_update_title(PyObject*);
|
||||
|
||||
static inline void
|
||||
@@ -526,23 +537,6 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
|
||||
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
|
||||
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
|
||||
ans->is_focused = os_window->is_focused;
|
||||
if (ans->shape == CURSOR_BLOCK && ans->is_focused) return;
|
||||
double left = rd->xstart + cursor->x * rd->dx;
|
||||
double top = rd->ystart - cursor->y * rd->dy;
|
||||
unsigned long mult = MAX(1, screen_current_char_width(rd->screen));
|
||||
double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
|
||||
double bottom = top - rd->dy;
|
||||
switch (ans->shape) {
|
||||
case CURSOR_UNDERLINE:
|
||||
top = bottom + cursor_width(2.0, false, os_window);
|
||||
break;
|
||||
case CURSOR_BLOCK:
|
||||
top -= 2.0 / os_window->viewport_height; // 1px adjustment for width of line
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -614,9 +608,6 @@ render_os_window(OSWindow *os_window, double now, unsigned int active_window_id,
|
||||
if (w->visible && WD.screen) {
|
||||
bool is_active_window = i == tab->active_window;
|
||||
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window, is_active_window, true);
|
||||
if (is_active_window && WD.screen->cursor_render_info.is_visible && (!WD.screen->cursor_render_info.is_focused || WD.screen->cursor_render_info.shape != CURSOR_BLOCK)) {
|
||||
draw_cursor(&WD.screen->cursor_render_info, os_window->is_focused);
|
||||
}
|
||||
if (WD.screen->start_visual_bell_at != 0) {
|
||||
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
|
||||
set_maximum_wait(bell_left);
|
||||
@@ -976,7 +967,12 @@ write_to_child(int fd, Screen *screen) {
|
||||
written = screen->write_buf_used;
|
||||
}
|
||||
}
|
||||
screen->write_buf_used -= written;
|
||||
if (written) {
|
||||
screen->write_buf_used -= written;
|
||||
if (screen->write_buf_used) {
|
||||
memmove(screen->write_buf, screen->write_buf + written, screen->write_buf_used);
|
||||
}
|
||||
}
|
||||
screen_mutex(unlock, write);
|
||||
}
|
||||
|
||||
@@ -1060,9 +1056,6 @@ io_loop(void *data) {
|
||||
|
||||
// {{{ Talk thread functions
|
||||
|
||||
#define MAX_PEERS 256
|
||||
#define MAX_LISTENERS 2
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t capacity, used;
|
||||
@@ -1081,15 +1074,18 @@ static PeerWriteData empty_pwd = {.fd = -1, 0};
|
||||
|
||||
typedef struct {
|
||||
size_t num_listen_fds, num_talk_fds, num_reads, num_writes, num_queued_writes;
|
||||
struct pollfd fds[MAX_PEERS + MAX_LISTENERS + 1];
|
||||
PeerReadData reads[MAX_LISTENERS];
|
||||
PeerWriteData writes[MAX_LISTENERS];
|
||||
PeerWriteData queued_writes[MAX_LISTENERS];
|
||||
size_t fds_capacity, reads_capacity, writes_capacity, queued_writes_capacity;
|
||||
struct pollfd *fds;
|
||||
PeerReadData *reads;
|
||||
PeerWriteData *writes;
|
||||
PeerWriteData *queued_writes;
|
||||
int wakeup_fds[2];
|
||||
pthread_mutex_t peer_lock;
|
||||
} TalkData;
|
||||
|
||||
static TalkData talk_data = {0};
|
||||
typedef struct pollfd PollFD;
|
||||
#define PEER_LIMIT 256
|
||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); close(s); }
|
||||
|
||||
static inline bool
|
||||
@@ -1101,11 +1097,16 @@ accept_peer(int listen_fd, bool shutting_down) {
|
||||
return false;
|
||||
}
|
||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_reads < arraysz(talk_data.reads)) {
|
||||
if (fd_idx < PEER_LIMIT && talk_data.reads_capacity < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
|
||||
ensure_space_for(&talk_data, reads, PeerReadData, talk_data.num_reads + 1, reads_capacity, 8, false);
|
||||
talk_data.reads[talk_data.num_reads] = empty_prd; talk_data.reads[talk_data.num_reads++].fd = peer;
|
||||
talk_data.num_talk_fds++;
|
||||
} else nuke_socket(peer);
|
||||
} else {
|
||||
log_error("Too many peers want to talk, ignoring one.");
|
||||
nuke_socket(peer);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1234,8 +1235,10 @@ move_queued_writes() {
|
||||
while (talk_data.num_queued_writes) {
|
||||
PeerWriteData *src = talk_data.queued_writes + --talk_data.num_queued_writes;
|
||||
size_t fd_idx = talk_data.num_listen_fds + talk_data.num_talk_fds;
|
||||
if (fd_idx < arraysz(talk_data.fds) && talk_data.num_writes < arraysz(talk_data.writes)) {
|
||||
if (fd_idx < PEER_LIMIT && talk_data.num_writes < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, fds, PollFD, fd_idx + 1, fds_capacity, 8, false);
|
||||
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
|
||||
ensure_space_for(&talk_data, writes, PeerWriteData, talk_data.num_writes + 1, writes_capacity, 8, false);
|
||||
talk_data.writes[talk_data.num_writes++] = *src;
|
||||
talk_data.num_talk_fds++;
|
||||
} else {
|
||||
@@ -1254,6 +1257,7 @@ talk_loop(void *data) {
|
||||
set_thread_name("KittyPeerMon");
|
||||
if ((pthread_mutex_init(&talk_data.peer_lock, NULL)) != 0) { perror("Failed to create peer mutex"); return 0; }
|
||||
if (!self_pipe(talk_data.wakeup_fds)) { perror("Failed to create wakeup fds for talk thread"); return 0; }
|
||||
ensure_space_for(&talk_data, fds, PollFD, 8, fds_capacity, 8, false);
|
||||
#define add_listener(which) \
|
||||
if (self->which > -1) { \
|
||||
talk_data.fds[talk_data.num_listen_fds].fd = self->which; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN; \
|
||||
@@ -1284,6 +1288,7 @@ talk_loop(void *data) {
|
||||
}
|
||||
end:
|
||||
close(talk_data.wakeup_fds[0]); close(talk_data.wakeup_fds[1]);
|
||||
free(talk_data.fds); free(talk_data.reads); free(talk_data.writes); free(talk_data.queued_writes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1291,7 +1296,8 @@ static inline bool
|
||||
add_peer_writer(int fd, const char* msg, size_t msg_sz) {
|
||||
bool ok = false;
|
||||
peer_mutex(lock);
|
||||
if (talk_data.num_queued_writes < arraysz(talk_data.queued_writes)) {
|
||||
if (talk_data.num_queued_writes < PEER_LIMIT) {
|
||||
ensure_space_for(&talk_data, queued_writes, PeerWriteData, talk_data.num_queued_writes + 1, queued_writes_capacity, 8, false);
|
||||
talk_data.queued_writes[talk_data.num_queued_writes] = empty_pwd;
|
||||
talk_data.queued_writes[talk_data.num_queued_writes].data = malloc(msg_sz);
|
||||
if (talk_data.queued_writes[talk_data.num_queued_writes].data) {
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
static inline char**
|
||||
serialize_string_tuple(PyObject *src) {
|
||||
Py_ssize_t sz = PyTuple_GET_SIZE(src);
|
||||
|
||||
char **ans = calloc(sz + 1, sizeof(char*));
|
||||
if (!ans) fatal("Out of memory");
|
||||
for (Py_ssize_t i = 0; i < sz; i++) {
|
||||
@@ -27,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
|
||||
@@ -44,18 +53,29 @@ write_to_stderr(const char *text) {
|
||||
}
|
||||
}
|
||||
|
||||
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
|
||||
|
||||
static inline void
|
||||
wait_for_terminal_ready(int fd) {
|
||||
char data;
|
||||
while(1) {
|
||||
int ret = read(fd, &data, 1);
|
||||
if (ret == -1 && (errno == EINTR || errno == EAGAIN)) continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
PyObject *argv_p, *env_p;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd;
|
||||
char *cwd, *exe;
|
||||
if (!PyArg_ParseTuple(args, "ssO!O!iiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "ssO!O!iiiiii", &exe, &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd, &ready_read_fd, &ready_write_fd)) return NULL;
|
||||
char name[2048] = {0};
|
||||
if (ttyname_r(slave, name, sizeof(name) - 1) != 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
char **argv = serialize_string_tuple(argv_p);
|
||||
char **env = serialize_string_tuple(env_p);
|
||||
|
||||
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
|
||||
pid_t pid = fork();
|
||||
switch(pid) {
|
||||
case 0:
|
||||
@@ -63,6 +83,17 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
// Use only signal-safe functions (man 7 signal-safety)
|
||||
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
|
||||
if (setsid() == -1) exit_on_err("setsid() in child process failed");
|
||||
|
||||
// Establish the controlling terminal (see man 7 credentials)
|
||||
int tfd = open(name, O_RDWR);
|
||||
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
|
||||
#ifdef TIOCSTTY
|
||||
// On BSD open() does not establish the controlling terminal
|
||||
if (ioctl(tfd, TIOCSCTTY, 0) == -1) exit_on_err("Failed to set controlling terminal with TIOCSCTTY");
|
||||
#endif
|
||||
close(tfd);
|
||||
|
||||
// Redirect stdin/stdout/stderr to the pty
|
||||
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||
if (stdin_read_fd > -1) {
|
||||
@@ -74,12 +105,14 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
}
|
||||
close(slave);
|
||||
close(master);
|
||||
for (int c = 3; c < 201; c++) close(c);
|
||||
|
||||
// Establish the controlling terminal (see man 7 credentials)
|
||||
int tfd = open(name, O_RDWR);
|
||||
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
|
||||
close(tfd);
|
||||
// Wait for READY_SIGNAL which indicates kitty has setup the screen object
|
||||
close(ready_write_fd);
|
||||
wait_for_terminal_ready(ready_read_fd);
|
||||
close(ready_read_fd);
|
||||
|
||||
// Close any extra fds inherited from parent
|
||||
for (int c = 3; c < 201; c++) close(c);
|
||||
|
||||
environ = env;
|
||||
execvp(exe, argv);
|
||||
@@ -100,8 +133,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);
|
||||
}
|
||||
|
||||
@@ -9,33 +9,81 @@ 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
|
||||
|
||||
def cwd_of_process(pid):
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import cwd_of_process
|
||||
ans = cwd_of_process(pid)
|
||||
else:
|
||||
def cwd_of_process(pid):
|
||||
return os.path.realpath(_cwd(pid))
|
||||
|
||||
else:
|
||||
|
||||
def cmdline_of_process(pid):
|
||||
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
|
||||
|
||||
def cwd_of_process(pid):
|
||||
ans = '/proc/{}/cwd'.format(pid)
|
||||
return os.path.realpath(ans)
|
||||
return os.path.realpath(ans)
|
||||
|
||||
def _environ_of_process(pid):
|
||||
return open('/proc/{}/environ'.format(pid), 'rb').read().decode('utf-8')
|
||||
|
||||
|
||||
def cmdline_of_process(pid):
|
||||
if is_macos:
|
||||
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
|
||||
raise NotImplementedError()
|
||||
return list(filter(None, open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')))
|
||||
def parse_environ_block(data):
|
||||
"""Parse a C environ block of environment variables into a dictionary."""
|
||||
# The block is usually raw data from the target process. It might contain
|
||||
# trailing garbage and lines that do not look like assignments.
|
||||
ret = {}
|
||||
pos = 0
|
||||
|
||||
while True:
|
||||
next_pos = data.find("\0", pos)
|
||||
# nul byte at the beginning or double nul byte means finish
|
||||
if next_pos <= pos:
|
||||
break
|
||||
# there might not be an equals sign
|
||||
equal_pos = data.find("=", pos, next_pos)
|
||||
if equal_pos > pos:
|
||||
key = data[pos:equal_pos]
|
||||
value = data[equal_pos + 1:next_pos]
|
||||
ret[key] = value
|
||||
pos = next_pos + 1
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def environ_of_process(pid):
|
||||
return parse_environ_block(_environ_of_process(pid))
|
||||
|
||||
|
||||
def remove_cloexec(fd):
|
||||
fcntl.fcntl(fd, fcntl.F_SETFD, fcntl.fcntl(fd, fcntl.F_GETFD) & ~fcntl.FD_CLOEXEC)
|
||||
|
||||
|
||||
def default_env():
|
||||
try:
|
||||
return default_env.env
|
||||
except AttributeError:
|
||||
return os.environ
|
||||
|
||||
|
||||
def set_default_env(val=None):
|
||||
env = os.environ.copy()
|
||||
if val:
|
||||
env.update(val)
|
||||
default_env.env = env
|
||||
|
||||
|
||||
class Child:
|
||||
|
||||
child_fd = pid = None
|
||||
forked = False
|
||||
|
||||
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
|
||||
self.allow_remote_control = False
|
||||
if argv and argv[0] == '@':
|
||||
self.allow_remote_control = True
|
||||
if len(argv) > 1:
|
||||
argv = argv[1:]
|
||||
self.argv = argv
|
||||
if cwd_from is not None:
|
||||
try:
|
||||
@@ -58,12 +106,14 @@ class Child:
|
||||
remove_cloexec(slave)
|
||||
fast_data_types.set_iutf8(master, True)
|
||||
stdin, self.stdin = self.stdin, None
|
||||
ready_read_fd, ready_write_fd = os.pipe()
|
||||
remove_cloexec(ready_read_fd)
|
||||
if stdin is not None:
|
||||
stdin_read_fd, stdin_write_fd = os.pipe()
|
||||
remove_cloexec(stdin_read_fd)
|
||||
else:
|
||||
stdin_read_fd = stdin_write_fd = -1
|
||||
env = os.environ.copy()
|
||||
env = default_env().copy()
|
||||
env.update(self.env)
|
||||
env['TERM'] = self.opts.term
|
||||
env['COLORTERM'] = 'truecolor'
|
||||
@@ -76,16 +126,22 @@ class Child:
|
||||
# Some macOS machines need the shell to have argv[0] prefixed by
|
||||
# hyphen, see https://github.com/kovidgoyal/kitty/issues/247
|
||||
argv[0] = ('-' + exe.split('/')[-1])
|
||||
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd)
|
||||
pid = fast_data_types.spawn(exe, self.cwd, tuple(argv), env, master, slave, stdin_read_fd, stdin_write_fd, ready_read_fd, ready_write_fd)
|
||||
os.close(slave)
|
||||
self.pid = pid
|
||||
self.child_fd = master
|
||||
if stdin is not None:
|
||||
os.close(stdin_read_fd)
|
||||
fast_data_types.thread_write(stdin_write_fd, stdin)
|
||||
os.close(ready_read_fd)
|
||||
self.terminal_ready_fd = ready_write_fd
|
||||
fcntl.fcntl(self.child_fd, fcntl.F_SETFL, fcntl.fcntl(self.child_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||
return pid
|
||||
|
||||
def mark_terminal_ready(self):
|
||||
os.close(self.terminal_ready_fd)
|
||||
self.terminal_ready_fd = -1
|
||||
|
||||
@property
|
||||
def cmdline(self):
|
||||
try:
|
||||
@@ -93,6 +149,13 @@ class Child:
|
||||
except Exception:
|
||||
return list(self.argv)
|
||||
|
||||
@property
|
||||
def environ(self):
|
||||
try:
|
||||
return environ_of_process(self.pid)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
@property
|
||||
def current_cwd(self):
|
||||
try:
|
||||
|
||||
297
kitty/cli.py
297
kitty/cli.py
@@ -8,9 +8,7 @@ import sys
|
||||
from collections import deque
|
||||
|
||||
from .conf.utils import resolve_config
|
||||
from .config import defaults, load_config
|
||||
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
||||
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
|
||||
|
||||
CONFIG_HELP = '''\
|
||||
Specify a path to the configuration file(s) to use. All configuration files are
|
||||
@@ -30,140 +28,10 @@ directory is always used and the above searching does not happen.
|
||||
If :file:`/etc/xdg/{appname}/{conf_name}.conf` exists it is merged before (i.e. with lower
|
||||
priority) than any user config files. It can be used to specify system-wide
|
||||
defaults for all users.
|
||||
'''.replace('{macos_confpath}', ':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else '')
|
||||
|
||||
OPTIONS = '''
|
||||
--class
|
||||
dest=cls
|
||||
default={appname}
|
||||
condition=not is_macos
|
||||
Set the class part of the :italic:`WM_CLASS` window property
|
||||
|
||||
|
||||
--name
|
||||
condition=not is_macos
|
||||
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
||||
|
||||
|
||||
--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.
|
||||
|
||||
|
||||
--config -c
|
||||
type=list
|
||||
{config_help}
|
||||
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
Override individual configuration options, can be specified multiple times.
|
||||
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
|
||||
|
||||
|
||||
--directory -d
|
||||
default=.
|
||||
Change to the specified directory when launching
|
||||
|
||||
|
||||
--detach
|
||||
type=bool-set
|
||||
condition=not is_macos
|
||||
Detach from the controlling terminal, if any
|
||||
|
||||
|
||||
--session
|
||||
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
|
||||
See the README file for details and an example.
|
||||
|
||||
|
||||
--hold
|
||||
type=bool-set
|
||||
Remain open after child process exits. Note that this only affects the first
|
||||
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
|
||||
|
||||
|
||||
--single-instance -1
|
||||
type=bool-set
|
||||
If specified only a single instance of :italic:`{appname}` will run. New invocations will
|
||||
instead create a new top-level window in the existing :italic:`{appname}` instance. This
|
||||
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
|
||||
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
|
||||
:option:`kitty --instance-group` option
|
||||
|
||||
|
||||
--instance-group
|
||||
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
|
||||
with the same :option:`kitty --instance-group` will result in new windows being created
|
||||
in the first :italic:`{appname}` instance within that group
|
||||
|
||||
|
||||
--wait-for-single-instance-window-close
|
||||
type=bool-set
|
||||
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
||||
instance and quit immediately. With this option, it will not quit till the newly opened
|
||||
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
||||
regardless of this option.
|
||||
|
||||
|
||||
--listen-on
|
||||
Tell kitty to listen on the specified address for control
|
||||
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
||||
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can also use abstract
|
||||
UNIX sockets, not associated with a file, like this: :option:`{appname} --listen-on`=unix:@mykitty.
|
||||
To control kitty, you can send it commands with :italic:`kitty @` using the :option:`kitty @ --to` option
|
||||
to specify this address. Note that this option will be ignored, unless you set
|
||||
:opt:`allow_remote_control` to yes in :file:`kitty.conf`.
|
||||
|
||||
|
||||
# Debugging options
|
||||
|
||||
--version -v
|
||||
type=bool-set
|
||||
The current {appname} version
|
||||
|
||||
|
||||
--dump-commands
|
||||
type=bool-set
|
||||
Output commands received from child process to stdout
|
||||
|
||||
|
||||
--replay-commands
|
||||
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
||||
can open a new kitty window to replay the commands with::
|
||||
|
||||
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
||||
|
||||
|
||||
--dump-bytes
|
||||
Path to file in which to store the raw bytes received from the child process
|
||||
|
||||
|
||||
--debug-gl
|
||||
type=bool-set
|
||||
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
|
||||
instead of ignoring them. Useful when debugging rendering problems
|
||||
|
||||
|
||||
--debug-keyboard
|
||||
type=bool-set
|
||||
This option will cause kitty to print out key events as they are received
|
||||
|
||||
|
||||
--debug-font-fallback
|
||||
type=bool-set
|
||||
Print out information about the selection of fallback fonts for characters not present in the main font.
|
||||
|
||||
|
||||
--debug-config
|
||||
type=bool-set
|
||||
Print out information about the system and kitty configuration.
|
||||
|
||||
|
||||
--execute -e
|
||||
type=bool-set
|
||||
!
|
||||
'''
|
||||
'''.replace(
|
||||
'{macos_confpath}',
|
||||
(':file:`~/Library/Preferences/{appname}/{conf_name}.conf`,' if is_macos else ''), 1
|
||||
)
|
||||
|
||||
|
||||
def surround(x, start, end):
|
||||
@@ -232,7 +100,9 @@ def file(x):
|
||||
return italic(x)
|
||||
|
||||
|
||||
def parse_option_spec(spec=OPTIONS):
|
||||
def parse_option_spec(spec=None):
|
||||
if spec is None:
|
||||
spec = options_spec()
|
||||
NORMAL, METADATA, HELP = 'NORMAL', 'METADATA', 'HELP'
|
||||
state = NORMAL
|
||||
lines = spec.splitlines()
|
||||
@@ -454,7 +324,7 @@ def seq_as_rst(seq, usage, message, appname, heading_char='-'):
|
||||
if defval is not None:
|
||||
a(textwrap.indent('Default: :code:`{}`'.format(defval), ' ' * 4))
|
||||
if 'choices' in opt:
|
||||
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(opt['choices'])), ' ' * 4))
|
||||
a(textwrap.indent('Choices: :code:`{}`'.format(', '.join(sorted(opt['choices']))), ' ' * 4))
|
||||
a('')
|
||||
|
||||
text = '\n'.join(blocks)
|
||||
@@ -589,6 +459,150 @@ def parse_cmdline(oc, disabled, args=None):
|
||||
|
||||
def options_spec():
|
||||
if not hasattr(options_spec, 'ans'):
|
||||
OPTIONS = '''
|
||||
--class
|
||||
dest=cls
|
||||
default={appname}
|
||||
condition=not is_macos
|
||||
Set the class part of the :italic:`WM_CLASS` window property. On Wayland, it sets the app id.
|
||||
|
||||
|
||||
--name
|
||||
condition=not is_macos
|
||||
Set the name part of the :italic:`WM_CLASS` property (defaults to using the value from :option:`{appname} --class`)
|
||||
|
||||
|
||||
--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. 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
|
||||
type=list
|
||||
{config_help}
|
||||
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
Override individual configuration options, can be specified multiple times.
|
||||
Syntax: :italic:`name=value`. For example: :option:`kitty -o` font_size=20
|
||||
|
||||
|
||||
--directory -d
|
||||
default=.
|
||||
Change to the specified directory when launching
|
||||
|
||||
|
||||
--detach
|
||||
type=bool-set
|
||||
condition=not is_macos
|
||||
Detach from the controlling terminal, if any
|
||||
|
||||
|
||||
--session
|
||||
Path to a file containing the startup :italic:`session` (tabs, windows, layout, programs).
|
||||
See the README file for details and an example.
|
||||
|
||||
|
||||
--hold
|
||||
type=bool-set
|
||||
Remain open after child process exits. Note that this only affects the first
|
||||
window. You can quit by either using the close window shortcut or :kbd:`Ctrl+d`.
|
||||
|
||||
|
||||
--single-instance -1
|
||||
type=bool-set
|
||||
If specified only a single instance of :italic:`{appname}` will run. New invocations will
|
||||
instead create a new top-level window in the existing :italic:`{appname}` instance. This
|
||||
allows :italic:`{appname}` to share a single sprite cache on the GPU and also reduces
|
||||
startup time. You can also have separate groups of :italic:`{appname}` instances by using the
|
||||
:option:`kitty --instance-group` option
|
||||
|
||||
|
||||
--instance-group
|
||||
Used in combination with the :option:`kitty --single-instance` option. All :italic:`{appname}` invocations
|
||||
with the same :option:`kitty --instance-group` will result in new windows being created
|
||||
in the first :italic:`{appname}` instance within that group
|
||||
|
||||
|
||||
--wait-for-single-instance-window-close
|
||||
type=bool-set
|
||||
Normally, when using :option:`--single-instance`, :italic:`{appname}` will open a new window in an existing
|
||||
instance and quit immediately. With this option, it will not quit till the newly opened
|
||||
window is closed. Note that if no previous instance is found, then :italic:`{appname}` will wait anyway,
|
||||
regardless of this option.
|
||||
|
||||
|
||||
--listen-on
|
||||
Tell kitty to listen on the specified address for control
|
||||
messages. For example, :option:`{appname} --listen-on`=unix:/tmp/mykitty or
|
||||
:option:`{appname} --listen-on`=tcp:localhost:12345. On Linux systems, you can
|
||||
also use abstract UNIX sockets, not associated with a file, like this:
|
||||
:option:`{appname} --listen-on`=unix:@mykitty. To control kitty, you can send
|
||||
it commands with :italic:`kitty @` using the :option:`kitty @ --to` option to
|
||||
specify this address. This option will be ignored, unless you set
|
||||
:opt:`allow_remote_control` to yes in :file:`kitty.conf`. Note that if you run
|
||||
:italic:`kitty @` within a kitty window, there is no need to specify the :italic:`--to`
|
||||
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
|
||||
type=bool-set
|
||||
The current {appname} version
|
||||
|
||||
|
||||
--dump-commands
|
||||
type=bool-set
|
||||
Output commands received from child process to stdout
|
||||
|
||||
|
||||
--replay-commands
|
||||
Replay previously dumped commands. Specify the path to a dump file previously created by --dump-commands. You
|
||||
can open a new kitty window to replay the commands with::
|
||||
|
||||
kitty sh -c "kitty --replay-commands /path/to/dump/file; read"
|
||||
|
||||
|
||||
--dump-bytes
|
||||
Path to file in which to store the raw bytes received from the child process
|
||||
|
||||
|
||||
--debug-gl
|
||||
type=bool-set
|
||||
Debug OpenGL commands. This will cause all OpenGL calls to check for errors
|
||||
instead of ignoring them. Useful when debugging rendering problems
|
||||
|
||||
|
||||
--debug-keyboard
|
||||
type=bool-set
|
||||
This option will cause kitty to print out key events as they are received
|
||||
|
||||
|
||||
--debug-font-fallback
|
||||
type=bool-set
|
||||
Print out information about the selection of fallback fonts for characters not present in the main font.
|
||||
|
||||
|
||||
--debug-config
|
||||
type=bool-set
|
||||
Print out information about the system and kitty configuration.
|
||||
|
||||
|
||||
--execute -e
|
||||
type=bool-set
|
||||
!
|
||||
'''
|
||||
options_spec.ans = OPTIONS.format(
|
||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname)
|
||||
|
||||
@@ -596,6 +610,10 @@ def options_spec():
|
||||
return options_spec.ans
|
||||
|
||||
|
||||
def options_for_completion():
|
||||
return parse_option_spec(options_spec())[0]
|
||||
|
||||
|
||||
def option_spec_as_rst(ospec=options_spec, usage=None, message=None, appname=None, heading_char='-'):
|
||||
options = parse_option_spec(ospec())
|
||||
seq, disabled = options
|
||||
@@ -631,6 +649,7 @@ def print_shortcut(key_sequence, action):
|
||||
names.append(name)
|
||||
if key:
|
||||
if is_native:
|
||||
from .fast_data_types import GLFW_KEY_UNKNOWN, glfw_get_key_name
|
||||
names.append(glfw_get_key_name(GLFW_KEY_UNKNOWN, key))
|
||||
else:
|
||||
names.append(krmap[key])
|
||||
@@ -665,6 +684,7 @@ def flatten_sequence_map(m):
|
||||
|
||||
|
||||
def compare_opts(opts):
|
||||
from .config import defaults, load_config
|
||||
print('\nConfig options different from defaults:')
|
||||
default_opts = load_config()
|
||||
changed_opts = [
|
||||
@@ -686,6 +706,7 @@ def compare_opts(opts):
|
||||
|
||||
|
||||
def create_opts(args, debug_config=False):
|
||||
from .config import load_config
|
||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||
if debug_config:
|
||||
print(version(add_rev=True))
|
||||
|
||||
@@ -122,6 +122,10 @@ def draw(*a):
|
||||
write(' '.join(a))
|
||||
|
||||
|
||||
def screen_manipulate_title_stack(op, which):
|
||||
write(CSI + '%d;%dt' % (op, which))
|
||||
|
||||
|
||||
def report_device_attributes(mode, char):
|
||||
x = CSI
|
||||
if char:
|
||||
|
||||
176
kitty/cmds.py
176
kitty/cmds.py
@@ -9,6 +9,7 @@ import sys
|
||||
from .cli import parse_args
|
||||
from .config import parse_config, parse_send_text_bytes
|
||||
from .constants import appname
|
||||
from .fast_data_types import focus_os_window
|
||||
from .tabs import SpecialWindow
|
||||
from .utils import natsort_ints
|
||||
|
||||
@@ -26,6 +27,14 @@ class OpacityError(ValueError):
|
||||
hide_traceback = True
|
||||
|
||||
|
||||
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):
|
||||
@@ -39,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
|
||||
|
||||
@@ -46,18 +56,20 @@ def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='..
|
||||
MATCH_WINDOW_OPTION = '''\
|
||||
--match -m
|
||||
The window to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num.
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline, num, env.
|
||||
You can use the :italic:`ls` command to get a list of windows. Note that for
|
||||
numeric fields such as id, pid and num the expression is interpreted as a number,
|
||||
not a regular expression. The field num refers to the window position in the current tab,
|
||||
starting from zero and counting clockwise (this is the same as the order in which the
|
||||
windows are reported by the :italic:`ls` command). The window id of the current window
|
||||
is available as the KITTY_WINDOW_ID environment variable.
|
||||
is available as the KITTY_WINDOW_ID environment variable. When using the :italic:`env` field
|
||||
to match on environment variables you can specify only the environment variable name or a name
|
||||
and value, for example, :italic:`env:MY_ENV_VAR=2`
|
||||
'''
|
||||
MATCH_TAB_OPTION = '''\
|
||||
--match -m
|
||||
The tab to match. Match specifications are of the form:
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, cmdline.
|
||||
:italic:`field:regexp`. Where field can be one of: id, title, pid, cwd, env, cmdline.
|
||||
You can use the :italic:`ls` command to get a list of tabs. Note that for
|
||||
numeric fields such as id and pid the expression is interpreted as a number,
|
||||
not a regular expression. When using title or id, first a matching tab is
|
||||
@@ -72,8 +84,8 @@ for that window is used.
|
||||
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
||||
' operating system {appname} windows. Each OS window has an :italic:`id` and a list'
|
||||
' of :italic:`tabs`. Each tab has its own :italic:`id`, a :italic:`title` and a list of :italic:`windows`.'
|
||||
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)` and'
|
||||
' :italic:`command-line` of the process running in the window.\n\n'
|
||||
' Each window has an :italic:`id`, :italic:`title`, :italic:`current working directory`, :italic:`process id (PID)`, '
|
||||
' :italic:`command-line` and :italic:`environment` of the process running in the window.\n\n'
|
||||
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
|
||||
argspec=''
|
||||
)
|
||||
@@ -270,6 +282,69 @@ def set_tab_title(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
# goto_layout {{{
|
||||
@cmd(
|
||||
'Set the window layout',
|
||||
'Set the 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,
|
||||
argspec='LAYOUT_NAME'
|
||||
)
|
||||
def cmd_goto_layout(global_opts, opts, args):
|
||||
try:
|
||||
return {'layout': args[0], 'match': opts.match}
|
||||
except IndexError:
|
||||
raise SystemExit('No layout specified')
|
||||
|
||||
|
||||
def goto_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:
|
||||
try:
|
||||
tab.goto_layout(payload['layout'], raise_exception=True)
|
||||
except ValueError:
|
||||
raise UnknownLayout('The layout {} is unknown or disabled'.format(payload['layout']))
|
||||
# }}}
|
||||
|
||||
|
||||
# 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)',
|
||||
@@ -320,7 +395,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
|
||||
@@ -381,7 +456,7 @@ def close_tab(boss, window, payload):
|
||||
'Open new window',
|
||||
'Open a new window in the specified tab. If you use the :option:`kitty @ new-window --match` option'
|
||||
' the first matching tab is used. Otherwise the currently active tab is used.'
|
||||
' Prints out the id of the newly opened window. Any command line arguments'
|
||||
' Prints out the id of the newly opened window (unless :option:`--no-response` is used). Any command line arguments'
|
||||
' are assumed to be the command line used to run in the new window, if none'
|
||||
' are provided, the default shell is run. For example:\n'
|
||||
':italic:`kitty @ new-window --title Email mutt`',
|
||||
@@ -400,6 +475,12 @@ type=bool-set
|
||||
Keep the current window focused instead of switching to the newly opened window
|
||||
|
||||
|
||||
--window-type
|
||||
default=kitty
|
||||
choices=kitty,os
|
||||
What kind of window to open. A kitty window or a top-level OS window.
|
||||
|
||||
|
||||
--new-tab
|
||||
type=bool-set
|
||||
Open a new tab
|
||||
@@ -407,12 +488,23 @@ Open a new tab
|
||||
|
||||
--tab-title
|
||||
When using --new-tab set the title of the tab.
|
||||
|
||||
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response giving the id of the newly opened window. Note that
|
||||
using this option means that you will not be notified of failures and that
|
||||
the id of the new window will not be printed out.
|
||||
''',
|
||||
argspec='[CMD ...]'
|
||||
)
|
||||
def cmd_new_window(global_opts, opts, args):
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
|
||||
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
|
||||
'window_type': opts.window_type, 'no_response': opts.no_response,
|
||||
'keep_focus': opts.keep_focus, 'args': args or []}
|
||||
|
||||
|
||||
@@ -427,7 +519,16 @@ def new_window(boss, window, payload):
|
||||
wid = boss.active_window.id
|
||||
if payload['keep_focus'] and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return str(wid)
|
||||
return None if payload['no_response'] else str(wid)
|
||||
|
||||
if payload['window_type'] == 'os':
|
||||
boss._new_os_window(w)
|
||||
wid = boss.active_window.id
|
||||
if payload['keep_focus'] and old_window:
|
||||
os_window_id = boss.set_active_window(old_window)
|
||||
if os_window_id:
|
||||
focus_os_window(os_window_id)
|
||||
return None if payload['no_response'] else str(wid)
|
||||
|
||||
match = payload['match']
|
||||
if match:
|
||||
@@ -440,7 +541,7 @@ def new_window(boss, window, payload):
|
||||
w = tab.new_special_window(w)
|
||||
if payload['keep_focus'] and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return str(w.id)
|
||||
return None if payload['no_response'] else str(w.id)
|
||||
# }}}
|
||||
|
||||
|
||||
@@ -448,11 +549,19 @@ def new_window(boss, window, payload):
|
||||
@cmd(
|
||||
'Focus the specified window',
|
||||
'Focus the specified window, if no window is specified, focus the window this command is run inside.',
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n\n
|
||||
--no-response
|
||||
type=bool-set
|
||||
default=false
|
||||
Don't wait for a response from kitty. This means that even if no matching window is found,
|
||||
the command will exit with a success code.
|
||||
'''
|
||||
)
|
||||
def cmd_focus_window(global_opts, opts, args):
|
||||
return {'match': opts.match}
|
||||
if opts.no_response:
|
||||
global_opts.no_command_response = True
|
||||
return {'match': opts.match, 'no_response': opts.no_response}
|
||||
|
||||
|
||||
def focus_window(boss, window, payload):
|
||||
@@ -464,7 +573,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
|
||||
# }}}
|
||||
|
||||
@@ -473,10 +584,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}
|
||||
|
||||
|
||||
@@ -680,7 +801,34 @@ def set_background_opacity(boss, window, payload):
|
||||
# }}}
|
||||
|
||||
|
||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
||||
# kitten {{{
|
||||
@cmd(
|
||||
'Run a kitten',
|
||||
'Run a kitten over the specified window (active window by default).'
|
||||
' The :italic:`kitten_name` can be either the name of a builtin kitten'
|
||||
' or the path to a python file containing a custom kitten. If a relative path'
|
||||
' is used it is searched for in the kitty config directory.',
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='kitten_name',
|
||||
)
|
||||
def cmd_kitten(global_opts, opts, args):
|
||||
if len(args) < 1:
|
||||
raise SystemExit('Must specify kitten name')
|
||||
return {'match': opts.match, 'args': list(args)[1:], 'kitten': args[0]}
|
||||
|
||||
|
||||
def kitten(boss, window, payload):
|
||||
windows = [window or boss.active_window]
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
boss._run_kitten(payload['kitten'], args=tuple(payload['args']), window=window)
|
||||
break
|
||||
# }}}
|
||||
|
||||
|
||||
def cli_params_for(func):
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
#include <AvailabilityMacros.h>
|
||||
// Needed for _NSGetProgname
|
||||
#include <crt_externs.h>
|
||||
typedef void* rusage_info_t; // needed for libproc.h
|
||||
#include <libproc.h>
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||
@@ -202,19 +200,53 @@ cocoa_update_title(PyObject *pytitle) {
|
||||
}
|
||||
|
||||
bool
|
||||
cocoa_make_window_resizable(void *w) {
|
||||
cocoa_make_window_resizable(void *w, bool resizable) {
|
||||
NSWindow *window = (NSWindow*)w;
|
||||
|
||||
@try {
|
||||
[window setStyleMask:
|
||||
[window styleMask] | NSWindowStyleMaskResizable];
|
||||
if (resizable) {
|
||||
[window setStyleMask:
|
||||
[window styleMask] | NSWindowStyleMaskResizable];
|
||||
} else {
|
||||
[window setStyleMask:
|
||||
[window styleMask] & ~NSWindowStyleMaskResizable];
|
||||
}
|
||||
} @catch (NSException *e) {
|
||||
return PyErr_Format(PyExc_ValueError, "Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
|
||||
log_error("Failed to set style mask: %s: %s", [[e name] UTF8String], [[e reason] UTF8String]);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_focus_window(void *w) {
|
||||
NSWindow *window = (NSWindow*)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;
|
||||
@@ -229,15 +261,6 @@ cocoa_get_lang(PyObject UNUSED *self) {
|
||||
return Py_BuildValue("s", [locale UTF8String]);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
long pid = PyLong_AsLong(pid_);
|
||||
struct proc_vnodepathinfo vpi;
|
||||
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
|
||||
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_set_hide_from_tasks(void) {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
@@ -271,7 +294,6 @@ cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
|
||||
{"cocoa_set_new_window_trigger", (PyCFunction)cocoa_set_new_window_trigger, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
384
kitty/complete.py
Normal file
384
kitty/complete.py
Normal file
@@ -0,0 +1,384 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
from kittens.runner import get_kitten_cli_docs, all_kitten_names
|
||||
|
||||
from .cli import options_for_completion, parse_option_spec
|
||||
from .cmds import cmap
|
||||
from .shell import options_for_cmd
|
||||
|
||||
parsers, serializers = {}, {}
|
||||
|
||||
|
||||
def debug(*a, **kw):
|
||||
kw['file'] = sys.stderr
|
||||
print(*a, **kw)
|
||||
|
||||
|
||||
class Completions:
|
||||
|
||||
def __init__(self):
|
||||
self.match_groups = {}
|
||||
self.no_space_groups = set()
|
||||
self.files_groups = set()
|
||||
|
||||
|
||||
# Shell specific code {{{
|
||||
|
||||
|
||||
completion_scripts = {
|
||||
'zsh': '''
|
||||
_kitty() {
|
||||
local src
|
||||
# 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}
|
||||
fi
|
||||
}
|
||||
compdef _kitty kitty
|
||||
''',
|
||||
'bash': '''
|
||||
_kitty_completions() {
|
||||
local src
|
||||
local limit
|
||||
# Send all words up to the word the cursor is currently on
|
||||
let limit=1+$COMP_CWORD
|
||||
src=$(printf "%s\n" "${COMP_WORDS[@]: 0:$limit}" | kitty +complete bash)
|
||||
if [[ $? == 0 ]]; then
|
||||
eval ${src}
|
||||
fi
|
||||
}
|
||||
|
||||
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)"
|
||||
''',
|
||||
}
|
||||
|
||||
|
||||
def input_parser(func):
|
||||
name = func.__name__.split('_')[0]
|
||||
parsers[name] = func
|
||||
return func
|
||||
|
||||
|
||||
def output_serializer(func):
|
||||
name = func.__name__.split('_')[0]
|
||||
serializers[name] = func
|
||||
return func
|
||||
|
||||
|
||||
@input_parser
|
||||
def zsh_input_parser(data):
|
||||
new_word = data.endswith('\n\n')
|
||||
words = data.rstrip().splitlines()
|
||||
return words, new_word
|
||||
|
||||
|
||||
@input_parser
|
||||
def bash_input_parser(data):
|
||||
new_word = data.endswith('\n\n')
|
||||
words = data.rstrip().splitlines()
|
||||
return words, new_word
|
||||
|
||||
|
||||
@input_parser
|
||||
def fish_input_parser(data):
|
||||
return data.rstrip().splitlines(), True
|
||||
|
||||
|
||||
@output_serializer
|
||||
def zsh_output_serializer(ans):
|
||||
lines = []
|
||||
for description, matches in ans.match_groups.items():
|
||||
cmd = ['compadd', '-U', '-J', shlex.quote(description), '-X', shlex.quote(description)]
|
||||
if description in ans.no_space_groups:
|
||||
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))
|
||||
lines.append(' '.join(cmd) + ';')
|
||||
# debug('\n'.join(lines))
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
@output_serializer
|
||||
def bash_output_serializer(ans):
|
||||
lines = []
|
||||
for description, matches in ans.match_groups.items():
|
||||
needs_space = description not in ans.no_space_groups
|
||||
for word in matches:
|
||||
if needs_space:
|
||||
word += ' '
|
||||
lines.append('COMPREPLY+=({})'.format(shlex.quote(word)))
|
||||
# debug('\n'.join(lines))
|
||||
return '\n'.join(lines)
|
||||
# }}}
|
||||
|
||||
|
||||
@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'] = {
|
||||
k: None for k in
|
||||
list(entry_points) + cmds + ['+' + k for k in namespaced_entry_points]
|
||||
if not prefix or k.startswith(prefix)
|
||||
}
|
||||
|
||||
|
||||
def kitty_cli_opts(ans, prefix=None):
|
||||
matches = {}
|
||||
for opt in options_for_completion():
|
||||
if isinstance(opt, str):
|
||||
continue
|
||||
aliases = frozenset(x for x in opt['aliases'] if x.startswith(prefix)) if prefix else opt['aliases']
|
||||
for alias in aliases:
|
||||
matches[alias] = opt['help'].strip()
|
||||
ans.match_groups['Options'] = matches
|
||||
|
||||
|
||||
def complete_kitty_cli_arg(ans, opt, prefix):
|
||||
prefix = prefix or ''
|
||||
if opt and opt['dest'] == 'override':
|
||||
from kitty.config import option_names_for_completion
|
||||
k = 'Config directives'
|
||||
ans.match_groups[k] = {k+'=': None for k in option_names_for_completion() if k.startswith(prefix)}
|
||||
ans.no_space_groups.add(k)
|
||||
|
||||
|
||||
def complete_alias_map(ans, words, new_word, option_map, complete_args=None):
|
||||
expecting_arg = False
|
||||
opt = None
|
||||
last_word = words[-1] if words else ''
|
||||
for w in words:
|
||||
if expecting_arg:
|
||||
if w is last_word and not new_word:
|
||||
if opt is not None:
|
||||
complete_args(ans, opt, w)
|
||||
return
|
||||
expecting_arg = False
|
||||
continue
|
||||
opt = option_map.get(w)
|
||||
if w is last_word and not new_word:
|
||||
if w.startswith('-'):
|
||||
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(last_word)}
|
||||
else:
|
||||
if complete_args is not None:
|
||||
complete_args(ans, None, last_word)
|
||||
return
|
||||
if opt is None:
|
||||
if complete_args is not None:
|
||||
complete_args(ans, None, '' if new_word else last_word)
|
||||
return # some non-option word encountered
|
||||
expecting_arg = not opt.get('type', '').startswith('bool-')
|
||||
if expecting_arg:
|
||||
if opt is not None and complete_args is not None:
|
||||
complete_args(ans, opt, '' if new_word else last_word)
|
||||
else:
|
||||
prefix = '' if new_word else last_word
|
||||
if complete_args is not None:
|
||||
complete_args(ans, None, prefix)
|
||||
ans.match_groups['Options'] = {k: opt['help'] for k, opt in option_map.items() if k.startswith(prefix)}
|
||||
|
||||
|
||||
def complete_cli(ans, words, new_word, seq, complete_args=lambda *a: None):
|
||||
option_map = {}
|
||||
for opt in seq:
|
||||
if not isinstance(opt, str):
|
||||
for alias in opt['aliases']:
|
||||
option_map[alias] = opt
|
||||
complete_alias_map(ans, words, new_word, option_map, complete_args)
|
||||
|
||||
|
||||
def complete_remote_command(ans, cmd_name, words, new_word):
|
||||
aliases, alias_map = options_for_cmd(cmd_name)
|
||||
if not alias_map:
|
||||
return
|
||||
complete_alias_map(ans, words, new_word, alias_map)
|
||||
|
||||
|
||||
def path_completion(prefix=''):
|
||||
prefix = prefix.replace(r'\ ', ' ')
|
||||
dirs, files = [], []
|
||||
base = '.'
|
||||
if prefix.endswith('/'):
|
||||
base = prefix
|
||||
elif '/' in prefix:
|
||||
base = os.path.dirname(prefix)
|
||||
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:
|
||||
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')
|
||||
if files:
|
||||
ans.match_groups[files_group_name] = dict.fromkeys(files)
|
||||
ans.files_groups.add(files_group_name)
|
||||
|
||||
|
||||
def complete_icat_args(ans, opt, prefix):
|
||||
from mimetypes import guess_type
|
||||
|
||||
def icat_file_predicate(filename):
|
||||
mt = guess_type(filename)[0]
|
||||
if mt and mt.startswith('image/'):
|
||||
return True
|
||||
|
||||
if opt is None:
|
||||
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)
|
||||
except SystemExit:
|
||||
cd = None
|
||||
if cd is None:
|
||||
return
|
||||
options = cd['options']()
|
||||
seq = parse_option_spec(options)[0]
|
||||
option_map = {}
|
||||
for opt in seq:
|
||||
if not isinstance(opt, str):
|
||||
for alias in opt['aliases']:
|
||||
option_map[alias] = opt
|
||||
complete_alias_map(ans, words, new_word, option_map, {
|
||||
'icat': complete_icat_args,
|
||||
'diff': complete_diff_args,
|
||||
}.get(kitten))
|
||||
|
||||
|
||||
def find_completions(words, new_word, entry_points, namespaced_entry_points):
|
||||
ans = Completions()
|
||||
if not words or words[0] != 'kitty':
|
||||
return ans
|
||||
words = words[1:]
|
||||
if not words or (len(words) == 1 and not new_word):
|
||||
prefix = words[0] if words else ''
|
||||
completions_for_first_word(ans, prefix, entry_points, namespaced_entry_points)
|
||||
kitty_cli_opts(ans, prefix)
|
||||
return ans
|
||||
if words[0] == '@':
|
||||
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||
prefix = words[1] if len(words) > 1 else ''
|
||||
ans.match_groups['Remote control commands'] = {c: None for c in cmap if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[1], words[2:], new_word)
|
||||
return ans
|
||||
if words[0].startswith('@'):
|
||||
if len(words) == 1 and not new_word:
|
||||
prefix = words[0]
|
||||
ans.match_groups['Remote control commands'] = {'@' + c: None for c in cmap if c.startswith(prefix)}
|
||||
else:
|
||||
complete_remote_command(ans, words[0][1:], words[1:], new_word)
|
||||
if words[0] == '+':
|
||||
if len(words) == 1 or (len(words) == 2 and not new_word):
|
||||
prefix = words[1] if len(words) > 1 else ''
|
||||
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
else:
|
||||
if words[1] == 'kitten':
|
||||
if len(words) == 2 or (len(words) == 3 and not new_word):
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith('' if len(words) == 2 else words[2]))
|
||||
else:
|
||||
complete_kitten(ans, words[2], words[3:], new_word)
|
||||
return ans
|
||||
if words[0].startswith('+'):
|
||||
if len(words) == 1:
|
||||
if new_word:
|
||||
if words[0] == '+kitten':
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(all_kitten_names())
|
||||
else:
|
||||
prefix = words[0]
|
||||
ans.match_groups['Entry points'] = {c: None for c in namespaced_entry_points if c.startswith(prefix)}
|
||||
else:
|
||||
if len(words) == 2 and not new_word:
|
||||
ans.match_groups['Kittens'] = dict.fromkeys(k for k in all_kitten_names() if k.startswith(words[1]))
|
||||
else:
|
||||
if words[0] == '+kitten':
|
||||
complete_kitten(ans, words[1], words[2:], new_word)
|
||||
else:
|
||||
complete_cli(ans, words, new_word, options_for_completion(), complete_kitty_cli_arg)
|
||||
|
||||
return ans
|
||||
|
||||
|
||||
def setup(cstyle):
|
||||
print(completion_scripts[cstyle])
|
||||
|
||||
|
||||
def main(args, entry_points, namespaced_entry_points):
|
||||
if not args:
|
||||
raise SystemExit('Must specify completion style')
|
||||
cstyle = args[0]
|
||||
if cstyle == 'setup':
|
||||
return setup(args[1])
|
||||
data = sys.stdin.read()
|
||||
try:
|
||||
parser = parsers[cstyle]
|
||||
serializer = serializers[cstyle]
|
||||
except KeyError:
|
||||
raise SystemExit('Unknown completion style: {}'.format(cstyle))
|
||||
words, new_word = parser(data)
|
||||
ans = find_completions(words, new_word, entry_points, namespaced_entry_points)
|
||||
print(serializer(ans), end='')
|
||||
@@ -119,7 +119,16 @@ def merged_opts(all_options, opt, i):
|
||||
|
||||
|
||||
def remove_markup(text):
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', r'\2', text, flags=re.DOTALL)
|
||||
|
||||
def sub(m):
|
||||
if m.group(1) == 'ref':
|
||||
return {
|
||||
'layouts': 'https://sw.kovidgoyal.net/kitty/index.html#layouts',
|
||||
'sessions': 'https://sw.kovidgoyal.net/kitty/index.html#sessions',
|
||||
}[m.group(2)]
|
||||
return m.group(2)
|
||||
|
||||
return re.sub(r':([a-zA-Z0-9]+):`(.+?)`', sub, text, flags=re.DOTALL)
|
||||
|
||||
|
||||
def iter_blocks(lines):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ast
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
@@ -40,6 +39,7 @@ def to_cmdline(x):
|
||||
|
||||
|
||||
def python_string(text):
|
||||
import ast
|
||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''")
|
||||
|
||||
|
||||
|
||||
133
kitty/config.py
133
kitty/config.py
@@ -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
|
||||
|
||||
@@ -121,6 +129,50 @@ def parse_change_font_size(func, rest):
|
||||
return func, args
|
||||
|
||||
|
||||
@func_with_args('clear_terminal')
|
||||
def clear_terminal(func, rest):
|
||||
vals = rest.split(' ', 1)
|
||||
if len(vals) != 2:
|
||||
log_error('clear_terminal needs two arguments, using defaults')
|
||||
args = ['reset', 'active']
|
||||
else:
|
||||
args = [vals[0].lower(), vals[1].lower() == 'active']
|
||||
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('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
|
||||
|
||||
|
||||
def parse_key_action(action):
|
||||
parts = action.split(' ', 1)
|
||||
func = parts[0]
|
||||
@@ -245,28 +297,73 @@ def parse_send_text(val, key_definitions):
|
||||
return parse_key(key_str, key_definitions)
|
||||
|
||||
|
||||
special_handlers = {}
|
||||
|
||||
|
||||
def special_handler(func):
|
||||
special_handlers[func.__name__.partition('_')[2]] = func
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_map(key, val, ans):
|
||||
parse_key(val, ans['key_definitions'])
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_symbol_map(key, val, ans):
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_send_text(key, val, ans):
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['key_definitions'])
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_clear_all_shortcuts(key, val, ans):
|
||||
if to_bool(val):
|
||||
ans['key_definitions'] = [None]
|
||||
|
||||
|
||||
def expandvars(val, env):
|
||||
|
||||
def sub(m):
|
||||
key = m.group(1)
|
||||
result = env.get(key)
|
||||
if result is None:
|
||||
result = os.environ.get(key)
|
||||
if result is None:
|
||||
result = m.group()
|
||||
return result
|
||||
|
||||
return re.sub(r'$\{(\S+)\}', sub, val)
|
||||
|
||||
|
||||
@special_handler
|
||||
def handle_env(key, val, ans):
|
||||
key, val = val.partition('=')[::2]
|
||||
key, val = key.strip(), val.strip()
|
||||
ans['env'][key] = expandvars(val, ans['env'])
|
||||
|
||||
|
||||
def special_handling(key, val, ans):
|
||||
if key == 'map':
|
||||
parse_key(val, ans['key_definitions'])
|
||||
func = special_handlers.get(key)
|
||||
if func is not None:
|
||||
func(key, val, ans)
|
||||
return True
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
return True
|
||||
if key == 'send_text':
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['key_definitions'])
|
||||
return True
|
||||
if key == 'clear_all_shortcuts':
|
||||
if to_bool(val):
|
||||
ans['key_definitions'] = [None]
|
||||
return
|
||||
|
||||
|
||||
defaults = None
|
||||
|
||||
|
||||
def option_names_for_completion():
|
||||
yield from defaults
|
||||
yield from special_handlers
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': []}
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': [], 'env': {}}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -131,7 +146,12 @@ You can also create shortcuts to go to specific tabs, with 1 being the first tab
|
||||
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,6 +160,11 @@ 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'), _('''\
|
||||
@@ -266,7 +291,16 @@ def to_cursor_shape(x):
|
||||
)
|
||||
|
||||
|
||||
def cursor_text_color(x):
|
||||
if x.lower() == 'background':
|
||||
return
|
||||
return to_color(x)
|
||||
|
||||
|
||||
o('cursor', '#cccccc', _('Default cursor color'), option_type=to_color)
|
||||
o('cursor_text_color', '#111111', option_type=cursor_text_color, long_text=_('''
|
||||
Choose the color of text under the cursor. If you want it rendered with the
|
||||
background color of the cell underneath instead, use the special keyword: background'''))
|
||||
o('cursor_shape', 'block', option_type=to_cursor_shape, long_text=_(
|
||||
'The cursor shape can be one of (block, beam, underline)'))
|
||||
o('cursor_blink_interval', 0.5, option_type=positive_float, long_text=_('''
|
||||
@@ -281,9 +315,19 @@ 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
|
||||
|
||||
|
||||
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 a 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
|
||||
@@ -293,8 +337,9 @@ 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('wheel_scroll_multiplier', 5.0, long_text=_('''
|
||||
Modify the amount scrolled by the mouse wheel or touchpad. Use
|
||||
negative numbers to change scroll direction.'''))
|
||||
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.'''))
|
||||
# }}}
|
||||
|
||||
g('mouse') # {{{
|
||||
@@ -422,7 +467,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]
|
||||
@@ -434,7 +479,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`.
|
||||
'''))
|
||||
|
||||
@@ -449,6 +494,13 @@ o('window_border_width', 1.0, option_type=positive_float, long_text=_('''
|
||||
The width (in pts) of window borders. Will be rounded to the nearest number of pixels based on screen resolution.
|
||||
Note that borders are displayed only when more than one window is visible. They are meant to separate multiple windows.'''))
|
||||
|
||||
o('draw_minimal_borders', True, long_text=_('''
|
||||
Draw only the minimum borders needed. This means that only the minimum
|
||||
needed borders for inactive windows are drawn. That is only the borders
|
||||
that separate the inactive window from a neighbor. Note that setting
|
||||
a non-zero window margin overrides this and causes all borders to be drawn.
|
||||
'''))
|
||||
|
||||
o('window_margin_width', 0.0, option_type=positive_float, long_text=_('''
|
||||
The window margin (in pts) (blank area outside the border)'''))
|
||||
|
||||
@@ -630,6 +682,19 @@ opening new windows, closing windows, reading the content of windows, etc.
|
||||
Note that this even works over ssh connections.
|
||||
'''))
|
||||
|
||||
o(
|
||||
'+env', '',
|
||||
add_to_default=False,
|
||||
long_text=_('''
|
||||
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
|
||||
|
||||
The value of MYVAR2 will be :code:`a/<path to home directory>/b`.
|
||||
'''))
|
||||
|
||||
|
||||
def startup_session(x):
|
||||
if x.lower() == 'none':
|
||||
@@ -715,7 +780,27 @@ kitty will stay running, even with no open windows, as is the expected
|
||||
behavior on macOS.
|
||||
'''))
|
||||
|
||||
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') # {{{
|
||||
@@ -762,13 +847,25 @@ 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
|
||||
|
||||
Placeholders available are: @text (which is plain text) and @ansi (which
|
||||
includes text styling escape codes). For only the current screen, use @screen
|
||||
or @ansi_screen. For the secondary screen, use @alternate and @ansi_alternate.
|
||||
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. You can also use
|
||||
``none`` for no :file:`STDIN` input.
|
||||
|
||||
To open in a new window, tab or new OS window, use ``window``, ``tab``, or
|
||||
``os_window`` respectively. You can also use ``none`` in which case the data
|
||||
will be piped into the program without creating any windows, useful if the
|
||||
program is a GUI program that creates its own windows. '''))
|
||||
|
||||
|
||||
# }}}
|
||||
@@ -783,6 +880,13 @@ You can open a new window with the current working directory set to the
|
||||
working directory of the current window using::
|
||||
|
||||
map ctrl+alt+enter new_window_with_cwd
|
||||
|
||||
You can open a new window that is allowed to control kitty via
|
||||
the kitty remote control facility by prefixing the command line with @.
|
||||
Any programs running in that window will be allowed to control kitty.
|
||||
For example::
|
||||
|
||||
map ctrl+enter new_window @ some_program
|
||||
'''))
|
||||
if is_macos:
|
||||
k('new_os_window', 'cmd+n', 'new_os_window', _('New OS window'))
|
||||
@@ -807,7 +911,11 @@ k('tenth_window', 'kitty_mod+0', 'tenth_window', _('Tenth window'))
|
||||
# }}}
|
||||
|
||||
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'))
|
||||
@@ -853,7 +961,7 @@ Useful with git, which uses sha1 hashes to identify commits'''))
|
||||
|
||||
g('shortcuts.misc') # {{{
|
||||
k('toggle_fullscreen', 'kitty_mod+f11', 'toggle_fullscreen', _('Toggle fullscreen'))
|
||||
k('input_unicode_character', 'kitty_mod+u', 'input_unicode_character', _('Unicode input'))
|
||||
k('input_unicode_character', 'kitty_mod+u', 'kitten unicode_input', _('Unicode input'))
|
||||
k('edit_config_file', 'kitty_mod+f2', 'edit_config_file', _('Edit config file'))
|
||||
k('kitty_shell', 'kitty_mod+escape', 'kitty_shell window', _('Open the kitty command shell'), long_text=_('''
|
||||
Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.'''))
|
||||
@@ -861,6 +969,17 @@ k('increase_background_opacity', 'kitty_mod+a>m', 'set_background_opacity +0.1',
|
||||
k('decrease_background_opacity', 'kitty_mod+a>l', 'set_background_opacity -0.1', _('Decrease background opacity'))
|
||||
k('full_background_opacity', 'kitty_mod+a>1', 'set_background_opacity 1', _('Make background fully opaque'))
|
||||
k('reset_background_opacity', 'kitty_mod+a>d', 'set_background_opacity default', _('Reset background opacity'))
|
||||
k('reset_terminal', 'kitty_mod+delete', 'clear_terminal reset active', _('Reset the terminal'),
|
||||
long_text=_('''
|
||||
You can create shortcuts to clear/reset the terminal. For example::
|
||||
|
||||
map kitty_mod+f9 clear_terminal reset active
|
||||
map kitty_mod+f10 clear_terminal clear active
|
||||
map kitty_mod+f11 clear_terminal scrollback 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`.
|
||||
'''))
|
||||
k('send_text', 'ctrl+shift+alt+h', 'send_text all Hello World', _('Send arbitrary text on key presses'),
|
||||
add_to_default=False, long_text=_('''
|
||||
You can tell kitty to send arbitrary (UTF-8) encoded text to
|
||||
|
||||
@@ -7,10 +7,9 @@ import pwd
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from .fast_data_types import set_boss as set_c_boss
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 11, 2)
|
||||
version = (0, 12, 2)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
@@ -58,7 +57,10 @@ def _get_config_dir():
|
||||
|
||||
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or '~/.config'))
|
||||
ans = os.path.join(candidate, appname)
|
||||
os.makedirs(ans, exist_ok=True)
|
||||
try:
|
||||
os.makedirs(ans, exist_ok=True)
|
||||
except FileExistsError:
|
||||
raise SystemExit('A file {} already exists. It must be a directory, not a file.'.format(ans))
|
||||
return ans
|
||||
|
||||
|
||||
@@ -91,6 +93,7 @@ def get_boss():
|
||||
|
||||
|
||||
def set_boss(m):
|
||||
from .fast_data_types import set_boss as set_c_boss
|
||||
get_boss.boss = m
|
||||
set_c_boss(m)
|
||||
|
||||
@@ -102,6 +105,7 @@ def wakeup():
|
||||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
terminfo_dir = os.path.join(base_dir, 'terminfo')
|
||||
logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
||||
beam_cursor_data_file = os.path.join(base_dir, 'logo', 'beam-cursor.png')
|
||||
try:
|
||||
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
|
||||
except KeyError:
|
||||
|
||||
@@ -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");
|
||||
@@ -364,10 +372,14 @@ 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
|
||||
|
||||
@@ -245,8 +245,8 @@ cursor_as_sgr(Cursor *self, Cursor *prev) {
|
||||
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->bold && !self->dim) { P("%d", 22); }
|
||||
else { if (self->bold) P("%d", 1); if (self->dim) P("%d", 2); }
|
||||
}
|
||||
if (self->italic != prev->italic) P("%d", self->italic ? 3 : 23);
|
||||
if (self->reverse != prev->reverse) P("%d", self->reverse ? 7 : 27);
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
#version GLSL_VERSION
|
||||
uniform vec3 color;
|
||||
out vec4 final_color;
|
||||
|
||||
void main() {
|
||||
final_color = vec4(color, 1);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#version GLSL_VERSION
|
||||
uniform vec4 pos;
|
||||
|
||||
const uvec2 pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
uvec2(0, 1), // left, bottom
|
||||
uvec2(0, 0) // left, top
|
||||
);
|
||||
|
||||
void main() {
|
||||
vec2 xpos = vec2(pos[0], pos[2]);
|
||||
vec2 ypos = vec2(pos[1], pos[3]);
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(xpos[pos[0]], ypos[pos[1]], 0, 1);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <termios.h>
|
||||
#include <signal.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#ifdef WITH_PROFILER
|
||||
#include <gperftools/profiler.h>
|
||||
#endif
|
||||
@@ -98,7 +99,7 @@ start_profiler(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
stop_profiler(PyObject UNUSED *self) {
|
||||
stop_profiler(PyObject UNUSED *self, PyObject *args UNUSED) {
|
||||
ProfilerStop();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -123,7 +124,9 @@ open_tty(PyObject *self UNUSED, PyObject *args) {
|
||||
if (!PyArg_ParseTuple(args, "|p", &read_with_timeout)) return NULL;
|
||||
int flags = O_RDWR | O_CLOEXEC | O_NOCTTY;
|
||||
if (!read_with_timeout) flags |= O_NONBLOCK;
|
||||
int fd = open("/dev/tty", flags);
|
||||
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; }
|
||||
@@ -211,9 +214,11 @@ extern bool init_shaders(PyObject *module);
|
||||
extern bool init_mouse(PyObject *module);
|
||||
extern bool init_kittens(PyObject *module);
|
||||
extern bool init_logging(PyObject *module);
|
||||
extern bool init_png_reader(PyObject *module);
|
||||
#ifdef __APPLE__
|
||||
extern int init_CoreText(PyObject *);
|
||||
extern bool init_cocoa(PyObject *module);
|
||||
extern bool init_macos_process_info(PyObject *module);
|
||||
#else
|
||||
extern bool init_freetype_library(PyObject*);
|
||||
#endif
|
||||
@@ -246,7 +251,9 @@ PyInit_fast_data_types(void) {
|
||||
if (!init_shaders(m)) return NULL;
|
||||
if (!init_mouse(m)) return NULL;
|
||||
if (!init_kittens(m)) return NULL;
|
||||
if (!init_png_reader(m)) return NULL;
|
||||
#ifdef __APPLE__
|
||||
if (!init_macos_process_info(m)) return NULL;
|
||||
if (!init_CoreText(m)) return NULL;
|
||||
if (!init_cocoa(m)) return NULL;
|
||||
#else
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||
void log_error(const char *fmt, ...);
|
||||
void log_error(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
#define fatal(...) { log_error(__VA_ARGS__); exit(EXIT_FAILURE); }
|
||||
|
||||
typedef unsigned long long id_type;
|
||||
@@ -197,7 +197,6 @@ typedef struct {
|
||||
bool is_visible, is_focused;
|
||||
CursorShape shape;
|
||||
unsigned int x, y;
|
||||
double left, right, top, bottom;
|
||||
color_type color;
|
||||
} CursorRenderInfo;
|
||||
|
||||
@@ -272,7 +271,7 @@ const char* cursor_as_sgr(Cursor*, Cursor*);
|
||||
|
||||
double monotonic();
|
||||
PyObject* cm_thread_write(PyObject *self, PyObject *args);
|
||||
bool schedule_write_to_child(unsigned long id, const char *data, size_t sz);
|
||||
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);
|
||||
@@ -281,10 +280,11 @@ void colorprofile_push_dynamic_colors(ColorProfile*);
|
||||
void colorprofile_pop_dynamic_colors(ColorProfile*);
|
||||
|
||||
void set_mouse_cursor(MouseShape);
|
||||
void mouse_event(int, int);
|
||||
void enter_event();
|
||||
void mouse_event(int, int, int);
|
||||
void focus_in_event();
|
||||
void wakeup_io_loop(bool);
|
||||
void scroll_event(double, double);
|
||||
void scroll_event(double, double, int);
|
||||
void fake_scroll(int, bool);
|
||||
void set_special_key_combo(int glfw_key, int mods, bool is_native);
|
||||
void on_key_input(int key, int scancode, int action, int mods, const char*, int);
|
||||
|
||||
2
kitty/emoji.h
generated
2
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-06-14
|
||||
// unicode data, built from the unicode standard on: 2018-08-04
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
@@ -1076,10 +1076,11 @@ send_prerendered_sprites(FontGroup *fg) {
|
||||
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, x, y, z, fg->canvas);
|
||||
do_increment(fg, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); fatal("failed"); }
|
||||
PyObject *args = PyObject_CallFunction(prerender_function, "IIIII", fg->cell_width, fg->cell_height, fg->baseline, fg->underline_position, fg->underline_thickness);
|
||||
if (args == NULL) { PyErr_Print(); fatal("Failed to prerender cells"); }
|
||||
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 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 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));
|
||||
@@ -1105,7 +1106,7 @@ initialize_font(FontGroup *fg, unsigned int desc_idx, const char *ftype) {
|
||||
Py_CLEAR(face);
|
||||
if (!ok) {
|
||||
if (PyErr_Occurred()) { PyErr_Print(); }
|
||||
fatal("Failed to initialize %s font: %d", ftype, idx);
|
||||
fatal("Failed to initialize %s font: %zu", ftype, idx);
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
@@ -1279,6 +1280,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;
|
||||
}
|
||||
|
||||
@@ -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, sqrt
|
||||
|
||||
from kitty.config import defaults
|
||||
from kitty.constants import is_macos
|
||||
@@ -116,7 +116,7 @@ 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
|
||||
yfactor = max(thickness, 2)
|
||||
|
||||
def clamp_y(y):
|
||||
return max(0, min(int(y), cell_height - 1))
|
||||
@@ -125,19 +125,18 @@ def add_curl(buf, cell_width, position, thickness, cell_height):
|
||||
return max(0, min(int(x), cell_width - 1))
|
||||
|
||||
def add_intensity(x, y, distance):
|
||||
buf[cell_width * y + x] = min(
|
||||
255, buf[cell_width * y + x] + int(255 * (1 - distance))
|
||||
idx = cell_width * y + x
|
||||
buf[idx] = min(
|
||||
255, buf[idx] + 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))
|
||||
y_exact = yfactor * cos(x_exact * xfactor) + position
|
||||
y = 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)
|
||||
dist = sqrt((x - x_exact)**2 + (y - y_exact)**2) / 2
|
||||
add_intensity(x, y, dist)
|
||||
|
||||
|
||||
def render_special(
|
||||
@@ -166,12 +165,43 @@ def render_special(
|
||||
return ans
|
||||
|
||||
|
||||
def prerender_function(cell_width, cell_height, baseline, underline_position, underline_thickness):
|
||||
# Pre-render the special underline, strikethrough and missing cells
|
||||
def render_cursor(which, cell_width=0, cell_height=0, dpi_x=0, dpi_y=0):
|
||||
CharTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||
ans = CharTexture()
|
||||
|
||||
def vert(edge, width_pt=1):
|
||||
width = max(1, int(round(width_pt * dpi_x / 72.0)))
|
||||
left = 0 if edge == 'left' else max(0, cell_width - width)
|
||||
for y in range(cell_height):
|
||||
offset = y * cell_width + left
|
||||
for x in range(offset, offset + width):
|
||||
ans[x] = 255
|
||||
|
||||
def horz(edge, height_pt=1):
|
||||
height = max(1, int(round(height_pt * dpi_y / 72.0)))
|
||||
top = 0 if edge == 'top' else max(0, cell_height - height)
|
||||
for y in range(top, top + height):
|
||||
offset = y * cell_width
|
||||
for x in range(cell_width):
|
||||
ans[offset + x] = 255
|
||||
|
||||
if which == 1: # beam
|
||||
vert('left', 1.5)
|
||||
elif which == 2: # underline
|
||||
horz('bottom', 2.0)
|
||||
elif which == 3: # hollow
|
||||
vert('left'), vert('right'), horz('top'), horz('bottom')
|
||||
return ans
|
||||
|
||||
|
||||
def prerender_function(cell_width, cell_height, baseline, underline_position, underline_thickness, dpi_x, dpi_y):
|
||||
# Pre-render the special underline, strikethrough and missing and cursor cells
|
||||
f = partial(
|
||||
render_special, cell_width=cell_width, cell_height=cell_height, baseline=baseline,
|
||||
underline_position=underline_position, underline_thickness=underline_thickness)
|
||||
cells = f(1), f(2), f(3), f(0, True), f(missing=True)
|
||||
c = partial(
|
||||
render_cursor, cell_width=cell_width, cell_height=cell_height, dpi_x=dpi_x, dpi_y=dpi_y)
|
||||
cells = f(1), f(2), f(3), f(0, True), f(missing=True), c(1), c(2), c(3)
|
||||
return tuple(map(ctypes.addressof, cells)) + (cells,)
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#endif
|
||||
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_BITMAP_H
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
@@ -175,7 +176,7 @@ face_from_descriptor(PyObject *descriptor, FONTS_DATA_HANDLE fg) {
|
||||
if (!missing_ok) { PyErr_SetString(PyExc_KeyError, "font descriptor is missing the key: " #key); return NULL; } \
|
||||
} else key = conv(t); \
|
||||
}
|
||||
char *path = NULL;
|
||||
const char *path = NULL;
|
||||
long index = 0;
|
||||
bool hinting = false;
|
||||
long hint_style = 0;
|
||||
@@ -231,6 +232,27 @@ load_glyph(Face *self, int glyph_index, int load_type) {
|
||||
int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
|
||||
int error = FT_Load_Glyph(self->face, glyph_index, flags);
|
||||
if (error) { set_freetype_error("Failed to load glyph, with error:", error); return false; }
|
||||
|
||||
// Embedded bitmap glyph?
|
||||
if (self->face->glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO && load_type != FT_LOAD_DEFAULT) {
|
||||
FT_Bitmap bitmap;
|
||||
FT_Bitmap_New(&bitmap);
|
||||
|
||||
// This also sets pixel_mode to FT_PIXEL_MODE_GRAY so we don't have to
|
||||
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;
|
||||
}
|
||||
error = FT_Bitmap_Copy(library, &bitmap, &self->face->glyph->bitmap);
|
||||
if (error) { set_freetype_error("Failed to copy bitmap, with error:", error); return false; }
|
||||
FT_Bitmap_Done(library, &bitmap);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -336,7 +358,7 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
|
||||
static void
|
||||
downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_height) {
|
||||
// Downsample using a simple area averaging algorithm. Could probably do
|
||||
// better with bi-cubic or lanczos, but at these small sizes I dont think
|
||||
// better with bi-cubic or lanczos, but at these small sizes I don't think
|
||||
// it matters
|
||||
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
|
||||
int factor = ceilf(ratio);
|
||||
|
||||
5
kitty/glfw-wrapper.c
generated
5
kitty/glfw-wrapper.c
generated
@@ -257,6 +257,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwSetKeyboardCallback_impl) = dlsym(handle, "glfwSetKeyboardCallback");
|
||||
if (glfwSetKeyboardCallback_impl == NULL) fail("Failed to load glfw function glfwSetKeyboardCallback with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwUpdateIMEState_impl) = dlsym(handle, "glfwUpdateIMEState");
|
||||
if (glfwUpdateIMEState_impl == NULL) fail("Failed to load glfw function glfwUpdateIMEState with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetMouseButtonCallback_impl) = dlsym(handle, "glfwSetMouseButtonCallback");
|
||||
if (glfwSetMouseButtonCallback_impl == NULL) fail("Failed to load glfw function glfwSetMouseButtonCallback with error: %s", dlerror());
|
||||
|
||||
@@ -359,6 +362,8 @@ load_glfw(const char* path) {
|
||||
|
||||
*(void **) (&glfwSetCocoaTextInputFilter_impl) = dlsym(handle, "glfwSetCocoaTextInputFilter");
|
||||
|
||||
*(void **) (&glfwSetCocoaToggleFullscreenIntercept_impl) = dlsym(handle, "glfwSetCocoaToggleFullscreenIntercept");
|
||||
|
||||
*(void **) (&glfwSetApplicationShouldHandleReopen_impl) = dlsym(handle, "glfwSetApplicationShouldHandleReopen");
|
||||
|
||||
*(void **) (&glfwGetCocoaKeyEquivalent_impl) = dlsym(handle, "glfwGetCocoaKeyEquivalent");
|
||||
|
||||
31
kitty/glfw-wrapper.h
generated
31
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);
|
||||
|
||||
|
||||
|
||||
@@ -734,6 +732,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 +743,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
|
||||
@@ -819,6 +823,7 @@ typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
* @{ */
|
||||
#define GLFW_JOYSTICK_HAT_BUTTONS 0x00050001
|
||||
#define GLFW_DEBUG_KEYBOARD 0x00050002
|
||||
#define GLFW_ENABLE_JOYSTICKS 0x00050003
|
||||
|
||||
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
||||
#define GLFW_COCOA_MENUBAR 0x00051002
|
||||
@@ -1133,15 +1138,17 @@ 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.
|
||||
*
|
||||
* @sa @ref scrolling
|
||||
* @sa @ref glfwSetScrollCallback
|
||||
*
|
||||
* @since Added in version 3.0. Replaces `GLFWmousewheelfun`.
|
||||
* @since Changed in version 4.0. Added `flags` parameter.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double,int);
|
||||
|
||||
/*! @brief The function signature for key callbacks.
|
||||
*
|
||||
@@ -1160,7 +1167,9 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
|
||||
* held down.
|
||||
* @param[in] text UTF-8 encoded text generated by this key event or empty string.
|
||||
* @param[in] reserved Reserved for future use.
|
||||
* @param[in] Used for Input Method events. Zero for normal key events.
|
||||
* A value of 1 means the pre-edit text for the input event has been changed.
|
||||
* A value of 2 means the text should be committed.
|
||||
*
|
||||
* @note On X11/Wayland if a modifier other than the modifiers GLFW reports
|
||||
* (ctrl/shift/alt/super) is used, GLFW will report the shifted key rather
|
||||
@@ -1375,6 +1384,10 @@ typedef struct GLFWgamepadstate
|
||||
*/
|
||||
|
||||
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
typedef int (* GLFWapplicationshouldhandlereopenfun)(int);
|
||||
typedef int (* GLFWcocoatogglefullscreenfun)(GLFWwindow*);
|
||||
|
||||
typedef int (*glfwInit_func)();
|
||||
glfwInit_func glfwInit_impl;
|
||||
#define glfwInit glfwInit_impl
|
||||
@@ -1679,7 +1692,7 @@ typedef void (*glfwSetCursorPos_func)(GLFWwindow*, double, double);
|
||||
glfwSetCursorPos_func glfwSetCursorPos_impl;
|
||||
#define glfwSetCursorPos glfwSetCursorPos_impl
|
||||
|
||||
typedef GLFWcursor* (*glfwCreateCursor_func)(const GLFWimage*, int, int);
|
||||
typedef GLFWcursor* (*glfwCreateCursor_func)(const GLFWimage*, int, int, int);
|
||||
glfwCreateCursor_func glfwCreateCursor_impl;
|
||||
#define glfwCreateCursor glfwCreateCursor_impl
|
||||
|
||||
@@ -1699,6 +1712,10 @@ typedef GLFWkeyboardfun (*glfwSetKeyboardCallback_func)(GLFWwindow*, GLFWkeyboar
|
||||
glfwSetKeyboardCallback_func glfwSetKeyboardCallback_impl;
|
||||
#define glfwSetKeyboardCallback glfwSetKeyboardCallback_impl
|
||||
|
||||
typedef void (*glfwUpdateIMEState_func)(GLFWwindow*, int, int, int, int, int);
|
||||
glfwUpdateIMEState_func glfwUpdateIMEState_impl;
|
||||
#define glfwUpdateIMEState glfwUpdateIMEState_impl
|
||||
|
||||
typedef GLFWmousebuttonfun (*glfwSetMouseButtonCallback_func)(GLFWwindow*, GLFWmousebuttonfun);
|
||||
glfwSetMouseButtonCallback_func glfwSetMouseButtonCallback_impl;
|
||||
#define glfwSetMouseButtonCallback glfwSetMouseButtonCallback_impl
|
||||
@@ -1839,6 +1856,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
|
||||
|
||||
209
kitty/glfw.c
209
kitty/glfw.c
@@ -8,7 +8,9 @@
|
||||
#include "fonts.h"
|
||||
#include <structmember.h>
|
||||
#include "glfw-wrapper.h"
|
||||
extern bool cocoa_make_window_resizable(void *w);
|
||||
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);
|
||||
@@ -105,6 +107,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;
|
||||
}
|
||||
@@ -138,15 +151,27 @@ key_callback(GLFWwindow *w, int key, int scancode, int action, int mods, const c
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
cursor_enter_callback(GLFWwindow *w, int entered) {
|
||||
if (!set_callback_window(w)) return;
|
||||
if (entered) {
|
||||
show_mouse_cursor(w);
|
||||
double now = monotonic();
|
||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
||||
if (is_window_ready_for_callbacks()) enter_event();
|
||||
}
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
|
||||
if (!set_callback_window(w)) return;
|
||||
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;
|
||||
}
|
||||
@@ -160,17 +185,17 @@ 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;
|
||||
}
|
||||
|
||||
static void
|
||||
scroll_callback(GLFWwindow *w, double xoffset, double yoffset) {
|
||||
scroll_callback(GLFWwindow *w, double xoffset, double yoffset, int flags) {
|
||||
if (!set_callback_window(w)) return;
|
||||
show_mouse_cursor(w);
|
||||
double now = monotonic();
|
||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
||||
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset);
|
||||
if (is_window_ready_for_callbacks()) scroll_event(xoffset, yoffset, flags);
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
@@ -202,6 +227,7 @@ push_focus_history(OSWindow *w) {
|
||||
|
||||
static void
|
||||
window_focus_callback(GLFWwindow *w, int focused) {
|
||||
global_state.active_drag_in_window = 0;
|
||||
if (!set_callback_window(w)) return;
|
||||
global_state.callback_os_window->is_focused = focused ? true : false;
|
||||
if (focused) {
|
||||
@@ -214,6 +240,7 @@ window_focus_callback(GLFWwindow *w, int focused) {
|
||||
global_state.callback_os_window->cursor_blink_zero_time = now;
|
||||
if (is_window_ready_for_callbacks()) {
|
||||
WINDOW_CALLBACK(on_focus, "O", focused ? Py_True : Py_False);
|
||||
glfwUpdateIMEState(global_state.callback_os_window->handle, 1, focused, 0, 0, 0);
|
||||
}
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
@@ -263,12 +290,11 @@ 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);
|
||||
}
|
||||
}
|
||||
@@ -313,7 +339,7 @@ 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();
|
||||
if (monitor == NULL) { PyErr_Print(); monitor = glfwGetPrimaryMonitor(); }
|
||||
float xscale = 1, yscale = 1;
|
||||
if (monitor) glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||
#ifdef __APPLE__
|
||||
@@ -330,11 +356,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
|
||||
@@ -345,6 +416,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
|
||||
@@ -380,7 +459,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, true);
|
||||
// We dont use depth and stencil buffers
|
||||
// We don't use depth and stencil buffers
|
||||
glfwWindowHint(GLFW_DEPTH_BITS, 0);
|
||||
glfwWindowHint(GLFW_STENCIL_BITS, 0);
|
||||
#ifdef __APPLE__
|
||||
@@ -394,8 +473,9 @@ 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);
|
||||
glfwWindowHintString(GLFW_WAYLAND_APP_ID, wm_class_class);
|
||||
if (OPT(x11_hide_window_decorations)) {
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_TRUE);
|
||||
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -418,7 +498,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
#endif
|
||||
|
||||
GLFWwindow *temp_window = glfwCreateWindow(640, 480, "temp", NULL, common_context);
|
||||
if (temp_window == NULL) { fatal("Failed to create GLFW temp window!"); }
|
||||
if (temp_window == NULL) { fatal("Failed to create GLFW temp window! This usually happens because of old/broken OpenGL drivers. kitty requires working OpenGL 3.3 drivers."); }
|
||||
|
||||
double dpi_x, dpi_y;
|
||||
get_window_dpi(temp_window, &dpi_x, &dpi_y);
|
||||
@@ -455,8 +535,10 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
if (OPT(macos_hide_from_tasks)) cocoa_set_hide_from_tasks();
|
||||
#endif
|
||||
#define CC(dest, shape) {\
|
||||
dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \
|
||||
if (dest##_cursor == NULL) { log_error("Failed to create the %s mouse cursor, using default cursor.", #shape); }}
|
||||
if (!dest##_cursor) { \
|
||||
dest##_cursor = glfwCreateStandardCursor(GLFW_##shape##_CURSOR); \
|
||||
if (dest##_cursor == NULL) { log_error("Failed to create the %s mouse cursor, using default cursor.", #shape); } \
|
||||
}}
|
||||
CC(standard, IBEAM); CC(click, HAND); CC(arrow, ARROW);
|
||||
#undef CC
|
||||
is_first_window = false;
|
||||
@@ -465,18 +547,18 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
w->handle = glfw_window;
|
||||
update_os_window_references();
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
// On some platforms (macOS) newly created windows dont get the initial focus in event
|
||||
// On some platforms (macOS) newly created windows don't get the initial focus in event
|
||||
OSWindow *q = global_state.os_windows + i;
|
||||
q->is_focused = q == w ? true : false;
|
||||
}
|
||||
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);
|
||||
#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);
|
||||
@@ -485,6 +567,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback);
|
||||
glfwSetWindowContentScaleCallback(glfw_window, dpi_change_callback);
|
||||
glfwSetWindowRefreshCallback(glfw_window, refresh_callback);
|
||||
glfwSetCursorEnterCallback(glfw_window, cursor_enter_callback);
|
||||
glfwSetMouseButtonCallback(glfw_window, mouse_button_callback);
|
||||
glfwSetScrollCallback(glfw_window, scroll_callback);
|
||||
glfwSetCursorPosCallback(glfw_window, cursor_pos_callback);
|
||||
@@ -492,10 +575,8 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwSetWindowFocusCallback(glfw_window, window_focus_callback);
|
||||
glfwSetDropCallback(glfw_window, drop_callback);
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_hide_titlebar)) {
|
||||
if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } }
|
||||
else log_error("Failed to load glfwGetCocoaWindow");
|
||||
}
|
||||
if (glfwGetCocoaWindow) cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window), OPT(macos_window_resizable));
|
||||
else log_error("Failed to load glfwGetCocoaWindow");
|
||||
#endif
|
||||
double now = monotonic();
|
||||
w->is_focused = true;
|
||||
@@ -519,7 +600,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__
|
||||
@@ -541,6 +621,19 @@ destroy_os_window(OSWindow *w) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
focus_os_window(OSWindow *w, bool also_raise) {
|
||||
if (w->handle) {
|
||||
#ifdef __APPLE__
|
||||
if (!also_raise) cocoa_focus_window(glfwGetCocoaWindow(w->handle));
|
||||
else glfwFocusWindow(w->handle);
|
||||
#else
|
||||
(void)also_raise;
|
||||
glfwFocusWindow(w->handle);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
bool
|
||||
application_quit_requested() {
|
||||
@@ -564,6 +657,10 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
|
||||
if (err) { PyErr_SetString(PyExc_RuntimeError, err); return NULL; }
|
||||
glfwSetErrorCallback(error_callback);
|
||||
glfwInitHint(GLFW_DEBUG_KEYBOARD, debug_keyboard);
|
||||
// Joysticks cause slow startup on some linux systems, see
|
||||
// https://github.com/kovidgoyal/kitty/issues/830
|
||||
glfwInitHint(GLFW_ENABLE_JOYSTICKS, 0);
|
||||
global_state.opts.debug_keyboard = debug_keyboard != 0;
|
||||
#ifdef __APPLE__
|
||||
glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, 0);
|
||||
glfwInitHint(GLFW_COCOA_MENUBAR, 0);
|
||||
@@ -660,26 +757,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);
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
if (toggle_fullscreen_for_os_window(w)) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
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
|
||||
@@ -840,6 +933,39 @@ set_smallest_allowed_resize(PyObject *self UNUSED, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
set_custom_cursor(PyObject *self UNUSED, PyObject *args) {
|
||||
int shape;
|
||||
int x=0, y=0;
|
||||
Py_ssize_t sz;
|
||||
PyObject *images;
|
||||
if (!PyArg_ParseTuple(args, "iO!|ii", &shape, &PyTuple_Type, &images, &x, &y)) return NULL;
|
||||
static GLFWimage gimages[16] = {{0}};
|
||||
size_t count = MIN((size_t)PyTuple_GET_SIZE(images), arraysz(gimages));
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(images, i), "s#ii", &gimages[i].pixels, &sz, &gimages[i].width, &gimages[i].height)) return NULL;
|
||||
if (gimages[i].width * gimages[i].height * 4 != sz) {
|
||||
PyErr_SetString(PyExc_ValueError, "The image data size does not match its width and height");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
#define CASE(which, dest) {\
|
||||
case which: \
|
||||
standard_cursor = glfwCreateCursor(gimages, x, y, count); \
|
||||
if (standard_cursor == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create custom cursor"); return NULL; } \
|
||||
break; \
|
||||
}
|
||||
switch(shape) {
|
||||
CASE(GLFW_IBEAM_CURSOR, standard_cursor);
|
||||
CASE(GLFW_HAND_CURSOR, click_cursor);
|
||||
CASE(GLFW_ARROW_CURSOR, arrow_cursor);
|
||||
default:
|
||||
PyErr_SetString(PyExc_ValueError, "Unknown cursor shape");
|
||||
return NULL;
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
void
|
||||
get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *cocoa_mods) {
|
||||
@@ -849,6 +975,7 @@ get_cocoa_key_equivalent(int key, int mods, unsigned short *cocoa_key, int *coco
|
||||
// Boilerplate {{{
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(set_custom_cursor, METH_VARARGS),
|
||||
METHODB(set_smallest_allowed_resize, METH_VARARGS),
|
||||
METHODB(create_os_window, METH_VARARGS),
|
||||
METHODB(set_default_window_icon, METH_VARARGS),
|
||||
@@ -856,6 +983,7 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(get_content_scale_for_window, 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),
|
||||
@@ -892,6 +1020,7 @@ init_glfw(PyObject *m) {
|
||||
ADDC(GLFW_PRESS);
|
||||
ADDC(GLFW_REPEAT);
|
||||
ADDC(GLFW_TRUE); ADDC(GLFW_FALSE);
|
||||
ADDC(GLFW_IBEAM_CURSOR); ADDC(GLFW_HAND_CURSOR); ADDC(GLFW_ARROW_CURSOR);
|
||||
|
||||
// --- Keys --------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <zlib.h>
|
||||
#include <png.h>
|
||||
#include <structmember.h>
|
||||
#include "png-reader.h"
|
||||
PyTypeObject GraphicsManager_Type;
|
||||
|
||||
#define STORAGE_LIMIT (320 * (1024 * 1024))
|
||||
@@ -227,87 +227,14 @@ err:
|
||||
return ok;
|
||||
}
|
||||
|
||||
struct fake_file { uint8_t *buf; size_t sz, cur; };
|
||||
|
||||
static void
|
||||
read_png_from_buffer(png_structp png, png_bytep out, png_size_t length) {
|
||||
struct fake_file *f = png_get_io_ptr(png);
|
||||
if (f) {
|
||||
size_t amt = MIN(length, f->sz - f->cur);
|
||||
memcpy(out, f->buf + f->cur, amt);
|
||||
f->cur += amt;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
read_png_error_handler(png_structp png_ptr, png_const_charp msg) {
|
||||
jmp_buf *jb;
|
||||
set_add_response("EBADPNG", msg);
|
||||
jb = png_get_error_ptr(png_ptr);
|
||||
if (jb == NULL) fatal("read_png_error_handler: could not retrieve jmp_buf");
|
||||
longjmp(*jb, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
read_png_warn_handler(png_structp UNUSED png_ptr, png_const_charp UNUSED msg) {
|
||||
// ignore warnings
|
||||
}
|
||||
|
||||
struct png_jmp_data { uint8_t *decompressed; bool ok; png_bytep *row_pointers; int width, height; size_t sz; };
|
||||
|
||||
static void
|
||||
inflate_png_inner(struct png_jmp_data *d, uint8_t *buf, size_t bufsz) {
|
||||
struct fake_file f = {.buf = buf, .sz = bufsz};
|
||||
png_structp png = NULL;
|
||||
png_infop info = NULL;
|
||||
jmp_buf jb;
|
||||
png = png_create_read_struct(PNG_LIBPNG_VER_STRING, &jb, read_png_error_handler, read_png_warn_handler);
|
||||
if (!png) ABRT(ENOMEM, "Failed to create PNG read structure");
|
||||
info = png_create_info_struct(png);
|
||||
if (!info) ABRT(ENOMEM, "Failed to create PNG info structure");
|
||||
|
||||
if (setjmp(jb)) goto err;
|
||||
|
||||
png_set_read_fn(png, &f, read_png_from_buffer);
|
||||
png_read_info(png, info);
|
||||
png_byte color_type, bit_depth;
|
||||
d->width = png_get_image_width(png, info);
|
||||
d->height = png_get_image_height(png, info);
|
||||
color_type = png_get_color_type(png, info);
|
||||
bit_depth = png_get_bit_depth(png, info);
|
||||
|
||||
// Ensure we get RGBA data out of libpng
|
||||
if (bit_depth == 16) png_set_strip_16(png);
|
||||
if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png);
|
||||
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) png_set_expand_gray_1_2_4_to_8(png);
|
||||
|
||||
if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png);
|
||||
|
||||
// These color_type don't have an alpha channel then fill it with 0xff.
|
||||
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
|
||||
|
||||
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png);
|
||||
png_read_update_info(png, info);
|
||||
|
||||
int rowbytes = png_get_rowbytes(png, info);
|
||||
d->sz = rowbytes * d->height * sizeof(png_byte);
|
||||
d->decompressed = malloc(d->sz + 16);
|
||||
if (d->decompressed == NULL) ABRT(ENOMEM, "Out of memory allocating decompression buffer for PNG");
|
||||
d->row_pointers = malloc(d->height * sizeof(png_bytep));
|
||||
if (d->row_pointers == NULL) ABRT(ENOMEM, "Out of memory allocating row_pointers buffer for PNG");
|
||||
for (int i = 0; i < d->height; i++) d->row_pointers[i] = d->decompressed + i * rowbytes;
|
||||
png_read_image(png, d->row_pointers);
|
||||
|
||||
d->ok = true;
|
||||
err:
|
||||
if (png) png_destroy_read_struct(&png, info ? &info : NULL, NULL);
|
||||
return;
|
||||
png_error_handler(const char *code, const char *msg) {
|
||||
set_add_response(code, "%s", msg);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
inflate_png(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz) {
|
||||
struct png_jmp_data d = {0};
|
||||
png_read_data d = {.err_handler=png_error_handler};
|
||||
inflate_png_inner(&d, buf, bufsz);
|
||||
if (d.ok) {
|
||||
free_load_data(&img->load_data);
|
||||
|
||||
@@ -61,7 +61,7 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
|
||||
if (!PyArg_ParseTuple(args, "II", &ynum, &xnum)) 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 +74,6 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
return (PyObject*)self;
|
||||
@@ -164,7 +161,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);
|
||||
@@ -210,7 +207,7 @@ 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 PyObject*
|
||||
as_text(HistoryBuf *self, PyObject *args) {
|
||||
@@ -225,7 +222,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));
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user