Compare commits
459 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b6240a067 | ||
|
|
f5c713b118 | ||
|
|
ac7596b7c5 | ||
|
|
f69ed984a4 | ||
|
|
83850b28ae | ||
|
|
5efa26a98d | ||
|
|
51d2c01bc1 | ||
|
|
be9d876997 | ||
|
|
866b53f384 | ||
|
|
de1a2f7682 | ||
|
|
86686bfac7 | ||
|
|
3f316c39d1 | ||
|
|
14459a7d18 | ||
|
|
c062b84bc6 | ||
|
|
890a68fa53 | ||
|
|
59017a6e6f | ||
|
|
06cb2cafe6 | ||
|
|
e4ed170748 | ||
|
|
f011a6c86c | ||
|
|
45fc1ace0c | ||
|
|
8d51edec17 | ||
|
|
f2afba2ef4 | ||
|
|
efcd3a5df7 | ||
|
|
2a52acdef4 | ||
|
|
eadfeeec9b | ||
|
|
f8a581129f | ||
|
|
93ff651d9e | ||
|
|
bec0f02b11 | ||
|
|
0721951c79 | ||
|
|
b37d07d31a | ||
|
|
9cacfc0c26 | ||
|
|
0b93b85cf2 | ||
|
|
f7001ea068 | ||
|
|
aa93c3fb66 | ||
|
|
166c7bf0e6 | ||
|
|
bb1374f64e | ||
|
|
053835afbe | ||
|
|
4760580d1c | ||
|
|
8fa492e0ee | ||
|
|
040ae75c77 | ||
|
|
759aaf294a | ||
|
|
9ea0de868c | ||
|
|
78002cf3ba | ||
|
|
f68f8bf9c9 | ||
|
|
54438cae3f | ||
|
|
2e693c31df | ||
|
|
0f06bf1bdb | ||
|
|
b7f4f2e898 | ||
|
|
7148ee8a0f | ||
|
|
238508fc0a | ||
|
|
84ab0f5062 | ||
|
|
cf04bdb36a | ||
|
|
d09a30d16c | ||
|
|
d54b58d2f3 | ||
|
|
0be0963dc7 | ||
|
|
61a2360df5 | ||
|
|
b3ed4e3bc2 | ||
|
|
316e7cb9f5 | ||
|
|
23851e31bf | ||
|
|
b7b64ec513 | ||
|
|
2b372373ae | ||
|
|
7a543fac86 | ||
|
|
5c4b14468c | ||
|
|
7d4d978d97 | ||
|
|
fe5b8f3aec | ||
|
|
f5abe7cbf7 | ||
|
|
bd0382830a | ||
|
|
0b99bb534f | ||
|
|
9d67198ff9 | ||
|
|
fda57a81e1 | ||
|
|
9962ac2703 | ||
|
|
edc4b91d15 | ||
|
|
92ca8ab9cc | ||
|
|
07e2d9175c | ||
|
|
c90dbb25b9 | ||
|
|
b5da109e7f | ||
|
|
aa18429a8c | ||
|
|
e578d41adb | ||
|
|
f3579e78af | ||
|
|
9c43707f40 | ||
|
|
99ff92a5a7 | ||
|
|
d866c0d202 | ||
|
|
20c42ab519 | ||
|
|
8bbc2b82d4 | ||
|
|
6929358976 | ||
|
|
82acd6b511 | ||
|
|
bb939ed4f8 | ||
|
|
cd25abc49f | ||
|
|
2e90e443ed | ||
|
|
92bb6e293a | ||
|
|
ea9b431386 | ||
|
|
23077fd6a6 | ||
|
|
5eee7ce338 | ||
|
|
784894cac3 | ||
|
|
757983b729 | ||
|
|
b6660353cd | ||
|
|
215a7689d9 | ||
|
|
dd18a1dfd8 | ||
|
|
32f16ee5f5 | ||
|
|
d4f426d2ce | ||
|
|
a6d96c9a14 | ||
|
|
bf3bfeaf69 | ||
|
|
6ce82177ea | ||
|
|
dc6efb5b37 | ||
|
|
b26d1f306d | ||
|
|
cf678d8179 | ||
|
|
8cecb22481 | ||
|
|
2504266806 | ||
|
|
e3795eb07e | ||
|
|
796ead5d75 | ||
|
|
d5907a1bf9 | ||
|
|
ddd4774169 | ||
|
|
1dc83a47cf | ||
|
|
0a8dc4be0a | ||
|
|
3d99361790 | ||
|
|
1e278938cc | ||
|
|
ba177c61bc | ||
|
|
458f10be10 | ||
|
|
c1667d9fc1 | ||
|
|
52f4e81d59 | ||
|
|
1056ab1b81 | ||
|
|
3f9d04b616 | ||
|
|
d9cc065c99 | ||
|
|
612fb3b596 | ||
|
|
532d51c411 | ||
|
|
5630c0e434 | ||
|
|
5ba8ca2745 | ||
|
|
e9ffb8d185 | ||
|
|
339a2e0800 | ||
|
|
98c04fb7da | ||
|
|
c95d1393cc | ||
|
|
1779cdd353 | ||
|
|
6a090ac740 | ||
|
|
3c7236557c | ||
|
|
83a3d99824 | ||
|
|
808750a76a | ||
|
|
32caea4197 | ||
|
|
038248f55d | ||
|
|
21b7bf6009 | ||
|
|
9dc571d8a9 | ||
|
|
805baad9cd | ||
|
|
cc910f6b79 | ||
|
|
56dd442e2c | ||
|
|
3192b61385 | ||
|
|
eef3a215a1 | ||
|
|
cb7fff6986 | ||
|
|
75b5130d96 | ||
|
|
2cf8c6aea7 | ||
|
|
5755ba72b1 | ||
|
|
785937ecfc | ||
|
|
955ead05be | ||
|
|
3830261384 | ||
|
|
662ab0df45 | ||
|
|
1ce50fb6f0 | ||
|
|
74dcf8800d | ||
|
|
e90df0ca95 | ||
|
|
49c375ae0f | ||
|
|
6c4d1a4951 | ||
|
|
f99e368831 | ||
|
|
8468ad60d5 | ||
|
|
a1ce29aab9 | ||
|
|
20b5084904 | ||
|
|
2fde0ea86b | ||
|
|
b64bceac7f | ||
|
|
a97174a350 | ||
|
|
6141c9fdda | ||
|
|
a32e010f89 | ||
|
|
4847fb52cc | ||
|
|
58e33e2512 | ||
|
|
584b59c7a1 | ||
|
|
2e657f0020 | ||
|
|
ae112294c8 | ||
|
|
8b4c7ea23f | ||
|
|
83e225a50f | ||
|
|
6256b9027a | ||
|
|
1fd84612a8 | ||
|
|
2efa83bc4d | ||
|
|
fa064bf9cf | ||
|
|
325d2bc14e | ||
|
|
90a4b4f859 | ||
|
|
ce0db16479 | ||
|
|
43ce3ce4b0 | ||
|
|
910cebeeb5 | ||
|
|
1c78633d1a | ||
|
|
530fd61125 | ||
|
|
9edfafcac2 | ||
|
|
677c47b9dd | ||
|
|
b59d7dda11 | ||
|
|
7a3534baf8 | ||
|
|
3bfcadcd81 | ||
|
|
529bb5c6a0 | ||
|
|
caf9a12b34 | ||
|
|
da8e2d3d4a | ||
|
|
582500d3e9 | ||
|
|
7926a140b8 | ||
|
|
023487a1d7 | ||
|
|
71f84b1023 | ||
|
|
ce85382c68 | ||
|
|
94d248d812 | ||
|
|
62db7639a8 | ||
|
|
80f3fe6e9f | ||
|
|
9ba2c4b397 | ||
|
|
bfa53d4b45 | ||
|
|
69f3ceb903 | ||
|
|
d0f839bbd8 | ||
|
|
48538c0e25 | ||
|
|
02ab22f08c | ||
|
|
b3b216a6dc | ||
|
|
431564c8bc | ||
|
|
57f1bbb71a | ||
|
|
faac8649e4 | ||
|
|
1e6a41186f | ||
|
|
eddee67130 | ||
|
|
9b834014e9 | ||
|
|
a467e79144 | ||
|
|
90cc785b55 | ||
|
|
09710425ab | ||
|
|
dd36727adf | ||
|
|
47b665a624 | ||
|
|
7011329761 | ||
|
|
ea24c23fde | ||
|
|
be8f2b8106 | ||
|
|
71db024a3b | ||
|
|
3790af6897 | ||
|
|
dc6ef63ddd | ||
|
|
ef762f7a2b | ||
|
|
4ed646e062 | ||
|
|
afbd843388 | ||
|
|
18c3598ef2 | ||
|
|
1c2623e609 | ||
|
|
e232863028 | ||
|
|
a89309153d | ||
|
|
fee7c8e573 | ||
|
|
1a52127c55 | ||
|
|
4920431c3a | ||
|
|
773f26b61d | ||
|
|
16e77d7329 | ||
|
|
2cec0908a5 | ||
|
|
b3ef62c188 | ||
|
|
3385f6fe67 | ||
|
|
0170bf636b | ||
|
|
b2561fd61e | ||
|
|
64b48945d4 | ||
|
|
bbfd463357 | ||
|
|
2179dab8ed | ||
|
|
801ead7f7f | ||
|
|
9e4834d58b | ||
|
|
f38e089de0 | ||
|
|
b252504306 | ||
|
|
81bcdaf48a | ||
|
|
44f654ff86 | ||
|
|
84cfa9382f | ||
|
|
c6da374aa9 | ||
|
|
d9fe0b2540 | ||
|
|
5a905dfe7f | ||
|
|
09b6d6e62b | ||
|
|
6aa26cf75f | ||
|
|
bae8915881 | ||
|
|
9c46de319e | ||
|
|
994eecadb7 | ||
|
|
f7f9ceec34 | ||
|
|
527255e3a1 | ||
|
|
0333da991d | ||
|
|
2bf8e1b1a3 | ||
|
|
ffc717717c | ||
|
|
2c8414db5d | ||
|
|
32473d7dc7 | ||
|
|
079f10eebd | ||
|
|
ae1f305a81 | ||
|
|
0b1f5209d5 | ||
|
|
fc9980903d | ||
|
|
ad87131926 | ||
|
|
4dfe1b664f | ||
|
|
d7b2ac97b5 | ||
|
|
fd9dac4ca4 | ||
|
|
8ca9348b07 | ||
|
|
5cc18fdb4b | ||
|
|
51b6b325b9 | ||
|
|
b3cc6f3802 | ||
|
|
1295094715 | ||
|
|
af85f58e3d | ||
|
|
3b34821c86 | ||
|
|
eaea0b16b5 | ||
|
|
964ffadc8e | ||
|
|
1b84064e5f | ||
|
|
dd128a99ac | ||
|
|
bc829a9712 | ||
|
|
3459c55ebf | ||
|
|
a631d950e1 | ||
|
|
f35e2d76ad | ||
|
|
8db838da9f | ||
|
|
7254a880dc | ||
|
|
a0df5b970b | ||
|
|
54ed08c24e | ||
|
|
97e1ecabdc | ||
|
|
e716f6d35e | ||
|
|
c7506496da | ||
|
|
1e3d3df9b8 | ||
|
|
ed75ad0d41 | ||
|
|
fd6159c0b4 | ||
|
|
976c322b83 | ||
|
|
28c1935800 | ||
|
|
1edf1524f8 | ||
|
|
0dfe101c0f | ||
|
|
a571821f73 | ||
|
|
f8e6f87984 | ||
|
|
c7338bca6f | ||
|
|
15f07f57bf | ||
|
|
ec989a45b5 | ||
|
|
e8286c50b2 | ||
|
|
bfbbe3db14 | ||
|
|
6214a87522 | ||
|
|
743ff719c8 | ||
|
|
b491eacb9c | ||
|
|
5bfc89e13f | ||
|
|
52d2b7b09e | ||
|
|
19e59d9575 | ||
|
|
6fe4baf1de | ||
|
|
bc21ec7551 | ||
|
|
aad4a1fd6a | ||
|
|
721595ef04 | ||
|
|
139838c717 | ||
|
|
c93238d8c9 | ||
|
|
5ee1fbe07d | ||
|
|
ee1287211d | ||
|
|
94e8be6b1c | ||
|
|
053ba7f082 | ||
|
|
e8f3f00792 | ||
|
|
740a89b77f | ||
|
|
1fe936dbbc | ||
|
|
11e85ac703 | ||
|
|
4cb99be8e6 | ||
|
|
8cf9fd3cdb | ||
|
|
b22950bb0a | ||
|
|
f87d62571b | ||
|
|
42afc56b65 | ||
|
|
10c3c7c41f | ||
|
|
e236539e3a | ||
|
|
1fc605cbc0 | ||
|
|
1e4963e727 | ||
|
|
95384d437a | ||
|
|
e44910212c | ||
|
|
36b8802f43 | ||
|
|
a6b0554edc | ||
|
|
c1df5eac57 | ||
|
|
68bf1be8d0 | ||
|
|
d5edea3c7f | ||
|
|
eb580f5ead | ||
|
|
c99588082b | ||
|
|
96d241e9f3 | ||
|
|
8f423c6d8f | ||
|
|
f25ecedcab | ||
|
|
2913c1aedc | ||
|
|
5a15f7e8ad | ||
|
|
e6cb64baa5 | ||
|
|
537023ef5a | ||
|
|
70125e0de5 | ||
|
|
8c5646b81b | ||
|
|
bd741365cb | ||
|
|
efd9843920 | ||
|
|
0bb8ec61e5 | ||
|
|
6a51096304 | ||
|
|
807c8f9005 | ||
|
|
9c21610215 | ||
|
|
97b4256ba4 | ||
|
|
e4b643fdaf | ||
|
|
0bda7b3679 | ||
|
|
51e3e9997c | ||
|
|
9f52d427e5 | ||
|
|
ad706f9924 | ||
|
|
57cf789c75 | ||
|
|
62174fdbcc | ||
|
|
dff7ddea39 | ||
|
|
0c3257b6b9 | ||
|
|
4bbf5c0bc9 | ||
|
|
b0820f6e13 | ||
|
|
b0f5d8dfdc | ||
|
|
67e0d3723a | ||
|
|
a36f4a4670 | ||
|
|
dab852c412 | ||
|
|
193ac6cd81 | ||
|
|
34ca8419bb | ||
|
|
6094aabe26 | ||
|
|
19648257f2 | ||
|
|
3363a9459b | ||
|
|
4d92f4871a | ||
|
|
f0d4ad16d2 | ||
|
|
2cc8402458 | ||
|
|
1c79320133 | ||
|
|
8b099d969f | ||
|
|
61157e8763 | ||
|
|
f97d078f81 | ||
|
|
a14cf81422 | ||
|
|
feeba9b5f5 | ||
|
|
b1b76ca250 | ||
|
|
e119ed0daa | ||
|
|
8c18486836 | ||
|
|
b6ed3951bc | ||
|
|
ea178ea689 | ||
|
|
973bbf3f8a | ||
|
|
3068846d8f | ||
|
|
194454715f | ||
|
|
ac02e053ee | ||
|
|
75db360929 | ||
|
|
f9141120aa | ||
|
|
d19f9e5b96 | ||
|
|
213a223861 | ||
|
|
ef9a24932f | ||
|
|
bf5b0070c1 | ||
|
|
2ab6f57159 | ||
|
|
77d46630d7 | ||
|
|
b78612aebd | ||
|
|
0f63d1bfa7 | ||
|
|
66a897cc80 | ||
|
|
3828511051 | ||
|
|
226a89c184 | ||
|
|
e3b4919c17 | ||
|
|
168bc58635 | ||
|
|
f4f0b8fd5f | ||
|
|
13c034c613 | ||
|
|
35f1658f4a | ||
|
|
328f22adab | ||
|
|
22cf67b407 | ||
|
|
0c7d51dd9d | ||
|
|
fa87ce72a8 | ||
|
|
ff2e5b3966 | ||
|
|
96c93fa252 | ||
|
|
e830b7edf7 | ||
|
|
9c874f6685 | ||
|
|
b8ab15bea8 | ||
|
|
6c7247f726 | ||
|
|
7674aa4e7c | ||
|
|
ebe97bf492 | ||
|
|
fbe4d036d8 | ||
|
|
c572b8bb1a | ||
|
|
59a5e23466 | ||
|
|
b9857f9499 | ||
|
|
b667e3a577 | ||
|
|
240b1475c7 | ||
|
|
3f24e5b571 | ||
|
|
fc7ec1d3f7 | ||
|
|
452ff02b15 | ||
|
|
c2cb43cc07 | ||
|
|
6736fd3835 | ||
|
|
a1bd69a9c2 | ||
|
|
561e63ee2d | ||
|
|
647b95631f | ||
|
|
aa518b2520 | ||
|
|
51c0f01539 | ||
|
|
cc9a19b303 | ||
|
|
2ee9844c2b | ||
|
|
4c53a74fa9 | ||
|
|
9b0ffdbf5a | ||
|
|
2777b89e45 | ||
|
|
7f90430414 | ||
|
|
ee4e56edf9 | ||
|
|
93c83d03c0 | ||
|
|
855b3de473 | ||
|
|
a9be05c885 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -10,3 +10,4 @@ kitty/khrplatform.h linguist-generated=true
|
||||
kitty/glfw-wrapper.* linguist-generated=true
|
||||
glfw/*.c linguist-vendored=true
|
||||
glfw/*.h linguist-vendored=true
|
||||
kittens/unicode_input/names.h linguist-generated=true
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
tags
|
||||
build
|
||||
linux-package
|
||||
kitty.app
|
||||
logo/*.iconset
|
||||
compile_commands.json
|
||||
glad/out
|
||||
|
||||
16
.travis.yml
16
.travis.yml
@@ -6,7 +6,7 @@ matrix:
|
||||
group: beta
|
||||
sudo: false
|
||||
env:
|
||||
- CC=gcc SANITIZE_ARG=--sanitize
|
||||
- CC=gcc CFLAGS=-funsigned-char SANITIZE_ARG=--sanitize
|
||||
language: python
|
||||
python: "3.5"
|
||||
addons:
|
||||
@@ -20,8 +20,6 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libunistring-dev
|
||||
- libxcb-xkb-dev
|
||||
- libxkbcommon-dev
|
||||
- libxkbcommon-x11-dev
|
||||
- libpng-dev
|
||||
- python3-pil
|
||||
|
||||
@@ -44,8 +42,6 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libunistring-dev
|
||||
- libxcb-xkb-dev
|
||||
- libxkbcommon-dev
|
||||
- libxkbcommon-x11-dev
|
||||
- libpng-dev
|
||||
- python3-pil
|
||||
|
||||
@@ -68,8 +64,6 @@ matrix:
|
||||
- libxcursor-dev
|
||||
- libunistring-dev
|
||||
- libxcb-xkb-dev
|
||||
- libxkbcommon-dev
|
||||
- libxkbcommon-x11-dev
|
||||
- libpng-dev
|
||||
- python3-pil
|
||||
- clang
|
||||
@@ -94,7 +88,7 @@ install: |
|
||||
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
|
||||
if [[ "$USE_BREW" == "1" ]]; then
|
||||
brew update;
|
||||
brew install python3;
|
||||
brew upgrade python;
|
||||
brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype;
|
||||
else
|
||||
mkdir -p $SW;
|
||||
@@ -102,6 +96,12 @@ install: |
|
||||
fi
|
||||
else
|
||||
pushd /tmp
|
||||
wget --no-check-certificate http://xkbcommon.org/download/libxkbcommon-0.6.1.tar.xz
|
||||
tar xf libxkbcommon-0.6.1.tar.xz
|
||||
cd libxkbcommon-0.6.1
|
||||
./configure --prefix=$HOME/harfbuzz --disable-dependency-tracking --disable-static
|
||||
make -j2
|
||||
make install
|
||||
wget https://github.com/behdad/harfbuzz/releases/download/1.6.3/harfbuzz-1.6.3.tar.bz2
|
||||
tar xf harfbuzz-1.6.3.tar.bz2
|
||||
cd harfbuzz-1.6.3
|
||||
|
||||
247
CHANGELOG.rst
247
CHANGELOG.rst
@@ -3,6 +3,253 @@ Changelog
|
||||
|
||||
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
version 0.9.1 [2018-05-05]
|
||||
------------------------------
|
||||
|
||||
- Show a bell symbol on the tab if a bell occurs in one of the windows in the tab and
|
||||
the window is not the currently focused window
|
||||
|
||||
- Change the window border color if a bell occurs in an unfocused window. Can
|
||||
be disabled by setting the bell_border_color to be the same as the
|
||||
inactive_border_color.
|
||||
|
||||
- macOS: Add support for dead keys
|
||||
|
||||
- Unicode input: When searching by name search for prefix matches as well as
|
||||
whole word matches
|
||||
|
||||
- Dynamically allocate the memory used for the scrollback history buffer.
|
||||
Reduces startup memory consumption when using very large scrollback
|
||||
buffer sizes.
|
||||
|
||||
- Add an option to not request window attention on bell.
|
||||
|
||||
- Remote control: Allow matching windows by number (visible position).
|
||||
|
||||
- macOS: Fix changing tab title and kitty shell not working
|
||||
|
||||
- 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.
|
||||
|
||||
- When calling pass_selection_to_program use the current directory of the child
|
||||
process as the cwd of the program.
|
||||
|
||||
- Add macos_hide_from_tasks option to hide kitty from the macOS task switcher
|
||||
|
||||
- macOS: When the macos_titlebar_color is set to background change the titlebar
|
||||
colors to match the current background color of the active kitty window
|
||||
|
||||
- Add a setting to clear all shortcuts defined up to that point in the config
|
||||
file(s)
|
||||
|
||||
- Add a setting (kitty_mod) to change the modifier used by all the default
|
||||
kitty shortcuts, globally
|
||||
|
||||
- Fix Shift+function key not working
|
||||
|
||||
- Support the F13 to F25 function keys
|
||||
|
||||
- Don't fail to start if the user deletes the hintstyle key from their
|
||||
fontconfig configuration.
|
||||
|
||||
- When rendering a private use unicode codepoint and a space as a two cell
|
||||
ligature, set the foreground colors of the space cell to match the colors of
|
||||
the first cell. Works around applications like powerline that use different
|
||||
colors for the two cells.
|
||||
|
||||
- Fix passing @text to other programs such as when viewing the scrollback
|
||||
buffer not working correctly if kitty is itself scrolled up.
|
||||
|
||||
- Fix window focus gained/lost events not being reported to child programs when
|
||||
switching windows/tabs using the various keyboard shortcuts.
|
||||
|
||||
- Fix tab title not changing to reflect the window title when switching between different windows in a tab
|
||||
|
||||
- Ignore -e if it is specified on the command line. This is for compatibility
|
||||
with broken software that assumes terminals should run with an -e option to
|
||||
execute commands instead of just passing the commands as arguments.
|
||||
|
||||
|
||||
version 0.9.0 [2018-04-15]
|
||||
------------------------------
|
||||
|
||||
- A new kitty command shell to allow controlling kitty via commands. Press
|
||||
`ctrl+shift+escape` to run the shell.
|
||||
|
||||
- The hints kitten has become much more powerful. Now in addition to URLs you
|
||||
can use it to select word, paths, filenames, lines, etc. from the screen.
|
||||
These can be inserted into the terminal, copied to clipboard or sent to
|
||||
external programs.
|
||||
|
||||
- Linux: Switch to libxkbcommon for keyboard handling. It allows kitty to
|
||||
support XCompose and dead keys and also react to keyboard remapping/layout
|
||||
change without needing a restart.
|
||||
|
||||
- Add support for multiple-key-sequence shortcuts
|
||||
|
||||
- A new remote control command `set-colors` to change the current and/or
|
||||
configured colors.
|
||||
|
||||
- When double-clicking to select a word, select words that continue onto the
|
||||
next/prev line as well.
|
||||
|
||||
- Add an `include` directive for the config files to read multiple config files
|
||||
|
||||
- Improve mouse selection for windows with padding. Moving the mouse into the
|
||||
padding area now acts as if the mouse is over the nearest cell.
|
||||
|
||||
- Allow setting all 256 terminal colors in the config file
|
||||
|
||||
- Fix using `kitty --single-instance` to open a new window in a running kitty
|
||||
instance, not respecting the `--directory` flag
|
||||
|
||||
- URL hints: Exclude trailing punctuation from URLs
|
||||
|
||||
- URL hints: Launch the browser from the kitty parent process rather than the
|
||||
hints kitten. Fixes launching on some systems where xdg-open doesn't like
|
||||
being run from a kitten.
|
||||
|
||||
- Allow using rectangle select mode by pressing shift in addition to the
|
||||
rectangle select modifiers even when the terminal program has grabbed the
|
||||
mouse.
|
||||
|
||||
|
||||
version 0.8.4 [2018-03-31]
|
||||
-----------------------------
|
||||
|
||||
- Fix presence of XDG_CONFIG_DIRS and absence of XDG_CONFIG_HOME preventing
|
||||
kitty from starting
|
||||
|
||||
- Revert change in last release to cell width calculation. Instead just clip
|
||||
the right edges of characters that overflow the cell by at most two pixels
|
||||
|
||||
|
||||
version 0.8.3 [2018-03-29]
|
||||
-----------------------------
|
||||
|
||||
- Fix a regression that broke the visual bell and invert screen colors escape
|
||||
code
|
||||
|
||||
- Allow double-click and triple-click + drag to extend selections word at a
|
||||
time or line at a time
|
||||
|
||||
- Add a keyboard shortcut to set the tab title
|
||||
|
||||
- Fix setting window title to empty via OSC escape code not working correctly
|
||||
|
||||
- Linux: Fix cell width calculation incorrect for some fonts (cell widths are
|
||||
now calculated by actually rendering bitmaps, which is slower but more
|
||||
accurate)
|
||||
|
||||
- Allow specifying a system wide kitty config file, for all users
|
||||
|
||||
- Add a --debug-config command line flag to output data about the system and
|
||||
kitty configuration.
|
||||
|
||||
- Wayland: Fix auto-repeat of keys not working
|
||||
|
||||
|
||||
version 0.8.2 [2018-03-17]
|
||||
-----------------------------
|
||||
|
||||
- Allow extending existing selections by right clicking
|
||||
|
||||
- Add a configurable keyboard shortcut and remote command to set the font size to a specific value
|
||||
|
||||
- Add an option to have kitty close the window when the main processes running in it exits, even if there are still background processes writing to that terminal
|
||||
|
||||
- Add configurable keyboard shortcuts to switch to a specific layout
|
||||
|
||||
- Add a keyboard shortcut to edit the kitty config file easily
|
||||
|
||||
- macOS: Fix restoring of window size not correct on Retina screens
|
||||
|
||||
- macOS: Add a facility to specify command line arguments when running kitty from the GUI
|
||||
|
||||
- Add a focus-tab remote command
|
||||
|
||||
- Fix screen not being refreshed immediately after moving a window.
|
||||
|
||||
- Fix a crash when getting the contents of the scrollback buffer as text
|
||||
|
||||
version 0.8.1 [2018-03-09]
|
||||
-----------------------------
|
||||
|
||||
- Extend kitty's remote control feature to work over both UNIX and TCP sockets,
|
||||
so now you can control kitty from across the internet, if you want to.
|
||||
|
||||
- Render private use unicode characters that are followed by a space as a two
|
||||
character ligature. This fixes rendering for applications that misuse
|
||||
private-use characters to display square symbols.
|
||||
|
||||
- Fix Unicode emoji presentation variant selector causing new a fallback font
|
||||
instance to be created
|
||||
|
||||
- Fix a rare error that prevented the Unicode input kitten from working
|
||||
sometimes
|
||||
|
||||
- Allow using Ctrl+Alt+letter in legacy keyboard modes by outputting them as Ctrl+letter and Alt+letter.
|
||||
This matches other terminals' behavior.
|
||||
|
||||
- Fix cursor position off-by-one on horizontal axis when resizing the terminal
|
||||
|
||||
- Wayland: Fix auto-repeat of keys not working
|
||||
|
||||
- Wayland: Add support for window decorations provided by the Wayland shell
|
||||
|
||||
- macOS: Fix URL hints not working
|
||||
|
||||
- macOS: Fix shell not starting in login mode on some computers
|
||||
|
||||
- macOS: Output errors into console.app when running as a bundle
|
||||
|
||||
|
||||
version 0.8.0 [2018-02-24]
|
||||
-----------------------------
|
||||
|
||||
- A framework for kittens, that is, small terminal programs designed to run
|
||||
inside kitty and extend its capabilities. Examples include unicode input and
|
||||
selecting URLs with the keyboard.
|
||||
|
||||
- Input arbitrary unicode characters by pressing Ctrl+Shift+u. You can choose
|
||||
characters by name, by hex code, by recently used, etc. There is even and
|
||||
editable Favorites list.
|
||||
|
||||
- Open URLs using only the keyboard. kitty has a new "hints mode". Press
|
||||
Ctrl+Shift+e and all detected URLs on the screen are highlighted with a key
|
||||
to press to open them. The facility is customizable so you can change
|
||||
what is detected as a URL and which program is used to open it.
|
||||
|
||||
- Add an option to change the titlebar color of kitty windows on macOS
|
||||
|
||||
- Only consider Emoji characters with default Emoji presentation to be two
|
||||
cells wide. This matches the standard. Also add support for the Unicode Emoji
|
||||
variation presentation selector.
|
||||
|
||||
- Prevent video tearing during high speed scrolling by syncing draws
|
||||
to the monitor's refresh rate. There is a new configuration option to
|
||||
control this ``sync_to_monitor``.
|
||||
|
||||
- When displaying only a single window, use the default background color of the
|
||||
window (which can be changed via escape codes) as the color for the margin
|
||||
and padding of the window.
|
||||
|
||||
- Add some non standard terminfo capabilities used by neovim and tmux.
|
||||
|
||||
- Fix large drop in performance when using multiple top-level windows on macOS
|
||||
|
||||
- Fix save/restore of window sizes not working correctly.
|
||||
|
||||
- Remove option to use system wcwidth(). Now always use a wcwidth() based on
|
||||
the Unicode standard. Only sane way.
|
||||
|
||||
- Fix a regression that caused a few ligature glyphs to not render correctly in
|
||||
rare circumstances.
|
||||
|
||||
- Browsing the scrollback buffer now happens in an overlay window instead of a
|
||||
new window/tab.
|
||||
|
||||
version 0.7.1 [2018-01-31]
|
||||
---------------------------
|
||||
|
||||
|
||||
25
CONTRIBUTING.md
Normal file
25
CONTRIBUTING.md
Normal file
@@ -0,0 +1,25 @@
|
||||
### Reporting bugs
|
||||
|
||||
Please first search existing bug reports (especially closed ones) for a report
|
||||
that matches your issue.
|
||||
|
||||
When reporting a bug, provide full details of your environment, that means, at
|
||||
a minimum, kitty version, OS and OS version, kitty config (ideally a minimal
|
||||
config to reproduce the issue with).
|
||||
|
||||
|
||||
### Contributing code
|
||||
|
||||
Install [the dependencies](https://github.com/kovidgoyal/kitty#dependencies)
|
||||
using your favorite package manager. Build and run kitty [from
|
||||
source](https://github.com/kovidgoyal/kitty#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
|
||||
request that gets rejected.
|
||||
|
||||
If the code you are submitting is reasonably easily testable, please contribute
|
||||
tests as well (see the `kitty_tests/` sub-directory for existing tests, which
|
||||
can be run with `./test.py`).
|
||||
|
||||
That's it.
|
||||
2
Makefile
2
Makefile
@@ -31,4 +31,4 @@ logo/kitty.iconset/icon_256x256.png: logo/kitty.svg logo/make.py
|
||||
rendered_logo: logo/kitty.iconset/icon_256x256.png
|
||||
|
||||
app: rendered_logo
|
||||
python3 setup.py app $(VVAL)
|
||||
python3 setup.py kitty.app $(VVAL)
|
||||
|
||||
255
README.asciidoc
255
README.asciidoc
@@ -6,11 +6,14 @@
|
||||
:sc_close_window: pass:quotes[`ctrl+shift+w`]
|
||||
:sc_copy_to_clipboard: pass:quotes[`ctrl+shift+c`]
|
||||
:sc_decrease_font_size: pass:quotes[`ctrl+shift+minus`]
|
||||
:sc_edit_config_file: pass:quotes[`ctrl+shift+f2`]
|
||||
:sc_eighth_window: pass:quotes[`ctrl+shift+8`]
|
||||
:sc_fifth_window: pass:quotes[`ctrl+shift+5`]
|
||||
:sc_first_window: pass:quotes[`ctrl+shift+1`]
|
||||
:sc_fourth_window: pass:quotes[`ctrl+shift+4`]
|
||||
:sc_increase_font_size: pass:quotes[`ctrl+shift+equal`]
|
||||
:sc_input_unicode_character: pass:quotes[`ctrl+shift+u`]
|
||||
:sc_kitty_shell_window: pass:quotes[`ctrl+shift+escape`]
|
||||
:sc_move_tab_backward: pass:quotes[`ctrl+shift+,`]
|
||||
:sc_move_tab_forward: pass:quotes[`ctrl+shift+.`]
|
||||
:sc_move_window_backward: pass:quotes[`ctrl+shift+b`]
|
||||
@@ -29,6 +32,11 @@
|
||||
:sc_previous_tab: pass:quotes[`ctrl+shift+left`]
|
||||
:sc_previous_window: pass:quotes[`ctrl+shift+[`]
|
||||
:sc_restore_font_size: pass:quotes[`ctrl+shift+backspace`]
|
||||
:sc_run_kitten_text_hints: pass:quotes[`ctrl+shift+e`]
|
||||
:sc_run_kitten_text_hints_type_line_program: pass:quotes[`ctrl+shift+p → l`]
|
||||
:sc_run_kitten_text_hints_type_path: pass:quotes[`ctrl+shift+p → shift+f`]
|
||||
:sc_run_kitten_text_hints_type_path_program: pass:quotes[`ctrl+shift+p → f`]
|
||||
:sc_run_kitten_text_hints_type_word_program: pass:quotes[`ctrl+shift+p → w`]
|
||||
:sc_scroll_end: pass:quotes[`ctrl+shift+end`]
|
||||
:sc_scroll_home: pass:quotes[`ctrl+shift+home`]
|
||||
:sc_scroll_line_down: pass:quotes[`ctrl+shift+down` or `ctrl+shift+j`]
|
||||
@@ -36,6 +44,7 @@
|
||||
:sc_scroll_page_down: pass:quotes[`ctrl+shift+page_down`]
|
||||
:sc_scroll_page_up: pass:quotes[`ctrl+shift+page_up`]
|
||||
:sc_second_window: pass:quotes[`ctrl+shift+2`]
|
||||
:sc_set_tab_title: pass:quotes[`ctrl+shift+alt+t`]
|
||||
:sc_seventh_window: pass:quotes[`ctrl+shift+7`]
|
||||
:sc_show_scrollback: pass:quotes[`ctrl+shift+h`]
|
||||
:sc_sixth_window: pass:quotes[`ctrl+shift+6`]
|
||||
@@ -51,29 +60,31 @@ image::https://travis-ci.org/kovidgoyal/kitty.svg?branch=master[Build status, li
|
||||
|
||||
== Major Features
|
||||
|
||||
* Uses OpenGL for rendering, offloads rendering to the GPU for lower system
|
||||
load and buttery smooth scrolling. Uses threaded rendering to minimize input
|
||||
* Uses OpenGL for rendering, offloads rendering to the GPU for link:#performance[lower system
|
||||
load] and buttery smooth scrolling. Uses threaded rendering to minimize input
|
||||
latency.
|
||||
|
||||
* Supports all modern terminal features: graphics (images), unicode,
|
||||
* Supports all modern terminal features: link:graphics-protocol.asciidoc[graphics (images)], unicode,
|
||||
true-color, OpenType ligatures, mouse protocol, focus tracking, bracketed paste and so on.
|
||||
|
||||
* Supports tiling multiple terminal windows side by side in different
|
||||
layouts without needing to use an extra program like tmux
|
||||
link:#layouts[layouts] without needing to use an extra program like tmux
|
||||
|
||||
* Can be controlled from scripts or the shell prompt, even over SSH.
|
||||
* Can be link:remote-control.asciidoc[controlled from scripts or the shell prompt], even over SSH.
|
||||
|
||||
* Supports startup sessions which allow you to specify the window/tab layout,
|
||||
* Has a framework for _kittens_, small terminal programs that can be used to extend kitty's functionality.
|
||||
For example, they are used for link:#unicode-input[Unicode input] and link:#url-hints[Hints].
|
||||
|
||||
* Supports link:#startup-sessions[startup sessions] which allow you to specify the window/tab layout,
|
||||
working directories and programs to run on startup.
|
||||
|
||||
* Cross-platform support: kitty currently works on Linux and macOS, but because
|
||||
it uses only OpenGL for rendering, it should be trivial to port to other
|
||||
platforms.
|
||||
|
||||
* Allows you to open the scrollback buffer and window contents in a separate
|
||||
* Allows you to open link:#the-scrollback-buffer[the scrollback buffer] in a separate
|
||||
window using arbitrary programs of your choice. This is useful for browsing
|
||||
the history comfortably in a pager or editor and also for doing things like
|
||||
opening URLs using only the keyboard.
|
||||
the history comfortably in a pager or editor.
|
||||
|
||||
|
||||
image::screenshot.png?raw=true[Screenshot, showing three programs in the "Tall" layout]
|
||||
@@ -97,7 +108,7 @@ the following dependencies are installed first.
|
||||
* ImageMagick (optional, needed to use the `kitty icat` tool to display images in the terminal)
|
||||
* gcc or clang (required only for building)
|
||||
* pkg-config (required only for building)
|
||||
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `xinerama` and `xkbcommon-x11`, if they are not already installed by your distro.
|
||||
* For building on Linux in addition to the above dependencies you might also need to install the `-dev` packages for `xcursor`, `xrandr`, `libxi`, `xinerama`, `libgl1-mesa` and `xkbcommon-x11`, if they are not already installed by your distro.
|
||||
|
||||
=== Install and run from source
|
||||
|
||||
@@ -105,7 +116,7 @@ the following dependencies are installed first.
|
||||
git clone https://github.com/kovidgoyal/kitty && cd kitty
|
||||
....
|
||||
|
||||
Now build the C parts of kitty with the following command:
|
||||
Now build the native code parts of kitty with the following command:
|
||||
|
||||
....
|
||||
make
|
||||
@@ -117,14 +128,31 @@ You can run kitty, as:
|
||||
python3 .
|
||||
....
|
||||
|
||||
If that works, you can create a script to launch kitty:
|
||||
|
||||
....
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import runpy
|
||||
runpy.run_path('/path/to/kitty/dir', run_name='__main__')
|
||||
....
|
||||
|
||||
And place it in `~/bin` or `/usr/bin` so that you can run kitty using
|
||||
just `kitty`.
|
||||
|
||||
=== Linux packages
|
||||
|
||||
* Arch Linux: https://www.archlinux.org/packages/community/x86_64/kitty/
|
||||
|
||||
* Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
|
||||
|
||||
* Arch Linux: AUR stable package https://aur.archlinux.org/packages/kitty/
|
||||
|
||||
* NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix
|
||||
|
||||
* Gentoo: https://packages.gentoo.org/packages/x11-terms/kitty
|
||||
|
||||
* openSUSE: https://build.opensuse.org/package/show/X11:terminals/kitty
|
||||
|
||||
* Debian: https://packages.qa.debian.org/k/kitty/news/20180419T070013Z.html
|
||||
|
||||
=== macOS packages
|
||||
|
||||
@@ -194,6 +222,7 @@ windows are:
|
||||
|Next layout | {sc_next_layout}
|
||||
|Move tab forward | {sc_move_tab_forward}
|
||||
|Move tab backward | {sc_move_tab_backward}
|
||||
|Set tab title | {sc_set_tab_title}
|
||||
|
||||
|===
|
||||
|
||||
@@ -226,7 +255,11 @@ windows are:
|
||||
|Decrease font size | {sc_decrease_font_size}
|
||||
|Restore font size | {sc_restore_font_size}
|
||||
|Toggle fullscreen | {sc_toggle_fullscreen}
|
||||
|Input unicode character | {sc_input_unicode_character}
|
||||
|Click URL using the keyboard | {sc_run_simple_kitten_text_url_hints}
|
||||
|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}
|
||||
|===
|
||||
|
||||
|
||||
@@ -234,35 +267,70 @@ windows are:
|
||||
|
||||
You can control kitty from the command line/scripts by sending it messages.
|
||||
You can tell kitty to open/close/rename tabs and windows. You can even send
|
||||
arbitrary input/text to any specified window. Messages can be sent using
|
||||
`kitty @`. Note that you must set `allow_remote_control yes` in your
|
||||
kitty.conf to use this feature. It even works over SSH connections. This
|
||||
feature is best illustrated with a link:remote-control.asciidoc[tutorial].
|
||||
arbitrary input/text to any specified window. Messages can be sent using `kitty
|
||||
@`. Note that you must set `allow_remote_control yes` in your
|
||||
link:kitty/kitty.conf[kitty.conf] to use this feature. It even works over SSH
|
||||
connections. This feature is best illustrated with a
|
||||
link:remote-control.asciidoc[tutorial].
|
||||
|
||||
|
||||
== The scrollback buffer
|
||||
|
||||
kitty supports scrolling back to view history, just like most terminals. You
|
||||
can use either the <<scrolling-shortcuts,keyboard shortcuts>> or the mouse
|
||||
scroll wheel to do so. However, kitty has an extra, neat feature. Sometimes you
|
||||
need to explore the scrollback buffer in more detail, maybe search for some
|
||||
scroll wheel to do so. However, kitty has an extra, neat feature. Sometimes
|
||||
you need to explore the scrollback buffer in more detail, maybe search for some
|
||||
text or refer to it side-by-side while typing in a follow-up command. kitty
|
||||
allows you to do this by pressing the {sc_show_scrollback} key-combination,
|
||||
which will open the scrollback buffer in a new window, using your favorite
|
||||
pager program (which is `less` by default). You can then explore the scrollback
|
||||
buffer using whatever program you normally use. Colors and text formatting are
|
||||
preserved.
|
||||
which will open the scrollback buffer in your favorite pager program (which is
|
||||
`less` by default). Colors and text formatting are preserved. You can explore
|
||||
the scrollback pager comfortably within the pager.
|
||||
|
||||
|
||||
== Unicode input
|
||||
|
||||
You can input unicode characters by name, hex code, recently used and even an editable favorites list.
|
||||
Press {sc_input_unicode_character} to start the unicode input widget, shown below.
|
||||
|
||||
image::unicode.png?raw=true[Unicode input widget, showing selection of unicode characters by name]
|
||||
|
||||
In Code mode, you enter a unicode character by typing in the hex code for the
|
||||
character and pressing enter, for example, type in 2716 and press enter to get
|
||||
✖. You can also choose a character from the list of recently used characters by
|
||||
typing a leading period and then the two character index and pressing Enter.
|
||||
|
||||
In 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
|
||||
keys.
|
||||
|
||||
== Hints
|
||||
|
||||
kitty has a _hints mode_ to select and act on arbitrary text snippets currently
|
||||
visible on the screen. For example, you can press {sc_run_kitten_text_hints}
|
||||
to choose any URL visible on the screen and then open it using your system
|
||||
browser.
|
||||
|
||||
image::hints_mode.png?raw=true[URL hints mode]
|
||||
|
||||
Similarly, you can press {sc_run_kitten_text_hints_type_path_program} to
|
||||
select anything that looks like a path or filename and then insert it into the
|
||||
terminal, very useful for picking files from the output of a git or ls command and
|
||||
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, use: `kitty +kitten hints --help`.
|
||||
|
||||
== Miscellaneous features
|
||||
|
||||
You can double click to select a word and triple click to select a line. You can also
|
||||
hold down `ctrl+shift` and click on a URL to open it in a browser.
|
||||
* You can also hold down `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
|
||||
|
||||
|
||||
== Layouts
|
||||
|
||||
Currently, there are three layouts available,
|
||||
Currently, there are five layouts available,
|
||||
|
||||
* Stack -- Only a single maximized window is shown at a time
|
||||
* Tall -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
@@ -270,7 +338,9 @@ Currently, there are three layouts available,
|
||||
* Horizontal -- All windows are shown side-by-side
|
||||
* Vertical -- All windows are shown one below the other
|
||||
|
||||
You can switch between layouts using the {sc_next_layout} key combination.
|
||||
You can switch between layouts using the {sc_next_layout} key combination. You can
|
||||
also create shortcuts to select particular layouts, and choose which layouts
|
||||
you want to enable/disable, see link:kitty/kitty.conf[kitty.conf] for examples.
|
||||
|
||||
Writing a new layout only requires about fifty lines of code, so if there is
|
||||
some layout you want, take a look at link:kitty/layout.py[layout.py] and submit
|
||||
@@ -279,13 +349,18 @@ a pull request!
|
||||
|
||||
== Configuration
|
||||
|
||||
kitty is highly customizable, everything from keyboard shortcuts, to
|
||||
painting frames-per-second. See the heavily commented
|
||||
link:kitty/kitty.conf[default config file]. By default kitty looks for a
|
||||
config file in the OS config directory (usually
|
||||
`~/.config/kitty/kitty.conf` on Linux and
|
||||
kitty is highly customizable, everything from keyboard shortcuts, to painting
|
||||
frames-per-second. See the heavily commented link:kitty/kitty.conf[default
|
||||
config file] for an overview of all customization possibilities.
|
||||
|
||||
By default kitty looks for a config file in the OS config directories (usually
|
||||
`~/.config/kitty/kitty.conf` and additionally
|
||||
`~/Library/Preferences/kitty/kitty.conf` on macOS) but you can pass a specific
|
||||
path via the `--config` option.
|
||||
path via the `--config` option or use the `KITTY_CONFIG_DIRECTORY` environment
|
||||
variable. See the help for the `--config` option in `kitty --help` for full
|
||||
details. You can also dump the current configuration using the `--debug-config`
|
||||
option.
|
||||
|
||||
|
||||
== Startup Sessions
|
||||
|
||||
@@ -348,6 +423,8 @@ 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
|
||||
render loop to reduce CPU usage. See the link:kitty/kitty.conf[config file] for details.
|
||||
See also the ``sync_to_monitor`` option to further decrease latency at the cost
|
||||
of some link:https://en.wikipedia.org/wiki/Screen_tearing[tearing] while scrolling.
|
||||
|
||||
You can generate detailed per-function performance data using
|
||||
link:https://github.com/gperftools/gperftools[gperftools]. Build kitty with
|
||||
@@ -400,6 +477,8 @@ installs the terminfo file and `kitty` that installs the main program.
|
||||
This allows users to install the terminfo file on servers into which they ssh,
|
||||
without needing to install all of kitty.
|
||||
|
||||
You also need `tic` to compile the terminfo files, it is usually found in the development package of `ncurses`
|
||||
|
||||
This applies to creating packages for kitty for macOS package managers such as
|
||||
brew or MacPorts as well.
|
||||
|
||||
@@ -408,31 +487,25 @@ brew or MacPorts as well.
|
||||
|
||||
=== Some special symbols are rendered small/truncated in kitty?
|
||||
|
||||
The number of cells a unicode character takes up are controlled by the
|
||||
`wcwidth()` system function. If wcwidth() returns 2 then kitty will render the
|
||||
character in two cells, otherwise it will render it in one cell. Often the
|
||||
system `wcwidth()` is old/outdated. You can use the `use_system_wcwidth=no`
|
||||
setting in your kitty.conf to workaround this. But note that it might cause
|
||||
other issues, since now kitty and the programs running inside it may not agree
|
||||
on how wide characters should be. When a symbol does not fit, it will either be
|
||||
rescaled to be smaller or truncated (depending on how much extra space it
|
||||
needs). This is often different from other terminals which just let the
|
||||
character overflow into neighboring cells, leading to ugly artifacts.
|
||||
The number of cells a unicode character takes up are controlled by the unicode
|
||||
standard. All characters are rendered in a single cell unless the unicode
|
||||
standard says they should be rendered in two cells. When a symbol does not fit,
|
||||
it will either be rescaled to be smaller or truncated (depending on how much
|
||||
extra space it needs). This is often different from other terminals which just
|
||||
let the character overflow into neighboring cells, which is fine if the
|
||||
neighboring cell is empty, but looks terrible if it is not.
|
||||
|
||||
In addition to the problem with `wcwidth()` above, some programs, like
|
||||
powerline, vim with fancy gutter symbols/status-bar, etc. use unicode
|
||||
characters from the private use area to represent symbols. Often these symbols
|
||||
are square and should be rendered in two cells. However, since private use
|
||||
area symbols all have `wcwdith() == 1` kitty renders them either smaller or
|
||||
truncated. The correct solution for this is to use either use different symbols
|
||||
that are not square, or to use a font that defines ligatures with the space
|
||||
character for these symbols. See
|
||||
link:https://github.com/kovidgoyal/kitty/issues/182[#182] for a discussion of
|
||||
the approach using ligatures.
|
||||
Some programs, like powerline, vim with fancy gutter symbols/status-bar, etc.
|
||||
misuse unicode characters from the private use area to represent symbols. Often
|
||||
these symbols are square and should be rendered in two cells. However, since
|
||||
private use area symbols all have their width set to one in the unicode
|
||||
standard, kitty renders them either smaller or truncated. The exception is if
|
||||
these characters are followed by a space or empty cell in which case kitty
|
||||
makes use of the extra cell to render them in two cells.
|
||||
|
||||
=== How do I build kitty.app on macOS?
|
||||
|
||||
Install `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
|
||||
Install `imagemagick`, `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
|
||||
|
||||
And run:
|
||||
```
|
||||
@@ -448,24 +521,68 @@ Note that the released kitty.dmg includes all dependencies, unlike the
|
||||
link:https://github.com/kovidgoyal/build-calibre[build-calibre] however, that
|
||||
is designed to run on Linux and is not for the faint of heart.
|
||||
|
||||
== A tribute
|
||||
|
||||
While over the decades I am sure many people have contributed to the
|
||||
development of the terminal emulator space, there is one individual in
|
||||
particular I would like to thank. link:http://invisible-island.net[Thomas E.
|
||||
Dickey], the creator of xterm. xterm is the most comprehensive and
|
||||
feature-rich terminal emulator I have had the pleasure of using. As I worked on
|
||||
kitty, I ran headlong into more and more gnarly corners of terminal behavior.
|
||||
On all those occasions, either the excellent documentation at
|
||||
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html[xterm-ctlseqs] or
|
||||
investigating the behavior and code of xterm or vttest were invaluable tools to
|
||||
aid my understanding.
|
||||
=== Using a color theme with a background color does not work well in vim?
|
||||
|
||||
My achievements, if any, in developing kitty would not have been possible without
|
||||
his prior work and the generous sharing of knowledge accumulated over decades.
|
||||
First make sure you have not changed the TERM environment variable, it should
|
||||
be `xterm-kitty`. vim uses *background color erase* even if the terminfo file
|
||||
does not contain the `bce` capability. This is a bug in vim. You can work around
|
||||
it by adding the following to your vimrc:
|
||||
|
||||
Thank you.
|
||||
....
|
||||
let &t_ut=''
|
||||
....
|
||||
|
||||
See link:https://github.com/kovidgoyal/kitty/blob/master/protocol-extensions.asciidoc#setting-text-styles-colors-in-arbitrary-regions-of-the-screen[here]
|
||||
for why kitty does not support background color erase.
|
||||
|
||||
=== I get errors about the terminal being unknown or opening the terminal failing when SSHing into a different computer?
|
||||
|
||||
This happens because the kitty terminfo files are not available on the server.
|
||||
Either install kitty on the server, or simply copy over the terminfo files,
|
||||
using:
|
||||
|
||||
....
|
||||
infocmp xterm-kitty | ssh myserver tic -x -o \~/.terminfo /dev/stdin
|
||||
....
|
||||
|
||||
Really, the correct solution for this is to convince the OpenSSH maintainers to
|
||||
have ssh do this automatically when connecting to a server, so that all
|
||||
terminals work transparently.
|
||||
|
||||
In the meantime, you can automate it by using a simple script called, for example,
|
||||
`~/bin/myssh`
|
||||
|
||||
....
|
||||
#!/bin/sh
|
||||
infocmp xterm-kitty | ssh $1 tic -x -o \~/.terminfo /dev/stdin && ssh $1
|
||||
....
|
||||
|
||||
Then you can use `myssh server` to log in to `server` with the terminfo file
|
||||
being automatically available.
|
||||
|
||||
|
||||
=== How do I change the colors in a running kitty instance?
|
||||
|
||||
You can either use the
|
||||
link:http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Operating-System-Commands[OSC
|
||||
terminal escape codes] to set colors or you can enable link:remote-control.asciidoc[remote control]
|
||||
for kitty and use `kitty @ set-colors --help`.
|
||||
|
||||
|
||||
=== How do I specify command line options for kitty on macOS?
|
||||
|
||||
Apple does not want you to use command line options with GUI applications. To
|
||||
workaround that limitation, kitty will read command line options from the file
|
||||
`<kitty config dir>/macos-launch-services-cmdline` when it is launched
|
||||
from the GUI, i.e. by clicking the kitty application icon or using `open -a kitty`.
|
||||
Note that this file is *only read* when running via the GUI.
|
||||
|
||||
You can, of course, also run kitty from a terminal with command line options, using:
|
||||
`/Applications/kitty.app/Contents/MacOS/kitty`.
|
||||
|
||||
And within kitty itself, you can always run kitty using just `kitty` as it
|
||||
cleverly adds itself to the PATH.
|
||||
|
||||
== Resources on terminal behavior
|
||||
|
||||
@@ -473,4 +590,4 @@ http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||
|
||||
https://en.wikipedia.org/wiki/C0_and_C1_control_codes
|
||||
|
||||
http://vt100.net/
|
||||
https://vt100.net/
|
||||
|
||||
21
__main__.py
21
__main__.py
@@ -20,6 +20,24 @@ def remote_control(args):
|
||||
main(args)
|
||||
|
||||
|
||||
def runpy(args):
|
||||
sys.argv = ['kitty'] + args[2:]
|
||||
exec(args[1])
|
||||
|
||||
|
||||
def launch(args):
|
||||
import runpy
|
||||
sys.argv = args[1:]
|
||||
runpy.run_path(args[1], run_name='__main__')
|
||||
|
||||
|
||||
def run_kitten(args):
|
||||
kitten = args[1]
|
||||
sys.argv = args[1:]
|
||||
from kittens.runner import run_kitten
|
||||
run_kitten(kitten)
|
||||
|
||||
|
||||
def namespaced(args):
|
||||
func = namespaced_entry_points[args[1]]
|
||||
func(args[1:])
|
||||
@@ -29,6 +47,9 @@ entry_points = {
|
||||
# These two are here for backwards compat
|
||||
'icat': icat,
|
||||
'list-fonts': list_fonts,
|
||||
'runpy': runpy,
|
||||
'launch': launch,
|
||||
'kitten': run_kitten,
|
||||
|
||||
'@': remote_control,
|
||||
'+': namespaced,
|
||||
|
||||
@@ -15,7 +15,7 @@ with open('terminfo/kitty.terminfo', 'w') as f:
|
||||
f.write(generate_terminfo())
|
||||
|
||||
os.environ['TERMINFO'] = os.path.join(base, 'terminfo')
|
||||
subprocess.check_call(['tic', 'terminfo/kitty.terminfo'])
|
||||
subprocess.check_call(['tic', '-x', 'terminfo/kitty.terminfo'])
|
||||
# On OS X tic puts the compiled database into a different directory
|
||||
try:
|
||||
os.mkdir('terminfo/78')
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
#!/bin/bash
|
||||
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty
|
||||
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h\nkittens/unicode_input/names.h') kitty kittens
|
||||
|
||||
221
gen-wcwidth.py
221
gen-wcwidth.py
@@ -4,16 +4,15 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from contextlib import contextmanager
|
||||
from datetime import date
|
||||
from functools import partial
|
||||
from html.entities import html5
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
from urllib.request import urlopen
|
||||
|
||||
# We ignore the first few emojis as they are widely assumed to be single width
|
||||
# in legacy applications
|
||||
FIRST_EMOJI = 0x2194
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
@@ -34,15 +33,35 @@ def get_data(fname, folder='UCD'):
|
||||
|
||||
# Map of class names to set of codepoints in class
|
||||
class_maps = {}
|
||||
name_map = {}
|
||||
word_search_map = defaultdict(set)
|
||||
marks = set()
|
||||
not_assigned = set(range(0, sys.maxunicode))
|
||||
|
||||
|
||||
def parse_ucd():
|
||||
|
||||
def add_word(w, c):
|
||||
if c <= 32 or c == 127 or 128 <= c <= 159:
|
||||
return
|
||||
if len(w) > 1:
|
||||
word_search_map[w.lower()].add(c)
|
||||
|
||||
first = None
|
||||
for word, c in html5.items():
|
||||
if len(c) == 1:
|
||||
add_word(word.rstrip(';'), ord(c))
|
||||
word_search_map['nnbsp'].add(0x202f)
|
||||
for line in get_data('ucd/UnicodeData.txt'):
|
||||
parts = [x.strip() for x in line.split(';')]
|
||||
codepoint = int(parts[0], 16)
|
||||
name = parts[1] or parts[10]
|
||||
if name == '<control>':
|
||||
name = parts[10]
|
||||
if name:
|
||||
name_map[codepoint] = name
|
||||
for word in name.lower().split():
|
||||
add_word(word, codepoint)
|
||||
category = parts[2]
|
||||
s = class_maps.setdefault(category, set())
|
||||
desc = parts[1]
|
||||
@@ -74,15 +93,22 @@ def split_two(line):
|
||||
|
||||
all_emoji = set()
|
||||
emoji_categories = {}
|
||||
emoji_presentation_bases = set()
|
||||
|
||||
|
||||
def parse_emoji():
|
||||
for line in get_data('emoji-data.txt', 'emoji'):
|
||||
chars, rest = split_two(line)
|
||||
if max(chars) >= FIRST_EMOJI:
|
||||
s = emoji_categories.setdefault(rest, set())
|
||||
s |= chars
|
||||
all_emoji.update(chars)
|
||||
s = emoji_categories.setdefault(rest, set())
|
||||
s.update(chars)
|
||||
all_emoji.update(chars)
|
||||
for line in get_data('emoji-variation-sequences.txt', 'emoji'):
|
||||
base, var, *rest = line.split()
|
||||
if base.startswith('#'):
|
||||
continue
|
||||
base = int(base, 16)
|
||||
if var.upper() == 'FE0F':
|
||||
emoji_presentation_bases.add(base)
|
||||
|
||||
|
||||
doublewidth, ambiguous = set(), set()
|
||||
@@ -125,19 +151,21 @@ def write_case(spec, p):
|
||||
|
||||
|
||||
@contextmanager
|
||||
def create_header(path):
|
||||
def create_header(path, include_data_types=True):
|
||||
f = open(path, 'w')
|
||||
p = partial(print, file=f)
|
||||
p('// unicode data, built from the unicode standard on:', date.today())
|
||||
p('// see gen-wcwidth.py')
|
||||
if path.endswith('.h'):
|
||||
p('#pragma once')
|
||||
p('#include "data-types.h"\n')
|
||||
p('START_ALLOW_CASE_RANGE')
|
||||
if include_data_types:
|
||||
p('#include "data-types.h"\n')
|
||||
p('START_ALLOW_CASE_RANGE')
|
||||
p()
|
||||
yield p
|
||||
p()
|
||||
p('END_ALLOW_CASE_RANGE')
|
||||
if include_data_types:
|
||||
p('END_ALLOW_CASE_RANGE')
|
||||
f.close()
|
||||
|
||||
|
||||
@@ -176,13 +204,48 @@ def category_test(name, p, classes, comment, static=False):
|
||||
p('\treturn false;\n}\n')
|
||||
|
||||
|
||||
def codepoint_to_mark_map(p, mark_map):
|
||||
p('\tswitch(c) { // {{{')
|
||||
rmap = {c: m for m, c in enumerate(mark_map)}
|
||||
for spec in get_ranges(mark_map):
|
||||
if isinstance(spec, tuple):
|
||||
s = rmap[spec[0]]
|
||||
cases = ' '.join(f'case {i}:' for i in range(spec[0], spec[1]+1))
|
||||
p(f'\t\t{cases} return {s} + c - {spec[0]};')
|
||||
else:
|
||||
p(f'\t\tcase {spec}: return {rmap[spec]};')
|
||||
p('default: return 0;')
|
||||
p('\t} // }}}')
|
||||
return rmap
|
||||
|
||||
|
||||
def classes_to_regex(classes):
|
||||
chars = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
|
||||
def as_string(codepoint):
|
||||
if codepoint < 256:
|
||||
return r'\x{:02x}'.format(codepoint)
|
||||
if codepoint <= 0xffff:
|
||||
return r'\u{:04x}'.format(codepoint)
|
||||
return r'\U{:08x}'.format(codepoint)
|
||||
|
||||
for spec in get_ranges(list(chars)):
|
||||
if isinstance(spec, tuple):
|
||||
yield '{}-{}'.format(*map(as_string, (spec[0], spec[1])))
|
||||
else:
|
||||
yield as_string(spec)
|
||||
|
||||
|
||||
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 (Cc Cf Cs)')
|
||||
category_test('is_word_char', p, {c for c in class_maps if c[0] in 'LN'}, 'L and N categories')
|
||||
category_test('is_CZ_category', p, {c for c in class_maps if c[0] in 'CZ'}, 'C and Z categories')
|
||||
category_test('is_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)')
|
||||
mark_map = [0] + list(sorted(marks))
|
||||
p('char_type codepoint_for_mark(combining_type m) {')
|
||||
@@ -191,17 +254,116 @@ def gen_ucd():
|
||||
p('\treturn 0;')
|
||||
p('}\n')
|
||||
p('combining_type mark_for_codepoint(char_type c) {')
|
||||
p('\tswitch(c) { // {{{')
|
||||
rmap = {c: m for m, c in enumerate(mark_map)}
|
||||
for spec in get_ranges(mark_map):
|
||||
if isinstance(spec, tuple):
|
||||
s = rmap[spec[0]]
|
||||
p(f'\t\tcase {spec[0]} ... {spec[1]}: return {s} + c - {spec[0]};')
|
||||
else:
|
||||
p(f'\t\tcase {spec}: return {rmap[spec]};')
|
||||
p('default: return 0;')
|
||||
p('\t} // }}}')
|
||||
rmap = codepoint_to_mark_map(p, mark_map)
|
||||
p('}\n')
|
||||
if rmap[0xfe0e] != 1275:
|
||||
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]
|
||||
))
|
||||
with open('kittens/hints/url_regex.py', 'w') as f:
|
||||
f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz))))
|
||||
|
||||
|
||||
def gen_names():
|
||||
with create_header('kittens/unicode_input/names.h') as p:
|
||||
mark_to_cp = list(sorted(name_map))
|
||||
cp_to_mark = {cp: m for m, cp in enumerate(mark_to_cp)}
|
||||
# Mapping of mark to codepoint name
|
||||
p(f'static const char* name_map[{len(mark_to_cp)}] = {{' ' // {{{')
|
||||
for cp in mark_to_cp:
|
||||
w = name_map[cp].replace('"', '\\"')
|
||||
p(f'\t"{w}",')
|
||||
p("}; // }}}\n")
|
||||
|
||||
# Mapping of mark to codepoint
|
||||
p(f'static const char_type mark_to_cp[{len(mark_to_cp)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, mark_to_cp)))
|
||||
p('}; // }}}\n')
|
||||
|
||||
# Function to get mark number for codepoint
|
||||
p('static char_type mark_for_codepoint(char_type c) {')
|
||||
codepoint_to_mark_map(p, mark_to_cp)
|
||||
p('}\n')
|
||||
p('static inline const char* name_for_codepoint(char_type cp) {')
|
||||
p('\tchar_type m = mark_for_codepoint(cp); if (m == 0) return NULL;')
|
||||
p('\treturn name_map[m];')
|
||||
p('}\n')
|
||||
|
||||
# Array of all words
|
||||
word_map = tuple(sorted(word_search_map))
|
||||
word_rmap = {w: i for i, w in enumerate(word_map)}
|
||||
p(f'static const char* all_words_map[{len(word_map)}] = {{' ' // {{{')
|
||||
cwords = (w.replace('"', '\\"') for w in word_map)
|
||||
p(', '.join(f'"{w}"' for w in cwords))
|
||||
p('}; // }}}\n')
|
||||
|
||||
# Array of sets of marks for each word
|
||||
word_to_marks = {word_rmap[w]: frozenset(map(cp_to_mark.__getitem__, cps)) for w, cps in word_search_map.items()}
|
||||
all_mark_groups = frozenset(word_to_marks.values())
|
||||
array = [0]
|
||||
mg_to_offset = {}
|
||||
for mg in all_mark_groups:
|
||||
mg_to_offset[mg] = len(array)
|
||||
array.append(len(mg))
|
||||
array.extend(sorted(mg))
|
||||
p(f'static const char_type mark_groups[{len(array)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, array)))
|
||||
p('}; // }}}\n')
|
||||
offsets_array = []
|
||||
for wi, w in enumerate(word_map):
|
||||
mg = word_to_marks[wi]
|
||||
offsets_array.append(mg_to_offset[mg])
|
||||
p(f'static const char_type mark_to_offset[{len(offsets_array)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, offsets_array)))
|
||||
p('}; // }}}\n')
|
||||
|
||||
# The trie
|
||||
p(f'typedef struct {{ uint32_t children_offset; uint32_t match_offset; }} word_trie;\n')
|
||||
all_trie_nodes = []
|
||||
|
||||
class TrieNode:
|
||||
|
||||
def __init__(self):
|
||||
self.match_offset = 0
|
||||
self.children_offset = 0
|
||||
self.children = {}
|
||||
|
||||
def add_letter(self, letter):
|
||||
if letter not in self.children:
|
||||
self.children[letter] = len(all_trie_nodes)
|
||||
all_trie_nodes.append(TrieNode())
|
||||
return self.children[letter]
|
||||
|
||||
def __str__(self):
|
||||
return f'{{ .children_offset={self.children_offset}, .match_offset={self.match_offset} }}'
|
||||
|
||||
root = TrieNode()
|
||||
all_trie_nodes.append(root)
|
||||
|
||||
def add_word(word_idx):
|
||||
word = word_map[word_idx]
|
||||
parent = root
|
||||
for letter in map(ord, word):
|
||||
idx = parent.add_letter(letter)
|
||||
parent = all_trie_nodes[idx]
|
||||
parent.match_offset = offsets_array[word_idx]
|
||||
|
||||
for i in range(len(word_map)):
|
||||
add_word(i)
|
||||
children_array = [0]
|
||||
for node in all_trie_nodes:
|
||||
if node.children:
|
||||
node.children_offset = len(children_array)
|
||||
children_array.append(len(node.children))
|
||||
for letter, child_offset in node.children.items():
|
||||
children_array.append((child_offset << 8) | (letter & 0xff))
|
||||
|
||||
p(f'static const word_trie all_trie_nodes[{len(all_trie_nodes)}] = {{' ' // {{{')
|
||||
p(',\n'.join(map(str, all_trie_nodes)))
|
||||
p('\n}; // }}}\n')
|
||||
p(f'static const uint32_t children_array[{len(children_array)}] = {{' ' // {{{')
|
||||
p(', '.join(map(str, children_array)))
|
||||
p('}; // }}}\n')
|
||||
|
||||
|
||||
def gen_wcwidth():
|
||||
@@ -221,12 +383,13 @@ def gen_wcwidth():
|
||||
p('\tswitch(code) {')
|
||||
|
||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||
add(p, 'Marks', marks | {0}, 0)
|
||||
add(p, 'Non-printing characters', non_printing, -1)
|
||||
add(p, 'Marks', marks, -1)
|
||||
add(p, 'Private use', class_maps['Co'], -3)
|
||||
add(p, 'Text Presentation', emoji_categories['Emoji'] - emoji_categories['Emoji_Presentation'], 1)
|
||||
add(p, 'East Asian ambiguous width', ambiguous, -2)
|
||||
add(p, 'East Asian double width', doublewidth, 2)
|
||||
add(p, 'Emoji', all_emoji, 2)
|
||||
add(p, 'Emoji Presentation', emoji_categories['Emoji_Presentation'], 2)
|
||||
|
||||
add(p, 'Not assigned in the unicode character database', not_assigned, -1)
|
||||
|
||||
@@ -234,6 +397,15 @@ def gen_wcwidth():
|
||||
p('\t}')
|
||||
p('\treturn 1;\n}')
|
||||
|
||||
p('static bool\nis_emoji_presentation_base(uint32_t code) {')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(emoji_presentation_bases)):
|
||||
write_case(spec, p)
|
||||
p('\t\t\treturn true;')
|
||||
p('\t\tdefault: return false;')
|
||||
p('\t}')
|
||||
p('\treturn 1;\n}')
|
||||
|
||||
|
||||
parse_ucd()
|
||||
parse_emoji()
|
||||
@@ -241,3 +413,4 @@ parse_eaw()
|
||||
gen_ucd()
|
||||
gen_wcwidth()
|
||||
gen_emoji()
|
||||
gen_names()
|
||||
|
||||
@@ -27,6 +27,10 @@ def regenerate():
|
||||
)
|
||||
|
||||
|
||||
def strip_trailing_whitespace(c):
|
||||
return re.sub(r'\s+$', '', c, flags=re.MULTILINE) + '\n'
|
||||
|
||||
|
||||
def export():
|
||||
c = open('out/src/glad.c', 'rb').read().decode('utf-8')
|
||||
functions = []
|
||||
@@ -48,12 +52,13 @@ init_glad(GLADloadproc load, int debug) {
|
||||
}
|
||||
return ret;
|
||||
}'''.replace('SUB', ';\n '.join(switch), 1)
|
||||
open('../kitty/gl-wrapper.c', 'w').write(c)
|
||||
open('../kitty/gl-wrapper.c', 'w').write(strip_trailing_whitespace(c))
|
||||
raw = open('out/include/glad/glad.h').read()
|
||||
raw = raw.replace('<KHR/khrplatform.h>', '"khrplatform.h"')
|
||||
raw += '\nint init_glad(GLADloadproc, int);\n'
|
||||
open('../kitty/gl-wrapper.h', 'w').write(raw)
|
||||
shutil.copy2('out/include/KHR/khrplatform.h', '../kitty')
|
||||
open('../kitty/gl-wrapper.h', 'w').write(strip_trailing_whitespace(raw))
|
||||
raw = open('out/include/KHR/khrplatform.h', 'rb').read().decode('utf-8')
|
||||
open('../kitty/khrplatform.h', 'w').write(strip_trailing_whitespace(raw))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -69,6 +69,7 @@ static void changeToResourcesDirectory(void)
|
||||
static void createKeyTables(void)
|
||||
{
|
||||
int scancode;
|
||||
_glfw.ns.debug_keyboard = getenv("GLFW_DEBUG_KEYBOARD") != NULL;
|
||||
|
||||
memset(_glfw.ns.keycodes, -1, sizeof(_glfw.ns.keycodes));
|
||||
memset(_glfw.ns.scancodes, -1, sizeof(_glfw.ns.scancodes));
|
||||
@@ -207,6 +208,9 @@ static GLFWbool updateUnicodeDataNS(void)
|
||||
_glfw.ns.unicodeData = nil;
|
||||
}
|
||||
|
||||
for (_GLFWwindow *window = _glfw.windowListHead; window; window = window->next)
|
||||
window->ns.deadKeyState = 0;
|
||||
|
||||
_glfw.ns.inputSource = TISCopyCurrentKeyboardLayoutInputSource();
|
||||
if (!_glfw.ns.inputSource)
|
||||
{
|
||||
@@ -371,4 +375,3 @@ const char* _glfwPlatformGetVersionString(void)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ void _glfwPollMonitorsNS(void)
|
||||
|
||||
// Change the current video mode
|
||||
//
|
||||
GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
{
|
||||
CFArrayRef modes;
|
||||
CFIndex count, i;
|
||||
@@ -299,7 +299,7 @@ GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
best = _glfwChooseVideoMode(monitor, desired);
|
||||
_glfwPlatformGetVideoMode(monitor, ¤t);
|
||||
if (_glfwCompareVideoModes(¤t, best) == 0)
|
||||
return GLFW_TRUE;
|
||||
return;
|
||||
|
||||
CVDisplayLinkCreateWithCGDisplay(monitor->ns.displayID, &link);
|
||||
|
||||
@@ -332,15 +332,6 @@ GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
|
||||
CFRelease(modes);
|
||||
CVDisplayLinkRelease(link);
|
||||
|
||||
if (!native)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Cocoa: Monitor mode list changed");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
// Restore the previously saved (original) video mode
|
||||
@@ -364,6 +355,10 @@ void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor)
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
{
|
||||
const CGRect bounds = CGDisplayBounds(monitor->ns.displayID);
|
||||
|
||||
10
glfw/cocoa_platform.h
vendored
10
glfw/cocoa_platform.h
vendored
@@ -37,6 +37,7 @@ typedef void* id;
|
||||
#endif
|
||||
|
||||
typedef VkFlags VkMacOSSurfaceCreateFlagsMVK;
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,int);
|
||||
|
||||
typedef struct VkMacOSSurfaceCreateInfoMVK
|
||||
{
|
||||
@@ -98,6 +99,10 @@ typedef struct _GLFWwindowNS
|
||||
// This is kept to counteract Cocoa doing the same internally
|
||||
double cursorWarpDeltaX, cursorWarpDeltaY;
|
||||
|
||||
// The text input filter callback
|
||||
GLFWcocoatextinputfilterfun textInputFilterCallback;
|
||||
// Dead key state
|
||||
UInt32 deadKeyState;
|
||||
} _GLFWwindowNS;
|
||||
|
||||
// Cocoa-specific global data
|
||||
@@ -114,6 +119,8 @@ typedef struct _GLFWlibraryNS
|
||||
id listener;
|
||||
|
||||
char keyName[64];
|
||||
char text[256];
|
||||
GLFWbool debug_keyboard;
|
||||
short int keycodes[256];
|
||||
short int scancodes[GLFW_KEY_LAST + 1];
|
||||
char* clipboardString;
|
||||
@@ -164,6 +171,5 @@ typedef struct _GLFWtimerNS
|
||||
void _glfwInitTimerNS(void);
|
||||
|
||||
void _glfwPollMonitorsNS(void);
|
||||
GLFWbool _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwSetVideoModeNS(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwRestoreVideoModeNS(_GLFWmonitor* monitor);
|
||||
|
||||
|
||||
@@ -128,6 +128,32 @@ static void updateCursorImage(_GLFWwindow* window)
|
||||
hideCursor(window);
|
||||
}
|
||||
|
||||
// Apply chosen cursor mode to a focused window
|
||||
//
|
||||
static void updateCursorMode(_GLFWwindow* window)
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.ns.restoreCursorPosX,
|
||||
&_glfw.ns.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
}
|
||||
else if (_glfw.ns.disabledCursorWindow == window)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = NULL;
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.ns.restoreCursorPosX,
|
||||
_glfw.ns.restoreCursorPosY);
|
||||
}
|
||||
|
||||
if (cursorInClientArea(window))
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
// Transforms the specified y-coordinate between the CG display and NS screen
|
||||
// coordinate systems
|
||||
//
|
||||
@@ -138,9 +164,9 @@ static float transformY(float y)
|
||||
|
||||
// Make the specified window and its video mode active on its monitor
|
||||
//
|
||||
static GLFWbool acquireMonitor(_GLFWwindow* window)
|
||||
static void acquireMonitor(_GLFWwindow* window)
|
||||
{
|
||||
const GLFWbool status = _glfwSetVideoModeNS(window->monitor, &window->videoMode);
|
||||
_glfwSetVideoModeNS(window->monitor, &window->videoMode);
|
||||
const CGRect bounds = CGDisplayBounds(window->monitor->ns.displayID);
|
||||
const NSRect frame = NSMakeRect(bounds.origin.x,
|
||||
transformY(bounds.origin.y + bounds.size.height),
|
||||
@@ -150,7 +176,6 @@ static GLFWbool acquireMonitor(_GLFWwindow* window)
|
||||
[window->ns.object setFrame:frame display:YES];
|
||||
|
||||
_glfwInputMonitorWindow(window->monitor, window);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Remove the window and restore the original video mode
|
||||
@@ -184,10 +209,112 @@ static int translateFlags(NSUInteger flags)
|
||||
return mods;
|
||||
}
|
||||
|
||||
#define debug_key(...) if (_glfw.ns.debug_keyboard) NSLog(__VA_ARGS__)
|
||||
|
||||
static inline const char*
|
||||
format_mods(int mods) {
|
||||
static char buf[128];
|
||||
char *p = buf, *s;
|
||||
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, x)
|
||||
pr("mods: ");
|
||||
s = p;
|
||||
if (mods & GLFW_MOD_CONTROL) pr("ctrl+");
|
||||
if (mods & GLFW_MOD_ALT) pr("alt+");
|
||||
if (mods & GLFW_MOD_SHIFT) pr("shift+");
|
||||
if (mods & GLFW_MOD_SUPER) pr("super+");
|
||||
if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+");
|
||||
if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+");
|
||||
if (p == s) pr("none");
|
||||
else p--;
|
||||
pr(" ");
|
||||
#undef pr
|
||||
return buf;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
format_text(const char *src) {
|
||||
static char buf[256];
|
||||
char *p = buf;
|
||||
if (!src[0]) return "<none>";
|
||||
while (*src) {
|
||||
p += snprintf(p, sizeof(buf) - (p - buf), "%x ", (unsigned char)*(src++));
|
||||
}
|
||||
if (p != buf) *(--p) = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static const char*
|
||||
safe_name_for_scancode(unsigned int scancode) {
|
||||
const char *ans = _glfwPlatformGetScancodeName(scancode);
|
||||
if (!ans) return "<noname>";
|
||||
if ((1 <= ans[0] && ans[0] <= 31) || ans[0] == 127) ans = "<cc>";
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
// Translates a macOS keycode to a GLFW keycode
|
||||
//
|
||||
static int translateKey(unsigned int key)
|
||||
static int translateKey(unsigned int key, GLFWbool apply_keymap)
|
||||
{
|
||||
if (apply_keymap) {
|
||||
// Look for the effective key name after applying any keyboard layouts/mappings
|
||||
const char *name = _glfwPlatformGetScancodeName(key);
|
||||
if (name && name[1] == 0) {
|
||||
// Single letter key name
|
||||
switch(name[0]) {
|
||||
#define K(ch, name) case ch: return GLFW_KEY_##name
|
||||
K('A', A); K('a', A);
|
||||
K('B', B); K('b', B);
|
||||
K('C', C); K('c', C);
|
||||
K('D', D); K('d', D);
|
||||
K('E', E); K('e', E);
|
||||
K('F', F); K('f', F);
|
||||
K('G', G); K('g', G);
|
||||
K('H', H); K('h', H);
|
||||
K('I', I); K('i', I);
|
||||
K('J', J); K('j', J);
|
||||
K('K', K); K('k', K);
|
||||
K('L', L); K('l', L);
|
||||
K('M', M); K('m', M);
|
||||
K('N', N); K('n', N);
|
||||
K('O', O); K('o', O);
|
||||
K('P', P); K('p', P);
|
||||
K('Q', Q); K('q', Q);
|
||||
K('R', R); K('r', R);
|
||||
K('S', S); K('s', S);
|
||||
K('T', T); K('t', T);
|
||||
K('U', U); K('u', U);
|
||||
K('V', V); K('v', V);
|
||||
K('W', W); K('w', W);
|
||||
K('X', X); K('x', X);
|
||||
K('Y', Y); K('y', Y);
|
||||
K('Z', Z); K('z', Z);
|
||||
K('0', 0);
|
||||
K('1', 1);
|
||||
K('2', 2);
|
||||
K('3', 3);
|
||||
K('5', 5);
|
||||
K('6', 6);
|
||||
K('7', 7);
|
||||
K('8', 8);
|
||||
K('9', 9);
|
||||
K('\'', APOSTROPHE);
|
||||
K(',', COMMA);
|
||||
K('.', PERIOD);
|
||||
K('/', SLASH);
|
||||
K('-', MINUS);
|
||||
K('=', EQUAL);
|
||||
K(';', SEMICOLON);
|
||||
K('[', LEFT_BRACKET);
|
||||
K(']', RIGHT_BRACKET);
|
||||
K('`', GRAVE_ACCENT);
|
||||
K('\\', BACKSLASH);
|
||||
#undef K
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (key >= sizeof(_glfw.ns.keycodes) / sizeof(_glfw.ns.keycodes[0]))
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
|
||||
@@ -322,7 +449,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
centerCursor(window);
|
||||
|
||||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||
_glfwPlatformSetCursorMode(window, window->cursorMode);
|
||||
updateCursorMode(window);
|
||||
}
|
||||
|
||||
- (void)windowDidResignKey:(NSNotification *)notification
|
||||
@@ -583,6 +710,9 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
window->ns.xscale = xscale;
|
||||
window->ns.yscale = yscale;
|
||||
_glfwInputWindowContentScale(window, xscale, yscale);
|
||||
|
||||
if (window->ns.layer)
|
||||
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,14 +745,99 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
[super updateTrackingAreas];
|
||||
}
|
||||
|
||||
static inline UInt32
|
||||
convert_cocoa_to_carbon_modifiers(NSUInteger flags) {
|
||||
UInt32 mods = 0;
|
||||
if (flags & NSEventModifierFlagShift)
|
||||
mods |= shiftKey;
|
||||
if (flags & NSEventModifierFlagControl)
|
||||
mods |= controlKey;
|
||||
if (flags & NSEventModifierFlagOption)
|
||||
mods |= optionKey;
|
||||
if (flags & NSEventModifierFlagCommand)
|
||||
mods |= cmdKey;
|
||||
if (flags & NSEventModifierFlagCapsLock)
|
||||
mods |= alphaLock;
|
||||
|
||||
return (mods >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
static inline void
|
||||
convert_utf16_to_utf8(UniChar *src, UniCharCount src_length, char *dest, size_t dest_sz) {
|
||||
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
|
||||
src,
|
||||
src_length,
|
||||
kCFAllocatorNull);
|
||||
CFStringGetCString(string,
|
||||
dest,
|
||||
dest_sz,
|
||||
kCFStringEncodingUTF8);
|
||||
CFRelease(string);
|
||||
}
|
||||
|
||||
static inline GLFWbool
|
||||
is_ascii_control_char(char x) {
|
||||
return x == 0 || (1 <= x && x <= 31) || x == 127;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)event
|
||||
{
|
||||
const int key = translateKey([event keyCode]);
|
||||
const int mods = translateFlags([event modifierFlags]);
|
||||
|
||||
_glfwInputKey(window, key, [event keyCode], GLFW_PRESS, mods);
|
||||
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
const unsigned int scancode = [event keyCode];
|
||||
const NSUInteger flags = [event modifierFlags];
|
||||
const int mods = translateFlags(flags);
|
||||
const int key = translateKey(scancode, GLFW_TRUE);
|
||||
const GLFWbool process_text = !window->ns.textInputFilterCallback || window->ns.textInputFilterCallback(key, mods, scancode) != 1;
|
||||
_glfw.ns.text[0] = 0;
|
||||
if (!_glfw.ns.unicodeData) {
|
||||
// Using the cocoa API for key handling is disabled, as there is no
|
||||
// reliable way to handle dead keys using it. Only use it if the
|
||||
// keyboard unicode data is not available.
|
||||
if (process_text) {
|
||||
// this will call insertText with the text for this event, if any
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
}
|
||||
} else {
|
||||
static UniChar text[256];
|
||||
UniCharCount char_count = 0;
|
||||
if (UCKeyTranslate(
|
||||
[(NSData*) _glfw.ns.unicodeData bytes],
|
||||
scancode,
|
||||
kUCKeyActionDown,
|
||||
convert_cocoa_to_carbon_modifiers(flags),
|
||||
LMGetKbdType(),
|
||||
(process_text ? 0 : kUCKeyTranslateNoDeadKeysMask),
|
||||
&(window->ns.deadKeyState),
|
||||
sizeof(text)/sizeof(text[0]),
|
||||
&char_count,
|
||||
text
|
||||
) != noErr) {
|
||||
debug_key(@"UCKeyTranslate failed for scancode: 0x%x (%s) %s\n", scancode, safe_name_for_scancode(scancode), format_mods(mods));
|
||||
window->ns.deadKeyState = 0;
|
||||
return;
|
||||
}
|
||||
if (process_text) {
|
||||
// We check if cocoa wants to insert text, as UCKeyTranslate
|
||||
// inserts text even when the cmd key is pressed. For instance,
|
||||
// cmd+a will result in the text a.
|
||||
[self interpretKeyEvents:[NSArray arrayWithObject:event]];
|
||||
debug_key(@"char_count: %lu cocoa text: %s\n", char_count, format_text(_glfw.ns.text));
|
||||
GLFWbool cocoa_wants_to_insert_text = !is_ascii_control_char(_glfw.ns.text[0]);
|
||||
_glfw.ns.text[0] = 0;
|
||||
if (char_count && cocoa_wants_to_insert_text) convert_utf16_to_utf8(text, char_count, _glfw.ns.text, sizeof(_glfw.ns.text));
|
||||
} else {
|
||||
window->ns.deadKeyState = 0;
|
||||
}
|
||||
if (window->ns.deadKeyState && char_count == 0) {
|
||||
debug_key(@"Ignoring dead key. deadKeyState: 0x%x scancode: 0x%x (%s) %s\n",
|
||||
window->ns.deadKeyState, scancode, safe_name_for_scancode(scancode), format_mods(mods));
|
||||
return;
|
||||
}
|
||||
}
|
||||
debug_key(@"scancode: 0x%x (%s) %stext: %s glfw_key: %s\n",
|
||||
scancode, safe_name_for_scancode(scancode), format_mods(mods),
|
||||
format_text(_glfw.ns.text), _glfwGetKeyName(key));
|
||||
if (is_ascii_control_char(_glfw.ns.text[0])) _glfw.ns.text[0] = 0; // dont send text for ascii control codes
|
||||
_glfwInputKeyboard(window, key, scancode, GLFW_PRESS, mods, _glfw.ns.text, 0);
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)event
|
||||
@@ -630,7 +845,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
int action;
|
||||
const unsigned int modifierFlags =
|
||||
[event modifierFlags] & NSEventModifierFlagDeviceIndependentFlagsMask;
|
||||
const int key = translateKey([event keyCode]);
|
||||
const int key = translateKey([event keyCode], GLFW_FALSE);
|
||||
const int mods = translateFlags(modifierFlags);
|
||||
const NSUInteger keyFlag = translateKeyToModifierFlag(key);
|
||||
|
||||
@@ -644,14 +859,14 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
else
|
||||
action = GLFW_RELEASE;
|
||||
|
||||
_glfwInputKey(window, key, [event keyCode], action, mods);
|
||||
_glfwInputKeyboard(window, key, [event keyCode], action, mods, "", 0);
|
||||
}
|
||||
|
||||
- (void)keyUp:(NSEvent *)event
|
||||
{
|
||||
const int key = translateKey([event keyCode]);
|
||||
const int key = translateKey([event keyCode], GLFW_TRUE);
|
||||
const int mods = translateFlags([event modifierFlags]);
|
||||
_glfwInputKey(window, key, [event keyCode], GLFW_RELEASE, mods);
|
||||
_glfwInputKeyboard(window, key, [event keyCode], GLFW_RELEASE, mods, "", 0);
|
||||
}
|
||||
|
||||
- (void)scrollWheel:(NSEvent *)event
|
||||
@@ -786,25 +1001,12 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
|
||||
{
|
||||
NSString* characters;
|
||||
NSEvent* event = [NSApp currentEvent];
|
||||
const int mods = translateFlags([event modifierFlags]);
|
||||
const int plain = !(mods & GLFW_MOD_SUPER);
|
||||
|
||||
if ([string isKindOfClass:[NSAttributedString class]])
|
||||
characters = [string string];
|
||||
else
|
||||
characters = (NSString*) string;
|
||||
|
||||
NSUInteger i, length = [characters length];
|
||||
|
||||
for (i = 0; i < length; i++)
|
||||
{
|
||||
const unichar codepoint = [characters characterAtIndex:i];
|
||||
if ((codepoint & 0xff00) == 0xf700)
|
||||
continue;
|
||||
|
||||
_glfwInputChar(window, codepoint, mods, plain);
|
||||
}
|
||||
snprintf(_glfw.ns.text, sizeof(_glfw.ns.text), "%s", [characters UTF8String]);
|
||||
_glfw.ns.text[sizeof(_glfw.ns.text) - 1] = 0;
|
||||
}
|
||||
|
||||
- (void)doCommandBySelector:(SEL)selector
|
||||
@@ -1137,6 +1339,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
window->ns.deadKeyState = 0;
|
||||
if (!initializeAppKit())
|
||||
return GLFW_FALSE;
|
||||
|
||||
@@ -1172,11 +1375,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
{
|
||||
_glfwPlatformShowWindow(window);
|
||||
_glfwPlatformFocusWindow(window);
|
||||
if (!acquireMonitor(window))
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (wndconfig->centerCursor)
|
||||
centerCursor(window);
|
||||
acquireMonitor(window);
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
@@ -1363,7 +1562,7 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
[NSApp requestUserAttention:NSInformationalRequest];
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window)
|
||||
{
|
||||
NSBeep();
|
||||
return GLFW_TRUE;
|
||||
@@ -1418,6 +1617,7 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
|
||||
const NSUInteger styleMask = getStyleMask(window);
|
||||
[window->ns.object setStyleMask:styleMask];
|
||||
// HACK: Changing the style mask can cause the first responder to be cleared
|
||||
[window->ns.object makeFirstResponder:window->ns.view];
|
||||
|
||||
if (monitor)
|
||||
@@ -1538,6 +1738,9 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
if (!initializeAppKit())
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
@@ -1636,26 +1839,8 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, double x, double y)
|
||||
|
||||
void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
{
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.ns.restoreCursorPosX,
|
||||
&_glfw.ns.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
}
|
||||
else if (_glfw.ns.disabledCursorWindow == window)
|
||||
{
|
||||
_glfw.ns.disabledCursorWindow = NULL;
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.ns.restoreCursorPosX,
|
||||
_glfw.ns.restoreCursorPosY);
|
||||
}
|
||||
|
||||
if (cursorInClientArea(window))
|
||||
updateCursorImage(window);
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
updateCursorMode(window);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
@@ -1681,16 +1866,7 @@ const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
if (!characterCount)
|
||||
return NULL;
|
||||
|
||||
CFStringRef string = CFStringCreateWithCharactersNoCopy(kCFAllocatorDefault,
|
||||
characters,
|
||||
characterCount,
|
||||
kCFAllocatorNull);
|
||||
CFStringGetCString(string,
|
||||
_glfw.ns.keyName,
|
||||
sizeof(_glfw.ns.keyName),
|
||||
kCFStringEncodingUTF8);
|
||||
CFRelease(string);
|
||||
|
||||
convert_utf16_to_utf8(characters, characterCount, _glfw.ns.keyName, sizeof(_glfw.ns.keyName));
|
||||
return _glfw.ns.keyName;
|
||||
}
|
||||
|
||||
@@ -1872,6 +2048,7 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
[window->ns.layer setContentsScale:[window->ns.object backingScaleFactor]];
|
||||
[window->ns.view setWantsLayer:YES];
|
||||
|
||||
memset(&sci, 0, sizeof(sci));
|
||||
@@ -1904,3 +2081,10 @@ GLFWAPI id glfwGetCocoaWindow(GLFWwindow* handle)
|
||||
return window->ns.object;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow *handle, GLFWcocoatextinputfilterfun callback) {
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(nil);
|
||||
GLFWcocoatextinputfilterfun previous = window->ns.textInputFilterCallback;
|
||||
window->ns.textInputFilterCallback = callback;
|
||||
return previous;
|
||||
}
|
||||
|
||||
27
glfw/context.c
vendored
27
glfw/context.c
vendored
@@ -46,6 +46,16 @@
|
||||
//
|
||||
GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
|
||||
{
|
||||
if (ctxconfig->share)
|
||||
{
|
||||
if (ctxconfig->client == GLFW_NO_API ||
|
||||
ctxconfig->share->context.client == GLFW_NO_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctxconfig->source != GLFW_NATIVE_CONTEXT_API &&
|
||||
ctxconfig->source != GLFW_EGL_CONTEXT_API &&
|
||||
ctxconfig->source != GLFW_OSMESA_CONTEXT_API)
|
||||
@@ -607,7 +617,8 @@ GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle)
|
||||
|
||||
if (window && window->context.client == GLFW_NO_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT,
|
||||
"Cannot make current with a window that has no OpenGL or OpenGL ES context");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -636,7 +647,8 @@ GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
|
||||
|
||||
if (window->context.client == GLFW_NO_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT,
|
||||
"Cannot swap buffers of a window that has no OpenGL or OpenGL ES context");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -652,7 +664,8 @@ GLFWAPI void glfwSwapInterval(int interval)
|
||||
window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
if (!window)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL);
|
||||
_glfwInputError(GLFW_NO_CURRENT_CONTEXT,
|
||||
"Cannot set swap interval without a current OpenGL or OpenGL ES context");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -669,13 +682,14 @@ GLFWAPI int glfwExtensionSupported(const char* extension)
|
||||
window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
if (!window)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL);
|
||||
_glfwInputError(GLFW_NO_CURRENT_CONTEXT,
|
||||
"Cannot query extension without a current OpenGL or OpenGL ES context");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (*extension == '\0')
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_VALUE, "Extension name is empty string");
|
||||
_glfwInputError(GLFW_INVALID_VALUE, "Extension name cannot be an empty string");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -734,7 +748,8 @@ GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname)
|
||||
window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
if (!window)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL);
|
||||
_glfwInputError(GLFW_NO_CURRENT_CONTEXT,
|
||||
"Cannot query entry point without a current OpenGL or OpenGL ES context");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
22
glfw/glfw.py
22
glfw/glfw.py
@@ -10,6 +10,10 @@ import sys
|
||||
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
is_freebsd = 'freebsd' in _plat
|
||||
is_netbsd = 'netbsd' in _plat
|
||||
is_dragonflybsd = 'dragonfly' in _plat
|
||||
is_bsd = is_freebsd or is_netbsd or is_dragonflybsd
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
@@ -42,8 +46,11 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
ans.sources = sinfo['common']['sources'] + sinfo[module]['sources']
|
||||
ans.all_headers = [x for x in os.listdir(base) if x.endswith('.h')]
|
||||
|
||||
if module in ('x11', 'wayland'):
|
||||
at_least_version('xkbcommon', 0, 5)
|
||||
|
||||
if module == 'x11':
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11'.split():
|
||||
for dep in 'x11 xrandr xinerama xcursor xkbcommon xkbcommon-x11 x11-xcb'.split():
|
||||
ans.cflags.extend(pkg_config(dep, '--cflags-only-I'))
|
||||
ans.ldpaths.extend(pkg_config(dep, '--libs'))
|
||||
|
||||
@@ -94,19 +101,20 @@ def collect_source_information():
|
||||
).group(1).strip().split()
|
||||
)
|
||||
|
||||
wayland_protocols = re.search('WaylandProtocols\s+(\S+)\s+', mraw).group(1)
|
||||
wayland_protocols = re.search(r'WaylandProtocols\s+(\S+)\s+', mraw).group(1)
|
||||
wayland_protocols = list(map(int, wayland_protocols.split('.')))
|
||||
ans = {
|
||||
'common': dict(extract_sources('common')),
|
||||
'wayland_protocols': wayland_protocols,
|
||||
}
|
||||
joystick = 'null' if is_bsd else 'linux'
|
||||
for group in 'cocoa win32 x11 wayland osmesa'.split():
|
||||
m = re.search('_GLFW_' + group.upper(), raw)
|
||||
ans[group] = dict(extract_sources('glfw', m.start()))
|
||||
if group == 'x11':
|
||||
ans[group]['headers'].append('linux_joystick.h')
|
||||
ans[group]['sources'].append('linux_joystick.c')
|
||||
elif group == 'wayland':
|
||||
if group in ('x11', 'wayland'):
|
||||
ans[group]['headers'].append('{}_joystick.h'.format(joystick))
|
||||
ans[group]['sources'].append('{}_joystick.c'.format(joystick))
|
||||
if group == 'wayland':
|
||||
ans[group]['protocols'] = p = []
|
||||
for m in re.finditer(r'WAYLAND_PROTOCOLS_PKGDATADIR\}/(.+?)"?$', raw, flags=re.M):
|
||||
p.append(m.group(1))
|
||||
@@ -188,6 +196,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
for line in '''\
|
||||
void* glfwGetCocoaWindow(GLFWwindow* window)
|
||||
uint32_t glfwGetCocoaMonitor(GLFWmonitor* monitor)
|
||||
GLFWcocoatextinputfilterfun glfwSetCocoaTextInputFilter(GLFWwindow* window, GLFWcocoatextinputfilterfun callback)
|
||||
void* glfwGetX11Display(void)
|
||||
int32_t glfwGetX11Window(GLFWwindow* window)
|
||||
void glfwSetX11SelectionString(const char* string)
|
||||
@@ -204,6 +213,7 @@ def generate_wrappers(glfw_header, glfw_native_header):
|
||||
#pragma once
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
typedef int (* GLFWcocoatextinputfilterfun)(int,int,unsigned int);
|
||||
{}
|
||||
|
||||
{}
|
||||
|
||||
219
glfw/glfw3.h
vendored
219
glfw/glfw3.h
vendored
@@ -1375,9 +1375,15 @@ typedef void (* GLFWcursorenterfun)(GLFWwindow*,int);
|
||||
*/
|
||||
typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
|
||||
/*! @brief The function signature for keyboard key callbacks.
|
||||
/*! @brief The function signature for key callbacks.
|
||||
*
|
||||
* This is the function signature for keyboard key callback functions.
|
||||
* This is the function signature for key callback functions.
|
||||
* The semantics of this function are that the key that is interacted with on the
|
||||
* keyboard is reported, and the text, if any generated by the key is reported.
|
||||
* So, for example, if on a US-ASCII keyboard the user presses Shift+= GLFW
|
||||
* will report the text "+" and the key as GLFW_KEY_EQUAL. The reported key takes into
|
||||
* account any current keyboard maps defined in the OS. So with a dvorak mapping, pressing
|
||||
* the "s" key will generate text "o" and GLFW_KEY_O.
|
||||
*
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] key The [keyboard key](@ref keys) that was pressed or released.
|
||||
@@ -1385,56 +1391,22 @@ typedef void (* GLFWscrollfun)(GLFWwindow*,double,double);
|
||||
* @param[in] action `GLFW_PRESS`, `GLFW_RELEASE` or `GLFW_REPEAT`.
|
||||
* @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.
|
||||
*
|
||||
* @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
|
||||
* than the unshifted key. So for example, if ISO_Shift_Level_5 is used to
|
||||
* convert the key A into UP GLFW will report the key as UP with no modifiers.
|
||||
*
|
||||
* @sa @ref input_key
|
||||
* @sa @ref glfwSetKeyCallback
|
||||
* @sa @ref glfwSetKeyboardCallback
|
||||
*
|
||||
* @since Added in version 1.0.
|
||||
* @glfw3 Added window handle, scancode and modifier mask parameters.
|
||||
* @since Added in version 4.0.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWkeyfun)(GLFWwindow*,int,int,int,int);
|
||||
|
||||
/*! @brief The function signature for Unicode character callbacks.
|
||||
*
|
||||
* This is the function signature for Unicode character callback functions.
|
||||
*
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] codepoint The Unicode code point of the character.
|
||||
*
|
||||
* @sa @ref input_char
|
||||
* @sa @ref glfwSetCharCallback
|
||||
*
|
||||
* @since Added in version 2.4.
|
||||
* @glfw3 Added window handle parameter.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int);
|
||||
|
||||
/*! @brief The function signature for Unicode character with modifiers
|
||||
* callbacks.
|
||||
*
|
||||
* This is the function signature for Unicode character with modifiers callback
|
||||
* functions. It is called for each input character, regardless of what
|
||||
* modifier keys are held down.
|
||||
*
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] codepoint The Unicode code point of the character.
|
||||
* @param[in] mods Bit field describing which [modifier keys](@ref mods) were
|
||||
* held down.
|
||||
*
|
||||
* @sa @ref input_char
|
||||
* @sa @ref glfwSetCharModsCallback
|
||||
*
|
||||
* @deprecated Scheduled for removal in version 4.0.
|
||||
*
|
||||
* @since Added in version 3.1.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int);
|
||||
typedef void (* GLFWkeyboardfun)(GLFWwindow*, int, int, int, int, const char*, int);
|
||||
|
||||
/*! @brief The function signature for file drop callbacks.
|
||||
*
|
||||
@@ -1458,7 +1430,8 @@ typedef void (* GLFWdropfun)(GLFWwindow*,int,const char**);
|
||||
* This is the function signature for monitor configuration callback functions.
|
||||
*
|
||||
* @param[in] monitor The monitor that was connected or disconnected.
|
||||
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`.
|
||||
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
|
||||
* values reserved for future use.
|
||||
*
|
||||
* @sa @ref monitor_event
|
||||
* @sa @ref glfwSetMonitorCallback
|
||||
@@ -1475,7 +1448,8 @@ typedef void (* GLFWmonitorfun)(GLFWmonitor*,int);
|
||||
* functions.
|
||||
*
|
||||
* @param[in] jid The joystick that was connected or disconnected.
|
||||
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`.
|
||||
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`. Remaining
|
||||
* values reserved for future use.
|
||||
*
|
||||
* @sa @ref joystick_event
|
||||
* @sa @ref glfwSetJoystickCallback
|
||||
@@ -2451,10 +2425,11 @@ GLFWAPI void glfwWindowHintString(int hint, const char* value);
|
||||
* GLFW_X11_CLASS_NAME and @ref GLFW_X11_INSTANCE_NAME window hints to override
|
||||
* this.
|
||||
*
|
||||
* @remark @wayland The window frame is currently unimplemented, as if
|
||||
* [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`.
|
||||
* A compositor can still emit close, resize or maximize events, using for
|
||||
* example a keybind mechanism.
|
||||
* @remark @wayland The window frame is currently very simple, only allowing
|
||||
* window resize or move. A compositor can still emit close, maximize or
|
||||
* fullscreen events, using for example a keybind mechanism. Additionally,
|
||||
* the wp_viewporter protocol is required for this feature, otherwise the
|
||||
* window will not be decorated.
|
||||
*
|
||||
* @remark @wayland A full screen window will not attempt to change the mode,
|
||||
* no matter what the requested size or refresh rate.
|
||||
@@ -2896,10 +2871,6 @@ GLFWAPI void glfwGetFramebufferSize(GLFWwindow* window, int* width, int* height)
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark @wayland The window frame is currently unimplemented, as if
|
||||
* [GLFW_DECORATED](@ref GLFW_DECORATED_hint) was always set to `GLFW_FALSE`,
|
||||
* so the returned values will always be zero.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_size
|
||||
@@ -3189,13 +3160,9 @@ GLFWAPI void glfwRequestWindowAttention(GLFWwindow* window);
|
||||
/*! @brief Sounds an audible bell associated with the window
|
||||
*
|
||||
* This function sounds an audible bell, on platforms where it is
|
||||
* supported. Currently (macOS, Windows and X11).
|
||||
* supported. Currently (macOS, Windows, X11 and Wayland).
|
||||
*
|
||||
* @param[in] window The window with which the bell is associated.
|
||||
* @param[in] param The meaning of this parameter is platform dependent. On
|
||||
* X11 it corresponds to the percentage controlling bell volume (see man
|
||||
* XBell). On Windows it is the type of sound to make, see the MSDN docs for
|
||||
* MessageBeep. On macOS, it is ignored.
|
||||
* @return GLFW_TRUE if the bell succeeded otherwise GLFW_FALSE
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
@@ -3210,7 +3177,7 @@ GLFWAPI void glfwRequestWindowAttention(GLFWwindow* window);
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
GLFWAPI int glfwWindowBell(GLFWwindow* window, int64_t param);
|
||||
GLFWAPI int glfwWindowBell(GLFWwindow* window);
|
||||
|
||||
|
||||
/*! @brief Returns the monitor that the window uses for full screen mode.
|
||||
@@ -4237,121 +4204,11 @@ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor);
|
||||
*/
|
||||
GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);
|
||||
|
||||
/*! @brief Sets the key callback.
|
||||
*
|
||||
* This function sets the key callback of the specified window, which is called
|
||||
* when a key is pressed, repeated or released.
|
||||
*
|
||||
* The key functions deal with physical keys, with layout independent
|
||||
* [key tokens](@ref keys) named after their values in the standard US keyboard
|
||||
* layout. If you want to input text, use the
|
||||
* [character callback](@ref glfwSetCharCallback) instead.
|
||||
*
|
||||
* When a window loses input focus, it will generate synthetic key release
|
||||
* events for all pressed keys. You can tell these events from user-generated
|
||||
* events by the fact that the synthetic ones are generated after the focus
|
||||
* loss event has been processed, i.e. after the
|
||||
* [window focus callback](@ref glfwSetWindowFocusCallback) has been called.
|
||||
*
|
||||
* The scancode of a key is specific to that platform or sometimes even to that
|
||||
* machine. Scancodes are intended to allow users to bind keys that don't have
|
||||
* a GLFW key token. Such keys have `key` set to `GLFW_KEY_UNKNOWN`, their
|
||||
* state is not saved and so it cannot be queried with @ref glfwGetKey.
|
||||
*
|
||||
* Sometimes GLFW needs to generate synthetic key events, in which case the
|
||||
* scancode may be zero.
|
||||
*
|
||||
* @param[in] window The window whose callback to set.
|
||||
* @param[in] cbfun The new key callback, or `NULL` to remove the currently
|
||||
* set callback.
|
||||
* @return The previously set callback, or `NULL` if no callback was set or the
|
||||
* library had not been [initialized](@ref intro_init).
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref input_key
|
||||
*
|
||||
* @since Added in version 1.0.
|
||||
* @glfw3 Added window handle parameter and return value.
|
||||
/*! @brief Sets the callback for handling keyboard events.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* window, GLFWkeyfun cbfun);
|
||||
|
||||
/*! @brief Sets the Unicode character callback.
|
||||
*
|
||||
* This function sets the character callback of the specified window, which is
|
||||
* called when a Unicode character is input.
|
||||
*
|
||||
* The character callback is intended for Unicode text input. As it deals with
|
||||
* characters, it is keyboard layout dependent, whereas the
|
||||
* [key callback](@ref glfwSetKeyCallback) is not. Characters do not map 1:1
|
||||
* to physical keys, as a key may produce zero, one or more characters. If you
|
||||
* want to know whether a specific physical key was pressed or released, see
|
||||
* the key callback instead.
|
||||
*
|
||||
* The character callback behaves as system text input normally does and will
|
||||
* not be called if modifier keys are held down that would prevent normal text
|
||||
* input on that platform, for example a Super (Command) key on macOS or Alt key
|
||||
* on Windows. There is a
|
||||
* [character with modifiers callback](@ref glfwSetCharModsCallback) that
|
||||
* receives these events.
|
||||
*
|
||||
* @param[in] window The window whose callback to set.
|
||||
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
|
||||
* callback.
|
||||
* @return The previously set callback, or `NULL` if no callback was set or the
|
||||
* library had not been [initialized](@ref intro_init).
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref input_char
|
||||
*
|
||||
* @since Added in version 2.4.
|
||||
* @glfw3 Added window handle parameter and return value.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun cbfun);
|
||||
|
||||
/*! @brief Sets the Unicode character with modifiers callback.
|
||||
*
|
||||
* This function sets the character with modifiers callback of the specified
|
||||
* window, which is called when a Unicode character is input regardless of what
|
||||
* modifier keys are used.
|
||||
*
|
||||
* The character with modifiers callback is intended for implementing custom
|
||||
* Unicode character input. For regular Unicode text input, see the
|
||||
* [character callback](@ref glfwSetCharCallback). Like the character
|
||||
* callback, the character with modifiers callback deals with characters and is
|
||||
* keyboard layout dependent. Characters do not map 1:1 to physical keys, as
|
||||
* a key may produce zero, one or more characters. If you want to know whether
|
||||
* a specific physical key was pressed or released, see the
|
||||
* [key callback](@ref glfwSetKeyCallback) instead.
|
||||
*
|
||||
* @param[in] window The window whose callback to set.
|
||||
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
|
||||
* callback.
|
||||
* @return The previously set callback, or `NULL` if no callback was set or an
|
||||
* [error](@ref error_handling) occurred.
|
||||
*
|
||||
* @deprecated Scheduled for removal in version 4.0.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref input_char
|
||||
*
|
||||
* @since Added in version 3.1.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun cbfun);
|
||||
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* window, GLFWkeyboardfun cbfun);
|
||||
|
||||
/*! @brief Sets the mouse button callback.
|
||||
*
|
||||
@@ -4725,7 +4582,7 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid);
|
||||
* This function may be called from the joystick callback, even for a joystick
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] joystick The joystick whose pointer to set.
|
||||
* @param[in] jid The joystick whose pointer to set.
|
||||
* @param[in] pointer The new value.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
@@ -4750,7 +4607,7 @@ GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer);
|
||||
* This function may be called from the joystick callback, even for a joystick
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] joystick The joystick whose pointer to return.
|
||||
* @param[in] jid The joystick whose pointer to return.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
@@ -4902,7 +4759,7 @@ GLFWAPI const char* glfwGetGamepadName(int jid);
|
||||
*
|
||||
* Not all devices have all the buttons or axes provided by @ref
|
||||
* GLFWgamepadstate. Unavailable buttons and axes will always report
|
||||
* `GLFW_RELEASE` and 1.0 respectively.
|
||||
* `GLFW_RELEASE` and 0.0 respectively.
|
||||
*
|
||||
* @param[in] jid The [joystick](@ref joysticks) to query.
|
||||
* @param[out] state The gamepad input state of the joystick.
|
||||
@@ -5468,6 +5325,11 @@ GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhys
|
||||
* glfwGetRequiredInstanceExtensions to check what instance extensions are
|
||||
* required.
|
||||
*
|
||||
* The window surface cannot be shared with another API so the window must
|
||||
* have been created with the [client api hint](@ref GLFW_CLIENT_API_attrib)
|
||||
* set to `GLFW_NO_API` otherwise it generates a @ref GLFW_INVALID_VALUE error
|
||||
* and returns `VK_ERROR_NATIVE_WINDOW_IN_USE_KHR`.
|
||||
*
|
||||
* The window surface must be destroyed before the specified Vulkan instance.
|
||||
* It is the responsibility of the caller to destroy the window surface. GLFW
|
||||
* does not destroy it for you. Call `vkDestroySurfaceKHR` to destroy the
|
||||
@@ -5483,7 +5345,7 @@ GLFWAPI int glfwGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhys
|
||||
* [error](@ref error_handling) occurred.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
* GLFW_API_UNAVAILABLE and @ref GLFW_PLATFORM_ERROR.
|
||||
* GLFW_API_UNAVAILABLE, @ref GLFW_PLATFORM_ERROR and @ref GLFW_INVALID_VALUE
|
||||
*
|
||||
* @remark If an error occurs before the creation call is made, GLFW returns
|
||||
* the Vulkan error code most appropriate for the error. Appropriate use of
|
||||
@@ -5542,4 +5404,3 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance, GLFWwindow* window
|
||||
#endif
|
||||
|
||||
#endif /* _glfw3_h_ */
|
||||
|
||||
|
||||
56
glfw/init.c
vendored
56
glfw/init.c
vendored
@@ -57,37 +57,6 @@ static _GLFWinitconfig _glfwInitHints =
|
||||
}
|
||||
};
|
||||
|
||||
// Returns a generic string representation of the specified error
|
||||
//
|
||||
static const char* getErrorString(int code)
|
||||
{
|
||||
switch (code)
|
||||
{
|
||||
case GLFW_NOT_INITIALIZED:
|
||||
return "The GLFW library is not initialized";
|
||||
case GLFW_NO_CURRENT_CONTEXT:
|
||||
return "There is no current context";
|
||||
case GLFW_INVALID_ENUM:
|
||||
return "Invalid argument for enum parameter";
|
||||
case GLFW_INVALID_VALUE:
|
||||
return "Invalid value for parameter";
|
||||
case GLFW_OUT_OF_MEMORY:
|
||||
return "Out of memory";
|
||||
case GLFW_API_UNAVAILABLE:
|
||||
return "The requested API is unavailable";
|
||||
case GLFW_VERSION_UNAVAILABLE:
|
||||
return "The requested API version is unavailable";
|
||||
case GLFW_PLATFORM_ERROR:
|
||||
return "An undocumented platform-specific error occurred";
|
||||
case GLFW_FORMAT_UNAVAILABLE:
|
||||
return "The requested format is unavailable";
|
||||
case GLFW_NO_WINDOW_CONTEXT:
|
||||
return "The specified window has no context";
|
||||
default:
|
||||
return "ERROR: UNKNOWN GLFW ERROR";
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate the library
|
||||
//
|
||||
static void terminate(void)
|
||||
@@ -173,7 +142,30 @@ void _glfwInputError(int code, const char* format, ...)
|
||||
description[sizeof(description) - 1] = '\0';
|
||||
}
|
||||
else
|
||||
strcpy(description, getErrorString(code));
|
||||
{
|
||||
if (code == GLFW_NOT_INITIALIZED)
|
||||
strcpy(description, "The GLFW library is not initialized");
|
||||
else if (code == GLFW_NO_CURRENT_CONTEXT)
|
||||
strcpy(description, "There is no current context");
|
||||
else if (code == GLFW_INVALID_ENUM)
|
||||
strcpy(description, "Invalid argument for enum parameter");
|
||||
else if (code == GLFW_INVALID_VALUE)
|
||||
strcpy(description, "Invalid value for parameter");
|
||||
else if (code == GLFW_OUT_OF_MEMORY)
|
||||
strcpy(description, "Out of memory");
|
||||
else if (code == GLFW_API_UNAVAILABLE)
|
||||
strcpy(description, "The requested API is unavailable");
|
||||
else if (code == GLFW_VERSION_UNAVAILABLE)
|
||||
strcpy(description, "The requested API version is unavailable");
|
||||
else if (code == GLFW_PLATFORM_ERROR)
|
||||
strcpy(description, "A platform-specific error occurred");
|
||||
else if (code == GLFW_FORMAT_UNAVAILABLE)
|
||||
strcpy(description, "The requested format is unavailable");
|
||||
else if (code == GLFW_NO_WINDOW_CONTEXT)
|
||||
strcpy(description, "The specified window has no context");
|
||||
else
|
||||
strcpy(description, "ERROR: UNKNOWN GLFW ERROR");
|
||||
}
|
||||
|
||||
if (_glfw.initialized)
|
||||
{
|
||||
|
||||
274
glfw/input.c
vendored
274
glfw/input.c
vendored
@@ -62,11 +62,11 @@ static _GLFWmapping* findMapping(const char* guid)
|
||||
static GLFWbool isValidElementForJoystick(const _GLFWmapelement* e,
|
||||
const _GLFWjoystick* js)
|
||||
{
|
||||
if (e->type == _GLFW_JOYSTICK_HATBIT && (e->value >> 4) >= js->hatCount)
|
||||
if (e->type == _GLFW_JOYSTICK_HATBIT && (e->index >> 4) >= js->hatCount)
|
||||
return GLFW_FALSE;
|
||||
else if (e->type == _GLFW_JOYSTICK_BUTTON && e->value >= js->buttonCount)
|
||||
else if (e->type == _GLFW_JOYSTICK_BUTTON && e->index >= js->buttonCount)
|
||||
return GLFW_FALSE;
|
||||
else if (e->type == _GLFW_JOYSTICK_AXIS && e->value >= js->axisCount)
|
||||
else if (e->type == _GLFW_JOYSTICK_AXIS && e->index >= js->axisCount)
|
||||
return GLFW_FALSE;
|
||||
|
||||
return GLFW_TRUE;
|
||||
@@ -167,6 +167,10 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
|
||||
|
||||
while (*c)
|
||||
{
|
||||
// TODO: Implement output modifiers
|
||||
if (*c == '+' || *c == '-')
|
||||
return GLFW_FALSE;
|
||||
|
||||
for (i = 0; i < sizeof(fields) / sizeof(fields[0]); i++)
|
||||
{
|
||||
length = strlen(fields[i].name);
|
||||
@@ -177,23 +181,50 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
|
||||
|
||||
if (fields[i].element)
|
||||
{
|
||||
_GLFWmapelement* e = fields[i].element;
|
||||
int8_t minimum = -1;
|
||||
int8_t maximum = 1;
|
||||
|
||||
if (*c == '+')
|
||||
{
|
||||
minimum = 0;
|
||||
c += 1;
|
||||
}
|
||||
else if (*c == '-')
|
||||
{
|
||||
maximum = 0;
|
||||
c += 1;
|
||||
}
|
||||
|
||||
if (*c == 'a')
|
||||
fields[i].element->type = _GLFW_JOYSTICK_AXIS;
|
||||
e->type = _GLFW_JOYSTICK_AXIS;
|
||||
else if (*c == 'b')
|
||||
fields[i].element->type = _GLFW_JOYSTICK_BUTTON;
|
||||
e->type = _GLFW_JOYSTICK_BUTTON;
|
||||
else if (*c == 'h')
|
||||
fields[i].element->type = _GLFW_JOYSTICK_HATBIT;
|
||||
e->type = _GLFW_JOYSTICK_HATBIT;
|
||||
else
|
||||
break;
|
||||
|
||||
if (fields[i].element->type == _GLFW_JOYSTICK_HATBIT)
|
||||
if (e->type == _GLFW_JOYSTICK_HATBIT)
|
||||
{
|
||||
const unsigned long hat = strtoul(c + 1, (char**) &c, 10);
|
||||
const unsigned long bit = strtoul(c + 1, (char**) &c, 10);
|
||||
fields[i].element->value = (uint8_t) ((hat << 4) | bit);
|
||||
e->index = (uint8_t) ((hat << 4) | bit);
|
||||
}
|
||||
else
|
||||
fields[i].element->value = (uint8_t) strtoul(c + 1, (char**) &c, 10);
|
||||
e->index = (uint8_t) strtoul(c + 1, (char**) &c, 10);
|
||||
|
||||
if (e->type == _GLFW_JOYSTICK_AXIS)
|
||||
{
|
||||
e->axisScale = 2 / (maximum - minimum);
|
||||
e->axisOffset = -(maximum + minimum);
|
||||
|
||||
if (*c == '~')
|
||||
{
|
||||
e->axisScale = -e->axisScale;
|
||||
e->axisOffset = -e->axisOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -224,9 +255,9 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
|
||||
////// GLFW event API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Notifies shared code of a physical key event
|
||||
// Notifies shared code of a key event
|
||||
//
|
||||
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
void _glfwInputKeyboard(_GLFWwindow* window, int key, int scancode, int action, int mods, const char* text, int state)
|
||||
{
|
||||
if (key >= 0 && key <= GLFW_KEY_LAST)
|
||||
{
|
||||
@@ -247,31 +278,10 @@ void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int m
|
||||
action = GLFW_REPEAT;
|
||||
}
|
||||
|
||||
if (!window->lockKeyMods)
|
||||
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
|
||||
|
||||
if (window->callbacks.key)
|
||||
window->callbacks.key((GLFWwindow*) window, key, scancode, action, mods);
|
||||
}
|
||||
|
||||
// Notifies shared code of a Unicode codepoint input event
|
||||
// The 'plain' parameter determines whether to emit a regular character event
|
||||
//
|
||||
void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain)
|
||||
{
|
||||
if (codepoint < 32 || (codepoint > 126 && codepoint < 160))
|
||||
return;
|
||||
|
||||
if (!window->lockKeyMods)
|
||||
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
|
||||
|
||||
if (window->callbacks.charmods)
|
||||
window->callbacks.charmods((GLFWwindow*) window, codepoint, mods);
|
||||
|
||||
if (plain)
|
||||
{
|
||||
if (window->callbacks.character)
|
||||
window->callbacks.character((GLFWwindow*) window, codepoint);
|
||||
if (window->callbacks.keyboard) {
|
||||
if (!window->lockKeyMods) mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
|
||||
window->callbacks.keyboard((GLFWwindow*) window, key, scancode, action, mods, text, state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,6 +433,137 @@ void _glfwFreeJoystick(_GLFWjoystick* js)
|
||||
memset(js, 0, sizeof(_GLFWjoystick));
|
||||
}
|
||||
|
||||
const char* _glfwGetKeyName(int key)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
// Printable keys
|
||||
case GLFW_KEY_A: return "A";
|
||||
case GLFW_KEY_B: return "B";
|
||||
case GLFW_KEY_C: return "C";
|
||||
case GLFW_KEY_D: return "D";
|
||||
case GLFW_KEY_E: return "E";
|
||||
case GLFW_KEY_F: return "F";
|
||||
case GLFW_KEY_G: return "G";
|
||||
case GLFW_KEY_H: return "H";
|
||||
case GLFW_KEY_I: return "I";
|
||||
case GLFW_KEY_J: return "J";
|
||||
case GLFW_KEY_K: return "K";
|
||||
case GLFW_KEY_L: return "L";
|
||||
case GLFW_KEY_M: return "M";
|
||||
case GLFW_KEY_N: return "N";
|
||||
case GLFW_KEY_O: return "O";
|
||||
case GLFW_KEY_P: return "P";
|
||||
case GLFW_KEY_Q: return "Q";
|
||||
case GLFW_KEY_R: return "R";
|
||||
case GLFW_KEY_S: return "S";
|
||||
case GLFW_KEY_T: return "T";
|
||||
case GLFW_KEY_U: return "U";
|
||||
case GLFW_KEY_V: return "V";
|
||||
case GLFW_KEY_W: return "W";
|
||||
case GLFW_KEY_X: return "X";
|
||||
case GLFW_KEY_Y: return "Y";
|
||||
case GLFW_KEY_Z: return "Z";
|
||||
case GLFW_KEY_1: return "1";
|
||||
case GLFW_KEY_2: return "2";
|
||||
case GLFW_KEY_3: return "3";
|
||||
case GLFW_KEY_4: return "4";
|
||||
case GLFW_KEY_5: return "5";
|
||||
case GLFW_KEY_6: return "6";
|
||||
case GLFW_KEY_7: return "7";
|
||||
case GLFW_KEY_8: return "8";
|
||||
case GLFW_KEY_9: return "9";
|
||||
case GLFW_KEY_0: return "0";
|
||||
case GLFW_KEY_SPACE: return "SPACE";
|
||||
case GLFW_KEY_MINUS: return "MINUS";
|
||||
case GLFW_KEY_EQUAL: return "EQUAL";
|
||||
case GLFW_KEY_LEFT_BRACKET: return "LEFT BRACKET";
|
||||
case GLFW_KEY_RIGHT_BRACKET: return "RIGHT BRACKET";
|
||||
case GLFW_KEY_BACKSLASH: return "BACKSLASH";
|
||||
case GLFW_KEY_SEMICOLON: return "SEMICOLON";
|
||||
case GLFW_KEY_APOSTROPHE: return "APOSTROPHE";
|
||||
case GLFW_KEY_GRAVE_ACCENT: return "GRAVE ACCENT";
|
||||
case GLFW_KEY_COMMA: return "COMMA";
|
||||
case GLFW_KEY_PERIOD: return "PERIOD";
|
||||
case GLFW_KEY_SLASH: return "SLASH";
|
||||
case GLFW_KEY_WORLD_1: return "WORLD 1";
|
||||
case GLFW_KEY_WORLD_2: return "WORLD 2";
|
||||
|
||||
// Function keys
|
||||
case GLFW_KEY_ESCAPE: return "ESCAPE";
|
||||
case GLFW_KEY_F1: return "F1";
|
||||
case GLFW_KEY_F2: return "F2";
|
||||
case GLFW_KEY_F3: return "F3";
|
||||
case GLFW_KEY_F4: return "F4";
|
||||
case GLFW_KEY_F5: return "F5";
|
||||
case GLFW_KEY_F6: return "F6";
|
||||
case GLFW_KEY_F7: return "F7";
|
||||
case GLFW_KEY_F8: return "F8";
|
||||
case GLFW_KEY_F9: return "F9";
|
||||
case GLFW_KEY_F10: return "F10";
|
||||
case GLFW_KEY_F11: return "F11";
|
||||
case GLFW_KEY_F12: return "F12";
|
||||
case GLFW_KEY_F13: return "F13";
|
||||
case GLFW_KEY_F14: return "F14";
|
||||
case GLFW_KEY_F15: return "F15";
|
||||
case GLFW_KEY_F16: return "F16";
|
||||
case GLFW_KEY_F17: return "F17";
|
||||
case GLFW_KEY_F18: return "F18";
|
||||
case GLFW_KEY_F19: return "F19";
|
||||
case GLFW_KEY_F20: return "F20";
|
||||
case GLFW_KEY_F21: return "F21";
|
||||
case GLFW_KEY_F22: return "F22";
|
||||
case GLFW_KEY_F23: return "F23";
|
||||
case GLFW_KEY_F24: return "F24";
|
||||
case GLFW_KEY_F25: return "F25";
|
||||
case GLFW_KEY_UP: return "UP";
|
||||
case GLFW_KEY_DOWN: return "DOWN";
|
||||
case GLFW_KEY_LEFT: return "LEFT";
|
||||
case GLFW_KEY_RIGHT: return "RIGHT";
|
||||
case GLFW_KEY_LEFT_SHIFT: return "LEFT SHIFT";
|
||||
case GLFW_KEY_RIGHT_SHIFT: return "RIGHT SHIFT";
|
||||
case GLFW_KEY_LEFT_CONTROL: return "LEFT CONTROL";
|
||||
case GLFW_KEY_RIGHT_CONTROL: return "RIGHT CONTROL";
|
||||
case GLFW_KEY_LEFT_ALT: return "LEFT ALT";
|
||||
case GLFW_KEY_RIGHT_ALT: return "RIGHT ALT";
|
||||
case GLFW_KEY_TAB: return "TAB";
|
||||
case GLFW_KEY_ENTER: return "ENTER";
|
||||
case GLFW_KEY_BACKSPACE: return "BACKSPACE";
|
||||
case GLFW_KEY_INSERT: return "INSERT";
|
||||
case GLFW_KEY_DELETE: return "DELETE";
|
||||
case GLFW_KEY_PAGE_UP: return "PAGE UP";
|
||||
case GLFW_KEY_PAGE_DOWN: return "PAGE DOWN";
|
||||
case GLFW_KEY_HOME: return "HOME";
|
||||
case GLFW_KEY_END: return "END";
|
||||
case GLFW_KEY_KP_0: return "KEYPAD 0";
|
||||
case GLFW_KEY_KP_1: return "KEYPAD 1";
|
||||
case GLFW_KEY_KP_2: return "KEYPAD 2";
|
||||
case GLFW_KEY_KP_3: return "KEYPAD 3";
|
||||
case GLFW_KEY_KP_4: return "KEYPAD 4";
|
||||
case GLFW_KEY_KP_5: return "KEYPAD 5";
|
||||
case GLFW_KEY_KP_6: return "KEYPAD 6";
|
||||
case GLFW_KEY_KP_7: return "KEYPAD 7";
|
||||
case GLFW_KEY_KP_8: return "KEYPAD 8";
|
||||
case GLFW_KEY_KP_9: return "KEYPAD 9";
|
||||
case GLFW_KEY_KP_DIVIDE: return "KEYPAD DIVIDE";
|
||||
case GLFW_KEY_KP_MULTIPLY: return "KEYPAD MULTPLY";
|
||||
case GLFW_KEY_KP_SUBTRACT: return "KEYPAD SUBTRACT";
|
||||
case GLFW_KEY_KP_ADD: return "KEYPAD ADD";
|
||||
case GLFW_KEY_KP_DECIMAL: return "KEYPAD DECIMAL";
|
||||
case GLFW_KEY_KP_EQUAL: return "KEYPAD EQUAL";
|
||||
case GLFW_KEY_KP_ENTER: return "KEYPAD ENTER";
|
||||
case GLFW_KEY_PRINT_SCREEN: return "PRINT SCREEN";
|
||||
case GLFW_KEY_NUM_LOCK: return "NUM LOCK";
|
||||
case GLFW_KEY_CAPS_LOCK: return "CAPS LOCK";
|
||||
case GLFW_KEY_SCROLL_LOCK: return "SCROLL LOCK";
|
||||
case GLFW_KEY_PAUSE: return "PAUSE";
|
||||
case GLFW_KEY_LEFT_SUPER: return "LEFT SUPER";
|
||||
case GLFW_KEY_RIGHT_SUPER: return "RIGHT SUPER";
|
||||
case GLFW_KEY_MENU: return "MENU";
|
||||
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW public API //////
|
||||
@@ -478,9 +619,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&window->virtualCursorPosX,
|
||||
&window->virtualCursorPosY);
|
||||
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
_glfwPlatformSetCursorMode(window, value);
|
||||
_glfwPlatformSetCursorMode(window, value);
|
||||
}
|
||||
else if (mode == GLFW_STICKY_KEYS)
|
||||
{
|
||||
@@ -760,33 +899,13 @@ GLFWAPI void glfwSetCursor(GLFWwindow* windowHandle, GLFWcursor* cursorHandle)
|
||||
_glfwPlatformSetCursor(window, cursor);
|
||||
}
|
||||
|
||||
GLFWAPI GLFWkeyfun glfwSetKeyCallback(GLFWwindow* handle, GLFWkeyfun cbfun)
|
||||
GLFWAPI GLFWkeyboardfun glfwSetKeyboardCallback(GLFWwindow* handle, GLFWkeyboardfun cbfun)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
_GLFW_SWAP_POINTERS(window->callbacks.key, cbfun);
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* handle, GLFWcharfun cbfun)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
_GLFW_SWAP_POINTERS(window->callbacks.character, cbfun);
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* handle, GLFWcharmodsfun cbfun)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
_GLFW_SWAP_POINTERS(window->callbacks.charmods, cbfun);
|
||||
_GLFW_SWAP_POINTERS(window->callbacks.keyboard, cbfun);
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
@@ -1188,35 +1307,41 @@ GLFWAPI int glfwGetGamepadState(int jid, GLFWgamepadstate* state)
|
||||
|
||||
for (i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++)
|
||||
{
|
||||
if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_AXIS)
|
||||
const _GLFWmapelement* e = js->mapping->buttons + i;
|
||||
if (e->type == _GLFW_JOYSTICK_AXIS)
|
||||
{
|
||||
if (fabs(js->axes[js->mapping->buttons[i].value]) > 0.5)
|
||||
const float value = js->axes[e->index] * e->axisScale + e->axisOffset;
|
||||
if (value > 0.f)
|
||||
state->buttons[i] = GLFW_PRESS;
|
||||
}
|
||||
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT)
|
||||
else if (e->type == _GLFW_JOYSTICK_HATBIT)
|
||||
{
|
||||
const unsigned int hat = js->mapping->buttons[i].value >> 4;
|
||||
const unsigned int bit = js->mapping->buttons[i].value & 0xf;
|
||||
const unsigned int hat = e->index >> 4;
|
||||
const unsigned int bit = e->index & 0xf;
|
||||
if (js->hats[hat] & bit)
|
||||
state->buttons[i] = GLFW_PRESS;
|
||||
}
|
||||
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_BUTTON)
|
||||
state->buttons[i] = js->buttons[js->mapping->buttons[i].value];
|
||||
else if (e->type == _GLFW_JOYSTICK_BUTTON)
|
||||
state->buttons[i] = js->buttons[e->index];
|
||||
}
|
||||
|
||||
for (i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++)
|
||||
{
|
||||
if (js->mapping->axes[i].type == _GLFW_JOYSTICK_AXIS)
|
||||
state->axes[i] = js->axes[js->mapping->axes[i].value];
|
||||
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_HATBIT)
|
||||
const _GLFWmapelement* e = js->mapping->axes + i;
|
||||
if (e->type == _GLFW_JOYSTICK_AXIS)
|
||||
{
|
||||
const unsigned int hat = js->mapping->axes[i].value >> 4;
|
||||
const unsigned int bit = js->mapping->axes[i].value & 0xf;
|
||||
const float value = js->axes[e->index] * e->axisScale + e->axisOffset;
|
||||
state->axes[i] = fminf(fmaxf(value, -1.f), 1.f);
|
||||
}
|
||||
else if (e->type == _GLFW_JOYSTICK_HATBIT)
|
||||
{
|
||||
const unsigned int hat = e->index >> 4;
|
||||
const unsigned int bit = e->index & 0xf;
|
||||
if (js->hats[hat] & bit)
|
||||
state->axes[i] = 1.f;
|
||||
}
|
||||
else if (js->mapping->buttons[i].type == _GLFW_JOYSTICK_BUTTON)
|
||||
state->axes[i] = (float) js->buttons[js->mapping->axes[i].value];
|
||||
else if (e->type == _GLFW_JOYSTICK_BUTTON)
|
||||
state->axes[i] = (float) js->buttons[e->index];
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
@@ -1268,4 +1393,3 @@ GLFWAPI uint64_t glfwGetTimerFrequency(void)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(0);
|
||||
return _glfwPlatformGetTimerFrequency();
|
||||
}
|
||||
|
||||
|
||||
57
glfw/internal.h
vendored
57
glfw/internal.h
vendored
@@ -128,7 +128,7 @@ typedef enum VkStructureType
|
||||
VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR = 1000006000,
|
||||
VK_STRUCTURE_TYPE_MIR_SURFACE_CREATE_INFO_KHR = 1000007000,
|
||||
VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR = 1000009000,
|
||||
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000053000,
|
||||
VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK = 1000123000,
|
||||
VK_STRUCTURE_TYPE_MAX_ENUM = 0x7FFFFFFF
|
||||
} VkStructureType;
|
||||
|
||||
@@ -407,9 +407,7 @@ struct _GLFWwindow
|
||||
GLFWcursorposfun cursorPos;
|
||||
GLFWcursorenterfun cursorEnter;
|
||||
GLFWscrollfun scroll;
|
||||
GLFWkeyfun key;
|
||||
GLFWcharfun character;
|
||||
GLFWcharmodsfun charmods;
|
||||
GLFWkeyboardfun keyboard;
|
||||
GLFWdropfun drop;
|
||||
} callbacks;
|
||||
|
||||
@@ -456,7 +454,9 @@ struct _GLFWcursor
|
||||
struct _GLFWmapelement
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t value;
|
||||
uint8_t index;
|
||||
int8_t axisScale;
|
||||
int8_t axisOffset;
|
||||
};
|
||||
|
||||
// Gamepad mapping structure
|
||||
@@ -596,7 +596,8 @@ const char* _glfwPlatformGetVersionString(void);
|
||||
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);
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image, int xhot, int yhot);
|
||||
int _glfwPlatformCreateStandardCursor(_GLFWcursor* cursor, int shape);
|
||||
void _glfwPlatformDestroyCursor(_GLFWcursor* cursor);
|
||||
void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
|
||||
@@ -604,8 +605,10 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor);
|
||||
const char* _glfwPlatformGetScancodeName(int scancode);
|
||||
int _glfwPlatformGetKeyScancode(int key);
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor);
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos);
|
||||
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor, float* xscale, float* yscale);
|
||||
void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
|
||||
float* xscale, float* yscale);
|
||||
GLFWvidmode* _glfwPlatformGetVideoModes(_GLFWmonitor* monitor, int* count);
|
||||
void _glfwPlatformGetVideoMode(_GLFWmonitor* monitor, GLFWvidmode* mode);
|
||||
void _glfwPlatformGetGammaRamp(_GLFWmonitor* monitor, GLFWgammaramp* ramp);
|
||||
@@ -626,25 +629,33 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
const _GLFWfbconfig* fbconfig);
|
||||
void _glfwPlatformDestroyWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title);
|
||||
void _glfwPlatformSetWindowIcon(_GLFWwindow* window, int count, const GLFWimage* images);
|
||||
void _glfwPlatformSetWindowIcon(_GLFWwindow* window,
|
||||
int count, const GLFWimage* images);
|
||||
void _glfwPlatformGetWindowPos(_GLFWwindow* window, int* xpos, int* ypos);
|
||||
void _glfwPlatformSetWindowPos(_GLFWwindow* window, int xpos, int ypos);
|
||||
void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height);
|
||||
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height);
|
||||
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window, int minwidth, int minheight, int maxwidth, int maxheight);
|
||||
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
int minwidth, int minheight,
|
||||
int maxwidth, int maxheight);
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom);
|
||||
void _glfwPlatformGetFramebufferSize(_GLFWwindow* window, int* width, int* height);
|
||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window, int* left, int* top, int* right, int* bottom);
|
||||
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window, float* xscale, float* yscale);
|
||||
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
int* left, int* top,
|
||||
int* right, int* bottom);
|
||||
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
float* xscale, float* yscale);
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformRestoreWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformShowWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformHideWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window);
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t);
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window);
|
||||
void _glfwPlatformFocusWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor, int xpos, int ypos, int width, int height, int refreshRate);
|
||||
void _glfwPlatformSetWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor,
|
||||
int xpos, int ypos, int width, int height,
|
||||
int refreshRate);
|
||||
int _glfwPlatformWindowFocused(_GLFWwindow* window);
|
||||
int _glfwPlatformWindowIconified(_GLFWwindow* window);
|
||||
int _glfwPlatformWindowVisible(_GLFWwindow* window);
|
||||
@@ -663,8 +674,13 @@ void _glfwPlatformWaitEventsTimeout(double timeout);
|
||||
void _glfwPlatformPostEmptyEvent(void);
|
||||
|
||||
void _glfwPlatformGetRequiredInstanceExtensions(char** extensions);
|
||||
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance, VkPhysicalDevice device, uint32_t queuefamily);
|
||||
VkResult _glfwPlatformCreateWindowSurface(VkInstance instance, _GLFWwindow* window, const VkAllocationCallbacks* allocator, VkSurfaceKHR* surface);
|
||||
int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
|
||||
VkPhysicalDevice device,
|
||||
uint32_t queuefamily);
|
||||
VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
_GLFWwindow* window,
|
||||
const VkAllocationCallbacks* allocator,
|
||||
VkSurfaceKHR* surface);
|
||||
|
||||
GLFWbool _glfwPlatformCreateTls(_GLFWtls* tls);
|
||||
void _glfwPlatformDestroyTls(_GLFWtls* tls);
|
||||
@@ -685,15 +701,15 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused);
|
||||
void _glfwInputWindowPos(_GLFWwindow* window, int xpos, int ypos);
|
||||
void _glfwInputWindowSize(_GLFWwindow* window, int width, int height);
|
||||
void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height);
|
||||
void _glfwInputWindowContentScale(_GLFWwindow* window, float xscale, float yscale);
|
||||
void _glfwInputWindowContentScale(_GLFWwindow* window,
|
||||
float xscale, float yscale);
|
||||
void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified);
|
||||
void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized);
|
||||
void _glfwInputWindowDamage(_GLFWwindow* window);
|
||||
void _glfwInputWindowCloseRequest(_GLFWwindow* window);
|
||||
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
|
||||
|
||||
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain);
|
||||
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 _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
|
||||
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||
@@ -708,7 +724,8 @@ void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement);
|
||||
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void _glfwInputError(int code, const char* format, ...) __attribute__((format(printf, 2, 3)));
|
||||
void _glfwInputError(int code, const char* format, ...)
|
||||
__attribute__((format(printf, 2, 3)));
|
||||
#else
|
||||
void _glfwInputError(int code, const char* format, ...);
|
||||
#endif
|
||||
@@ -741,10 +758,10 @@ _GLFWjoystick* _glfwAllocJoystick(const char* name,
|
||||
int buttonCount,
|
||||
int hatCount);
|
||||
void _glfwFreeJoystick(_GLFWjoystick* js);
|
||||
const char* _glfwGetKeyName(int key);
|
||||
|
||||
GLFWbool _glfwInitVulkan(int mode);
|
||||
void _glfwTerminateVulkan(void);
|
||||
const char* _glfwGetVulkanResultString(VkResult result);
|
||||
|
||||
char* _glfw_strdup(const char* source);
|
||||
|
||||
|
||||
5
glfw/linux_joystick.c
vendored
5
glfw/linux_joystick.c
vendored
@@ -38,6 +38,11 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef SYN_DROPPED // < v2.6.39 kernel headers
|
||||
// Workaround for CentOS-6, which is supported till 2020-11-30, but still on v2.6.32
|
||||
#define SYN_DROPPED 3
|
||||
#endif
|
||||
|
||||
// Apply an EV_KEY event to the specified joystick
|
||||
//
|
||||
static void handleKeyEvent(_GLFWjoystick* js, int code, int value)
|
||||
|
||||
624
glfw/mappings.h
vendored
624
glfw/mappings.h
vendored
@@ -60,243 +60,411 @@
|
||||
|
||||
const char* _glfwDefaultMappings[] =
|
||||
{
|
||||
"02200090000000000000504944564944,8Bitdo NES30 PRO USB,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
|
||||
"20380900000000000000504944564944,8Bitdo NES30 PRO Wireless,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
|
||||
"10280900000000000000504944564944,8Bitdo SFC30 GamePad,a:b1,b:b0,y:b3,x:b4,start:b11,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,platform:Windows,",
|
||||
"8f0e1200000000000000504944564944,Acme,platform:Windows,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
|
||||
"341a3608000000000000504944564944,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"c0111352000000000000504944564944,Battalife Joystick,platform:Windows,x:b4,a:b6,b:b7,y:b5,back:b2,start:b3,leftshoulder:b0,rightshoulder:b1,leftx:a0,lefty:a1,",
|
||||
"d81d0b00000000000000504944564944,BUFFALO BSGP1601 Series ,platform:Windows,x:b4,a:b5,b:b3,y:b2,back:b12,start:b13,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b8,lefttrigger:b6,rightshoulder:b9,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"5e048e02000000000000504944564944,Controller (XBOX 360 For Windows),platform:Windows,x:b2,a:b0,b:b1,y:b3,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,righttrigger:a2,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"4f0423b3000000000000504944564944,Dual Trigger 3-in-1,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"341a0108000000000000504944564944,EXEQ RF USB Gamepad 8206,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,leftstick:b8,rightstick:b7,back:b8,start:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows,",
|
||||
"0d0f8500000000000000504944564944,Fighting Commander 2016 PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f5f00000000000000504944564944,Fighting Commander 4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f5e00000000000000504944564944,Fighting Commander 4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a4,righttrigger:a3,platform:Windows,",
|
||||
"0d0f8400000000000000504944564944,Fighting Commander 5,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f8700000000000000504944564944,Fighting Stick mini 4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f8800000000000000504944564944,Fighting Stick mini 4,a:b1,b:b2,x:b0,y:b3,back:b9,guide:b12,start:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f2700000000000000504944564944,FIGHTING STICK V3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"79000600000000000000504944564944,G-Shark GS-GP702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"28040140000000000000504944564944,GamePad Pro USB,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7,",
|
||||
"ffff0000000000000000504944564944,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"6d0416c2000000000000504944564944,Generic DirectInput Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"45130010000000000000504944564944,Generic USB Joystick,a:b0,b:b1,x:b2,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f4900000000000000504944564944,Hatsune Miku Sho Controller,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"d8140862000000000000504944564944,HitBox Edition Cthulhu+,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b5,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b4,righttrigger:b6,platform:Windows,",
|
||||
"0d0f4000000000000000504944564944,Hori Fighting Stick Mini 3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b5,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b4,righttrigger:b6,platform:Windows,",
|
||||
"0d0f6e00000000000000504944564944,HORIPAD 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f4d00000000000000504944564944,HORIPAD3 A,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"25090017000000000000504944564944,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b5,rightshoulder:b7,leftx:a0,lefty:a1,lefttrigger:b4,righttrigger:b6,platform:Windows,",
|
||||
"d81d0f00000000000000504944564944,iBUFFALO BSGP1204 Series,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"d81d1000000000000000504944564944,iBUFFALO BSGP1204P Series,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"83056020000000000000504944564944,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,y:b2,x:b3,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Windows,",
|
||||
"6f0e2401000000000000504944564944,INJUSTICE FightStick for PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,platform:Windows",
|
||||
"49190204000000000000504944564944,Ipega PG-9023,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,platform:Windows,",
|
||||
"6d0419c2000000000000504944564944,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"6d0418c2000000000000504944564944,Logitech RumblePad 2 USB,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"38075032000000000000504944564944,Mad Catz FightPad PRO PS3,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"38075082000000000000504944564944,Mad Catz FightPad PRO PS4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b13,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
|
||||
"38078433000000000000504944564944,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"38078483000000000000504944564944,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:b6,platform:Windows,",
|
||||
"38078134000000000000504944564944,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b7,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b4,platform:Windows,",
|
||||
"38078184000000000000504944564944,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a4,righttrigger:b7,platform:Windows,",
|
||||
"38078034000000000000504944564944,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"38078084000000000000504944564944,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
|
||||
"38078532000000000000504944564944,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"38073888000000000000504944564944,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"38071888000000000000504944564944,MadCatz SFIV FightStick PS3,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b7,righttrigger:b6,platform:Windows,",
|
||||
"03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"25090128000000000000504944564944,Mayflash Arcade Stick,a:b1,b:b2,x:b5,y:b6,back:b8,start:b9,leftshoulder:b0,rightshoulder:b3,leftx:a0,lefty:a1,rightx:h0.4,righty:h0.0,lefttrigger:b4,righttrigger:b7,platform:Windows,",
|
||||
"79004318000000000000504944564944,Mayflash GameCube Controller Adapter,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b0,start:b9,guide:b0,leftshoulder:b4,rightshoulder:b7,leftstick:b0,rightstick:b0,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
|
||||
"8f0e1030000000000000504944564944,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,x:b3,y:b4,start:b9,leftshoulder:b6,rightshoulder:b2,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b5,righttrigger:b7,platform:Windows,",
|
||||
"2509e803000000000000504944564944,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"79000018000000000000504944564944,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"8f0e0d31000000000000504944564944,Multilaser JS071 USB,platform:Windows,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
|
||||
"100801e5000000000000504944564944,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Windows,",
|
||||
"bd1215d0000000000000504944564944,Nintendo Retrolink USB Super SNES Classic Controller,y:b0,b:b1,a:b2,x:b3,leftshoulder:b4,rightshoulder:b5,start:b9,back:b8,leftx:a0,lefty:a1,platform:Windows,",
|
||||
"4b12014d000000000000504944564944,NYKO AIRFLO,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b10,start:b9,leftstick:a0,rightstick:a2,leftshoulder:a3,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:h0.6,lefty:h0.12,rightx:h0.9,righty:h0.4,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"36280100000000000000504944564944,OUYA Controller,platform:Windows,a:b0,b:b3,y:b2,x:b1,start:b14,guide:b15,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b8,dpleft:b10,dpdown:b9,dpright:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b12,righttrigger:b13,",
|
||||
"120cf60e000000000000504944564944,P4 Wired Gamepad,a:b1,b:b2,x:b0,y:b3,back:b12,guide:b8,start:b9,leftshoulder:b5,rightshoulder:b4,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:h0.0,lefttrigger:b7,righttrigger:b6,platform:Windows,",
|
||||
"8f0e0300000000000000504944564944,Piranha xtreme,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
|
||||
"d6206dca000000000000504944564944,PowerA Pro Ex,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"10080100000000000000504944564944,PS1 USB,platform:Windows,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
|
||||
"10080300000000000000504944564944,PS2 USB,platform:Windows,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a4,righty:a2,lefttrigger:b4,righttrigger:b5,",
|
||||
"88880803000000000000504944564944,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,",
|
||||
"4c056802000000000000504944564944,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,",
|
||||
"25090500000000000000504944564944,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,",
|
||||
"10008200000000000000504944564944,PS360+ v1.66,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:h0.4,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"4c05c405000000000000504944564944,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"300f0011000000000000504944564944,QanBa Arcade JoyStick 1008,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,start:b10,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1,",
|
||||
"300f1611000000000000504944564944,QanBa Arcade JoyStick 4018,a:b1,b:b2,x:b0,y:b3,back:b10,guide:b9,start:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"222c0020000000000000504944564944,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:a3,righttrigger:a4,platform:Windows,",
|
||||
"300f1210000000000000504944564944,QanBa Joystick Plus,a:b0,b:b1,x:b2,y:b3,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,platform:Windows,",
|
||||
"341a0104000000000000504944564944,QanBa Joystick Q4RAF,a:b5,b:b6,x:b1,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b0,rightshoulder:b3,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,lefttrigger:b4,righttrigger:b7,platform:Windows,",
|
||||
"222c0223000000000000504944564944,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"222c0023000000000000504944564944,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,x:b0,y:b3,back:b13,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
|
||||
"0d0f1100000000000000504944564944,REAL ARCADE PRO.3,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f8b00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"0d0f8a00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
|
||||
"0d0f6b00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"0d0f6a00000000000000504944564944,Real Arcade Pro.4,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
|
||||
"0d0f7000000000000000504944564944,REAL ARCADE PRO.4 VLX,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"0d0f2200000000000000504944564944,REAL ARCADE Pro.V3,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"00f00300000000000000504944564944,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows,",
|
||||
"00f0f100000000000000504944564944,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows,",
|
||||
"6f0e1e01000000000000504944564944,Rock Candy Gamepad for PS3,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
|
||||
"300f1201000000000000504944564944,Saitek Dual Analog Pad,a:b2,b:b3,x:b0,y:b1,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b5,righttrigger:b7,platform:Windows,",
|
||||
"a3060cff000000000000504944564944,Saitek P2500,a:b2,b:b3,y:b1,x:b0,start:b4,guide:b10,back:b5,leftstick:b8,rightstick:b9,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows,",
|
||||
"300f1001000000000000504944564944,Saitek P480 Rumble Pad,a:b2,b:b3,x:b0,y:b1,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b5,righttrigger:b7,platform:Windows,",
|
||||
"9b280500000000000000504944564944,Saturn_Adapter_2.0,a:b1,b:b2,x:b0,y:b3,start:b9,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,lefttrigger:b4,righttrigger:b5,platform:Windows,",
|
||||
"79001100000000000000504944564944,Sega Saturn Gamepad,a:b1,b:b2,x:b4,y:b5,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a4,lefttrigger:b3,righttrigger:b0,platform:Windows,",
|
||||
"4c05cc09000000000000504944564944,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
|
||||
"4c05a00b000000000000504944564944,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows,",
|
||||
"ff113133000000000000504944564944,SVEN X-PAD,platform:Windows,a:b2,b:b3,y:b1,x:b0,start:b5,back:b4,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b8,righttrigger:b9,",
|
||||
"4f0415b3000000000000504944564944,Thrustmaster Dual Analog 3.2,platform:Windows,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"4f0400b3000000000000504944564944,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Windows,",
|
||||
"66660488000000000000504944564944,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:b12,dpdown:b14,dpleft:b15,dpright:b13,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,platform:Windows,",
|
||||
"38076652000000000000504944564944,UnKnown,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"63252305000000000000504944564944,USB Vibration Joystick (BM),platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"79001b18000000000000504944564944,Venom Arcade Joystick,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,lefttrigger:b6,righttrigger:b7,platform:Windows,",
|
||||
"10280000000000000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,",
|
||||
"79000000000000000600000000000000,G-Shark GP-702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Mac OS X,",
|
||||
"AD1B00000000000001F9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,",
|
||||
"0d0f0000000000004d00000000000000,HORI Gem Pad 3,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
|
||||
"0d0f0000000000006600000000000000,HORIPAD FPS PLUS 4,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:a4,",
|
||||
"83050000000000006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X,",
|
||||
"6d0400000000000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"6d0400000000000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"6d040000000000001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"6d0400000000000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"2509000000000000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Mac OS X,",
|
||||
"79000000000000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,x:b0,y:b12,back:b32,start:b36,leftstick:b40,rightstick:b44,leftshoulder:b16,rightshoulder:b20,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a4,rightx:a8,righty:a12,lefttrigger:b24,righttrigger:b28,platform:Mac OS X,",
|
||||
"d814000000000000cecf000000000000,MC Cthulhu,platform:Mac OS X,leftx:,lefty:,rightx:,righty:,lefttrigger:b6,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,righttrigger:b7,",
|
||||
"8f0e0000000000000300000000000000,Piranha xtreme,platform:Mac OS X,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
|
||||
"4c050000000000006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,",
|
||||
"4c05000000000000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"891600000000000000fd000000000000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b8,guide:b10,back:b9,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b11,dpleft:b13,dpdown:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Mac OS X,",
|
||||
"79000000000000001100000000000000,Retrolink Classic Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a3,lefty:a4,platform:Mac OS X,",
|
||||
"81170000000000007e05000000000000,Sega Saturn,x:b0,a:b2,b:b4,y:b6,start:b13,dpleft:b15,dpdown:b16,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,lefttrigger:b10,rightshoulder:b9,righttrigger:a4,righttrigger:b11,leftx:a0,lefty:a2,platform:Mac OS X,",
|
||||
"b4040000000000000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,x:b3,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,",
|
||||
"351200000000000021ab000000000000,SFC30 Joystick,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X,",
|
||||
"4c05000000000000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X,",
|
||||
"4c05000000000000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X,",
|
||||
"4f0400000000000015b3000000000000,Thrustmaster Dual Analog 3.2,platform:Mac OS X,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"4f0400000000000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Mac OS X,",
|
||||
"bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X,",
|
||||
"10080000000000000100000000000000,Twin USB Joystick,a:b4,b:b2,x:b6,y:b0,back:b16,start:b18,leftstick:b20,rightstick:b22,leftshoulder:b12,rightshoulder:b14,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a2,rightx:a6,righty:a4,lefttrigger:b8,righttrigger:b10,platform:Mac OS X,",
|
||||
"050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,y:b9,x:b10,start:b6,guide:b8,back:b7,dpup:b2,dpleft:b0,dpdown:b3,dpright:b1,leftx:a0,lefty:a1,lefttrigger:b12,righttrigger:,leftshoulder:b11,platform:Mac OS X,",
|
||||
"050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,x:b18,y:b17,back:b7,guide:b8,start:b6,leftstick:b23,rightstick:b24,leftshoulder:b19,rightshoulder:b20,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b21,righttrigger:b22,platform:Mac OS X,",
|
||||
"5e040000000000008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"5e04000000000000dd02000000000000,Xbox One Wired Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"5e04000000000000ea02000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"5e04000000000000e002000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b10,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"05000000102800000900000000010000,8Bitdo SFC30 GamePad,platform:Linux,x:b4,a:b1,b:b0,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
|
||||
"05000000a00500003232000001000000,8Bitdo Zero GamePad,platform:Linux,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
|
||||
"030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux,",
|
||||
"03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000006f0e00003001000001010000,EA Sports PS3 Controller,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
|
||||
"03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,y:b3,x:b0,start:b9,guide:,back:,leftstick:,rightstick:,leftshoulder:,dpleft:b15,dpdown:b14,dpright:b13,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,rightshoulder:b7,dpup:b12,platform:Linux,",
|
||||
"03000000260900008888000000010000,GameCube {WiseGroup USB box},a:b0,b:b2,y:b3,x:b1,start:b7,leftshoulder:,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,rightstick:,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Linux,",
|
||||
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"030000006f0e00001f01000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000006f0e00001304000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:a0,rightstick:a3,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"03000000280400000140000000010000,Gravis GamePad Pro USB ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1,",
|
||||
"030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
|
||||
"030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
|
||||
"03000000ff1100004133000010010000,GreenAsia Inc.USB Joystick,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
|
||||
"06000000adde0000efbe000002010000,Hidromancer Game Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,righttrigger:b7,leftx:a0,lefty:a1,",
|
||||
"03000000c9110000f055000011010000,HJC Game GAMEPAD,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b11,rightshoulder:b5,rightx:a2,start:b9,righty:a3,dpleft:h0.8,lefttrigger:b6,x:b2,dpup:h0.1,back:b8,leftstick:b10,leftshoulder:b4,y:b3,a:b0,dpright:h0.2,righttrigger:b7,b:b1,platform:Linux,",
|
||||
"030000000d0f00000d00000000010000,hori,platform:Linux,a:b0,b:b6,y:b2,x:b1,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,start:b9,guide:b10,back:b8,leftshoulder:b3,rightshoulder:b7,leftx:b4,lefty:b5,",
|
||||
"030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7",
|
||||
"030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,",
|
||||
"03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
|
||||
"03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,",
|
||||
"03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),platform:Linux,a:b3,b:b4,y:b1,x:b0,start:b7,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,",
|
||||
"030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,platform:Linux,x:b0,a:b2,b:b3,y:b1,back:b10,guide:b12,start:b11,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,platform:Linux,x:b0,a:b2,b:b3,y:b1,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,",
|
||||
"03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"030000006f0e00000103000000020000,Logic3 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006d04000016c2000011010000,Logitech F310 Gamepad (DInput),x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux,",
|
||||
"03000000fa2d00000100000000000000,3DRUDDER,leftx:a0,lefty:a1,rightx:a5,righty:a2,platform:Windows,",
|
||||
"03000000022000000090000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,",
|
||||
"03000000203800000900000000000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b4,y:b3,platform:Windows,",
|
||||
"03000000102800000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Windows,",
|
||||
"03000000a00500003232000000000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Windows,",
|
||||
"030000008f0e00001200000000000000,Acme,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000341a00003608000000000000,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000c01100001352000000000000,Battalife Joystick,a:b6,b:b7,back:b2,leftshoulder:b0,leftx:a0,lefty:a1,rightshoulder:b1,start:b3,x:b4,y:b5,platform:Windows,",
|
||||
"030000006b1400000055000000000000,bigben ps3padstreetnew,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"0300000066f700000500000000000000,BrutalLegendTest,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000d81d00000b00000000000000,BUFFALO BSGP1601 Series ,a:b5,b:b3,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b8,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b9,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b13,x:b4,y:b2,platform:Windows,",
|
||||
"03000000e82000006058000000000000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"030000005e0400008e02000000000000,Controller (XBOX 360 For Windows),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,",
|
||||
"03000000260900008888000000000000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a4,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Windows,",
|
||||
"03000000a306000022f6000000000000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000791d00000103000000000000,Dual Box WII,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"030000004f04000023b3000000000000,Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000341a00000108000000000000,EXEQ RF USB Gamepad 8206,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"030000000d0f00008500000000000000,Fighting Commander 2016 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00008400000000000000,Fighting Commander 5,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00008800000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00008700000000000000,Fighting Stick mini 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00002700000000000000,FIGHTING STICK V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"78696e70757403000000000000000000,Fightstick TES,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Windows,",
|
||||
"03000000790000000600000000000000,G-Shark GS-GP702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000260900002625000000000000,Gamecube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,lefttrigger:a4,leftx:a0,lefty:a1,righttrigger:a5,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Windows,",
|
||||
"030000008f0e00000d31000000000000,GAMEPAD 3 TURBO,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000280400000140000000000000,GamePad Pro USB,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000ffff00000000000000000000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000451300000010000000000000,Generic USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000341a00000302000000000000,Hama Scorpad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00004900000000000000,Hatsune Miku Sho Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000d81400000862000000000000,HitBox Edition Cthulhu+,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00005f00000000000000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00005e00000000000000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00004000000000000000,Hori Fighting Stick Mini 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b4,rightshoulder:b7,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00006e00000000000000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00006600000000000000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f0000ee00000000000000,HORIPAD mini4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00004d00000000000000,HORIPAD3 A,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000250900000017000000000000,HRAP2 on PS/SS/N64 Joypad to USB BOX,a:b2,b:b1,back:b9,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b8,x:b3,y:b0,platform:Windows,",
|
||||
"030000008f0e00001330000000000000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000d81d00000f00000000000000,iBUFFALO BSGP1204 Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000d81d00001000000000000000,iBUFFALO BSGP1204P Series,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000830500006020000000000000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Windows,",
|
||||
"03000000b50700001403000000000000,IMPACT BLACK,a:b2,b:b3,back:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
|
||||
"030000006f0e00002401000000000000,INJUSTICE FightStick for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000491900000204000000000000,Ipega PG-9023,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a3,righty:a4,start:b11,x:b3,y:b4,platform:Windows,",
|
||||
"030000006d04000011c2000000000000,Logitech Cordless Wingman,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b5,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b2,righttrigger:b7,rightx:a3,righty:a4,x:b4,platform:Windows,",
|
||||
"030000006d04000016c2000000000000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000006d04000018c2000000000000,Logitech F510 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000006d04000019c2000000000000,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700005032000000000000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700005082000000000000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700008433000000000000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700008483000000000000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b6,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700008134000000000000,Mad Catz FightStick TE2+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b4,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700008184000000000000,Mad Catz FightStick TE2+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,leftstick:b10,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700008034000000000000,Mad Catz TE2 PS3 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700008084000000000000,Mad Catz TE2 PS4 Fightstick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700008532000000000000,Madcatz Arcade Fightstick TE S PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700003888000000000000,Madcatz Arcade Fightstick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000380700001888000000000000,MadCatz SFIV FightStick PS3,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b5,lefttrigger:b7,leftx:a0,lefty:a1,rightshoulder:b4,righttrigger:b6,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000380700008081000000000000,MADCATZ SFV Arcade FightStick Alpha PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000008305000031b0000000000000,MaxfireBlaze3,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000250900000128000000000000,Mayflash Arcade Stick,a:b1,b:b2,back:b8,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b5,y:b6,platform:Windows,",
|
||||
"03000000790000004418000000000000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000790000004318000000000000,Mayflash GameCube Controller Adapter,a:b1,b:b2,back:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b0,leftshoulder:b4,leftstick:b0,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b0,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000008f0e00001030000000000000,Mayflash USB Adapter for original Sega Saturn controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b5,rightshoulder:b2,righttrigger:b7,start:b9,x:b3,y:b4,platform:Windows,",
|
||||
"0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Windows,",
|
||||
"03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000001008000001e5000000000000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000bd12000015d0000000000000,Nintendo Retrolink USB Super SNES Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"030000004b120000014d000000000000,NYKO AIRFLO,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:a3,leftstick:a0,lefttrigger:b6,leftx:h0.6,lefty:h0.12,rightshoulder:b5,rightstick:a2,righttrigger:b7,rightx:h0.9,righty:h0.4,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000362800000100000000000000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:b13,rightx:a3,righty:a4,x:b1,y:b2,platform:Windows,",
|
||||
"03000000120c0000f60e000000000000,P4 Wired Gamepad,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b7,rightshoulder:b4,righttrigger:b6,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000d62000006dca000000000000,PowerA Pro Ex,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000008f0e00007530000000000000,PS (R) Gamepad,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b1,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000e30500009605000000000000,PS to USB convert cable,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,",
|
||||
"03000000100800000100000000000000,PS1 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000100800000300000000000000,PS2 USB,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a4,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000888800000803000000000000,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows,",
|
||||
"030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows,",
|
||||
"03000000250900000500000000000000,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows,",
|
||||
"03000000100000008200000000000000,PS360+ v1.66,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:h0.4,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000004c050000a00b000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000004c050000cc09000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000300f00000011000000000000,QanBa Arcade JoyStick 1008,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b10,x:b0,y:b3,platform:Windows,",
|
||||
"03000000300f00001611000000000000,QanBa Arcade JoyStick 4018,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b8,x:b0,y:b3,platform:Windows,",
|
||||
"03000000222c00000020000000000000,QANBA DRONE ARCADE JOYSTICK,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,rightshoulder:b5,righttrigger:a4,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000300f00001210000000000000,QanBa Joystick Plus,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000341a00000104000000000000,QanBa Joystick Q4RAF,a:b5,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b0,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b3,righttrigger:b7,start:b9,x:b1,y:b2,platform:Windows,",
|
||||
"03000000222c00000223000000000000,Qanba Obsidian Arcade Joystick PS3 Mode,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000222c00000023000000000000,Qanba Obsidian Arcade Joystick PS4 Mode,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000321500000003000000000000,Razer Hydra,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a2,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,",
|
||||
"030000000d0f00001100000000000000,REAL ARCADE PRO.3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00008b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00008a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00006b00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00006a00000000000000,Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00007000000000000000,REAL ARCADE PRO.4 VLX,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,rightshoulder:b5,rightstick:b11,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00002200000000000000,REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00005c00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000000d0f00005b00000000000000,Real Arcade Pro.V4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000790000001100000000000000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"0300000000f000000300000000000000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,",
|
||||
"0300000000f00000f100000000000000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Windows,",
|
||||
"030000006b140000010d000000000000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000006f0e00001e01000000000000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000004f04000003d0000000000000,run'n'drive,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b7,leftshoulder:a3,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:a4,rightstick:b11,righttrigger:b5,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000a30600001af5000000000000,Saitek Cyborg,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000a306000023f6000000000000,Saitek Cyborg V.1 Game pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000300f00001201000000000000,Saitek Dual Analog Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
|
||||
"03000000a30600000cff000000000000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,x:b0,y:b1,platform:Windows,",
|
||||
"03000000a30600000c04000000000000,Saitek P2900,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000300f00001001000000000000,Saitek P480 Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
|
||||
"03000000a30600000b04000000000000,Saitek P990,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Windows,",
|
||||
"03000000300f00001101000000000000,saitek rumble pad,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
|
||||
"0300000000050000289b000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"030000009b2800000500000000000000,Saturn_Adapter_2.0,a:b1,b:b2,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000341a00000208000000000000,SL-6555-SBK,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:-a4,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a3,righty:a2,start:b7,x:b2,y:b3,platform:Windows,",
|
||||
"030000008f0e00000800000000000000,SpeedLink Strike FX Wireless,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000ff1100003133000000000000,SVEN X-PAD,a:b2,b:b3,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a4,start:b5,x:b0,y:b1,platform:Windows,",
|
||||
"03000000fa1900000706000000000000,Team 5,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000b50700001203000000000000,Techmobility X6-38V,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Windows,",
|
||||
"030000004f04000015b3000000000000,Thrustmaster Dual Analog 2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,",
|
||||
"030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Windows,",
|
||||
"030000004f04000004b3000000000000,Thrustmaster Firestorm Dual Power 3,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Windows,",
|
||||
"03000000666600000488000000000000,TigerGame PS/PS2 Game Controller Adapter,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Windows,",
|
||||
"03000000d90400000200000000000000,TwinShock PS2,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000380700006652000000000000,UnKnown,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000632500002305000000000000,USB Vibration Joystick (BM),a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Windows,",
|
||||
"03000000790000001b18000000000000,Venom Arcade Joystick,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Windows,",
|
||||
"03000000450c00002043000000000000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,",
|
||||
"03000000172700004431000000000000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a7,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Windows,",
|
||||
"03000000786901006e70000000000000,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Windows,",
|
||||
"03000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,",
|
||||
"03000000022000000090000001000000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,",
|
||||
"03000000102800000900000000000000,8Bitdo SFC30 GamePad Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,",
|
||||
"03000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Mac OS X,",
|
||||
"030000008305000031b0000000000000,Cideko AK08b,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"03000000260900008888000088020000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000a306000022f6000001030000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"03000000790000000600000000000000,G-Shark GP-702,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Mac OS X,",
|
||||
"03000000ad1b000001f9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00005f00000000010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00005e00000000010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00005f00000000000000,HORI Fighting Commander 4 PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00005e00000000000000,HORI Fighting Commander 4 PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00004d00000000000000,HORI Gem Pad 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00006e00000000010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00006600000000010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000000d0f00006600000000000000,HORIPAD FPS PLUS 4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000008f0e00001330000011010000,HuiJia SNES Controller,a:b4,b:b2,back:b16,dpdown:+a2,dpleft:-a0,dpright:+a0,dpup:-a2,leftshoulder:b12,rightshoulder:b14,start:b18,x:b6,y:b0,platform:Mac OS X,",
|
||||
"03000000830500006020000000010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,",
|
||||
"03000000830500006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Mac OS X,",
|
||||
"030000006d04000016c2000000020000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000006d04000016c2000000030000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000006d04000016c2000014040000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000006d04000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000006d04000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000006d0400001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000006d04000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"03000000380700005032000000010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"03000000380700005082000000010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"03000000790000004418000000010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"0300000025090000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,back:b8,dpdown:b13,dpleft:b12,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Mac OS X,",
|
||||
"03000000790000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,back:b32,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b16,leftstick:b40,lefttrigger:b24,leftx:a0,lefty:a4,rightshoulder:b20,rightstick:b44,righttrigger:b28,rightx:a8,righty:a12,start:b36,x:b0,y:b12,platform:Mac OS X,",
|
||||
"03000000d8140000cecf000000000000,MC Cthulhu,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000001008000001e5000006010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Mac OS X,",
|
||||
"030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000008f0e00000300000000000000,Piranha xtreme,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Mac OS X,",
|
||||
"030000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,",
|
||||
"030000004c0500006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X,",
|
||||
"030000004c050000a00b000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000004c050000c405000000000000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000008916000000fd000000000000,Razer Onza TE,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000321500000010000000010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"0300000032150000030a000000000000,Razer Wildcat,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000790000001100000000000000,Retrolink Classic Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a3,lefty:a4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
|
||||
"03000000790000001100000006010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a4,dpleft:-a3,dpright:+a3,dpup:-a4,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
|
||||
"030000006b140000010d000000010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"03000000811700007e05000000000000,Sega Saturn,a:b2,b:b4,dpdown:b16,dpleft:b15,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,leftx:a0,lefty:a2,rightshoulder:b9,righttrigger:a4,start:b13,x:b0,y:b6,platform:Mac OS X,",
|
||||
"03000000b40400000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Mac OS X,",
|
||||
"030000003512000021ab000000000000,SFC30 Joystick,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,",
|
||||
"030000004c050000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000004c050000a00b000000000000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"030000005e0400008e02000001000000,Steam Virtual GamePad,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000110100002014000000000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b12,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000110100002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000381000002014000001000000,SteelSeries Nimbus,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000110100001714000000000000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000110100001714000020010000,SteelSeries Stratus XL,a:b0,b:b1,dpdown:b9,dpleft:b11,dpright:b10,dpup:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1~,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3~,start:b12,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000004f04000015b3000000000000,Thrustmaster Dual Analog 3.2,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Mac OS X,",
|
||||
"030000004f04000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Mac OS X,",
|
||||
"03000000bd12000015d0000000010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
|
||||
"03000000bd12000015d0000000000000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Mac OS X,",
|
||||
"03000000100800000100000000000000,Twin USB Joystick,a:b4,b:b2,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b12,leftstick:b20,lefttrigger:b8,leftx:a0,lefty:a2,rightshoulder:b14,rightstick:b22,righttrigger:b10,rightx:a6,righty:a4,start:b18,x:b6,y:b0,platform:Mac OS X,",
|
||||
"050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,back:b7,dpdown:b3,dpleft:b0,dpright:b1,dpup:b2,guide:b8,leftshoulder:b11,lefttrigger:b12,leftx:a0,lefty:a1,start:b6,x:b10,y:b9,platform:Mac OS X,",
|
||||
"050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,back:b7,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b19,leftstick:b23,lefttrigger:b21,leftx:a0,lefty:a1,rightshoulder:b20,rightstick:b24,righttrigger:b22,rightx:a2,righty:a3,start:b6,x:b18,y:b17,platform:Mac OS X,",
|
||||
"030000005e0400008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000c6240000045d000000000000,Xbox 360 Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000005e040000e302000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000005e040000d102000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000005e040000dd02000000000000,Xbox One Wired Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000005e040000fd02000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b16,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,",
|
||||
"030000005e040000ea02000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X,",
|
||||
"030000005e040000e002000003090000,Xbox Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Mac OS X,",
|
||||
"03000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b15,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Mac OS X,",
|
||||
"03000000120c0000100e000000010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Mac OS X,",
|
||||
"05000000203800000900000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
|
||||
"03000000022000000090000011010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
|
||||
"05000000c82d00002038000000010000,8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
|
||||
"03000000c82d00000190000011010000,8Bitdo NES30 Pro 8Bitdo NES30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Linux,",
|
||||
"05000000c82d00003028000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,",
|
||||
"05000000102800000900000000010000,8Bitdo SFC30 GamePad,a:b1,b:b0,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Linux,",
|
||||
"05000000a00500003232000008010000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,",
|
||||
"05000000a00500003232000001000000,8Bitdo Zero GamePad,a:b0,b:b1,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b11,x:b3,y:b4,platform:Linux,",
|
||||
"030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"05000000050b00000045000031000000,ASUS Gamepad,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b10,x:b2,y:b3,platform:Linux,",
|
||||
"03000000666600006706000000010000,boom PSX to PC Converter,a:b2,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a2,righty:a3,start:b11,x:b3,y:b0,platform:Linux,",
|
||||
"03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,platform:Linux,",
|
||||
"03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"030000006f0e00003001000001010000,EA Sports PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"030000006f0e00000104000000010000,Gamestop Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000006f0e00001304000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:a0,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:a3,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000006f0e00001f01000000010000,Generic X-Box pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000280400000140000000010000,Gravis GamePad Pro USB ,a:b1,b:b2,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000008f0e00000610000000010000,GreenAsia Electronics 4Axes 12Keys GamePad ,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b9,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b10,righttrigger:b5,rightx:a3,righty:a2,start:b11,x:b3,y:b0,platform:Linux,",
|
||||
"030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"0500000047532067616d657061640000,GS gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"06000000adde0000efbe000002010000,Hidromancer Game Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,a:b1,b:b2,back:b8,guide:b9,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b12,x:b0,y:b3,platform:Linux,",
|
||||
"03000000c9110000f055000011010000,HJC Game GAMEPAD,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"030000000d0f00000d00000000010000,hori,a:b0,b:b6,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftx:b4,lefty:b5,rightshoulder:b7,start:b9,x:b1,y:b2,platform:Linux,",
|
||||
"030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000000d0f00006a00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000000d0f00006b00000011010000,HORI CO. LTD. Real Arcade Pro.4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000000d0f00005f00000011010000,Hori Fighting Commander 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000000d0f00005e00000011010000,Hori Fighting Commander 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000000d0f00006e00000011010000,HORIPAD 4 (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000000d0f00006600000011010000,HORIPAD 4 (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000000d0f00006700000001010000,HORIPAD ONE,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000008f0e00001330000010010000,HuiJia SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"03000000830500006020000010010000,iBuffalo SNES Controller,a:b1,b:b0,back:b6,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,",
|
||||
"050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,",
|
||||
"03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),a:b3,b:b4,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5,start:b7,x:b0,y:b1,platform:Linux,",
|
||||
"030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,a:b2,b:b3,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a2,righty:a3,start:b11,x:b0,y:b1,platform:Linux,",
|
||||
"03000000300f00001001000010010000,Jess Tech Dual Analog Rumble Pad,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,platform:Linux,",
|
||||
"03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"030000006f0e00000103000000020000,Logic3 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006d04000016c2000011010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006d04000016c2000010010000,Logitech Dual Action,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000006d04000016c2000010010000,Logitech Logitech Dual Action,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"030000006d04000018c2000010010000,Logitech Logitech RumblePad 2 USB,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,y:b4,x:b3,start:b8,guide:b5,back:b2,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b9,righttrigger:b10,platform:Linux,",
|
||||
"05000000380700006652000025010000,Mad Catz C.T.R.L.R ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,lefttrigger:a2,righttrigger:a5,",
|
||||
"03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,",
|
||||
"03000000780000000600000010010000,Microntek USB Joystick,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftx:a0,lefty:a1,",
|
||||
"030000005e0400008e02000004010000,Microsoft X-Box 360 pad,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
|
||||
"030000005e0400008e02000062230000,Microsoft X-Box 360 pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000005e040000d102000001010000,Microsoft X-Box One pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000005e040000dd02000003020000,Microsoft X-Box One pad v2,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux,",
|
||||
"030000005e0400008502000000010000,Microsoft X-Box pad (Japan),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"05000000d6200000ad0d000001000000,Moga Pro,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4,",
|
||||
"030000001008000001e5000010010000,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Linux,",
|
||||
"050000007e0500003003000001000000,Nintendo Wii U Pro Controller,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b8,start:b9,guide:b10,leftshoulder:b4,rightshoulder:b5,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:b13,dpleft:b15,dpdown:b14,dpright:b16,",
|
||||
"05000000010000000100000003000000,Nintendo Wiimote,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
|
||||
"03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Linux,",
|
||||
"05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
|
||||
"05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,",
|
||||
"03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux,",
|
||||
"030000006d04000015c2000010010000,Logitech Logitech Extreme 3D,a:b0,b:b4,back:b6,guide:b8,leftshoulder:b9,leftstick:h0.8,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:h0.2,start:b7,x:b2,y:b5,platform:Linux,",
|
||||
"030000006d04000018c2000010010000,Logitech RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,back:b2,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b6,lefttrigger:b9,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b10,rightx:a3,righty:a4,start:b8,x:b3,y:b4,platform:Linux,",
|
||||
"05000000380700006652000025010000,Mad Catz C.T.R.L.R ,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000380700005032000011010000,Mad Catz FightPad PRO (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000380700005082000011010000,Mad Catz FightPad PRO (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000380700008034000011010000,Mad Catz fightstick (PS3),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000380700008084000011010000,Mad Catz fightstick (PS4),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000380700008433000011010000,Mad Catz FightStick TE S+ PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000380700008483000011010000,Mad Catz FightStick TE S+ PS4,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000380700003847000090040000,Mad Catz Wired Xbox 360 Controller (SFIV),a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000380700001888000010010000,MadCatz PC USB Wired Stick 8818,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000380700003888000010010000,MadCatz PC USB Wired Stick 8838,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:a0,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000790000004418000010010000,Mayflash GameCube Controller,a:b1,b:b2,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:a4,rightx:a5,righty:a2,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000780000000600000010010000,Microntek USB Joystick,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"030000005e0400008e02000004010000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e0400008e02000062230000,Microsoft X-Box 360 pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e040000d102000001010000,Microsoft X-Box One pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e040000d102000003020000,Microsoft X-Box One pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e0400008502000000010000,Microsoft X-Box pad (Japan),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,",
|
||||
"030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,",
|
||||
"05000000d6200000ad0d000001000000,Moga Pro,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b7,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Linux,",
|
||||
"030000001008000001e5000010010000,NEXT SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b6,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"050000007e0500000920000001000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"050000007e0500003003000001000000,Nintendo Wii Remote Pro Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"05000000010000000100000003000000,Nintendo Wiimote,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"030000000d0500000308000010010000,Nostromo n45 Dual Analog Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Linux,",
|
||||
"03000000550900001072000011010000,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b13,leftshoulder:b4,leftstick:b8,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000451300000830000010010000,NYKO CORE,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000005e0400000202000000010000,Old Xbox pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,",
|
||||
"05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,",
|
||||
"05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2,platform:Linux,",
|
||||
"03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"030000006f0e00006401000001010000,PDP Battlefield One,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000ff1100004133000010010000,PS2 Controller,a:b2,b:b1,back:b8,leftshoulder:b6,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b5,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"030000004c0500006802000010010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
|
||||
"050000004c0500006802000000810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"03000000341a00003608000011010000,PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000004c0500006802000011810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"050000004c0500006802000000010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:a12,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:a13,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
|
||||
"030000004c0500006802000010810000,PS3 Controller,a:b0,b:b1,back:b8,dpdown:b14,dpleft:b15,dpright:b16,dpup:b13,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
|
||||
"060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,",
|
||||
"050000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,",
|
||||
"05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux,",
|
||||
"030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,platform:Linux,x:b1,a:b0,b:b4,y:b5,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
|
||||
"030000008916000001fd000024010000,Razer Onza Classic Edition,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:b11,dpdown:b14,dpright:b12,dpup:b13,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
|
||||
"03000000790000001100000010010000,RetroLink Saturn Classic Controller,platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,",
|
||||
"0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux,",
|
||||
"0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux,",
|
||||
"030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,",
|
||||
"030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,platform:Linux,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,guide:b8,leftstick:b9,rightstick:b10,lefttrigger:a2,righttrigger:a5,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Linux,",
|
||||
"03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux,",
|
||||
"03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,y:b1,x:b0,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b6,righttrigger:b7,platform:Linux,",
|
||||
"03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,platform:Linux,a:b12,b:b10,x:b13,y:b11,back:b4,start:b5,leftstick:b14,rightstick:b15,leftshoulder:b9,rightshoulder:b8,dpup:b0,dpdown:b2,dpleft:b3,dpright:b1,leftx:a1,lefty:a0,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
|
||||
"030000004c050000c405000011810000,Sony DualShock 4,a:b0,b:b1,y:b2,x:b3,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
|
||||
"030000004c050000c405000011010000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Linux,",
|
||||
"050000004c050000cc09000000810000,Sony DualShock 4 (CUH-ZCT2U) (Bluetooth),platform:Linux,a:b0,b:b1,y:b2,x:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,guide:b10,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
|
||||
"030000004c050000cc09000011810000,Sony DualShock 4 (CUH-ZCT2U) (USB),platform:Linux,a:b0,b:b1,y:b2,x:b3,leftshoulder:b4,rightshoulder:b5,back:b8,start:b9,guide:b10,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,lefttrigger:a2,rightx:a3,righty:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
|
||||
"050000004c050000c405000000010000,Sony DualShock 4 BT,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000004c050000cc09000011010000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,",
|
||||
"050000004c050000cc09000000010000,Sony DualShock 4 V2 BT,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,",
|
||||
"030000004c050000a00b000011010000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux,",
|
||||
"03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,platform:Linux,a:b2,b:b1,y:b0,x:b3,start:b8,back:b9,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,",
|
||||
"030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,",
|
||||
"03000000666600000488000000010000,Super Joy Box 5 Pro,platform:Linux,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,dpup:b12,dpleft:b15,dpdown:b14,dpright:b13,",
|
||||
"030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,y:b3,x:b1,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux,",
|
||||
"030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,platform:Linux,a:b0,b:b2,x:b1,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,",
|
||||
"030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5,",
|
||||
"030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux,",
|
||||
"030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,",
|
||||
"030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,",
|
||||
"03000000bd12000015d0000010010000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux,",
|
||||
"03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,platform:Linux,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7,",
|
||||
"03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,y:b0,x:b3,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux,",
|
||||
"03000000100800000300000010010000,USB Gamepad,platform:Linux,a:b2,b:b1,x:b3,y:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,",
|
||||
"03000000de280000ff11000001000000,Valve Streaming Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"05000000ac0500003232000001000000,VR-BOX,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,",
|
||||
"060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
|
||||
"05000000504c415953544154494f4e00,PS3 Controller (Bluetooth),a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux,",
|
||||
"050000004c050000c405000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000004c050000a00b000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"050000004c050000cc09000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"050000004c050000c405000000810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"030000004c050000c405000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"050000004c050000cc09000000010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000004c050000cc09000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000004c050000a00b000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"030000004c050000cc09000011810000,PS4 Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b11,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b12,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"030000004c050000c405000011010000,PS4 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000300f00001211000011010000,QanBa Arcade JoyStick,a:b2,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b5,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b6,start:b9,x:b1,y:b3,platform:Linux,",
|
||||
"030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,platform:Linux,",
|
||||
"030000008916000001fd000024010000,Razer Onza Classic Edition,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000321500000010000011010000,Razer RAIJU,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000c6240000045d000025010000,Razer Sabertooth,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000321500000009000011010000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"050000003215000000090000163a0000,Razer Serval,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"0300000032150000030a000001010000,Razer Wildcat,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000790000001100000010010000,Retrolink SNES Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,",
|
||||
"0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b0,y:b4,platform:Linux,",
|
||||
"030000006b140000010d000011010000,Revolution Pro Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a4,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000a30600000cff000010010000,Saitek P2500 Force Rumble Pad,a:b2,b:b3,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,",
|
||||
"03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b12,x:b0,y:b3,platform:Linux,",
|
||||
"03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b8,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:b7,rightx:a3,righty:a2,x:b0,y:b1,platform:Linux,",
|
||||
"03000000a30600000b04000000010000,Saitek P990 Dual Analog Pad,a:b1,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b8,x:b0,y:b3,platform:Linux,",
|
||||
"03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000c01600008704000011010000,Serial/Keyboard/Mouse/Joystick,a:b12,b:b10,back:b4,dpdown:b2,dpleft:b3,dpright:b1,dpup:b0,leftshoulder:b9,leftstick:b14,lefttrigger:b6,leftx:a1,lefty:a0,rightshoulder:b8,rightstick:b15,righttrigger:b7,rightx:a2,righty:a3,start:b5,x:b13,y:b11,platform:Linux,",
|
||||
"03000000f025000021c1000010010000,ShanWan Gioteck PS3 Wired Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,a:b2,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,",
|
||||
"030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000de2800000211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000de2800000112000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000de280000fc11000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000de2800004211000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000666600000488000000010000,Super Joy Box 5 Pro,a:b2,b:b1,back:b9,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b3,y:b0,platform:Linux,",
|
||||
"030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,",
|
||||
"030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,a:b0,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b1,y:b3,platform:Linux,",
|
||||
"030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b11,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b12,righttrigger:b7,rightx:a2,righty:a3,start:b10,x:b1,y:b3,platform:Linux,",
|
||||
"030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"03000000bd12000015d0000010010000,Tomee SNES USB Controller,a:b2,b:b1,back:b8,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b4,rightshoulder:b5,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,a:b0,b:b1,back:b8,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,start:b9,x:b3,y:b2,platform:Linux,",
|
||||
"03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"03000000100800000300000010010000,USB Gamepad,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux,",
|
||||
"03000000790000001100000000010000,USB Gamepad1,a:b2,b:b1,back:b8,dpdown:a0,dpleft:a1,dpright:a2,dpup:a4,start:b9,platform:Linux,",
|
||||
"05000000ac0500003232000001000000,VR-BOX,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a3,righty:a2,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,",
|
||||
"030000005e040000a102000000010000,X360 Wireless Controller,platform:Linux,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
|
||||
"0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b7,back:b6,guide:b8,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,lefttrigger:a5,righttrigger:a4,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a2,righty:a3,",
|
||||
"030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e040000a102000007010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"030000005e040000a102000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"0000000058626f782033363020576900,Xbox 360 Wireless Controller,a:b0,b:b1,back:b14,dpdown:b11,dpleft:b12,dpright:b13,dpup:b10,guide:b7,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:Linux,",
|
||||
"0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a4,rightx:a2,righty:a3,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"050000005e040000e002000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b4,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,",
|
||||
"03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,platform:Linux,y:b0,x:b1,b:b3,a:b4,leftshoulder:b2,rightshoulder:b5,back:b6,start:b7,guide:b9,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftx:a0,lefty:a1,",
|
||||
"050000005e040000fd02000003090000,Xbox One Wireless Controller,a:b0,b:b1,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b16,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,",
|
||||
"03000000450c00002043000010010000,XEOX Gamepad SL-6556-BK,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,",
|
||||
"05000000172700004431000029010000,XiaoMi Game Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b20,leftshoulder:b6,leftstick:b13,lefttrigger:a7,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a6,rightx:a2,righty:a5,start:b11,x:b3,y:b4,platform:Linux,",
|
||||
"03000000c0160000e105000001010000,Xin-Mo Xin-Mo Dual Arcade,a:b4,b:b3,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b9,leftshoulder:b2,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b1,y:b0,platform:Linux,",
|
||||
"03000000120c0000100e000011010000,ZEROPLUS P4 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux,",
|
||||
"64633436313965656664373634323364,Microsoft X-Box 360 pad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,x:b2,y:b3,platform:Android,",
|
||||
"61363931656135336130663561616264,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,",
|
||||
"4e564944494120436f72706f72617469,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,",
|
||||
"37336435666338653565313731303834,NVIDIA Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,platform:Android,",
|
||||
"35643031303033326130316330353564,PS4 Controller,a:b1,b:b17,back:b15,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b3,leftstick:b4,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b6,righttrigger:+a4,rightx:a2,righty:a5,start:b16,x:b0,y:b2,platform:Android,",
|
||||
"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:Android,",
|
||||
"5477696e20555342204a6f7973746963,Twin USB Joystick,a:b22,b:b21,back:b28,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b26,leftstick:b30,lefttrigger:b24,leftx:a0,lefty:a1,rightshoulder:b27,rightstick:b31,righttrigger:b25,rightx:a3,righty:a2,start:b29,x:b23,y:b20,platform:Android,",
|
||||
"34356136633366613530316338376136,Xbox Wireless Controller,a:b0,b:b1,back:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b10,leftshoulder:b3,leftstick:b15,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b18,rightstick:b16,righttrigger:a5,rightx:a3,righty:a4,x:b17,y:b2,platform:Android,",
|
||||
"4d466947616d65706164010000000000,MFi Extended Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,platform:iOS,",
|
||||
"4d466947616d65706164020000000000,MFi Gamepad,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,rightshoulder:b5,start:b6,x:b2,y:b3,platform:iOS,",
|
||||
"05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,platform:iOS,",
|
||||
|
||||
"78696e70757401000000000000000000,XInput Gamepad (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
|
||||
"78696e70757402000000000000000000,XInput Wheel (GLFW),platform:Windows,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8,",
|
||||
|
||||
2
glfw/monitor.c
vendored
2
glfw/monitor.c
vendored
@@ -177,6 +177,8 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor)
|
||||
if (monitor == NULL)
|
||||
return;
|
||||
|
||||
_glfwPlatformFreeMonitor(monitor);
|
||||
|
||||
_glfwFreeGammaArrays(&monitor->originalRamp);
|
||||
_glfwFreeGammaArrays(&monitor->currentRamp);
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ GLFWbool _glfwCreateContextNSGL(_GLFWwindow* window,
|
||||
// Info.plist for unbundled applications
|
||||
// HACK: This assumes that NSOpenGLPixelFormat will remain
|
||||
// a straightforward wrapper of its CGL counterpart
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 100800
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080
|
||||
addAttrib(kCGLPFASupportsAutomaticGraphicsSwitching);
|
||||
#endif /*MAC_OS_X_VERSION_MAX_ALLOWED*/
|
||||
}
|
||||
|
||||
4
glfw/null_monitor.c
vendored
4
glfw/null_monitor.c
vendored
@@ -32,6 +32,10 @@
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
{
|
||||
}
|
||||
|
||||
3
glfw/null_window.c
vendored
3
glfw/null_window.c
vendored
@@ -205,7 +205,7 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
{
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window)
|
||||
{
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
@@ -323,4 +323,3 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
// This seems like the most appropriate error to return here
|
||||
return VK_ERROR_INITIALIZATION_FAILED;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,15 +55,16 @@
|
||||
"wayland": {
|
||||
"headers": [
|
||||
"wl_platform.h",
|
||||
"linux_joystick.h",
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
"xkb_unicode.h",
|
||||
"xkb_glfw.h",
|
||||
"egl_context.h",
|
||||
"osmesa_context.h"
|
||||
"osmesa_context.h",
|
||||
"linux_joystick.h"
|
||||
],
|
||||
"protocols": [
|
||||
"stable/xdg-shell/xdg-shell.xml",
|
||||
"stable/viewporter/viewporter.xml",
|
||||
"unstable/relative-pointer/relative-pointer-unstable-v1.xml",
|
||||
"unstable/pointer-constraints/pointer-constraints-unstable-v1.xml",
|
||||
"unstable/idle-inhibit/idle-inhibit-unstable-v1.xml"
|
||||
@@ -72,12 +73,12 @@
|
||||
"wl_init.c",
|
||||
"wl_monitor.c",
|
||||
"wl_window.c",
|
||||
"linux_joystick.c",
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"xkb_unicode.c",
|
||||
"xkb_glfw.c",
|
||||
"egl_context.c",
|
||||
"osmesa_context.c"
|
||||
"osmesa_context.c",
|
||||
"linux_joystick.c"
|
||||
]
|
||||
},
|
||||
"wayland_protocols": [
|
||||
@@ -107,7 +108,7 @@
|
||||
"x11": {
|
||||
"headers": [
|
||||
"x11_platform.h",
|
||||
"xkb_unicode.h",
|
||||
"xkb_glfw.h",
|
||||
"posix_time.h",
|
||||
"posix_thread.h",
|
||||
"glx_context.h",
|
||||
@@ -119,7 +120,7 @@
|
||||
"x11_init.c",
|
||||
"x11_monitor.c",
|
||||
"x11_window.c",
|
||||
"xkb_unicode.c",
|
||||
"xkb_glfw.c",
|
||||
"posix_time.c",
|
||||
"posix_thread.c",
|
||||
"glx_context.c",
|
||||
|
||||
9
glfw/vulkan.c
vendored
9
glfw/vulkan.c
vendored
@@ -54,7 +54,7 @@ GLFWbool _glfwInitVulkan(int mode)
|
||||
#elif defined(_GLFW_WIN32)
|
||||
_glfw.vk.handle = _glfw_dlopen("vulkan-1.dll");
|
||||
#elif defined(_GLFW_COCOA)
|
||||
_glfw.vk.handle = _glfw_dlopen("libMoltenVK.dylib");
|
||||
_glfw.vk.handle = _glfw_dlopen("libvulkan.1.dylib");
|
||||
#else
|
||||
_glfw.vk.handle = _glfw_dlopen("libvulkan.so.1");
|
||||
#endif
|
||||
@@ -317,6 +317,13 @@ GLFWAPI VkResult glfwCreateWindowSurface(VkInstance instance,
|
||||
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
||||
}
|
||||
|
||||
if (window->context.client != GLFW_NO_API)
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_VALUE,
|
||||
"Vulkan: Window surface creation requires the window to have the client API set to GLFW_NO_API");
|
||||
return VK_ERROR_NATIVE_WINDOW_IN_USE_KHR;
|
||||
}
|
||||
|
||||
return _glfwPlatformCreateWindowSurface(instance, window, allocator, surface);
|
||||
}
|
||||
|
||||
|
||||
17
glfw/win32_monitor.c
vendored
17
glfw/win32_monitor.c
vendored
@@ -241,7 +241,7 @@ void _glfwPollMonitorsWin32(void)
|
||||
|
||||
// Change the current video mode
|
||||
//
|
||||
GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
{
|
||||
GLFWvidmode current;
|
||||
const GLFWvidmode* best;
|
||||
@@ -251,7 +251,7 @@ GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desire
|
||||
best = _glfwChooseVideoMode(monitor, desired);
|
||||
_glfwPlatformGetVideoMode(monitor, ¤t);
|
||||
if (_glfwCompareVideoModes(¤t, best) == 0)
|
||||
return GLFW_TRUE;
|
||||
return;
|
||||
|
||||
ZeroMemory(&dm, sizeof(dm));
|
||||
dm.dmSize = sizeof(dm);
|
||||
@@ -270,7 +270,9 @@ GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desire
|
||||
NULL,
|
||||
CDS_FULLSCREEN,
|
||||
NULL);
|
||||
if (result != DISP_CHANGE_SUCCESSFUL)
|
||||
if (result == DISP_CHANGE_SUCCESSFUL)
|
||||
monitor->win32.modeChanged = GLFW_TRUE;
|
||||
else
|
||||
{
|
||||
const char* description = "Unknown error";
|
||||
|
||||
@@ -292,12 +294,7 @@ GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desire
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to set video mode: %s",
|
||||
description);
|
||||
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
monitor->win32.modeChanged = GLFW_TRUE;
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
// Restore the previously saved (original) video mode
|
||||
@@ -337,6 +334,10 @@ void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* ysc
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
{
|
||||
DEVMODEW dm;
|
||||
|
||||
4
glfw/win32_platform.h
vendored
4
glfw/win32_platform.h
vendored
@@ -120,6 +120,8 @@ typedef struct
|
||||
HRGN hRgnBlur;
|
||||
BOOL fTransitionOnMaximized;
|
||||
} DWM_BLURBEHIND;
|
||||
#else
|
||||
#include <dwmapi.h>
|
||||
#endif /*Windows Vista*/
|
||||
|
||||
#ifndef DPI_ENUMS_DECLARED
|
||||
@@ -398,7 +400,7 @@ void _glfwUpdateKeyNamesWin32(void);
|
||||
void _glfwInitTimerWin32(void);
|
||||
|
||||
void _glfwPollMonitorsWin32(void);
|
||||
GLFWbool _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwSetVideoModeWin32(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwRestoreVideoModeWin32(_GLFWmonitor* monitor);
|
||||
void _glfwGetMonitorContentScaleWin32(HMONITOR handle, float* xscale, float* yscale);
|
||||
|
||||
|
||||
197
glfw/win32_window.c
vendored
197
glfw/win32_window.c
vendored
@@ -235,26 +235,6 @@ static void centerCursor(_GLFWwindow* window)
|
||||
_glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0);
|
||||
}
|
||||
|
||||
// Returns whether the cursor is in the client area of the specified window
|
||||
//
|
||||
static GLFWbool cursorInClientArea(_GLFWwindow* window)
|
||||
{
|
||||
RECT area;
|
||||
POINT pos;
|
||||
|
||||
if (!GetCursorPos(&pos))
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (WindowFromPoint(pos) != window->win32.handle)
|
||||
return GLFW_FALSE;
|
||||
|
||||
GetClientRect(window->win32.handle, &area);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.right);
|
||||
|
||||
return PtInRect(&area, pos);
|
||||
}
|
||||
|
||||
// Updates the cursor image according to its cursor mode
|
||||
//
|
||||
static void updateCursorImage(_GLFWwindow* window)
|
||||
@@ -286,6 +266,67 @@ static void updateClipRect(_GLFWwindow* window)
|
||||
ClipCursor(NULL);
|
||||
}
|
||||
|
||||
// Apply disabled cursor mode to a focused window
|
||||
//
|
||||
static void disableCursor(_GLFWwindow* window)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, 0, window->win32.handle };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.win32.restoreCursorPosX,
|
||||
&_glfw.win32.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
centerCursor(window);
|
||||
updateClipRect(window);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to register raw input device");
|
||||
}
|
||||
}
|
||||
|
||||
// Exit disabled cursor mode for the specified window
|
||||
//
|
||||
static void enableCursor(_GLFWwindow* window)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = NULL;
|
||||
updateClipRect(NULL);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.win32.restoreCursorPosX,
|
||||
_glfw.win32.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to remove raw input device");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns whether the cursor is in the client area of the specified window
|
||||
//
|
||||
static GLFWbool cursorInClientArea(_GLFWwindow* window)
|
||||
{
|
||||
RECT area;
|
||||
POINT pos;
|
||||
|
||||
if (!GetCursorPos(&pos))
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (WindowFromPoint(pos) != window->win32.handle)
|
||||
return GLFW_FALSE;
|
||||
|
||||
GetClientRect(window->win32.handle, &area);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.left);
|
||||
ClientToScreen(window->win32.handle, (POINT*) &area.right);
|
||||
|
||||
return PtInRect(&area, pos);
|
||||
}
|
||||
|
||||
// Update native window styles to match attributes
|
||||
//
|
||||
static void updateWindowStyles(const _GLFWwindow* window)
|
||||
@@ -471,30 +512,29 @@ static int translateKey(WPARAM wParam, LPARAM lParam)
|
||||
return _glfw.win32.keycodes[HIWORD(lParam) & 0x1FF];
|
||||
}
|
||||
|
||||
static void fitToMonitor(_GLFWwindow* window)
|
||||
{
|
||||
MONITORINFO mi = { sizeof(mi) };
|
||||
GetMonitorInfo(window->monitor->win32.handle, &mi);
|
||||
SetWindowPos(window->win32.handle, HWND_TOPMOST,
|
||||
mi.rcMonitor.left,
|
||||
mi.rcMonitor.top,
|
||||
mi.rcMonitor.right - mi.rcMonitor.left,
|
||||
mi.rcMonitor.bottom - mi.rcMonitor.top,
|
||||
SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOCOPYBITS);
|
||||
}
|
||||
|
||||
// Make the specified window and its video mode active on its monitor
|
||||
//
|
||||
static GLFWbool acquireMonitor(_GLFWwindow* window)
|
||||
static void acquireMonitor(_GLFWwindow* window)
|
||||
{
|
||||
GLFWvidmode mode;
|
||||
GLFWbool status;
|
||||
int xpos, ypos;
|
||||
|
||||
if (!_glfw.win32.acquiredMonitorCount)
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED);
|
||||
if (!window->monitor->window)
|
||||
_glfw.win32.acquiredMonitorCount++;
|
||||
|
||||
status = _glfwSetVideoModeWin32(window->monitor, &window->videoMode);
|
||||
|
||||
_glfwPlatformGetVideoMode(window->monitor, &mode);
|
||||
_glfwPlatformGetMonitorPos(window->monitor, &xpos, &ypos);
|
||||
|
||||
SetWindowPos(window->win32.handle, HWND_TOPMOST,
|
||||
xpos, ypos, mode.width, mode.height,
|
||||
SWP_NOACTIVATE | SWP_NOCOPYBITS);
|
||||
|
||||
_glfwSetVideoModeWin32(window->monitor, &window->videoMode);
|
||||
_glfwInputMonitorWindow(window->monitor, window);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Remove the window and restore the original video mode
|
||||
@@ -576,7 +616,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
if (lParam == 0 && window->win32.frameAction)
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
window->win32.frameAction = GLFW_FALSE;
|
||||
}
|
||||
@@ -594,7 +634,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
break;
|
||||
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -602,7 +642,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
case WM_KILLFOCUS:
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
|
||||
enableCursor(window);
|
||||
|
||||
if (window->monitor && window->autoIconify)
|
||||
_glfwPlatformIconifyWindow(window);
|
||||
@@ -858,10 +898,10 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
case WM_ENTERSIZEMOVE:
|
||||
case WM_ENTERMENULOOP:
|
||||
{
|
||||
// HACK: Postpone cursor disabling while the user is moving or
|
||||
// resizing the window or using the menu
|
||||
// HACK: Enable the cursor while the user is moving or
|
||||
// resizing the window or using the window menu
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
|
||||
enableCursor(window);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -872,7 +912,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
// HACK: Disable the cursor once the user is done moving or
|
||||
// resizing the window or using the menu
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -901,7 +941,10 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
if (iconified)
|
||||
releaseMonitor(window);
|
||||
else
|
||||
{
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
}
|
||||
|
||||
window->win32.iconified = iconified;
|
||||
@@ -1240,11 +1283,8 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
{
|
||||
_glfwPlatformShowWindow(window);
|
||||
_glfwPlatformFocusWindow(window);
|
||||
if (!acquireMonitor(window))
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (wndconfig->centerCursor)
|
||||
centerCursor(window);
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
@@ -1360,7 +1400,10 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
if (window->monitor)
|
||||
{
|
||||
if (window->monitor->window == window)
|
||||
{
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1472,9 +1515,9 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
FlashWindow(window->win32.handle, TRUE);
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window)
|
||||
{
|
||||
return MessageBeep(0xFFFFFFFF & param) ? GLFW_TRUE : GLFW_FALSE;
|
||||
return MessageBeep(0xFFFFFFFF) ? GLFW_TRUE : GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformFocusWindow(_GLFWwindow* window)
|
||||
@@ -1495,7 +1538,10 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
if (monitor)
|
||||
{
|
||||
if (monitor->window == window)
|
||||
{
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1518,20 +1564,27 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
|
||||
if (monitor)
|
||||
{
|
||||
MONITORINFO mi = { sizeof(mi) };
|
||||
UINT flags = SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOCOPYBITS;
|
||||
|
||||
if (window->decorated)
|
||||
{
|
||||
DWORD style = GetWindowLongW(window->win32.handle, GWL_STYLE);
|
||||
UINT flags = SWP_FRAMECHANGED | SWP_SHOWWINDOW |
|
||||
SWP_NOACTIVATE | SWP_NOCOPYBITS |
|
||||
SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE;
|
||||
|
||||
style &= ~WS_OVERLAPPEDWINDOW;
|
||||
style |= getWindowStyle(window);
|
||||
SetWindowLongW(window->win32.handle, GWL_STYLE, style);
|
||||
SetWindowPos(window->win32.handle, HWND_TOPMOST, 0, 0, 0, 0, flags);
|
||||
flags |= SWP_FRAMECHANGED;
|
||||
}
|
||||
|
||||
acquireMonitor(window);
|
||||
|
||||
GetMonitorInfo(window->monitor->win32.handle, &mi);
|
||||
SetWindowPos(window->win32.handle, HWND_TOPMOST,
|
||||
mi.rcMonitor.left,
|
||||
mi.rcMonitor.top,
|
||||
mi.rcMonitor.right - mi.rcMonitor.left,
|
||||
mi.rcMonitor.bottom - mi.rcMonitor.top,
|
||||
flags);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1765,39 +1818,12 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
{
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, 0, window->win32.handle };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.win32.restoreCursorPosX,
|
||||
&_glfw.win32.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
updateClipRect(window);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to register raw input device");
|
||||
}
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
disableCursor(window);
|
||||
}
|
||||
else if (_glfw.win32.disabledCursorWindow == window)
|
||||
{
|
||||
const RAWINPUTDEVICE rid = { 0x01, 0x02, RIDEV_REMOVE, NULL };
|
||||
|
||||
_glfw.win32.disabledCursorWindow = NULL;
|
||||
updateClipRect(NULL);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.win32.restoreCursorPosX,
|
||||
_glfw.win32.restoreCursorPosY);
|
||||
|
||||
if (!RegisterRawInputDevices(&rid, 1, sizeof(rid)))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"Win32: Failed to remove raw input device");
|
||||
}
|
||||
}
|
||||
|
||||
if (cursorInClientArea(window))
|
||||
enableCursor(window);
|
||||
else if (cursorInClientArea(window))
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
@@ -2002,4 +2028,3 @@ GLFWAPI HWND glfwGetWin32Window(GLFWwindow* handle)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return window->win32.handle;
|
||||
}
|
||||
|
||||
|
||||
28
glfw/window.c
vendored
28
glfw/window.c
vendored
@@ -54,7 +54,7 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
||||
if (window->keys[key] == GLFW_PRESS)
|
||||
{
|
||||
const int scancode = _glfwPlatformGetKeyScancode(key);
|
||||
_glfwInputKey(window, key, scancode, GLFW_RELEASE, 0);
|
||||
_glfwInputKeyboard(window, key, scancode, GLFW_RELEASE, 0, "", 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,16 +182,6 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
wndconfig.title = title;
|
||||
ctxconfig.share = (_GLFWwindow*) share;
|
||||
|
||||
if (ctxconfig.share)
|
||||
{
|
||||
if (ctxconfig.client == GLFW_NO_API ||
|
||||
ctxconfig.share->context.client == GLFW_NO_API)
|
||||
{
|
||||
_glfwInputError(GLFW_NO_WINDOW_CONTEXT, NULL);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_glfwIsValidContextConfig(&ctxconfig))
|
||||
return NULL;
|
||||
|
||||
@@ -236,7 +226,16 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
}
|
||||
}
|
||||
|
||||
if (!window->monitor)
|
||||
if (window->monitor)
|
||||
{
|
||||
if (wndconfig.centerCursor)
|
||||
{
|
||||
int width, height;
|
||||
_glfwPlatformGetWindowSize(window, &width, &height);
|
||||
_glfwPlatformSetCursorPos(window, width / 2.0, height / 2.0);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wndconfig.visible)
|
||||
{
|
||||
@@ -769,14 +768,14 @@ GLFWAPI void glfwRequestWindowAttention(GLFWwindow* handle)
|
||||
_glfwPlatformRequestWindowAttention(window);
|
||||
}
|
||||
|
||||
GLFWAPI int glfwWindowBell(GLFWwindow* handle, int64_t param)
|
||||
GLFWAPI int glfwWindowBell(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
|
||||
|
||||
return _glfwPlatformWindowBell(window, param);
|
||||
return _glfwPlatformWindowBell(window);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwHideWindow(GLFWwindow* handle)
|
||||
@@ -1107,4 +1106,3 @@ GLFWAPI void glfwPostEmptyEvent(void)
|
||||
|
||||
_glfwPlatformPostEmptyEvent();
|
||||
}
|
||||
|
||||
|
||||
666
glfw/wl_init.c
vendored
666
glfw/wl_init.c
vendored
@@ -41,6 +41,39 @@ static inline int min(int n1, int n2)
|
||||
return n1 < n2 ? n1 : n2;
|
||||
}
|
||||
|
||||
static _GLFWwindow* findWindowFromDecorationSurface(struct wl_surface* surface, int* which)
|
||||
{
|
||||
int focus;
|
||||
_GLFWwindow* window = _glfw.windowListHead;
|
||||
if (!which)
|
||||
which = &focus;
|
||||
while (window)
|
||||
{
|
||||
if (surface == window->wl.decorations.top.surface)
|
||||
{
|
||||
*which = topDecoration;
|
||||
break;
|
||||
}
|
||||
if (surface == window->wl.decorations.left.surface)
|
||||
{
|
||||
*which = leftDecoration;
|
||||
break;
|
||||
}
|
||||
if (surface == window->wl.decorations.right.surface)
|
||||
{
|
||||
*which = rightDecoration;
|
||||
break;
|
||||
}
|
||||
if (surface == window->wl.decorations.bottom.surface)
|
||||
{
|
||||
*which = bottomDecoration;
|
||||
break;
|
||||
}
|
||||
window = window->next;
|
||||
}
|
||||
return window;
|
||||
}
|
||||
|
||||
static void pointerHandleEnter(void* data,
|
||||
struct wl_pointer* pointer,
|
||||
uint32_t serial,
|
||||
@@ -52,8 +85,16 @@ static void pointerHandleEnter(void* data,
|
||||
if (!surface)
|
||||
return;
|
||||
|
||||
int focus = 0;
|
||||
_GLFWwindow* window = wl_surface_get_user_data(surface);
|
||||
if (!window)
|
||||
{
|
||||
window = findWindowFromDecorationSurface(surface, &focus);
|
||||
if (!window)
|
||||
return;
|
||||
}
|
||||
|
||||
window->wl.decorations.focus = focus;
|
||||
_glfw.wl.pointerSerial = serial;
|
||||
_glfw.wl.pointerFocus = window;
|
||||
|
||||
@@ -80,6 +121,39 @@ static void pointerHandleLeave(void* data,
|
||||
_glfwInputCursorEnter(window, GLFW_FALSE);
|
||||
}
|
||||
|
||||
static void setCursor(const char* name)
|
||||
{
|
||||
struct wl_buffer* buffer;
|
||||
struct wl_cursor* cursor;
|
||||
struct wl_cursor_image* image;
|
||||
struct wl_surface* surface = _glfw.wl.cursorSurface;
|
||||
|
||||
cursor = wl_cursor_theme_get_cursor(_glfw.wl.cursorTheme,
|
||||
name);
|
||||
if (!cursor)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Standard cursor not found");
|
||||
return;
|
||||
}
|
||||
image = cursor->images[0];
|
||||
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void pointerHandleMotion(void* data,
|
||||
struct wl_pointer* pointer,
|
||||
uint32_t time,
|
||||
@@ -87,6 +161,7 @@ static void pointerHandleMotion(void* data,
|
||||
wl_fixed_t sy)
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
const char* cursorName;
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
@@ -99,13 +174,47 @@ static void pointerHandleMotion(void* data,
|
||||
window->wl.cursorPosY = wl_fixed_to_double(sy);
|
||||
}
|
||||
|
||||
_glfwInputCursorPos(window,
|
||||
wl_fixed_to_double(sx),
|
||||
wl_fixed_to_double(sy));
|
||||
switch (window->wl.decorations.focus)
|
||||
{
|
||||
case mainWindow:
|
||||
_glfwInputCursorPos(window,
|
||||
wl_fixed_to_double(sx),
|
||||
wl_fixed_to_double(sy));
|
||||
return;
|
||||
case topDecoration:
|
||||
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
|
||||
cursorName = "n-resize";
|
||||
else
|
||||
cursorName = "left_ptr";
|
||||
break;
|
||||
case leftDecoration:
|
||||
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
|
||||
cursorName = "nw-resize";
|
||||
else
|
||||
cursorName = "w-resize";
|
||||
break;
|
||||
case rightDecoration:
|
||||
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
|
||||
cursorName = "ne-resize";
|
||||
else
|
||||
cursorName = "e-resize";
|
||||
break;
|
||||
case bottomDecoration:
|
||||
if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH)
|
||||
cursorName = "sw-resize";
|
||||
else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH)
|
||||
cursorName = "se-resize";
|
||||
else
|
||||
cursorName = "s-resize";
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
setCursor(cursorName);
|
||||
}
|
||||
|
||||
static void pointerHandleButton(void* data,
|
||||
struct wl_pointer* wl_pointer,
|
||||
struct wl_pointer* pointer,
|
||||
uint32_t serial,
|
||||
uint32_t time,
|
||||
uint32_t button,
|
||||
@@ -114,8 +223,76 @@ static void pointerHandleButton(void* data,
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
int glfwButton;
|
||||
|
||||
// Both xdg-shell and wl_shell use the same values.
|
||||
uint32_t edges = WL_SHELL_SURFACE_RESIZE_NONE;
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
if (button == BTN_LEFT)
|
||||
{
|
||||
switch (window->wl.decorations.focus)
|
||||
{
|
||||
case mainWindow:
|
||||
break;
|
||||
case topDecoration:
|
||||
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
|
||||
edges = WL_SHELL_SURFACE_RESIZE_TOP;
|
||||
else
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_move(window->wl.xdg.toplevel, _glfw.wl.seat, serial);
|
||||
else
|
||||
wl_shell_surface_move(window->wl.shellSurface, _glfw.wl.seat, serial);
|
||||
}
|
||||
break;
|
||||
case leftDecoration:
|
||||
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
|
||||
edges = WL_SHELL_SURFACE_RESIZE_TOP_LEFT;
|
||||
else
|
||||
edges = WL_SHELL_SURFACE_RESIZE_LEFT;
|
||||
break;
|
||||
case rightDecoration:
|
||||
if (window->wl.cursorPosY < _GLFW_DECORATION_WIDTH)
|
||||
edges = WL_SHELL_SURFACE_RESIZE_TOP_RIGHT;
|
||||
else
|
||||
edges = WL_SHELL_SURFACE_RESIZE_RIGHT;
|
||||
break;
|
||||
case bottomDecoration:
|
||||
if (window->wl.cursorPosX < _GLFW_DECORATION_WIDTH)
|
||||
edges = WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT;
|
||||
else if (window->wl.cursorPosX > window->wl.width + _GLFW_DECORATION_WIDTH)
|
||||
edges = WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT;
|
||||
else
|
||||
edges = WL_SHELL_SURFACE_RESIZE_BOTTOM;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
if (edges != WL_SHELL_SURFACE_RESIZE_NONE)
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_resize(window->wl.xdg.toplevel, _glfw.wl.seat,
|
||||
serial, edges);
|
||||
else
|
||||
wl_shell_surface_resize(window->wl.shellSurface, _glfw.wl.seat,
|
||||
serial, edges);
|
||||
}
|
||||
}
|
||||
else if (button == BTN_RIGHT)
|
||||
{
|
||||
if (window->wl.decorations.focus != mainWindow && window->wl.xdg.toplevel)
|
||||
{
|
||||
xdg_toplevel_show_window_menu(window->wl.xdg.toplevel,
|
||||
_glfw.wl.seat, serial,
|
||||
window->wl.cursorPosX,
|
||||
window->wl.cursorPosY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Don’t pass the button to the user if it was related to a decoration.
|
||||
if (window->wl.decorations.focus != mainWindow)
|
||||
return;
|
||||
|
||||
_glfw.wl.pointerSerial = serial;
|
||||
|
||||
@@ -132,14 +309,17 @@ static void pointerHandleButton(void* data,
|
||||
}
|
||||
|
||||
static void pointerHandleAxis(void* data,
|
||||
struct wl_pointer* wl_pointer,
|
||||
struct wl_pointer* pointer,
|
||||
uint32_t time,
|
||||
uint32_t axis,
|
||||
wl_fixed_t value)
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
double scrollFactor;
|
||||
double x = 0, y = 0;
|
||||
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;
|
||||
@@ -147,22 +327,10 @@ static void pointerHandleAxis(void* data,
|
||||
assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
|
||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||
|
||||
/* 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. */
|
||||
scrollFactor = 1.0/10.0;
|
||||
|
||||
switch (axis)
|
||||
{
|
||||
case WL_POINTER_AXIS_HORIZONTAL_SCROLL:
|
||||
x = wl_fixed_to_double(value) * scrollFactor;
|
||||
y = 0.0;
|
||||
break;
|
||||
case WL_POINTER_AXIS_VERTICAL_SCROLL:
|
||||
x = 0.0;
|
||||
y = wl_fixed_to_double(value) * scrollFactor;
|
||||
break;
|
||||
}
|
||||
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
|
||||
x = wl_fixed_to_double(value) * scrollFactor;
|
||||
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
|
||||
y = wl_fixed_to_double(value) * scrollFactor;
|
||||
|
||||
_glfwInputScroll(window, x, y);
|
||||
}
|
||||
@@ -181,16 +349,7 @@ static void keyboardHandleKeymap(void* data,
|
||||
int fd,
|
||||
uint32_t size)
|
||||
{
|
||||
struct xkb_keymap* keymap;
|
||||
struct xkb_state* state;
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
struct xkb_compose_table* composeTable;
|
||||
struct xkb_compose_state* composeState;
|
||||
#endif
|
||||
|
||||
char* mapStr;
|
||||
const char* locale;
|
||||
|
||||
if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
|
||||
{
|
||||
@@ -203,78 +362,10 @@ static void keyboardHandleKeymap(void* data,
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
keymap = xkb_keymap_new_from_string(_glfw.wl.xkb.context,
|
||||
mapStr,
|
||||
XKB_KEYMAP_FORMAT_TEXT_V1,
|
||||
0);
|
||||
glfw_xkb_compile_keymap(&_glfw.wl.xkb, mapStr);
|
||||
munmap(mapStr, size);
|
||||
close(fd);
|
||||
|
||||
if (!keymap)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to compile keymap");
|
||||
return;
|
||||
}
|
||||
|
||||
state = xkb_state_new(keymap);
|
||||
if (!state)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to create XKB state");
|
||||
xkb_keymap_unref(keymap);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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";
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
composeTable =
|
||||
xkb_compose_table_new_from_locale(_glfw.wl.xkb.context, locale,
|
||||
XKB_COMPOSE_COMPILE_NO_FLAGS);
|
||||
if (composeTable)
|
||||
{
|
||||
composeState =
|
||||
xkb_compose_state_new(composeTable, XKB_COMPOSE_STATE_NO_FLAGS);
|
||||
xkb_compose_table_unref(composeTable);
|
||||
if (composeState)
|
||||
_glfw.wl.xkb.composeState = composeState;
|
||||
else
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to create XKB compose state");
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to create XKB compose table");
|
||||
}
|
||||
#endif
|
||||
|
||||
xkb_keymap_unref(_glfw.wl.xkb.keymap);
|
||||
xkb_state_unref(_glfw.wl.xkb.state);
|
||||
_glfw.wl.xkb.keymap = keymap;
|
||||
_glfw.wl.xkb.state = state;
|
||||
|
||||
_glfw.wl.xkb.controlMask =
|
||||
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Control");
|
||||
_glfw.wl.xkb.altMask =
|
||||
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod1");
|
||||
_glfw.wl.xkb.shiftMask =
|
||||
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Shift");
|
||||
_glfw.wl.xkb.superMask =
|
||||
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod4");
|
||||
_glfw.wl.xkb.capsLockMask =
|
||||
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Lock");
|
||||
_glfw.wl.xkb.numLockMask =
|
||||
1 << xkb_keymap_mod_get_index(_glfw.wl.xkb.keymap, "Mod2");
|
||||
}
|
||||
|
||||
static void keyboardHandleEnter(void* data,
|
||||
@@ -288,6 +379,12 @@ static void keyboardHandleEnter(void* data,
|
||||
return;
|
||||
|
||||
_GLFWwindow* window = wl_surface_get_user_data(surface);
|
||||
if (!window)
|
||||
{
|
||||
window = findWindowFromDecorationSurface(surface, NULL);
|
||||
if (!window)
|
||||
return;
|
||||
}
|
||||
|
||||
_glfw.wl.keyboardFocus = window;
|
||||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||
@@ -307,63 +404,6 @@ static void keyboardHandleLeave(void* data,
|
||||
_glfwInputWindowFocus(window, GLFW_FALSE);
|
||||
}
|
||||
|
||||
static int toGLFWKeyCode(uint32_t key)
|
||||
{
|
||||
if (key < sizeof(_glfw.wl.keycodes) / sizeof(_glfw.wl.keycodes[0]))
|
||||
return _glfw.wl.keycodes[key];
|
||||
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
}
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
static xkb_keysym_t composeSymbol(xkb_keysym_t sym)
|
||||
{
|
||||
if (sym == XKB_KEY_NoSymbol || !_glfw.wl.xkb.composeState)
|
||||
return sym;
|
||||
if (xkb_compose_state_feed(_glfw.wl.xkb.composeState, sym)
|
||||
!= XKB_COMPOSE_FEED_ACCEPTED)
|
||||
return sym;
|
||||
switch (xkb_compose_state_get_status(_glfw.wl.xkb.composeState))
|
||||
{
|
||||
case XKB_COMPOSE_COMPOSED:
|
||||
return xkb_compose_state_get_one_sym(_glfw.wl.xkb.composeState);
|
||||
case XKB_COMPOSE_COMPOSING:
|
||||
case XKB_COMPOSE_CANCELLED:
|
||||
return XKB_KEY_NoSymbol;
|
||||
case XKB_COMPOSE_NOTHING:
|
||||
default:
|
||||
return sym;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static void inputChar(_GLFWwindow* window, uint32_t key)
|
||||
{
|
||||
uint32_t code, numSyms;
|
||||
long cp;
|
||||
const xkb_keysym_t *syms;
|
||||
xkb_keysym_t sym;
|
||||
|
||||
code = key + 8;
|
||||
numSyms = xkb_state_key_get_syms(_glfw.wl.xkb.state, code, &syms);
|
||||
|
||||
if (numSyms == 1)
|
||||
{
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
sym = composeSymbol(syms[0]);
|
||||
#else
|
||||
sym = syms[0];
|
||||
#endif
|
||||
cp = _glfwKeySym2Unicode(sym);
|
||||
if (cp != -1)
|
||||
{
|
||||
const int mods = _glfw.wl.xkb.modifiers;
|
||||
const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT));
|
||||
_glfwInputChar(window, cp, mods, plain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void keyboardHandleKey(void* data,
|
||||
struct wl_keyboard* keyboard,
|
||||
uint32_t serial,
|
||||
@@ -371,22 +411,19 @@ static void keyboardHandleKey(void* data,
|
||||
uint32_t key,
|
||||
uint32_t state)
|
||||
{
|
||||
int keyCode;
|
||||
int action;
|
||||
_GLFWwindow* window = _glfw.wl.keyboardFocus;
|
||||
|
||||
if (!window)
|
||||
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;
|
||||
|
||||
keyCode = toGLFWKeyCode(key);
|
||||
action = state == WL_KEYBOARD_KEY_STATE_PRESSED
|
||||
? GLFW_PRESS : GLFW_RELEASE;
|
||||
|
||||
_glfwInputKey(window, keyCode, key, action,
|
||||
_glfw.wl.xkb.modifiers);
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
inputChar(window, key);
|
||||
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;
|
||||
_glfw.wl.keyRepeatInfo.keyboardFocus = window;
|
||||
}
|
||||
}
|
||||
|
||||
static void keyboardHandleModifiers(void* data,
|
||||
@@ -397,38 +434,19 @@ static void keyboardHandleModifiers(void* data,
|
||||
uint32_t modsLocked,
|
||||
uint32_t group)
|
||||
{
|
||||
xkb_mod_mask_t mask;
|
||||
unsigned int modifiers = 0;
|
||||
glfw_xkb_update_modifiers(&_glfw.wl.xkb, modsDepressed, modsLatched, modsLocked, 0, 0, group);
|
||||
}
|
||||
|
||||
if (!_glfw.wl.xkb.keymap)
|
||||
static void keyboardHandleRepeatInfo(void* data,
|
||||
struct wl_keyboard* keyboard,
|
||||
int32_t rate,
|
||||
int32_t delay)
|
||||
{
|
||||
if (keyboard != _glfw.wl.keyboard)
|
||||
return;
|
||||
|
||||
xkb_state_update_mask(_glfw.wl.xkb.state,
|
||||
modsDepressed,
|
||||
modsLatched,
|
||||
modsLocked,
|
||||
0,
|
||||
0,
|
||||
group);
|
||||
|
||||
mask = xkb_state_serialize_mods(_glfw.wl.xkb.state,
|
||||
XKB_STATE_MODS_DEPRESSED |
|
||||
XKB_STATE_LAYOUT_DEPRESSED |
|
||||
XKB_STATE_MODS_LATCHED |
|
||||
XKB_STATE_LAYOUT_LATCHED);
|
||||
if (mask & _glfw.wl.xkb.controlMask)
|
||||
modifiers |= GLFW_MOD_CONTROL;
|
||||
if (mask & _glfw.wl.xkb.altMask)
|
||||
modifiers |= GLFW_MOD_ALT;
|
||||
if (mask & _glfw.wl.xkb.shiftMask)
|
||||
modifiers |= GLFW_MOD_SHIFT;
|
||||
if (mask & _glfw.wl.xkb.superMask)
|
||||
modifiers |= GLFW_MOD_SUPER;
|
||||
if (mask & _glfw.wl.xkb.capsLockMask)
|
||||
modifiers |= GLFW_MOD_CAPS_LOCK;
|
||||
if (mask & _glfw.wl.xkb.numLockMask)
|
||||
modifiers |= GLFW_MOD_NUM_LOCK;
|
||||
_glfw.wl.xkb.modifiers = modifiers;
|
||||
_glfw.wl.keyboardRepeatRate = rate;
|
||||
_glfw.wl.keyboardRepeatDelay = delay;
|
||||
}
|
||||
|
||||
static const struct wl_keyboard_listener keyboardListener = {
|
||||
@@ -437,6 +455,7 @@ static const struct wl_keyboard_listener keyboardListener = {
|
||||
keyboardHandleLeave,
|
||||
keyboardHandleKey,
|
||||
keyboardHandleModifiers,
|
||||
keyboardHandleRepeatInfo,
|
||||
};
|
||||
|
||||
static void seatHandleCapabilities(void* data,
|
||||
@@ -466,8 +485,15 @@ static void seatHandleCapabilities(void* data,
|
||||
}
|
||||
}
|
||||
|
||||
static void seatHandleName(void* data,
|
||||
struct wl_seat* seat,
|
||||
const char* name)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct wl_seat_listener seatListener = {
|
||||
seatHandleCapabilities
|
||||
seatHandleCapabilities,
|
||||
seatHandleName,
|
||||
};
|
||||
|
||||
static void wmBaseHandlePing(void* data,
|
||||
@@ -494,6 +520,11 @@ static void registryHandleGlobal(void* data,
|
||||
wl_registry_bind(registry, name, &wl_compositor_interface,
|
||||
_glfw.wl.compositorVersion);
|
||||
}
|
||||
else if (strcmp(interface, "wl_subcompositor") == 0)
|
||||
{
|
||||
_glfw.wl.subcompositor =
|
||||
wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
|
||||
}
|
||||
else if (strcmp(interface, "wl_shm") == 0)
|
||||
{
|
||||
_glfw.wl.shm =
|
||||
@@ -512,8 +543,10 @@ static void registryHandleGlobal(void* data,
|
||||
{
|
||||
if (!_glfw.wl.seat)
|
||||
{
|
||||
_glfw.wl.seatVersion = min(4, version);
|
||||
_glfw.wl.seat =
|
||||
wl_registry_bind(registry, name, &wl_seat_interface, 1);
|
||||
wl_registry_bind(registry, name, &wl_seat_interface,
|
||||
_glfw.wl.seatVersion);
|
||||
wl_seat_add_listener(_glfw.wl.seat, &seatListener, NULL);
|
||||
}
|
||||
}
|
||||
@@ -523,6 +556,11 @@ static void registryHandleGlobal(void* data,
|
||||
wl_registry_bind(registry, name, &xdg_wm_base_interface, 1);
|
||||
xdg_wm_base_add_listener(_glfw.wl.wmBase, &wmBaseListener, NULL);
|
||||
}
|
||||
else if (strcmp(interface, "wp_viewporter") == 0)
|
||||
{
|
||||
_glfw.wl.viewporter =
|
||||
wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
|
||||
}
|
||||
else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0)
|
||||
{
|
||||
_glfw.wl.relativePointerManager =
|
||||
@@ -550,6 +588,18 @@ static void registryHandleGlobalRemove(void *data,
|
||||
struct wl_registry *registry,
|
||||
uint32_t name)
|
||||
{
|
||||
int i;
|
||||
_GLFWmonitor* monitor;
|
||||
|
||||
for (i = 0; i < _glfw.monitorCount; ++i)
|
||||
{
|
||||
monitor = _glfw.monitors[i];
|
||||
if (monitor->wl.name == name)
|
||||
{
|
||||
_glfwInputMonitor(monitor, GLFW_DISCONNECTED, 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -558,140 +608,6 @@ static const struct wl_registry_listener registryListener = {
|
||||
registryHandleGlobalRemove
|
||||
};
|
||||
|
||||
// Create key code translation tables
|
||||
//
|
||||
static void createKeyTables(void)
|
||||
{
|
||||
int scancode;
|
||||
|
||||
memset(_glfw.wl.keycodes, -1, sizeof(_glfw.wl.keycodes));
|
||||
memset(_glfw.wl.scancodes, -1, sizeof(_glfw.wl.scancodes));
|
||||
|
||||
_glfw.wl.keycodes[KEY_GRAVE] = GLFW_KEY_GRAVE_ACCENT;
|
||||
_glfw.wl.keycodes[KEY_1] = GLFW_KEY_1;
|
||||
_glfw.wl.keycodes[KEY_2] = GLFW_KEY_2;
|
||||
_glfw.wl.keycodes[KEY_3] = GLFW_KEY_3;
|
||||
_glfw.wl.keycodes[KEY_4] = GLFW_KEY_4;
|
||||
_glfw.wl.keycodes[KEY_5] = GLFW_KEY_5;
|
||||
_glfw.wl.keycodes[KEY_6] = GLFW_KEY_6;
|
||||
_glfw.wl.keycodes[KEY_7] = GLFW_KEY_7;
|
||||
_glfw.wl.keycodes[KEY_8] = GLFW_KEY_8;
|
||||
_glfw.wl.keycodes[KEY_9] = GLFW_KEY_9;
|
||||
_glfw.wl.keycodes[KEY_0] = GLFW_KEY_0;
|
||||
_glfw.wl.keycodes[KEY_SPACE] = GLFW_KEY_SPACE;
|
||||
_glfw.wl.keycodes[KEY_MINUS] = GLFW_KEY_MINUS;
|
||||
_glfw.wl.keycodes[KEY_EQUAL] = GLFW_KEY_EQUAL;
|
||||
_glfw.wl.keycodes[KEY_Q] = GLFW_KEY_Q;
|
||||
_glfw.wl.keycodes[KEY_W] = GLFW_KEY_W;
|
||||
_glfw.wl.keycodes[KEY_E] = GLFW_KEY_E;
|
||||
_glfw.wl.keycodes[KEY_R] = GLFW_KEY_R;
|
||||
_glfw.wl.keycodes[KEY_T] = GLFW_KEY_T;
|
||||
_glfw.wl.keycodes[KEY_Y] = GLFW_KEY_Y;
|
||||
_glfw.wl.keycodes[KEY_U] = GLFW_KEY_U;
|
||||
_glfw.wl.keycodes[KEY_I] = GLFW_KEY_I;
|
||||
_glfw.wl.keycodes[KEY_O] = GLFW_KEY_O;
|
||||
_glfw.wl.keycodes[KEY_P] = GLFW_KEY_P;
|
||||
_glfw.wl.keycodes[KEY_LEFTBRACE] = GLFW_KEY_LEFT_BRACKET;
|
||||
_glfw.wl.keycodes[KEY_RIGHTBRACE] = GLFW_KEY_RIGHT_BRACKET;
|
||||
_glfw.wl.keycodes[KEY_A] = GLFW_KEY_A;
|
||||
_glfw.wl.keycodes[KEY_S] = GLFW_KEY_S;
|
||||
_glfw.wl.keycodes[KEY_D] = GLFW_KEY_D;
|
||||
_glfw.wl.keycodes[KEY_F] = GLFW_KEY_F;
|
||||
_glfw.wl.keycodes[KEY_G] = GLFW_KEY_G;
|
||||
_glfw.wl.keycodes[KEY_H] = GLFW_KEY_H;
|
||||
_glfw.wl.keycodes[KEY_J] = GLFW_KEY_J;
|
||||
_glfw.wl.keycodes[KEY_K] = GLFW_KEY_K;
|
||||
_glfw.wl.keycodes[KEY_L] = GLFW_KEY_L;
|
||||
_glfw.wl.keycodes[KEY_SEMICOLON] = GLFW_KEY_SEMICOLON;
|
||||
_glfw.wl.keycodes[KEY_APOSTROPHE] = GLFW_KEY_APOSTROPHE;
|
||||
_glfw.wl.keycodes[KEY_Z] = GLFW_KEY_Z;
|
||||
_glfw.wl.keycodes[KEY_X] = GLFW_KEY_X;
|
||||
_glfw.wl.keycodes[KEY_C] = GLFW_KEY_C;
|
||||
_glfw.wl.keycodes[KEY_V] = GLFW_KEY_V;
|
||||
_glfw.wl.keycodes[KEY_B] = GLFW_KEY_B;
|
||||
_glfw.wl.keycodes[KEY_N] = GLFW_KEY_N;
|
||||
_glfw.wl.keycodes[KEY_M] = GLFW_KEY_M;
|
||||
_glfw.wl.keycodes[KEY_COMMA] = GLFW_KEY_COMMA;
|
||||
_glfw.wl.keycodes[KEY_DOT] = GLFW_KEY_PERIOD;
|
||||
_glfw.wl.keycodes[KEY_SLASH] = GLFW_KEY_SLASH;
|
||||
_glfw.wl.keycodes[KEY_BACKSLASH] = GLFW_KEY_BACKSLASH;
|
||||
_glfw.wl.keycodes[KEY_ESC] = GLFW_KEY_ESCAPE;
|
||||
_glfw.wl.keycodes[KEY_TAB] = GLFW_KEY_TAB;
|
||||
_glfw.wl.keycodes[KEY_LEFTSHIFT] = GLFW_KEY_LEFT_SHIFT;
|
||||
_glfw.wl.keycodes[KEY_RIGHTSHIFT] = GLFW_KEY_RIGHT_SHIFT;
|
||||
_glfw.wl.keycodes[KEY_LEFTCTRL] = GLFW_KEY_LEFT_CONTROL;
|
||||
_glfw.wl.keycodes[KEY_RIGHTCTRL] = GLFW_KEY_RIGHT_CONTROL;
|
||||
_glfw.wl.keycodes[KEY_LEFTALT] = GLFW_KEY_LEFT_ALT;
|
||||
_glfw.wl.keycodes[KEY_RIGHTALT] = GLFW_KEY_RIGHT_ALT;
|
||||
_glfw.wl.keycodes[KEY_LEFTMETA] = GLFW_KEY_LEFT_SUPER;
|
||||
_glfw.wl.keycodes[KEY_RIGHTMETA] = GLFW_KEY_RIGHT_SUPER;
|
||||
_glfw.wl.keycodes[KEY_MENU] = GLFW_KEY_MENU;
|
||||
_glfw.wl.keycodes[KEY_NUMLOCK] = GLFW_KEY_NUM_LOCK;
|
||||
_glfw.wl.keycodes[KEY_CAPSLOCK] = GLFW_KEY_CAPS_LOCK;
|
||||
_glfw.wl.keycodes[KEY_PRINT] = GLFW_KEY_PRINT_SCREEN;
|
||||
_glfw.wl.keycodes[KEY_SCROLLLOCK] = GLFW_KEY_SCROLL_LOCK;
|
||||
_glfw.wl.keycodes[KEY_PAUSE] = GLFW_KEY_PAUSE;
|
||||
_glfw.wl.keycodes[KEY_DELETE] = GLFW_KEY_DELETE;
|
||||
_glfw.wl.keycodes[KEY_BACKSPACE] = GLFW_KEY_BACKSPACE;
|
||||
_glfw.wl.keycodes[KEY_ENTER] = GLFW_KEY_ENTER;
|
||||
_glfw.wl.keycodes[KEY_HOME] = GLFW_KEY_HOME;
|
||||
_glfw.wl.keycodes[KEY_END] = GLFW_KEY_END;
|
||||
_glfw.wl.keycodes[KEY_PAGEUP] = GLFW_KEY_PAGE_UP;
|
||||
_glfw.wl.keycodes[KEY_PAGEDOWN] = GLFW_KEY_PAGE_DOWN;
|
||||
_glfw.wl.keycodes[KEY_INSERT] = GLFW_KEY_INSERT;
|
||||
_glfw.wl.keycodes[KEY_LEFT] = GLFW_KEY_LEFT;
|
||||
_glfw.wl.keycodes[KEY_RIGHT] = GLFW_KEY_RIGHT;
|
||||
_glfw.wl.keycodes[KEY_DOWN] = GLFW_KEY_DOWN;
|
||||
_glfw.wl.keycodes[KEY_UP] = GLFW_KEY_UP;
|
||||
_glfw.wl.keycodes[KEY_F1] = GLFW_KEY_F1;
|
||||
_glfw.wl.keycodes[KEY_F2] = GLFW_KEY_F2;
|
||||
_glfw.wl.keycodes[KEY_F3] = GLFW_KEY_F3;
|
||||
_glfw.wl.keycodes[KEY_F4] = GLFW_KEY_F4;
|
||||
_glfw.wl.keycodes[KEY_F5] = GLFW_KEY_F5;
|
||||
_glfw.wl.keycodes[KEY_F6] = GLFW_KEY_F6;
|
||||
_glfw.wl.keycodes[KEY_F7] = GLFW_KEY_F7;
|
||||
_glfw.wl.keycodes[KEY_F8] = GLFW_KEY_F8;
|
||||
_glfw.wl.keycodes[KEY_F9] = GLFW_KEY_F9;
|
||||
_glfw.wl.keycodes[KEY_F10] = GLFW_KEY_F10;
|
||||
_glfw.wl.keycodes[KEY_F11] = GLFW_KEY_F11;
|
||||
_glfw.wl.keycodes[KEY_F12] = GLFW_KEY_F12;
|
||||
_glfw.wl.keycodes[KEY_F13] = GLFW_KEY_F13;
|
||||
_glfw.wl.keycodes[KEY_F14] = GLFW_KEY_F14;
|
||||
_glfw.wl.keycodes[KEY_F15] = GLFW_KEY_F15;
|
||||
_glfw.wl.keycodes[KEY_F16] = GLFW_KEY_F16;
|
||||
_glfw.wl.keycodes[KEY_F17] = GLFW_KEY_F17;
|
||||
_glfw.wl.keycodes[KEY_F18] = GLFW_KEY_F18;
|
||||
_glfw.wl.keycodes[KEY_F19] = GLFW_KEY_F19;
|
||||
_glfw.wl.keycodes[KEY_F20] = GLFW_KEY_F20;
|
||||
_glfw.wl.keycodes[KEY_F21] = GLFW_KEY_F21;
|
||||
_glfw.wl.keycodes[KEY_F22] = GLFW_KEY_F22;
|
||||
_glfw.wl.keycodes[KEY_F23] = GLFW_KEY_F23;
|
||||
_glfw.wl.keycodes[KEY_F24] = GLFW_KEY_F24;
|
||||
_glfw.wl.keycodes[KEY_KPSLASH] = GLFW_KEY_KP_DIVIDE;
|
||||
_glfw.wl.keycodes[KEY_KPDOT] = GLFW_KEY_KP_MULTIPLY;
|
||||
_glfw.wl.keycodes[KEY_KPMINUS] = GLFW_KEY_KP_SUBTRACT;
|
||||
_glfw.wl.keycodes[KEY_KPPLUS] = GLFW_KEY_KP_ADD;
|
||||
_glfw.wl.keycodes[KEY_KP0] = GLFW_KEY_KP_0;
|
||||
_glfw.wl.keycodes[KEY_KP1] = GLFW_KEY_KP_1;
|
||||
_glfw.wl.keycodes[KEY_KP2] = GLFW_KEY_KP_2;
|
||||
_glfw.wl.keycodes[KEY_KP3] = GLFW_KEY_KP_3;
|
||||
_glfw.wl.keycodes[KEY_KP4] = GLFW_KEY_KP_4;
|
||||
_glfw.wl.keycodes[KEY_KP5] = GLFW_KEY_KP_5;
|
||||
_glfw.wl.keycodes[KEY_KP6] = GLFW_KEY_KP_6;
|
||||
_glfw.wl.keycodes[KEY_KP7] = GLFW_KEY_KP_7;
|
||||
_glfw.wl.keycodes[KEY_KP8] = GLFW_KEY_KP_8;
|
||||
_glfw.wl.keycodes[KEY_KP9] = GLFW_KEY_KP_9;
|
||||
_glfw.wl.keycodes[KEY_KPCOMMA] = GLFW_KEY_KP_DECIMAL;
|
||||
_glfw.wl.keycodes[KEY_KPEQUAL] = GLFW_KEY_KP_EQUAL;
|
||||
_glfw.wl.keycodes[KEY_KPENTER] = GLFW_KEY_KP_ENTER;
|
||||
|
||||
for (scancode = 0; scancode < 256; scancode++)
|
||||
{
|
||||
if (_glfw.wl.keycodes[scancode] > 0)
|
||||
_glfw.wl.scancodes[_glfw.wl.keycodes[scancode]] = scancode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
@@ -731,52 +647,6 @@ int _glfwPlatformInit(void)
|
||||
_glfw.wl.egl.window_resize = (PFN_wl_egl_window_resize)
|
||||
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_resize");
|
||||
|
||||
_glfw.wl.xkb.handle = _glfw_dlopen("libxkbcommon.so.0");
|
||||
if (!_glfw.wl.xkb.handle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to open libxkbcommon");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
_glfw.wl.xkb.context_new = (PFN_xkb_context_new)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_new");
|
||||
_glfw.wl.xkb.context_unref = (PFN_xkb_context_unref)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_unref");
|
||||
_glfw.wl.xkb.keymap_new_from_string = (PFN_xkb_keymap_new_from_string)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_new_from_string");
|
||||
_glfw.wl.xkb.keymap_unref = (PFN_xkb_keymap_unref)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_unref");
|
||||
_glfw.wl.xkb.keymap_mod_get_index = (PFN_xkb_keymap_mod_get_index)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_mod_get_index");
|
||||
_glfw.wl.xkb.state_new = (PFN_xkb_state_new)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_new");
|
||||
_glfw.wl.xkb.state_unref = (PFN_xkb_state_unref)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_unref");
|
||||
_glfw.wl.xkb.state_key_get_syms = (PFN_xkb_state_key_get_syms)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_key_get_syms");
|
||||
_glfw.wl.xkb.state_update_mask = (PFN_xkb_state_update_mask)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_update_mask");
|
||||
_glfw.wl.xkb.state_serialize_mods = (PFN_xkb_state_serialize_mods)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_serialize_mods");
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
_glfw.wl.xkb.compose_table_new_from_locale = (PFN_xkb_compose_table_new_from_locale)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_new_from_locale");
|
||||
_glfw.wl.xkb.compose_table_unref = (PFN_xkb_compose_table_unref)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_table_unref");
|
||||
_glfw.wl.xkb.compose_state_new = (PFN_xkb_compose_state_new)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_new");
|
||||
_glfw.wl.xkb.compose_state_unref = (PFN_xkb_compose_state_unref)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_unref");
|
||||
_glfw.wl.xkb.compose_state_feed = (PFN_xkb_compose_state_feed)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_feed");
|
||||
_glfw.wl.xkb.compose_state_get_status = (PFN_xkb_compose_state_get_status)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_status");
|
||||
_glfw.wl.xkb.compose_state_get_one_sym = (PFN_xkb_compose_state_get_one_sym)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_compose_state_get_one_sym");
|
||||
#endif
|
||||
|
||||
_glfw.wl.display = wl_display_connect(NULL);
|
||||
if (!_glfw.wl.display)
|
||||
{
|
||||
@@ -788,15 +658,7 @@ int _glfwPlatformInit(void)
|
||||
_glfw.wl.registry = wl_display_get_registry(_glfw.wl.display);
|
||||
wl_registry_add_listener(_glfw.wl.registry, ®istryListener, NULL);
|
||||
|
||||
createKeyTables();
|
||||
|
||||
_glfw.wl.xkb.context = xkb_context_new(0);
|
||||
if (!_glfw.wl.xkb.context)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to initialize xkb context");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
if (!glfw_xkb_create_context(&_glfw.wl.xkb)) return GLFW_FALSE;
|
||||
|
||||
// Sync so we got all registry objects
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
@@ -804,8 +666,10 @@ int _glfwPlatformInit(void)
|
||||
// Sync so we got all initial output events
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
|
||||
#ifdef __linux__
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
#endif
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
|
||||
@@ -827,7 +691,9 @@ int _glfwPlatformInit(void)
|
||||
|
||||
void _glfwPlatformTerminate(void)
|
||||
{
|
||||
#ifdef __linux__
|
||||
_glfwTerminateJoysticksLinux();
|
||||
#endif
|
||||
_glfwTerminateEGL();
|
||||
if (_glfw.wl.egl.handle)
|
||||
{
|
||||
@@ -835,21 +701,7 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.wl.egl.handle = NULL;
|
||||
}
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
if (_glfw.wl.xkb.composeState)
|
||||
xkb_compose_state_unref(_glfw.wl.xkb.composeState);
|
||||
#endif
|
||||
if (_glfw.wl.xkb.keymap)
|
||||
xkb_keymap_unref(_glfw.wl.xkb.keymap);
|
||||
if (_glfw.wl.xkb.state)
|
||||
xkb_state_unref(_glfw.wl.xkb.state);
|
||||
if (_glfw.wl.xkb.context)
|
||||
xkb_context_unref(_glfw.wl.xkb.context);
|
||||
if (_glfw.wl.xkb.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.wl.xkb.handle);
|
||||
_glfw.wl.xkb.handle = NULL;
|
||||
}
|
||||
glfw_xkb_release(&_glfw.wl.xkb);
|
||||
|
||||
if (_glfw.wl.cursorTheme)
|
||||
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
|
||||
@@ -861,12 +713,16 @@ void _glfwPlatformTerminate(void)
|
||||
|
||||
if (_glfw.wl.cursorSurface)
|
||||
wl_surface_destroy(_glfw.wl.cursorSurface);
|
||||
if (_glfw.wl.subcompositor)
|
||||
wl_subcompositor_destroy(_glfw.wl.subcompositor);
|
||||
if (_glfw.wl.compositor)
|
||||
wl_compositor_destroy(_glfw.wl.compositor);
|
||||
if (_glfw.wl.shm)
|
||||
wl_shm_destroy(_glfw.wl.shm);
|
||||
if (_glfw.wl.shell)
|
||||
wl_shell_destroy(_glfw.wl.shell);
|
||||
if (_glfw.wl.viewporter)
|
||||
wp_viewporter_destroy(_glfw.wl.viewporter);
|
||||
if (_glfw.wl.wmBase)
|
||||
xdg_wm_base_destroy(_glfw.wl.wmBase);
|
||||
if (_glfw.wl.pointer)
|
||||
|
||||
7
glfw/wl_monitor.c
vendored
7
glfw/wl_monitor.c
vendored
@@ -136,6 +136,7 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version)
|
||||
|
||||
monitor->wl.scale = 1;
|
||||
monitor->wl.output = output;
|
||||
monitor->wl.name = name;
|
||||
|
||||
wl_output_add_listener(output, &outputListener, monitor);
|
||||
}
|
||||
@@ -145,6 +146,12 @@ void _glfwAddOutputWayland(uint32_t name, uint32_t version)
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
if (monitor->wl.output)
|
||||
wl_output_destroy(monitor->wl.output);
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
{
|
||||
if (xpos)
|
||||
|
||||
126
glfw/wl_platform.h
vendored
126
glfw/wl_platform.h
vendored
@@ -25,10 +25,6 @@
|
||||
//========================================================================
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
#endif
|
||||
#include <dlfcn.h>
|
||||
|
||||
typedef VkFlags VkWaylandSurfaceCreateFlagsKHR;
|
||||
@@ -47,12 +43,17 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
|
||||
#include "posix_thread.h"
|
||||
#include "posix_time.h"
|
||||
#ifdef __linux__
|
||||
#include "linux_joystick.h"
|
||||
#include "xkb_unicode.h"
|
||||
#else
|
||||
#include "null_joystick.h"
|
||||
#endif
|
||||
#include "xkb_glfw.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
|
||||
#include "wayland-xdg-shell-client-protocol.h"
|
||||
#include "wayland-viewporter-client-protocol.h"
|
||||
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
|
||||
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
|
||||
#include "wayland-idle-inhibit-unstable-v1-client-protocol.h"
|
||||
@@ -100,44 +101,28 @@ typedef void (* PFN_wl_egl_window_resize)(struct wl_egl_window*, int, int, int,
|
||||
#define wl_egl_window_destroy _glfw.wl.egl.window_destroy
|
||||
#define wl_egl_window_resize _glfw.wl.egl.window_resize
|
||||
|
||||
typedef struct xkb_context* (* PFN_xkb_context_new)(enum xkb_context_flags);
|
||||
typedef void (* PFN_xkb_context_unref)(struct xkb_context*);
|
||||
typedef struct xkb_keymap* (* PFN_xkb_keymap_new_from_string)(struct xkb_context*, const char*, enum xkb_keymap_format, enum xkb_keymap_compile_flags);
|
||||
typedef void (* PFN_xkb_keymap_unref)(struct xkb_keymap*);
|
||||
typedef xkb_mod_index_t (* PFN_xkb_keymap_mod_get_index)(struct xkb_keymap*, const char*);
|
||||
typedef struct xkb_state* (* PFN_xkb_state_new)(struct xkb_keymap*);
|
||||
typedef void (* PFN_xkb_state_unref)(struct xkb_state*);
|
||||
typedef int (* PFN_xkb_state_key_get_syms)(struct xkb_state*, xkb_keycode_t, const xkb_keysym_t**);
|
||||
typedef enum xkb_state_component (* PFN_xkb_state_update_mask)(struct xkb_state*, xkb_mod_mask_t, xkb_mod_mask_t, xkb_mod_mask_t, xkb_layout_index_t, xkb_layout_index_t, xkb_layout_index_t);
|
||||
typedef xkb_mod_mask_t (* PFN_xkb_state_serialize_mods)(struct xkb_state*, enum xkb_state_component);
|
||||
#define xkb_context_new _glfw.wl.xkb.context_new
|
||||
#define xkb_context_unref _glfw.wl.xkb.context_unref
|
||||
#define xkb_keymap_new_from_string _glfw.wl.xkb.keymap_new_from_string
|
||||
#define xkb_keymap_unref _glfw.wl.xkb.keymap_unref
|
||||
#define xkb_keymap_mod_get_index _glfw.wl.xkb.keymap_mod_get_index
|
||||
#define xkb_state_new _glfw.wl.xkb.state_new
|
||||
#define xkb_state_unref _glfw.wl.xkb.state_unref
|
||||
#define xkb_state_key_get_syms _glfw.wl.xkb.state_key_get_syms
|
||||
#define xkb_state_update_mask _glfw.wl.xkb.state_update_mask
|
||||
#define xkb_state_serialize_mods _glfw.wl.xkb.state_serialize_mods
|
||||
#define _GLFW_DECORATION_WIDTH 4
|
||||
#define _GLFW_DECORATION_TOP 24
|
||||
#define _GLFW_DECORATION_VERTICAL (_GLFW_DECORATION_TOP + _GLFW_DECORATION_WIDTH)
|
||||
#define _GLFW_DECORATION_HORIZONTAL (2 * _GLFW_DECORATION_WIDTH)
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
typedef struct xkb_compose_table* (* PFN_xkb_compose_table_new_from_locale)(struct xkb_context*, const char*, enum xkb_compose_compile_flags);
|
||||
typedef void (* PFN_xkb_compose_table_unref)(struct xkb_compose_table*);
|
||||
typedef struct xkb_compose_state* (* PFN_xkb_compose_state_new)(struct xkb_compose_table*, enum xkb_compose_state_flags);
|
||||
typedef void (* PFN_xkb_compose_state_unref)(struct xkb_compose_state*);
|
||||
typedef enum xkb_compose_feed_result (* PFN_xkb_compose_state_feed)(struct xkb_compose_state*, xkb_keysym_t);
|
||||
typedef enum xkb_compose_status (* PFN_xkb_compose_state_get_status)(struct xkb_compose_state*);
|
||||
typedef xkb_keysym_t (* PFN_xkb_compose_state_get_one_sym)(struct xkb_compose_state*);
|
||||
#define xkb_compose_table_new_from_locale _glfw.wl.xkb.compose_table_new_from_locale
|
||||
#define xkb_compose_table_unref _glfw.wl.xkb.compose_table_unref
|
||||
#define xkb_compose_state_new _glfw.wl.xkb.compose_state_new
|
||||
#define xkb_compose_state_unref _glfw.wl.xkb.compose_state_unref
|
||||
#define xkb_compose_state_feed _glfw.wl.xkb.compose_state_feed
|
||||
#define xkb_compose_state_get_status _glfw.wl.xkb.compose_state_get_status
|
||||
#define xkb_compose_state_get_one_sym _glfw.wl.xkb.compose_state_get_one_sym
|
||||
#endif
|
||||
typedef enum _GLFWdecorationSideWayland
|
||||
{
|
||||
mainWindow,
|
||||
topDecoration,
|
||||
leftDecoration,
|
||||
rightDecoration,
|
||||
bottomDecoration,
|
||||
|
||||
} _GLFWdecorationSideWayland;
|
||||
|
||||
typedef struct _GLFWdecorationWayland
|
||||
{
|
||||
struct wl_surface* surface;
|
||||
struct wl_subsurface* subsurface;
|
||||
struct wp_viewport* viewport;
|
||||
|
||||
} _GLFWdecorationWayland;
|
||||
|
||||
// Wayland-specific per-window data
|
||||
//
|
||||
@@ -180,6 +165,12 @@ typedef struct _GLFWwindowWayland
|
||||
// This is a hack to prevent auto-iconification on creation.
|
||||
GLFWbool justCreated;
|
||||
|
||||
struct {
|
||||
struct wl_buffer* buffer;
|
||||
_GLFWdecorationWayland top, left, right, bottom;
|
||||
int focus;
|
||||
} decorations;
|
||||
|
||||
} _GLFWwindowWayland;
|
||||
|
||||
// Wayland-specific global data
|
||||
@@ -189,64 +180,33 @@ typedef struct _GLFWlibraryWayland
|
||||
struct wl_display* display;
|
||||
struct wl_registry* registry;
|
||||
struct wl_compositor* compositor;
|
||||
struct wl_subcompositor* subcompositor;
|
||||
struct wl_shell* shell;
|
||||
struct wl_shm* shm;
|
||||
struct wl_seat* seat;
|
||||
struct wl_pointer* pointer;
|
||||
struct wl_keyboard* keyboard;
|
||||
struct xdg_wm_base* wmBase;
|
||||
struct wp_viewporter* viewporter;
|
||||
struct zwp_relative_pointer_manager_v1* relativePointerManager;
|
||||
struct zwp_pointer_constraints_v1* pointerConstraints;
|
||||
struct zwp_idle_inhibit_manager_v1* idleInhibitManager;
|
||||
|
||||
int compositorVersion;
|
||||
int seatVersion;
|
||||
|
||||
struct wl_cursor_theme* cursorTheme;
|
||||
struct wl_surface* cursorSurface;
|
||||
uint32_t pointerSerial;
|
||||
|
||||
short int keycodes[256];
|
||||
short int scancodes[GLFW_KEY_LAST + 1];
|
||||
|
||||
int32_t keyboardRepeatRate;
|
||||
int32_t keyboardRepeatDelay;
|
||||
struct {
|
||||
void* handle;
|
||||
struct xkb_context* context;
|
||||
struct xkb_keymap* keymap;
|
||||
struct xkb_state* state;
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
struct xkb_compose_state* composeState;
|
||||
#endif
|
||||
|
||||
xkb_mod_mask_t controlMask;
|
||||
xkb_mod_mask_t altMask;
|
||||
xkb_mod_mask_t shiftMask;
|
||||
xkb_mod_mask_t superMask;
|
||||
xkb_mod_mask_t capsLockMask;
|
||||
xkb_mod_mask_t numLockMask;
|
||||
unsigned int modifiers;
|
||||
|
||||
PFN_xkb_context_new context_new;
|
||||
PFN_xkb_context_unref context_unref;
|
||||
PFN_xkb_keymap_new_from_string keymap_new_from_string;
|
||||
PFN_xkb_keymap_unref keymap_unref;
|
||||
PFN_xkb_keymap_mod_get_index keymap_mod_get_index;
|
||||
PFN_xkb_state_new state_new;
|
||||
PFN_xkb_state_unref state_unref;
|
||||
PFN_xkb_state_key_get_syms state_key_get_syms;
|
||||
PFN_xkb_state_update_mask state_update_mask;
|
||||
PFN_xkb_state_serialize_mods state_serialize_mods;
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
PFN_xkb_compose_table_new_from_locale compose_table_new_from_locale;
|
||||
PFN_xkb_compose_table_unref compose_table_unref;
|
||||
PFN_xkb_compose_state_new compose_state_new;
|
||||
PFN_xkb_compose_state_unref compose_state_unref;
|
||||
PFN_xkb_compose_state_feed compose_state_feed;
|
||||
PFN_xkb_compose_state_get_status compose_state_get_status;
|
||||
PFN_xkb_compose_state_get_one_sym compose_state_get_one_sym;
|
||||
#endif
|
||||
} xkb;
|
||||
uint32_t key;
|
||||
double nextRepeatAt;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
} keyRepeatInfo;
|
||||
_GLFWXKBData xkb;
|
||||
|
||||
_GLFWwindow* pointerFocus;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
@@ -275,6 +235,7 @@ typedef struct _GLFWlibraryWayland
|
||||
typedef struct _GLFWmonitorWayland
|
||||
{
|
||||
struct wl_output* output;
|
||||
int name;
|
||||
int currentMode;
|
||||
|
||||
int x;
|
||||
@@ -295,4 +256,3 @@ typedef struct _GLFWcursorWayland
|
||||
|
||||
|
||||
void _glfwAddOutputWayland(uint32_t name, uint32_t version);
|
||||
|
||||
|
||||
558
glfw/wl_window.c
vendored
558
glfw/wl_window.c
vendored
@@ -57,6 +57,16 @@ static void handleConfigure(void* data,
|
||||
|
||||
if (!window->monitor)
|
||||
{
|
||||
if (_glfw.wl.viewporter && window->decorated)
|
||||
{
|
||||
width -= _GLFW_DECORATION_HORIZONTAL;
|
||||
height -= _GLFW_DECORATION_VERTICAL;
|
||||
}
|
||||
if (width < 1)
|
||||
width = 1;
|
||||
if (height < 1)
|
||||
height = 1;
|
||||
|
||||
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
|
||||
{
|
||||
aspectRatio = (float)width / (float)height;
|
||||
@@ -94,9 +104,265 @@ static const struct wl_shell_surface_listener shellSurfaceListener = {
|
||||
handlePopupDone
|
||||
};
|
||||
|
||||
static int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink(tmpname);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new, unique, anonymous file of the given size, and
|
||||
* return the file descriptor for it. The file descriptor is set
|
||||
* CLOEXEC. The file is immediately suitable for mmap()'ing
|
||||
* the given size at offset zero.
|
||||
*
|
||||
* The file should not have a permanent backing store like a disk,
|
||||
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
|
||||
*
|
||||
* The file name is deleted from the file system.
|
||||
*
|
||||
* The file is suitable for buffer sharing between processes by
|
||||
* transmitting the file descriptor over Unix sockets using the
|
||||
* SCM_RIGHTS methods.
|
||||
*
|
||||
* posix_fallocate() is used to guarantee that disk space is available
|
||||
* for the file at the given size. If disk space is insufficent, errno
|
||||
* is set to ENOSPC. If posix_fallocate() is not supported, program may
|
||||
* receive SIGBUS on accessing mmap()'ed file contents instead.
|
||||
*/
|
||||
static int
|
||||
createAnonymousFile(off_t size)
|
||||
{
|
||||
static const char template[] = "/glfw-shared-XXXXXX";
|
||||
const char* path;
|
||||
char* name;
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
path = getenv("XDG_RUNTIME_DIR");
|
||||
if (!path)
|
||||
{
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
name = calloc(strlen(path) + sizeof(template), 1);
|
||||
strcpy(name, path);
|
||||
strcat(name, template);
|
||||
|
||||
fd = createTmpfileCloexec(name);
|
||||
|
||||
free(name);
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
ret = posix_fallocate(fd, 0, size);
|
||||
if (ret != 0)
|
||||
{
|
||||
close(fd);
|
||||
errno = ret;
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
static struct wl_buffer* createShmBuffer(const GLFWimage* image)
|
||||
{
|
||||
struct wl_shm_pool* pool;
|
||||
struct wl_buffer* buffer;
|
||||
int stride = image->width * 4;
|
||||
int length = image->width * image->height * 4;
|
||||
void* data;
|
||||
int fd, i;
|
||||
|
||||
fd = createAnonymousFile(length);
|
||||
if (fd < 0)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Creating a buffer file for %d B failed: %m",
|
||||
length);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: mmap failed: %m");
|
||||
close(fd);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
|
||||
|
||||
close(fd);
|
||||
unsigned char* source = (unsigned char*) image->pixels;
|
||||
unsigned char* target = data;
|
||||
for (i = 0; i < image->width * image->height; i++, source += 4)
|
||||
{
|
||||
unsigned int alpha = source[3];
|
||||
|
||||
*target++ = (unsigned char) ((source[2] * alpha) / 255);
|
||||
*target++ = (unsigned char) ((source[1] * alpha) / 255);
|
||||
*target++ = (unsigned char) ((source[0] * alpha) / 255);
|
||||
*target++ = (unsigned char) alpha;
|
||||
}
|
||||
|
||||
buffer =
|
||||
wl_shm_pool_create_buffer(pool, 0,
|
||||
image->width,
|
||||
image->height,
|
||||
stride, WL_SHM_FORMAT_ARGB8888);
|
||||
munmap(data, length);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static void createDecoration(_GLFWdecorationWayland* decoration,
|
||||
struct wl_surface* parent,
|
||||
struct wl_buffer* buffer, GLFWbool opaque,
|
||||
int x, int y,
|
||||
int width, int height)
|
||||
{
|
||||
struct wl_region* region;
|
||||
|
||||
decoration->surface = wl_compositor_create_surface(_glfw.wl.compositor);
|
||||
decoration->subsurface =
|
||||
wl_subcompositor_get_subsurface(_glfw.wl.subcompositor,
|
||||
decoration->surface, parent);
|
||||
wl_subsurface_set_position(decoration->subsurface, x, y);
|
||||
decoration->viewport = wp_viewporter_get_viewport(_glfw.wl.viewporter,
|
||||
decoration->surface);
|
||||
wp_viewport_set_destination(decoration->viewport, width, height);
|
||||
wl_surface_attach(decoration->surface, buffer, 0, 0);
|
||||
|
||||
if (opaque)
|
||||
{
|
||||
region = wl_compositor_create_region(_glfw.wl.compositor);
|
||||
wl_region_add(region, 0, 0, width, height);
|
||||
wl_surface_set_opaque_region(decoration->surface, region);
|
||||
wl_surface_commit(decoration->surface);
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
else
|
||||
wl_surface_commit(decoration->surface);
|
||||
}
|
||||
|
||||
static void createDecorations(_GLFWwindow* window)
|
||||
{
|
||||
unsigned char data[] = { 224, 224, 224, 255 };
|
||||
const GLFWimage image = { 1, 1, data };
|
||||
GLFWbool opaque = (data[3] == 255);
|
||||
|
||||
if (!_glfw.wl.viewporter)
|
||||
return;
|
||||
|
||||
if (!window->wl.decorations.buffer)
|
||||
window->wl.decorations.buffer = createShmBuffer(&image);
|
||||
|
||||
createDecoration(&window->wl.decorations.top, window->wl.surface,
|
||||
window->wl.decorations.buffer, opaque,
|
||||
0, -_GLFW_DECORATION_TOP,
|
||||
window->wl.width, _GLFW_DECORATION_TOP);
|
||||
createDecoration(&window->wl.decorations.left, window->wl.surface,
|
||||
window->wl.decorations.buffer, opaque,
|
||||
-_GLFW_DECORATION_WIDTH, -_GLFW_DECORATION_TOP,
|
||||
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
|
||||
createDecoration(&window->wl.decorations.right, window->wl.surface,
|
||||
window->wl.decorations.buffer, opaque,
|
||||
window->wl.width, -_GLFW_DECORATION_TOP,
|
||||
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
|
||||
createDecoration(&window->wl.decorations.bottom, window->wl.surface,
|
||||
window->wl.decorations.buffer, opaque,
|
||||
-_GLFW_DECORATION_WIDTH, window->wl.height,
|
||||
window->wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
|
||||
}
|
||||
|
||||
static void destroyDecoration(_GLFWdecorationWayland* decoration)
|
||||
{
|
||||
if (decoration->surface)
|
||||
wl_surface_destroy(decoration->surface);
|
||||
if (decoration->subsurface)
|
||||
wl_subsurface_destroy(decoration->subsurface);
|
||||
if (decoration->viewport)
|
||||
wp_viewport_destroy(decoration->viewport);
|
||||
decoration->surface = NULL;
|
||||
decoration->subsurface = NULL;
|
||||
decoration->viewport = NULL;
|
||||
}
|
||||
|
||||
static void destroyDecorations(_GLFWwindow* window)
|
||||
{
|
||||
destroyDecoration(&window->wl.decorations.top);
|
||||
destroyDecoration(&window->wl.decorations.left);
|
||||
destroyDecoration(&window->wl.decorations.right);
|
||||
destroyDecoration(&window->wl.decorations.bottom);
|
||||
}
|
||||
|
||||
// Makes the surface considered as XRGB instead of ARGB.
|
||||
static void setOpaqueRegion(_GLFWwindow* window)
|
||||
{
|
||||
struct wl_region* region;
|
||||
|
||||
region = wl_compositor_create_region(_glfw.wl.compositor);
|
||||
if (!region)
|
||||
return;
|
||||
|
||||
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
|
||||
wl_surface_set_opaque_region(window->wl.surface, region);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
|
||||
|
||||
static void resizeWindow(_GLFWwindow* window)
|
||||
{
|
||||
int scale = window->wl.scale;
|
||||
int scaledWidth = window->wl.width * scale;
|
||||
int scaledHeight = window->wl.height * scale;
|
||||
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
|
||||
if (!window->wl.transparent)
|
||||
setOpaqueRegion(window);
|
||||
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
|
||||
_glfwInputWindowContentScale(window, scale, scale);
|
||||
|
||||
if (!window->wl.decorations.top.surface)
|
||||
return;
|
||||
|
||||
// Top decoration.
|
||||
wp_viewport_set_destination(window->wl.decorations.top.viewport,
|
||||
window->wl.width, _GLFW_DECORATION_TOP);
|
||||
wl_surface_commit(window->wl.decorations.top.surface);
|
||||
|
||||
// Left decoration.
|
||||
wp_viewport_set_destination(window->wl.decorations.left.viewport,
|
||||
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
|
||||
wl_surface_commit(window->wl.decorations.left.surface);
|
||||
|
||||
// Right decoration.
|
||||
wl_subsurface_set_position(window->wl.decorations.right.subsurface,
|
||||
window->wl.width, -_GLFW_DECORATION_TOP);
|
||||
wp_viewport_set_destination(window->wl.decorations.right.viewport,
|
||||
_GLFW_DECORATION_WIDTH, window->wl.height + _GLFW_DECORATION_TOP);
|
||||
wl_surface_commit(window->wl.decorations.right.surface);
|
||||
|
||||
// Bottom decoration.
|
||||
wl_subsurface_set_position(window->wl.decorations.bottom.subsurface,
|
||||
-_GLFW_DECORATION_WIDTH, window->wl.height);
|
||||
wp_viewport_set_destination(window->wl.decorations.bottom.viewport,
|
||||
window->wl.width + _GLFW_DECORATION_HORIZONTAL, _GLFW_DECORATION_WIDTH);
|
||||
wl_surface_commit(window->wl.decorations.bottom.surface);
|
||||
}
|
||||
|
||||
static void checkScaleChange(_GLFWwindow* window)
|
||||
{
|
||||
int scaledWidth, scaledHeight;
|
||||
int scale = 1;
|
||||
int i;
|
||||
int monitorScale;
|
||||
@@ -117,12 +383,8 @@ static void checkScaleChange(_GLFWwindow* window)
|
||||
if (scale != window->wl.scale)
|
||||
{
|
||||
window->wl.scale = scale;
|
||||
scaledWidth = window->wl.width * scale;
|
||||
scaledHeight = window->wl.height * scale;
|
||||
wl_surface_set_buffer_scale(window->wl.surface, scale);
|
||||
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
|
||||
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
|
||||
_glfwInputWindowContentScale(window, scale, scale);
|
||||
resizeWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,21 +434,6 @@ static const struct wl_surface_listener surfaceListener = {
|
||||
handleLeave
|
||||
};
|
||||
|
||||
// Makes the surface considered as XRGB instead of ARGB.
|
||||
static void setOpaqueRegion(_GLFWwindow* window)
|
||||
{
|
||||
struct wl_region* region;
|
||||
|
||||
region = wl_compositor_create_region(_glfw.wl.compositor);
|
||||
if (!region)
|
||||
return;
|
||||
|
||||
wl_region_add(region, 0, 0, window->wl.width, window->wl.height);
|
||||
wl_surface_set_opaque_region(window->wl.surface, region);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
wl_region_destroy(region);
|
||||
}
|
||||
|
||||
static void setIdleInhibitor(_GLFWwindow* window, GLFWbool enable)
|
||||
{
|
||||
if (enable && !window->wl.idleInhibitor && _glfw.wl.idleInhibitManager)
|
||||
@@ -231,9 +478,32 @@ static GLFWbool createSurface(_GLFWwindow* window,
|
||||
if (!window->wl.transparent)
|
||||
setOpaqueRegion(window);
|
||||
|
||||
if (window->decorated && !window->monitor)
|
||||
createDecorations(window);
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static void setFullscreen(_GLFWwindow* window, _GLFWmonitor* monitor, int refreshRate)
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
{
|
||||
xdg_toplevel_set_fullscreen(
|
||||
window->wl.xdg.toplevel,
|
||||
monitor->wl.output);
|
||||
}
|
||||
else if (window->wl.shellSurface)
|
||||
{
|
||||
wl_shell_surface_set_fullscreen(
|
||||
window->wl.shellSurface,
|
||||
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
|
||||
refreshRate * 1000, // Convert Hz to mHz.
|
||||
monitor->wl.output);
|
||||
}
|
||||
setIdleInhibitor(window, GLFW_TRUE);
|
||||
destroyDecorations(window);
|
||||
}
|
||||
|
||||
static GLFWbool createShellSurface(_GLFWwindow* window)
|
||||
{
|
||||
if (!_glfw.wl.shell)
|
||||
@@ -261,12 +531,7 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
|
||||
|
||||
if (window->monitor)
|
||||
{
|
||||
wl_shell_surface_set_fullscreen(
|
||||
window->wl.shellSurface,
|
||||
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
|
||||
0,
|
||||
window->monitor->wl.output);
|
||||
setIdleInhibitor(window, GLFW_TRUE);
|
||||
setFullscreen(window, window->monitor, 0);
|
||||
}
|
||||
else if (window->wl.maximized)
|
||||
{
|
||||
@@ -424,25 +689,32 @@ static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink(tmpname);
|
||||
|
||||
return fd;
|
||||
adjustTimeoutForKeyRepeat(int 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 / 1000. > _glfw.wl.keyRepeatInfo.nextRepeatAt) {
|
||||
timeout = _glfw.wl.keyRepeatInfo.nextRepeatAt <= now ? 0 : ( (_glfw.wl.keyRepeatInfo.nextRepeatAt - now) * 1000 + 1 );
|
||||
}
|
||||
return timeout;
|
||||
}
|
||||
|
||||
static void
|
||||
handleEvents(int timeout)
|
||||
{
|
||||
struct wl_display* display = _glfw.wl.display;
|
||||
struct pollfd fds[] = {
|
||||
{ wl_display_get_fd(display), POLLIN },
|
||||
};
|
||||
struct pollfd pfd = { wl_display_get_fd(display), POLLIN };
|
||||
|
||||
while (wl_display_prepare_read(display) != 0)
|
||||
wl_display_dispatch_pending(display);
|
||||
@@ -462,71 +734,27 @@ handleEvents(int timeout)
|
||||
return;
|
||||
}
|
||||
|
||||
if (poll(fds, 1, timeout) > 0)
|
||||
dispatchPendingKeyRepeats();
|
||||
timeout = adjustTimeoutForKeyRepeat(timeout);
|
||||
|
||||
if (poll(&pfd, 1, timeout) > 0)
|
||||
{
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
if (pfd.revents & POLLIN)
|
||||
{
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new, unique, anonymous file of the given size, and
|
||||
* return the file descriptor for it. The file descriptor is set
|
||||
* CLOEXEC. The file is immediately suitable for mmap()'ing
|
||||
* the given size at offset zero.
|
||||
*
|
||||
* The file should not have a permanent backing store like a disk,
|
||||
* but may have if XDG_RUNTIME_DIR is not properly implemented in OS.
|
||||
*
|
||||
* The file name is deleted from the file system.
|
||||
*
|
||||
* The file is suitable for buffer sharing between processes by
|
||||
* transmitting the file descriptor over Unix sockets using the
|
||||
* SCM_RIGHTS methods.
|
||||
*
|
||||
* posix_fallocate() is used to guarantee that disk space is available
|
||||
* for the file at the given size. If disk space is insufficent, errno
|
||||
* is set to ENOSPC. If posix_fallocate() is not supported, program may
|
||||
* receive SIGBUS on accessing mmap()'ed file contents instead.
|
||||
*/
|
||||
int
|
||||
createAnonymousFile(off_t size)
|
||||
{
|
||||
static const char template[] = "/glfw-shared-XXXXXX";
|
||||
const char* path;
|
||||
char* name;
|
||||
int fd;
|
||||
int ret;
|
||||
|
||||
path = getenv("XDG_RUNTIME_DIR");
|
||||
if (!path)
|
||||
{
|
||||
errno = ENOENT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
name = calloc(strlen(path) + sizeof(template), 1);
|
||||
strcpy(name, path);
|
||||
strcat(name, template);
|
||||
|
||||
fd = createTmpfileCloexec(name);
|
||||
|
||||
free(name);
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
ret = posix_fallocate(fd, 0, size);
|
||||
if (ret != 0)
|
||||
{
|
||||
close(fd);
|
||||
errno = ret;
|
||||
return -1;
|
||||
}
|
||||
return fd;
|
||||
dispatchPendingKeyRepeats();
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a theme cursor name
|
||||
@@ -639,6 +867,10 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
if (window->context.destroy)
|
||||
window->context.destroy(window);
|
||||
|
||||
destroyDecorations(window);
|
||||
if (window->wl.decorations.buffer)
|
||||
wl_buffer_destroy(window->wl.decorations.buffer);
|
||||
|
||||
if (window->wl.native)
|
||||
wl_egl_window_destroy(window->wl.native);
|
||||
|
||||
@@ -703,14 +935,9 @@ void _glfwPlatformGetWindowSize(_GLFWwindow* window, int* width, int* height)
|
||||
|
||||
void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
{
|
||||
int scaledWidth = width * window->wl.scale;
|
||||
int scaledHeight = height * window->wl.scale;
|
||||
window->wl.width = width;
|
||||
window->wl.height = height;
|
||||
wl_egl_window_resize(window->wl.native, scaledWidth, scaledHeight, 0, 0);
|
||||
if (!window->wl.transparent)
|
||||
setOpaqueRegion(window);
|
||||
_glfwInputFramebufferSize(window, scaledWidth, scaledHeight);
|
||||
resizeWindow(window);
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowSizeLimits(_GLFWwindow* window,
|
||||
@@ -754,8 +981,17 @@ void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
|
||||
int* left, int* top,
|
||||
int* right, int* bottom)
|
||||
{
|
||||
// TODO: will need a proper implementation once decorations are
|
||||
// implemented, but for now just leave everything as 0.
|
||||
if (window->decorated && !window->monitor)
|
||||
{
|
||||
if (top)
|
||||
*top = _GLFW_DECORATION_TOP;
|
||||
if (left)
|
||||
*left = _GLFW_DECORATION_WIDTH;
|
||||
if (right)
|
||||
*right = _GLFW_DECORATION_WIDTH;
|
||||
if (bottom)
|
||||
*bottom = _GLFW_DECORATION_WIDTH;
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
@@ -851,11 +1087,15 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
"Wayland: Window attention request not implemented yet");
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Window bell request not implemented yet");
|
||||
// TODO: Use an actual Wayland API to implement this when one becomes available
|
||||
int fd = open("/dev/tty", O_WRONLY | O_CLOEXEC);
|
||||
if (fd > -1) {
|
||||
int ret = write(fd, "\x07", 1) == 1 ? GLFW_TRUE : GLFW_FALSE;
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -871,35 +1111,20 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
int width, int height,
|
||||
int refreshRate)
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
if (monitor)
|
||||
{
|
||||
if (monitor)
|
||||
{
|
||||
xdg_toplevel_set_fullscreen(
|
||||
window->wl.xdg.toplevel,
|
||||
monitor->wl.output);
|
||||
}
|
||||
else
|
||||
{
|
||||
setFullscreen(window, monitor, refreshRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
|
||||
}
|
||||
}
|
||||
else if (window->wl.shellSurface)
|
||||
{
|
||||
if (monitor)
|
||||
{
|
||||
wl_shell_surface_set_fullscreen(
|
||||
window->wl.shellSurface,
|
||||
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
|
||||
refreshRate * 1000, // Convert Hz to mHz.
|
||||
monitor->wl.output);
|
||||
}
|
||||
else
|
||||
{
|
||||
else if (window->wl.shellSurface)
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
}
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
if (window->decorated)
|
||||
createDecorations(window);
|
||||
}
|
||||
setIdleInhibitor(window, monitor ? GLFW_TRUE : GLFW_FALSE);
|
||||
_glfwInputWindowMonitor(window, monitor);
|
||||
}
|
||||
|
||||
@@ -944,9 +1169,13 @@ void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
|
||||
|
||||
void _glfwPlatformSetWindowDecorated(_GLFWwindow* window, GLFWbool enabled)
|
||||
{
|
||||
// TODO
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Window attribute setting not implemented yet");
|
||||
if (!window->monitor)
|
||||
{
|
||||
if (enabled)
|
||||
createDecorations(window);
|
||||
else
|
||||
destroyDecorations(window);
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowFloating(_GLFWwindow* window, GLFWbool enabled)
|
||||
@@ -1013,66 +1242,19 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
|
||||
const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
{
|
||||
// TODO
|
||||
return NULL;
|
||||
return glfw_xkb_keysym_name(scancode);
|
||||
}
|
||||
|
||||
int _glfwPlatformGetKeyScancode(int key)
|
||||
{
|
||||
return _glfw.wl.scancodes[key];
|
||||
return glfw_xkb_sym_for_key(key);
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
const GLFWimage* image,
|
||||
int xhot, int yhot)
|
||||
{
|
||||
struct wl_shm_pool* pool;
|
||||
int stride = image->width * 4;
|
||||
int length = image->width * image->height * 4;
|
||||
void* data;
|
||||
int fd, i;
|
||||
|
||||
fd = createAnonymousFile(length);
|
||||
if (fd < 0)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Creating a buffer file for %d B failed: %m",
|
||||
length);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (data == MAP_FAILED)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Cursor mmap failed: %m");
|
||||
close(fd);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
pool = wl_shm_create_pool(_glfw.wl.shm, fd, length);
|
||||
|
||||
close(fd);
|
||||
unsigned char* source = (unsigned char*) image->pixels;
|
||||
unsigned char* target = data;
|
||||
for (i = 0; i < image->width * image->height; i++, source += 4)
|
||||
{
|
||||
unsigned int alpha = source[3];
|
||||
|
||||
*target++ = (unsigned char) ((source[2] * alpha) / 255);
|
||||
*target++ = (unsigned char) ((source[1] * alpha) / 255);
|
||||
*target++ = (unsigned char) ((source[0] * alpha) / 255);
|
||||
*target++ = (unsigned char) alpha;
|
||||
}
|
||||
|
||||
cursor->wl.buffer =
|
||||
wl_shm_pool_create_buffer(pool, 0,
|
||||
image->width,
|
||||
image->height,
|
||||
stride, WL_SHM_FORMAT_ARGB8888);
|
||||
munmap(data, length);
|
||||
wl_shm_pool_destroy(pool);
|
||||
|
||||
cursor->wl.buffer = createShmBuffer(image);
|
||||
cursor->wl.width = image->width;
|
||||
cursor->wl.height = image->height;
|
||||
cursor->wl.xhot = xhot;
|
||||
|
||||
447
glfw/x11_init.c
vendored
447
glfw/x11_init.c
vendored
@@ -36,319 +36,6 @@
|
||||
#include <locale.h>
|
||||
|
||||
|
||||
// Translate an X11 key code to a GLFW key code.
|
||||
//
|
||||
static int translateKeyCode(int scancode)
|
||||
{
|
||||
int keySym;
|
||||
|
||||
// Valid key code range is [8,255], according to the Xlib manual
|
||||
if (scancode < 8 || scancode > 255)
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
|
||||
if (_glfw.x11.xkb.available)
|
||||
{
|
||||
// Try secondary keysym, for numeric keypad keys
|
||||
// Note: This way we always force "NumLock = ON", which is intentional
|
||||
// since the returned key code should correspond to a physical
|
||||
// location.
|
||||
keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 1);
|
||||
switch (keySym)
|
||||
{
|
||||
case XK_KP_0: return GLFW_KEY_KP_0;
|
||||
case XK_KP_1: return GLFW_KEY_KP_1;
|
||||
case XK_KP_2: return GLFW_KEY_KP_2;
|
||||
case XK_KP_3: return GLFW_KEY_KP_3;
|
||||
case XK_KP_4: return GLFW_KEY_KP_4;
|
||||
case XK_KP_5: return GLFW_KEY_KP_5;
|
||||
case XK_KP_6: return GLFW_KEY_KP_6;
|
||||
case XK_KP_7: return GLFW_KEY_KP_7;
|
||||
case XK_KP_8: return GLFW_KEY_KP_8;
|
||||
case XK_KP_9: return GLFW_KEY_KP_9;
|
||||
case XK_KP_Separator:
|
||||
case XK_KP_Decimal: return GLFW_KEY_KP_DECIMAL;
|
||||
case XK_KP_Equal: return GLFW_KEY_KP_EQUAL;
|
||||
case XK_KP_Enter: return GLFW_KEY_KP_ENTER;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Now try primary keysym for function keys (non-printable keys)
|
||||
// These should not depend on the current keyboard layout
|
||||
keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
int dummy;
|
||||
KeySym* keySyms;
|
||||
|
||||
keySyms = XGetKeyboardMapping(_glfw.x11.display, scancode, 1, &dummy);
|
||||
keySym = keySyms[0];
|
||||
XFree(keySyms);
|
||||
}
|
||||
|
||||
switch (keySym)
|
||||
{
|
||||
case XK_Escape: return GLFW_KEY_ESCAPE;
|
||||
case XK_Tab: return GLFW_KEY_TAB;
|
||||
case XK_Shift_L: return GLFW_KEY_LEFT_SHIFT;
|
||||
case XK_Shift_R: return GLFW_KEY_RIGHT_SHIFT;
|
||||
case XK_Control_L: return GLFW_KEY_LEFT_CONTROL;
|
||||
case XK_Control_R: return GLFW_KEY_RIGHT_CONTROL;
|
||||
case XK_Meta_L:
|
||||
case XK_Alt_L: return GLFW_KEY_LEFT_ALT;
|
||||
case XK_Mode_switch: // Mapped to Alt_R on many keyboards
|
||||
case XK_ISO_Level3_Shift: // AltGr on at least some machines
|
||||
case XK_Meta_R:
|
||||
case XK_Alt_R: return GLFW_KEY_RIGHT_ALT;
|
||||
case XK_Super_L: return GLFW_KEY_LEFT_SUPER;
|
||||
case XK_Super_R: return GLFW_KEY_RIGHT_SUPER;
|
||||
case XK_Menu: return GLFW_KEY_MENU;
|
||||
case XK_Num_Lock: return GLFW_KEY_NUM_LOCK;
|
||||
case XK_Caps_Lock: return GLFW_KEY_CAPS_LOCK;
|
||||
case XK_Print: return GLFW_KEY_PRINT_SCREEN;
|
||||
case XK_Scroll_Lock: return GLFW_KEY_SCROLL_LOCK;
|
||||
case XK_Pause: return GLFW_KEY_PAUSE;
|
||||
case XK_Delete: return GLFW_KEY_DELETE;
|
||||
case XK_BackSpace: return GLFW_KEY_BACKSPACE;
|
||||
case XK_Return: return GLFW_KEY_ENTER;
|
||||
case XK_Home: return GLFW_KEY_HOME;
|
||||
case XK_End: return GLFW_KEY_END;
|
||||
case XK_Page_Up: return GLFW_KEY_PAGE_UP;
|
||||
case XK_Page_Down: return GLFW_KEY_PAGE_DOWN;
|
||||
case XK_Insert: return GLFW_KEY_INSERT;
|
||||
case XK_Left: return GLFW_KEY_LEFT;
|
||||
case XK_Right: return GLFW_KEY_RIGHT;
|
||||
case XK_Down: return GLFW_KEY_DOWN;
|
||||
case XK_Up: return GLFW_KEY_UP;
|
||||
case XK_F1: return GLFW_KEY_F1;
|
||||
case XK_F2: return GLFW_KEY_F2;
|
||||
case XK_F3: return GLFW_KEY_F3;
|
||||
case XK_F4: return GLFW_KEY_F4;
|
||||
case XK_F5: return GLFW_KEY_F5;
|
||||
case XK_F6: return GLFW_KEY_F6;
|
||||
case XK_F7: return GLFW_KEY_F7;
|
||||
case XK_F8: return GLFW_KEY_F8;
|
||||
case XK_F9: return GLFW_KEY_F9;
|
||||
case XK_F10: return GLFW_KEY_F10;
|
||||
case XK_F11: return GLFW_KEY_F11;
|
||||
case XK_F12: return GLFW_KEY_F12;
|
||||
case XK_F13: return GLFW_KEY_F13;
|
||||
case XK_F14: return GLFW_KEY_F14;
|
||||
case XK_F15: return GLFW_KEY_F15;
|
||||
case XK_F16: return GLFW_KEY_F16;
|
||||
case XK_F17: return GLFW_KEY_F17;
|
||||
case XK_F18: return GLFW_KEY_F18;
|
||||
case XK_F19: return GLFW_KEY_F19;
|
||||
case XK_F20: return GLFW_KEY_F20;
|
||||
case XK_F21: return GLFW_KEY_F21;
|
||||
case XK_F22: return GLFW_KEY_F22;
|
||||
case XK_F23: return GLFW_KEY_F23;
|
||||
case XK_F24: return GLFW_KEY_F24;
|
||||
case XK_F25: return GLFW_KEY_F25;
|
||||
|
||||
// Numeric keypad
|
||||
case XK_KP_Divide: return GLFW_KEY_KP_DIVIDE;
|
||||
case XK_KP_Multiply: return GLFW_KEY_KP_MULTIPLY;
|
||||
case XK_KP_Subtract: return GLFW_KEY_KP_SUBTRACT;
|
||||
case XK_KP_Add: return GLFW_KEY_KP_ADD;
|
||||
|
||||
// These should have been detected in secondary keysym test above!
|
||||
case XK_KP_Insert: return GLFW_KEY_KP_0;
|
||||
case XK_KP_End: return GLFW_KEY_KP_1;
|
||||
case XK_KP_Down: return GLFW_KEY_KP_2;
|
||||
case XK_KP_Page_Down: return GLFW_KEY_KP_3;
|
||||
case XK_KP_Left: return GLFW_KEY_KP_4;
|
||||
case XK_KP_Right: return GLFW_KEY_KP_6;
|
||||
case XK_KP_Home: return GLFW_KEY_KP_7;
|
||||
case XK_KP_Up: return GLFW_KEY_KP_8;
|
||||
case XK_KP_Page_Up: return GLFW_KEY_KP_9;
|
||||
case XK_KP_Delete: return GLFW_KEY_KP_DECIMAL;
|
||||
case XK_KP_Equal: return GLFW_KEY_KP_EQUAL;
|
||||
case XK_KP_Enter: return GLFW_KEY_KP_ENTER;
|
||||
|
||||
// Last resort: Check for printable keys (should not happen if the XKB
|
||||
// extension is available). This will give a layout dependent mapping
|
||||
// (which is wrong, and we may miss some keys, especially on non-US
|
||||
// keyboards), but it's better than nothing...
|
||||
case XK_a: return GLFW_KEY_A;
|
||||
case XK_b: return GLFW_KEY_B;
|
||||
case XK_c: return GLFW_KEY_C;
|
||||
case XK_d: return GLFW_KEY_D;
|
||||
case XK_e: return GLFW_KEY_E;
|
||||
case XK_f: return GLFW_KEY_F;
|
||||
case XK_g: return GLFW_KEY_G;
|
||||
case XK_h: return GLFW_KEY_H;
|
||||
case XK_i: return GLFW_KEY_I;
|
||||
case XK_j: return GLFW_KEY_J;
|
||||
case XK_k: return GLFW_KEY_K;
|
||||
case XK_l: return GLFW_KEY_L;
|
||||
case XK_m: return GLFW_KEY_M;
|
||||
case XK_n: return GLFW_KEY_N;
|
||||
case XK_o: return GLFW_KEY_O;
|
||||
case XK_p: return GLFW_KEY_P;
|
||||
case XK_q: return GLFW_KEY_Q;
|
||||
case XK_r: return GLFW_KEY_R;
|
||||
case XK_s: return GLFW_KEY_S;
|
||||
case XK_t: return GLFW_KEY_T;
|
||||
case XK_u: return GLFW_KEY_U;
|
||||
case XK_v: return GLFW_KEY_V;
|
||||
case XK_w: return GLFW_KEY_W;
|
||||
case XK_x: return GLFW_KEY_X;
|
||||
case XK_y: return GLFW_KEY_Y;
|
||||
case XK_z: return GLFW_KEY_Z;
|
||||
case XK_1: return GLFW_KEY_1;
|
||||
case XK_2: return GLFW_KEY_2;
|
||||
case XK_3: return GLFW_KEY_3;
|
||||
case XK_4: return GLFW_KEY_4;
|
||||
case XK_5: return GLFW_KEY_5;
|
||||
case XK_6: return GLFW_KEY_6;
|
||||
case XK_7: return GLFW_KEY_7;
|
||||
case XK_8: return GLFW_KEY_8;
|
||||
case XK_9: return GLFW_KEY_9;
|
||||
case XK_0: return GLFW_KEY_0;
|
||||
case XK_space: return GLFW_KEY_SPACE;
|
||||
case XK_minus: return GLFW_KEY_MINUS;
|
||||
case XK_equal: return GLFW_KEY_EQUAL;
|
||||
case XK_bracketleft: return GLFW_KEY_LEFT_BRACKET;
|
||||
case XK_bracketright: return GLFW_KEY_RIGHT_BRACKET;
|
||||
case XK_backslash: return GLFW_KEY_BACKSLASH;
|
||||
case XK_semicolon: return GLFW_KEY_SEMICOLON;
|
||||
case XK_apostrophe: return GLFW_KEY_APOSTROPHE;
|
||||
case XK_grave: return GLFW_KEY_GRAVE_ACCENT;
|
||||
case XK_comma: return GLFW_KEY_COMMA;
|
||||
case XK_period: return GLFW_KEY_PERIOD;
|
||||
case XK_slash: return GLFW_KEY_SLASH;
|
||||
case XK_less: return GLFW_KEY_WORLD_1; // At least in some layouts...
|
||||
default: break;
|
||||
}
|
||||
|
||||
// No matching translation was found
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
}
|
||||
|
||||
// Create key code translation tables
|
||||
//
|
||||
static void createKeyTables(void)
|
||||
{
|
||||
int scancode, key;
|
||||
|
||||
memset(_glfw.x11.keycodes, -1, sizeof(_glfw.x11.keycodes));
|
||||
memset(_glfw.x11.scancodes, -1, sizeof(_glfw.x11.scancodes));
|
||||
|
||||
if (_glfw.x11.xkb.available)
|
||||
{
|
||||
// Use XKB to determine physical key locations independently of the
|
||||
// current keyboard layout
|
||||
|
||||
char name[XkbKeyNameLength + 1];
|
||||
XkbDescPtr desc = XkbGetMap(_glfw.x11.display, 0, XkbUseCoreKbd);
|
||||
XkbGetNames(_glfw.x11.display, XkbKeyNamesMask, desc);
|
||||
|
||||
// Find the X11 key code -> GLFW key code mapping
|
||||
for (scancode = desc->min_key_code; scancode <= desc->max_key_code; scancode++)
|
||||
{
|
||||
memcpy(name, desc->names->keys[scancode].name, XkbKeyNameLength);
|
||||
name[XkbKeyNameLength] = '\0';
|
||||
|
||||
// Map the key name to a GLFW key code. Note: We only map printable
|
||||
// keys here, and we use the US keyboard layout. The rest of the
|
||||
// keys (function keys) are mapped using traditional KeySym
|
||||
// translations.
|
||||
if (strcmp(name, "TLDE") == 0) key = GLFW_KEY_GRAVE_ACCENT;
|
||||
else if (strcmp(name, "AE01") == 0) key = GLFW_KEY_1;
|
||||
else if (strcmp(name, "AE02") == 0) key = GLFW_KEY_2;
|
||||
else if (strcmp(name, "AE03") == 0) key = GLFW_KEY_3;
|
||||
else if (strcmp(name, "AE04") == 0) key = GLFW_KEY_4;
|
||||
else if (strcmp(name, "AE05") == 0) key = GLFW_KEY_5;
|
||||
else if (strcmp(name, "AE06") == 0) key = GLFW_KEY_6;
|
||||
else if (strcmp(name, "AE07") == 0) key = GLFW_KEY_7;
|
||||
else if (strcmp(name, "AE08") == 0) key = GLFW_KEY_8;
|
||||
else if (strcmp(name, "AE09") == 0) key = GLFW_KEY_9;
|
||||
else if (strcmp(name, "AE10") == 0) key = GLFW_KEY_0;
|
||||
else if (strcmp(name, "AE11") == 0) key = GLFW_KEY_MINUS;
|
||||
else if (strcmp(name, "AE12") == 0) key = GLFW_KEY_EQUAL;
|
||||
else if (strcmp(name, "AD01") == 0) key = GLFW_KEY_Q;
|
||||
else if (strcmp(name, "AD02") == 0) key = GLFW_KEY_W;
|
||||
else if (strcmp(name, "AD03") == 0) key = GLFW_KEY_E;
|
||||
else if (strcmp(name, "AD04") == 0) key = GLFW_KEY_R;
|
||||
else if (strcmp(name, "AD05") == 0) key = GLFW_KEY_T;
|
||||
else if (strcmp(name, "AD06") == 0) key = GLFW_KEY_Y;
|
||||
else if (strcmp(name, "AD07") == 0) key = GLFW_KEY_U;
|
||||
else if (strcmp(name, "AD08") == 0) key = GLFW_KEY_I;
|
||||
else if (strcmp(name, "AD09") == 0) key = GLFW_KEY_O;
|
||||
else if (strcmp(name, "AD10") == 0) key = GLFW_KEY_P;
|
||||
else if (strcmp(name, "AD11") == 0) key = GLFW_KEY_LEFT_BRACKET;
|
||||
else if (strcmp(name, "AD12") == 0) key = GLFW_KEY_RIGHT_BRACKET;
|
||||
else if (strcmp(name, "AC01") == 0) key = GLFW_KEY_A;
|
||||
else if (strcmp(name, "AC02") == 0) key = GLFW_KEY_S;
|
||||
else if (strcmp(name, "AC03") == 0) key = GLFW_KEY_D;
|
||||
else if (strcmp(name, "AC04") == 0) key = GLFW_KEY_F;
|
||||
else if (strcmp(name, "AC05") == 0) key = GLFW_KEY_G;
|
||||
else if (strcmp(name, "AC06") == 0) key = GLFW_KEY_H;
|
||||
else if (strcmp(name, "AC07") == 0) key = GLFW_KEY_J;
|
||||
else if (strcmp(name, "AC08") == 0) key = GLFW_KEY_K;
|
||||
else if (strcmp(name, "AC09") == 0) key = GLFW_KEY_L;
|
||||
else if (strcmp(name, "AC10") == 0) key = GLFW_KEY_SEMICOLON;
|
||||
else if (strcmp(name, "AC11") == 0) key = GLFW_KEY_APOSTROPHE;
|
||||
else if (strcmp(name, "AB01") == 0) key = GLFW_KEY_Z;
|
||||
else if (strcmp(name, "AB02") == 0) key = GLFW_KEY_X;
|
||||
else if (strcmp(name, "AB03") == 0) key = GLFW_KEY_C;
|
||||
else if (strcmp(name, "AB04") == 0) key = GLFW_KEY_V;
|
||||
else if (strcmp(name, "AB05") == 0) key = GLFW_KEY_B;
|
||||
else if (strcmp(name, "AB06") == 0) key = GLFW_KEY_N;
|
||||
else if (strcmp(name, "AB07") == 0) key = GLFW_KEY_M;
|
||||
else if (strcmp(name, "AB08") == 0) key = GLFW_KEY_COMMA;
|
||||
else if (strcmp(name, "AB09") == 0) key = GLFW_KEY_PERIOD;
|
||||
else if (strcmp(name, "AB10") == 0) key = GLFW_KEY_SLASH;
|
||||
else if (strcmp(name, "BKSL") == 0) key = GLFW_KEY_BACKSLASH;
|
||||
else if (strcmp(name, "LSGT") == 0) key = GLFW_KEY_WORLD_1;
|
||||
else key = GLFW_KEY_UNKNOWN;
|
||||
|
||||
if ((scancode >= 0) && (scancode < 256))
|
||||
_glfw.x11.keycodes[scancode] = key;
|
||||
}
|
||||
|
||||
XkbFreeNames(desc, XkbKeyNamesMask, True);
|
||||
XkbFreeKeyboard(desc, 0, True);
|
||||
}
|
||||
|
||||
for (scancode = 0; scancode < 256; scancode++)
|
||||
{
|
||||
// Translate the un-translated key codes using traditional X11 KeySym
|
||||
// lookups
|
||||
if (_glfw.x11.keycodes[scancode] < 0)
|
||||
_glfw.x11.keycodes[scancode] = translateKeyCode(scancode);
|
||||
|
||||
// Store the reverse translation for faster key name lookup
|
||||
if (_glfw.x11.keycodes[scancode] > 0)
|
||||
_glfw.x11.scancodes[_glfw.x11.keycodes[scancode]] = scancode;
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the IM has a usable style
|
||||
//
|
||||
static GLFWbool hasUsableInputMethodStyle(void)
|
||||
{
|
||||
unsigned int i;
|
||||
GLFWbool found = GLFW_FALSE;
|
||||
XIMStyles* styles = NULL;
|
||||
|
||||
if (XGetIMValues(_glfw.x11.im, XNQueryInputStyle, &styles, NULL) != NULL)
|
||||
return GLFW_FALSE;
|
||||
|
||||
for (i = 0; i < styles->count_styles; i++)
|
||||
{
|
||||
if (styles->supported_styles[i] == (XIMPreeditNothing | XIMStatusNothing))
|
||||
{
|
||||
found = GLFW_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XFree(styles);
|
||||
return found;
|
||||
}
|
||||
|
||||
// Check whether the specified atom is supported
|
||||
//
|
||||
static Atom getSupportedAtom(Atom* supportedAtoms,
|
||||
@@ -479,7 +166,11 @@ static GLFWbool initExtensions(void)
|
||||
&_glfw.x11.vidmode.errorBase);
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xi.handle = _glfw_dlopen("libXi-6.so");
|
||||
#else
|
||||
_glfw.x11.xi.handle = _glfw_dlopen("libXi.so.6");
|
||||
#endif
|
||||
if (_glfw.x11.xi.handle)
|
||||
{
|
||||
_glfw.x11.xi.QueryVersion = (PFN_XIQueryVersion)
|
||||
@@ -505,7 +196,11 @@ static GLFWbool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.randr.handle = _glfw_dlopen("libXrandr-2.so");
|
||||
#else
|
||||
_glfw.x11.randr.handle = _glfw_dlopen("libXrandr.so.2");
|
||||
#endif
|
||||
if (_glfw.x11.randr.handle)
|
||||
{
|
||||
_glfw.x11.randr.AllocGamma = (PFN_XRRAllocGamma)
|
||||
@@ -593,7 +288,11 @@ static GLFWbool initExtensions(void)
|
||||
RROutputChangeNotifyMask);
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor-1.so");
|
||||
#else
|
||||
_glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor.so.1");
|
||||
#endif
|
||||
if (_glfw.x11.xcursor.handle)
|
||||
{
|
||||
_glfw.x11.xcursor.ImageCreate = (PFN_XcursorImageCreate)
|
||||
@@ -604,7 +303,11 @@ static GLFWbool initExtensions(void)
|
||||
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor");
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama-1.so");
|
||||
#else
|
||||
_glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama.so.1");
|
||||
#endif
|
||||
if (_glfw.x11.xinerama.handle)
|
||||
{
|
||||
_glfw.x11.xinerama.IsActive = (PFN_XineramaIsActive)
|
||||
@@ -623,35 +326,11 @@ static GLFWbool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
_glfw.x11.xkb.major = 1;
|
||||
_glfw.x11.xkb.minor = 0;
|
||||
_glfw.x11.xkb.available =
|
||||
XkbQueryExtension(_glfw.x11.display,
|
||||
&_glfw.x11.xkb.majorOpcode,
|
||||
&_glfw.x11.xkb.eventBase,
|
||||
&_glfw.x11.xkb.errorBase,
|
||||
&_glfw.x11.xkb.major,
|
||||
&_glfw.x11.xkb.minor);
|
||||
|
||||
if (_glfw.x11.xkb.available)
|
||||
{
|
||||
Bool supported;
|
||||
|
||||
if (XkbSetDetectableAutoRepeat(_glfw.x11.display, True, &supported))
|
||||
{
|
||||
if (supported)
|
||||
_glfw.x11.xkb.detectable = GLFW_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
_glfw.x11.x11xcb.handle = _glfw_dlopen("libX11-xcb.so.1");
|
||||
if (_glfw.x11.x11xcb.handle)
|
||||
{
|
||||
_glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection)
|
||||
_glfw_dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
|
||||
}
|
||||
|
||||
#if defined(__CYGWIN__)
|
||||
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender-1.so");
|
||||
#else
|
||||
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender.so.1");
|
||||
#endif
|
||||
if (_glfw.x11.xrender.handle)
|
||||
{
|
||||
_glfw.x11.xrender.QueryExtension = (PFN_XRenderQueryExtension)
|
||||
@@ -674,10 +353,31 @@ static GLFWbool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the key code LUT
|
||||
// FIXME: We should listen to XkbMapNotify events to track changes to
|
||||
// the keyboard mapping.
|
||||
createKeyTables();
|
||||
_glfw.x11.xkb.major = 1;
|
||||
_glfw.x11.xkb.minor = 0;
|
||||
_glfw.x11.xkb.available = XkbQueryExtension(_glfw.x11.display,
|
||||
&_glfw.x11.xkb.majorOpcode,
|
||||
&_glfw.x11.xkb.eventBase,
|
||||
&_glfw.x11.xkb.errorBase,
|
||||
&_glfw.x11.xkb.major,
|
||||
&_glfw.x11.xkb.minor);
|
||||
|
||||
if (!_glfw.x11.xkb.available)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to load Xkb extension");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
Bool supported;
|
||||
if (XkbSetDetectableAutoRepeat(_glfw.x11.display, True, &supported))
|
||||
{
|
||||
if (supported)
|
||||
_glfw.x11.xkb.detectable = GLFW_TRUE;
|
||||
}
|
||||
|
||||
if (!glfw_xkb_set_x11_events_mask()) return GLFW_FALSE;
|
||||
if (!glfw_xkb_create_context(&_glfw.x11.xkb)) return GLFW_FALSE;
|
||||
if (!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return GLFW_FALSE;
|
||||
if (!glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL)) return GLFW_FALSE;
|
||||
|
||||
// Detect whether an EWMH-conformant window manager is running
|
||||
detectEWMH();
|
||||
@@ -896,16 +596,6 @@ Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot)
|
||||
|
||||
int _glfwPlatformInit(void)
|
||||
{
|
||||
#if !defined(X_HAVE_UTF8_STRING)
|
||||
// HACK: If the current locale is "C" and the Xlib UTF-8 functions are
|
||||
// unavailable, apply the environment's locale in the hope that it's
|
||||
// both available and not "C"
|
||||
// This is done because the "C" locale breaks wide character input,
|
||||
// which is what we fall back on when UTF-8 support is missing
|
||||
if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0)
|
||||
setlocale(LC_CTYPE, "");
|
||||
#endif
|
||||
|
||||
XInitThreads();
|
||||
XrmInitialize();
|
||||
|
||||
@@ -939,21 +629,6 @@ int _glfwPlatformInit(void)
|
||||
_glfw.x11.helperWindowHandle = createHelperWindow();
|
||||
_glfw.x11.hiddenCursorHandle = createHiddenCursor();
|
||||
|
||||
if (XSupportsLocale())
|
||||
{
|
||||
XSetLocaleModifiers("");
|
||||
|
||||
_glfw.x11.im = XOpenIM(_glfw.x11.display, 0, NULL, NULL);
|
||||
if (_glfw.x11.im)
|
||||
{
|
||||
if (!hasUsableInputMethodStyle())
|
||||
{
|
||||
XCloseIM(_glfw.x11.im);
|
||||
_glfw.x11.im = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(__linux__)
|
||||
if (!_glfwInitJoysticksLinux())
|
||||
return GLFW_FALSE;
|
||||
@@ -985,27 +660,16 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.x11.hiddenCursorHandle = (Cursor) 0;
|
||||
}
|
||||
|
||||
glfw_xkb_release(&_glfw.x11.xkb);
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
free(_glfw.x11.clipboardString);
|
||||
|
||||
if (_glfw.x11.im)
|
||||
{
|
||||
XCloseIM(_glfw.x11.im);
|
||||
_glfw.x11.im = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.display)
|
||||
{
|
||||
XCloseDisplay(_glfw.x11.display);
|
||||
_glfw.x11.display = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.x11xcb.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.x11xcb.handle);
|
||||
_glfw.x11.x11xcb.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xcursor.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.xcursor.handle);
|
||||
@@ -1024,6 +688,24 @@ void _glfwPlatformTerminate(void)
|
||||
_glfw.x11.xinerama.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xrender.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.xrender.handle);
|
||||
_glfw.x11.xrender.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.vidmode.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.vidmode.handle);
|
||||
_glfw.x11.vidmode.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xi.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.x11.xi.handle);
|
||||
_glfw.x11.xi.handle = NULL;
|
||||
}
|
||||
|
||||
// NOTE: These need to be unloaded after XCloseDisplay, as they register
|
||||
// cleanup callbacks that get called by that function
|
||||
_glfwTerminateEGL();
|
||||
@@ -1050,4 +732,3 @@ const char* _glfwPlatformGetVersionString(void)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
17
glfw/x11_monitor.c
vendored
17
glfw/x11_monitor.c
vendored
@@ -216,7 +216,7 @@ void _glfwPollMonitorsX11(void)
|
||||
|
||||
// Set the current video mode for the specified monitor
|
||||
//
|
||||
GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
{
|
||||
@@ -231,7 +231,7 @@ GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
best = _glfwChooseVideoMode(monitor, desired);
|
||||
_glfwPlatformGetVideoMode(monitor, ¤t);
|
||||
if (_glfwCompareVideoModes(¤t, best) == 0)
|
||||
return GLFW_TRUE;
|
||||
return;
|
||||
|
||||
sr = XRRGetScreenResourcesCurrent(_glfw.x11.display, _glfw.x11.root);
|
||||
ci = XRRGetCrtcInfo(_glfw.x11.display, sr, monitor->x11.crtc);
|
||||
@@ -269,16 +269,7 @@ GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired)
|
||||
XRRFreeOutputInfo(oi);
|
||||
XRRFreeCrtcInfo(ci);
|
||||
XRRFreeScreenResources(sr);
|
||||
|
||||
if (!native)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"X11: Monitor mode list changed");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
// Restore the saved (original) video mode for the specified monitor
|
||||
@@ -317,6 +308,10 @@ void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor)
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void _glfwPlatformFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
}
|
||||
|
||||
void _glfwPlatformGetMonitorPos(_GLFWmonitor* monitor, int* xpos, int* ypos)
|
||||
{
|
||||
if (_glfw.x11.randr.available && !_glfw.x11.randr.monitorBroken)
|
||||
|
||||
38
glfw/x11_platform.h
vendored
38
glfw/x11_platform.h
vendored
@@ -35,6 +35,9 @@
|
||||
#include <X11/Xatom.h>
|
||||
#include <X11/Xcursor/Xcursor.h>
|
||||
|
||||
// The xcb library is needed to work with libxkb
|
||||
#include <X11/Xlib-xcb.h>
|
||||
|
||||
// The XRandR extension provides mode setting and gamma control
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
@@ -47,6 +50,9 @@
|
||||
// The XInput extension provides raw mouse motion input
|
||||
#include <X11/extensions/XInput2.h>
|
||||
|
||||
// The libxkb library is used for improved keyboard support
|
||||
#include "xkb_glfw.h"
|
||||
|
||||
typedef XRRCrtcGamma* (* PFN_XRRAllocGamma)(int);
|
||||
typedef void (* PFN_XRRFreeCrtcInfo)(XRRCrtcInfo*);
|
||||
typedef void (* PFN_XRRFreeGamma)(XRRCrtcGamma*);
|
||||
@@ -96,11 +102,7 @@ typedef XineramaScreenInfo* (* PFN_XineramaQueryScreens)(Display*,int*);
|
||||
#define XineramaQueryExtension _glfw.x11.xinerama.QueryExtension
|
||||
#define XineramaQueryScreens _glfw.x11.xinerama.QueryScreens
|
||||
|
||||
typedef XID xcb_window_t;
|
||||
typedef XID xcb_visualid_t;
|
||||
typedef struct xcb_connection_t xcb_connection_t;
|
||||
typedef xcb_connection_t* (* PFN_XGetXCBConnection)(Display*);
|
||||
#define XGetXCBConnection _glfw.x11.x11xcb.GetXCBConnection
|
||||
|
||||
typedef Bool (* PFN_XF86VidModeQueryExtension)(Display*,int*,int*);
|
||||
typedef Bool (* PFN_XF86VidModeGetGammaRamp)(Display*,int,int,unsigned short*,unsigned short*,unsigned short*);
|
||||
@@ -151,7 +153,6 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR)(Vk
|
||||
|
||||
#include "posix_thread.h"
|
||||
#include "posix_time.h"
|
||||
#include "xkb_unicode.h"
|
||||
#include "glx_context.h"
|
||||
#include "egl_context.h"
|
||||
#include "osmesa_context.h"
|
||||
@@ -180,7 +181,6 @@ typedef struct _GLFWwindowX11
|
||||
{
|
||||
Colormap colormap;
|
||||
Window handle;
|
||||
XIC ic;
|
||||
|
||||
GLFWbool overrideRedirect;
|
||||
GLFWbool iconified;
|
||||
@@ -219,20 +219,12 @@ typedef struct _GLFWlibraryX11
|
||||
Cursor hiddenCursorHandle;
|
||||
// Context for mapping window XIDs to _GLFWwindow pointers
|
||||
XContext context;
|
||||
// XIM input method
|
||||
XIM im;
|
||||
// Most recent error code received by X error handler
|
||||
int errorCode;
|
||||
// Primary selection string (while the primary selection is owned)
|
||||
char* primarySelectionString;
|
||||
// Clipboard string (while the selection is owned)
|
||||
char* clipboardString;
|
||||
// Key name string
|
||||
char keyName[5];
|
||||
// X11 keycode to GLFW key LUT
|
||||
short int keycodes[256];
|
||||
// GLFW key to X11 keycode LUT
|
||||
short int scancodes[GLFW_KEY_LAST + 1];
|
||||
// Where to place the cursor when re-enabled
|
||||
double restoreCursorPosX, restoreCursorPosY;
|
||||
// The window whose disabled cursor mode is active
|
||||
@@ -318,15 +310,7 @@ typedef struct _GLFWlibraryX11
|
||||
PFN_XRRUpdateConfiguration UpdateConfiguration;
|
||||
} randr;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
GLFWbool detectable;
|
||||
int majorOpcode;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
int major;
|
||||
int minor;
|
||||
} xkb;
|
||||
_GLFWXKBData xkb;
|
||||
|
||||
struct {
|
||||
int count;
|
||||
@@ -359,11 +343,6 @@ typedef struct _GLFWlibraryX11
|
||||
PFN_XineramaQueryScreens QueryScreens;
|
||||
} xinerama;
|
||||
|
||||
struct {
|
||||
void* handle;
|
||||
PFN_XGetXCBConnection GetXCBConnection;
|
||||
} x11xcb;
|
||||
|
||||
struct {
|
||||
GLFWbool available;
|
||||
void* handle;
|
||||
@@ -425,7 +404,7 @@ typedef struct _GLFWcursorX11
|
||||
|
||||
|
||||
void _glfwPollMonitorsX11(void);
|
||||
GLFWbool _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwSetVideoModeX11(_GLFWmonitor* monitor, const GLFWvidmode* desired);
|
||||
void _glfwRestoreVideoModeX11(_GLFWmonitor* monitor);
|
||||
|
||||
Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot);
|
||||
@@ -441,4 +420,3 @@ void _glfwReleaseErrorHandlerX11(void);
|
||||
void _glfwInputErrorX11(int error, const char* message);
|
||||
|
||||
void _glfwPushSelectionToManagerX11(void);
|
||||
|
||||
|
||||
347
glfw/x11_window.c
vendored
347
glfw/x11_window.c
vendored
@@ -220,17 +220,6 @@ static int translateState(int state)
|
||||
return mods;
|
||||
}
|
||||
|
||||
// Translates an X11 key code to a GLFW key token
|
||||
//
|
||||
static int translateKey(int scancode)
|
||||
{
|
||||
// Use the pre-filled LUT (see createKeyTables() in x11_init.c)
|
||||
if (scancode < 0 || scancode > 255)
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
|
||||
return _glfw.x11.keycodes[scancode];
|
||||
}
|
||||
|
||||
// Return the GLFW window corresponding to the specified X11 window
|
||||
//
|
||||
static _GLFWwindow* findWindowByHandle(Window handle)
|
||||
@@ -497,31 +486,6 @@ static size_t encodeUTF8(char* s, unsigned int ch)
|
||||
return count;
|
||||
}
|
||||
|
||||
// Decode a Unicode code point from a UTF-8 stream
|
||||
// Based on cutef8 by Jeff Bezanson (Public Domain)
|
||||
//
|
||||
#if defined(X_HAVE_UTF8_STRING)
|
||||
static unsigned int decodeUTF8(const char** s)
|
||||
{
|
||||
unsigned int ch = 0, count = 0;
|
||||
static const unsigned int offsets[] =
|
||||
{
|
||||
0x00000000u, 0x00003080u, 0x000e2080u,
|
||||
0x03c82080u, 0xfa082080u, 0x82082080u
|
||||
};
|
||||
|
||||
do
|
||||
{
|
||||
ch = (ch << 6) + (unsigned char) **s;
|
||||
(*s)++;
|
||||
count++;
|
||||
} while ((**s & 0xc0) == 0x80);
|
||||
|
||||
assert(count <= 6);
|
||||
return ch - offsets[count - 1];
|
||||
}
|
||||
#endif /*X_HAVE_UTF8_STRING*/
|
||||
|
||||
// Convert the specified Latin-1 string to UTF-8
|
||||
//
|
||||
static char* convertLatin1toUTF8(const char* source)
|
||||
@@ -571,6 +535,61 @@ static void updateCursorImage(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply disabled cursor mode to a focused window
|
||||
//
|
||||
static void disableCursor(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.x11.restoreCursorPosX,
|
||||
&_glfw.x11.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
centerCursor(window);
|
||||
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync,
|
||||
window->x11.handle,
|
||||
_glfw.x11.hiddenCursorHandle,
|
||||
CurrentTime);
|
||||
}
|
||||
|
||||
// Exit disabled cursor mode for the specified window
|
||||
//
|
||||
static void enableCursor(_GLFWwindow* window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.x11.restoreCursorPosX,
|
||||
_glfw.x11.restoreCursorPosY);
|
||||
updateCursorImage(window);
|
||||
}
|
||||
|
||||
// Create the X11 window (and its colormap)
|
||||
//
|
||||
static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
@@ -746,19 +765,6 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
}
|
||||
|
||||
_glfwPlatformSetWindowTitle(window, wndconfig->title);
|
||||
|
||||
if (_glfw.x11.im)
|
||||
{
|
||||
window->x11.ic = XCreateIC(_glfw.x11.im,
|
||||
XNInputStyle,
|
||||
XIMPreeditNothing | XIMStatusNothing,
|
||||
XNClientWindow,
|
||||
window->x11.handle,
|
||||
XNFocusWindow,
|
||||
window->x11.handle,
|
||||
NULL);
|
||||
}
|
||||
|
||||
_glfwPlatformGetWindowPos(window, &window->x11.xpos, &window->x11.ypos);
|
||||
_glfwPlatformGetWindowSize(window, &window->x11.width, &window->x11.height);
|
||||
|
||||
@@ -1076,10 +1082,8 @@ static const char* getSelectionString(Atom selection)
|
||||
|
||||
// Make the specified window and its video mode active on its monitor
|
||||
//
|
||||
static GLFWbool acquireMonitor(_GLFWwindow* window)
|
||||
static void acquireMonitor(_GLFWwindow* window)
|
||||
{
|
||||
GLFWbool status;
|
||||
|
||||
if (_glfw.x11.saver.count == 0)
|
||||
{
|
||||
// Remember old screen saver settings
|
||||
@@ -1097,7 +1101,7 @@ static GLFWbool acquireMonitor(_GLFWwindow* window)
|
||||
if (!window->monitor->window)
|
||||
_glfw.x11.saver.count++;
|
||||
|
||||
status = _glfwSetVideoModeX11(window->monitor, &window->videoMode);
|
||||
_glfwSetVideoModeX11(window->monitor, &window->videoMode);
|
||||
|
||||
if (window->x11.overrideRedirect)
|
||||
{
|
||||
@@ -1113,7 +1117,6 @@ static GLFWbool acquireMonitor(_GLFWwindow* window)
|
||||
}
|
||||
|
||||
_glfwInputMonitorWindow(window->monitor, window);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Remove the window and restore the original video mode
|
||||
@@ -1144,16 +1147,8 @@ static void releaseMonitor(_GLFWwindow* window)
|
||||
static void processEvent(XEvent *event)
|
||||
{
|
||||
_GLFWwindow* window = NULL;
|
||||
int keycode = 0;
|
||||
Bool filtered = False;
|
||||
|
||||
// HACK: Save scancode as some IMs clear the field in XFilterEvent
|
||||
if (event->type == KeyPress || event->type == KeyRelease)
|
||||
keycode = event->xkey.keycode;
|
||||
|
||||
if (_glfw.x11.im)
|
||||
filtered = XFilterEvent(event, None);
|
||||
|
||||
if (_glfw.x11.randr.available)
|
||||
{
|
||||
if (event->type == _glfw.x11.randr.eventBase + RRNotify)
|
||||
@@ -1211,6 +1206,30 @@ static void processEvent(XEvent *event)
|
||||
handleSelectionRequest(event);
|
||||
return;
|
||||
}
|
||||
else if (event->type == _glfw.x11.xkb.eventBase)
|
||||
{
|
||||
XkbEvent *kb_event = (XkbEvent*)event;
|
||||
switch(kb_event->any.xkb_type) {
|
||||
case XkbNewKeyboardNotify:
|
||||
if(!glfw_xkb_update_x11_keyboard_id(&_glfw.x11.xkb)) return;
|
||||
/* fallthrough */
|
||||
case XkbMapNotify:
|
||||
{
|
||||
glfw_xkb_compile_keymap(&_glfw.x11.xkb, NULL);
|
||||
return;
|
||||
}
|
||||
case XkbStateNotify:
|
||||
{
|
||||
XkbStateNotifyEvent *state_event = (XkbStateNotifyEvent*)kb_event;
|
||||
glfw_xkb_update_modifiers(
|
||||
&_glfw.x11.xkb, state_event->base_mods, state_event->latched_mods,
|
||||
state_event->locked_mods, state_event->base_group, state_event->latched_group, state_event->locked_group
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
window = findWindowByHandle(event->xany.window);
|
||||
if (window == NULL)
|
||||
@@ -1223,105 +1242,12 @@ static void processEvent(XEvent *event)
|
||||
{
|
||||
case KeyPress:
|
||||
{
|
||||
const int key = translateKey(keycode);
|
||||
const int mods = translateState(event->xkey.state);
|
||||
const int plain = !(mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT));
|
||||
|
||||
if (window->x11.ic)
|
||||
{
|
||||
// HACK: Ignore duplicate key press events generated by ibus
|
||||
// These have the same timestamp as the original event
|
||||
// Corresponding release events are filtered out
|
||||
// implicitly by the GLFW key repeat logic
|
||||
if (window->x11.lastKeyTime < event->xkey.time)
|
||||
{
|
||||
if (keycode)
|
||||
_glfwInputKey(window, key, keycode, GLFW_PRESS, mods);
|
||||
|
||||
window->x11.lastKeyTime = event->xkey.time;
|
||||
}
|
||||
|
||||
if (!filtered)
|
||||
{
|
||||
int count;
|
||||
Status status;
|
||||
#if defined(X_HAVE_UTF8_STRING)
|
||||
char buffer[100];
|
||||
char* chars = buffer;
|
||||
|
||||
count = Xutf8LookupString(window->x11.ic,
|
||||
&event->xkey,
|
||||
buffer, sizeof(buffer) - 1,
|
||||
NULL, &status);
|
||||
|
||||
if (status == XBufferOverflow)
|
||||
{
|
||||
chars = calloc(count + 1, 1);
|
||||
count = Xutf8LookupString(window->x11.ic,
|
||||
&event->xkey,
|
||||
chars, count,
|
||||
NULL, &status);
|
||||
}
|
||||
|
||||
if (status == XLookupChars || status == XLookupBoth)
|
||||
{
|
||||
const char* c = chars;
|
||||
chars[count] = '\0';
|
||||
while (c - chars < count)
|
||||
_glfwInputChar(window, decodeUTF8(&c), mods, plain);
|
||||
}
|
||||
#else /*X_HAVE_UTF8_STRING*/
|
||||
wchar_t buffer[16];
|
||||
wchar_t* chars = buffer;
|
||||
|
||||
count = XwcLookupString(window->x11.ic,
|
||||
&event->xkey,
|
||||
buffer,
|
||||
sizeof(buffer) / sizeof(wchar_t),
|
||||
NULL,
|
||||
&status);
|
||||
|
||||
if (status == XBufferOverflow)
|
||||
{
|
||||
chars = calloc(count, sizeof(wchar_t));
|
||||
count = XwcLookupString(window->x11.ic,
|
||||
&event->xkey,
|
||||
chars, count,
|
||||
NULL, &status);
|
||||
}
|
||||
|
||||
if (status == XLookupChars || status == XLookupBoth)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < count; i++)
|
||||
_glfwInputChar(window, chars[i], mods, plain);
|
||||
}
|
||||
#endif /*X_HAVE_UTF8_STRING*/
|
||||
|
||||
if (chars != buffer)
|
||||
free(chars);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
KeySym keysym;
|
||||
XLookupString(&event->xkey, NULL, 0, &keysym, NULL);
|
||||
|
||||
_glfwInputKey(window, key, keycode, GLFW_PRESS, mods);
|
||||
|
||||
const long character = _glfwKeySym2Unicode(keysym);
|
||||
if (character != -1)
|
||||
_glfwInputChar(window, character, mods, plain);
|
||||
}
|
||||
|
||||
glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_PRESS);
|
||||
return;
|
||||
}
|
||||
|
||||
case KeyRelease:
|
||||
{
|
||||
const int key = translateKey(keycode);
|
||||
const int mods = translateState(event->xkey.state);
|
||||
|
||||
if (!_glfw.x11.xkb.detectable)
|
||||
{
|
||||
// HACK: Key repeat events will arrive as KeyRelease/KeyPress
|
||||
@@ -1335,7 +1261,7 @@ static void processEvent(XEvent *event)
|
||||
|
||||
if (next.type == KeyPress &&
|
||||
next.xkey.window == event->xkey.window &&
|
||||
next.xkey.keycode == keycode)
|
||||
next.xkey.keycode == event->xkey.keycode)
|
||||
{
|
||||
// HACK: The time of repeat events sometimes doesn't
|
||||
// match that of the press event, so add an
|
||||
@@ -1354,7 +1280,7 @@ static void processEvent(XEvent *event)
|
||||
}
|
||||
}
|
||||
|
||||
_glfwInputKey(window, key, keycode, GLFW_RELEASE, mods);
|
||||
glfw_xkb_handle_key_event(window, &_glfw.x11.xkb, event->xkey.keycode, GLFW_RELEASE);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1435,7 +1361,7 @@ static void processEvent(XEvent *event)
|
||||
// HACK: This is a workaround for WMs (KWM, Fluxbox) that otherwise
|
||||
// ignore the defined cursor for hidden cursor mode
|
||||
if (window->cursorMode == GLFW_CURSOR_HIDDEN)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_HIDDEN);
|
||||
updateCursorImage(window);
|
||||
|
||||
_glfwInputCursorEnter(window, GLFW_TRUE);
|
||||
return;
|
||||
@@ -1728,7 +1654,7 @@ static void processEvent(XEvent *event)
|
||||
case FocusIn:
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_DISABLED);
|
||||
disableCursor(window);
|
||||
|
||||
if (event->xfocus.mode == NotifyGrab ||
|
||||
event->xfocus.mode == NotifyUngrab)
|
||||
@@ -1738,9 +1664,6 @@ static void processEvent(XEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (window->x11.ic)
|
||||
XSetICFocus(window->x11.ic);
|
||||
|
||||
_glfwInputWindowFocus(window, GLFW_TRUE);
|
||||
return;
|
||||
}
|
||||
@@ -1748,7 +1671,7 @@ static void processEvent(XEvent *event)
|
||||
case FocusOut:
|
||||
{
|
||||
if (window->cursorMode == GLFW_CURSOR_DISABLED)
|
||||
_glfwPlatformSetCursorMode(window, GLFW_CURSOR_NORMAL);
|
||||
enableCursor(window);
|
||||
|
||||
if (event->xfocus.mode == NotifyGrab ||
|
||||
event->xfocus.mode == NotifyUngrab)
|
||||
@@ -1758,9 +1681,6 @@ static void processEvent(XEvent *event)
|
||||
return;
|
||||
}
|
||||
|
||||
if (window->x11.ic)
|
||||
XUnsetICFocus(window->x11.ic);
|
||||
|
||||
if (window->monitor && window->autoIconify)
|
||||
_glfwPlatformIconifyWindow(window);
|
||||
|
||||
@@ -1977,11 +1897,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
{
|
||||
_glfwPlatformShowWindow(window);
|
||||
updateWindowMode(window);
|
||||
if (!acquireMonitor(window))
|
||||
return GLFW_FALSE;
|
||||
|
||||
if (wndconfig->centerCursor)
|
||||
centerCursor(window);
|
||||
acquireMonitor(window);
|
||||
}
|
||||
|
||||
XFlush(_glfw.x11.display);
|
||||
@@ -1996,12 +1912,6 @@ void _glfwPlatformDestroyWindow(_GLFWwindow* window)
|
||||
if (window->monitor)
|
||||
releaseMonitor(window);
|
||||
|
||||
if (window->x11.ic)
|
||||
{
|
||||
XDestroyIC(window->x11.ic);
|
||||
window->x11.ic = NULL;
|
||||
}
|
||||
|
||||
if (window->context.destroy)
|
||||
window->context.destroy(window);
|
||||
|
||||
@@ -2347,9 +2257,9 @@ void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
0, 1, 0);
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t param)
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window)
|
||||
{
|
||||
return XkbBell(_glfw.x11.display, window->x11.handle, (int)param, (Atom)0) ? GLFW_TRUE : GLFW_FALSE;
|
||||
return XkbBell(_glfw.x11.display, window->x11.handle, 100, (Atom)0) ? GLFW_TRUE : GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformFocusWindow(_GLFWwindow* window)
|
||||
@@ -2720,80 +2630,26 @@ void _glfwPlatformSetCursorMode(_GLFWwindow* window, int mode)
|
||||
{
|
||||
if (mode == GLFW_CURSOR_DISABLED)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 };
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
XISetMask(mask, XI_RawMotion);
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = window;
|
||||
_glfwPlatformGetCursorPos(window,
|
||||
&_glfw.x11.restoreCursorPosX,
|
||||
&_glfw.x11.restoreCursorPosY);
|
||||
centerCursor(window);
|
||||
XGrabPointer(_glfw.x11.display, window->x11.handle, True,
|
||||
ButtonPressMask | ButtonReleaseMask | PointerMotionMask,
|
||||
GrabModeAsync, GrabModeAsync,
|
||||
window->x11.handle,
|
||||
_glfw.x11.hiddenCursorHandle,
|
||||
CurrentTime);
|
||||
if (_glfwPlatformWindowFocused(window))
|
||||
disableCursor(window);
|
||||
}
|
||||
else if (_glfw.x11.disabledCursorWindow == window)
|
||||
{
|
||||
if (_glfw.x11.xi.available)
|
||||
{
|
||||
XIEventMask em;
|
||||
unsigned char mask[] = { 0 };
|
||||
enableCursor(window);
|
||||
else
|
||||
updateCursorImage(window);
|
||||
|
||||
em.deviceid = XIAllMasterDevices;
|
||||
em.mask_len = sizeof(mask);
|
||||
em.mask = mask;
|
||||
|
||||
XISelectEvents(_glfw.x11.display, _glfw.x11.root, &em, 1);
|
||||
}
|
||||
|
||||
_glfw.x11.disabledCursorWindow = NULL;
|
||||
XUngrabPointer(_glfw.x11.display, CurrentTime);
|
||||
_glfwPlatformSetCursorPos(window,
|
||||
_glfw.x11.restoreCursorPosX,
|
||||
_glfw.x11.restoreCursorPosY);
|
||||
}
|
||||
|
||||
updateCursorImage(window);
|
||||
XFlush(_glfw.x11.display);
|
||||
}
|
||||
|
||||
const char* _glfwPlatformGetScancodeName(int scancode)
|
||||
{
|
||||
if (!_glfw.x11.xkb.available)
|
||||
return NULL;
|
||||
|
||||
const KeySym keysym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, 0, 0);
|
||||
if (keysym == NoSymbol)
|
||||
return NULL;
|
||||
|
||||
const long ch = _glfwKeySym2Unicode(keysym);
|
||||
if (ch == -1)
|
||||
return NULL;
|
||||
|
||||
const size_t count = encodeUTF8(_glfw.x11.keyName, (unsigned int) ch);
|
||||
if (count == 0)
|
||||
return NULL;
|
||||
|
||||
_glfw.x11.keyName[count] = '\0';
|
||||
return _glfw.x11.keyName;
|
||||
return glfw_xkb_keysym_name(scancode);
|
||||
}
|
||||
|
||||
int _glfwPlatformGetKeyScancode(int key)
|
||||
{
|
||||
return _glfw.x11.scancodes[key];
|
||||
return glfw_xkb_sym_for_key(key);
|
||||
}
|
||||
|
||||
int _glfwPlatformCreateCursor(_GLFWcursor* cursor,
|
||||
@@ -2864,7 +2720,7 @@ void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
if (!_glfw.vk.KHR_surface)
|
||||
return;
|
||||
|
||||
if (!_glfw.vk.KHR_xcb_surface || !_glfw.x11.x11xcb.handle)
|
||||
if (!_glfw.vk.KHR_xcb_surface)
|
||||
{
|
||||
if (!_glfw.vk.KHR_xlib_surface)
|
||||
return;
|
||||
@@ -2874,7 +2730,7 @@ void _glfwPlatformGetRequiredInstanceExtensions(char** extensions)
|
||||
|
||||
// NOTE: VK_KHR_xcb_surface is preferred due to some early ICDs exposing but
|
||||
// not correctly implementing VK_KHR_xlib_surface
|
||||
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
|
||||
if (_glfw.vk.KHR_xcb_surface)
|
||||
extensions[1] = "VK_KHR_xcb_surface";
|
||||
else
|
||||
extensions[1] = "VK_KHR_xlib_surface";
|
||||
@@ -2887,7 +2743,7 @@ int _glfwPlatformGetPhysicalDevicePresentationSupport(VkInstance instance,
|
||||
VisualID visualID = XVisualIDFromVisual(DefaultVisual(_glfw.x11.display,
|
||||
_glfw.x11.screen));
|
||||
|
||||
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
|
||||
if (_glfw.vk.KHR_xcb_surface)
|
||||
{
|
||||
PFN_vkGetPhysicalDeviceXcbPresentationSupportKHR
|
||||
vkGetPhysicalDeviceXcbPresentationSupportKHR =
|
||||
@@ -2938,7 +2794,7 @@ VkResult _glfwPlatformCreateWindowSurface(VkInstance instance,
|
||||
const VkAllocationCallbacks* allocator,
|
||||
VkSurfaceKHR* surface)
|
||||
{
|
||||
if (_glfw.vk.KHR_xcb_surface && _glfw.x11.x11xcb.handle)
|
||||
if (_glfw.vk.KHR_xcb_surface)
|
||||
{
|
||||
VkResult err;
|
||||
VkXcbSurfaceCreateInfoKHR sci;
|
||||
@@ -3051,4 +2907,3 @@ GLFWAPI const char* glfwGetX11SelectionString(void)
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return getSelectionString(_glfw.x11.PRIMARY);
|
||||
}
|
||||
|
||||
|
||||
427
glfw/xkb_glfw.c
vendored
Normal file
427
glfw/xkb_glfw.c
vendored
Normal file
@@ -0,0 +1,427 @@
|
||||
//========================================================================
|
||||
// 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 <string.h>
|
||||
#include <stdlib.h>
|
||||
#include "internal.h"
|
||||
#include "xkb_glfw.h"
|
||||
|
||||
static GLFWbool debug_keyboard = GLFW_FALSE;
|
||||
#define debug(...) if (debug_keyboard) printf(__VA_ARGS__);
|
||||
|
||||
#define map_key(key) { \
|
||||
switch(key) { \
|
||||
S(space, SPACE); \
|
||||
S(apostrophe, APOSTROPHE); \
|
||||
S(comma, COMMA); \
|
||||
S(minus, MINUS); \
|
||||
S(period, PERIOD); \
|
||||
S(slash, SLASH); \
|
||||
S(semicolon, SEMICOLON); \
|
||||
S(equal, EQUAL); \
|
||||
S(bracketleft, LEFT_BRACKET); \
|
||||
S(backslash, BACKSLASH); \
|
||||
S(bracketright, RIGHT_BRACKET); \
|
||||
S(grave, GRAVE_ACCENT); \
|
||||
S(Escape, ESCAPE); \
|
||||
S(Return, ENTER); \
|
||||
S(Tab, TAB); \
|
||||
S(BackSpace, BACKSPACE); \
|
||||
S(Insert, INSERT); \
|
||||
S(Delete, DELETE); \
|
||||
S(Right, RIGHT); \
|
||||
S(Left, LEFT); \
|
||||
S(Up, UP); \
|
||||
S(Down, DOWN); \
|
||||
S(Page_Up, PAGE_UP); \
|
||||
S(Page_Down, PAGE_DOWN); \
|
||||
S(Home, HOME); \
|
||||
S(End, END); \
|
||||
S(Caps_Lock, CAPS_LOCK); \
|
||||
S(Scroll_Lock, SCROLL_LOCK); \
|
||||
S(Num_Lock, NUM_LOCK); \
|
||||
S(Print, PRINT_SCREEN); \
|
||||
S(Pause, PAUSE); \
|
||||
S(KP_Decimal, KP_DECIMAL); \
|
||||
S(KP_Divide, KP_DIVIDE); \
|
||||
S(KP_Multiply, KP_MULTIPLY); \
|
||||
S(KP_Subtract, KP_SUBTRACT); \
|
||||
S(KP_Add, KP_ADD); \
|
||||
S(KP_Enter, KP_ENTER); \
|
||||
S(KP_Equal, KP_EQUAL); \
|
||||
F(KP_Home, HOME); \
|
||||
F(KP_End, END); \
|
||||
F(KP_Page_Up, PAGE_UP); \
|
||||
F(KP_Page_Down, PAGE_DOWN); \
|
||||
F(KP_Insert, INSERT); \
|
||||
F(KP_Delete, DELETE); \
|
||||
S(Shift_L, LEFT_SHIFT); \
|
||||
S(Control_L, LEFT_CONTROL); \
|
||||
S(Alt_L, LEFT_ALT); \
|
||||
S(Super_L, LEFT_SUPER); \
|
||||
S(Shift_R, RIGHT_SHIFT); \
|
||||
S(Control_R, RIGHT_CONTROL); \
|
||||
S(Alt_R, RIGHT_ALT); \
|
||||
S(Super_R, RIGHT_SUPER); \
|
||||
S(Menu, MENU); \
|
||||
R(0, 9, 0, 9); \
|
||||
R(a, z, A, Z); \
|
||||
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) {
|
||||
#define S(f, t) case XKB_KEY_##f: return GLFW_KEY_##t
|
||||
#define F(f, t) S(f, t)
|
||||
#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)
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
#undef F
|
||||
#undef D
|
||||
#undef R
|
||||
#undef S
|
||||
};
|
||||
|
||||
xkb_keysym_t
|
||||
glfw_xkb_sym_for_key(int key) {
|
||||
#define S(f, t) case GLFW_KEY_##t: return XKB_KEY_##f
|
||||
#define F(...)
|
||||
#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)
|
||||
return GLFW_KEY_UNKNOWN;
|
||||
#undef F
|
||||
#undef D
|
||||
#undef R
|
||||
#undef S
|
||||
}
|
||||
|
||||
#ifdef _GLFW_X11
|
||||
|
||||
GLFWbool
|
||||
glfw_xkb_set_x11_events_mask(void) {
|
||||
if (!XkbSelectEvents(_glfw.x11.display, XkbUseCoreKbd, XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask, XkbNewKeyboardNotifyMask | XkbMapNotifyMask | XkbStateNotifyMask)) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Failed to set XKB events mask");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb) {
|
||||
xkb->keyboard_device_id = -1;
|
||||
xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display);
|
||||
if (!conn) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve XCB connection");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
xkb->keyboard_device_id = xkb_x11_get_core_keyboard_device_id(conn);
|
||||
if (xkb->keyboard_device_id == -1) {
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "X11: Failed to retrieve core keyboard device id");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
#define xkb_glfw_load_keymap(keymap, ...) {\
|
||||
xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display); \
|
||||
if (conn) keymap = xkb_x11_keymap_new_from_device(xkb->context, conn, xkb->keyboard_device_id, XKB_KEYMAP_COMPILE_NO_FLAGS); \
|
||||
}
|
||||
|
||||
#define xkb_glfw_load_state(keymap, state, ...) {\
|
||||
xcb_connection_t* conn = XGetXCBConnection(_glfw.x11.display); \
|
||||
if (conn) state = xkb_x11_state_new_from_device(keymap, conn, xkb->keyboard_device_id); \
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define xkb_glfw_load_keymap(keymap, map_str) keymap = xkb_keymap_new_from_string(xkb->context, map_str, XKB_KEYMAP_FORMAT_TEXT_V1, 0);
|
||||
#define xkb_glfw_load_state(keymap, state, ...) state = xkb_state_new(keymap);
|
||||
|
||||
#endif
|
||||
|
||||
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->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->context) {
|
||||
xkb_context_unref(xkb->context);
|
||||
xkb->context = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_xkb_create_context(_GLFWXKBData *xkb) {
|
||||
xkb->context = xkb_context_new(0);
|
||||
debug_keyboard = getenv("GLFW_DEBUG_KEYBOARD") != NULL;
|
||||
if (!xkb->context)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Failed to initialize XKB context");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (xkb->keymap) {
|
||||
#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);
|
||||
#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;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static inline xkb_mod_mask_t
|
||||
active_unknown_modifiers(_GLFWXKBData *xkb) {
|
||||
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]);
|
||||
i++;
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
GLFWbool
|
||||
glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode) {
|
||||
#ifdef _GLFW_WAYLAND
|
||||
scancode += 8;
|
||||
#endif
|
||||
return xkb_keymap_key_repeats(xkb->keymap, scancode);
|
||||
}
|
||||
|
||||
|
||||
static char text[256];
|
||||
|
||||
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)) {
|
||||
case XKB_COMPOSE_COMPOSED:
|
||||
xkb_compose_state_get_utf8(xkb->composeState, text, sizeof(text));
|
||||
return xkb_compose_state_get_one_sym(xkb->composeState);
|
||||
case XKB_COMPOSE_COMPOSING:
|
||||
case XKB_COMPOSE_CANCELLED:
|
||||
return XKB_KEY_NoSymbol;
|
||||
case XKB_COMPOSE_NOTHING:
|
||||
default:
|
||||
return sym;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
glfw_xkb_keysym_name(xkb_keysym_t sym) {
|
||||
static char name[256];
|
||||
name[0] = 0;
|
||||
xkb_keysym_get_name(sym, name, sizeof(name));
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
static inline const char*
|
||||
format_mods(unsigned int mods) {
|
||||
static char buf[128];
|
||||
char *p = buf, *s;
|
||||
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
|
||||
pr("mods: ");
|
||||
s = p;
|
||||
if (mods & GLFW_MOD_CONTROL) pr("ctrl+");
|
||||
if (mods & GLFW_MOD_ALT) pr("alt+");
|
||||
if (mods & GLFW_MOD_SHIFT) pr("shift+");
|
||||
if (mods & GLFW_MOD_SUPER) pr("super+");
|
||||
if (mods & GLFW_MOD_CAPS_LOCK) pr("capslock+");
|
||||
if (mods & GLFW_MOD_NUM_LOCK) pr("numlock+");
|
||||
if (p == s) pr("none");
|
||||
else p--;
|
||||
pr(" ");
|
||||
#undef pr
|
||||
return buf;
|
||||
}
|
||||
|
||||
static inline const char*
|
||||
format_xkb_mods(_GLFWXKBData *xkb, const char* name, xkb_mod_mask_t mods) {
|
||||
static char buf[512];
|
||||
char *p = buf, *s;
|
||||
#define pr(x) p += snprintf(p, sizeof(buf) - (p - buf) - 1, "%s", x)
|
||||
pr(name); pr(": ");
|
||||
s = p;
|
||||
for (xkb_mod_index_t i = 0; i < xkb_keymap_num_mods(xkb->keymap); i++) {
|
||||
xkb_mod_mask_t m = 1 << i;
|
||||
if (m & mods) { pr(xkb_keymap_mod_get_name(xkb->keymap, i)); pr("+"); }
|
||||
}
|
||||
if (p == s) pr("none");
|
||||
else p--;
|
||||
pr(" ");
|
||||
#undef pr
|
||||
return buf;
|
||||
}
|
||||
|
||||
void
|
||||
glfw_xkb_handle_key_event(_GLFWwindow *window, _GLFWXKBData *xkb, xkb_keycode_t scancode, int action) {
|
||||
const xkb_keysym_t *syms, *clean_syms;
|
||||
xkb_keysym_t glfw_sym;
|
||||
xkb_keycode_t code_for_sym = scancode;
|
||||
#ifdef _GLFW_WAYLAND
|
||||
code_for_sym += 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;
|
||||
// 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);
|
||||
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) {
|
||||
debug("compose not complete, ignoring.\n");
|
||||
return;
|
||||
}
|
||||
debug("composed_sym: %s ", glfw_xkb_keysym_name(glfw_sym));
|
||||
if (glfw_sym == syms[0]) { // composed sym is the same as non-composed sym
|
||||
// Only use the clean_sym if no mods other than the mods we report
|
||||
// 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));
|
||||
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));
|
||||
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); }
|
||||
}
|
||||
int glfw_keycode = glfw_key_for_sym(glfw_sym);
|
||||
debug("%sglfw_key: %s\n", format_mods(xkb->modifiers), _glfwGetKeyName(glfw_keycode));
|
||||
_glfwInputKeyboard(window, glfw_keycode, glfw_sym, action, xkb->modifiers, text, 0);
|
||||
}
|
||||
83
glfw/xkb_glfw.h
vendored
Normal file
83
glfw/xkb_glfw.h
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
//========================================================================
|
||||
// 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 <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-compose.h>
|
||||
#ifdef _GLFW_X11
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
struct xkb_context* context;
|
||||
struct xkb_keymap* keymap;
|
||||
struct xkb_state* state;
|
||||
struct xkb_state* clean_state;
|
||||
struct xkb_compose_state* composeState;
|
||||
|
||||
xkb_mod_index_t controlIdx;
|
||||
xkb_mod_index_t altIdx;
|
||||
xkb_mod_index_t shiftIdx;
|
||||
xkb_mod_index_t superIdx;
|
||||
xkb_mod_index_t capsLockIdx;
|
||||
xkb_mod_index_t numLockIdx;
|
||||
xkb_mod_mask_t controlMask;
|
||||
xkb_mod_mask_t altMask;
|
||||
xkb_mod_mask_t shiftMask;
|
||||
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];
|
||||
|
||||
#ifdef _GLFW_X11
|
||||
int32_t keyboard_device_id;
|
||||
GLFWbool available;
|
||||
GLFWbool detectable;
|
||||
int majorOpcode;
|
||||
int eventBase;
|
||||
int errorBase;
|
||||
int major;
|
||||
int minor;
|
||||
#endif
|
||||
|
||||
} _GLFWXKBData;
|
||||
|
||||
#ifdef _GLFW_X11
|
||||
GLFWbool glfw_xkb_set_x11_events_mask(void);
|
||||
GLFWbool glfw_xkb_update_x11_keyboard_id(_GLFWXKBData *xkb);
|
||||
#endif
|
||||
|
||||
void glfw_xkb_release(_GLFWXKBData *xkb);
|
||||
GLFWbool glfw_xkb_create_context(_GLFWXKBData *xkb);
|
||||
GLFWbool glfw_xkb_compile_keymap(_GLFWXKBData *xkb, const char *map_str);
|
||||
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);
|
||||
GLFWbool glfw_xkb_should_repeat(_GLFWXKBData *xkb, xkb_keycode_t scancode);
|
||||
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);
|
||||
940
glfw/xkb_unicode.c
vendored
940
glfw/xkb_unicode.c
vendored
@@ -1,940 +0,0 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 X11 - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2002-2006 Marcus Geelnard
|
||||
// Copyright (c) 2006-2016 Camilla Löwy <elmindreda@glfw.org>
|
||||
//
|
||||
// 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"
|
||||
|
||||
|
||||
/*
|
||||
* Marcus: This code was originally written by Markus G. Kuhn.
|
||||
* I have made some slight changes (trimmed it down a bit from >60 KB to
|
||||
* 20 KB), but the functionality is the same.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This module converts keysym values into the corresponding ISO 10646
|
||||
* (UCS, Unicode) values.
|
||||
*
|
||||
* The array keysymtab[] contains pairs of X11 keysym values for graphical
|
||||
* characters and the corresponding Unicode value. The function
|
||||
* _glfwKeySym2Unicode() maps a keysym onto a Unicode value using a binary
|
||||
* search, therefore keysymtab[] must remain SORTED by keysym value.
|
||||
*
|
||||
* We allow to represent any UCS character in the range U-00000000 to
|
||||
* U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff.
|
||||
* This admittedly does not cover the entire 31-bit space of UCS, but
|
||||
* it does cover all of the characters up to U-10FFFF, which can be
|
||||
* represented by UTF-16, and more, and it is very unlikely that higher
|
||||
* UCS codes will ever be assigned by ISO. So to get Unicode character
|
||||
* U+ABCD you can directly use keysym 0x0100abcd.
|
||||
*
|
||||
* Original author: Markus G. Kuhn <mkuhn@acm.org>, University of
|
||||
* Cambridge, April 2001
|
||||
*
|
||||
* Special thanks to Richard Verhoeven <river@win.tue.nl> for preparing
|
||||
* an initial draft of the mapping table.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//************************************************************************
|
||||
//**** KeySym to Unicode mapping table ****
|
||||
//************************************************************************
|
||||
|
||||
static const struct codepair {
|
||||
unsigned short keysym;
|
||||
unsigned short ucs;
|
||||
} keysymtab[] = {
|
||||
{ 0x01a1, 0x0104 },
|
||||
{ 0x01a2, 0x02d8 },
|
||||
{ 0x01a3, 0x0141 },
|
||||
{ 0x01a5, 0x013d },
|
||||
{ 0x01a6, 0x015a },
|
||||
{ 0x01a9, 0x0160 },
|
||||
{ 0x01aa, 0x015e },
|
||||
{ 0x01ab, 0x0164 },
|
||||
{ 0x01ac, 0x0179 },
|
||||
{ 0x01ae, 0x017d },
|
||||
{ 0x01af, 0x017b },
|
||||
{ 0x01b1, 0x0105 },
|
||||
{ 0x01b2, 0x02db },
|
||||
{ 0x01b3, 0x0142 },
|
||||
{ 0x01b5, 0x013e },
|
||||
{ 0x01b6, 0x015b },
|
||||
{ 0x01b7, 0x02c7 },
|
||||
{ 0x01b9, 0x0161 },
|
||||
{ 0x01ba, 0x015f },
|
||||
{ 0x01bb, 0x0165 },
|
||||
{ 0x01bc, 0x017a },
|
||||
{ 0x01bd, 0x02dd },
|
||||
{ 0x01be, 0x017e },
|
||||
{ 0x01bf, 0x017c },
|
||||
{ 0x01c0, 0x0154 },
|
||||
{ 0x01c3, 0x0102 },
|
||||
{ 0x01c5, 0x0139 },
|
||||
{ 0x01c6, 0x0106 },
|
||||
{ 0x01c8, 0x010c },
|
||||
{ 0x01ca, 0x0118 },
|
||||
{ 0x01cc, 0x011a },
|
||||
{ 0x01cf, 0x010e },
|
||||
{ 0x01d0, 0x0110 },
|
||||
{ 0x01d1, 0x0143 },
|
||||
{ 0x01d2, 0x0147 },
|
||||
{ 0x01d5, 0x0150 },
|
||||
{ 0x01d8, 0x0158 },
|
||||
{ 0x01d9, 0x016e },
|
||||
{ 0x01db, 0x0170 },
|
||||
{ 0x01de, 0x0162 },
|
||||
{ 0x01e0, 0x0155 },
|
||||
{ 0x01e3, 0x0103 },
|
||||
{ 0x01e5, 0x013a },
|
||||
{ 0x01e6, 0x0107 },
|
||||
{ 0x01e8, 0x010d },
|
||||
{ 0x01ea, 0x0119 },
|
||||
{ 0x01ec, 0x011b },
|
||||
{ 0x01ef, 0x010f },
|
||||
{ 0x01f0, 0x0111 },
|
||||
{ 0x01f1, 0x0144 },
|
||||
{ 0x01f2, 0x0148 },
|
||||
{ 0x01f5, 0x0151 },
|
||||
{ 0x01f8, 0x0159 },
|
||||
{ 0x01f9, 0x016f },
|
||||
{ 0x01fb, 0x0171 },
|
||||
{ 0x01fe, 0x0163 },
|
||||
{ 0x01ff, 0x02d9 },
|
||||
{ 0x02a1, 0x0126 },
|
||||
{ 0x02a6, 0x0124 },
|
||||
{ 0x02a9, 0x0130 },
|
||||
{ 0x02ab, 0x011e },
|
||||
{ 0x02ac, 0x0134 },
|
||||
{ 0x02b1, 0x0127 },
|
||||
{ 0x02b6, 0x0125 },
|
||||
{ 0x02b9, 0x0131 },
|
||||
{ 0x02bb, 0x011f },
|
||||
{ 0x02bc, 0x0135 },
|
||||
{ 0x02c5, 0x010a },
|
||||
{ 0x02c6, 0x0108 },
|
||||
{ 0x02d5, 0x0120 },
|
||||
{ 0x02d8, 0x011c },
|
||||
{ 0x02dd, 0x016c },
|
||||
{ 0x02de, 0x015c },
|
||||
{ 0x02e5, 0x010b },
|
||||
{ 0x02e6, 0x0109 },
|
||||
{ 0x02f5, 0x0121 },
|
||||
{ 0x02f8, 0x011d },
|
||||
{ 0x02fd, 0x016d },
|
||||
{ 0x02fe, 0x015d },
|
||||
{ 0x03a2, 0x0138 },
|
||||
{ 0x03a3, 0x0156 },
|
||||
{ 0x03a5, 0x0128 },
|
||||
{ 0x03a6, 0x013b },
|
||||
{ 0x03aa, 0x0112 },
|
||||
{ 0x03ab, 0x0122 },
|
||||
{ 0x03ac, 0x0166 },
|
||||
{ 0x03b3, 0x0157 },
|
||||
{ 0x03b5, 0x0129 },
|
||||
{ 0x03b6, 0x013c },
|
||||
{ 0x03ba, 0x0113 },
|
||||
{ 0x03bb, 0x0123 },
|
||||
{ 0x03bc, 0x0167 },
|
||||
{ 0x03bd, 0x014a },
|
||||
{ 0x03bf, 0x014b },
|
||||
{ 0x03c0, 0x0100 },
|
||||
{ 0x03c7, 0x012e },
|
||||
{ 0x03cc, 0x0116 },
|
||||
{ 0x03cf, 0x012a },
|
||||
{ 0x03d1, 0x0145 },
|
||||
{ 0x03d2, 0x014c },
|
||||
{ 0x03d3, 0x0136 },
|
||||
{ 0x03d9, 0x0172 },
|
||||
{ 0x03dd, 0x0168 },
|
||||
{ 0x03de, 0x016a },
|
||||
{ 0x03e0, 0x0101 },
|
||||
{ 0x03e7, 0x012f },
|
||||
{ 0x03ec, 0x0117 },
|
||||
{ 0x03ef, 0x012b },
|
||||
{ 0x03f1, 0x0146 },
|
||||
{ 0x03f2, 0x014d },
|
||||
{ 0x03f3, 0x0137 },
|
||||
{ 0x03f9, 0x0173 },
|
||||
{ 0x03fd, 0x0169 },
|
||||
{ 0x03fe, 0x016b },
|
||||
{ 0x047e, 0x203e },
|
||||
{ 0x04a1, 0x3002 },
|
||||
{ 0x04a2, 0x300c },
|
||||
{ 0x04a3, 0x300d },
|
||||
{ 0x04a4, 0x3001 },
|
||||
{ 0x04a5, 0x30fb },
|
||||
{ 0x04a6, 0x30f2 },
|
||||
{ 0x04a7, 0x30a1 },
|
||||
{ 0x04a8, 0x30a3 },
|
||||
{ 0x04a9, 0x30a5 },
|
||||
{ 0x04aa, 0x30a7 },
|
||||
{ 0x04ab, 0x30a9 },
|
||||
{ 0x04ac, 0x30e3 },
|
||||
{ 0x04ad, 0x30e5 },
|
||||
{ 0x04ae, 0x30e7 },
|
||||
{ 0x04af, 0x30c3 },
|
||||
{ 0x04b0, 0x30fc },
|
||||
{ 0x04b1, 0x30a2 },
|
||||
{ 0x04b2, 0x30a4 },
|
||||
{ 0x04b3, 0x30a6 },
|
||||
{ 0x04b4, 0x30a8 },
|
||||
{ 0x04b5, 0x30aa },
|
||||
{ 0x04b6, 0x30ab },
|
||||
{ 0x04b7, 0x30ad },
|
||||
{ 0x04b8, 0x30af },
|
||||
{ 0x04b9, 0x30b1 },
|
||||
{ 0x04ba, 0x30b3 },
|
||||
{ 0x04bb, 0x30b5 },
|
||||
{ 0x04bc, 0x30b7 },
|
||||
{ 0x04bd, 0x30b9 },
|
||||
{ 0x04be, 0x30bb },
|
||||
{ 0x04bf, 0x30bd },
|
||||
{ 0x04c0, 0x30bf },
|
||||
{ 0x04c1, 0x30c1 },
|
||||
{ 0x04c2, 0x30c4 },
|
||||
{ 0x04c3, 0x30c6 },
|
||||
{ 0x04c4, 0x30c8 },
|
||||
{ 0x04c5, 0x30ca },
|
||||
{ 0x04c6, 0x30cb },
|
||||
{ 0x04c7, 0x30cc },
|
||||
{ 0x04c8, 0x30cd },
|
||||
{ 0x04c9, 0x30ce },
|
||||
{ 0x04ca, 0x30cf },
|
||||
{ 0x04cb, 0x30d2 },
|
||||
{ 0x04cc, 0x30d5 },
|
||||
{ 0x04cd, 0x30d8 },
|
||||
{ 0x04ce, 0x30db },
|
||||
{ 0x04cf, 0x30de },
|
||||
{ 0x04d0, 0x30df },
|
||||
{ 0x04d1, 0x30e0 },
|
||||
{ 0x04d2, 0x30e1 },
|
||||
{ 0x04d3, 0x30e2 },
|
||||
{ 0x04d4, 0x30e4 },
|
||||
{ 0x04d5, 0x30e6 },
|
||||
{ 0x04d6, 0x30e8 },
|
||||
{ 0x04d7, 0x30e9 },
|
||||
{ 0x04d8, 0x30ea },
|
||||
{ 0x04d9, 0x30eb },
|
||||
{ 0x04da, 0x30ec },
|
||||
{ 0x04db, 0x30ed },
|
||||
{ 0x04dc, 0x30ef },
|
||||
{ 0x04dd, 0x30f3 },
|
||||
{ 0x04de, 0x309b },
|
||||
{ 0x04df, 0x309c },
|
||||
{ 0x05ac, 0x060c },
|
||||
{ 0x05bb, 0x061b },
|
||||
{ 0x05bf, 0x061f },
|
||||
{ 0x05c1, 0x0621 },
|
||||
{ 0x05c2, 0x0622 },
|
||||
{ 0x05c3, 0x0623 },
|
||||
{ 0x05c4, 0x0624 },
|
||||
{ 0x05c5, 0x0625 },
|
||||
{ 0x05c6, 0x0626 },
|
||||
{ 0x05c7, 0x0627 },
|
||||
{ 0x05c8, 0x0628 },
|
||||
{ 0x05c9, 0x0629 },
|
||||
{ 0x05ca, 0x062a },
|
||||
{ 0x05cb, 0x062b },
|
||||
{ 0x05cc, 0x062c },
|
||||
{ 0x05cd, 0x062d },
|
||||
{ 0x05ce, 0x062e },
|
||||
{ 0x05cf, 0x062f },
|
||||
{ 0x05d0, 0x0630 },
|
||||
{ 0x05d1, 0x0631 },
|
||||
{ 0x05d2, 0x0632 },
|
||||
{ 0x05d3, 0x0633 },
|
||||
{ 0x05d4, 0x0634 },
|
||||
{ 0x05d5, 0x0635 },
|
||||
{ 0x05d6, 0x0636 },
|
||||
{ 0x05d7, 0x0637 },
|
||||
{ 0x05d8, 0x0638 },
|
||||
{ 0x05d9, 0x0639 },
|
||||
{ 0x05da, 0x063a },
|
||||
{ 0x05e0, 0x0640 },
|
||||
{ 0x05e1, 0x0641 },
|
||||
{ 0x05e2, 0x0642 },
|
||||
{ 0x05e3, 0x0643 },
|
||||
{ 0x05e4, 0x0644 },
|
||||
{ 0x05e5, 0x0645 },
|
||||
{ 0x05e6, 0x0646 },
|
||||
{ 0x05e7, 0x0647 },
|
||||
{ 0x05e8, 0x0648 },
|
||||
{ 0x05e9, 0x0649 },
|
||||
{ 0x05ea, 0x064a },
|
||||
{ 0x05eb, 0x064b },
|
||||
{ 0x05ec, 0x064c },
|
||||
{ 0x05ed, 0x064d },
|
||||
{ 0x05ee, 0x064e },
|
||||
{ 0x05ef, 0x064f },
|
||||
{ 0x05f0, 0x0650 },
|
||||
{ 0x05f1, 0x0651 },
|
||||
{ 0x05f2, 0x0652 },
|
||||
{ 0x06a1, 0x0452 },
|
||||
{ 0x06a2, 0x0453 },
|
||||
{ 0x06a3, 0x0451 },
|
||||
{ 0x06a4, 0x0454 },
|
||||
{ 0x06a5, 0x0455 },
|
||||
{ 0x06a6, 0x0456 },
|
||||
{ 0x06a7, 0x0457 },
|
||||
{ 0x06a8, 0x0458 },
|
||||
{ 0x06a9, 0x0459 },
|
||||
{ 0x06aa, 0x045a },
|
||||
{ 0x06ab, 0x045b },
|
||||
{ 0x06ac, 0x045c },
|
||||
{ 0x06ae, 0x045e },
|
||||
{ 0x06af, 0x045f },
|
||||
{ 0x06b0, 0x2116 },
|
||||
{ 0x06b1, 0x0402 },
|
||||
{ 0x06b2, 0x0403 },
|
||||
{ 0x06b3, 0x0401 },
|
||||
{ 0x06b4, 0x0404 },
|
||||
{ 0x06b5, 0x0405 },
|
||||
{ 0x06b6, 0x0406 },
|
||||
{ 0x06b7, 0x0407 },
|
||||
{ 0x06b8, 0x0408 },
|
||||
{ 0x06b9, 0x0409 },
|
||||
{ 0x06ba, 0x040a },
|
||||
{ 0x06bb, 0x040b },
|
||||
{ 0x06bc, 0x040c },
|
||||
{ 0x06be, 0x040e },
|
||||
{ 0x06bf, 0x040f },
|
||||
{ 0x06c0, 0x044e },
|
||||
{ 0x06c1, 0x0430 },
|
||||
{ 0x06c2, 0x0431 },
|
||||
{ 0x06c3, 0x0446 },
|
||||
{ 0x06c4, 0x0434 },
|
||||
{ 0x06c5, 0x0435 },
|
||||
{ 0x06c6, 0x0444 },
|
||||
{ 0x06c7, 0x0433 },
|
||||
{ 0x06c8, 0x0445 },
|
||||
{ 0x06c9, 0x0438 },
|
||||
{ 0x06ca, 0x0439 },
|
||||
{ 0x06cb, 0x043a },
|
||||
{ 0x06cc, 0x043b },
|
||||
{ 0x06cd, 0x043c },
|
||||
{ 0x06ce, 0x043d },
|
||||
{ 0x06cf, 0x043e },
|
||||
{ 0x06d0, 0x043f },
|
||||
{ 0x06d1, 0x044f },
|
||||
{ 0x06d2, 0x0440 },
|
||||
{ 0x06d3, 0x0441 },
|
||||
{ 0x06d4, 0x0442 },
|
||||
{ 0x06d5, 0x0443 },
|
||||
{ 0x06d6, 0x0436 },
|
||||
{ 0x06d7, 0x0432 },
|
||||
{ 0x06d8, 0x044c },
|
||||
{ 0x06d9, 0x044b },
|
||||
{ 0x06da, 0x0437 },
|
||||
{ 0x06db, 0x0448 },
|
||||
{ 0x06dc, 0x044d },
|
||||
{ 0x06dd, 0x0449 },
|
||||
{ 0x06de, 0x0447 },
|
||||
{ 0x06df, 0x044a },
|
||||
{ 0x06e0, 0x042e },
|
||||
{ 0x06e1, 0x0410 },
|
||||
{ 0x06e2, 0x0411 },
|
||||
{ 0x06e3, 0x0426 },
|
||||
{ 0x06e4, 0x0414 },
|
||||
{ 0x06e5, 0x0415 },
|
||||
{ 0x06e6, 0x0424 },
|
||||
{ 0x06e7, 0x0413 },
|
||||
{ 0x06e8, 0x0425 },
|
||||
{ 0x06e9, 0x0418 },
|
||||
{ 0x06ea, 0x0419 },
|
||||
{ 0x06eb, 0x041a },
|
||||
{ 0x06ec, 0x041b },
|
||||
{ 0x06ed, 0x041c },
|
||||
{ 0x06ee, 0x041d },
|
||||
{ 0x06ef, 0x041e },
|
||||
{ 0x06f0, 0x041f },
|
||||
{ 0x06f1, 0x042f },
|
||||
{ 0x06f2, 0x0420 },
|
||||
{ 0x06f3, 0x0421 },
|
||||
{ 0x06f4, 0x0422 },
|
||||
{ 0x06f5, 0x0423 },
|
||||
{ 0x06f6, 0x0416 },
|
||||
{ 0x06f7, 0x0412 },
|
||||
{ 0x06f8, 0x042c },
|
||||
{ 0x06f9, 0x042b },
|
||||
{ 0x06fa, 0x0417 },
|
||||
{ 0x06fb, 0x0428 },
|
||||
{ 0x06fc, 0x042d },
|
||||
{ 0x06fd, 0x0429 },
|
||||
{ 0x06fe, 0x0427 },
|
||||
{ 0x06ff, 0x042a },
|
||||
{ 0x07a1, 0x0386 },
|
||||
{ 0x07a2, 0x0388 },
|
||||
{ 0x07a3, 0x0389 },
|
||||
{ 0x07a4, 0x038a },
|
||||
{ 0x07a5, 0x03aa },
|
||||
{ 0x07a7, 0x038c },
|
||||
{ 0x07a8, 0x038e },
|
||||
{ 0x07a9, 0x03ab },
|
||||
{ 0x07ab, 0x038f },
|
||||
{ 0x07ae, 0x0385 },
|
||||
{ 0x07af, 0x2015 },
|
||||
{ 0x07b1, 0x03ac },
|
||||
{ 0x07b2, 0x03ad },
|
||||
{ 0x07b3, 0x03ae },
|
||||
{ 0x07b4, 0x03af },
|
||||
{ 0x07b5, 0x03ca },
|
||||
{ 0x07b6, 0x0390 },
|
||||
{ 0x07b7, 0x03cc },
|
||||
{ 0x07b8, 0x03cd },
|
||||
{ 0x07b9, 0x03cb },
|
||||
{ 0x07ba, 0x03b0 },
|
||||
{ 0x07bb, 0x03ce },
|
||||
{ 0x07c1, 0x0391 },
|
||||
{ 0x07c2, 0x0392 },
|
||||
{ 0x07c3, 0x0393 },
|
||||
{ 0x07c4, 0x0394 },
|
||||
{ 0x07c5, 0x0395 },
|
||||
{ 0x07c6, 0x0396 },
|
||||
{ 0x07c7, 0x0397 },
|
||||
{ 0x07c8, 0x0398 },
|
||||
{ 0x07c9, 0x0399 },
|
||||
{ 0x07ca, 0x039a },
|
||||
{ 0x07cb, 0x039b },
|
||||
{ 0x07cc, 0x039c },
|
||||
{ 0x07cd, 0x039d },
|
||||
{ 0x07ce, 0x039e },
|
||||
{ 0x07cf, 0x039f },
|
||||
{ 0x07d0, 0x03a0 },
|
||||
{ 0x07d1, 0x03a1 },
|
||||
{ 0x07d2, 0x03a3 },
|
||||
{ 0x07d4, 0x03a4 },
|
||||
{ 0x07d5, 0x03a5 },
|
||||
{ 0x07d6, 0x03a6 },
|
||||
{ 0x07d7, 0x03a7 },
|
||||
{ 0x07d8, 0x03a8 },
|
||||
{ 0x07d9, 0x03a9 },
|
||||
{ 0x07e1, 0x03b1 },
|
||||
{ 0x07e2, 0x03b2 },
|
||||
{ 0x07e3, 0x03b3 },
|
||||
{ 0x07e4, 0x03b4 },
|
||||
{ 0x07e5, 0x03b5 },
|
||||
{ 0x07e6, 0x03b6 },
|
||||
{ 0x07e7, 0x03b7 },
|
||||
{ 0x07e8, 0x03b8 },
|
||||
{ 0x07e9, 0x03b9 },
|
||||
{ 0x07ea, 0x03ba },
|
||||
{ 0x07eb, 0x03bb },
|
||||
{ 0x07ec, 0x03bc },
|
||||
{ 0x07ed, 0x03bd },
|
||||
{ 0x07ee, 0x03be },
|
||||
{ 0x07ef, 0x03bf },
|
||||
{ 0x07f0, 0x03c0 },
|
||||
{ 0x07f1, 0x03c1 },
|
||||
{ 0x07f2, 0x03c3 },
|
||||
{ 0x07f3, 0x03c2 },
|
||||
{ 0x07f4, 0x03c4 },
|
||||
{ 0x07f5, 0x03c5 },
|
||||
{ 0x07f6, 0x03c6 },
|
||||
{ 0x07f7, 0x03c7 },
|
||||
{ 0x07f8, 0x03c8 },
|
||||
{ 0x07f9, 0x03c9 },
|
||||
{ 0x08a1, 0x23b7 },
|
||||
{ 0x08a2, 0x250c },
|
||||
{ 0x08a3, 0x2500 },
|
||||
{ 0x08a4, 0x2320 },
|
||||
{ 0x08a5, 0x2321 },
|
||||
{ 0x08a6, 0x2502 },
|
||||
{ 0x08a7, 0x23a1 },
|
||||
{ 0x08a8, 0x23a3 },
|
||||
{ 0x08a9, 0x23a4 },
|
||||
{ 0x08aa, 0x23a6 },
|
||||
{ 0x08ab, 0x239b },
|
||||
{ 0x08ac, 0x239d },
|
||||
{ 0x08ad, 0x239e },
|
||||
{ 0x08ae, 0x23a0 },
|
||||
{ 0x08af, 0x23a8 },
|
||||
{ 0x08b0, 0x23ac },
|
||||
{ 0x08bc, 0x2264 },
|
||||
{ 0x08bd, 0x2260 },
|
||||
{ 0x08be, 0x2265 },
|
||||
{ 0x08bf, 0x222b },
|
||||
{ 0x08c0, 0x2234 },
|
||||
{ 0x08c1, 0x221d },
|
||||
{ 0x08c2, 0x221e },
|
||||
{ 0x08c5, 0x2207 },
|
||||
{ 0x08c8, 0x223c },
|
||||
{ 0x08c9, 0x2243 },
|
||||
{ 0x08cd, 0x21d4 },
|
||||
{ 0x08ce, 0x21d2 },
|
||||
{ 0x08cf, 0x2261 },
|
||||
{ 0x08d6, 0x221a },
|
||||
{ 0x08da, 0x2282 },
|
||||
{ 0x08db, 0x2283 },
|
||||
{ 0x08dc, 0x2229 },
|
||||
{ 0x08dd, 0x222a },
|
||||
{ 0x08de, 0x2227 },
|
||||
{ 0x08df, 0x2228 },
|
||||
{ 0x08ef, 0x2202 },
|
||||
{ 0x08f6, 0x0192 },
|
||||
{ 0x08fb, 0x2190 },
|
||||
{ 0x08fc, 0x2191 },
|
||||
{ 0x08fd, 0x2192 },
|
||||
{ 0x08fe, 0x2193 },
|
||||
{ 0x09e0, 0x25c6 },
|
||||
{ 0x09e1, 0x2592 },
|
||||
{ 0x09e2, 0x2409 },
|
||||
{ 0x09e3, 0x240c },
|
||||
{ 0x09e4, 0x240d },
|
||||
{ 0x09e5, 0x240a },
|
||||
{ 0x09e8, 0x2424 },
|
||||
{ 0x09e9, 0x240b },
|
||||
{ 0x09ea, 0x2518 },
|
||||
{ 0x09eb, 0x2510 },
|
||||
{ 0x09ec, 0x250c },
|
||||
{ 0x09ed, 0x2514 },
|
||||
{ 0x09ee, 0x253c },
|
||||
{ 0x09ef, 0x23ba },
|
||||
{ 0x09f0, 0x23bb },
|
||||
{ 0x09f1, 0x2500 },
|
||||
{ 0x09f2, 0x23bc },
|
||||
{ 0x09f3, 0x23bd },
|
||||
{ 0x09f4, 0x251c },
|
||||
{ 0x09f5, 0x2524 },
|
||||
{ 0x09f6, 0x2534 },
|
||||
{ 0x09f7, 0x252c },
|
||||
{ 0x09f8, 0x2502 },
|
||||
{ 0x0aa1, 0x2003 },
|
||||
{ 0x0aa2, 0x2002 },
|
||||
{ 0x0aa3, 0x2004 },
|
||||
{ 0x0aa4, 0x2005 },
|
||||
{ 0x0aa5, 0x2007 },
|
||||
{ 0x0aa6, 0x2008 },
|
||||
{ 0x0aa7, 0x2009 },
|
||||
{ 0x0aa8, 0x200a },
|
||||
{ 0x0aa9, 0x2014 },
|
||||
{ 0x0aaa, 0x2013 },
|
||||
{ 0x0aae, 0x2026 },
|
||||
{ 0x0aaf, 0x2025 },
|
||||
{ 0x0ab0, 0x2153 },
|
||||
{ 0x0ab1, 0x2154 },
|
||||
{ 0x0ab2, 0x2155 },
|
||||
{ 0x0ab3, 0x2156 },
|
||||
{ 0x0ab4, 0x2157 },
|
||||
{ 0x0ab5, 0x2158 },
|
||||
{ 0x0ab6, 0x2159 },
|
||||
{ 0x0ab7, 0x215a },
|
||||
{ 0x0ab8, 0x2105 },
|
||||
{ 0x0abb, 0x2012 },
|
||||
{ 0x0abc, 0x2329 },
|
||||
{ 0x0abe, 0x232a },
|
||||
{ 0x0ac3, 0x215b },
|
||||
{ 0x0ac4, 0x215c },
|
||||
{ 0x0ac5, 0x215d },
|
||||
{ 0x0ac6, 0x215e },
|
||||
{ 0x0ac9, 0x2122 },
|
||||
{ 0x0aca, 0x2613 },
|
||||
{ 0x0acc, 0x25c1 },
|
||||
{ 0x0acd, 0x25b7 },
|
||||
{ 0x0ace, 0x25cb },
|
||||
{ 0x0acf, 0x25af },
|
||||
{ 0x0ad0, 0x2018 },
|
||||
{ 0x0ad1, 0x2019 },
|
||||
{ 0x0ad2, 0x201c },
|
||||
{ 0x0ad3, 0x201d },
|
||||
{ 0x0ad4, 0x211e },
|
||||
{ 0x0ad6, 0x2032 },
|
||||
{ 0x0ad7, 0x2033 },
|
||||
{ 0x0ad9, 0x271d },
|
||||
{ 0x0adb, 0x25ac },
|
||||
{ 0x0adc, 0x25c0 },
|
||||
{ 0x0add, 0x25b6 },
|
||||
{ 0x0ade, 0x25cf },
|
||||
{ 0x0adf, 0x25ae },
|
||||
{ 0x0ae0, 0x25e6 },
|
||||
{ 0x0ae1, 0x25ab },
|
||||
{ 0x0ae2, 0x25ad },
|
||||
{ 0x0ae3, 0x25b3 },
|
||||
{ 0x0ae4, 0x25bd },
|
||||
{ 0x0ae5, 0x2606 },
|
||||
{ 0x0ae6, 0x2022 },
|
||||
{ 0x0ae7, 0x25aa },
|
||||
{ 0x0ae8, 0x25b2 },
|
||||
{ 0x0ae9, 0x25bc },
|
||||
{ 0x0aea, 0x261c },
|
||||
{ 0x0aeb, 0x261e },
|
||||
{ 0x0aec, 0x2663 },
|
||||
{ 0x0aed, 0x2666 },
|
||||
{ 0x0aee, 0x2665 },
|
||||
{ 0x0af0, 0x2720 },
|
||||
{ 0x0af1, 0x2020 },
|
||||
{ 0x0af2, 0x2021 },
|
||||
{ 0x0af3, 0x2713 },
|
||||
{ 0x0af4, 0x2717 },
|
||||
{ 0x0af5, 0x266f },
|
||||
{ 0x0af6, 0x266d },
|
||||
{ 0x0af7, 0x2642 },
|
||||
{ 0x0af8, 0x2640 },
|
||||
{ 0x0af9, 0x260e },
|
||||
{ 0x0afa, 0x2315 },
|
||||
{ 0x0afb, 0x2117 },
|
||||
{ 0x0afc, 0x2038 },
|
||||
{ 0x0afd, 0x201a },
|
||||
{ 0x0afe, 0x201e },
|
||||
{ 0x0ba3, 0x003c },
|
||||
{ 0x0ba6, 0x003e },
|
||||
{ 0x0ba8, 0x2228 },
|
||||
{ 0x0ba9, 0x2227 },
|
||||
{ 0x0bc0, 0x00af },
|
||||
{ 0x0bc2, 0x22a5 },
|
||||
{ 0x0bc3, 0x2229 },
|
||||
{ 0x0bc4, 0x230a },
|
||||
{ 0x0bc6, 0x005f },
|
||||
{ 0x0bca, 0x2218 },
|
||||
{ 0x0bcc, 0x2395 },
|
||||
{ 0x0bce, 0x22a4 },
|
||||
{ 0x0bcf, 0x25cb },
|
||||
{ 0x0bd3, 0x2308 },
|
||||
{ 0x0bd6, 0x222a },
|
||||
{ 0x0bd8, 0x2283 },
|
||||
{ 0x0bda, 0x2282 },
|
||||
{ 0x0bdc, 0x22a2 },
|
||||
{ 0x0bfc, 0x22a3 },
|
||||
{ 0x0cdf, 0x2017 },
|
||||
{ 0x0ce0, 0x05d0 },
|
||||
{ 0x0ce1, 0x05d1 },
|
||||
{ 0x0ce2, 0x05d2 },
|
||||
{ 0x0ce3, 0x05d3 },
|
||||
{ 0x0ce4, 0x05d4 },
|
||||
{ 0x0ce5, 0x05d5 },
|
||||
{ 0x0ce6, 0x05d6 },
|
||||
{ 0x0ce7, 0x05d7 },
|
||||
{ 0x0ce8, 0x05d8 },
|
||||
{ 0x0ce9, 0x05d9 },
|
||||
{ 0x0cea, 0x05da },
|
||||
{ 0x0ceb, 0x05db },
|
||||
{ 0x0cec, 0x05dc },
|
||||
{ 0x0ced, 0x05dd },
|
||||
{ 0x0cee, 0x05de },
|
||||
{ 0x0cef, 0x05df },
|
||||
{ 0x0cf0, 0x05e0 },
|
||||
{ 0x0cf1, 0x05e1 },
|
||||
{ 0x0cf2, 0x05e2 },
|
||||
{ 0x0cf3, 0x05e3 },
|
||||
{ 0x0cf4, 0x05e4 },
|
||||
{ 0x0cf5, 0x05e5 },
|
||||
{ 0x0cf6, 0x05e6 },
|
||||
{ 0x0cf7, 0x05e7 },
|
||||
{ 0x0cf8, 0x05e8 },
|
||||
{ 0x0cf9, 0x05e9 },
|
||||
{ 0x0cfa, 0x05ea },
|
||||
{ 0x0da1, 0x0e01 },
|
||||
{ 0x0da2, 0x0e02 },
|
||||
{ 0x0da3, 0x0e03 },
|
||||
{ 0x0da4, 0x0e04 },
|
||||
{ 0x0da5, 0x0e05 },
|
||||
{ 0x0da6, 0x0e06 },
|
||||
{ 0x0da7, 0x0e07 },
|
||||
{ 0x0da8, 0x0e08 },
|
||||
{ 0x0da9, 0x0e09 },
|
||||
{ 0x0daa, 0x0e0a },
|
||||
{ 0x0dab, 0x0e0b },
|
||||
{ 0x0dac, 0x0e0c },
|
||||
{ 0x0dad, 0x0e0d },
|
||||
{ 0x0dae, 0x0e0e },
|
||||
{ 0x0daf, 0x0e0f },
|
||||
{ 0x0db0, 0x0e10 },
|
||||
{ 0x0db1, 0x0e11 },
|
||||
{ 0x0db2, 0x0e12 },
|
||||
{ 0x0db3, 0x0e13 },
|
||||
{ 0x0db4, 0x0e14 },
|
||||
{ 0x0db5, 0x0e15 },
|
||||
{ 0x0db6, 0x0e16 },
|
||||
{ 0x0db7, 0x0e17 },
|
||||
{ 0x0db8, 0x0e18 },
|
||||
{ 0x0db9, 0x0e19 },
|
||||
{ 0x0dba, 0x0e1a },
|
||||
{ 0x0dbb, 0x0e1b },
|
||||
{ 0x0dbc, 0x0e1c },
|
||||
{ 0x0dbd, 0x0e1d },
|
||||
{ 0x0dbe, 0x0e1e },
|
||||
{ 0x0dbf, 0x0e1f },
|
||||
{ 0x0dc0, 0x0e20 },
|
||||
{ 0x0dc1, 0x0e21 },
|
||||
{ 0x0dc2, 0x0e22 },
|
||||
{ 0x0dc3, 0x0e23 },
|
||||
{ 0x0dc4, 0x0e24 },
|
||||
{ 0x0dc5, 0x0e25 },
|
||||
{ 0x0dc6, 0x0e26 },
|
||||
{ 0x0dc7, 0x0e27 },
|
||||
{ 0x0dc8, 0x0e28 },
|
||||
{ 0x0dc9, 0x0e29 },
|
||||
{ 0x0dca, 0x0e2a },
|
||||
{ 0x0dcb, 0x0e2b },
|
||||
{ 0x0dcc, 0x0e2c },
|
||||
{ 0x0dcd, 0x0e2d },
|
||||
{ 0x0dce, 0x0e2e },
|
||||
{ 0x0dcf, 0x0e2f },
|
||||
{ 0x0dd0, 0x0e30 },
|
||||
{ 0x0dd1, 0x0e31 },
|
||||
{ 0x0dd2, 0x0e32 },
|
||||
{ 0x0dd3, 0x0e33 },
|
||||
{ 0x0dd4, 0x0e34 },
|
||||
{ 0x0dd5, 0x0e35 },
|
||||
{ 0x0dd6, 0x0e36 },
|
||||
{ 0x0dd7, 0x0e37 },
|
||||
{ 0x0dd8, 0x0e38 },
|
||||
{ 0x0dd9, 0x0e39 },
|
||||
{ 0x0dda, 0x0e3a },
|
||||
{ 0x0ddf, 0x0e3f },
|
||||
{ 0x0de0, 0x0e40 },
|
||||
{ 0x0de1, 0x0e41 },
|
||||
{ 0x0de2, 0x0e42 },
|
||||
{ 0x0de3, 0x0e43 },
|
||||
{ 0x0de4, 0x0e44 },
|
||||
{ 0x0de5, 0x0e45 },
|
||||
{ 0x0de6, 0x0e46 },
|
||||
{ 0x0de7, 0x0e47 },
|
||||
{ 0x0de8, 0x0e48 },
|
||||
{ 0x0de9, 0x0e49 },
|
||||
{ 0x0dea, 0x0e4a },
|
||||
{ 0x0deb, 0x0e4b },
|
||||
{ 0x0dec, 0x0e4c },
|
||||
{ 0x0ded, 0x0e4d },
|
||||
{ 0x0df0, 0x0e50 },
|
||||
{ 0x0df1, 0x0e51 },
|
||||
{ 0x0df2, 0x0e52 },
|
||||
{ 0x0df3, 0x0e53 },
|
||||
{ 0x0df4, 0x0e54 },
|
||||
{ 0x0df5, 0x0e55 },
|
||||
{ 0x0df6, 0x0e56 },
|
||||
{ 0x0df7, 0x0e57 },
|
||||
{ 0x0df8, 0x0e58 },
|
||||
{ 0x0df9, 0x0e59 },
|
||||
{ 0x0ea1, 0x3131 },
|
||||
{ 0x0ea2, 0x3132 },
|
||||
{ 0x0ea3, 0x3133 },
|
||||
{ 0x0ea4, 0x3134 },
|
||||
{ 0x0ea5, 0x3135 },
|
||||
{ 0x0ea6, 0x3136 },
|
||||
{ 0x0ea7, 0x3137 },
|
||||
{ 0x0ea8, 0x3138 },
|
||||
{ 0x0ea9, 0x3139 },
|
||||
{ 0x0eaa, 0x313a },
|
||||
{ 0x0eab, 0x313b },
|
||||
{ 0x0eac, 0x313c },
|
||||
{ 0x0ead, 0x313d },
|
||||
{ 0x0eae, 0x313e },
|
||||
{ 0x0eaf, 0x313f },
|
||||
{ 0x0eb0, 0x3140 },
|
||||
{ 0x0eb1, 0x3141 },
|
||||
{ 0x0eb2, 0x3142 },
|
||||
{ 0x0eb3, 0x3143 },
|
||||
{ 0x0eb4, 0x3144 },
|
||||
{ 0x0eb5, 0x3145 },
|
||||
{ 0x0eb6, 0x3146 },
|
||||
{ 0x0eb7, 0x3147 },
|
||||
{ 0x0eb8, 0x3148 },
|
||||
{ 0x0eb9, 0x3149 },
|
||||
{ 0x0eba, 0x314a },
|
||||
{ 0x0ebb, 0x314b },
|
||||
{ 0x0ebc, 0x314c },
|
||||
{ 0x0ebd, 0x314d },
|
||||
{ 0x0ebe, 0x314e },
|
||||
{ 0x0ebf, 0x314f },
|
||||
{ 0x0ec0, 0x3150 },
|
||||
{ 0x0ec1, 0x3151 },
|
||||
{ 0x0ec2, 0x3152 },
|
||||
{ 0x0ec3, 0x3153 },
|
||||
{ 0x0ec4, 0x3154 },
|
||||
{ 0x0ec5, 0x3155 },
|
||||
{ 0x0ec6, 0x3156 },
|
||||
{ 0x0ec7, 0x3157 },
|
||||
{ 0x0ec8, 0x3158 },
|
||||
{ 0x0ec9, 0x3159 },
|
||||
{ 0x0eca, 0x315a },
|
||||
{ 0x0ecb, 0x315b },
|
||||
{ 0x0ecc, 0x315c },
|
||||
{ 0x0ecd, 0x315d },
|
||||
{ 0x0ece, 0x315e },
|
||||
{ 0x0ecf, 0x315f },
|
||||
{ 0x0ed0, 0x3160 },
|
||||
{ 0x0ed1, 0x3161 },
|
||||
{ 0x0ed2, 0x3162 },
|
||||
{ 0x0ed3, 0x3163 },
|
||||
{ 0x0ed4, 0x11a8 },
|
||||
{ 0x0ed5, 0x11a9 },
|
||||
{ 0x0ed6, 0x11aa },
|
||||
{ 0x0ed7, 0x11ab },
|
||||
{ 0x0ed8, 0x11ac },
|
||||
{ 0x0ed9, 0x11ad },
|
||||
{ 0x0eda, 0x11ae },
|
||||
{ 0x0edb, 0x11af },
|
||||
{ 0x0edc, 0x11b0 },
|
||||
{ 0x0edd, 0x11b1 },
|
||||
{ 0x0ede, 0x11b2 },
|
||||
{ 0x0edf, 0x11b3 },
|
||||
{ 0x0ee0, 0x11b4 },
|
||||
{ 0x0ee1, 0x11b5 },
|
||||
{ 0x0ee2, 0x11b6 },
|
||||
{ 0x0ee3, 0x11b7 },
|
||||
{ 0x0ee4, 0x11b8 },
|
||||
{ 0x0ee5, 0x11b9 },
|
||||
{ 0x0ee6, 0x11ba },
|
||||
{ 0x0ee7, 0x11bb },
|
||||
{ 0x0ee8, 0x11bc },
|
||||
{ 0x0ee9, 0x11bd },
|
||||
{ 0x0eea, 0x11be },
|
||||
{ 0x0eeb, 0x11bf },
|
||||
{ 0x0eec, 0x11c0 },
|
||||
{ 0x0eed, 0x11c1 },
|
||||
{ 0x0eee, 0x11c2 },
|
||||
{ 0x0eef, 0x316d },
|
||||
{ 0x0ef0, 0x3171 },
|
||||
{ 0x0ef1, 0x3178 },
|
||||
{ 0x0ef2, 0x317f },
|
||||
{ 0x0ef3, 0x3181 },
|
||||
{ 0x0ef4, 0x3184 },
|
||||
{ 0x0ef5, 0x3186 },
|
||||
{ 0x0ef6, 0x318d },
|
||||
{ 0x0ef7, 0x318e },
|
||||
{ 0x0ef8, 0x11eb },
|
||||
{ 0x0ef9, 0x11f0 },
|
||||
{ 0x0efa, 0x11f9 },
|
||||
{ 0x0eff, 0x20a9 },
|
||||
{ 0x13a4, 0x20ac },
|
||||
{ 0x13bc, 0x0152 },
|
||||
{ 0x13bd, 0x0153 },
|
||||
{ 0x13be, 0x0178 },
|
||||
{ 0x20ac, 0x20ac },
|
||||
{ 0xfe50, '`' },
|
||||
{ 0xfe51, 0x00b4 },
|
||||
{ 0xfe52, '^' },
|
||||
{ 0xfe53, '~' },
|
||||
{ 0xfe54, 0x00af },
|
||||
{ 0xfe55, 0x02d8 },
|
||||
{ 0xfe56, 0x02d9 },
|
||||
{ 0xfe57, 0x00a8 },
|
||||
{ 0xfe58, 0x02da },
|
||||
{ 0xfe59, 0x02dd },
|
||||
{ 0xfe5a, 0x02c7 },
|
||||
{ 0xfe5b, 0x00b8 },
|
||||
{ 0xfe5c, 0x02db },
|
||||
{ 0xfe5d, 0x037a },
|
||||
{ 0xfe5e, 0x309b },
|
||||
{ 0xfe5f, 0x309c },
|
||||
{ 0xfe63, '/' },
|
||||
{ 0xfe64, 0x02bc },
|
||||
{ 0xfe65, 0x02bd },
|
||||
{ 0xfe66, 0x02f5 },
|
||||
{ 0xfe67, 0x02f3 },
|
||||
{ 0xfe68, 0x02cd },
|
||||
{ 0xfe69, 0xa788 },
|
||||
{ 0xfe6a, 0x02f7 },
|
||||
{ 0xfe6e, ',' },
|
||||
{ 0xfe6f, 0x00a4 },
|
||||
{ 0xfe80, 'a' }, // XK_dead_a
|
||||
{ 0xfe81, 'A' }, // XK_dead_A
|
||||
{ 0xfe82, 'e' }, // XK_dead_e
|
||||
{ 0xfe83, 'E' }, // XK_dead_E
|
||||
{ 0xfe84, 'i' }, // XK_dead_i
|
||||
{ 0xfe85, 'I' }, // XK_dead_I
|
||||
{ 0xfe86, 'o' }, // XK_dead_o
|
||||
{ 0xfe87, 'O' }, // XK_dead_O
|
||||
{ 0xfe88, 'u' }, // XK_dead_u
|
||||
{ 0xfe89, 'U' }, // XK_dead_U
|
||||
{ 0xfe8a, 0x0259 },
|
||||
{ 0xfe8b, 0x018f },
|
||||
{ 0xfe8c, 0x00b5 },
|
||||
{ 0xfe90, '_' },
|
||||
{ 0xfe91, 0x02c8 },
|
||||
{ 0xfe92, 0x02cc },
|
||||
{ 0xff80 /*XKB_KEY_KP_Space*/, ' ' },
|
||||
{ 0xff95 /*XKB_KEY_KP_7*/, 0x0037 },
|
||||
{ 0xff96 /*XKB_KEY_KP_4*/, 0x0034 },
|
||||
{ 0xff97 /*XKB_KEY_KP_8*/, 0x0038 },
|
||||
{ 0xff98 /*XKB_KEY_KP_6*/, 0x0036 },
|
||||
{ 0xff99 /*XKB_KEY_KP_2*/, 0x0032 },
|
||||
{ 0xff9a /*XKB_KEY_KP_9*/, 0x0039 },
|
||||
{ 0xff9b /*XKB_KEY_KP_3*/, 0x0033 },
|
||||
{ 0xff9c /*XKB_KEY_KP_1*/, 0x0031 },
|
||||
{ 0xff9d /*XKB_KEY_KP_5*/, 0x0035 },
|
||||
{ 0xff9e /*XKB_KEY_KP_0*/, 0x0030 },
|
||||
{ 0xffaa /*XKB_KEY_KP_Multiply*/, '*' },
|
||||
{ 0xffab /*XKB_KEY_KP_Add*/, '+' },
|
||||
{ 0xffac /*XKB_KEY_KP_Separator*/, ',' },
|
||||
{ 0xffad /*XKB_KEY_KP_Subtract*/, '-' },
|
||||
{ 0xffae /*XKB_KEY_KP_Decimal*/, '.' },
|
||||
{ 0xffaf /*XKB_KEY_KP_Divide*/, '/' },
|
||||
{ 0xffb0 /*XKB_KEY_KP_0*/, 0x0030 },
|
||||
{ 0xffb1 /*XKB_KEY_KP_1*/, 0x0031 },
|
||||
{ 0xffb2 /*XKB_KEY_KP_2*/, 0x0032 },
|
||||
{ 0xffb3 /*XKB_KEY_KP_3*/, 0x0033 },
|
||||
{ 0xffb4 /*XKB_KEY_KP_4*/, 0x0034 },
|
||||
{ 0xffb5 /*XKB_KEY_KP_5*/, 0x0035 },
|
||||
{ 0xffb6 /*XKB_KEY_KP_6*/, 0x0036 },
|
||||
{ 0xffb7 /*XKB_KEY_KP_7*/, 0x0037 },
|
||||
{ 0xffb8 /*XKB_KEY_KP_8*/, 0x0038 },
|
||||
{ 0xffb9 /*XKB_KEY_KP_9*/, 0x0039 },
|
||||
{ 0xffbd /*XKB_KEY_KP_Equal*/, '=' }
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Convert XKB KeySym to Unicode
|
||||
//
|
||||
long _glfwKeySym2Unicode(unsigned int keysym)
|
||||
{
|
||||
int min = 0;
|
||||
int max = sizeof(keysymtab) / sizeof(struct codepair) - 1;
|
||||
int mid;
|
||||
|
||||
// First check for Latin-1 characters (1:1 mapping)
|
||||
if ((keysym >= 0x0020 && keysym <= 0x007e) ||
|
||||
(keysym >= 0x00a0 && keysym <= 0x00ff))
|
||||
{
|
||||
return keysym;
|
||||
}
|
||||
|
||||
// Also check for directly encoded 24-bit UCS characters
|
||||
if ((keysym & 0xff000000) == 0x01000000)
|
||||
return keysym & 0x00ffffff;
|
||||
|
||||
// Binary search in table
|
||||
while (max >= min)
|
||||
{
|
||||
mid = (min + max) / 2;
|
||||
if (keysymtab[mid].keysym < keysym)
|
||||
min = mid + 1;
|
||||
else if (keysymtab[mid].keysym > keysym)
|
||||
max = mid - 1;
|
||||
else
|
||||
return keysymtab[mid].ucs;
|
||||
}
|
||||
|
||||
// No matching Unicode value found
|
||||
return -1;
|
||||
}
|
||||
|
||||
28
glfw/xkb_unicode.h
vendored
28
glfw/xkb_unicode.h
vendored
@@ -1,28 +0,0 @@
|
||||
//========================================================================
|
||||
// GLFW 3.3 Linux - www.glfw.org
|
||||
//------------------------------------------------------------------------
|
||||
// Copyright (c) 2014 Jonas Ådahl <jadahl@gmail.com>
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
//========================================================================
|
||||
|
||||
long _glfwKeySym2Unicode(unsigned int keysym);
|
||||
|
||||
@@ -24,20 +24,34 @@ kitty icat path/to/some/image.png
|
||||
You can also see a screenshot with more sophisticated features such as alpha-blending and text over graphics
|
||||
link:https://github.com/kovidgoyal/kitty/issues/33#issuecomment-334436100[here].
|
||||
|
||||
Some third-party programs that use the kitty graphics protocol:
|
||||
|
||||
* link:https://github.com/dsanson/termpdf[termdpf] - a terminal PDF/DJVU/CBR viewer
|
||||
* link:https://github.com/ranger/ranger[ranger] - a terminal file manager, with image previews, see this link:https://github.com/ranger/ranger/pull/1077[PR]
|
||||
|
||||
toc::[]
|
||||
|
||||
== Getting the window size
|
||||
|
||||
In order to know what size of images to display and how to position them, the client must be able to get the
|
||||
window size in pixels and the number of cells per row and column. This can be done by using the `TIOCGWINSZ` ioctl.
|
||||
Some C code to demonstrate its use
|
||||
Some code to demonstrate its use
|
||||
|
||||
In C:
|
||||
```C
|
||||
struct ttysize ts;
|
||||
ioctl(0, TIOCGWINSZ, &ts);
|
||||
printf("number of columns: %i, number of rows: %i, screen width: %i, screen height: %i\n", sz.ws_col, sz.ws_row, sz.ws_xpixel, sz.ws_ypixel);
|
||||
```
|
||||
|
||||
In Python:
|
||||
```py
|
||||
import array, fcntl, termios
|
||||
buf = array.array('H', [0, 0, 0, 0])
|
||||
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
|
||||
print('number of columns: {}, number of rows: {}, screen width: {}, screen height: {}'.format(*buf))
|
||||
```
|
||||
|
||||
Note that some terminals return `0` for the width and height values. Such terminals should be modified to return the correct values.
|
||||
Examples of terminals that return correct values: `kitty, xterm`
|
||||
|
||||
|
||||
BIN
hints_mode.png
Normal file
BIN
hints_mode.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
@@ -1,6 +1,7 @@
|
||||
= Key encoding for extended keyboard protocol
|
||||
|
||||
See link:protocol-extensions.asciidoc#keyboard-handling[Keyboard Handling protocol extension]
|
||||
for more information and link:key_encoding.json[for this table in JSON format].
|
||||
|
||||
|===
|
||||
| Name | Encoded representation (base64)
|
||||
|
||||
121
key_encoding.json
Normal file
121
key_encoding.json
Normal file
@@ -0,0 +1,121 @@
|
||||
{
|
||||
"0": "G",
|
||||
"1": "H",
|
||||
"2": "I",
|
||||
"3": "J",
|
||||
"4": "K",
|
||||
"5": "L",
|
||||
"6": "M",
|
||||
"7": "N",
|
||||
"8": "O",
|
||||
"9": "P",
|
||||
"A": "S",
|
||||
"APOSTROPHE": "B",
|
||||
"B": "T",
|
||||
"BACKSLASH": "t",
|
||||
"BACKSPACE": "1",
|
||||
"C": "U",
|
||||
"CAPS LOCK": ":",
|
||||
"COMMA": "C",
|
||||
"D": "V",
|
||||
"DELETE": "3",
|
||||
"DOWN": "6",
|
||||
"E": "W",
|
||||
"END": "-",
|
||||
"ENTER": "z",
|
||||
"EQUAL": "R",
|
||||
"ESCAPE": "y",
|
||||
"F": "X",
|
||||
"F1": "/",
|
||||
"F10": "]",
|
||||
"F11": "{",
|
||||
"F12": "}",
|
||||
"F13": "@",
|
||||
"F14": "%",
|
||||
"F15": "$",
|
||||
"F16": "#",
|
||||
"F17": "BA",
|
||||
"F18": "BB",
|
||||
"F19": "BC",
|
||||
"F2": "*",
|
||||
"F20": "BD",
|
||||
"F21": "BE",
|
||||
"F22": "BF",
|
||||
"F23": "BG",
|
||||
"F24": "BH",
|
||||
"F25": "BI",
|
||||
"F3": "?",
|
||||
"F4": "&",
|
||||
"F5": "<",
|
||||
"F6": ">",
|
||||
"F7": "(",
|
||||
"F8": ")",
|
||||
"F9": "[",
|
||||
"G": "Y",
|
||||
"GRAVE ACCENT": "v",
|
||||
"H": "Z",
|
||||
"HOME": ".",
|
||||
"I": "a",
|
||||
"INSERT": "2",
|
||||
"J": "b",
|
||||
"K": "c",
|
||||
"KP 0": "BJ",
|
||||
"KP 1": "BK",
|
||||
"KP 2": "BL",
|
||||
"KP 3": "BM",
|
||||
"KP 4": "BN",
|
||||
"KP 5": "BO",
|
||||
"KP 6": "BP",
|
||||
"KP 7": "BQ",
|
||||
"KP 8": "BR",
|
||||
"KP 9": "BS",
|
||||
"KP ADD": "BX",
|
||||
"KP DECIMAL": "BT",
|
||||
"KP DIVIDE": "BU",
|
||||
"KP ENTER": "BY",
|
||||
"KP EQUAL": "BZ",
|
||||
"KP MULTIPLY": "BV",
|
||||
"KP SUBTRACT": "BW",
|
||||
"L": "d",
|
||||
"LEFT": "5",
|
||||
"LEFT ALT": "Bc",
|
||||
"LEFT BRACKET": "s",
|
||||
"LEFT CONTROL": "Bb",
|
||||
"LEFT SHIFT": "Ba",
|
||||
"LEFT SUPER": "Bd",
|
||||
"M": "e",
|
||||
"MINUS": "D",
|
||||
"N": "f",
|
||||
"NUM LOCK": "=",
|
||||
"O": "g",
|
||||
"P": "h",
|
||||
"PAGE DOWN": "9",
|
||||
"PAGE UP": "8",
|
||||
"PAUSE": "!",
|
||||
"PERIOD": "E",
|
||||
"PRINT SCREEN": "^",
|
||||
"Q": "i",
|
||||
"R": "j",
|
||||
"RIGHT": "4",
|
||||
"RIGHT ALT": "Bg",
|
||||
"RIGHT BRACKET": "u",
|
||||
"RIGHT CONTROL": "Bf",
|
||||
"RIGHT SHIFT": "Be",
|
||||
"RIGHT SUPER": "Bh",
|
||||
"S": "k",
|
||||
"SCROLL LOCK": "+",
|
||||
"SEMICOLON": "Q",
|
||||
"SLASH": "F",
|
||||
"SPACE": "A",
|
||||
"T": "l",
|
||||
"TAB": "0",
|
||||
"U": "m",
|
||||
"UP": "7",
|
||||
"V": "n",
|
||||
"W": "o",
|
||||
"WORLD 1": "w",
|
||||
"WORLD 2": "x",
|
||||
"X": "p",
|
||||
"Y": "q",
|
||||
"Z": "r"
|
||||
}
|
||||
0
kittens/__init__.py
Normal file
0
kittens/__init__.py
Normal file
0
kittens/ask/__init__.py
Normal file
0
kittens/ask/__init__.py
Normal file
124
kittens/ask/main.py
Normal file
124
kittens/ask/main.py
Normal file
@@ -0,0 +1,124 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.constants import cache_dir
|
||||
|
||||
from ..tui.operations import alternate_screen, styled
|
||||
|
||||
readline = None
|
||||
|
||||
|
||||
def get_history_items():
|
||||
return list(map(readline.get_history_item, range(1, readline.get_current_history_length() + 1)))
|
||||
|
||||
|
||||
def sort_key(item):
|
||||
return len(item), item.lower()
|
||||
|
||||
|
||||
class HistoryCompleter:
|
||||
|
||||
def __init__(self, name=None):
|
||||
self.matches = []
|
||||
self.history_path = None
|
||||
if name:
|
||||
ddir = os.path.join(cache_dir(), 'ask')
|
||||
try:
|
||||
os.makedirs(ddir)
|
||||
except FileExistsError:
|
||||
pass
|
||||
self.history_path = os.path.join(ddir, name)
|
||||
|
||||
def complete(self, text, state):
|
||||
response = None
|
||||
if state == 0:
|
||||
history_values = get_history_items()
|
||||
if text:
|
||||
self.matches = sorted(
|
||||
(h for h in history_values if h and h.startswith(text)), key=sort_key)
|
||||
else:
|
||||
self.matches = []
|
||||
try:
|
||||
response = self.matches[state]
|
||||
except IndexError:
|
||||
response = None
|
||||
return response
|
||||
|
||||
def __enter__(self):
|
||||
if self.history_path:
|
||||
if os.path.exists(self.history_path):
|
||||
readline.read_history_file(self.history_path)
|
||||
readline.set_completer(self.complete)
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
if self.history_path:
|
||||
readline.write_history_file(self.history_path)
|
||||
|
||||
|
||||
def option_text():
|
||||
return '''\
|
||||
--type -t
|
||||
choices=line
|
||||
default=line
|
||||
Type of input. Defaults to asking for a line of text.
|
||||
|
||||
|
||||
--message -m
|
||||
The message to display to the user. If not specified a default
|
||||
message is shown.
|
||||
|
||||
|
||||
--name -n
|
||||
The name for this question. Used to store history of previous answers which can
|
||||
be used for completions and via the browse history readline bindings.
|
||||
'''
|
||||
|
||||
|
||||
def main(args):
|
||||
# For some reason importing readline in a key handler in the main kitty process
|
||||
# causes a crash of the python interpreter, probably because of some global
|
||||
# lock
|
||||
global readline
|
||||
import readline as rl
|
||||
readline = rl
|
||||
from kitty.shell import init_readline
|
||||
msg = 'Ask the user for input'
|
||||
try:
|
||||
args, items = parse_args(args[1:], option_text, '', msg, 'kitty ask')
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0])
|
||||
input('Press enter to quit...')
|
||||
raise SystemExit(e.code)
|
||||
|
||||
init_readline(readline)
|
||||
ans = {'items': items}
|
||||
|
||||
with alternate_screen(), HistoryCompleter(args.name):
|
||||
if args.message:
|
||||
print(styled(args.message, bold=True))
|
||||
|
||||
prompt = '> '
|
||||
try:
|
||||
ans['response'] = input(prompt)
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
||||
def handle_result(args, data, target_window_id, boss):
|
||||
if 'response' in data:
|
||||
func, *args = data['items']
|
||||
getattr(boss, func)(data['response'], *args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
ans = main(sys.argv)
|
||||
if ans:
|
||||
print(ans)
|
||||
0
kittens/diff/__init__.py
Normal file
0
kittens/diff/__init__.py
Normal file
138
kittens/diff/collect.py
Normal file
138
kittens/diff/collect.py
Normal file
@@ -0,0 +1,138 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from hashlib import md5
|
||||
from mimetypes import guess_type
|
||||
|
||||
path_name_map = {}
|
||||
|
||||
|
||||
class Collection:
|
||||
|
||||
def __init__(self):
|
||||
self.changes = {}
|
||||
self.renames = {}
|
||||
self.adds = set()
|
||||
self.removes = set()
|
||||
self.all_paths = []
|
||||
self.type_map = {}
|
||||
|
||||
def add_change(self, left_path, right_path):
|
||||
self.changes[left_path] = right_path
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'diff'
|
||||
|
||||
def add_rename(self, left_path, right_path):
|
||||
self.renames[left_path] = right_path
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'rename'
|
||||
|
||||
def add_add(self, right_path):
|
||||
self.adds.add(right_path)
|
||||
self.all_paths.append(right_path)
|
||||
self.type_map[right_path] = 'add'
|
||||
|
||||
def add_removal(self, left_path):
|
||||
self.removes.add(left_path)
|
||||
self.all_paths.append(left_path)
|
||||
self.type_map[left_path] = 'removal'
|
||||
|
||||
def finalize(self):
|
||||
self.all_paths.sort(key=path_name_map.get)
|
||||
|
||||
def __iter__(self):
|
||||
for path in self.all_paths:
|
||||
typ = self.type_map[path]
|
||||
data = self.changes[path] if typ == 'diff' else None
|
||||
yield path, self.type_map[path], data
|
||||
|
||||
|
||||
def collect_files(collection, left, right):
|
||||
left_names, right_names = set(), set()
|
||||
left_path_map, right_path_map = {}, {}
|
||||
|
||||
def walk(base, names, pmap):
|
||||
for dirpath, dirnames, filenames in os.walk(base):
|
||||
for filename in filenames:
|
||||
path = os.path.abspath(os.path.join(dirpath, filename))
|
||||
path_name_map[path] = name = os.path.relpath(path, base)
|
||||
names.add(name)
|
||||
pmap[name] = path
|
||||
|
||||
walk(left, left_names, left_path_map), walk(right, right_names, right_path_map)
|
||||
common_names = left_names & right_names
|
||||
changed_names = {n for n in common_names if data_for_path(left_path_map[n]) != data_for_path(right_path_map[n])}
|
||||
for n in changed_names:
|
||||
collection.add_change(left_path_map[n], right_path_map[n])
|
||||
|
||||
removed = left_names - common_names
|
||||
added = right_names - common_names
|
||||
ahash = {a: hash_for_path(right_path_map[a]) for a in added}
|
||||
rhash = {r: hash_for_path(left_path_map[r]) for r in removed}
|
||||
for name, rh in rhash.items():
|
||||
for n, ah in ahash.items():
|
||||
if ah == rh and data_for_path(left_path_map[name]) == data_for_path(right_path_map[n]):
|
||||
collection.add_rename(left_path_map[name], right_path_map[name])
|
||||
added.discard(n)
|
||||
break
|
||||
else:
|
||||
collection.add_removal(left_path_map[name])
|
||||
|
||||
for name in added:
|
||||
collection.add_add(right_path_map[name])
|
||||
|
||||
|
||||
sanitize_pat = re.compile('[\x00-\x1f\x7f\x80-\x9f]')
|
||||
|
||||
|
||||
def sanitize_sub(m):
|
||||
return '<{:x}>'.format(ord(m.group()[0]))
|
||||
|
||||
|
||||
def sanitize(text):
|
||||
return sanitize_pat.sub(sanitize_sub, text)
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def mime_type_for_path(path):
|
||||
return guess_type(path)[0] or 'application/octet-stream'
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def data_for_path(path):
|
||||
with open(path, 'rb') as f:
|
||||
ans = f.read()
|
||||
if not mime_type_for_path(path).startswith('image/'):
|
||||
try:
|
||||
ans = ans.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
return ans
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def lines_for_path(path):
|
||||
data = data_for_path(path)
|
||||
return tuple(map(sanitize, data.splitlines()))
|
||||
|
||||
|
||||
@lru_cache(maxsize=1024)
|
||||
def hash_for_path(path):
|
||||
md5(data_for_path(path)).digest()
|
||||
|
||||
|
||||
def create_collection(left, right):
|
||||
collection = Collection()
|
||||
if os.path.isdir(left):
|
||||
collect_files(collection, left, right)
|
||||
else:
|
||||
pl, pr = os.path.abspath(left), os.path.abspath(right)
|
||||
path_name_map[left] = pl
|
||||
path_name_map[right] = pr
|
||||
collection.add_change(pl, pr)
|
||||
collection.finalize()
|
||||
return collection
|
||||
98
kittens/diff/config.py
Normal file
98
kittens/diff/config.py
Normal file
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
|
||||
from kitty.config_utils import (
|
||||
init_config, load_config as _load_config, merge_dicts, parse_config_base,
|
||||
resolve_config, to_color
|
||||
)
|
||||
from kitty.constants import config_dir
|
||||
from kitty.rgb import color_as_sgr
|
||||
|
||||
defaults = None
|
||||
default_config_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), 'diff.conf'
|
||||
)
|
||||
|
||||
formats = {
|
||||
'title': '',
|
||||
'margin': '',
|
||||
'text': '',
|
||||
}
|
||||
|
||||
|
||||
def set_formats(opts):
|
||||
formats['text'] = '38' + color_as_sgr(opts.foreground) + ';48' + color_as_sgr(opts.background)
|
||||
formats['title'] = '38' + color_as_sgr(opts.title_fg) + ';48' + color_as_sgr(opts.title_bg) + ';1'
|
||||
formats['margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.margin_bg)
|
||||
formats['added_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.added_margin_bg)
|
||||
formats['removed_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.removed_margin_bg)
|
||||
formats['added'] = '38' + color_as_sgr(opts.foreground) + ';48' + color_as_sgr(opts.added_bg)
|
||||
formats['removed'] = '38' + color_as_sgr(opts.foreground) + ';48' + color_as_sgr(opts.removed_bg)
|
||||
formats['filler'] = '48' + color_as_sgr(opts.filler_bg)
|
||||
formats['hunk_margin'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_margin_bg)
|
||||
formats['hunk'] = '38' + color_as_sgr(opts.margin_fg) + ';48' + color_as_sgr(opts.hunk_bg)
|
||||
formats['removed_highlight'] = '48' + color_as_sgr(opts.highlight_removed_bg)
|
||||
formats['added_highlight'] = '48' + color_as_sgr(opts.highlight_added_bg)
|
||||
|
||||
|
||||
type_map = {}
|
||||
|
||||
for name in (
|
||||
'foreground background title_fg title_bg margin_bg margin_fg removed_bg removed_margin_bg added_bg added_margin_bg filler_bg hunk_bg hunk_margin_bg'
|
||||
' highlight_removed_bg highlight_added_bg'
|
||||
).split():
|
||||
type_map[name] = to_color
|
||||
|
||||
|
||||
def special_handling(*a):
|
||||
pass
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
type_map,
|
||||
special_handling,
|
||||
ans,
|
||||
check_keys=check_keys
|
||||
)
|
||||
return ans
|
||||
|
||||
|
||||
def merge_configs(defaults, vals):
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
newvals = vals.get(k, {})
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
else:
|
||||
ans[k] = vals.get(k, v)
|
||||
return ans
|
||||
|
||||
|
||||
def parse_defaults(lines, check_keys=False):
|
||||
return parse_config(lines, check_keys)
|
||||
|
||||
|
||||
Options, defaults = init_config(default_config_path, parse_defaults)
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None):
|
||||
return _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
|
||||
|
||||
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/diff.conf'
|
||||
defconf = os.path.join(config_dir, 'diff.conf')
|
||||
|
||||
|
||||
def init_config(args):
|
||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
|
||||
opts = load_config(*config, overrides=overrides)
|
||||
set_formats(opts)
|
||||
return opts
|
||||
15
kittens/diff/diff.conf
Normal file
15
kittens/diff/diff.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
foreground black
|
||||
background white
|
||||
title_fg black
|
||||
title_bg white
|
||||
margin_bg #fafbfc
|
||||
margin_fg #aaaaaa
|
||||
removed_bg #ffeef0
|
||||
highlight_removed_bg #fdb8c0
|
||||
removed_margin_bg #ffdce0
|
||||
added_bg #e6ffed
|
||||
highlight_added_bg #acf2bd
|
||||
added_margin_bg #cdffd8
|
||||
filler_bg #fafbfc
|
||||
hunk_margin_bg #dbedff
|
||||
hunk_bg #f1f8ff
|
||||
205
kittens/diff/git.py
Normal file
205
kittens/diff/git.py
Normal file
@@ -0,0 +1,205 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import concurrent.futures
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from .collect import lines_for_path
|
||||
from .diff_speedup import changed_center
|
||||
|
||||
left_lines = right_lines = None
|
||||
|
||||
|
||||
def run_diff(file1, file2, context=3):
|
||||
# returns: ok, is_different, patch
|
||||
p = subprocess.Popen([
|
||||
'git', 'diff', '--no-color', '--no-ext-diff', '--exit-code', '-U' + str(context), '--no-index', '--'
|
||||
] + [file1, file2],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.DEVNULL)
|
||||
stdout, stderr = p.communicate()
|
||||
returncode = p.wait()
|
||||
if returncode in (0, 1):
|
||||
return True, returncode == 1, stdout.decode('utf-8')
|
||||
return False, returncode, stderr.decode('utf-8')
|
||||
|
||||
|
||||
class Chunk:
|
||||
|
||||
__slots__ = ('is_context', 'left_start', 'right_start', 'left_count', 'right_count', 'centers')
|
||||
|
||||
def __init__(self, left_start, right_start, is_context=False):
|
||||
self.is_context = is_context
|
||||
self.left_start = left_start
|
||||
self.right_start = right_start
|
||||
self.left_count = self.right_count = 0
|
||||
self.centers = None
|
||||
|
||||
def add_line(self):
|
||||
self.right_count += 1
|
||||
|
||||
def remove_line(self):
|
||||
self.left_count += 1
|
||||
|
||||
def context_line(self):
|
||||
self.left_count += 1
|
||||
self.right_count += 1
|
||||
|
||||
def finalize(self):
|
||||
if not self.is_context and self.left_count == self.right_count:
|
||||
self.centers = tuple(changed_center(left_lines[self.left_start + i], right_lines[self.right_start + i]) for i in range(self.left_count))
|
||||
|
||||
def __repr__(self):
|
||||
return 'Chunk(is_context={}, left_start={}, left_count={}, right_start={}, right_count={})'.format(
|
||||
self.is_context, self.left_start, self.left_count, self.right_start, self.right_count)
|
||||
|
||||
|
||||
class Hunk:
|
||||
|
||||
def __init__(self, title, left, right):
|
||||
self.left_start, self.left_count = left
|
||||
self.right_start, self.right_count = right
|
||||
self.left_start -= 1 # 0-index
|
||||
self.right_start -= 1 # 0-index
|
||||
self.title = title
|
||||
self.chunks = []
|
||||
self.current_chunk = None
|
||||
self.largest_line_number = max(self.left_start + self.left_count, self.right_start + self.right_count)
|
||||
|
||||
def new_chunk(self, is_context=False):
|
||||
if self.chunks:
|
||||
c = self.chunks[-1]
|
||||
left_start = c.left_start + c.left_count
|
||||
right_start = c.right_start + c.right_count
|
||||
else:
|
||||
left_start = self.left_start
|
||||
right_start = self.right_start
|
||||
return Chunk(left_start, right_start, is_context)
|
||||
|
||||
def ensure_diff_chunk(self):
|
||||
if self.current_chunk is None:
|
||||
self.current_chunk = self.new_chunk(is_context=False)
|
||||
elif self.current_chunk.is_context:
|
||||
self.chunks.append(self.current_chunk)
|
||||
self.current_chunk = self.new_chunk(is_context=False)
|
||||
|
||||
def ensure_context_chunk(self):
|
||||
if self.current_chunk is None:
|
||||
self.current_chunk = self.new_chunk(is_context=True)
|
||||
elif not self.current_chunk.is_context:
|
||||
self.chunks.append(self.current_chunk)
|
||||
self.current_chunk = self.new_chunk(is_context=True)
|
||||
|
||||
def add_line(self):
|
||||
self.ensure_diff_chunk()
|
||||
self.current_chunk.add_line()
|
||||
|
||||
def remove_line(self):
|
||||
self.ensure_diff_chunk()
|
||||
self.current_chunk.remove_line()
|
||||
|
||||
def context_line(self):
|
||||
self.ensure_context_chunk()
|
||||
self.current_chunk.context_line()
|
||||
|
||||
def finalize(self):
|
||||
self.chunks.append(self.current_chunk)
|
||||
del self.current_chunk
|
||||
# Sanity check
|
||||
c = self.chunks[-1]
|
||||
if c.left_start + c.left_count != self.left_start + self.left_count:
|
||||
raise ValueError('Left side line mismatch {} != {}'.format(c.left_start + c.left_count, self.left_start + self.left_count))
|
||||
if c.right_start + c.right_count != self.right_start + self.right_count:
|
||||
raise ValueError('Left side line mismatch {} != {}'.format(c.right_start + c.right_count, self.right_start + self.right_count))
|
||||
for c in self.chunks:
|
||||
c.finalize()
|
||||
|
||||
|
||||
def parse_range(x):
|
||||
parts = x[1:].split(',', 1)
|
||||
start = abs(int(parts[0]))
|
||||
count = 1 if len(parts) < 2 else int(parts[1])
|
||||
return start, count
|
||||
|
||||
|
||||
def parse_hunk_header(line):
|
||||
parts = tuple(filter(None, line.split('@@', 2)))
|
||||
linespec = parts[0].strip()
|
||||
title = ''
|
||||
if len(parts) == 2:
|
||||
title = parts[1].strip()
|
||||
left, right = map(parse_range, linespec.split())
|
||||
return Hunk(title, left, right)
|
||||
|
||||
|
||||
class Patch:
|
||||
|
||||
def __init__(self, all_hunks):
|
||||
self.all_hunks = all_hunks
|
||||
self.largest_line_number = self.all_hunks[-1].largest_line_number if self.all_hunks else 0
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.all_hunks)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.all_hunks)
|
||||
|
||||
|
||||
def parse_patch(raw):
|
||||
all_hunks = []
|
||||
for line in raw.splitlines():
|
||||
if line.startswith('@@ '):
|
||||
current_hunk = parse_hunk_header(line)
|
||||
all_hunks.append(current_hunk)
|
||||
else:
|
||||
if not all_hunks:
|
||||
continue
|
||||
q = line[0]
|
||||
if q == '+':
|
||||
all_hunks[-1].add_line()
|
||||
elif q == '-':
|
||||
all_hunks[-1].remove_line()
|
||||
else:
|
||||
all_hunks[-1].context_line()
|
||||
for h in all_hunks:
|
||||
h.finalize()
|
||||
return Patch(all_hunks)
|
||||
|
||||
|
||||
class Differ:
|
||||
|
||||
def __init__(self):
|
||||
self.jmap = {}
|
||||
self.jobs = []
|
||||
|
||||
def add_diff(self, file1, file2):
|
||||
self.jmap[file1] = file2
|
||||
self.jobs.append(file1)
|
||||
|
||||
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(key[0], key[1])
|
||||
else:
|
||||
ans[key] = patch
|
||||
return ans
|
||||
167
kittens/diff/main.py
Normal file
167
kittens/diff/main.py
Normal file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.cli import CONFIG_HELP, appname, parse_args
|
||||
from kitty.key_encoding import ESCAPE
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, set_default_colors, set_line_wrapping, set_window_title
|
||||
)
|
||||
from .collect import create_collection, data_for_path
|
||||
from .config import init_config
|
||||
from .git import Differ
|
||||
from .render import render_diff
|
||||
|
||||
INITIALIZING, COLLECTED, DIFFED = range(3)
|
||||
|
||||
|
||||
def generate_diff(collection, context):
|
||||
d = Differ()
|
||||
|
||||
for path, item_type, changed_path in collection:
|
||||
if item_type == 'diff':
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
if not is_binary:
|
||||
d.add_diff(path, changed_path)
|
||||
|
||||
return d(context)
|
||||
|
||||
|
||||
class DiffHandler(Handler):
|
||||
|
||||
def __init__(self, args, opts, left, right):
|
||||
self.state = INITIALIZING
|
||||
self.opts = opts
|
||||
self.left, self.right = left, right
|
||||
self.report_traceback_on_exit = None
|
||||
self.args = args
|
||||
self.scroll_pos = 0
|
||||
|
||||
def create_collection(self):
|
||||
self.start_job('collect', create_collection, self.left, self.right)
|
||||
|
||||
def generate_diff(self):
|
||||
self.start_job('diff', generate_diff, self.collection, self.args.context)
|
||||
|
||||
def render_diff(self):
|
||||
self.diff_lines = tuple(render_diff(self.collection, self.diff_map, self.args, self.screen_size.cols))
|
||||
|
||||
def init_terminal_state(self):
|
||||
self.write(set_line_wrapping(False))
|
||||
self.write(set_window_title('kitty +diff'))
|
||||
self.write(set_default_colors(self.opts.foreground, self.opts.background))
|
||||
|
||||
def initialize(self):
|
||||
self.init_terminal_state()
|
||||
self.draw_screen()
|
||||
self.create_collection()
|
||||
|
||||
def finalize(self):
|
||||
self.write(set_default_colors())
|
||||
|
||||
def draw_screen(self):
|
||||
if self.state < DIFFED:
|
||||
self.write(clear_screen())
|
||||
self.write(_('Calculating diff, please wait...'))
|
||||
return
|
||||
self.write(clear_screen())
|
||||
for i in range(self.screen_size.rows - 1):
|
||||
lpos = self.scroll_pos + i
|
||||
if lpos >= len(self.diff_lines):
|
||||
text = ''
|
||||
else:
|
||||
text = self.diff_lines[lpos].text
|
||||
self.write(text)
|
||||
self.write('\x1b[0m\n\r')
|
||||
|
||||
def on_key(self, key_event):
|
||||
if self.state is INITIALIZING:
|
||||
if key_event.key is ESCAPE:
|
||||
self.quit_loop(0)
|
||||
return
|
||||
|
||||
def on_resize(self, screen_size):
|
||||
self.screen_size = screen_size
|
||||
if self.state > COLLECTED:
|
||||
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.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.render_diff()
|
||||
self.draw_screen()
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
|
||||
OPTIONS = partial('''\
|
||||
--context
|
||||
type=int
|
||||
default=3
|
||||
Number of lines of context to show between changes.
|
||||
|
||||
|
||||
--config
|
||||
type=list
|
||||
{config_help}
|
||||
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
Override individual configuration options, can be specified multiple times.
|
||||
Syntax: |_ name=value|. For example: |_ -o background=gray|
|
||||
|
||||
'''.format, config_help=CONFIG_HELP.format(conf_name='diff', appname=appname))
|
||||
|
||||
|
||||
def main(args):
|
||||
msg = 'Show a side-by-side diff of the specified files/directories'
|
||||
args, items = parse_args(args[1:], OPTIONS, 'file_or_directory file_or_directory', msg, 'kitty +kitten diff')
|
||||
if len(items) != 2:
|
||||
raise SystemExit('You must specify exactly two files/directories to compare')
|
||||
left, right = items
|
||||
if os.path.isdir(left) != os.path.isdir(right):
|
||||
raise SystemExit('The items to be diffed should both be either directories or files. Comparing a directory to a file is not valid.')
|
||||
opts = init_config(args)
|
||||
|
||||
loop = Loop()
|
||||
handler = DiffHandler(args, opts, left, right)
|
||||
loop.loop(handler)
|
||||
if loop.return_code != 0:
|
||||
if handler.report_traceback_on_exit:
|
||||
print(handler.report_traceback_on_exit, file=sys.stderr)
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
def handle_result(args, current_char, target_window_id, boss):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
334
kittens/diff/render.py
Normal file
334
kittens/diff/render.py
Normal file
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from gettext import gettext as _
|
||||
from itertools import repeat
|
||||
|
||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
||||
|
||||
from .collect import data_for_path, lines_for_path, path_name_map, sanitize
|
||||
from .config import formats
|
||||
|
||||
|
||||
class HunkRef:
|
||||
|
||||
__slots__ = ('hunk_num', 'line_num', 'chunk_num')
|
||||
|
||||
def __init__(self, hunk_num, chunk_num=None, line_num=None):
|
||||
self.hunk_num = hunk_num
|
||||
self.chunk_num = chunk_num
|
||||
self.line_num = line_num
|
||||
|
||||
|
||||
class LineRef:
|
||||
|
||||
__slots__ = ('src_line_number', 'wrapped_line_idx')
|
||||
|
||||
def __init__(self, sln, wli):
|
||||
self.src_line_number = sln
|
||||
self.wrapped_line_idx = wli
|
||||
|
||||
|
||||
class Reference:
|
||||
|
||||
__slots__ = ('path', 'extra')
|
||||
|
||||
def __init__(self, path, extra=None):
|
||||
self.path = path
|
||||
self.extra = extra
|
||||
|
||||
|
||||
class Line:
|
||||
|
||||
__slots__ = ('text', 'ref')
|
||||
|
||||
def __init__(self, text, ref):
|
||||
self.text = text
|
||||
self.ref = ref
|
||||
|
||||
|
||||
def yield_lines_from(iterator, reference):
|
||||
for text in iterator:
|
||||
yield Line(text, reference)
|
||||
|
||||
|
||||
def human_readable(size, sep=' '):
|
||||
""" Convert a size in bytes into a human readable form """
|
||||
divisor, suffix = 1, "B"
|
||||
for i, candidate in enumerate(('B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB')):
|
||||
if size < (1 << ((i + 1) * 10)):
|
||||
divisor, suffix = (1 << (i * 10)), candidate
|
||||
break
|
||||
size = str(float(size)/divisor)
|
||||
if size.find(".") > -1:
|
||||
size = size[:size.find(".")+2]
|
||||
if size.endswith('.0'):
|
||||
size = size[:-2]
|
||||
return size + sep + suffix
|
||||
|
||||
|
||||
def fit_in(text, count):
|
||||
p = truncate_point_for_length(text, count)
|
||||
if p >= len(text):
|
||||
return text
|
||||
if count > 1:
|
||||
p = truncate_point_for_length(text, count - 1)
|
||||
return text[:p] + '…'
|
||||
|
||||
|
||||
def fill_in(text, sz):
|
||||
w = wcswidth(text)
|
||||
if w < sz:
|
||||
text += ' ' * (sz - w)
|
||||
return text
|
||||
|
||||
|
||||
def place_in(text, sz):
|
||||
return fill_in(fit_in(text, sz), sz)
|
||||
|
||||
|
||||
def format_func(which):
|
||||
def formatted(text):
|
||||
fmt = formats[which]
|
||||
return '\x1b[' + fmt + 'm' + text + '\x1b[0m'
|
||||
formatted.__name__ = which + '_format'
|
||||
return formatted
|
||||
|
||||
|
||||
text_format = format_func('text')
|
||||
title_format = format_func('title')
|
||||
margin_format = format_func('margin')
|
||||
added_format = format_func('added')
|
||||
removed_format = format_func('removed')
|
||||
removed_margin_format = format_func('removed_margin')
|
||||
added_margin_format = format_func('added_margin')
|
||||
filler_format = format_func('filler')
|
||||
hunk_margin_format = format_func('hunk_margin')
|
||||
hunk_format = format_func('hunk')
|
||||
highlight_map = {'remove': ('removed_highlight', 'removed'), 'add': ('added_highlight', 'added')}
|
||||
|
||||
|
||||
def highlight_boundaries(ltype):
|
||||
s, e = highlight_map[ltype]
|
||||
start = '\x1b[' + formats[s] + 'm'
|
||||
stop = '\x1b[' + formats[e] + 'm'
|
||||
return start, stop
|
||||
|
||||
|
||||
def title_lines(left_path, args, columns, margin_size):
|
||||
name = fit_in(sanitize(path_name_map[left_path]), columns - 2 * margin_size)
|
||||
yield title_format(place_in(' ' + name, columns))
|
||||
yield title_format('━' * columns)
|
||||
|
||||
|
||||
def binary_lines(path, other_path, columns, margin_size):
|
||||
template = _('Binary file: {}')
|
||||
|
||||
def fl(path):
|
||||
text = template.format(human_readable(len(data_for_path(path))))
|
||||
text = place_in(text, columns // 2 - margin_size)
|
||||
return margin_format(' ' * margin_size) + text_format(text)
|
||||
|
||||
return fl(path) + fl(other_path)
|
||||
|
||||
|
||||
def split_to_size(line, width):
|
||||
if not line:
|
||||
yield line
|
||||
while line:
|
||||
p = truncate_point_for_length(line, width)
|
||||
yield line[:p]
|
||||
line = line[p:]
|
||||
|
||||
|
||||
def split_to_size_with_center(line, width, prefix_count, suffix_count, start, stop):
|
||||
sz = len(line)
|
||||
if prefix_count + suffix_count == sz:
|
||||
yield from split_to_size(line, width)
|
||||
return
|
||||
suffix_pos = sz - suffix_count
|
||||
pos = state = 0
|
||||
while line:
|
||||
p = truncate_point_for_length(line, width)
|
||||
if state is 0:
|
||||
if pos + p > prefix_count:
|
||||
state = 1
|
||||
a, line = line[:p], line[p:]
|
||||
if pos + p > suffix_pos:
|
||||
a = a[:suffix_pos - pos] + stop + a[suffix_pos - pos:]
|
||||
state = 2
|
||||
yield a[:prefix_count - pos] + start + a[prefix_count - pos:]
|
||||
else:
|
||||
yield line[:p]
|
||||
line = line[p:]
|
||||
elif state is 1:
|
||||
if pos + p > suffix_pos:
|
||||
state = 2
|
||||
a, line = line[:p], line[p:]
|
||||
yield start + a[:suffix_pos - pos] + stop + a[suffix_pos - pos:]
|
||||
else:
|
||||
yield start + line[:p]
|
||||
line = line[p:]
|
||||
elif state is 2:
|
||||
yield line[:p]
|
||||
line = line[p:]
|
||||
pos += p
|
||||
|
||||
|
||||
margin_bg_map = {'filler': filler_format, 'remove': removed_margin_format, 'add': added_margin_format, 'context': margin_format}
|
||||
text_bg_map = {'filler': filler_format, 'remove': removed_format, 'add': added_format, 'context': text_format}
|
||||
|
||||
|
||||
class DiffData:
|
||||
|
||||
def __init__(self, left_path, right_path, available_cols, margin_size):
|
||||
self.left_path, self.right_path = left_path, right_path
|
||||
self.available_cols = available_cols
|
||||
self.margin_size = margin_size
|
||||
self.left_lines, self.right_lines = map(lines_for_path, (left_path, right_path))
|
||||
self.filler_line = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
self.left_filler_line = render_diff_line('', '', 'remove', margin_size, available_cols)
|
||||
self.right_filler_line = render_diff_line('', '', 'add', margin_size, available_cols)
|
||||
|
||||
|
||||
def render_diff_line(number, text, ltype, margin_size, available_cols):
|
||||
margin = margin_bg_map[ltype](place_in(number, margin_size))
|
||||
content = text_bg_map[ltype](fill_in(text or '', available_cols))
|
||||
return margin + content
|
||||
|
||||
|
||||
def render_diff_pair(left_line_number, left, left_is_change, right_line_number, right, right_is_change, is_first, margin_size, available_cols):
|
||||
ltype = 'filler' if left_line_number is None else ('remove' if left_is_change else 'context')
|
||||
rtype = 'filler' if right_line_number is None else ('add' if right_is_change else 'context')
|
||||
return (
|
||||
render_diff_line(left_line_number if is_first else None, left, ltype, margin_size, available_cols) +
|
||||
render_diff_line(right_line_number if is_first else None, right, rtype, margin_size, available_cols)
|
||||
)
|
||||
|
||||
|
||||
def hunk_title(hunk_num, hunk, margin_size, available_cols):
|
||||
m = hunk_margin_format(' ' * margin_size)
|
||||
t = '@@ -{},{} +{},{} @@ {}'.format(hunk.left_start + 1, hunk.left_count, hunk.right_start + 1, hunk.right_count, hunk.title)
|
||||
return m + hunk_format(place_in(t, available_cols))
|
||||
|
||||
|
||||
def render_half_line(line_number, src, ltype, margin_size, available_cols, changed_center=None):
|
||||
if changed_center is not None and changed_center[0]:
|
||||
start, stop = highlight_boundaries(ltype)
|
||||
lines = split_to_size_with_center(src[line_number], available_cols, changed_center[0], changed_center[1], start, stop)
|
||||
else:
|
||||
lines = split_to_size(src[line_number], available_cols)
|
||||
line_number = str(line_number + 1)
|
||||
for line in lines:
|
||||
yield render_diff_line(line_number, line, ltype, margin_size, available_cols)
|
||||
line_number = ''
|
||||
|
||||
|
||||
def lines_for_chunk(data, hunk_num, chunk, chunk_num):
|
||||
if chunk.is_context:
|
||||
for i in range(chunk.left_count):
|
||||
left_line_number = chunk.left_start + i
|
||||
right_line_number = chunk.right_start + i
|
||||
lines = split_to_size(data.left_lines[left_line_number], data.available_cols)
|
||||
ref = Reference(data.left_path, HunkRef(hunk_num, chunk_num, i))
|
||||
left_line_number = str(left_line_number + 1)
|
||||
right_line_number = str(right_line_number + 1)
|
||||
for text in lines:
|
||||
line = render_diff_line(left_line_number, text, 'context', data.margin_size, data.available_cols)
|
||||
if right_line_number == left_line_number:
|
||||
r = line
|
||||
else:
|
||||
r = render_diff_line(right_line_number, text, 'context', data.margin_size, data.available_cols)
|
||||
yield Line(line + r, ref)
|
||||
left_line_number = right_line_number = ''
|
||||
else:
|
||||
common = min(chunk.left_count, chunk.right_count)
|
||||
for i in range(max(chunk.left_count, chunk.right_count)):
|
||||
ref = Reference(data.left_path, HunkRef(hunk_num, chunk_num, i))
|
||||
ll, rl = [], []
|
||||
if i < chunk.left_count:
|
||||
ll.extend(render_half_line(
|
||||
chunk.left_start + i, data.left_lines, 'remove', data.margin_size,
|
||||
data.available_cols, None if chunk.centers is None else chunk.centers[i]))
|
||||
if i < chunk.right_count:
|
||||
rl.extend(render_half_line(
|
||||
chunk.right_start + i, data.right_lines, 'add', data.margin_size,
|
||||
data.available_cols, None if chunk.centers is None else chunk.centers[i]))
|
||||
if i < common:
|
||||
extra = len(ll) - len(rl)
|
||||
if extra != 0:
|
||||
if extra < 0:
|
||||
x, fl = ll, data.left_filler_line
|
||||
extra = -extra
|
||||
else:
|
||||
x, fl = rl, data.right_filler_line
|
||||
x.extend(repeat(fl, extra))
|
||||
else:
|
||||
if ll:
|
||||
x, count = rl, len(ll)
|
||||
else:
|
||||
x, count = ll, len(rl)
|
||||
x.extend(repeat(data.filler_line, count))
|
||||
for left_line, right_line in zip(ll, rl):
|
||||
yield Line(left_line + right_line, ref)
|
||||
|
||||
|
||||
def lines_for_diff(left_path, right_path, hunks, args, columns, margin_size):
|
||||
available_cols = columns // 2 - margin_size
|
||||
data = DiffData(left_path, right_path, available_cols, margin_size)
|
||||
|
||||
for hunk_num, hunk in enumerate(hunks):
|
||||
yield Line(hunk_title(hunk_num, hunk, margin_size, columns - margin_size), Reference(left_path, HunkRef(hunk_num)))
|
||||
for cnum, chunk in enumerate(hunk.chunks):
|
||||
yield from lines_for_chunk(data, hunk_num, chunk, cnum)
|
||||
|
||||
|
||||
def all_lines(path, args, columns, margin_size, is_add=True):
|
||||
available_cols = columns // 2 - margin_size
|
||||
ltype = 'add' if is_add else 'remove'
|
||||
lines = lines_for_path(path)
|
||||
filler = render_diff_line('', '', 'filler', margin_size, available_cols)
|
||||
for line_number in range(len(lines)):
|
||||
h = render_half_line(line_number, lines, ltype, margin_size, available_cols)
|
||||
for i, hl in enumerate(h):
|
||||
ref = Reference(path, LineRef(line_number, i))
|
||||
text = (filler + h) if is_add else (h + filler)
|
||||
yield Line(text, ref)
|
||||
|
||||
|
||||
def rename_lines(path, other_path, args, columns, margin_size):
|
||||
m = ' ' * margin_size
|
||||
for line in split_to_size(margin_size + _('The file {0} was renamed to {1}').format(
|
||||
sanitize(path_name_map[path]), sanitize(path_name_map[other_path])), columns - margin_size):
|
||||
yield m + line
|
||||
|
||||
|
||||
def render_diff(collection, diff_map, args, columns):
|
||||
largest_line_number = 0
|
||||
for path, item_type, other_path in collection:
|
||||
if item_type == 'diff':
|
||||
patch = diff_map.get(path)
|
||||
if patch is not None:
|
||||
largest_line_number = max(largest_line_number, patch.largest_line_number)
|
||||
|
||||
margin_size = max(3, len(str(largest_line_number)) + 1)
|
||||
|
||||
for path, item_type, other_path in collection:
|
||||
item_ref = Reference(path)
|
||||
if item_type == 'diff':
|
||||
yield from yield_lines_from(title_lines(path, args, columns, margin_size), item_ref)
|
||||
is_binary = isinstance(data_for_path(path), bytes)
|
||||
if is_binary:
|
||||
yield from yield_lines_from(binary_lines(path, other_path, columns, margin_size), item_ref)
|
||||
else:
|
||||
yield from lines_for_diff(path, other_path, diff_map[path], args, columns, margin_size)
|
||||
elif item_type == 'add':
|
||||
yield from yield_lines_from(title_lines(other_path, args, columns, margin_size), item_ref)
|
||||
yield from all_lines(other_path, args, columns, margin_size, is_add=True)
|
||||
elif item_type == 'removal':
|
||||
yield from yield_lines_from(title_lines(path, args, columns, margin_size), item_ref)
|
||||
yield from all_lines(path, args, columns, margin_size, is_add=False)
|
||||
elif item_type == 'rename':
|
||||
yield from yield_lines_from(title_lines(path, args, columns, margin_size), item_ref)
|
||||
yield from yield_lines_from(rename_lines(path, other_path, args, columns, margin_size))
|
||||
52
kittens/diff/speedup.c
Normal file
52
kittens/diff/speedup.c
Normal file
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* speedup.c
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
|
||||
static PyObject*
|
||||
changed_center(PyObject *self UNUSED, PyObject *args) {
|
||||
unsigned int prefix_count = 0, suffix_count = 0;
|
||||
PyObject *lp, *rp;
|
||||
if (!PyArg_ParseTuple(args, "UU", &lp, &rp)) return NULL;
|
||||
const size_t left_len = PyUnicode_GET_LENGTH(lp), right_len = PyUnicode_GET_LENGTH(rp);
|
||||
|
||||
#define R(which, index) PyUnicode_READ(PyUnicode_KIND(which), PyUnicode_DATA(which), index)
|
||||
while(prefix_count < MIN(left_len, right_len)) {
|
||||
if (R(lp, prefix_count) != R(rp, prefix_count)) break;
|
||||
prefix_count++;
|
||||
}
|
||||
if (left_len && right_len && prefix_count < MIN(left_len, right_len)) {
|
||||
while(suffix_count < MIN(left_len - prefix_count, right_len - prefix_count)) {
|
||||
if(R(lp, left_len - 1 - suffix_count) != R(rp, right_len - 1 - suffix_count)) break;
|
||||
suffix_count++;
|
||||
}
|
||||
}
|
||||
#undef R
|
||||
return Py_BuildValue("II", prefix_count, suffix_count);
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"changed_center", (PyCFunction)changed_center, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "diff_speedup", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
};
|
||||
|
||||
EXPORTED PyMODINIT_FUNC
|
||||
PyInit_diff_speedup(void) {
|
||||
PyObject *m;
|
||||
|
||||
m = PyModule_Create(&module);
|
||||
if (m == NULL) return NULL;
|
||||
return m;
|
||||
}
|
||||
0
kittens/hints/__init__.py
Normal file
0
kittens/hints/__init__.py
Normal file
326
kittens/hints/main.py
Normal file
326
kittens/hints/main.py
Normal file
@@ -0,0 +1,326 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
from functools import lru_cache, partial
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.fast_data_types import set_clipboard_string
|
||||
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, faint, set_cursor_visible, set_window_title, styled
|
||||
)
|
||||
|
||||
URL_PREFIXES = 'http https file ftp'.split()
|
||||
HINT_ALPHABET = string.digits + string.ascii_lowercase
|
||||
|
||||
|
||||
class Mark(object):
|
||||
|
||||
__slots__ = ('index', 'start', 'end', 'text')
|
||||
|
||||
def __init__(self, index, start, end, text):
|
||||
self.index, self.start, self.end = index, start, end
|
||||
self.text = text
|
||||
|
||||
|
||||
@lru_cache(maxsize=2048)
|
||||
def encode_hint(num):
|
||||
res = ''
|
||||
d = len(HINT_ALPHABET)
|
||||
while not res or num > 0:
|
||||
num, i = divmod(num, d)
|
||||
res = HINT_ALPHABET[i] + res
|
||||
return res
|
||||
|
||||
|
||||
def decode_hint(x):
|
||||
return int(x, 36)
|
||||
|
||||
|
||||
def render(lines, current_input):
|
||||
ans = []
|
||||
|
||||
def mark(m):
|
||||
hint = encode_hint(m.index)
|
||||
text = m.text
|
||||
if current_input and not hint.startswith(current_input):
|
||||
return faint(text)
|
||||
hint = hint[len(current_input):] or ' '
|
||||
text = text[len(hint):]
|
||||
return styled(
|
||||
hint,
|
||||
fg='black',
|
||||
bg='green',
|
||||
bold=True
|
||||
) + styled(
|
||||
text, fg='gray', fg_intense=True, bold=True
|
||||
)
|
||||
|
||||
for line, marks in lines:
|
||||
if not marks:
|
||||
ans.append(faint(line))
|
||||
continue
|
||||
buf = []
|
||||
|
||||
for i, m in enumerate(marks):
|
||||
if i == 0 and m.start:
|
||||
buf.append(faint(line[:m.start]))
|
||||
buf.append(mark(m))
|
||||
if m is not marks[-1]:
|
||||
buf.append(faint(line[m.end:marks[i + 1].start]))
|
||||
|
||||
rest = line[marks[-1].end:]
|
||||
if rest:
|
||||
buf.append(faint(rest))
|
||||
|
||||
ans.append(''.join(buf))
|
||||
return '\r\n'.join(ans)
|
||||
|
||||
|
||||
class Hints(Handler):
|
||||
|
||||
def __init__(self, lines, index_map, args):
|
||||
self.lines, self.index_map = tuple(lines), index_map
|
||||
self.current_input = ''
|
||||
self.current_text = None
|
||||
self.args = args
|
||||
self.window_title = _('Choose URL') if args.type == 'url' else _('Choose text')
|
||||
self.chosen = None
|
||||
|
||||
def init_terminal_state(self):
|
||||
self.write(set_cursor_visible(False))
|
||||
self.write(set_window_title(self.window_title))
|
||||
|
||||
def initialize(self):
|
||||
self.init_terminal_state()
|
||||
self.draw_screen()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste):
|
||||
changed = False
|
||||
for c in text:
|
||||
if c in HINT_ALPHABET:
|
||||
self.current_input += c
|
||||
changed = True
|
||||
if changed:
|
||||
matches = [
|
||||
m.text for idx, m in self.index_map.items()
|
||||
if encode_hint(idx).startswith(self.current_input)
|
||||
]
|
||||
if len(matches) == 1:
|
||||
self.chosen = matches[0]
|
||||
self.quit_loop(0)
|
||||
return
|
||||
self.current_text = None
|
||||
self.draw_screen()
|
||||
|
||||
def on_key(self, key_event):
|
||||
if key_event is backspace_key:
|
||||
self.current_input = self.current_input[:-1]
|
||||
self.current_text = None
|
||||
self.draw_screen()
|
||||
elif key_event is enter_key and self.current_input:
|
||||
idx = decode_hint(self.current_input)
|
||||
self.chosen = self.index_map[idx].text
|
||||
self.quit_loop(0)
|
||||
elif key_event.key is ESCAPE:
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.draw_screen()
|
||||
|
||||
def draw_screen(self):
|
||||
if self.current_text is None:
|
||||
self.current_text = render(self.lines, self.current_input)
|
||||
self.write(clear_screen())
|
||||
self.write(self.current_text)
|
||||
|
||||
|
||||
def regex_finditer(pat, minimum_match_length, line):
|
||||
for m in pat.finditer(line):
|
||||
s, e = m.span(pat.groups)
|
||||
if e - s >= minimum_match_length:
|
||||
yield s, e
|
||||
|
||||
|
||||
closing_bracket_map = {'(': ')', '[': ']', '{': '}', '<': '>'}
|
||||
|
||||
|
||||
def find_urls(pat, line):
|
||||
for m in pat.finditer(line):
|
||||
s, e = m.span()
|
||||
if s > 4 and line[s - 5:s] == 'link:': # asciidoc URLs
|
||||
url = line[s:e]
|
||||
idx = url.rfind('[')
|
||||
if idx > -1:
|
||||
e -= len(url) - idx
|
||||
while line[e - 1] in '.,?!' and e > 1: # remove trailing punctuation
|
||||
e -= 1
|
||||
# Detect a bracketed URL
|
||||
if s > 0 and e > s + 4 and line[s-1] in '({[<' and line[e-1] == closing_bracket_map[line[s-1]]:
|
||||
e -= 1
|
||||
yield s, e
|
||||
|
||||
|
||||
def mark(finditer, line, all_marks):
|
||||
marks = []
|
||||
for s, e in finditer(line):
|
||||
idx = len(all_marks)
|
||||
text = line[s:e]
|
||||
marks.append(Mark(idx, s, e, text))
|
||||
all_marks.append(marks[-1])
|
||||
return line, marks
|
||||
|
||||
|
||||
def run_loop(args, lines, index_map):
|
||||
loop = Loop()
|
||||
handler = Hints(lines, index_map, args)
|
||||
loop.loop(handler)
|
||||
if handler.chosen and loop.return_code == 0:
|
||||
return {'match': handler.chosen, 'program': args.program}
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
def escape(chars):
|
||||
return chars.replace('\\', '\\\\').replace('-', r'\-').replace(']', r'\]')
|
||||
|
||||
|
||||
def run(args, text):
|
||||
if args.type == 'url':
|
||||
from .url_regex import url_delimiters
|
||||
url_pat = '(?:{})://[^{}]{{3,}}'.format(
|
||||
'|'.join(args.url_prefixes.split(',')), url_delimiters
|
||||
)
|
||||
finditer = partial(find_urls, re.compile(url_pat))
|
||||
elif args.type == 'path':
|
||||
finditer = partial(regex_finditer, re.compile(r'(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,5})'), args.minimum_match_length)
|
||||
elif args.type == 'line':
|
||||
finditer = partial(regex_finditer, re.compile(r'(?m)^\s*(.+)\s*$'), args.minimum_match_length)
|
||||
elif args.type == 'word':
|
||||
chars = args.word_characters
|
||||
if chars is None:
|
||||
import json
|
||||
chars = json.loads(os.environ['KITTY_COMMON_OPTS'])['select_by_word_characters']
|
||||
pat = re.compile(r'(?u)[{}\w]{{{},}}'.format(escape(chars), args.minimum_match_length))
|
||||
finditer = partial(regex_finditer, pat, args.minimum_match_length)
|
||||
else:
|
||||
finditer = partial(regex_finditer, re.compile(args.regex), args.minimum_match_length)
|
||||
lines = []
|
||||
all_marks = []
|
||||
for line in text.splitlines():
|
||||
marked = mark(finditer, line, all_marks)
|
||||
lines.append(marked)
|
||||
if not all_marks:
|
||||
input(_('No {} found, press Enter to abort.').format(
|
||||
'URLs' if args.type == 'url' else 'matches'
|
||||
))
|
||||
return
|
||||
|
||||
largest_index = all_marks[-1].index
|
||||
for m in all_marks:
|
||||
m.index = largest_index - m.index
|
||||
index_map = {m.index: m for m in all_marks}
|
||||
|
||||
return run_loop(args, lines, index_map)
|
||||
|
||||
|
||||
OPTIONS = partial(r'''
|
||||
--program
|
||||
default=default
|
||||
What program to use to open matched text. Defaults to the default open program
|
||||
for the operating system. Use a value of - to paste the match into the
|
||||
terminal window instead. A value of @ will copy the match to the clipboard.
|
||||
|
||||
|
||||
--type
|
||||
default=url
|
||||
choices=url,regex,path,line,word
|
||||
The type of text to search for.
|
||||
|
||||
|
||||
--regex
|
||||
default=(?m)^\s*(.+)\s*$
|
||||
The regular expression to use when --type=regex. If you specify a group in the
|
||||
regular expression only the group will be matched. This allow you to match text
|
||||
ignoring a prefix/suffix, as needed. The default expression matches lines.
|
||||
|
||||
|
||||
--url-prefixes
|
||||
default={0}
|
||||
Comma separated list of recognized URL prefixes.
|
||||
|
||||
|
||||
--word-characters
|
||||
Characters to consider as part of a word. In addition, all characters marked as
|
||||
alpha-numeric in the unicode database will be considered as word characters.
|
||||
Defaults to the select_by_word_characters setting from kitty.conf.
|
||||
|
||||
|
||||
--minimum-match-length
|
||||
default=3
|
||||
type=int
|
||||
The minimum number of characters to consider a match.
|
||||
'''.format, ','.join(sorted(URL_PREFIXES)))
|
||||
|
||||
|
||||
def main(args):
|
||||
msg = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
|
||||
text = ''
|
||||
if sys.stdin.isatty():
|
||||
if '--help' not in args and '-h' not in args:
|
||||
print('You must pass the text to be hinted on STDIN', file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return
|
||||
else:
|
||||
text = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open('/dev/tty')
|
||||
try:
|
||||
args, items = parse_args(args[1:], OPTIONS, '', msg, 'hints')
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return
|
||||
if items:
|
||||
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
|
||||
input(_('Press Enter to quit'))
|
||||
return
|
||||
return run(args, text)
|
||||
|
||||
|
||||
def handle_result(args, data, target_window_id, boss):
|
||||
program = data['program']
|
||||
if program == '-':
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
w.paste(data['match'])
|
||||
elif program == '@':
|
||||
set_clipboard_string(data['match'])
|
||||
else:
|
||||
cwd = None
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
cwd = w.cwd_of_child
|
||||
boss.open_url(data['match'], None if program == 'default' else program, cwd=cwd)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Run with kitty +kitten hints
|
||||
ans = main(sys.argv)
|
||||
if ans:
|
||||
print(ans)
|
||||
1
kittens/hints/url_regex.py
Normal file
1
kittens/hints/url_regex.py
Normal file
@@ -0,0 +1 @@
|
||||
url_delimiters = '\x00-\x20\x7f-\xa0\xad\u0600-\u0605\u061c\u06dd\u070f\u08e2\u1680\u180e\u2000-\u200f\u2028-\u202f\u205f-\u2064\u2066-\u206f\u3000\ud800-\uf8ff\ufeff\ufff9-\ufffb\U000110bd\U0001bca0-\U0001bca3\U0001d173-\U0001d17a\U000e0001\U000e0020-\U000e007f\U000f0000-\U000ffffd\U00100000-\U0010fffd' # noqa
|
||||
87
kittens/runner.py
Normal file
87
kittens/runner.py
Normal file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
aliases = {'url_hints': 'hints'}
|
||||
|
||||
|
||||
def resolved_kitten(k):
|
||||
return aliases.get(k, k)
|
||||
|
||||
|
||||
def import_kitten_main_module(config_dir, kitten):
|
||||
if kitten.endswith('.py'):
|
||||
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))
|
||||
with open(path) as f:
|
||||
src = f.read()
|
||||
code = compile(src, path, 'exec')
|
||||
g = {'__name__': 'kitten'}
|
||||
exec(code, g)
|
||||
return {'start': g['main'], 'end': g['handle_result']}
|
||||
else:
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||
return {'start': m.main, 'end': m.handle_result}
|
||||
|
||||
|
||||
def create_kitten_handler(kitten, orig_args):
|
||||
from kitty.constants import config_dir
|
||||
kitten = resolved_kitten(kitten)
|
||||
m = import_kitten_main_module(config_dir, kitten)
|
||||
return partial(m['end'], [kitten] + orig_args)
|
||||
|
||||
|
||||
def launch(args):
|
||||
config_dir, kitten = args[:2]
|
||||
kitten = resolved_kitten(kitten)
|
||||
del args[:2]
|
||||
args = [kitten] + args
|
||||
os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir
|
||||
from kittens.tui.operations import clear_screen, reset_mode
|
||||
m = import_kitten_main_module(config_dir, kitten)
|
||||
try:
|
||||
result = m['start'](args)
|
||||
finally:
|
||||
sys.stdin = sys.__stdin__
|
||||
print(reset_mode('ALTERNATE_SCREEN') + clear_screen(), end='')
|
||||
if result is not None:
|
||||
import json
|
||||
data = json.dumps(result)
|
||||
print('OK:', len(data), data)
|
||||
sys.stderr.flush()
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def deserialize(output):
|
||||
import json
|
||||
if output.startswith('OK: '):
|
||||
prefix, sz, rest = output.split(' ', 2)
|
||||
return json.loads(rest[:int(sz)])
|
||||
|
||||
|
||||
def run_kitten(kitten):
|
||||
import runpy
|
||||
kitten = resolved_kitten(kitten)
|
||||
runpy.run_module('kittens.{}.main'.format(kitten), run_name='__main__')
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
args = sys.argv[1:]
|
||||
launch(args)
|
||||
except Exception:
|
||||
print('Unhandled exception running kitten:')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input('Press Enter to quit...')
|
||||
0
kittens/tui/__init__.py
Normal file
0
kittens/tui/__init__.py
Normal file
63
kittens/tui/handler.py
Normal file
63
kittens/tui/handler.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
|
||||
class Handler:
|
||||
|
||||
def _initialize(self, screen_size, quit_loop, wakeup, start_job):
|
||||
self.screen_size, self.quit_loop = screen_size, quit_loop
|
||||
self.wakeup = wakeup
|
||||
self.start_job = start_job
|
||||
|
||||
def __enter__(self):
|
||||
self.initialize()
|
||||
|
||||
def __exit__(self, *a):
|
||||
del self.write_buf[:]
|
||||
self.finalize()
|
||||
|
||||
def initialize(self):
|
||||
pass
|
||||
|
||||
def finalize(self):
|
||||
pass
|
||||
|
||||
def on_resize(self, screen_size):
|
||||
self.screen_size = screen_size
|
||||
|
||||
def on_term(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_text(self, text, in_bracketed_paste=False):
|
||||
pass
|
||||
|
||||
def on_key(self, key_event):
|
||||
pass
|
||||
|
||||
def on_mouse(self, mouse_event):
|
||||
pass
|
||||
|
||||
def on_interrupt(self):
|
||||
pass
|
||||
|
||||
def on_eot(self):
|
||||
pass
|
||||
|
||||
def on_wakeup(self):
|
||||
pass
|
||||
|
||||
def on_job_done(self, job_id, job_result):
|
||||
pass
|
||||
|
||||
def write(self, data):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
self.write_buf.append(data)
|
||||
|
||||
def print(self, *args, sep=' ', end='\r\n'):
|
||||
data = sep.join(map(str, args)) + end
|
||||
self.write(data)
|
||||
|
||||
def suspend(self):
|
||||
return self._term_manager.suspend()
|
||||
403
kittens/tui/loop.py
Normal file
403
kittens/tui/loop.py
Normal file
@@ -0,0 +1,403 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import codecs
|
||||
import fcntl
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import selectors
|
||||
import signal
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
from collections import namedtuple
|
||||
from contextlib import closing, contextmanager
|
||||
from functools import partial
|
||||
from queue import Empty, Queue
|
||||
|
||||
from kitty.fast_data_types import parse_input_from_terminal, safe_pipe
|
||||
from kitty.icat import screen_size
|
||||
from kitty.key_encoding import (
|
||||
ALT, CTRL, PRESS, RELEASE, REPEAT, SHIFT, C, D, backspace_key,
|
||||
decode_key_event, enter_key
|
||||
)
|
||||
|
||||
from .handler import Handler
|
||||
from .operations import clear_screen, init_state, reset_state
|
||||
|
||||
|
||||
def log(*a, **kw):
|
||||
fd = getattr(log, 'fd', None)
|
||||
if fd is None:
|
||||
fd = log.fd = open('/tmp/kitten-debug', 'w')
|
||||
kw['file'] = fd
|
||||
print(*a, **kw)
|
||||
fd.flush()
|
||||
|
||||
|
||||
def write_all(fd, data):
|
||||
if isinstance(data, str):
|
||||
data = data.encode('utf-8')
|
||||
while data:
|
||||
n = os.write(fd, data)
|
||||
if not n:
|
||||
break
|
||||
data = data[n:]
|
||||
|
||||
|
||||
class TermManager:
|
||||
|
||||
def __init__(self, input_fd, output_fd):
|
||||
self.input_fd = input_fd
|
||||
self.output_fd = output_fd
|
||||
self.original_fl = fcntl.fcntl(self.input_fd, fcntl.F_GETFL)
|
||||
self.isatty = os.isatty(self.input_fd)
|
||||
if self.isatty:
|
||||
self.original_termios = termios.tcgetattr(self.input_fd)
|
||||
|
||||
def set_state_for_loop(self):
|
||||
write_all(self.output_fd, init_state())
|
||||
fcntl.fcntl(self.input_fd, fcntl.F_SETFL, self.original_fl | os.O_NONBLOCK)
|
||||
if self.isatty:
|
||||
tty.setraw(self.input_fd)
|
||||
|
||||
def reset_state_to_original(self):
|
||||
if self.isatty:
|
||||
termios.tcsetattr(self.input_fd, termios.TCSADRAIN, self.original_termios)
|
||||
fcntl.fcntl(self.input_fd, fcntl.F_SETFL, self.original_fl)
|
||||
write_all(self.output_fd, reset_state())
|
||||
|
||||
@contextmanager
|
||||
def suspend(self):
|
||||
self.reset_state_to_original()
|
||||
yield self
|
||||
self.set_state_for_loop()
|
||||
|
||||
def __enter__(self):
|
||||
self.set_state_for_loop()
|
||||
return self
|
||||
|
||||
def __exit__(self, *a):
|
||||
self.reset_state_to_original()
|
||||
|
||||
|
||||
LEFT, MIDDLE, RIGHT, FOURTH, FIFTH = 1, 2, 4, 8, 16
|
||||
DRAG = REPEAT
|
||||
MouseEvent = namedtuple('MouseEvent', 'x y type buttons mods')
|
||||
bmap = {0: LEFT, 1: MIDDLE, 2: RIGHT}
|
||||
MOTION_INDICATOR = 1 << 5
|
||||
EXTRA_BUTTON_INDICATOR = 1 << 6
|
||||
SHIFT_INDICATOR = 1 << 2
|
||||
ALT_INDICATOR = 1 << 3
|
||||
CTRL_INDICATOR = 1 << 4
|
||||
|
||||
|
||||
def decode_sgr_mouse(text):
|
||||
cb, x, y = text.split(';')
|
||||
m, y = y[-1], y[:-1]
|
||||
cb, x, y = map(int, (cb, x, y))
|
||||
typ = RELEASE if m == 'm' else (DRAG if cb & MOTION_INDICATOR else PRESS)
|
||||
buttons = 0
|
||||
cb3 = cb & 3
|
||||
if cb3 != 3:
|
||||
if cb & EXTRA_BUTTON_INDICATOR:
|
||||
buttons |= FIFTH if cb3 & 1 else FOURTH
|
||||
else:
|
||||
buttons |= bmap[cb3]
|
||||
mods = 0
|
||||
if cb & SHIFT_INDICATOR:
|
||||
mods |= SHIFT
|
||||
if cb & ALT_INDICATOR:
|
||||
mods |= ALT
|
||||
if cb & CTRL_INDICATOR:
|
||||
mods |= CTRL
|
||||
return MouseEvent(x, y, typ, buttons, mods)
|
||||
|
||||
|
||||
class UnhandledException(Handler):
|
||||
|
||||
def __init__(self, tb):
|
||||
self.tb = tb
|
||||
|
||||
def initialize(self):
|
||||
self.write(clear_screen())
|
||||
self.write(self.tb.replace('\n', '\r\n'))
|
||||
self.write('\r\n')
|
||||
self.write('Press the Enter key to quit')
|
||||
|
||||
def on_key(self, key_event):
|
||||
if key_event is enter_key:
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
|
||||
class Loop:
|
||||
|
||||
def __init__(self, input_fd=None, output_fd=None,
|
||||
sanitize_bracketed_paste='[\x03\x04\x0e\x0f\r\x07\x7f\x8d\x8e\x8f\x90\x9b\x9d\x9e\x9f]'):
|
||||
self.input_fd = sys.stdin.fileno() if input_fd is None else input_fd
|
||||
self.output_fd = sys.stdout.fileno() if output_fd is None else output_fd
|
||||
self.wakeup_read_fd, self.wakeup_write_fd = safe_pipe()
|
||||
# For some reason on macOS the DefaultSelector fails when input_fd is
|
||||
# open('/dev/tty')
|
||||
self.sel = s = selectors.PollSelector()
|
||||
s.register(self.input_fd, selectors.EVENT_READ, self._read_ready)
|
||||
s.register(
|
||||
self.wakeup_read_fd, selectors.EVENT_READ, self._wakeup_ready
|
||||
)
|
||||
s.register(self.output_fd, selectors.EVENT_WRITE, self._write_ready)
|
||||
self.return_code = 0
|
||||
self.read_allowed = True
|
||||
self.read_buf = ''
|
||||
self.decoder = codecs.getincrementaldecoder('utf-8')('ignore')
|
||||
try:
|
||||
self.iov_limit = os.sysconf('SC_IOV_MAX') - 1
|
||||
except Exception:
|
||||
self.iov_limit = 255
|
||||
self.parse_input_from_terminal = partial(parse_input_from_terminal, self._on_text, self._on_dcs, self._on_csi, self._on_osc, self._on_pm, self._on_apc)
|
||||
self.ebs_pat = re.compile('([\177\r\x03\x04])')
|
||||
self.in_bracketed_paste = False
|
||||
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):
|
||||
if not self.read_allowed:
|
||||
return
|
||||
try:
|
||||
data = os.read(self.input_fd, io.DEFAULT_BUFFER_SIZE)
|
||||
except BlockingIOError:
|
||||
return
|
||||
if not data:
|
||||
raise EOFError('The input stream is closed')
|
||||
data = self.decoder.decode(data)
|
||||
if self.read_buf:
|
||||
data = self.read_buf + data
|
||||
self.read_buf = data
|
||||
self.handler = handler
|
||||
try:
|
||||
self.read_buf = self.parse_input_from_terminal(self.read_buf, self.in_bracketed_paste)
|
||||
except Exception:
|
||||
self.read_buf = ''
|
||||
raise
|
||||
finally:
|
||||
del self.handler
|
||||
|
||||
def _on_text(self, text):
|
||||
if self.in_bracketed_paste and self.sanitize_bracketed_paste:
|
||||
text = self.sanitize_ibp_pat.sub('', text)
|
||||
|
||||
for chunk in self.ebs_pat.split(text):
|
||||
if len(chunk) == 1:
|
||||
if chunk == '\r':
|
||||
self.handler.on_key(enter_key)
|
||||
elif chunk == '\177':
|
||||
self.handler.on_key(backspace_key)
|
||||
elif chunk == '\x03':
|
||||
self.handler.on_interrupt()
|
||||
elif chunk == '\x04':
|
||||
self.handler.on_eot()
|
||||
else:
|
||||
self.handler.on_text(chunk, self.in_bracketed_paste)
|
||||
else:
|
||||
self.handler.on_text(chunk, self.in_bracketed_paste)
|
||||
|
||||
def _on_dcs(self, dcs):
|
||||
pass
|
||||
|
||||
def _on_csi(self, csi):
|
||||
q = csi[-1]
|
||||
if q in 'mM':
|
||||
if csi.startswith('<'):
|
||||
# SGR mouse event
|
||||
try:
|
||||
ev = decode_sgr_mouse(csi[1:])
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
self.handler.on_mouse(ev)
|
||||
elif q == '~':
|
||||
if csi == '200~':
|
||||
self.in_bracketed_paste = True
|
||||
elif csi == '201~':
|
||||
self.in_bracketed_paste = False
|
||||
|
||||
def _on_pm(self, pm):
|
||||
pass
|
||||
|
||||
def _on_osc(self, osc):
|
||||
pass
|
||||
|
||||
def _on_apc(self, apc):
|
||||
if apc.startswith('K'):
|
||||
try:
|
||||
k = decode_key_event(apc)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if k.mods is CTRL and k.type is not RELEASE:
|
||||
if k.key is C:
|
||||
self.handler.on_interrupt()
|
||||
return
|
||||
if k.key is D:
|
||||
self.handler.on_eot()
|
||||
return
|
||||
self.handler.on_key(k)
|
||||
|
||||
def _write_ready(self, handler):
|
||||
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))
|
||||
written = os.writev(self.output_fd, handler.write_buf)
|
||||
if not written:
|
||||
raise EOFError('The output stream is closed')
|
||||
if written >= sum(sizes):
|
||||
handler.write_buf = []
|
||||
else:
|
||||
consumed = 0
|
||||
for i, buf in enumerate(handler.write_buf):
|
||||
if not written:
|
||||
break
|
||||
if len(buf) <= written:
|
||||
written -= len(buf)
|
||||
consumed += 1
|
||||
continue
|
||||
handler.write_buf[i] = buf[written:]
|
||||
break
|
||||
del handler.write_buf[:consumed]
|
||||
|
||||
def _wakeup_ready(self, handler):
|
||||
data = os.read(self.wakeup_read_fd, 1024)
|
||||
if b'r' in data:
|
||||
screen_size.changed = True
|
||||
handler.on_resize(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')
|
||||
|
||||
def quit(self, return_code=None):
|
||||
self.read_allowed = False
|
||||
if return_code is not None:
|
||||
self.return_code = return_code
|
||||
|
||||
def wakeup(self):
|
||||
self._wakeup_write(b'1')
|
||||
|
||||
def _modify_output_selector(self, waiting_for_write):
|
||||
if waiting_for_write:
|
||||
try:
|
||||
self.sel.register(self.output_fd, selectors.EVENT_WRITE, self._write_ready)
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
self.sel.unregister(self.output_fd)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def loop(self, handler):
|
||||
select = self.sel.select
|
||||
tb = None
|
||||
waiting_for_write = True
|
||||
with closing(self.sel), TermManager(self.input_fd, self.output_fd) as term_manager:
|
||||
signal.signal(signal.SIGWINCH, self._on_sigwinch)
|
||||
signal.signal(signal.SIGTERM, self._on_sigterm)
|
||||
signal.signal(signal.SIGINT, self._on_sigint)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
keep_going = True
|
||||
try:
|
||||
handler._initialize(screen_size(), self.quit, self.wakeup, self.start_job)
|
||||
with handler:
|
||||
while keep_going:
|
||||
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(waiting_for_write)
|
||||
events = select()
|
||||
for key, mask in events:
|
||||
key.data(handler)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
self.return_code = 1
|
||||
keep_going = False
|
||||
|
||||
finalize_output = b''.join(handler.write_buf).decode('utf-8')
|
||||
|
||||
if tb is not None:
|
||||
self._report_error_loop(finalize_output + tb, term_manager)
|
||||
if tb is None:
|
||||
os.write(self.output_fd, finalize_output.encode('utf-8'))
|
||||
|
||||
def _report_error_loop(self, tb, term_manager):
|
||||
select = self.sel.select
|
||||
waiting_for_write = False
|
||||
handler = UnhandledException(tb)
|
||||
handler.write_buf = []
|
||||
handler._term_manager = term_manager
|
||||
handler._initialize(screen_size(), self.quit, self.wakeup, self.start_job)
|
||||
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(waiting_for_write)
|
||||
events = select()
|
||||
for key, mask in events:
|
||||
key.data(handler)
|
||||
176
kittens/tui/operations.py
Normal file
176
kittens/tui/operations.py
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
|
||||
from kitty.rgb import Color, color_as_sharp, to_color
|
||||
from kitty.terminfo import string_capabilities
|
||||
|
||||
S7C1T = '\033 F'
|
||||
SAVE_CURSOR = '\0337'
|
||||
RESTORE_CURSOR = '\0338'
|
||||
SAVE_PRIVATE_MODE_VALUES = '\033[?s'
|
||||
RESTORE_PRIVATE_MODE_VALUES = '\033[?r'
|
||||
|
||||
MODES = dict(
|
||||
LNM=(20, ''),
|
||||
IRM=(4, ''),
|
||||
DECKM=(1, '?'),
|
||||
DECSCNM=(5, '?'),
|
||||
DECOM=(6, '?'),
|
||||
DECAWM=(7, '?'),
|
||||
DECARM=(8, '?'),
|
||||
DECTCEM=(25, '?'),
|
||||
MOUSE_BUTTON_TRACKING=(1000, '?'),
|
||||
MOUSE_MOTION_TRACKING=(1002, '?'),
|
||||
MOUSE_MOVE_TRACKING=(1003, '?'),
|
||||
FOCUS_TRACKING=(1004, '?'),
|
||||
MOUSE_UTF8_MODE=(1005, '?'),
|
||||
MOUSE_SGR_MODE=(1006, '?'),
|
||||
MOUSE_URXVT_MODE=(1015, '?'),
|
||||
ALTERNATE_SCREEN=(1049, '?'),
|
||||
BRACKETED_PASTE=(2004, '?'),
|
||||
EXTENDED_KEYBOARD=(2017, '?'),
|
||||
)
|
||||
|
||||
|
||||
def set_mode(which, private=True):
|
||||
num, private = MODES[which]
|
||||
return '\033[{}{}h'.format(private, num)
|
||||
|
||||
|
||||
def reset_mode(which):
|
||||
num, private = MODES[which]
|
||||
return '\033[{}{}l'.format(private, num)
|
||||
|
||||
|
||||
def clear_screen():
|
||||
return string_capabilities['clear'].replace(r'\E', '\033')
|
||||
|
||||
|
||||
def set_window_title(value):
|
||||
return ('\033]2;' + value.replace('\033', '').replace('\x9c', '') + '\033\\')
|
||||
|
||||
|
||||
def set_line_wrapping(yes_or_no):
|
||||
return (set_mode if yes_or_no else reset_mode)('DECAWM')
|
||||
|
||||
|
||||
def set_cursor_visible(yes_or_no):
|
||||
return (set_mode if yes_or_no else reset_mode)('DECTCEM')
|
||||
|
||||
|
||||
STANDARD_COLORS = {name: i for i, name in enumerate(
|
||||
'black red green yellow blue magenta cyan gray'.split())}
|
||||
UNDERLINE_STYLES = {name: i + 1 for i, name in enumerate(
|
||||
'straight double curly'.split())}
|
||||
|
||||
|
||||
def color_code(color, intense=False, base=30):
|
||||
if isinstance(color, str):
|
||||
e = str((base + 60 if intense else base) + STANDARD_COLORS[color])
|
||||
elif isinstance(color, int):
|
||||
e = '{}:5:{}'.format(base + 8, max(0, min(color, 255)))
|
||||
else:
|
||||
e = '{}:2:{}:{}:{}'.format(base + 8, *color)
|
||||
return e
|
||||
|
||||
|
||||
def sgr(*parts):
|
||||
return '\033[{}m'.format(';'.join(parts))
|
||||
|
||||
|
||||
def colored(text, color, intense=False, reset_to=None, reset_to_intense=False):
|
||||
e = color_code(color, intense)
|
||||
return '\033[{}m{}\033[{}m'.format(e, text, 39 if reset_to is None else color_code(reset_to, reset_to_intense))
|
||||
|
||||
|
||||
def faint(text):
|
||||
return colored(text, 'black', True)
|
||||
|
||||
|
||||
def styled(text, fg=None, bg=None, fg_intense=False, bg_intense=False, italic=None, bold=None, underline=None, underline_color=None, reverse=None):
|
||||
start, end = [], []
|
||||
if fg is not None:
|
||||
start.append(color_code(fg, fg_intense))
|
||||
end.append('39')
|
||||
if bg is not None:
|
||||
start.append(color_code(bg, bg_intense, 40))
|
||||
end.append('49')
|
||||
if underline_color is not None:
|
||||
if isinstance(underline_color, str):
|
||||
underline_color = STANDARD_COLORS[underline_color]
|
||||
start.append(color_code(underline_color, base=50))
|
||||
end.append('59')
|
||||
if underline is not None:
|
||||
start.append('4:{}'.format(UNDERLINE_STYLES[underline]))
|
||||
end.append('4:0')
|
||||
if italic is not None:
|
||||
s, e = (start, end) if italic else (end, start)
|
||||
s.append('3'), e.append('23')
|
||||
if bold is not None:
|
||||
s, e = (start, end) if bold else (end, start)
|
||||
s.append('1'), e.append('22')
|
||||
if reverse is not None:
|
||||
s, e = (start, end) if reverse else (end, start)
|
||||
s.append('7'), e.append('27')
|
||||
if not start:
|
||||
return text
|
||||
return '\033[{}m{}\033[{}m'.format(';'.join(start), text, ';'.join(end))
|
||||
|
||||
|
||||
def init_state(alternate_screen=True):
|
||||
ans = (
|
||||
S7C1T + SAVE_CURSOR + SAVE_PRIVATE_MODE_VALUES + reset_mode('LNM') +
|
||||
reset_mode('IRM') + reset_mode('DECKM') + reset_mode('DECSCNM') +
|
||||
set_mode('DECARM') + reset_mode('DECOM') + set_mode('DECAWM') +
|
||||
set_mode('DECTCEM') + reset_mode('MOUSE_BUTTON_TRACKING') +
|
||||
reset_mode('MOUSE_MOTION_TRACKING') + reset_mode('MOUSE_MOVE_TRACKING')
|
||||
+ reset_mode('FOCUS_TRACKING') + reset_mode('MOUSE_UTF8_MODE') +
|
||||
reset_mode('MOUSE_SGR_MODE') + reset_mode('MOUSE_UTF8_MODE') +
|
||||
set_mode('BRACKETED_PASTE') + set_mode('EXTENDED_KEYBOARD') +
|
||||
'\033[*x' # reset DECSACE to default region select
|
||||
)
|
||||
if alternate_screen:
|
||||
ans += set_mode('ALTERNATE_SCREEN')
|
||||
ans += clear_screen()
|
||||
return ans
|
||||
|
||||
|
||||
def reset_state(normal_screen=True):
|
||||
ans = ''
|
||||
if normal_screen:
|
||||
ans += reset_mode('ALTERNATE_SCREEN')
|
||||
ans += RESTORE_PRIVATE_MODE_VALUES
|
||||
ans += RESTORE_CURSOR
|
||||
return ans
|
||||
|
||||
|
||||
@contextmanager
|
||||
def cursor(write):
|
||||
write(SAVE_CURSOR)
|
||||
yield
|
||||
write(RESTORE_CURSOR)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def alternate_screen(f=None):
|
||||
f = f or sys.stdout
|
||||
print(set_mode('ALTERNATE_SCREEN'), end='', file=f)
|
||||
yield
|
||||
print(reset_mode('ALTERNATE_SCREEN'), end='', file=f)
|
||||
|
||||
|
||||
def set_default_colors(fg=None, bg=None):
|
||||
ans = ''
|
||||
if fg is None:
|
||||
ans += '\x1b]110\x1b\\'
|
||||
else:
|
||||
ans += '\x1b]10;{}\x1b\\'.format(color_as_sharp(fg if isinstance(fg, Color) else to_color(fg)))
|
||||
if bg is None:
|
||||
ans += '\x1b]111\x1b\\'
|
||||
else:
|
||||
ans += '\x1b]11;{}\x1b\\'.format(color_as_sharp(bg if isinstance(bg, Color) else to_color(bg)))
|
||||
return ans
|
||||
0
kittens/unicode_input/__init__.py
Normal file
0
kittens/unicode_input/__init__.py
Normal file
485
kittens/unicode_input/main.py
Normal file
485
kittens/unicode_input/main.py
Normal file
@@ -0,0 +1,485 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import string
|
||||
import subprocess
|
||||
from functools import lru_cache
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.config import cached_values_for
|
||||
from kitty.constants import config_dir, editor
|
||||
from kitty.fast_data_types import wcswidth
|
||||
from kitty.key_encoding import (
|
||||
DOWN, ESCAPE, F1, F2, F3, F4, F12, LEFT, RELEASE, RIGHT, SHIFT, TAB, UP,
|
||||
backspace_key, enter_key
|
||||
)
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, color_code, colored, cursor, faint, set_line_wrapping,
|
||||
set_window_title, sgr, styled
|
||||
)
|
||||
|
||||
HEX, NAME, EMOTICONS, FAVORITES = 'HEX', 'NAME', 'EMOTICONS', 'FAVORITES'
|
||||
favorites_path = os.path.join(config_dir, 'unicode-input-favorites.conf')
|
||||
INDEX_CHAR = '.'
|
||||
DEFAULT_SET = tuple(map(
|
||||
ord,
|
||||
'‘’“”‹›«»‚„' '😀😛😇😈😉😍😎😮👍👎' '—–§¶†‡©®™' '→⇒•·°±−×÷¼½½¾'
|
||||
'…µ¢£€¿¡¨´¸ˆ˜' 'ÀÁÂÃÄÅÆÇÈÉÊË' 'ÌÍÎÏÐÑÒÓÔÕÖØ' 'ŒŠÙÚÛÜÝŸÞßàá' 'âãäåæçèéêëìí'
|
||||
'îïðñòóôõöøœš' 'ùúûüýÿþªºαΩ∞'
|
||||
))
|
||||
EMOTICONS_SET = tuple(range(0x1f600, 0x1f64f + 1))
|
||||
|
||||
|
||||
def codepoint_ok(code):
|
||||
return not (code <= 32 or code == 127 or 128 <= code <= 159 or 0xd800 <= code <= 0xdbff or 0xDC00 <= code <= 0xDFFF)
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def points_for_word(w):
|
||||
from .unicode_names import codepoints_for_word
|
||||
return codepoints_for_word(w.lower())
|
||||
|
||||
|
||||
@lru_cache(maxsize=4096)
|
||||
def name(cp):
|
||||
from .unicode_names import name_for_codepoint
|
||||
if isinstance(cp, str):
|
||||
cp = ord(cp[0])
|
||||
return (name_for_codepoint(cp) or '').capitalize()
|
||||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def codepoints_matching_search(parts):
|
||||
ans = []
|
||||
if parts and parts[0] and len(parts[0]) > 1:
|
||||
codepoints = points_for_word(parts[0])
|
||||
for word in parts[1:]:
|
||||
pts = points_for_word(word)
|
||||
if pts:
|
||||
intersection = codepoints & pts
|
||||
if intersection:
|
||||
codepoints = intersection
|
||||
continue
|
||||
codepoints = {c for c in codepoints if word in name(c).lower()}
|
||||
if codepoints:
|
||||
ans = list(sorted(codepoints))
|
||||
return ans
|
||||
|
||||
|
||||
def parse_favorites(raw):
|
||||
for line in raw.splitlines():
|
||||
line = line.strip()
|
||||
if line.startswith('#') or not line:
|
||||
continue
|
||||
idx = line.find('#')
|
||||
if idx > -1:
|
||||
line = line[:idx]
|
||||
code_text = line.partition(' ')[0]
|
||||
try:
|
||||
code = int(code_text, 16)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
if codepoint_ok(code):
|
||||
yield code
|
||||
|
||||
|
||||
def serialize_favorites(favorites):
|
||||
ans = '''\
|
||||
# Favorite characters for unicode input
|
||||
# Enter the hex code for each favorite character on a new line. Blank lines are
|
||||
# ignored and anything after a # is considered a comment.
|
||||
|
||||
'''.splitlines()
|
||||
for cp in favorites:
|
||||
ans.append('{:x} # {} {}'.format(cp, chr(cp), name(cp)))
|
||||
return '\n'.join(ans)
|
||||
|
||||
|
||||
def load_favorites(refresh=False):
|
||||
ans = getattr(load_favorites, 'ans', None)
|
||||
if ans is None or refresh:
|
||||
try:
|
||||
with open(favorites_path, 'rb') as f:
|
||||
raw = f.read().decode('utf-8')
|
||||
ans = load_favorites.ans = list(parse_favorites(raw)) or list(DEFAULT_SET)
|
||||
except FileNotFoundError:
|
||||
ans = load_favorites.ans = list(DEFAULT_SET)
|
||||
return ans
|
||||
|
||||
|
||||
def encode_hint(num, digits=string.digits + string.ascii_lowercase):
|
||||
res = ''
|
||||
d = len(digits)
|
||||
while not res or num > 0:
|
||||
num, i = divmod(num, d)
|
||||
res = digits[i] + res
|
||||
return res
|
||||
|
||||
|
||||
def decode_hint(x):
|
||||
return int(x, 36)
|
||||
|
||||
|
||||
class Table:
|
||||
|
||||
def __init__(self):
|
||||
self.layout_dirty = True
|
||||
self.last_rows = self.last_cols = -1
|
||||
self.codepoints = []
|
||||
self.current_idx = 0
|
||||
self.text = ''
|
||||
self.num_cols = 0
|
||||
self.mode = HEX
|
||||
|
||||
@property
|
||||
def current_codepoint(self):
|
||||
if self.codepoints:
|
||||
return self.codepoints[self.current_idx]
|
||||
|
||||
def set_codepoints(self, codepoints, mode=HEX):
|
||||
self.codepoints = codepoints
|
||||
self.mode = mode
|
||||
self.layout_dirty = True
|
||||
self.current_idx = 0
|
||||
|
||||
def codepoint_at_hint(self, hint):
|
||||
return self.codepoints[decode_hint(hint)]
|
||||
|
||||
def layout(self, rows, cols):
|
||||
if not self.layout_dirty and self.last_cols == cols and self.last_rows == rows:
|
||||
return self.text
|
||||
self.last_cols, self.last_rows = cols, rows
|
||||
self.layout_dirty = False
|
||||
|
||||
if self.mode is NAME:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), name(codepoint)
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
is_current = i == self.current_idx
|
||||
if is_current:
|
||||
yield sgr(color_code('gray', base=40))
|
||||
yield colored(idx, 'green') + ' '
|
||||
yield colored(c, 'black' if is_current else 'gray', True) + ' '
|
||||
w = wcswidth(c)
|
||||
if w < 2:
|
||||
yield ' ' * (2 - w)
|
||||
if len(desc) > space_for_desc:
|
||||
desc = desc[:space_for_desc - 1] + '…'
|
||||
yield faint(desc)
|
||||
extra = space_for_desc - len(desc)
|
||||
if extra > 0:
|
||||
yield ' ' * extra
|
||||
if is_current:
|
||||
yield sgr('49')
|
||||
else:
|
||||
def as_parts(i, codepoint):
|
||||
return encode_hint(i).ljust(idx_size), chr(codepoint), ''
|
||||
|
||||
def cell(i, idx, c, desc):
|
||||
yield colored(idx, 'green') + ' '
|
||||
yield colored(c, 'gray', True)
|
||||
w = wcswidth(c)
|
||||
if w < 2:
|
||||
yield ' ' * (2 - w)
|
||||
|
||||
num = len(self.codepoints)
|
||||
if num < 1:
|
||||
self.text = ''
|
||||
self.num_cols = 0
|
||||
return self.text
|
||||
idx_size = len(encode_hint(num - 1))
|
||||
|
||||
parts = [as_parts(i, c) for i, c in enumerate(self.codepoints)]
|
||||
if self.mode is NAME:
|
||||
sizes = [idx_size + 2 + len(p[2]) + 2 for p in parts]
|
||||
else:
|
||||
sizes = [idx_size + 3]
|
||||
longest = max(sizes) if sizes else 0
|
||||
col_width = longest + 2
|
||||
col_width = min(col_width, 40)
|
||||
space_for_desc = col_width - 2 - idx_size - 4
|
||||
num_cols = self.num_cols = max(cols // col_width, 1)
|
||||
buf = []
|
||||
a = buf.append
|
||||
rows_left = rows
|
||||
|
||||
for i, (idx, c, desc) in enumerate(parts):
|
||||
if i > 0 and i % num_cols == 0:
|
||||
rows_left -= 1
|
||||
if rows_left == 0:
|
||||
break
|
||||
buf.append('\r\n')
|
||||
buf.extend(cell(i, idx, c, desc))
|
||||
a(' ')
|
||||
self.text = ''.join(buf)
|
||||
return self.text
|
||||
|
||||
def move_current(self, rows=0, cols=0):
|
||||
if len(self.codepoints) == 0:
|
||||
return
|
||||
if cols:
|
||||
self.current_idx = (self.current_idx + len(self.codepoints) + cols) % len(self.codepoints)
|
||||
self.layout_dirty = True
|
||||
if rows:
|
||||
amt = rows * self.num_cols
|
||||
self.current_idx += amt
|
||||
self.current_idx = max(0, min(self.current_idx, len(self.codepoints) - 1))
|
||||
self.layout_dirty = True
|
||||
|
||||
|
||||
def is_index(w):
|
||||
try:
|
||||
int(w.lstrip(INDEX_CHAR), 16)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
class UnicodeInput(Handler):
|
||||
|
||||
def __init__(self, cached_values):
|
||||
self.cached_values = cached_values
|
||||
self.recent = list(self.cached_values.get('recent', DEFAULT_SET))
|
||||
self.current_input = ''
|
||||
self.current_char = None
|
||||
self.prompt_template = '{}> '
|
||||
self.last_updated_code_point_at = None
|
||||
self.choice_line = ''
|
||||
self.mode = globals().get(cached_values.get('mode', 'HEX'), 'HEX')
|
||||
self.table = Table()
|
||||
self.update_prompt()
|
||||
|
||||
def update_codepoints(self):
|
||||
codepoints = None
|
||||
if self.mode is HEX:
|
||||
q = self.mode, None
|
||||
codepoints = self.recent
|
||||
elif self.mode is EMOTICONS:
|
||||
q = self.mode, None
|
||||
codepoints = list(EMOTICONS_SET)
|
||||
elif self.mode is FAVORITES:
|
||||
codepoints = load_favorites()
|
||||
q = self.mode, tuple(codepoints)
|
||||
elif self.mode is NAME:
|
||||
q = self.mode, self.current_input
|
||||
if q != self.last_updated_code_point_at:
|
||||
words = self.current_input.split()
|
||||
words = [w for w in words if w != INDEX_CHAR]
|
||||
index_words = [i for i, w in enumerate(words) if i > 0 and is_index(w)]
|
||||
if index_words:
|
||||
index_word = words[index_words[0]]
|
||||
words = words[:index_words[0]]
|
||||
codepoints = codepoints_matching_search(tuple(words))
|
||||
if index_words:
|
||||
index_word = int(index_word.lstrip(INDEX_CHAR), 16)
|
||||
if index_word < len(codepoints):
|
||||
codepoints = [codepoints[index_word]]
|
||||
if q != self.last_updated_code_point_at:
|
||||
self.last_updated_code_point_at = q
|
||||
self.table.set_codepoints(codepoints, self.mode)
|
||||
|
||||
def update_current_char(self):
|
||||
self.update_codepoints()
|
||||
self.current_char = None
|
||||
if self.mode is HEX:
|
||||
try:
|
||||
if self.current_input.startswith(INDEX_CHAR) and len(self.current_input) > 1:
|
||||
self.current_char = chr(self.table.codepoint_at_hint(self.current_input[1:]))
|
||||
else:
|
||||
code = int(self.current_input, 16)
|
||||
self.current_char = chr(code)
|
||||
except Exception:
|
||||
pass
|
||||
elif self.mode is NAME:
|
||||
cc = self.table.current_codepoint
|
||||
if cc:
|
||||
self.current_char = chr(cc)
|
||||
else:
|
||||
try:
|
||||
if self.current_input:
|
||||
self.current_char = chr(self.table.codepoint_at_hint(self.current_input.lstrip(INDEX_CHAR)))
|
||||
except Exception:
|
||||
pass
|
||||
if self.current_char is not None:
|
||||
code = ord(self.current_char)
|
||||
if not codepoint_ok(code):
|
||||
self.current_char = None
|
||||
|
||||
def update_prompt(self):
|
||||
self.update_current_char()
|
||||
if self.current_char is None:
|
||||
c, color = '??', 'red'
|
||||
self.choice_line = ''
|
||||
else:
|
||||
c, color = self.current_char, 'green'
|
||||
self.choice_line = _('Chosen:') + ' {} U+{} {}'.format(
|
||||
colored(c, 'green'), hex(ord(c))[2:], faint(styled(name(c) or '', italic=True)))
|
||||
self.prompt = self.prompt_template.format(colored(c, color))
|
||||
|
||||
def init_terminal_state(self):
|
||||
self.write(set_line_wrapping(False))
|
||||
self.write(set_window_title(_('Unicode input')))
|
||||
|
||||
def initialize(self):
|
||||
self.init_terminal_state()
|
||||
self.draw_screen()
|
||||
|
||||
def draw_title_bar(self):
|
||||
entries = []
|
||||
for name, key, mode in [
|
||||
(_('Code'), 'F1', HEX),
|
||||
(_('Name'), 'F2', NAME),
|
||||
(_('Emoji'), 'F3', EMOTICONS),
|
||||
(_('Favorites'), 'F4', FAVORITES),
|
||||
]:
|
||||
entry = ' {} ({}) '.format(name, key)
|
||||
if mode is self.mode:
|
||||
entry = styled(entry, reverse=False, bold=True)
|
||||
entries.append(entry)
|
||||
text = _('Search by:{}').format(' '.join(entries))
|
||||
extra = self.screen_size.cols - wcswidth(text)
|
||||
if extra > 0:
|
||||
text += ' ' * extra
|
||||
self.print(styled(text, reverse=True))
|
||||
|
||||
def draw_screen(self):
|
||||
self.write(clear_screen())
|
||||
self.draw_title_bar()
|
||||
y = 1
|
||||
|
||||
def writeln(text=''):
|
||||
nonlocal y
|
||||
self.print(text)
|
||||
y += 1
|
||||
|
||||
if self.mode is NAME:
|
||||
writeln(_('Enter words from the name of the character'))
|
||||
elif self.mode is HEX:
|
||||
writeln(_('Enter the hex code for the character'))
|
||||
else:
|
||||
writeln(_('Enter the index for the character you want from the list below'))
|
||||
self.write(self.prompt)
|
||||
self.write(self.current_input)
|
||||
with cursor(self.write):
|
||||
writeln()
|
||||
writeln(self.choice_line)
|
||||
if self.mode is HEX:
|
||||
writeln(faint(_('Type {} followed by the index for the recent entries below').format(INDEX_CHAR)))
|
||||
elif self.mode is NAME:
|
||||
writeln(faint(_('Use Tab or the arrow keys to choose a character from below')))
|
||||
elif self.mode is FAVORITES:
|
||||
writeln(faint(_('Press F12 to edit the list of favorites')))
|
||||
self.table_at = y
|
||||
self.write(self.table.layout(self.screen_size.rows - self.table_at, self.screen_size.cols))
|
||||
|
||||
def refresh(self):
|
||||
self.update_prompt()
|
||||
self.draw_screen()
|
||||
|
||||
def on_text(self, text, in_bracketed_paste):
|
||||
self.current_input += text
|
||||
self.refresh()
|
||||
|
||||
def on_key(self, key_event):
|
||||
if key_event is backspace_key:
|
||||
self.current_input = self.current_input[:-1]
|
||||
self.refresh()
|
||||
elif key_event is enter_key:
|
||||
self.quit_loop(0)
|
||||
elif key_event.type is RELEASE and not key_event.mods:
|
||||
if key_event.key is ESCAPE:
|
||||
self.quit_loop(1)
|
||||
elif key_event.key is F1:
|
||||
self.switch_mode(HEX)
|
||||
elif key_event.key is F2:
|
||||
self.switch_mode(NAME)
|
||||
elif key_event.key is F3:
|
||||
self.switch_mode(EMOTICONS)
|
||||
elif key_event.key is F4:
|
||||
self.switch_mode(FAVORITES)
|
||||
elif key_event.key is F12 and self.mode is FAVORITES:
|
||||
self.edit_favorites()
|
||||
elif self.mode is NAME:
|
||||
if key_event.key is TAB:
|
||||
if key_event.mods == SHIFT:
|
||||
self.table.move_current(cols=-1), self.refresh()
|
||||
elif not key_event.mods:
|
||||
self.table.move_current(cols=1), self.refresh()
|
||||
elif key_event.key is LEFT and not key_event.mods:
|
||||
self.table.move_current(cols=-1), self.refresh()
|
||||
elif key_event.key is RIGHT and not key_event.mods:
|
||||
self.table.move_current(cols=1), self.refresh()
|
||||
elif key_event.key is UP and not key_event.mods:
|
||||
self.table.move_current(rows=-1), self.refresh()
|
||||
elif key_event.key is DOWN and not key_event.mods:
|
||||
self.table.move_current(rows=1), self.refresh()
|
||||
|
||||
def edit_favorites(self):
|
||||
if not os.path.exists(favorites_path):
|
||||
with open(favorites_path, 'wb') as f:
|
||||
f.write(serialize_favorites(load_favorites()).encode('utf-8'))
|
||||
with self.suspend():
|
||||
p = subprocess.Popen(editor + [favorites_path])
|
||||
if p.wait() == 0:
|
||||
load_favorites(refresh=True)
|
||||
self.init_terminal_state()
|
||||
self.refresh()
|
||||
|
||||
def switch_mode(self, mode):
|
||||
if mode is not self.mode:
|
||||
self.mode = mode
|
||||
self.cached_values['mode'] = mode
|
||||
self.current_input = ''
|
||||
self.current_char = None
|
||||
self.choice_line = ''
|
||||
self.refresh()
|
||||
|
||||
def on_interrupt(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_eot(self):
|
||||
self.quit_loop(1)
|
||||
|
||||
def on_resize(self, new_size):
|
||||
Handler.on_resize(self, new_size)
|
||||
self.refresh()
|
||||
|
||||
|
||||
def main(args):
|
||||
loop = Loop()
|
||||
with cached_values_for('unicode-input') as cached_values:
|
||||
handler = UnicodeInput(cached_values)
|
||||
loop.loop(handler)
|
||||
if handler.current_char and loop.return_code == 0:
|
||||
try:
|
||||
handler.recent.remove(ord(handler.current_char))
|
||||
except Exception:
|
||||
pass
|
||||
recent = [ord(handler.current_char)] + handler.recent
|
||||
cached_values['recent'] = recent[:len(DEFAULT_SET)]
|
||||
return handler.current_char
|
||||
if loop.return_code != 0:
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
def handle_result(args, current_char, target_window_id, boss):
|
||||
w = boss.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
w.paste(current_char)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
if '-h' in sys.argv or '--help' in sys.argv:
|
||||
print('Choose a unicode character to input into the terminal')
|
||||
raise SystemExit(0)
|
||||
ans = main(sys.argv)
|
||||
if ans:
|
||||
print(ans)
|
||||
64658
kittens/unicode_input/names.h
generated
Normal file
64658
kittens/unicode_input/names.h
generated
Normal file
File diff suppressed because one or more lines are too long
121
kittens/unicode_input/unicode_names.c
Normal file
121
kittens/unicode_input/unicode_names.c
Normal file
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* unicode_names.c
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "names.h"
|
||||
|
||||
static PyObject*
|
||||
all_words(PYNOARG) {
|
||||
PyObject *ans = PyTuple_New(arraysz(all_words_map));
|
||||
if (!ans) return NULL;
|
||||
for (size_t i = 0; i < arraysz(all_words_map); i++) {
|
||||
PyObject *w = PyUnicode_FromString(all_words_map[i]);
|
||||
if (w == NULL) { Py_DECREF(ans); return NULL; }
|
||||
PyTuple_SET_ITEM(ans, i, w);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline void
|
||||
add_matches(const word_trie *wt, char_type *codepoints, size_t *pos, const size_t sz) {
|
||||
size_t num = mark_groups[wt->match_offset];
|
||||
for (size_t i = wt->match_offset + 1; i < wt->match_offset + 1 + num && *pos < sz; i++, (*pos)++) {
|
||||
codepoints[*pos] = mark_to_cp[mark_groups[i]];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
process_trie_node(const word_trie *wt, char_type *codepoints, size_t *pos, const size_t sz) {
|
||||
if (wt->match_offset) add_matches(wt, codepoints, pos, sz);
|
||||
size_t num_children = children_array[wt->children_offset];
|
||||
if (!num_children) return;
|
||||
for (size_t c = wt->children_offset + 1; c < wt->children_offset + 1 + num_children; c++) {
|
||||
if (*pos > sz) return;
|
||||
uint32_t x = children_array[c];
|
||||
process_trie_node(&all_trie_nodes[x >> 8], codepoints, pos, sz);
|
||||
}
|
||||
}
|
||||
|
||||
static inline PyObject*
|
||||
codepoints_for_word(const char *word, size_t len) {
|
||||
const word_trie *wt = all_trie_nodes;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
unsigned char ch = word[i];
|
||||
size_t num_children = children_array[wt->children_offset];
|
||||
if (!num_children) return PyFrozenSet_New(NULL);
|
||||
bool found = false;
|
||||
for (size_t c = wt->children_offset + 1; c < wt->children_offset + 1 + num_children; c++) {
|
||||
uint32_t x = children_array[c];
|
||||
if ((x & 0xff) == ch) {
|
||||
found = true;
|
||||
wt = &all_trie_nodes[x >> 8];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return PyFrozenSet_New(NULL);
|
||||
}
|
||||
static char_type codepoints[1024];
|
||||
size_t cpos = 0;
|
||||
process_trie_node(wt, codepoints, &cpos, arraysz(codepoints));
|
||||
PyObject *ans = PyFrozenSet_New(NULL); if (ans == NULL) return NULL;
|
||||
for (size_t i = 0; i < cpos; i++) {
|
||||
PyObject *t = PyLong_FromUnsignedLong(codepoints[i]); if (t == NULL) { Py_DECREF(ans); return NULL; }
|
||||
int ret = PySet_Add(ans, t); Py_DECREF(t); if (ret != 0) { Py_DECREF(ans); return NULL; }
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cfw(PyObject *self UNUSED, PyObject *args) {
|
||||
const char *word;
|
||||
if (!PyArg_ParseTuple(args, "s", &word)) return NULL;
|
||||
return codepoints_for_word(word, strlen(word));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
nfc(PyObject *self UNUSED, PyObject *args) {
|
||||
unsigned int cp;
|
||||
if (!PyArg_ParseTuple(args, "I", &cp)) return NULL;
|
||||
const char *n = name_for_codepoint(cp);
|
||||
if (n == NULL) Py_RETURN_NONE;
|
||||
return PyUnicode_FromString(n);
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"all_words", (PyCFunction)all_words, METH_NOARGS, ""},
|
||||
{"codepoints_for_word", (PyCFunction)cfw, METH_VARARGS, ""},
|
||||
{"name_for_codepoint", (PyCFunction)nfc, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03000000
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "unicode_names", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
};
|
||||
|
||||
|
||||
EXPORTED PyMODINIT_FUNC
|
||||
PyInit_unicode_names(void) {
|
||||
PyObject *m;
|
||||
|
||||
m = PyModule_Create(&module);
|
||||
if (m == NULL) return NULL;
|
||||
return m;
|
||||
}
|
||||
#else
|
||||
EXPORTED
|
||||
initunicode_names(void) {
|
||||
PyObject *m;
|
||||
m = Py_InitModule3("unicode_names", module_methods,
|
||||
""
|
||||
);
|
||||
if (m == NULL) return;
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,9 @@
|
||||
#version GLSL_VERSION
|
||||
uniform uvec2 viewport;
|
||||
uniform vec3 default_bg;
|
||||
uniform vec3 active_border_color;
|
||||
uniform vec3 inactive_border_color;
|
||||
uniform vec3 bell_border_color;
|
||||
in uvec4 rect; // left, top, right, bottom
|
||||
in uint rect_color;
|
||||
out vec3 color;
|
||||
@@ -9,6 +13,7 @@ const int LEFT = 0;
|
||||
const int TOP = 1;
|
||||
const int RIGHT = 2;
|
||||
const int BOTTOM = 3;
|
||||
const uint FF = uint(0xff);
|
||||
|
||||
const uvec2 pos_map[] = uvec2[4](
|
||||
uvec2(RIGHT, TOP),
|
||||
@@ -25,11 +30,13 @@ vec2 to_opengl(uint x, uint y) {
|
||||
}
|
||||
|
||||
float to_color(uint c) {
|
||||
return float(c & uint(0xff)) / 255.0;
|
||||
return float(c & FF) / 255.0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
uvec2 pos = pos_map[gl_VertexID];
|
||||
gl_Position = vec4(to_opengl(rect[pos.x], rect[pos.y]), 0, 1);
|
||||
color = vec3(to_color(rect_color >> 16), to_color(rect_color >> 8), to_color(rect_color));
|
||||
int rc = int(rect_color);
|
||||
vec3 window_bg = vec3(to_color(rect_color >> 24), to_color(rect_color >> 16), to_color(rect_color >> 8));
|
||||
color = float(1 & rc) * default_bg + float((2 & rc) >> 1) * active_border_color + float((4 & rc) >> 2) * inactive_border_color + float((8 & rc) >> 3) * window_bg + float((16 & rc) >> 4) * bell_border_color;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ from .fast_data_types import (
|
||||
BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
|
||||
pt_to_px
|
||||
)
|
||||
from .utils import color_as_int, load_shaders
|
||||
from .utils import load_shaders
|
||||
|
||||
|
||||
def vertical_edge(os_window_id, tab_id, color, width, top, bottom, left):
|
||||
@@ -44,9 +44,6 @@ class Borders:
|
||||
self.tab_id = tab_id
|
||||
self.border_width = pt_to_px(opts.window_border_width)
|
||||
self.padding_width = pt_to_px(opts.window_padding_width)
|
||||
self.background = color_as_int(opts.background)
|
||||
self.active_border = color_as_int(opts.active_border_color)
|
||||
self.inactive_border = color_as_int(opts.inactive_border_color)
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
@@ -56,9 +53,9 @@ class Borders:
|
||||
extra_blank_rects,
|
||||
draw_window_borders=True
|
||||
):
|
||||
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 0)
|
||||
add_borders_rect(self.os_window_id, self.tab_id, 0, 0, 0, 0, 1)
|
||||
for br in chain(current_layout.blank_rects, extra_blank_rects):
|
||||
add_borders_rect(self.os_window_id, self.tab_id, *br, self.background)
|
||||
add_borders_rect(self.os_window_id, self.tab_id, *br, 1)
|
||||
bw, pw = self.border_width, self.padding_width
|
||||
fw = bw + pw
|
||||
|
||||
@@ -67,7 +64,7 @@ class Borders:
|
||||
g = w.geometry
|
||||
if bw > 0 and draw_window_borders:
|
||||
# Draw the border rectangles
|
||||
color = self.active_border if w is active_window else self.inactive_border
|
||||
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,
|
||||
@@ -75,9 +72,9 @@ class Borders:
|
||||
)
|
||||
if pw > 0:
|
||||
# Draw the background rectangles over the padding region
|
||||
color = self.background
|
||||
color = w.screen.color_profile.default_bg
|
||||
border(
|
||||
self.os_window_id, self.tab_id,
|
||||
color, pw, g.left - pw, g.top - pw, g.right + pw,
|
||||
(color << 8) | 8, pw, g.left - pw, g.top - pw, g.right + pw,
|
||||
g.bottom + pw
|
||||
)
|
||||
|
||||
379
kitty/boss.py
379
kitty/boss.py
@@ -2,28 +2,37 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import atexit
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from functools import partial
|
||||
from gettext import gettext as _
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from .cli import create_opts, parse_args
|
||||
from .config import MINIMUM_FONT_SIZE, cached_values, initial_window_size
|
||||
from .constants import appname, set_boss, wakeup
|
||||
from .config import (
|
||||
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
|
||||
)
|
||||
from .config_utils import to_cmdline
|
||||
from .constants import appname, config_dir, editor, set_boss
|
||||
from .fast_data_types import (
|
||||
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
|
||||
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
|
||||
layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
|
||||
set_dpi_from_os_window, show_window, toggle_fullscreen,
|
||||
viewport_for_window
|
||||
set_dpi_from_os_window, set_in_sequence_mode, show_window,
|
||||
toggle_fullscreen, viewport_for_window
|
||||
)
|
||||
from .fonts.render import prerender, resize_fonts, set_font_family
|
||||
from .keys import get_shortcut
|
||||
from .keys import get_shortcut, shortcut_matches
|
||||
from .remote_control import handle_cmd
|
||||
from .rgb import Color, color_from_int
|
||||
from .session import create_session
|
||||
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
|
||||
from .utils import (
|
||||
end_startup_notification, get_primary_selection, init_startup_notification,
|
||||
open_url, safe_print, set_primary_selection, single_instance
|
||||
log_error, open_url, parse_address_spec, remove_socket_file, safe_print,
|
||||
set_primary_selection, single_instance
|
||||
)
|
||||
|
||||
|
||||
@@ -32,6 +41,16 @@ def initialize_renderer():
|
||||
prerender()
|
||||
|
||||
|
||||
def listen_on(spec):
|
||||
import socket
|
||||
family, address, socket_path = parse_address_spec(spec)
|
||||
s = socket.socket(family)
|
||||
atexit.register(remove_socket_file, s, socket_path)
|
||||
s.bind(address)
|
||||
s.listen()
|
||||
return s.fileno()
|
||||
|
||||
|
||||
class DumpCommands: # {{{
|
||||
|
||||
def __init__(self, args):
|
||||
@@ -61,17 +80,23 @@ class DumpCommands: # {{{
|
||||
|
||||
class Boss:
|
||||
|
||||
def __init__(self, os_window_id, opts, args):
|
||||
def __init__(self, os_window_id, opts, args, cached_values):
|
||||
self.window_id_map = WeakValueDictionary()
|
||||
self.startup_colors = {k: opts[k] for k in opts if isinstance(opts[k], Color)}
|
||||
self.pending_sequences = None
|
||||
self.cached_values = cached_values
|
||||
self.os_window_map = {}
|
||||
self.cursor_blinking = True
|
||||
self.shutting_down = False
|
||||
talk_fd = getattr(single_instance, 'socket', None)
|
||||
talk_fd = -1 if talk_fd is None else talk_fd.fileno()
|
||||
listen_fd = -1
|
||||
if opts.allow_remote_control and args.listen_on:
|
||||
listen_fd = listen_on(args.listen_on)
|
||||
self.child_monitor = ChildMonitor(
|
||||
self.on_child_death,
|
||||
DumpCommands(args) if args.dump_commands or args.dump_bytes else None,
|
||||
talk_fd
|
||||
talk_fd, listen_fd
|
||||
)
|
||||
set_boss(self)
|
||||
self.current_font_size = opts.font_size
|
||||
@@ -84,7 +109,7 @@ class Boss:
|
||||
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, startup_id=None):
|
||||
dpi_changed = False
|
||||
if os_window_id is None:
|
||||
w, h = initial_window_size(self.opts) if size is None else size
|
||||
w, h = initial_window_size(self.opts, self.cached_values) if size is None else size
|
||||
cls = wclass or self.args.cls or appname
|
||||
os_window_id = create_os_window(w, h, appname, wname or self.args.name or cls, cls)
|
||||
if startup_id:
|
||||
@@ -96,6 +121,7 @@ class Boss:
|
||||
self.os_window_map[os_window_id] = tm
|
||||
if dpi_changed:
|
||||
self.on_dpi_change(os_window_id)
|
||||
return os_window_id
|
||||
|
||||
def list_os_windows(self):
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
@@ -104,33 +130,58 @@ class Boss:
|
||||
'tabs': list(tm.list_tabs()),
|
||||
}
|
||||
|
||||
@property
|
||||
def all_tab_managers(self):
|
||||
yield from self.os_window_map.values()
|
||||
|
||||
@property
|
||||
def all_tabs(self):
|
||||
for tm in self.all_tab_managers:
|
||||
yield from tm
|
||||
|
||||
@property
|
||||
def all_windows(self):
|
||||
for tab in self.all_tabs:
|
||||
yield from tab
|
||||
|
||||
def match_windows(self, match):
|
||||
field, exp = match.split(':', 1)
|
||||
pat = re.compile(exp)
|
||||
for tm in self.os_window_map.values():
|
||||
for tab in tm:
|
||||
for window in tab:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
try:
|
||||
field, exp = match.split(':', 1)
|
||||
except ValueError:
|
||||
return
|
||||
if field == 'num':
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
try:
|
||||
w = tab.get_nth_window(int(exp))
|
||||
except Exception:
|
||||
return
|
||||
if w is not None:
|
||||
yield w
|
||||
else:
|
||||
pat = re.compile(exp)
|
||||
for window in self.all_windows:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
|
||||
def tab_for_window(self, window):
|
||||
for tm in self.os_window_map.values():
|
||||
for tab in tm:
|
||||
for w in tab:
|
||||
if w.id == window.id:
|
||||
return tab
|
||||
for tab in self.all_tabs:
|
||||
for w in tab:
|
||||
if w.id == window.id:
|
||||
return tab
|
||||
|
||||
def match_tabs(self, match):
|
||||
field, exp = match.split(':', 1)
|
||||
try:
|
||||
field, exp = match.split(':', 1)
|
||||
except ValueError:
|
||||
return
|
||||
pat = re.compile(exp)
|
||||
tms = tuple(self.os_window_map.values())
|
||||
found = False
|
||||
if field in ('title', 'id'):
|
||||
for tm in tms:
|
||||
for tab in tm:
|
||||
if tab.matches(field, pat):
|
||||
yield tab
|
||||
found = True
|
||||
for tab in self.all_tabs:
|
||||
if tab.matches(field, pat):
|
||||
yield tab
|
||||
found = True
|
||||
if not found:
|
||||
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
|
||||
for tab in tabs:
|
||||
@@ -150,7 +201,7 @@ class Boss:
|
||||
def _new_os_window(self, args, cwd_from=None):
|
||||
sw = self.args_to_special_window(args, cwd_from) if args else None
|
||||
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
|
||||
self.add_os_window(startup_session)
|
||||
return self.add_os_window(startup_session)
|
||||
|
||||
def new_os_window(self, *args):
|
||||
self._new_os_window(args)
|
||||
@@ -164,30 +215,45 @@ class Boss:
|
||||
self.child_monitor.add_child(window.id, window.child.pid, window.child.child_fd, window.screen)
|
||||
self.window_id_map[window.id] = window
|
||||
|
||||
def peer_messages_received(self, messages):
|
||||
import json
|
||||
for msg in messages:
|
||||
msg = json.loads(msg.decode('utf-8'))
|
||||
def _handle_remote_command(self, cmd, window=None):
|
||||
response = None
|
||||
if self.opts.allow_remote_control or getattr(window, 'allow_remote_control', False):
|
||||
try:
|
||||
response = handle_cmd(self, window, cmd)
|
||||
except Exception as err:
|
||||
import traceback
|
||||
response = {'ok': False, 'error': str(err)}
|
||||
if not getattr(err, 'hide_traceback', False):
|
||||
response['tb'] = traceback.format_exc()
|
||||
else:
|
||||
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
|
||||
return response
|
||||
|
||||
def peer_message_received(self, msg):
|
||||
msg = msg.decode('utf-8')
|
||||
cmd_prefix = '\x1bP@kitty-cmd'
|
||||
if msg.startswith(cmd_prefix):
|
||||
cmd = msg[len(cmd_prefix):-2]
|
||||
response = self._handle_remote_command(cmd)
|
||||
if response is not None:
|
||||
response = (cmd_prefix + json.dumps(response) + '\x1b\\').encode('utf-8')
|
||||
return response
|
||||
else:
|
||||
msg = json.loads(msg)
|
||||
if isinstance(msg, dict) and msg.get('cmd') == 'new_instance':
|
||||
startup_id = msg.get('startup_id')
|
||||
args, rest = parse_args(msg['args'][1:])
|
||||
args.args = rest
|
||||
opts = create_opts(args)
|
||||
session = create_session(opts, args)
|
||||
self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts), startup_id=startup_id)
|
||||
if not os.path.isabs(args.directory):
|
||||
args.directory = os.path.join(msg['cwd'], args.directory)
|
||||
session = create_session(opts, args, respect_cwd=True)
|
||||
self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts, self.cached_values), startup_id=startup_id)
|
||||
else:
|
||||
safe_print('Unknown message received from peer, ignoring')
|
||||
log_error('Unknown message received from peer, ignoring')
|
||||
|
||||
def handle_remote_cmd(self, cmd, window=None):
|
||||
response = None
|
||||
if self.opts.allow_remote_control:
|
||||
try:
|
||||
response = handle_cmd(self, window, cmd)
|
||||
except Exception as err:
|
||||
import traceback
|
||||
response = {'ok': False, 'error': str(err), 'tb': traceback.format_exc()}
|
||||
else:
|
||||
response = {'ok': False, 'error': 'Remote control is disabled. Add allow_remote_control yes to your kitty.conf'}
|
||||
response = self._handle_remote_command(cmd, window)
|
||||
if response is not None:
|
||||
if window is not None:
|
||||
window.send_cmd_response(response)
|
||||
@@ -196,6 +262,12 @@ class Boss:
|
||||
window = self.window_id_map.pop(window_id, None)
|
||||
if window is None:
|
||||
return
|
||||
if window.action_on_close:
|
||||
try:
|
||||
window.action_on_close(window)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
os_window_id = window.os_window_id
|
||||
window.destroy()
|
||||
tm = self.os_window_map.get(os_window_id)
|
||||
@@ -251,26 +323,23 @@ class Boss:
|
||||
tm.resize()
|
||||
|
||||
def increase_font_size(self):
|
||||
self.change_font_size(
|
||||
self.set_font_size(
|
||||
min(
|
||||
self.opts.font_size * 5, self.current_font_size +
|
||||
self.opts.font_size_delta))
|
||||
|
||||
def decrease_font_size(self):
|
||||
self.change_font_size(
|
||||
max(
|
||||
MINIMUM_FONT_SIZE, self.current_font_size -
|
||||
self.opts.font_size_delta))
|
||||
self.set_font_size(self.current_font_size - self.opts.font_size_delta)
|
||||
|
||||
def restore_font_size(self):
|
||||
self.change_font_size(self.opts.font_size)
|
||||
self.set_font_size(self.opts.font_size)
|
||||
|
||||
def _change_font_size(self, new_size=None):
|
||||
def _change_font_size(self, new_size=None, on_dpi_change=False):
|
||||
if new_size is not None:
|
||||
self.current_font_size = new_size
|
||||
old_cell_width, old_cell_height = viewport_for_window()[-2:]
|
||||
windows = tuple(filter(None, self.window_id_map.values()))
|
||||
resize_fonts(self.current_font_size)
|
||||
resize_fonts(self.current_font_size, on_dpi_change=on_dpi_change)
|
||||
layout_sprite_map()
|
||||
prerender()
|
||||
for window in windows:
|
||||
@@ -281,7 +350,8 @@ class Boss:
|
||||
tm.refresh_sprite_positions()
|
||||
glfw_post_empty_event()
|
||||
|
||||
def change_font_size(self, new_size):
|
||||
def set_font_size(self, new_size):
|
||||
new_size = max(MINIMUM_FONT_SIZE, new_size)
|
||||
if new_size == self.current_font_size:
|
||||
return
|
||||
self._change_font_size(new_size)
|
||||
@@ -309,8 +379,46 @@ class Boss:
|
||||
def dispatch_special_key(self, key, scancode, action, mods):
|
||||
# Handles shortcuts, return True if the key was consumed
|
||||
key_action = get_shortcut(self.opts.keymap, mods, key, scancode)
|
||||
self.current_key_press_info = key, scancode, action, mods
|
||||
return self.dispatch_action(key_action)
|
||||
if key_action is None:
|
||||
sequences = get_shortcut(self.opts.sequence_map, mods, key, scancode)
|
||||
if sequences:
|
||||
self.pending_sequences = sequences
|
||||
set_in_sequence_mode(True)
|
||||
return True
|
||||
else:
|
||||
self.current_key_press_info = key, scancode, action, mods
|
||||
return self.dispatch_action(key_action)
|
||||
|
||||
def process_sequence(self, key, scancode, action, mods):
|
||||
if not self.pending_sequences:
|
||||
set_in_sequence_mode(False)
|
||||
|
||||
remaining = {}
|
||||
matched_action = None
|
||||
for seq, key_action in self.pending_sequences.items():
|
||||
if shortcut_matches(seq[0], mods, key, scancode):
|
||||
seq = seq[1:]
|
||||
if seq:
|
||||
remaining[seq] = key_action
|
||||
else:
|
||||
matched_action = key_action
|
||||
|
||||
if remaining:
|
||||
self.pending_sequences = remaining
|
||||
else:
|
||||
self.pending_sequences = None
|
||||
set_in_sequence_mode(False)
|
||||
if matched_action is not None:
|
||||
self.dispatch_action(matched_action)
|
||||
|
||||
def default_bg_changed_for(self, window_id):
|
||||
w = self.window_id_map.get(window_id)
|
||||
if w is not None:
|
||||
tm = self.os_window_map.get(w.os_window_id)
|
||||
if tm is not None:
|
||||
t = tm.tab_for_id(w.tab_id)
|
||||
if t is not None:
|
||||
t.relayout_borders()
|
||||
|
||||
def dispatch_action(self, key_action):
|
||||
if key_action is not None:
|
||||
@@ -343,6 +451,12 @@ class Boss:
|
||||
w = tm.active_window
|
||||
if w is not None:
|
||||
w.focus_changed(focused)
|
||||
tm.mark_tab_bar_dirty()
|
||||
|
||||
def update_tab_bar_data(self, os_window_id):
|
||||
tm = self.os_window_map.get(os_window_id)
|
||||
if tm is not None:
|
||||
tm.update_tab_bar_data()
|
||||
|
||||
def on_drop(self, os_window_id, paths):
|
||||
tm = self.os_window_map.get(os_window_id)
|
||||
@@ -352,43 +466,125 @@ class Boss:
|
||||
w.paste('\n'.join(paths))
|
||||
|
||||
def on_os_window_closed(self, os_window_id, viewport_width, viewport_height):
|
||||
cached_values['window-size'] = viewport_width, viewport_height
|
||||
self.cached_values['window-size'] = viewport_width, viewport_height
|
||||
tm = self.os_window_map.pop(os_window_id, None)
|
||||
if tm is not None:
|
||||
tm.destroy()
|
||||
for window_id in tuple(w.id for w in self.window_id_map.values() if getattr(w, 'os_window_id', None) == os_window_id):
|
||||
self.window_id_map.pop(window_id, None)
|
||||
|
||||
def display_scrollback(self, data):
|
||||
if self.opts.scrollback_in_new_tab:
|
||||
self.display_scrollback_in_new_tab(data)
|
||||
else:
|
||||
def display_scrollback(self, window, data):
|
||||
tab = self.active_tab
|
||||
if tab is not None and window.overlay_for is None:
|
||||
tab.new_special_window(
|
||||
SpecialWindow(
|
||||
self.opts.scrollback_pager, data, _('History'), overlay_for=window.id))
|
||||
|
||||
def edit_config_file(self, *a):
|
||||
confpath = prepare_config_file_for_editing()
|
||||
# On macOS vim fails to handle SIGWINCH if it occurs early, so add a
|
||||
# small delay.
|
||||
cmd = ['kitty', '+runpy', 'import os, sys, time; time.sleep(0.05); os.execvp(sys.argv[1], sys.argv[1:])'] + editor + [confpath]
|
||||
self.new_os_window(*cmd)
|
||||
|
||||
def get_output(self, source_window, num_lines=1):
|
||||
output = ''
|
||||
s = source_window.screen
|
||||
if num_lines is None:
|
||||
num_lines = s.lines
|
||||
for i in range(min(num_lines, s.lines)):
|
||||
output += str(s.linebuf.line(i))
|
||||
return output
|
||||
|
||||
def _run_kitten(self, kitten, args=(), type_of_input='none'):
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
orig_args, args = list(args), list(args)
|
||||
args[0:0] = [config_dir, kitten]
|
||||
if type_of_input in ('text', 'history', 'ansi', 'ansi-history'):
|
||||
data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8')
|
||||
elif type_of_input == 'none':
|
||||
data = None
|
||||
else:
|
||||
raise ValueError('Unknown type_of_input: {}'.format(type_of_input))
|
||||
from kittens.runner import create_kitten_handler
|
||||
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||
copts = {k: self.opts[k] for k in ('select_by_word_characters', 'open_url_with')}
|
||||
overlay_window = tab.new_special_window(
|
||||
SpecialWindow(
|
||||
['kitty', '+runpy', 'from kittens.runner import main; main()'] + args,
|
||||
stdin=data,
|
||||
env={'KITTY_COMMON_OPTS': json.dumps(copts), 'PYTHONWARNINGS': 'ignore'},
|
||||
overlay_for=w.id))
|
||||
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
|
||||
|
||||
def run_kitten(self, type_of_input, kitten, *args):
|
||||
import shlex
|
||||
cmdline = args[0] if args else ''
|
||||
args = shlex.split(cmdline) if cmdline else []
|
||||
self._run_kitten(kitten, args, type_of_input)
|
||||
|
||||
def on_kitten_finish(self, target_window_id, end_kitten, source_window):
|
||||
output = self.get_output(source_window, num_lines=None)
|
||||
from kittens.runner import deserialize
|
||||
data = deserialize(output)
|
||||
if data is not None:
|
||||
end_kitten(data, target_window_id, self)
|
||||
|
||||
def input_unicode_character(self):
|
||||
self._run_kitten('unicode_input')
|
||||
|
||||
def set_tab_title(self):
|
||||
tab = self.active_tab
|
||||
if tab:
|
||||
args = ['--name=tab-title', '--message', _('Enter the new title for this tab below.'), 'do_set_tab_title', str(tab.id)]
|
||||
self._run_kitten('ask', args)
|
||||
|
||||
def do_set_tab_title(self, title, tab_id):
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None and title:
|
||||
tab_id = int(tab_id)
|
||||
for tab in tm.tabs:
|
||||
if tab.id == tab_id:
|
||||
tab.set_title(title)
|
||||
break
|
||||
|
||||
def kitty_shell(self, window_type):
|
||||
cmd = ['kitty', '@']
|
||||
if window_type == 'tab':
|
||||
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
|
||||
elif window_type == 'overlay':
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
tab.new_special_window(
|
||||
SpecialWindow(
|
||||
self.opts.scrollback_pager, data, _('History')))
|
||||
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
|
||||
else:
|
||||
window = self._new_window(cmd)
|
||||
if window is not None:
|
||||
window.allow_remote_control = True
|
||||
|
||||
def switch_focus_to(self, window_idx):
|
||||
tab = self.active_tab
|
||||
tab.set_active_window_idx(window_idx)
|
||||
old_focus = tab.active_window
|
||||
if not old_focus.destroyed:
|
||||
old_focus.focus_changed(False)
|
||||
tab.active_window.focus_changed(True)
|
||||
|
||||
def open_url(self, url):
|
||||
def open_url(self, url, program=None, cwd=None):
|
||||
if url:
|
||||
open_url(url, self.opts.open_url_with)
|
||||
if isinstance(program, str):
|
||||
program = to_cmdline(program)
|
||||
open_url(url, program or self.opts.open_url_with, cwd=cwd)
|
||||
|
||||
def open_url_lines(self, lines):
|
||||
self.open_url(''.join(lines))
|
||||
def open_url_lines(self, lines, program=None):
|
||||
self.open_url(''.join(lines), program)
|
||||
|
||||
def destroy(self):
|
||||
self.shutting_down = True
|
||||
self.child_monitor.shutdown_monitor()
|
||||
wakeup()
|
||||
self.child_monitor.join()
|
||||
del self.child_monitor
|
||||
for tm in self.os_window_map.values():
|
||||
tm.destroy()
|
||||
@@ -424,6 +620,11 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.goto_tab(tab_num - 1)
|
||||
|
||||
def set_active_tab(self, tab):
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.set_active_tab(tab)
|
||||
|
||||
def next_tab(self):
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
@@ -443,9 +644,13 @@ class Boss:
|
||||
if arg == '@selection':
|
||||
return w.text_for_selection()
|
||||
if arg == '@ansi':
|
||||
return w.buffer_as_ansi()
|
||||
return w.as_text(as_ansi=True, add_history=True)
|
||||
if arg == '@text':
|
||||
return w.buffer_as_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
|
||||
@@ -471,7 +676,7 @@ class Boss:
|
||||
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.new_tab(special_window=special_window, cwd_from=cwd_from)
|
||||
return tm.new_tab(special_window=special_window, cwd_from=cwd_from)
|
||||
|
||||
def new_tab(self, *args):
|
||||
self._new_tab(args)
|
||||
@@ -485,15 +690,17 @@ class Boss:
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
if args:
|
||||
tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
|
||||
return tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
|
||||
else:
|
||||
tab.new_window(cwd_from=cwd_from)
|
||||
return tab.new_window(cwd_from=cwd_from)
|
||||
|
||||
def new_window(self, *args):
|
||||
self._new_window(args)
|
||||
|
||||
def new_window_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
if w is None:
|
||||
return self.new_window(*args)
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
self._new_window(args, cwd_from=cwd_from)
|
||||
|
||||
@@ -507,8 +714,10 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.move_tab(-1)
|
||||
|
||||
def display_scrollback_in_new_tab(self, data):
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.new_tab(special_window=SpecialWindow(
|
||||
self.opts.scrollback_pager, data, _('History')))
|
||||
def patch_colors(self, spec, configured=False):
|
||||
if configured:
|
||||
for k, v in spec.items():
|
||||
if hasattr(self.opts, k):
|
||||
setattr(self.opts, k, color_from_int(v))
|
||||
for tm in self.all_tab_managers:
|
||||
tm.tab_bar.patch_colors(spec)
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
layout(std140) uniform CellRenderData {
|
||||
float xstart, ystart, dx, dy, sprite_dx, sprite_dy, background_opacity;
|
||||
|
||||
uint default_fg, default_bg, highlight_fg, highlight_bg, cursor_color, url_color, url_style;
|
||||
|
||||
int color1, color2;
|
||||
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;
|
||||
|
||||
@@ -21,7 +19,7 @@ layout(location=1) in uvec4 sprite_coords;
|
||||
layout(location=2) in uint is_selected;
|
||||
|
||||
|
||||
|
||||
const int fg_index_map[] = int[3](0, 1, 0);
|
||||
const uvec2 cell_pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
@@ -135,10 +133,10 @@ void main() {
|
||||
|
||||
// set cell color indices {{{
|
||||
uvec2 default_colors = uvec2(default_fg, default_bg);
|
||||
ivec2 color_indices = ivec2(color1, color2);
|
||||
uint text_attrs = sprite_coords[3];
|
||||
int fg_index = color_indices[(text_attrs >> 6) & REVERSE_MASK];
|
||||
int bg_index = color_indices[1 - fg_index];
|
||||
uint is_inverted = ((text_attrs >> 6) & REVERSE_MASK) + inverted;
|
||||
int fg_index = fg_index_map[is_inverted];
|
||||
int bg_index = 1 - fg_index;
|
||||
float cursor = is_cursor(c, r);
|
||||
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
|
||||
// }}}
|
||||
|
||||
@@ -5,16 +5,7 @@
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <pthread.h>
|
||||
// I cant figure out how to get pthread.h to include this definition on macOS. MACOSX_DEPLOYMENT_TARGET does not work.
|
||||
extern int pthread_setname_np(const char *name);
|
||||
#else
|
||||
// Need _GNU_SOURCE for pthread_setname_np on linux
|
||||
#define _GNU_SOURCE
|
||||
#include <pthread.h>
|
||||
#undef _GNU_SOURCE
|
||||
#endif
|
||||
#include "threading.h"
|
||||
#include "state.h"
|
||||
#include "screen.h"
|
||||
#include <termios.h>
|
||||
@@ -28,12 +19,17 @@ extern int pthread_setname_np(const char *name);
|
||||
extern PyTypeObject Screen_Type;
|
||||
|
||||
#define EXTRA_FDS 2
|
||||
#ifndef MSG_NOSIGNAL
|
||||
// Apple does not implement MSG_NOSIGNAL
|
||||
#define MSG_NOSIGNAL 0
|
||||
#endif
|
||||
|
||||
static void (*parse_func)(Screen*, PyObject*);
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t sz;
|
||||
int fd;
|
||||
} Message;
|
||||
|
||||
typedef struct {
|
||||
@@ -44,7 +40,7 @@ typedef struct {
|
||||
bool shutting_down;
|
||||
pthread_t io_thread, talk_thread;
|
||||
|
||||
int talk_fd;
|
||||
int talk_fd, listen_fd;
|
||||
Message *messages;
|
||||
size_t messages_capacity, messages_count;
|
||||
} ChildMonitor;
|
||||
@@ -63,6 +59,9 @@ static const Child EMPTY_CHILD = {0};
|
||||
pthread_mutex_##op(&screen->which##_buf_lock);
|
||||
#define children_mutex(op) \
|
||||
pthread_mutex_##op(&children_lock);
|
||||
#define peer_mutex(op) \
|
||||
pthread_mutex_##op(&talk_data.peer_lock);
|
||||
|
||||
|
||||
|
||||
static Child children[MAX_CHILDREN] = {{0}};
|
||||
@@ -72,23 +71,12 @@ static unsigned long remove_notify[MAX_CHILDREN] = {0};
|
||||
static size_t add_queue_count = 0, remove_queue_count = 0;
|
||||
static struct pollfd fds[MAX_CHILDREN + EXTRA_FDS] = {{0}};
|
||||
static pthread_mutex_t children_lock;
|
||||
static bool signal_received = false;
|
||||
static bool kill_signal_received = false;
|
||||
static ChildMonitor *the_monitor = NULL;
|
||||
static uint8_t drain_buf[1024];
|
||||
static int signal_fds[2], wakeup_fds[2];
|
||||
|
||||
|
||||
static inline void
|
||||
set_thread_name(const char *name) {
|
||||
int ret = 0;
|
||||
#ifdef __APPLE__
|
||||
ret = pthread_setname_np(name);
|
||||
#else
|
||||
ret = pthread_setname_np(pthread_self(), name);
|
||||
#endif
|
||||
if (ret != 0) perror("Failed to set thread name");
|
||||
}
|
||||
|
||||
|
||||
// Main thread functions {{{
|
||||
|
||||
@@ -139,11 +127,11 @@ static PyObject *
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
ChildMonitor *self;
|
||||
PyObject *dump_callback, *death_notify;
|
||||
int talk_fd = -1;
|
||||
int talk_fd = -1, listen_fd = -1;
|
||||
int ret;
|
||||
|
||||
if (the_monitor) { PyErr_SetString(PyExc_RuntimeError, "Can have only a single ChildMonitor instance"); return NULL; }
|
||||
if (!PyArg_ParseTuple(args, "OO|i", &death_notify, &dump_callback, &talk_fd)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "OO|ii", &death_notify, &dump_callback, &talk_fd, &listen_fd)) return NULL;
|
||||
if ((ret = pthread_mutex_init(&children_lock, NULL)) != 0) {
|
||||
PyErr_Format(PyExc_RuntimeError, "Failed to create children_lock mutex: %s", strerror(ret));
|
||||
return NULL;
|
||||
@@ -151,12 +139,12 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
if (!self_pipe(wakeup_fds)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (!self_pipe(signal_fds)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
struct sigaction act = {.sa_handler=handle_signal};
|
||||
if (sigaction(SIGINT, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (sigaction(SIGTERM, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (siginterrupt(SIGINT, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
if (siginterrupt(SIGTERM, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
#define SA(which) { if (sigaction(which, &act, NULL) != 0) return PyErr_SetFromErrno(PyExc_OSError); if (siginterrupt(which, false) != 0) return PyErr_SetFromErrno(PyExc_OSError);}
|
||||
SA(SIGINT); SA(SIGTERM); SA(SIGCHLD);
|
||||
#undef SA
|
||||
self = (ChildMonitor *)type->tp_alloc(type, 0);
|
||||
self->talk_fd = talk_fd;
|
||||
self->listen_fd = listen_fd;
|
||||
if (self == NULL) return PyErr_NoMemory();
|
||||
self->death_notify = death_notify; Py_INCREF(death_notify);
|
||||
if (dump_callback != Py_None) {
|
||||
@@ -205,12 +193,17 @@ wakeup_io_loop(bool in_signal_handler) {
|
||||
|
||||
static void* io_loop(void *data);
|
||||
static void* talk_loop(void *data);
|
||||
static void send_response(int fd, const char *msg, size_t msg_sz);
|
||||
static void wakeup_talk_loop(bool);
|
||||
static bool talk_thread_started = false;
|
||||
|
||||
static PyObject *
|
||||
start(ChildMonitor *self) {
|
||||
start(PyObject *s, PyObject *a UNUSED) {
|
||||
#define start_doc "start() -> Start the I/O thread"
|
||||
if (self->talk_fd > -1) {
|
||||
ChildMonitor *self = (ChildMonitor*)s;
|
||||
if (self->talk_fd > -1 || self->listen_fd > -1) {
|
||||
if (pthread_create(&self->talk_thread, NULL, talk_loop, self) != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
talk_thread_started = true;
|
||||
}
|
||||
int ret = pthread_create(&self->io_thread, NULL, io_loop, self);
|
||||
if (ret != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
@@ -218,17 +211,9 @@ start(ChildMonitor *self) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
join(ChildMonitor *self) {
|
||||
#define join_doc "join() -> Wait for the I/O thread to finish"
|
||||
int ret = pthread_join(self->io_thread, NULL);
|
||||
if (ret != 0) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
|
||||
static PyObject *
|
||||
wakeup(ChildMonitor UNUSED *self) {
|
||||
wakeup(PYNOARG) {
|
||||
#define wakeup_doc "wakeup() -> wakeup the ChildMonitor I/O thread, forcing it to exit from poll() if it is waiting there."
|
||||
wakeup_io_loop(false);
|
||||
Py_RETURN_NONE;
|
||||
@@ -266,7 +251,7 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
||||
size_t space_left = screen->write_buf_sz - screen->write_buf_used;
|
||||
if (space_left < sz) {
|
||||
if (screen->write_buf_used + sz > 100 * 1024 * 1024) {
|
||||
fprintf(stderr, "Too much data being sent to child with id: %lu, ignoring it\n", id);
|
||||
log_error("Too much data being sent to child with id: %lu, ignoring it", id);
|
||||
screen_mutex(unlock, write);
|
||||
break;
|
||||
}
|
||||
@@ -301,11 +286,21 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
shutdown_monitor(ChildMonitor *self) {
|
||||
shutdown_monitor(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
#define shutdown_monitor_doc "shutdown_monitor() -> Shutdown the monitor loop."
|
||||
signal(SIGINT, SIG_DFL);
|
||||
signal(SIGTERM, SIG_DFL);
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
self->shutting_down = true;
|
||||
wakeup_talk_loop(false);
|
||||
wakeup_io_loop(false);
|
||||
int ret = pthread_join(self->io_thread, NULL);
|
||||
if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to join() I/O thread with error: %s", strerror(ret));
|
||||
if (talk_thread_started) {
|
||||
ret = pthread_join(self->talk_thread, NULL);
|
||||
if (ret != 0) return PyErr_Format(PyExc_OSError, "Failed to join() talk thread with error: %s", strerror(ret));
|
||||
}
|
||||
talk_thread_started = false;
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -343,14 +338,14 @@ parse_input(ChildMonitor *self) {
|
||||
if (msg) {
|
||||
for (size_t i = 0; i < self->messages_count; i++) {
|
||||
Message *m = self->messages + i;
|
||||
PyTuple_SET_ITEM(msg, i, PyBytes_FromStringAndSize(m->data, m->sz));
|
||||
PyTuple_SET_ITEM(msg, i, Py_BuildValue("y#i", m->data, (int)m->sz, m->fd));
|
||||
free(m->data); m->data = NULL; m->sz = 0;
|
||||
}
|
||||
self->messages_count = 0;
|
||||
} else fatal("Out of memory");
|
||||
}
|
||||
|
||||
if (UNLIKELY(signal_received)) {
|
||||
if (UNLIKELY(kill_signal_received)) {
|
||||
global_state.close_all_windows = true;
|
||||
} else {
|
||||
count = self->count;
|
||||
@@ -361,7 +356,13 @@ parse_input(ChildMonitor *self) {
|
||||
}
|
||||
children_mutex(unlock);
|
||||
if (msg) {
|
||||
call_boss(peer_messages_received, "(O)", msg);
|
||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(msg); i++) {
|
||||
PyObject *resp = PyObject_CallMethod(global_state.boss, "peer_message_received", "O", PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 0));
|
||||
int peer_fd = (int)PyLong_AsLong(PyTuple_GET_ITEM(PyTuple_GET_ITEM(msg, i), 1));
|
||||
if (resp && PyBytes_Check(resp)) send_response(peer_fd, PyBytes_AS_STRING(resp), PyBytes_GET_SIZE(resp));
|
||||
else { send_response(peer_fd, NULL, 0); if (!resp) PyErr_Print(); }
|
||||
Py_CLEAR(resp);
|
||||
}
|
||||
Py_CLEAR(msg);
|
||||
}
|
||||
|
||||
@@ -411,7 +412,7 @@ pty_resize(int fd, struct winsize *dim) {
|
||||
if (ioctl(fd, TIOCSWINSZ, dim) == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno != EBADF && errno != ENOTTY) {
|
||||
fprintf(stderr, "Failed to resize tty associated with fd: %d with error: %s", fd, strerror(errno));
|
||||
log_error("Failed to resize tty associated with fd: %d with error: %s", fd, strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -439,7 +440,7 @@ resize_pty(ChildMonitor *self, PyObject *args) {
|
||||
if (fd == -1) FIND(add_queue, add_queue_count);
|
||||
if (fd != -1) {
|
||||
if (!pty_resize(fd, &dim)) PyErr_SetFromErrno(PyExc_OSError);
|
||||
} else fprintf(stderr, "Failed to send resize signal to child with id: %lu (children count: %u) (add queue: %zu)\n", window_id, self->count, add_queue_count);
|
||||
} else log_error("Failed to send resize signal to child with id: %lu (children count: %u) (add queue: %zu)", window_id, self->count, add_queue_count);
|
||||
children_mutex(unlock);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
@@ -560,23 +561,37 @@ simple_render_screen(PyObject UNUSED *self, PyObject *args) {
|
||||
if (vao_idx == -1) vao_idx = create_cell_vao();
|
||||
if (gvao_idx == -1) gvao_idx = create_graphics_vao();
|
||||
if (!PyArg_ParseTuple(args, "O!ffff", &Screen_Type, &screen, &xstart, &ystart, &dx, &dy)) return NULL;
|
||||
send_cell_data_to_gpu(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window());
|
||||
draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window(), true);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id) {
|
||||
static inline bool
|
||||
prepare_to_render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id, color_type *active_window_bg, unsigned int *num_visible_windows) {
|
||||
#define TD os_window->tab_bar_render_data
|
||||
bool needs_render = os_window->needs_render;
|
||||
os_window->needs_render = false;
|
||||
if (TD.screen && os_window->num_tabs > 1) {
|
||||
if (!os_window->tab_bar_data_updated) {
|
||||
call_boss(update_tab_bar_data, "K", os_window->id);
|
||||
os_window->tab_bar_data_updated = true;
|
||||
}
|
||||
if (send_cell_data_to_gpu(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window)) needs_render = true;
|
||||
}
|
||||
if (OPT(mouse_hide_wait) > 0 && now - os_window->last_mouse_activity_at > OPT(mouse_hide_wait)) hide_mouse(os_window);
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
*active_window_bg = OPT(background);
|
||||
for (unsigned int i = 0; i < tab->num_windows; i++) {
|
||||
Window *w = tab->windows + i;
|
||||
#define WD w->render_data
|
||||
if (w->visible && WD.screen) {
|
||||
*num_visible_windows += 1;
|
||||
if (w->last_drag_scroll_at > 0) {
|
||||
if (now - w->last_drag_scroll_at >= 0.02) {
|
||||
if (drag_scroll(w, os_window)) {
|
||||
w->last_drag_scroll_at = now;
|
||||
set_maximum_wait(0.02);
|
||||
needs_render = true;
|
||||
} else w->last_drag_scroll_at = 0;
|
||||
} else set_maximum_wait(now - w->last_drag_scroll_at);
|
||||
}
|
||||
@@ -584,8 +599,27 @@ render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id
|
||||
if (is_active_window) {
|
||||
*active_window_id = w->id;
|
||||
collect_cursor_info(&WD.screen->cursor_render_info, w, now, os_window);
|
||||
if (w->cursor_visible_at_last_render != WD.screen->cursor_render_info.is_visible || w->last_cursor_x != WD.screen->cursor_render_info.x || w->last_cursor_y != WD.screen->cursor_render_info.y || w->last_cursor_shape != WD.screen->cursor_render_info.shape) needs_render = true;
|
||||
update_window_title(w, os_window);
|
||||
*active_window_bg = colorprofile_to_color(WD.screen->color_profile, WD.screen->color_profile->overridden.default_bg, WD.screen->color_profile->configured.default_bg);
|
||||
} else WD.screen->cursor_render_info.is_visible = false;
|
||||
if (send_cell_data_to_gpu(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window)) needs_render = true;
|
||||
if (WD.screen->start_visual_bell_at != 0) needs_render = true;
|
||||
}
|
||||
}
|
||||
return needs_render;
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_os_window(OSWindow *os_window, double now, unsigned int active_window_id, color_type active_window_bg, unsigned int num_visible_windows) {
|
||||
Tab *tab = os_window->tabs + os_window->active_tab;
|
||||
BorderRects *br = &tab->border_rects;
|
||||
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, os_window->viewport_width, os_window->viewport_height, active_window_bg, num_visible_windows);
|
||||
if (TD.screen && os_window->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, os_window, true);
|
||||
for (unsigned int i = 0; i < tab->num_windows; i++) {
|
||||
Window *w = tab->windows + i;
|
||||
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);
|
||||
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);
|
||||
@@ -594,9 +628,16 @@ render_os_window(OSWindow *os_window, double now, unsigned int *active_window_id
|
||||
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
|
||||
set_maximum_wait(bell_left);
|
||||
}
|
||||
w->cursor_visible_at_last_render = WD.screen->cursor_render_info.is_visible; w->last_cursor_x = WD.screen->cursor_render_info.x; w->last_cursor_y = WD.screen->cursor_render_info.y; w->last_cursor_shape = WD.screen->cursor_render_info.shape;
|
||||
}
|
||||
}
|
||||
swap_window_buffers(os_window);
|
||||
br->is_dirty = false;
|
||||
os_window->last_active_tab = os_window->active_tab; os_window->last_num_tabs = os_window->num_tabs; os_window->last_active_window_id = active_window_id;
|
||||
os_window->focused_at_last_render = os_window->is_focused;
|
||||
os_window->is_damaged = false;
|
||||
#undef WD
|
||||
#undef TD
|
||||
}
|
||||
|
||||
static inline void
|
||||
@@ -606,26 +647,23 @@ render(double now) {
|
||||
set_maximum_wait(OPT(repaint_delay) - time_since_last_render);
|
||||
return;
|
||||
}
|
||||
#define TD w->tab_bar_render_data
|
||||
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
if (!w->num_tabs || !should_os_window_be_rendered(w)) continue;
|
||||
bool needs_render = w->is_damaged;
|
||||
make_os_window_context_current(w);
|
||||
if (w->viewport_size_dirty) {
|
||||
w->clear_count = 0;
|
||||
update_surface_size(w->viewport_width, w->viewport_height, w->offscreen_texture_id);
|
||||
w->viewport_size_dirty = false;
|
||||
needs_render = true;
|
||||
}
|
||||
unsigned int active_window_id = 0;
|
||||
Tab *active_tab = w->tabs + w->active_tab;
|
||||
BorderRects *br = &active_tab->border_rects;
|
||||
draw_borders(br->vao_idx, br->num_border_rects, br->rect_buf, br->is_dirty, w->viewport_width, w->viewport_height);
|
||||
if (TD.screen && w->num_tabs > 1) draw_cells(TD.vao_idx, 0, TD.xstart, TD.ystart, TD.dx, TD.dy, TD.screen, w, true);
|
||||
render_os_window(w, now, &active_window_id);
|
||||
swap_window_buffers(w);
|
||||
w->last_active_tab = w->active_tab; w->last_num_tabs = w->num_tabs; w->last_active_window_id = active_window_id;
|
||||
br->is_dirty = false;
|
||||
unsigned int active_window_id = 0, num_visible_windows = 0;
|
||||
color_type active_window_bg = 0;
|
||||
if (prepare_to_render_os_window(w, now, &active_window_id, &active_window_bg, &num_visible_windows)) needs_render = true;
|
||||
if (w->last_active_window_id != active_window_id || w->last_active_tab != w->active_tab || w->focused_at_last_render != w->is_focused) needs_render = true;
|
||||
if (needs_render) render_os_window(w, now, active_window_id, active_window_bg, num_visible_windows);
|
||||
}
|
||||
last_render_at = now;
|
||||
#undef TD
|
||||
@@ -657,7 +695,7 @@ thread_write(void *x) {
|
||||
set_thread_name("KittyWriteStdin");
|
||||
FILE *f = fdopen(data->fd, "w");
|
||||
if (fwrite(data->buf, 1, data->sz, f) != data->sz) {
|
||||
fprintf(stderr, "Failed to write all data\n");
|
||||
log_error("Failed to write all data");
|
||||
}
|
||||
fclose(f);
|
||||
free_twd(data);
|
||||
@@ -692,17 +730,41 @@ process_pending_resizes(double now) {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
if (w->has_pending_resizes) {
|
||||
if (now - w->last_resize_at >= RESIZE_DEBOUNCE_TIME) update_os_window_viewport(w, true);
|
||||
if (now - w->last_resize_event_at >= RESIZE_DEBOUNCE_TIME) update_os_window_viewport(w, true);
|
||||
else {
|
||||
global_state.has_pending_resizes = true;
|
||||
set_maximum_wait(RESIZE_DEBOUNCE_TIME - now + w->last_resize_at);
|
||||
set_maximum_wait(RESIZE_DEBOUNCE_TIME - now + w->last_resize_event_at);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
close_all_windows() {
|
||||
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
|
||||
global_state.close_all_windows = false;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
process_pending_closes(ChildMonitor *self) {
|
||||
bool has_open_windows = false;
|
||||
for (size_t w = global_state.num_os_windows; w > 0; w--) {
|
||||
OSWindow *os_window = global_state.os_windows + w - 1;
|
||||
if (should_os_window_close(os_window)) {
|
||||
destroy_os_window(os_window);
|
||||
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->window_width, os_window->window_height);
|
||||
for (size_t t=0; t < os_window->num_tabs; t++) {
|
||||
Tab *tab = os_window->tabs + t;
|
||||
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
|
||||
}
|
||||
remove_os_window(os_window->id);
|
||||
} else has_open_windows = true;
|
||||
}
|
||||
return has_open_windows;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
main_loop(ChildMonitor *self) {
|
||||
main_loop(ChildMonitor *self, PyObject *a UNUSED) {
|
||||
#define main_loop_doc "The main thread loop"
|
||||
bool has_open_windows = true;
|
||||
|
||||
@@ -712,23 +774,8 @@ main_loop(ChildMonitor *self) {
|
||||
render(now);
|
||||
wait_for_events();
|
||||
parse_input(self);
|
||||
if (global_state.close_all_windows) {
|
||||
for (size_t w = 0; w < global_state.num_os_windows; w++) mark_os_window_for_close(&global_state.os_windows[w], true);
|
||||
global_state.close_all_windows = false;
|
||||
}
|
||||
has_open_windows = false;
|
||||
for (size_t w = global_state.num_os_windows; w > 0; w--) {
|
||||
OSWindow *os_window = global_state.os_windows + w - 1;
|
||||
if (should_os_window_close(os_window)) {
|
||||
destroy_os_window(os_window);
|
||||
call_boss(on_os_window_closed, "Kii", os_window->id, os_window->viewport_width, os_window->viewport_width);
|
||||
for (size_t t=0; t < os_window->num_tabs; t++) {
|
||||
Tab *tab = os_window->tabs + t;
|
||||
for (size_t w = 0; w < tab->num_windows; w++) mark_child_for_close(self, tab->windows[w].id);
|
||||
}
|
||||
remove_os_window(os_window->id);
|
||||
} else has_open_windows = true;
|
||||
}
|
||||
if (global_state.close_all_windows) close_all_windows();
|
||||
has_open_windows = process_pending_closes(self);
|
||||
}
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
Py_RETURN_NONE;
|
||||
@@ -841,6 +888,58 @@ drain_fd(int fd) {
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
read_signals(int fd, bool *kill_signal, bool *child_died) {
|
||||
static char buf[256];
|
||||
while(true) {
|
||||
ssize_t len = read(fd, buf, sizeof(buf));
|
||||
if (len < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
if (errno != EIO) perror("Call to read() from read_signals() failed");
|
||||
break;
|
||||
}
|
||||
for (ssize_t i = 0; i < len; i++) {
|
||||
switch(buf[i]) {
|
||||
case SIGCHLD:
|
||||
*child_died = true; break;
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
*kill_signal = true; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
mark_child_for_removal(ChildMonitor *self, pid_t pid) {
|
||||
children_mutex(lock);
|
||||
for (size_t i = 0; i < self->count; i++) {
|
||||
if (children[i].pid == pid) {
|
||||
children[i].needs_removal = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
children_mutex(unlock);
|
||||
}
|
||||
|
||||
static inline void
|
||||
reap_children(ChildMonitor *self, bool enable_close_on_child_death) {
|
||||
int status;
|
||||
pid_t pid;
|
||||
(void)self;
|
||||
while(true) {
|
||||
pid = waitpid(-1, &status, WNOHANG);
|
||||
if (pid == -1) {
|
||||
if (errno != EINTR) break;
|
||||
} else if (pid > 0) {
|
||||
if (enable_close_on_child_death) mark_child_for_removal(self, pid);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
write_to_child(int fd, Screen *screen) {
|
||||
size_t written = 0;
|
||||
@@ -892,10 +991,10 @@ io_loop(void *data) {
|
||||
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
|
||||
if (fds[1].revents && POLLIN) {
|
||||
data_received = true;
|
||||
drain_fd(fds[1].fd);
|
||||
children_mutex(lock);
|
||||
signal_received = true;
|
||||
children_mutex(unlock);
|
||||
bool kill_signal = false, child_died = false;
|
||||
read_signals(fds[1].fd, &kill_signal, &child_died);
|
||||
if (kill_signal) { children_mutex(lock); kill_signal_received = true; children_mutex(unlock); }
|
||||
if (child_died) reap_children(self, OPT(close_on_child_death));
|
||||
}
|
||||
for (i = 0; i < self->count; i++) {
|
||||
if (fds[EXTRA_FDS + i].revents & (POLLIN | POLLHUP)) {
|
||||
@@ -916,7 +1015,7 @@ io_loop(void *data) {
|
||||
children_mutex(lock);
|
||||
children[i].needs_removal = true;
|
||||
children_mutex(unlock);
|
||||
fprintf(stderr, "The child %lu had its fd unexpectedly closed\n", children[i].id);
|
||||
log_error("The child %lu had its fd unexpectedly closed", children[i].id);
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_POLL_EVENTS
|
||||
@@ -943,58 +1042,258 @@ io_loop(void *data) {
|
||||
|
||||
// {{{ Talk thread functions
|
||||
|
||||
static inline void
|
||||
handle_peer(ChildMonitor *self, int s) {
|
||||
size_t bufsz = 0;
|
||||
char *buf = NULL;
|
||||
size_t buf_used = 0;
|
||||
#define MAX_PEERS 256
|
||||
#define MAX_LISTENERS 2
|
||||
|
||||
while(true) {
|
||||
if (buf_used >= bufsz) {
|
||||
bufsz = MAX(1024, bufsz) * 2;
|
||||
if (bufsz > 1024 * 1024) return;
|
||||
buf = realloc(buf, bufsz);
|
||||
if (buf == NULL) return;
|
||||
}
|
||||
ssize_t n = recv(s, buf + buf_used, bufsz - buf_used, 0);
|
||||
if (n == 0) break;
|
||||
if (n < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
perror("Error reading from talk peer");
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t capacity, used;
|
||||
int fd;
|
||||
bool finished, close_socket;
|
||||
} PeerReadData;
|
||||
static PeerReadData empty_prd = {.fd = -1, 0};
|
||||
|
||||
typedef struct {
|
||||
char *data;
|
||||
size_t sz, pos;
|
||||
int fd;
|
||||
bool finished;
|
||||
} PeerWriteData;
|
||||
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];
|
||||
int wakeup_fds[2];
|
||||
pthread_mutex_t peer_lock;
|
||||
} TalkData;
|
||||
|
||||
static TalkData talk_data = {0};
|
||||
#define nuke_socket(s) { shutdown(s, SHUT_RDWR); close(s); }
|
||||
|
||||
static inline bool
|
||||
accept_peer(int listen_fd, bool shutting_down) {
|
||||
int peer = accept(listen_fd, NULL, NULL);
|
||||
if (UNLIKELY(peer == -1)) {
|
||||
if (errno == EINTR) return true;
|
||||
if (!shutting_down) perror("accept() on talk socket failed!");
|
||||
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)) {
|
||||
talk_data.fds[fd_idx].fd = peer; talk_data.fds[fd_idx].events = POLLIN;
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
read_from_peer(ChildMonitor *self, int s) {
|
||||
bool read_finished = false;
|
||||
for (size_t i = 0; i < talk_data.num_reads; i++) {
|
||||
PeerReadData *rd = talk_data.reads + i;
|
||||
#define failed(msg) { read_finished = true; log_error("%s", msg); rd->finished = true; rd->close_socket = true; break; }
|
||||
if (rd->fd == s) {
|
||||
if (rd->used >= rd->capacity) {
|
||||
if (rd->capacity >= 1024 * 1024) failed("Ignoring too large message from peer");
|
||||
rd->capacity = MAX(8192, rd->capacity * 2);
|
||||
rd->data = realloc(rd->data, rd->capacity);
|
||||
if (!rd->data) failed("Out of memory");
|
||||
}
|
||||
ssize_t n = recv(s, rd->data + rd->used, rd->capacity - rd->used, 0);
|
||||
if (n == 0) {
|
||||
read_finished = true; rd->finished = true;
|
||||
children_mutex(lock);
|
||||
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
|
||||
Message *m = self->messages + self->messages_count++;
|
||||
m->data = rd->data; rd->data = NULL; m->sz = rd->used; m->fd = s;
|
||||
children_mutex(unlock);
|
||||
wakeup_main_loop();
|
||||
} else if (n < 0) {
|
||||
if (errno != EINTR) {
|
||||
perror("Error reading from talk peer");
|
||||
failed("");
|
||||
}
|
||||
} else rd->used += n;
|
||||
break;
|
||||
}
|
||||
buf_used += n;
|
||||
}
|
||||
if (buf_used) {
|
||||
children_mutex(lock);
|
||||
ensure_space_for(self, messages, Message, self->messages_count + 1, messages_capacity, 16, true);
|
||||
Message *m = self->messages + self->messages_count++;
|
||||
m->data = buf; m->sz = buf_used;
|
||||
children_mutex(unlock);
|
||||
wakeup_main_loop();
|
||||
} else free(buf);
|
||||
#undef failed
|
||||
return read_finished;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
write_to_peer(int fd) {
|
||||
bool write_finished = false;
|
||||
for (size_t i = 0; i < talk_data.num_writes; i++) {
|
||||
PeerWriteData *wd = talk_data.writes + i;
|
||||
#define failed(msg) { write_finished = true; log_error("%s", msg); wd->finished = true; break; }
|
||||
if (wd->fd == fd) {
|
||||
ssize_t n = send(fd, wd->data + wd->pos, wd->sz - wd->pos, MSG_NOSIGNAL);
|
||||
if (n == 0) { failed("send() to peer failed to send any data"); }
|
||||
else if (n < 0) {
|
||||
if (errno != EINTR) { perror("write() to peer socket failed with error"); failed(""); }
|
||||
} else {
|
||||
wd->pos += n;
|
||||
if (wd->pos >= wd->sz) { write_finished = true; wd->finished = true; }
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
#undef failed
|
||||
return write_finished;
|
||||
}
|
||||
|
||||
static inline void
|
||||
remove_poll_fd(int fd) {
|
||||
size_t count = talk_data.num_talk_fds + talk_data.num_listen_fds;
|
||||
for (size_t i = talk_data.num_listen_fds; i < count; i++) {
|
||||
struct pollfd *pfd = talk_data.fds + i;
|
||||
if (pfd->fd == fd) {
|
||||
size_t num_to_right = count - 1 - i;
|
||||
if (num_to_right) memmove(talk_data.fds + i, talk_data.fds + i + 1, num_to_right * sizeof(struct pollfd));
|
||||
talk_data.num_talk_fds--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
prune_finished_reads() {
|
||||
if (!talk_data.num_reads) return;
|
||||
for (ssize_t i = talk_data.num_reads - 1; i >= 0; i--) {
|
||||
PeerReadData *rd = talk_data.reads + i;
|
||||
if (rd->finished) {
|
||||
remove_poll_fd(rd->fd);
|
||||
if (rd->close_socket) { nuke_socket(rd->fd); }
|
||||
else shutdown(rd->fd, SHUT_RD);
|
||||
free(rd->data);
|
||||
ssize_t num_to_right = talk_data.num_reads - 1 - i;
|
||||
if (num_to_right > 0) memmove(talk_data.reads + i, talk_data.reads + i + 1, num_to_right * sizeof(PeerReadData));
|
||||
else talk_data.reads[i] = empty_prd;
|
||||
talk_data.num_reads--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
prune_finished_writes() {
|
||||
if (!talk_data.num_writes) return;
|
||||
for (ssize_t i = talk_data.num_writes - 1; i >= 0; i--) {
|
||||
PeerWriteData *wd = talk_data.writes + i;
|
||||
if (wd->finished) {
|
||||
remove_poll_fd(wd->fd);
|
||||
shutdown(wd->fd, SHUT_WR); close(wd->fd);
|
||||
free(wd->data);
|
||||
ssize_t num_to_right = talk_data.num_writes - 1 - i;
|
||||
if (num_to_right > 0) memmove(talk_data.writes + i, talk_data.writes + i + 1, num_to_right * sizeof(PeerWriteData));
|
||||
else talk_data.writes[i] = empty_pwd;
|
||||
talk_data.num_writes--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
wakeup_talk_loop(bool in_signal_handler) {
|
||||
if (talk_data.wakeup_fds[1] <= 0) return;
|
||||
while(true) {
|
||||
ssize_t ret = write(talk_data.wakeup_fds[1], "w", 1);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
if (!in_signal_handler) perror("Failed to write to talk wakeup fd with error");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
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)) {
|
||||
talk_data.fds[fd_idx].fd = src->fd; talk_data.fds[fd_idx].events = POLLOUT;
|
||||
talk_data.writes[talk_data.num_writes++] = *src;
|
||||
talk_data.num_talk_fds++;
|
||||
} else {
|
||||
log_error("Cannot send response to peer, too many peers");
|
||||
free(src->data); nuke_socket(src->fd);
|
||||
}
|
||||
*src = empty_pwd;
|
||||
}
|
||||
}
|
||||
|
||||
static void*
|
||||
talk_loop(void *data) {
|
||||
// The I/O thread loop
|
||||
// The talk thread loop
|
||||
|
||||
ChildMonitor *self = (ChildMonitor*)data;
|
||||
set_thread_name("KittyTalkMon");
|
||||
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; }
|
||||
#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; \
|
||||
}
|
||||
add_listener(talk_fd); add_listener(listen_fd);
|
||||
#undef add_listener
|
||||
talk_data.fds[talk_data.num_listen_fds].fd = talk_data.wakeup_fds[0]; talk_data.fds[talk_data.num_listen_fds++].events = POLLIN;
|
||||
|
||||
while (LIKELY(!self->shutting_down)) {
|
||||
int peer = accept(self->talk_fd, NULL, NULL);
|
||||
if (peer == -1) {
|
||||
if (errno == EINTR) continue;
|
||||
if (!self->shutting_down) perror("accept() on talk socket failed!");
|
||||
break;
|
||||
}
|
||||
handle_peer(self, peer);
|
||||
shutdown(peer, SHUT_RDWR);
|
||||
close(peer);
|
||||
for (size_t i = 0; i < talk_data.num_listen_fds + talk_data.num_talk_fds; i++) { talk_data.fds[i].revents = 0; }
|
||||
int ret = poll(talk_data.fds, talk_data.num_listen_fds + talk_data.num_talk_fds, -1);
|
||||
if (ret > 0) {
|
||||
bool has_finished_reads = false, has_finished_writes = false;
|
||||
for (size_t i = 0; i < talk_data.num_listen_fds - 1; i++) {
|
||||
if (talk_data.fds[i].revents & POLLIN) {if (!accept_peer(talk_data.fds[i].fd, self->shutting_down)) goto end; }
|
||||
}
|
||||
if (talk_data.fds[talk_data.num_listen_fds - 1].revents & POLLIN) drain_fd(talk_data.fds[talk_data.num_listen_fds - 1].fd); // wakeup
|
||||
for (size_t i = talk_data.num_listen_fds; i < talk_data.num_talk_fds + talk_data.num_listen_fds; i++) {
|
||||
if (talk_data.fds[i].revents & (POLLIN | POLLHUP)) { if (read_from_peer(self, talk_data.fds[i].fd)) has_finished_reads = true; }
|
||||
if (talk_data.fds[i].revents & POLLOUT) { if (write_to_peer(talk_data.fds[i].fd)) has_finished_writes = true; }
|
||||
}
|
||||
if (has_finished_reads) prune_finished_reads();
|
||||
if (has_finished_writes) prune_finished_writes();
|
||||
peer_mutex(lock);
|
||||
if (talk_data.num_queued_writes) move_queued_writes();
|
||||
peer_mutex(unlock);
|
||||
} else if (ret < 0) { if (errno != EAGAIN && errno != EINTR) perror("poll() on talk fds failed"); }
|
||||
}
|
||||
end:
|
||||
close(talk_data.wakeup_fds[0]); close(talk_data.wakeup_fds[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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)) {
|
||||
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) {
|
||||
memcpy(talk_data.queued_writes[talk_data.num_queued_writes].data, msg, msg_sz);
|
||||
talk_data.queued_writes[talk_data.num_queued_writes].sz = msg_sz;
|
||||
talk_data.queued_writes[talk_data.num_queued_writes++].fd = fd;
|
||||
ok = true;
|
||||
}
|
||||
} else log_error("Cannot send response to peer, too many peers");
|
||||
peer_mutex(unlock);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void
|
||||
send_response(int fd, const char *msg, size_t msg_sz) {
|
||||
if (msg == NULL) { shutdown(fd, SHUT_WR); close(fd); return; }
|
||||
if (!add_peer_writer(fd, msg, msg_sz)) { shutdown(fd, SHUT_WR); close(fd); }
|
||||
else wakeup_talk_loop(false);
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
// Boilerplate {{{
|
||||
@@ -1002,7 +1301,6 @@ static PyMethodDef methods[] = {
|
||||
METHOD(add_child, METH_VARARGS)
|
||||
METHOD(needs_write, METH_VARARGS)
|
||||
METHOD(start, METH_NOARGS)
|
||||
METHOD(join, METH_NOARGS)
|
||||
METHOD(wakeup, METH_NOARGS)
|
||||
METHOD(shutdown_monitor, METH_NOARGS)
|
||||
METHOD(main_loop, METH_NOARGS)
|
||||
@@ -1024,8 +1322,16 @@ PyTypeObject ChildMonitor_Type = {
|
||||
.tp_new = new,
|
||||
};
|
||||
|
||||
static PyObject*
|
||||
safe_pipe(PYNOARG) {
|
||||
int fds[2] = {0};
|
||||
if (!self_pipe(fds)) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
return Py_BuildValue("ii", fds[0], fds[1]);
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHOD(simple_render_screen, METH_VARARGS)
|
||||
METHODB(safe_pipe, METH_NOARGS),
|
||||
{NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ static PyObject*
|
||||
spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
PyObject *argv_p, *env_p;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd;
|
||||
char* cwd;
|
||||
if (!PyArg_ParseTuple(args, "sO!O!iiii", &cwd, &PyTuple_Type, &argv_p, &PyTuple_Type, &env_p, &master, &slave, &stdin_read_fd, &stdin_write_fd)) return NULL;
|
||||
char *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;
|
||||
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);
|
||||
@@ -76,7 +76,7 @@ spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
close(tfd);
|
||||
|
||||
environ = env;
|
||||
execvp(argv[0], argv);
|
||||
execvp(exe, argv);
|
||||
// Report the failure and exec a shell instead, so that we are not left
|
||||
// with a forked but not exec'ed process
|
||||
write_to_stderr("Failed to launch child: ");
|
||||
|
||||
@@ -7,7 +7,7 @@ import os
|
||||
|
||||
import kitty.fast_data_types as fast_data_types
|
||||
|
||||
from .constants import terminfo_dir, is_macos
|
||||
from .constants import is_macos, shell_path, terminfo_dir
|
||||
|
||||
|
||||
def cwd_of_process(pid):
|
||||
@@ -70,7 +70,13 @@ class Child:
|
||||
if os.path.isdir(terminfo_dir):
|
||||
env['TERMINFO'] = terminfo_dir
|
||||
env = tuple('{}={}'.format(k, v) for k, v in env.items())
|
||||
pid = fast_data_types.spawn(self.cwd, tuple(self.argv), env, master, slave, stdin_read_fd, stdin_write_fd)
|
||||
argv = list(self.argv)
|
||||
exe = argv[0]
|
||||
if is_macos and exe == shell_path:
|
||||
# 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)
|
||||
os.close(slave)
|
||||
self.pid = pid
|
||||
self.child_fd = master
|
||||
|
||||
186
kitty/cli.py
186
kitty/cli.py
@@ -2,16 +2,36 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import deque
|
||||
|
||||
from .config import load_config
|
||||
from .constants import appname, defconf, is_macos, str_version
|
||||
from .config import defaults, load_config
|
||||
from .config_utils import resolve_config
|
||||
from .constants import appname, defconf, is_macos, is_wayland, str_version
|
||||
from .layout import all_layouts
|
||||
|
||||
is_macos
|
||||
CONFIG_HELP = '''\
|
||||
Specify a path to the configuration file(s) to use. All configuration files are
|
||||
merged onto the builtin {conf_name}.conf, overriding the builtin values. This option
|
||||
can be specified multiple times to read multiple configuration files in
|
||||
sequence, which are merged. Use the special value NONE to not load a config
|
||||
file.
|
||||
|
||||
If this option is not specified, config files are searched for in the order:
|
||||
"$XDG_CONFIG_HOME/{appname}/{conf_name}.conf", "~/.config/{appname}/{conf_name}.conf", {macos_confpath}
|
||||
"$XDG_CONFIG_DIRS/{appname}/{conf_name}.conf". The first one that exists is used as the
|
||||
config file.
|
||||
|
||||
If the environment variable "KITTY_CONFIG_DIRECTORY" is specified, that
|
||||
directory is always used and the above searching does not happen.
|
||||
|
||||
If "/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}', '~/Library/Preferences/{appname}/{conf_name}.conf, ' if is_macos else '')
|
||||
|
||||
OPTIONS = '''
|
||||
--class
|
||||
dest=cls
|
||||
@@ -30,12 +50,9 @@ Set the window title. This will override any title set by the program running in
|
||||
only use this if you are running a program that does not set titles.
|
||||
|
||||
|
||||
--config
|
||||
--config -c
|
||||
type=list
|
||||
default={config_path}
|
||||
Specify a path to the configuration file(s) to use.
|
||||
Can be specified multiple times to read multiple configuration files in sequence, which are merged.
|
||||
|
||||
{config_help}
|
||||
|
||||
--override -o
|
||||
type=list
|
||||
@@ -43,10 +60,6 @@ Override individual configuration options, can be specified multiple times.
|
||||
Syntax: |_ name=value|. For example: |_ -o font_size=20|
|
||||
|
||||
|
||||
--cmd -c
|
||||
Run python code in the kitty context
|
||||
|
||||
|
||||
--directory -d
|
||||
default=.
|
||||
Change to the specified directory when launching
|
||||
@@ -84,6 +97,16 @@ with the same |_ --instance-group| will result in new windows being created
|
||||
in the first |_ {appname}| instance within that group
|
||||
|
||||
|
||||
--listen-on
|
||||
Tell kitty to listen on the specified address for control
|
||||
messages. For example, |_ --listen-on=unix:/tmp/mykitty| or
|
||||
|_ --listen-on=tcp:localhost:12345|. On Linux systems, you can also use abstract
|
||||
UNIX sockets, not associated with a file, like this: |_ --listen-on=unix:@mykitty|.
|
||||
To control kitty, you can send it commands with |_ kitty @| using the |_ --to| option
|
||||
to specify this address. Note that this option will be ignored, unless you set
|
||||
|_ allow_remote_control| to yes in |_ kitty.conf|
|
||||
|
||||
|
||||
# Debugging options
|
||||
|
||||
--version -v
|
||||
@@ -107,6 +130,21 @@ 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-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
|
||||
!
|
||||
'''
|
||||
|
||||
|
||||
@@ -186,10 +224,10 @@ def parse_option_spec(spec=OPTIONS):
|
||||
current_cmd['choices'] = {x.strip() for x in current_cmd['choices'].split(',')}
|
||||
elif state is HELP:
|
||||
if line:
|
||||
current_cmd['help'] += ' ' + line
|
||||
current_cmd['help'] += ' ' + line.lstrip()
|
||||
else:
|
||||
if prev_line:
|
||||
current_cmd['help'] += '\n'
|
||||
current_cmd['help'] += '\n\n\t'
|
||||
else:
|
||||
state = NORMAL
|
||||
(seq if current_cmd.get('condition', True) else disabled).append(current_cmd)
|
||||
@@ -213,8 +251,12 @@ def prettify(text):
|
||||
return text
|
||||
|
||||
|
||||
def version():
|
||||
return '{} {} created by {}'.format(italic(appname), green(str_version), title('Kovid Goyal'))
|
||||
def version(add_rev=False):
|
||||
rev = ''
|
||||
from . import fast_data_types
|
||||
if add_rev and hasattr(fast_data_types, 'KITTY_VCS_REV'):
|
||||
rev = ' ({})'.format(fast_data_types.KITTY_VCS_REV[:10])
|
||||
return '{} {}{} created by {}'.format(italic(appname), green(str_version), rev, title('Kovid Goyal'))
|
||||
|
||||
|
||||
def wrap(text, limit=80):
|
||||
@@ -252,6 +294,7 @@ def wrap(text, limit=80):
|
||||
|
||||
def print_help_for_seq(seq, usage, message, appname):
|
||||
from kitty.icat import screen_size
|
||||
screen_size.changed = True
|
||||
try:
|
||||
linesz = min(screen_size().cols, 76)
|
||||
except EnvironmentError:
|
||||
@@ -271,8 +314,9 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
lines.append('')
|
||||
a((' ' * leading_indent) + j.join(lines))
|
||||
|
||||
usage = usage or '[program-to-run ...]'
|
||||
a('{}: {} [options] {}'.format(title('Usage'), bold(yellow(appname)), usage))
|
||||
usage = '[program-to-run ...]' if usage is None else usage
|
||||
optstring = '[options] ' if seq else ''
|
||||
a('{}: {} {}{}'.format(title('Usage'), bold(yellow(appname)), optstring, usage))
|
||||
a('')
|
||||
message = message or (
|
||||
'Run the |G {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal'
|
||||
@@ -286,12 +330,15 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
if isinstance(opt, str):
|
||||
a('{}:'.format(title(opt)))
|
||||
continue
|
||||
help_text = opt['help']
|
||||
if help_text == '!':
|
||||
continue # hidden option
|
||||
a(' ' + ', '.join(map(green, sorted(opt['aliases']))))
|
||||
if not opt.get('type', '').startswith('bool-'):
|
||||
blocks[-1] += '={}'.format(italic(opt['dest'].upper()))
|
||||
if opt.get('help'):
|
||||
defval = opt.get('default')
|
||||
t = opt['help'].replace('%default', str(defval))
|
||||
t = help_text.replace('%default', str(defval))
|
||||
wa(prettify(t.strip()), indent=4)
|
||||
if defval is not None:
|
||||
wa('Default: {}'.format(defval), indent=4)
|
||||
@@ -300,7 +347,8 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
a('')
|
||||
|
||||
text = '\n'.join(blocks) + '\n\n' + version()
|
||||
if sys.stdout.isatty():
|
||||
if print_help_for_seq.allow_pager and sys.stdout.isatty():
|
||||
import subprocess
|
||||
p = subprocess.Popen(['less', '-isRXF'], stdin=subprocess.PIPE)
|
||||
p.communicate(text.encode('utf-8'))
|
||||
raise SystemExit(p.wait())
|
||||
@@ -308,6 +356,9 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
print(text)
|
||||
|
||||
|
||||
print_help_for_seq.allow_pager = True
|
||||
|
||||
|
||||
def defval_for_opt(opt):
|
||||
dv = opt.get('default')
|
||||
typ = opt.get('type', '')
|
||||
@@ -437,7 +488,7 @@ def parse_cmdline(oc, disabled, args=None):
|
||||
def options_spec():
|
||||
if not hasattr(options_spec, 'ans'):
|
||||
options_spec.ans = OPTIONS.format(
|
||||
appname=appname, config_path=defconf,
|
||||
appname=appname, config_help=CONFIG_HELP.format(appname=appname, conf_name=appname),
|
||||
window_layout_choices=', '.join(all_layouts)
|
||||
)
|
||||
return options_spec.ans
|
||||
@@ -450,8 +501,95 @@ def parse_args(args=None, ospec=options_spec, usage=None, message=None, appname=
|
||||
return parse_cmdline(oc, disabled, args=args)
|
||||
|
||||
|
||||
def create_opts(args):
|
||||
config = args.config or (defconf, )
|
||||
SYSTEM_CONF = '/etc/xdg/kitty/kitty.conf'
|
||||
|
||||
|
||||
def print_shortcut(key_sequence, action):
|
||||
if not getattr(print_shortcut, 'maps', None):
|
||||
from kitty.keys import defines
|
||||
v = vars(defines)
|
||||
mmap = {m[len('GLFW_MOD_'):].lower(): x for m, x in v.items() if m.startswith('GLFW_MOD_')}
|
||||
kmap = {k[len('GLFW_KEY_'):].lower(): x for k, x in v.items() if k.startswith('GLFW_KEY_')}
|
||||
krmap = {v: k for k, v in kmap.items()}
|
||||
print_shortcut.maps = mmap, krmap
|
||||
mmap, krmap = print_shortcut.maps
|
||||
keys = []
|
||||
for key in key_sequence:
|
||||
names = []
|
||||
mods, key = key
|
||||
for name, val in mmap.items():
|
||||
if mods & val:
|
||||
names.append(name)
|
||||
if key:
|
||||
names.append(krmap[key])
|
||||
keys.append('+'.join(names))
|
||||
|
||||
print('\t', ' > '.join(keys), action)
|
||||
|
||||
|
||||
def print_shortcut_changes(defns, text, changes):
|
||||
if changes:
|
||||
print(title(text))
|
||||
|
||||
for k in sorted(changes):
|
||||
print_shortcut(k, defns[k])
|
||||
|
||||
|
||||
def compare_keymaps(final, initial):
|
||||
added = set(final) - set(initial)
|
||||
removed = set(initial) - set(final)
|
||||
changed = {k for k in set(final) & set(initial) if final[k] != initial[k]}
|
||||
print_shortcut_changes(final, 'Added shortcuts:', added)
|
||||
print_shortcut_changes(initial, 'Removed shortcuts:', removed)
|
||||
print_shortcut_changes(final, 'Changed shortcuts:', changed)
|
||||
|
||||
|
||||
def flatten_sequence_map(m):
|
||||
ans = {}
|
||||
for k, rest_map in m.items():
|
||||
for r, action in rest_map.items():
|
||||
ans[(k,) + (r)] = action
|
||||
return ans
|
||||
|
||||
|
||||
def compare_opts(opts):
|
||||
print('\nConfig options different from defaults:')
|
||||
default_opts = load_config()
|
||||
|
||||
for f in sorted(defaults._fields):
|
||||
if getattr(opts, f) != getattr(defaults, f):
|
||||
if f in ('key_definitions', 'keymap', 'sequence_map'):
|
||||
continue
|
||||
print(title('{:20s}'.format(f)), getattr(opts, f))
|
||||
|
||||
final, initial = opts.keymap, default_opts.keymap
|
||||
final = {(k,): v for k, v in final.items()}
|
||||
initial = {(k,): v for k, v in initial.items()}
|
||||
final_s, initial_s = map(flatten_sequence_map, (opts.sequence_map, default_opts.sequence_map))
|
||||
final.update(final_s)
|
||||
initial.update(initial_s)
|
||||
compare_keymaps(final, initial)
|
||||
|
||||
|
||||
def create_opts(args, debug_config=False):
|
||||
config = tuple(resolve_config(SYSTEM_CONF, defconf, args.config))
|
||||
if debug_config:
|
||||
print(version(add_rev=True))
|
||||
print(' '.join(os.uname()))
|
||||
if is_macos:
|
||||
import subprocess
|
||||
print(' '.join(subprocess.check_output(['sw_vers']).decode('utf-8').splitlines()).strip())
|
||||
else:
|
||||
print('Running under:', green('Wayland' if is_wayland else 'X11'))
|
||||
if os.path.exists('/etc/issue'):
|
||||
print(open('/etc/issue', encoding='utf-8', errors='replace').read().strip())
|
||||
if os.path.exists('/etc/lsb-release'):
|
||||
print(open('/etc/lsb-release', encoding='utf-8', errors='replace').read().strip())
|
||||
config = tuple(x for x in config if os.path.exists(x))
|
||||
if config:
|
||||
print(green('Loaded config files:'), ', '.join(config))
|
||||
overrides = (a.replace('=', ' ', 1) for a in args.override or ())
|
||||
opts = load_config(*config, overrides=overrides)
|
||||
if debug_config:
|
||||
compare_opts(opts)
|
||||
return opts
|
||||
|
||||
549
kitty/cmds.py
Normal file
549
kitty/cmds.py
Normal file
@@ -0,0 +1,549 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .cli import parse_args
|
||||
from .config import parse_config, parse_send_text_bytes
|
||||
from .constants import appname
|
||||
from .tabs import SpecialWindow
|
||||
from .utils import non_blocking_read
|
||||
|
||||
|
||||
class MatchError(ValueError):
|
||||
|
||||
hide_traceback = True
|
||||
|
||||
def __init__(self, expression, target='windows'):
|
||||
ValueError.__init__(self, 'No matching {} for expression: {}'.format(target, expression))
|
||||
|
||||
|
||||
def cmd(short_desc, desc=None, options_spec=None, no_response=False, argspec='...'):
|
||||
|
||||
def w(func):
|
||||
func.short_desc = short_desc
|
||||
func.argspec = argspec
|
||||
func.desc = desc or short_desc
|
||||
func.name = func.__name__[4:].replace('_', '-')
|
||||
func.options_spec = options_spec
|
||||
func.is_cmd = True
|
||||
func.impl = lambda: globals()[func.__name__[4:]]
|
||||
func.no_response = no_response
|
||||
return func
|
||||
return w
|
||||
|
||||
|
||||
MATCH_WINDOW_OPTION = '''\
|
||||
--match -m
|
||||
The window to match. Match specifications are of the form:
|
||||
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline, num.
|
||||
You can use the |_ 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 |_ ls| command).
|
||||
'''
|
||||
MATCH_TAB_OPTION = '''\
|
||||
--match -m
|
||||
The tab to match. Match specifications are of the form:
|
||||
|_ field:regexp|. Where field can be one of: id, title, pid, cwd, cmdline.
|
||||
You can use the |_ ls| command to get a list of tabs. Note that for
|
||||
numeric fields such as id and pid the expression is interpreted as a number,
|
||||
not a regular expression. When using title or id, first a matching tab is
|
||||
looked for and if not found a matching window is looked for, and the tab
|
||||
for that window is used.
|
||||
'''
|
||||
|
||||
|
||||
# ls {{{
|
||||
@cmd(
|
||||
'List all tabs/windows',
|
||||
'List all windows. The list is returned as JSON tree. The top-level is a list of'
|
||||
' operating system {appname} windows. Each OS window has an |_ id| and a list'
|
||||
' of |_ tabs|. Each tab has its own |_ id|, a |_ title| and a list of |_ windows|.'
|
||||
' Each window has an |_ id|, |_ title|, |_ current working directory|, |_ process id (PID)| and'
|
||||
' |_ command-line| of the process running in the window.\n\n'
|
||||
'You can use these criteria to select windows/tabs for the other commands.'.format(appname=appname),
|
||||
argspec=''
|
||||
)
|
||||
def cmd_ls(global_opts, opts, args):
|
||||
pass
|
||||
|
||||
|
||||
def ls(boss, window):
|
||||
data = list(boss.list_os_windows())
|
||||
data = json.dumps(data, indent=2, sort_keys=True)
|
||||
return data
|
||||
# }}}
|
||||
|
||||
|
||||
# set_font_size {{{
|
||||
@cmd(
|
||||
'Set the font size in all windows',
|
||||
'Sets the font size to the specified size, in pts.',
|
||||
argspec='FONT_SIZE'
|
||||
)
|
||||
def cmd_set_font_size(global_opts, opts, args):
|
||||
try:
|
||||
return {'size': float(args[0])}
|
||||
except IndexError:
|
||||
raise SystemExit('No font size specified')
|
||||
|
||||
|
||||
def set_font_size(boss, window, payload):
|
||||
boss.set_font_size(payload['size'])
|
||||
# }}}
|
||||
|
||||
|
||||
# send_text {{{
|
||||
@cmd(
|
||||
'Send arbitrary text to specified windows',
|
||||
'Send arbitrary text to specified windows. The text follows Python'
|
||||
' escaping rules. So you can use escapes like |_ \\x1b| to send control codes'
|
||||
' and |_ \\u21fa| to send unicode characters. If you use the |_ --match| option'
|
||||
' the text will be sent to all matched windows. By default, text is sent to'
|
||||
' only the currently active window.',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
--stdin
|
||||
type=bool-set
|
||||
Read the text to be sent from |_ stdin|. Note that in this case the text is sent as is,
|
||||
not interpreted for escapes. If stdin is a terminal, you can press Ctrl-D to end reading.
|
||||
|
||||
|
||||
--from-file
|
||||
Path to a file whose contents you wish to send. Note that in this case the file contents
|
||||
are sent as is, not interpreted for escapes.
|
||||
''',
|
||||
no_response=True,
|
||||
argspec='[TEXT TO SEND]'
|
||||
)
|
||||
def cmd_send_text(global_opts, opts, args):
|
||||
limit = 1024
|
||||
ret = {'match': opts.match, 'is_binary': False}
|
||||
|
||||
def pipe(src=sys.stdin):
|
||||
ret['is_binary'] = True
|
||||
import select
|
||||
with non_blocking_read() as fd:
|
||||
keep_going = True
|
||||
while keep_going:
|
||||
rd = select.select([fd], [], [])
|
||||
if rd:
|
||||
data = sys.stdin.buffer.read()
|
||||
if not data:
|
||||
break
|
||||
data = data.decode('utf-8')
|
||||
if '\x04' in data:
|
||||
data = data[:data.index('\x04')]
|
||||
keep_going = False
|
||||
while data:
|
||||
ret['text'] = data[:limit]
|
||||
yield ret
|
||||
data = data[limit:]
|
||||
else:
|
||||
break
|
||||
|
||||
def chunks(text):
|
||||
ret['is_binary'] = False
|
||||
while text:
|
||||
ret['text'] = text[:limit]
|
||||
yield ret
|
||||
text = text[limit:]
|
||||
|
||||
def file_pipe(path):
|
||||
ret['is_binary'] = True
|
||||
with open(path, encoding='utf-8') as f:
|
||||
while True:
|
||||
data = f.read(limit)
|
||||
if not data:
|
||||
break
|
||||
ret['text'] = data
|
||||
yield ret
|
||||
|
||||
sources = []
|
||||
if opts.stdin:
|
||||
sources.append(pipe())
|
||||
|
||||
if opts.from_file:
|
||||
sources.append(file_pipe(opts.from_file))
|
||||
|
||||
text = ' '.join(args)
|
||||
sources.append(chunks(text))
|
||||
|
||||
def chain():
|
||||
for src in sources:
|
||||
yield from src
|
||||
return chain()
|
||||
|
||||
|
||||
def send_text(boss, window, payload):
|
||||
windows = [boss.active_window]
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
data = payload['text'].encode('utf-8') if payload['is_binary'] else parse_send_text_bytes(payload['text'])
|
||||
for window in windows:
|
||||
if window is not None:
|
||||
window.write_to_child(data)
|
||||
# }}}
|
||||
|
||||
|
||||
# set_window_title {{{
|
||||
@cmd(
|
||||
'Set the window title',
|
||||
'Set the title for the specified window(s). If you use the |_ --match| option'
|
||||
' the title will be set for all matched windows. By default, only the window'
|
||||
' in which the command is run is affected. If you do not specify a title, the'
|
||||
' last title set by the child process running in the window will be used.',
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='TITLE ...'
|
||||
)
|
||||
def cmd_set_window_title(global_opts, opts, args):
|
||||
return {'title': ' '.join(args), 'match': opts.match}
|
||||
|
||||
|
||||
def set_window_title(boss, window, payload):
|
||||
windows = [window or boss.active_window]
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
window.set_title(payload['title'])
|
||||
# }}}
|
||||
|
||||
|
||||
# set_tab_title {{{
|
||||
@cmd(
|
||||
'Set the tab title',
|
||||
'Set the title for the specified tab(s). If you use the |_ --match| option'
|
||||
' the title will be set for all matched tabs. By default, only the tab'
|
||||
' in which the command is run is affected. If you do not specify a title, the'
|
||||
' title of the currently active window in the tab is used.',
|
||||
options_spec=MATCH_TAB_OPTION,
|
||||
argspec='TITLE ...'
|
||||
)
|
||||
def cmd_set_tab_title(global_opts, opts, args):
|
||||
return {'title': ' '.join(args), 'match': opts.match}
|
||||
|
||||
|
||||
def set_tab_title(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
tab.set_title(payload['title'])
|
||||
# }}}
|
||||
|
||||
|
||||
# close_window {{{
|
||||
@cmd(
|
||||
'Close the specified window(s)',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the window this command is run in, rather than the active window.
|
||||
''',
|
||||
argspec=''
|
||||
)
|
||||
def cmd_close_window(global_opts, opts, args):
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
|
||||
|
||||
def close_window(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload['self'] else boss.active_window]
|
||||
for window in windows:
|
||||
if window:
|
||||
boss.close_window(window)
|
||||
# }}}
|
||||
|
||||
|
||||
# close_tab {{{
|
||||
@cmd(
|
||||
'Close the specified tab(s)',
|
||||
options_spec=MATCH_TAB_OPTION + '''\n
|
||||
--self
|
||||
type=bool-set
|
||||
If specified close the tab this command is run in, rather than the active tab.
|
||||
''',
|
||||
argspec=''
|
||||
)
|
||||
def cmd_close_tab(global_opts, opts, args):
|
||||
return {'match': opts.match, 'self': opts.self}
|
||||
|
||||
|
||||
def close_tab(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.tab_for_window(window) if window and payload['self'] else boss.active_tab]
|
||||
for tab in tabs:
|
||||
if window:
|
||||
if tab:
|
||||
boss.close_tab(tab)
|
||||
# }}}
|
||||
|
||||
|
||||
# new_window {{{
|
||||
@cmd(
|
||||
'Open new window',
|
||||
'Open a new window in the specified tab. If you use the |_ --match| option'
|
||||
' the first matching tab is used. Otherwise the currently active tab is used.'
|
||||
' Prints out the id of the newly opened window. Any command line arguments'
|
||||
' are assumed to be the command line used to run in the new window, if none'
|
||||
' are provided, the default shell is run. For example:\n'
|
||||
'|_ kitty @ new-window --title Email mutt|',
|
||||
options_spec=MATCH_TAB_OPTION + '''\n
|
||||
--title
|
||||
The title for the new window. By default it will use the title set by the
|
||||
program running in it.
|
||||
|
||||
|
||||
--cwd
|
||||
The initial working directory for the new window.
|
||||
|
||||
|
||||
--keep-focus
|
||||
type=bool-set
|
||||
Keep the current window focused instead of switching to the newly opened window
|
||||
|
||||
|
||||
--new-tab
|
||||
type=bool-set
|
||||
Open a new tab
|
||||
|
||||
|
||||
--tab-title
|
||||
When using --new-tab set the title of the tab.
|
||||
''',
|
||||
argspec='[CMD ...]'
|
||||
)
|
||||
def cmd_new_window(global_opts, opts, args):
|
||||
return {'match': opts.match, 'title': opts.title, 'cwd': opts.cwd,
|
||||
'new_tab': opts.new_tab, 'tab_title': opts.tab_title,
|
||||
'keep_focus': opts.keep_focus, 'args': args or []}
|
||||
|
||||
|
||||
def new_window(boss, window, payload):
|
||||
w = SpecialWindow(cmd=payload['args'] or None, override_title=payload['title'], cwd=payload['cwd'])
|
||||
old_window = boss.active_window
|
||||
if payload['new_tab']:
|
||||
boss._new_tab(w)
|
||||
tab = boss.active_tab
|
||||
if payload['tab_title']:
|
||||
tab.set_title(payload['tab_title'])
|
||||
wid = boss.active_window.id
|
||||
if payload['keep_focus'] and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return str(wid)
|
||||
|
||||
match = payload['match']
|
||||
if match:
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
else:
|
||||
tabs = [boss.active_tab]
|
||||
tab = tabs[0]
|
||||
w = tab.new_special_window(w)
|
||||
if payload['keep_focus'] and old_window:
|
||||
boss.set_active_window(old_window)
|
||||
return str(w.id)
|
||||
# }}}
|
||||
|
||||
|
||||
# focus_window {{{
|
||||
@cmd(
|
||||
'Focus the specified window',
|
||||
options_spec=MATCH_WINDOW_OPTION,
|
||||
argspec='',
|
||||
)
|
||||
def cmd_focus_window(global_opts, opts, args):
|
||||
return {'match': opts.match}
|
||||
|
||||
|
||||
def focus_window(boss, window, payload):
|
||||
windows = [window or boss.active_window]
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
for window in windows:
|
||||
if window:
|
||||
boss.set_active_window(window)
|
||||
break
|
||||
# }}}
|
||||
|
||||
|
||||
# focus_tab {{{
|
||||
@cmd(
|
||||
'Focus the specified tab',
|
||||
'The active window in the specified tab will be focused.',
|
||||
options_spec=MATCH_TAB_OPTION,
|
||||
argspec='',
|
||||
)
|
||||
def cmd_focus_tab(global_opts, opts, args):
|
||||
return {'match': opts.match}
|
||||
|
||||
|
||||
def focus_tab(boss, window, payload):
|
||||
match = payload['match']
|
||||
tabs = tuple(boss.match_tabs(match))
|
||||
if not tabs:
|
||||
raise MatchError(match, 'tabs')
|
||||
tab = tabs[0]
|
||||
boss.set_active_tab(tab)
|
||||
# }}}
|
||||
|
||||
|
||||
# get_text {{{
|
||||
@cmd(
|
||||
'Get text from the specified window',
|
||||
options_spec=MATCH_WINDOW_OPTION + '''\n
|
||||
--extent
|
||||
default=screen
|
||||
choices=screen, all, selection
|
||||
What text to get. The default of screen means all text currently on the screen. all means
|
||||
all the screen+scrollback and selection means currently selected text.
|
||||
|
||||
|
||||
--ansi
|
||||
type=bool-set
|
||||
By default, only plain text is returned. If you specify this flag, the text will
|
||||
include the formatting escape codes for colors/bold/italic/etc. Note that when
|
||||
getting the current selection, the result is always plain text.
|
||||
|
||||
|
||||
--self
|
||||
type=bool-set
|
||||
If specified get text from the window this command is run in, rather than the active window.
|
||||
''',
|
||||
argspec=''
|
||||
)
|
||||
def cmd_get_text(global_opts, opts, args):
|
||||
return {'match': opts.match, 'extent': opts.extent, 'ansi': opts.ansi, 'self': opts.self}
|
||||
|
||||
|
||||
def get_text(boss, window, payload):
|
||||
match = payload['match']
|
||||
if match:
|
||||
windows = tuple(boss.match_windows(match))
|
||||
if not windows:
|
||||
raise MatchError(match)
|
||||
else:
|
||||
windows = [window if window and payload['self'] else boss.active_window]
|
||||
window = windows[0]
|
||||
if payload['extent'] == 'selection':
|
||||
ans = window.text_for_selection()
|
||||
else:
|
||||
ans = window.as_text(as_ansi=bool(payload['ansi']), add_history=True)
|
||||
return ans
|
||||
# }}}
|
||||
|
||||
|
||||
# set_colors {{{
|
||||
@cmd(
|
||||
'Set terminal colors',
|
||||
'Set the terminal colors for the specified windows/tabs (defaults to active window). You can either specify the path to a conf file'
|
||||
' (in the same format as kitty.conf) to read the colors from or you can specify individual colors,'
|
||||
' for example: kitty @ set-colors foreground=red background=white',
|
||||
options_spec='''\
|
||||
--all -a
|
||||
type=bool-set
|
||||
By default, colors are only changed for the currently active window. This option will
|
||||
cause colors to be changed in all windows.
|
||||
|
||||
|
||||
--configured -c
|
||||
type=bool-set
|
||||
Also change the configured colors (i.e. the colors kitty will use for new
|
||||
windows or after a reset).
|
||||
|
||||
|
||||
--reset
|
||||
type=bool-set
|
||||
Restore all colors to the values they had at kitty startup. Note that if you specify
|
||||
this option, any color arguments are ignored and --configured and --all are implied.
|
||||
''' + '\n\n' + MATCH_WINDOW_OPTION + '\n\n' + MATCH_TAB_OPTION.replace('--match -m', '--match-tab -t'),
|
||||
argspec='COLOR_OR_FILE ...'
|
||||
)
|
||||
def cmd_set_colors(global_opts, opts, args):
|
||||
from .rgb import color_as_int, Color
|
||||
colors = {}
|
||||
if not opts.reset:
|
||||
for spec in args:
|
||||
if '=' in spec:
|
||||
colors.update(parse_config((spec.replace('=', ' '),)))
|
||||
else:
|
||||
with open(os.path.expanduser(spec), encoding='utf-8', errors='replace') as f:
|
||||
colors.update(parse_config(f))
|
||||
colors = {k: color_as_int(v) for k, v in colors.items() if isinstance(v, Color)}
|
||||
return {
|
||||
'title': ' '.join(args), 'match_window': opts.match, 'match_tab': opts.match_tab,
|
||||
'all': opts.all or opts.reset, 'configured': opts.configured or opts.reset, 'colors': colors, 'reset': opts.reset
|
||||
}
|
||||
|
||||
|
||||
def set_colors(boss, window, payload):
|
||||
from .rgb import color_as_int
|
||||
if payload['all']:
|
||||
windows = tuple(boss.all_windows)
|
||||
else:
|
||||
windows = (window or boss.active_window,)
|
||||
if payload['match_window']:
|
||||
windows = tuple(boss.match_windows(payload['match_window']))
|
||||
if not windows:
|
||||
raise MatchError(payload['match_window'])
|
||||
if payload['match_tab']:
|
||||
tabs = tuple(boss.match_tabs(payload['match_tab']))
|
||||
if not tabs:
|
||||
raise MatchError(payload['match_tab'], 'tabs')
|
||||
for tab in tabs:
|
||||
windows += tuple(tab)
|
||||
if payload['reset']:
|
||||
payload['colors'] = {k: color_as_int(v) for k, v in boss.startup_colors.items()}
|
||||
profiles = tuple(w.screen.color_profile for w in windows)
|
||||
from .fast_data_types import patch_color_profiles
|
||||
patch_color_profiles(payload['colors'], profiles, payload['configured'])
|
||||
boss.patch_colors(payload['colors'], payload['configured'])
|
||||
default_bg_changed = 'background' in payload['colors']
|
||||
for w in windows:
|
||||
if default_bg_changed:
|
||||
boss.default_bg_changed_for(w.id)
|
||||
w.refresh()
|
||||
# }}}
|
||||
|
||||
|
||||
cmap = {v.name: v for v in globals().values() if hasattr(v, 'is_cmd')}
|
||||
|
||||
|
||||
def parse_subcommand_cli(func, args):
|
||||
opts, items = parse_args(args[1:], (func.options_spec or '\n').format, func.argspec, func.desc, '{} @ {}'.format(appname, func.name))
|
||||
return opts, items
|
||||
|
||||
|
||||
def display_subcommand_help(func):
|
||||
try:
|
||||
parse_args(['--help'], (func.options_spec or '\n').format, func.argspec, func.desc, func.name)
|
||||
except SystemExit:
|
||||
pass
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#include "data-types.h"
|
||||
#include "state.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
@@ -24,6 +24,7 @@ typedef void* rusage_info_t; // needed for libproc.h
|
||||
|
||||
static NSMenuItem* title_menu = NULL;
|
||||
|
||||
|
||||
static NSString*
|
||||
find_app_name(void) {
|
||||
size_t i;
|
||||
@@ -56,11 +57,35 @@ find_app_name(void) {
|
||||
return @"kitty";
|
||||
}
|
||||
|
||||
@interface GlobalMenuTarget : NSObject
|
||||
+ (GlobalMenuTarget *) shared_instance;
|
||||
@end
|
||||
|
||||
@implementation GlobalMenuTarget
|
||||
|
||||
- (void) show_preferences : (id)sender {
|
||||
(void)sender;
|
||||
call_boss(edit_config_file, NULL);
|
||||
}
|
||||
|
||||
+ (GlobalMenuTarget *) shared_instance
|
||||
{
|
||||
static GlobalMenuTarget *sharedGlobalMenuTarget = nil;
|
||||
@synchronized(self)
|
||||
{
|
||||
if (!sharedGlobalMenuTarget)
|
||||
sharedGlobalMenuTarget = [[GlobalMenuTarget alloc] init];
|
||||
return sharedGlobalMenuTarget;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
void
|
||||
cocoa_create_global_menu(void) {
|
||||
NSString* app_name = find_app_name();
|
||||
NSMenu* bar = [[NSMenu alloc] init];
|
||||
GlobalMenuTarget *global_menu_target = [GlobalMenuTarget shared_instance];
|
||||
[NSApp setMainMenu:bar];
|
||||
|
||||
NSMenuItem* appMenuItem =
|
||||
@@ -72,6 +97,9 @@ cocoa_create_global_menu(void) {
|
||||
action:@selector(orderFrontStandardAboutPanel:)
|
||||
keyEquivalent:@""];
|
||||
[appMenu addItem:[NSMenuItem separatorItem]];
|
||||
NSMenuItem* preferences_menu_item = [[NSMenuItem alloc] initWithTitle:@"Preferences..." action:@selector(show_preferences:) keyEquivalent:@","];
|
||||
[preferences_menu_item setTarget:global_menu_target];
|
||||
[appMenu addItem:preferences_menu_item];
|
||||
[appMenu addItemWithTitle:[NSString stringWithFormat:@"Hide %@", app_name]
|
||||
action:@selector(hide:)
|
||||
keyEquivalent:@"h"];
|
||||
@@ -121,7 +149,7 @@ cocoa_create_global_menu(void) {
|
||||
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
|
||||
[NSApp setWindowsMenu:windowMenu];
|
||||
[windowMenu release];
|
||||
|
||||
[preferences_menu_item release];
|
||||
|
||||
[bar release];
|
||||
}
|
||||
@@ -154,7 +182,7 @@ cocoa_make_window_resizable(void *w) {
|
||||
return true;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
static PyObject*
|
||||
cocoa_get_lang(PyObject UNUSED *self) {
|
||||
NSString* locale = nil;
|
||||
NSString* lang_code = [[NSLocale currentLocale] objectForKey:NSLocaleLanguageCode];
|
||||
@@ -168,7 +196,7 @@ cocoa_get_lang(PyObject UNUSED *self) {
|
||||
return Py_BuildValue("s", [locale UTF8String]);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
static PyObject*
|
||||
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
long pid = PyLong_AsLong(pid_);
|
||||
struct proc_vnodepathinfo vpi;
|
||||
@@ -177,6 +205,36 @@ cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_set_hide_from_tasks(void) {
|
||||
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_set_titlebar_color(void *w, color_type titlebar_color)
|
||||
{
|
||||
NSWindow *window = (NSWindow *)w;
|
||||
|
||||
double red = ((titlebar_color >> 16) & 0xFF) / 255.0;
|
||||
double green = ((titlebar_color >> 8) & 0xFF) / 255.0;
|
||||
double blue = (titlebar_color & 0xFF) / 255.0;
|
||||
|
||||
NSColor *background =
|
||||
[NSColor colorWithSRGBRed:red
|
||||
green:green
|
||||
blue:blue
|
||||
alpha:1.0];
|
||||
[window setTitlebarAppearsTransparent:YES];
|
||||
[window setBackgroundColor:background];
|
||||
|
||||
double luma = 0.2126 * red + 0.7152 * green + 0.0722 * blue;
|
||||
|
||||
if (luma < 0.5) {
|
||||
[window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
|
||||
} else {
|
||||
[window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
|
||||
}
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#define EXTRA_INIT if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
#include "data-types.h"
|
||||
#include <structmember.h>
|
||||
|
||||
@@ -29,23 +30,29 @@ static uint32_t FG_BG_256[256] = {
|
||||
0xffffff, // 15
|
||||
};
|
||||
|
||||
PyObject* create_256_color_table() {
|
||||
// colors 16..232: the 6x6x6 color cube
|
||||
const uint8_t valuerange[6] = {0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
|
||||
uint8_t i, j=16;
|
||||
for(i = 0; i < 217; i++, j++) {
|
||||
uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6];
|
||||
FG_BG_256[j] = (r << 16) | (g << 8) | b;
|
||||
}
|
||||
// colors 233..255: grayscale
|
||||
for(i = 1; i < 24; i++, j++) {
|
||||
uint8_t v = 8 + i * 10;
|
||||
FG_BG_256[j] = (v << 16) | (v << 8) | v;
|
||||
static inline void
|
||||
init_FG_BG_table() {
|
||||
if (UNLIKELY(FG_BG_256[255] == 0)) {
|
||||
// colors 16..232: the 6x6x6 color cube
|
||||
const uint8_t valuerange[6] = {0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff};
|
||||
uint8_t i, j=16;
|
||||
for(i = 0; i < 217; i++, j++) {
|
||||
uint8_t r = valuerange[(i / 36) % 6], g = valuerange[(i / 6) % 6], b = valuerange[i % 6];
|
||||
FG_BG_256[j] = (r << 16) | (g << 8) | b;
|
||||
}
|
||||
// colors 233..255: grayscale
|
||||
for(i = 1; i < 24; i++, j++) {
|
||||
uint8_t v = 8 + i * 10;
|
||||
FG_BG_256[j] = (v << 16) | (v << 8) | v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyObject *ans = PyTuple_New(255);
|
||||
PyObject* create_256_color_table() {
|
||||
init_FG_BG_table();
|
||||
PyObject *ans = PyTuple_New(arraysz(FG_BG_256));
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
for (i=0; i < 255; i++) {
|
||||
for (size_t i=0; i < arraysz(FG_BG_256); i++) {
|
||||
PyObject *temp = PyLong_FromUnsignedLong(FG_BG_256[i]);
|
||||
if (temp == NULL) { Py_CLEAR(ans); return NULL; }
|
||||
PyTuple_SET_ITEM(ans, i, temp);
|
||||
@@ -59,7 +66,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
||||
|
||||
self = (ColorProfile *)type->tp_alloc(type, 0);
|
||||
if (self != NULL) {
|
||||
if (FG_BG_256[255] == 0) create_256_color_table();
|
||||
init_FG_BG_table();
|
||||
memcpy(self->color_table, FG_BG_256, sizeof(FG_BG_256));
|
||||
memcpy(self->orig_color_table, FG_BG_256, sizeof(FG_BG_256));
|
||||
self->dirty = true;
|
||||
@@ -80,12 +87,10 @@ alloc_color_profile() {
|
||||
|
||||
static PyObject*
|
||||
update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
||||
#define update_ansi_color_table_doc "Update the 16 basic colors"
|
||||
index_type i;
|
||||
|
||||
#define update_ansi_color_table_doc "Update the 256 basic colors"
|
||||
if (!PyList_Check(val)) { PyErr_SetString(PyExc_TypeError, "color table must be a list"); return NULL; }
|
||||
if (PyList_GET_SIZE(val) != 16) { PyErr_SetString(PyExc_TypeError, "color table must have 16 items"); return NULL; }
|
||||
for (i = 0; i < 16; i++) {
|
||||
if (PyList_GET_SIZE(val) != arraysz(FG_BG_256)) { PyErr_SetString(PyExc_TypeError, "color table must have 256 items"); return NULL; }
|
||||
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
||||
self->color_table[i] = PyLong_AsUnsignedLong(PyList_GET_ITEM(val, i));
|
||||
self->orig_color_table[i] = self->color_table[i];
|
||||
}
|
||||
@@ -93,6 +98,43 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
patch_color_profiles(PyObject *module UNUSED, PyObject *args) {
|
||||
PyObject *spec, *profiles, *v; ColorProfile *self; int change_configured;
|
||||
if (!PyArg_ParseTuple(args, "O!O!p", &PyDict_Type, &spec, &PyTuple_Type, &profiles, &change_configured)) return NULL;
|
||||
char key[32] = {0};
|
||||
for (size_t i = 0; i < arraysz(FG_BG_256); i++) {
|
||||
snprintf(key, sizeof(key) - 1, "color%zu", i);
|
||||
v = PyDict_GetItemString(spec, key);
|
||||
if (v) {
|
||||
color_type color = PyLong_AsUnsignedLong(v);
|
||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) {
|
||||
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i);
|
||||
self->color_table[i] = color;
|
||||
if (change_configured) self->orig_color_table[i] = color;
|
||||
self->dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#define S(config_name, profile_name) { \
|
||||
v = PyDict_GetItemString(spec, #config_name); \
|
||||
if (v) { \
|
||||
color_type color = PyLong_AsUnsignedLong(v); \
|
||||
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(profiles); i++) { \
|
||||
self = (ColorProfile*)PyTuple_GET_ITEM(profiles, i); \
|
||||
self->overridden.profile_name = (color << 8) | 2; \
|
||||
if (change_configured) self->configured.profile_name = color; \
|
||||
self->dirty = true; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
S(foreground, default_fg); S(background, default_bg); S(cursor, cursor_color);
|
||||
S(selection_foreground, highlight_fg); S(selection_background, highlight_bg);
|
||||
#undef S
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
color_type
|
||||
colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
||||
color_type t = entry & 0xFF, r;
|
||||
@@ -133,7 +175,7 @@ as_color(ColorProfile *self, PyObject *val) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
reset_color_table(ColorProfile *self) {
|
||||
reset_color_table(ColorProfile *self, PyObject *a UNUSED) {
|
||||
#define reset_color_table_doc "Reset all customized colors back to defaults"
|
||||
memcpy(self->color_table, self->orig_color_table, sizeof(FG_BG_256));
|
||||
self->dirty = true;
|
||||
@@ -179,11 +221,15 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
color_table_address(ColorProfile *self) {
|
||||
color_table_address(ColorProfile *self, PyObject *a UNUSED) {
|
||||
#define color_table_address_doc "Pointer address to start of color table"
|
||||
return PyLong_FromVoidPtr((void*)self->color_table);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
default_color_table(PyObject *self UNUSED, PyObject *args UNUSED) {
|
||||
return create_256_color_table();
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
@@ -236,5 +282,12 @@ PyTypeObject ColorProfile_Type = {
|
||||
.tp_new = new,
|
||||
};
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(default_color_table, METH_NOARGS),
|
||||
METHODB(patch_color_profiles, METH_VARARGS),
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
INIT_TYPE(ColorProfile)
|
||||
// }}}
|
||||
|
||||
401
kitty/config.py
401
kitty/config.py
@@ -6,20 +6,20 @@ import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from . import fast_data_types as defines
|
||||
from .config_utils import (
|
||||
init_config, parse_config_base, positive_float, positive_int, to_bool,
|
||||
to_color, unit_float
|
||||
init_config, load_config as _load_config, merge_dicts, parse_config_base,
|
||||
positive_float, positive_int, to_bool, to_cmdline, to_color, unit_float
|
||||
)
|
||||
from .constants import config_dir
|
||||
from .constants import cache_dir, defconf
|
||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||
from .layout import all_layouts
|
||||
from .utils import safe_print
|
||||
from .rgb import color_as_int, color_from_int
|
||||
from .utils import log_error
|
||||
|
||||
MINIMUM_FONT_SIZE = 4
|
||||
|
||||
@@ -46,22 +46,20 @@ def to_cursor_shape(x):
|
||||
)
|
||||
|
||||
|
||||
def parse_mods(parts):
|
||||
mod_map = {'CTRL': 'CONTROL', 'CMD': 'SUPER', '⌘': 'SUPER', '⌥': 'ALT', 'OPTION': 'ALT', 'KITTY_MOD': 'KITTY'}
|
||||
|
||||
|
||||
def parse_mods(parts, sc):
|
||||
|
||||
def map_mod(m):
|
||||
return {'CTRL': 'CONTROL', 'CMD': 'SUPER', 'OPTION': 'ALT'}.get(m, m)
|
||||
return mod_map.get(m, m)
|
||||
|
||||
mods = 0
|
||||
for m in parts:
|
||||
try:
|
||||
mods |= getattr(defines, 'GLFW_MOD_' + map_mod(m.upper()))
|
||||
except AttributeError:
|
||||
safe_print(
|
||||
'Shortcut: {} has an unknown modifier, ignoring'.format(
|
||||
parts.join('+')
|
||||
),
|
||||
file=sys.stderr
|
||||
)
|
||||
log_error('Shortcut: {} has unknown modifier, ignoring'.format(sc))
|
||||
return
|
||||
|
||||
return mods
|
||||
@@ -83,16 +81,21 @@ named_keys = {
|
||||
|
||||
def parse_shortcut(sc):
|
||||
parts = sc.split('+')
|
||||
mods = parse_mods(parts[:-1])
|
||||
mods = parse_mods(parts[:-1], sc)
|
||||
if mods is None:
|
||||
return None, None
|
||||
key = parts[-1].upper()
|
||||
key = getattr(defines, 'GLFW_KEY_' + named_keys.get(key, key), None)
|
||||
if key is not None:
|
||||
return mods, key
|
||||
return None, None
|
||||
return mods, None
|
||||
|
||||
|
||||
KeyAction = namedtuple('KeyAction', 'func args')
|
||||
shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window', 'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'}
|
||||
shlex_actions = {
|
||||
'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window',
|
||||
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'
|
||||
}
|
||||
|
||||
|
||||
def parse_key_action(action):
|
||||
@@ -107,35 +110,82 @@ def parse_key_action(action):
|
||||
args = tuple(map(parse_key_action, filter(None, parts)))
|
||||
elif func == 'send_text':
|
||||
args = rest.split(' ', 1)
|
||||
if len(args) > 0:
|
||||
try:
|
||||
args[1] = parse_send_text_bytes(args[1])
|
||||
except Exception:
|
||||
log_error('Ignoring invalid send_text string: ' + args[1])
|
||||
args[1] = ''
|
||||
elif func in ('run_kitten', 'run_simple_kitten'):
|
||||
if func == 'run_simple_kitten':
|
||||
func = 'run_kitten'
|
||||
args = rest.split(' ', 2)
|
||||
elif func == 'goto_tab':
|
||||
args = (max(0, int(rest)),)
|
||||
args = (max(0, int(rest)), )
|
||||
elif func == 'goto_layout' or func == 'kitty_shell':
|
||||
args = [rest]
|
||||
elif func == 'set_font_size':
|
||||
args = (float(rest),)
|
||||
elif func in shlex_actions:
|
||||
args = shlex.split(rest)
|
||||
args = to_cmdline(rest)
|
||||
return KeyAction(func, args)
|
||||
|
||||
|
||||
def parse_key(val, keymap):
|
||||
all_key_actions = set()
|
||||
sequence_sep = '>'
|
||||
|
||||
|
||||
class KeyDefinition:
|
||||
|
||||
def __init__(self, is_sequence, action, mods, key, rest=()):
|
||||
self.is_sequence = is_sequence
|
||||
self.action = action
|
||||
self.trigger = mods, key
|
||||
self.rest = rest
|
||||
|
||||
def resolve(self, kitty_mod):
|
||||
self.trigger = defines.resolve_key_mods(kitty_mod, self.trigger[0]), self.trigger[1]
|
||||
self.rest = tuple((defines.resolve_key_mods(kitty_mod, mods), key) for mods, key in self.rest)
|
||||
|
||||
|
||||
def parse_key(val, key_definitions):
|
||||
sc, action = val.partition(' ')[::2]
|
||||
sc, action = sc.strip(), action.strip()
|
||||
sc, action = sc.strip().strip(sequence_sep), action.strip()
|
||||
if not sc or not action:
|
||||
return
|
||||
mods, key = parse_shortcut(sc)
|
||||
if key is None:
|
||||
safe_print(
|
||||
'Shortcut: {} has an unknown key, ignoring'.format(val),
|
||||
file=sys.stderr
|
||||
)
|
||||
return
|
||||
is_sequence = sequence_sep in sc
|
||||
if is_sequence:
|
||||
trigger = None
|
||||
rest = []
|
||||
for part in sc.split(sequence_sep):
|
||||
mods, key = parse_shortcut(part)
|
||||
if key is None:
|
||||
if mods is not None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
|
||||
return
|
||||
if trigger is None:
|
||||
trigger = mods, key
|
||||
else:
|
||||
rest.append((mods, key))
|
||||
rest = tuple(rest)
|
||||
else:
|
||||
mods, key = parse_shortcut(sc)
|
||||
if key is None:
|
||||
if mods is not None:
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(sc))
|
||||
return
|
||||
try:
|
||||
paction = parse_key_action(action)
|
||||
except Exception:
|
||||
safe_print(
|
||||
'Invalid shortcut action: {}. Ignoring.'.format(action),
|
||||
file=sys.stderr
|
||||
)
|
||||
log_error('Invalid shortcut action: {}. Ignoring.'.format(
|
||||
action))
|
||||
else:
|
||||
if paction is not None:
|
||||
keymap[(mods, key)] = paction
|
||||
all_key_actions.add(paction.func)
|
||||
if is_sequence:
|
||||
key_definitions.append(KeyDefinition(True, paction, trigger[0], trigger[1], rest))
|
||||
else:
|
||||
key_definitions.append(KeyDefinition(False, paction, mods, key))
|
||||
|
||||
|
||||
def parse_symbol_map(val):
|
||||
@@ -143,9 +193,8 @@ def parse_symbol_map(val):
|
||||
symbol_map = {}
|
||||
|
||||
def abort():
|
||||
safe_print(
|
||||
'Symbol map: {} is invalid, ignoring'.format(val), file=sys.stderr
|
||||
)
|
||||
log_error('Symbol map: {} is invalid, ignoring'.format(
|
||||
val))
|
||||
return {}
|
||||
|
||||
if len(parts) < 2:
|
||||
@@ -172,17 +221,16 @@ def parse_symbol_map(val):
|
||||
|
||||
|
||||
def parse_send_text_bytes(text):
|
||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''").encode('utf-8')
|
||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''"
|
||||
).encode('utf-8')
|
||||
|
||||
|
||||
def parse_send_text(val, keymap):
|
||||
def parse_send_text(val, key_definitions):
|
||||
parts = val.split(' ')
|
||||
|
||||
def abort(msg):
|
||||
safe_print(
|
||||
'Send text: {} is invalid ({}), ignoring'.format(val, msg),
|
||||
file=sys.stderr
|
||||
)
|
||||
log_error('Send text: {} is invalid ({}), ignoring'.format(
|
||||
val, msg))
|
||||
return {}
|
||||
|
||||
if len(parts) < 3:
|
||||
@@ -190,11 +238,11 @@ def parse_send_text(val, keymap):
|
||||
mode, sc = parts[:2]
|
||||
text = ' '.join(parts[2:])
|
||||
key_str = '{} send_text {} {}'.format(sc, mode, text)
|
||||
return parse_key(key_str, keymap)
|
||||
return parse_key(key_str, key_definitions)
|
||||
|
||||
|
||||
def to_modifiers(val):
|
||||
return parse_mods(val.split('+'))
|
||||
return parse_mods(val.split('+'), val) or 0
|
||||
|
||||
|
||||
def to_layout_names(raw):
|
||||
@@ -213,6 +261,15 @@ def adjust_line_height(x):
|
||||
return int(x)
|
||||
|
||||
|
||||
def macos_titlebar_color(x):
|
||||
x = x.strip('"')
|
||||
if x == 'system':
|
||||
return 0
|
||||
if x == 'background':
|
||||
return 1
|
||||
return (color_as_int(to_color(x)) << 8) | 2
|
||||
|
||||
|
||||
def box_drawing_scale(x):
|
||||
ans = tuple(float(x.strip()) for x in x.split(','))
|
||||
if len(ans) != 4:
|
||||
@@ -231,7 +288,11 @@ def tab_separator(x):
|
||||
|
||||
|
||||
def tab_font_style(x):
|
||||
return {'bold-italic': (True, True), 'bold': (True, False), 'italic': (False, True)}.get(x.lower().replace('_', '-'), (False, False))
|
||||
return {
|
||||
'bold-italic': (True, True),
|
||||
'bold': (True, False),
|
||||
'italic': (False, True)
|
||||
}.get(x.lower().replace('_', '-'), (False, False))
|
||||
|
||||
|
||||
def tab_bar_edge(x):
|
||||
@@ -242,16 +303,17 @@ def url_style(x):
|
||||
return url_style.map.get(x, url_style.map['curly'])
|
||||
|
||||
|
||||
url_style.map = dict(((v, i) for i, v in enumerate('none single double curly'.split())))
|
||||
|
||||
url_style.map = dict(
|
||||
((v, i) for i, v in enumerate('none single double curly'.split()))
|
||||
)
|
||||
|
||||
type_map = {
|
||||
'allow_remote_control': to_bool,
|
||||
'adjust_line_height': adjust_line_height,
|
||||
'adjust_column_width': adjust_line_height,
|
||||
'scrollback_lines': positive_int,
|
||||
'scrollback_pager': shlex.split,
|
||||
'scrollback_in_new_tab': to_bool,
|
||||
'scrollback_pager': to_cmdline,
|
||||
'open_url_with': to_cmdline,
|
||||
'font_size': to_font_size,
|
||||
'font_size_delta': positive_float,
|
||||
'focus_follows_mouse': to_bool,
|
||||
@@ -260,6 +322,8 @@ type_map = {
|
||||
'rectangle_select_modifiers': to_modifiers,
|
||||
'repaint_delay': positive_int,
|
||||
'input_delay': positive_int,
|
||||
'sync_to_monitor': to_bool,
|
||||
'close_on_child_death': to_bool,
|
||||
'window_border_width': positive_float,
|
||||
'window_margin_width': positive_float,
|
||||
'window_padding_width': positive_float,
|
||||
@@ -274,29 +338,32 @@ type_map = {
|
||||
'remember_window_size': to_bool,
|
||||
'initial_window_width': positive_int,
|
||||
'initial_window_height': positive_int,
|
||||
'use_system_wcwidth': to_bool,
|
||||
'macos_hide_titlebar': to_bool,
|
||||
'macos_hide_from_tasks': to_bool,
|
||||
'macos_option_as_alt': to_bool,
|
||||
'macos_titlebar_color': macos_titlebar_color,
|
||||
'box_drawing_scale': box_drawing_scale,
|
||||
'x11_bell_volume': int,
|
||||
'background_opacity': unit_float,
|
||||
'tab_separator': tab_separator,
|
||||
'active_tab_font_style': tab_font_style,
|
||||
'inactive_tab_font_style': tab_font_style,
|
||||
'inactive_text_alpha': unit_float,
|
||||
'url_style': url_style,
|
||||
'prefer_color_emoji': to_bool,
|
||||
'copy_on_select': to_bool,
|
||||
'window_alert_on_bell': to_bool,
|
||||
'tab_bar_edge': tab_bar_edge,
|
||||
'bell_on_tab': to_bool,
|
||||
'kitty_mod': to_modifiers,
|
||||
'clear_all_shortcuts': to_bool,
|
||||
}
|
||||
|
||||
for name in (
|
||||
'foreground background cursor active_border_color inactive_border_color'
|
||||
' selection_foreground selection_background url_color'
|
||||
' selection_foreground selection_background url_color bell_border_color'
|
||||
).split():
|
||||
type_map[name] = to_color
|
||||
for i in range(16):
|
||||
type_map['color%d' % i] = to_color
|
||||
for i in range(256):
|
||||
type_map['color{}'.format(i)] = to_color
|
||||
for a in ('active', 'inactive'):
|
||||
for b in ('foreground', 'background'):
|
||||
type_map['%s_tab_%s' % (a, b)] = to_color
|
||||
@@ -304,88 +371,72 @@ for a in ('active', 'inactive'):
|
||||
|
||||
def special_handling(key, val, ans):
|
||||
if key == 'map':
|
||||
parse_key(val, ans['keymap'])
|
||||
parse_key(val, ans['key_definitions'])
|
||||
return True
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
return True
|
||||
if key == 'send_text':
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['keymap'])
|
||||
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
|
||||
default_config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf')
|
||||
default_config_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), 'kitty.conf'
|
||||
)
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
ans = {
|
||||
'keymap': {},
|
||||
'symbol_map': {},
|
||||
}
|
||||
parse_config_base(lines, defaults, type_map, special_handling, ans, check_keys=check_keys)
|
||||
ans = {'symbol_map': {}, 'keymap': {}, 'sequence_map': {}, 'key_definitions': []}
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
type_map,
|
||||
special_handling,
|
||||
ans,
|
||||
check_keys=check_keys
|
||||
)
|
||||
return ans
|
||||
|
||||
|
||||
Options, defaults = init_config(default_config_path, parse_config)
|
||||
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset(
|
||||
'combine send_text goto_tab new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.split()
|
||||
def parse_defaults(lines, check_keys=False):
|
||||
ans = parse_config(lines, check_keys)
|
||||
dfctl = defines.default_color_table()
|
||||
|
||||
for i in range(16, 256):
|
||||
k = 'color{}'.format(i)
|
||||
ans.setdefault(k, color_from_int(dfctl[i]))
|
||||
return ans
|
||||
|
||||
|
||||
Options, defaults = init_config(default_config_path, parse_defaults)
|
||||
actions = frozenset(all_key_actions) | frozenset(
|
||||
'run_simple_kitten combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
|
||||
split()
|
||||
)
|
||||
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
|
||||
|
||||
|
||||
def merge_keymaps(defaults, newvals):
|
||||
ans = defaults.copy()
|
||||
for k, v in newvals.items():
|
||||
f = v.func
|
||||
if f in no_op_actions:
|
||||
ans.pop(k, None)
|
||||
continue
|
||||
if f in actions:
|
||||
ans[k] = v
|
||||
return ans
|
||||
|
||||
|
||||
def merge_dicts(defaults, newvals):
|
||||
ans = defaults.copy()
|
||||
ans.update(newvals)
|
||||
return ans
|
||||
|
||||
|
||||
def merge_configs(defaults, vals):
|
||||
ans = {}
|
||||
for k, v in defaults.items():
|
||||
if isinstance(v, dict):
|
||||
newvals = vals.get(k, {})
|
||||
if k == 'keymap':
|
||||
ans['keymap'] = merge_keymaps(v, newvals)
|
||||
else:
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
ans[k] = merge_dicts(v, newvals)
|
||||
elif k == 'key_definitions':
|
||||
ans['key_definitions'] = v + vals.get('key_definitions', [])
|
||||
else:
|
||||
ans[k] = vals.get(k, v)
|
||||
return ans
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None) -> Options:
|
||||
ans = defaults._asdict()
|
||||
for path in paths:
|
||||
if not path:
|
||||
continue
|
||||
try:
|
||||
f = open(path)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
with f:
|
||||
vals = parse_config(f)
|
||||
ans = merge_configs(ans, vals)
|
||||
if overrides is not None:
|
||||
vals = parse_config(overrides)
|
||||
ans = merge_configs(ans, vals)
|
||||
return Options(**ans)
|
||||
|
||||
|
||||
def build_ansi_color_table(opts: Options = defaults):
|
||||
def build_ansi_color_table(opts=defaults):
|
||||
|
||||
def as_int(x):
|
||||
return (x[0] << 16) | (x[1] << 8) | x[2]
|
||||
@@ -393,59 +444,121 @@ def build_ansi_color_table(opts: Options = defaults):
|
||||
def col(i):
|
||||
return as_int(getattr(opts, 'color{}'.format(i)))
|
||||
|
||||
return list(map(col, range(16)))
|
||||
return list(map(col, range(256)))
|
||||
|
||||
|
||||
cached_values = {}
|
||||
cached_path = os.path.join(config_dir, 'cached.json')
|
||||
|
||||
|
||||
def load_cached_values():
|
||||
cached_values.clear()
|
||||
try:
|
||||
with open(cached_path, 'rb') as f:
|
||||
cached_values.update(json.loads(f.read().decode('utf-8')))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as err:
|
||||
safe_print(
|
||||
'Failed to load cached values with error: {}'.format(err),
|
||||
file=sys.stderr
|
||||
)
|
||||
|
||||
|
||||
def save_cached_values():
|
||||
fd, p = tempfile.mkstemp(
|
||||
dir=os.path.dirname(cached_path), suffix='cached.json.tmp'
|
||||
)
|
||||
def atomic_save(data, path):
|
||||
import tempfile
|
||||
fd, p = tempfile.mkstemp(dir=os.path.dirname(path), suffix='.tmp')
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
f.write(json.dumps(cached_values).encode('utf-8'))
|
||||
os.rename(p, cached_path)
|
||||
except Exception as err:
|
||||
safe_print(
|
||||
'Failed to save cached values with error: {}'.format(err),
|
||||
file=sys.stderr
|
||||
)
|
||||
f.write(data)
|
||||
os.rename(p, path)
|
||||
finally:
|
||||
try:
|
||||
os.remove(p)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as err:
|
||||
safe_print(
|
||||
'Failed to delete temp file for saved cached values with error: {}'.
|
||||
format(err),
|
||||
file=sys.stderr
|
||||
)
|
||||
log_error('Failed to delete temp file {} for atomic save with error: {}'.format(
|
||||
p, err))
|
||||
|
||||
|
||||
def initial_window_size(opts):
|
||||
@contextmanager
|
||||
def cached_values_for(name):
|
||||
cached_path = os.path.join(cache_dir(), name + '.json')
|
||||
cached_values = {}
|
||||
try:
|
||||
with open(cached_path, 'rb') as f:
|
||||
cached_values.update(json.loads(f.read().decode('utf-8')))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as err:
|
||||
log_error('Failed to load cached in {} values with error: {}'.format(
|
||||
name, err))
|
||||
|
||||
yield cached_values
|
||||
|
||||
try:
|
||||
data = json.dumps(cached_values).encode('utf-8')
|
||||
atomic_save(data, cached_path)
|
||||
except Exception as err:
|
||||
log_error('Failed to save cached values with error: {}'.format(
|
||||
err))
|
||||
|
||||
|
||||
def initial_window_size(opts, cached_values):
|
||||
w, h = opts.initial_window_width, opts.initial_window_height
|
||||
if 'window-size' in cached_values and opts.remember_window_size:
|
||||
ws = cached_values['window-size']
|
||||
try:
|
||||
w, h = map(int, ws)
|
||||
except Exception:
|
||||
safe_print('Invalid cached window size, ignoring', file=sys.stderr)
|
||||
log_error('Invalid cached window size, ignoring')
|
||||
return w, h
|
||||
|
||||
|
||||
def commented_out_default_config():
|
||||
with open(default_config_path, encoding='utf-8', errors='replace') as f:
|
||||
config = f.read()
|
||||
lines = []
|
||||
for line in config.splitlines():
|
||||
if line.strip() and not line.startswith('#'):
|
||||
line = '# ' + line
|
||||
lines.append(line)
|
||||
config = '\n'.join(lines)
|
||||
return config
|
||||
|
||||
|
||||
def prepare_config_file_for_editing():
|
||||
if not os.path.exists(defconf):
|
||||
d = os.path.dirname(defconf)
|
||||
try:
|
||||
os.makedirs(d)
|
||||
except FileExistsError:
|
||||
pass
|
||||
with open(defconf, 'w') as f:
|
||||
f.write(commented_out_default_config())
|
||||
return defconf
|
||||
|
||||
|
||||
def finalize_keys(opts):
|
||||
defns = []
|
||||
for d in opts.key_definitions:
|
||||
if d is None: # clear_all_shortcuts
|
||||
defns = []
|
||||
else:
|
||||
defns.append(d)
|
||||
for d in defns:
|
||||
d.resolve(opts.kitty_mod)
|
||||
keymap = {}
|
||||
sequence_map = {}
|
||||
|
||||
for defn in defns:
|
||||
is_no_op = defn.action.func in no_op_actions
|
||||
if defn.is_sequence:
|
||||
keymap.pop(defn.trigger, None)
|
||||
s = sequence_map.setdefault(defn.trigger, {})
|
||||
if is_no_op:
|
||||
s.pop(defn.rest, None)
|
||||
if not s:
|
||||
del sequence_map[defn.trigger]
|
||||
else:
|
||||
s[defn.rest] = defn.action
|
||||
else:
|
||||
sequence_map.pop(defn.trigger, None)
|
||||
if is_no_op:
|
||||
keymap.pop(defn.trigger, None)
|
||||
else:
|
||||
keymap[defn.trigger] = defn.action
|
||||
opts.keymap = keymap
|
||||
opts.sequence_map = sequence_map
|
||||
|
||||
|
||||
def load_config(*paths, overrides=None):
|
||||
opts = _load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=overrides)
|
||||
finalize_keys(opts)
|
||||
if opts.background_opacity < 1.0 and opts.macos_titlebar_color:
|
||||
log_error('Cannot use both macos_titlebar_color and background_opacity')
|
||||
opts.macos_titlebar_color = 0
|
||||
return opts
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
import shlex
|
||||
|
||||
from .utils import safe_print
|
||||
from .rgb import to_color as as_color
|
||||
from .utils import log_error
|
||||
|
||||
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
|
||||
|
||||
@@ -32,36 +32,136 @@ def to_bool(x):
|
||||
return x.lower() in 'y yes true'.split()
|
||||
|
||||
|
||||
def to_cmdline(x):
|
||||
ans = shlex.split(x)
|
||||
ans[0] = os.path.expandvars(os.path.expanduser(ans[0]))
|
||||
return ans
|
||||
|
||||
|
||||
def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
return
|
||||
m = key_pat.match(line)
|
||||
if m is not None:
|
||||
key, val = m.groups()
|
||||
if special_handling(key, val, ans):
|
||||
return
|
||||
if key == 'include':
|
||||
val = val.strip()
|
||||
if not os.path.isabs(val):
|
||||
val = os.path.join(base_path_for_includes, val)
|
||||
try:
|
||||
with open(val, encoding='utf-8', errors='replace') as include:
|
||||
_parse(include, type_map, special_handling, ans, all_keys)
|
||||
except FileNotFoundError:
|
||||
log_error('Could not find included config file: {}, ignoring'.format(val))
|
||||
except EnvironmentError:
|
||||
log_error('Could not read from included config file: {}, ignoring'.format(val))
|
||||
return
|
||||
if all_keys is not None and key not in all_keys:
|
||||
log_error('Ignoring unknown config key: {}'.format(key))
|
||||
return
|
||||
tm = type_map.get(key)
|
||||
if tm is not None:
|
||||
val = tm(val)
|
||||
ans[key] = val
|
||||
|
||||
|
||||
def _parse(lines, type_map, special_handling, ans, all_keys):
|
||||
name = getattr(lines, 'name', None)
|
||||
if name:
|
||||
base_path_for_includes = os.path.dirname(os.path.abspath(name))
|
||||
else:
|
||||
from .constants import config_dir
|
||||
base_path_for_includes = config_dir
|
||||
for line in lines:
|
||||
parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes)
|
||||
|
||||
|
||||
def parse_config_base(
|
||||
lines, defaults, type_map, special_handling, ans, check_keys=True
|
||||
):
|
||||
if check_keys:
|
||||
all_keys = defaults._asdict()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
all_keys = defaults._asdict() if check_keys else None
|
||||
_parse(lines, type_map, special_handling, ans, all_keys)
|
||||
|
||||
|
||||
def create_options_class(keys):
|
||||
keys = tuple(sorted(keys))
|
||||
slots = keys + ('_fields',)
|
||||
|
||||
def __init__(self, kw):
|
||||
for k, v in kw.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(keys)
|
||||
|
||||
def __len__(self):
|
||||
return len(keys)
|
||||
|
||||
def __getitem__(self, i):
|
||||
if isinstance(i, int):
|
||||
i = keys[i]
|
||||
try:
|
||||
return getattr(self, i)
|
||||
except AttributeError:
|
||||
raise KeyError('No option named: {}'.format(i))
|
||||
|
||||
def _asdict(self):
|
||||
return {k: getattr(self, k) for k in self._fields}
|
||||
|
||||
def _replace(self, **kw):
|
||||
ans = self._asdict()
|
||||
ans.update(kw)
|
||||
return self.__class__(ans)
|
||||
|
||||
ans = type('Options', (), {
|
||||
'__slots__': slots, '__init__': __init__, '_asdict': _asdict, '_replace': _replace, '__iter__': __iter__,
|
||||
'__len__': __len__, '__getitem__': __getitem__
|
||||
})
|
||||
ans._fields = keys
|
||||
return ans
|
||||
|
||||
|
||||
def merge_dicts(defaults, newvals):
|
||||
ans = defaults.copy()
|
||||
ans.update(newvals)
|
||||
return ans
|
||||
|
||||
|
||||
def resolve_config(SYSTEM_CONF, defconf, config_files_on_cmd_line):
|
||||
if config_files_on_cmd_line:
|
||||
if 'NONE' not in config_files_on_cmd_line:
|
||||
yield SYSTEM_CONF
|
||||
for cf in config_files_on_cmd_line:
|
||||
yield cf
|
||||
else:
|
||||
yield SYSTEM_CONF
|
||||
yield defconf
|
||||
|
||||
|
||||
def load_config(Options, defaults, parse_config, merge_configs, *paths, overrides=None):
|
||||
ans = defaults._asdict()
|
||||
for path in paths:
|
||||
if not path:
|
||||
continue
|
||||
m = key_pat.match(line)
|
||||
if m is not None:
|
||||
key, val = m.groups()
|
||||
if special_handling(key, val, ans):
|
||||
continue
|
||||
if check_keys:
|
||||
if key not in all_keys:
|
||||
safe_print(
|
||||
'Ignoring unknown config key: {}'.format(key),
|
||||
file=sys.stderr
|
||||
)
|
||||
continue
|
||||
tm = type_map.get(key)
|
||||
if tm is not None:
|
||||
val = tm(val)
|
||||
ans[key] = val
|
||||
try:
|
||||
f = open(path, encoding='utf-8', errors='replace')
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
with f:
|
||||
vals = parse_config(f)
|
||||
ans = merge_configs(ans, vals)
|
||||
if overrides is not None:
|
||||
vals = parse_config(overrides)
|
||||
ans = merge_configs(ans, vals)
|
||||
return Options(ans)
|
||||
|
||||
|
||||
def init_config(defaults_path, parse_config):
|
||||
with open(defaults_path, encoding='utf-8') as f:
|
||||
defaults = parse_config(f.read().splitlines(), check_keys=False)
|
||||
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
||||
defaults = Options(**defaults)
|
||||
with open(defaults_path, encoding='utf-8', errors='replace') as f:
|
||||
defaults = parse_config(f, check_keys=False)
|
||||
Options = create_options_class(defaults.keys())
|
||||
defaults = Options(defaults)
|
||||
return Options, defaults
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
|
||||
import os
|
||||
import pwd
|
||||
import shlex
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
from .fast_data_types import set_boss as set_c_boss
|
||||
|
||||
appname = 'kitty'
|
||||
version = (0, 7, 1)
|
||||
version = (0, 9, 1)
|
||||
str_version = '.'.join(map(str, version))
|
||||
_plat = sys.platform.lower()
|
||||
is_macos = 'darwin' in _plat
|
||||
base = os.path.dirname(os.path.abspath(__file__))
|
||||
editor = shlex.split(os.environ.get('EDITOR', 'vim'))
|
||||
|
||||
|
||||
ScreenGeometry = namedtuple('ScreenGeometry', 'xstart ystart xnum ynum dx dy')
|
||||
@@ -25,7 +27,22 @@ def _get_config_dir():
|
||||
if 'KITTY_CONFIG_DIRECTORY' in os.environ:
|
||||
return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
|
||||
|
||||
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config')))
|
||||
locations = []
|
||||
if 'XDG_CONFIG_HOME' in os.environ:
|
||||
locations.append(os.path.abspath(os.path.expanduser(os.environ['XDG_CONFIG_HOME'])))
|
||||
locations.append(os.path.expanduser('~/.config'))
|
||||
if is_macos:
|
||||
locations.append(os.path.expanduser('~/Library/Preferences'))
|
||||
if 'XDG_CONFIG_DIRS' in os.environ:
|
||||
for loc in os.environ['XDG_CONFIG_DIRS'].split(os.pathsep):
|
||||
locations.append(os.path.abspath(os.path.expanduser(loc)))
|
||||
for loc in locations:
|
||||
if loc:
|
||||
q = os.path.join(loc, appname)
|
||||
if os.access(q, os.W_OK) and os.path.exists(os.path.join(q, 'kitty.conf')):
|
||||
return q
|
||||
|
||||
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)
|
||||
return ans
|
||||
@@ -36,6 +53,25 @@ del _get_config_dir
|
||||
defconf = os.path.join(config_dir, 'kitty.conf')
|
||||
|
||||
|
||||
def _get_cache_dir():
|
||||
if 'KITTY_CACHE_DIRECTORY' in os.environ:
|
||||
candidate = os.path.abspath(os.environ['KITTY_CACHE_DIRECTORY'])
|
||||
elif is_macos:
|
||||
candidate = os.path.join(os.path.expanduser('~/Library/Caches'), appname)
|
||||
else:
|
||||
candidate = os.environ.get('XDG_CACHE_HOME', '~/.cache')
|
||||
candidate = os.path.join(os.path.expanduser(candidate), appname)
|
||||
os.makedirs(candidate, exist_ok=True)
|
||||
return candidate
|
||||
|
||||
|
||||
def cache_dir():
|
||||
ans = getattr(cache_dir, 'ans', None)
|
||||
if ans is None:
|
||||
ans = cache_dir.ans = _get_cache_dir()
|
||||
return ans
|
||||
|
||||
|
||||
def get_boss():
|
||||
return get_boss.boss
|
||||
|
||||
@@ -55,7 +91,10 @@ logo_data_file = os.path.join(base_dir, 'logo', 'kitty.rgba')
|
||||
try:
|
||||
shell_path = pwd.getpwuid(os.geteuid()).pw_shell or '/bin/sh'
|
||||
except KeyError:
|
||||
print('Failed to read login shell from /etc/passwd for current user, falling back to /bin/sh', file=sys.stderr)
|
||||
try:
|
||||
print('Failed to read login shell via getpwuid() for current user, falling back to /bin/sh', file=sys.stderr)
|
||||
except Exception:
|
||||
pass
|
||||
shell_path = '/bin/sh'
|
||||
|
||||
|
||||
|
||||
@@ -230,12 +230,6 @@
|
||||
// Forward tab
|
||||
#define CHT 'I'
|
||||
|
||||
// Save cursor
|
||||
#define SC 's'
|
||||
|
||||
// Restore cursor
|
||||
#define RC 'u'
|
||||
|
||||
// Misc sequences
|
||||
// ----------------
|
||||
|
||||
|
||||
@@ -178,14 +178,18 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic) {
|
||||
create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic, bool emoji_presentation) {
|
||||
CTFace *self = (CTFace*)base_face;
|
||||
char text[256] = {0};
|
||||
cell_as_utf8(cell, true, text, ' ');
|
||||
CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
|
||||
if (str == NULL) return PyErr_NoMemory();
|
||||
CTFontRef new_font = find_substitute_face(str, self->ct_font);
|
||||
CFRelease(str);
|
||||
CTFontRef new_font;
|
||||
if (emoji_presentation) new_font = CTFontCreateWithName((CFStringRef)@"AppleColorEmoji", self->scaled_point_sz, NULL);
|
||||
else {
|
||||
char text[256] = {0};
|
||||
cell_as_utf8(cell, true, text, ' ');
|
||||
CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
|
||||
if (str == NULL) return PyErr_NoMemory();
|
||||
new_font = find_substitute_face(str, self->ct_font);
|
||||
CFRelease(str);
|
||||
}
|
||||
if (new_font == NULL) return NULL;
|
||||
return (PyObject*)ct_face(new_font);
|
||||
}
|
||||
|
||||
@@ -253,7 +253,7 @@ cursor_as_sgr(Cursor *self, Cursor *prev) {
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
reset_display_attrs(Cursor *self) {
|
||||
reset_display_attrs(Cursor *self, PyObject *a UNUSED) {
|
||||
#define reset_display_attrs_doc "Reset all display attributes to unset"
|
||||
cursor_reset_display_attrs(self);
|
||||
Py_RETURN_NONE;
|
||||
@@ -272,7 +272,7 @@ void cursor_copy_to(Cursor *src, Cursor *dest) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
copy(Cursor *self);
|
||||
copy(Cursor *self, PyObject*);
|
||||
#define copy_doc "Create a clone of this cursor"
|
||||
|
||||
// Boilerplate {{{
|
||||
@@ -342,7 +342,7 @@ cursor_copy(Cursor *self) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
copy(Cursor *self) {
|
||||
copy(Cursor *self, PyObject *a UNUSED) {
|
||||
return (PyObject*)cursor_copy(self);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
#include <stddef.h>
|
||||
#include <termios.h>
|
||||
#include <signal.h>
|
||||
#include <sys/wait.h>
|
||||
#ifdef WITH_PROFILER
|
||||
#include <gperftools/profiler.h>
|
||||
#endif
|
||||
@@ -70,17 +69,6 @@ static inline double monotonic_() {
|
||||
|
||||
double monotonic() { return monotonic_(); }
|
||||
|
||||
static PyObject*
|
||||
wcwidth_wrap(PyObject UNUSED *self, PyObject *chr) {
|
||||
return PyLong_FromUnsignedLong(safe_wcwidth(PyLong_AsLong(chr)));
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
change_wcwidth_wrap(PyObject UNUSED *self, PyObject *use9) {
|
||||
change_wcwidth(PyObject_IsTrue(use9));
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
redirect_std_streams(PyObject UNUSED *self, PyObject *args) {
|
||||
char *devnull = NULL;
|
||||
@@ -99,31 +87,6 @@ pyset_iutf8(PyObject UNUSED *self, PyObject *args) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static void
|
||||
handle_sigchld(int UNUSED signum, siginfo_t *sinfo, void UNUSED *unused) {
|
||||
if (sinfo->si_code != CLD_EXITED) return;
|
||||
int sav_errno = errno, status;
|
||||
while(true) {
|
||||
if (waitpid(sinfo->si_pid, &status, WNOHANG) == -1) {
|
||||
if (errno != EINTR) break;
|
||||
} else break;
|
||||
}
|
||||
// wakeup I/O loop as without this on macOS sometimes poll() does not detect the fd close, so
|
||||
// kitty does not detect child death.
|
||||
wakeup_io_loop(true);
|
||||
errno = sav_errno;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
install_sigchld_handler(PyObject UNUSED *self) {
|
||||
struct sigaction sa;
|
||||
sa.sa_flags = SA_SIGINFO;
|
||||
sa.sa_sigaction = handle_sigchld;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
if (sigaction(SIGCHLD, &sa, NULL) == -1) return PyErr_SetFromErrno(PyExc_OSError);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#ifdef WITH_PROFILER
|
||||
static PyObject*
|
||||
start_profiler(PyObject UNUSED *self, PyObject *args) {
|
||||
@@ -146,9 +109,6 @@ static PyMethodDef module_methods[] = {
|
||||
{"parse_bytes", (PyCFunction)parse_bytes, METH_VARARGS, ""},
|
||||
{"parse_bytes_dump", (PyCFunction)parse_bytes_dump, METH_VARARGS, ""},
|
||||
{"redirect_std_streams", (PyCFunction)redirect_std_streams, METH_VARARGS, ""},
|
||||
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
|
||||
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
|
||||
{"install_sigchld_handler", (PyCFunction)install_sigchld_handler, METH_NOARGS, ""},
|
||||
#ifdef __APPLE__
|
||||
METHODB(user_cache_dir, METH_NOARGS),
|
||||
#endif
|
||||
@@ -186,6 +146,8 @@ extern bool init_keys(PyObject *module);
|
||||
extern bool init_graphics(PyObject *module);
|
||||
extern bool init_shaders(PyObject *module);
|
||||
extern bool init_mouse(PyObject *module);
|
||||
extern bool init_kittens(PyObject *module);
|
||||
extern bool init_logging(PyObject *module);
|
||||
#ifdef __APPLE__
|
||||
extern int init_CoreText(PyObject *);
|
||||
extern bool init_cocoa(PyObject *module);
|
||||
@@ -205,6 +167,7 @@ PyInit_fast_data_types(void) {
|
||||
#endif
|
||||
|
||||
if (m != NULL) {
|
||||
if (!init_logging(m)) return NULL;
|
||||
if (!init_LineBuf(m)) return NULL;
|
||||
if (!init_HistoryBuf(m)) return NULL;
|
||||
if (!init_Line(m)) return NULL;
|
||||
@@ -219,6 +182,7 @@ PyInit_fast_data_types(void) {
|
||||
if (!init_graphics(m)) return NULL;
|
||||
if (!init_shaders(m)) return NULL;
|
||||
if (!init_mouse(m)) return NULL;
|
||||
if (!init_kittens(m)) return NULL;
|
||||
#ifdef __APPLE__
|
||||
if (!init_CoreText(m)) return NULL;
|
||||
if (!init_cocoa(m)) return NULL;
|
||||
@@ -229,16 +193,15 @@ PyInit_fast_data_types(void) {
|
||||
#endif
|
||||
if (!init_fonts(m)) return NULL;
|
||||
|
||||
#define OOF(n) #n, offsetof(Cell, n)
|
||||
if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}",
|
||||
OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc_idx), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL;
|
||||
#undef OOF
|
||||
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
|
||||
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
|
||||
PyModule_AddIntConstant(m, "REVERSE", REVERSE_SHIFT);
|
||||
PyModule_AddIntConstant(m, "STRIKETHROUGH", STRIKE_SHIFT);
|
||||
PyModule_AddIntConstant(m, "DECORATION", DECORATION_SHIFT);
|
||||
PyModule_AddStringMacro(m, ERROR_PREFIX);
|
||||
#ifdef KITTY_VCS_REV
|
||||
PyModule_AddStringMacro(m, KITTY_VCS_REV);
|
||||
#endif
|
||||
PyModule_AddIntMacro(m, CURSOR_BLOCK);
|
||||
PyModule_AddIntMacro(m, CURSOR_BEAM);
|
||||
PyModule_AddIntMacro(m, CURSOR_UNDERLINE);
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
// Required minimum OpenGL version
|
||||
#define OPENGL_REQUIRED_VERSION_MAJOR 3
|
||||
#define OPENGL_REQUIRED_VERSION_MINOR 3
|
||||
#define GLFW_MOD_KITTY 1024
|
||||
#define UNUSED __attribute__ ((unused))
|
||||
#define PYNOARG PyObject *__a1 UNUSED, PyObject *__a2 UNUSED
|
||||
#define EXPORTED __attribute__ ((visibility ("default")))
|
||||
#define LIKELY(x) __builtin_expect (!!(x), 1)
|
||||
#define UNLIKELY(x) __builtin_expect (!!(x), 0)
|
||||
@@ -26,7 +28,8 @@
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||
#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); }
|
||||
void log_error(const char *fmt, ...);
|
||||
#define fatal(...) { log_error(__VA_ARGS__); exit(EXIT_FAILURE); }
|
||||
|
||||
typedef unsigned long long id_type;
|
||||
typedef uint32_t char_type;
|
||||
@@ -158,15 +161,18 @@ typedef struct {
|
||||
Line *line;
|
||||
} LineBuf;
|
||||
|
||||
typedef struct {
|
||||
Cell *cells;
|
||||
line_attrs_type *line_attrs;
|
||||
} HistoryBufSegment;
|
||||
|
||||
typedef struct {
|
||||
PyObject_HEAD
|
||||
|
||||
Cell *buf;
|
||||
index_type xnum, ynum;
|
||||
index_type xnum, ynum, num_segments;
|
||||
HistoryBufSegment* segments;
|
||||
Line *line;
|
||||
index_type start_of_data, count;
|
||||
line_attrs_type *line_attrs;
|
||||
} HistoryBuf;
|
||||
|
||||
typedef struct {
|
||||
@@ -201,29 +207,13 @@ typedef struct {
|
||||
DynamicColor configured, overridden;
|
||||
} ColorProfile;
|
||||
|
||||
#define SAVEPOINTS_SZ 256
|
||||
|
||||
typedef struct {
|
||||
uint32_t utf8_state, utf8_codepoint, *g0_charset, *g1_charset, *g_charset;
|
||||
bool use_latin1;
|
||||
Cursor cursor;
|
||||
bool mDECOM, mDECAWM, mDECSCNM;
|
||||
|
||||
} Savepoint;
|
||||
|
||||
|
||||
typedef struct {
|
||||
Savepoint buf[SAVEPOINTS_SZ];
|
||||
index_type start_of_data, count;
|
||||
} SavepointBuffer;
|
||||
|
||||
|
||||
#define PARSER_BUF_SZ (8 * 1024)
|
||||
#define READ_BUF_SZ (1024*1024)
|
||||
|
||||
#define clear_sprite_position(cell) (cell).sprite_x = 0; (cell).sprite_y = 0; (cell).sprite_z = 0;
|
||||
|
||||
#define left_shift_line(line, at, num) \
|
||||
#define left_shift_line(line, at, num) { \
|
||||
for(index_type __i__ = (at); __i__ < (line)->xnum - (num); __i__++) { \
|
||||
COPY_CELL(line, __i__ + (num), line, __i__) \
|
||||
} \
|
||||
@@ -231,7 +221,8 @@ typedef struct {
|
||||
(line)->cells[(at)].ch = BLANK_CHAR; \
|
||||
(line)->cells[(at)].attrs = BLANK_CHAR ? 1 : 0; \
|
||||
clear_sprite_position((line)->cells[(at)]); \
|
||||
}
|
||||
}\
|
||||
}
|
||||
|
||||
#define ensure_space_for(base, array, type, num, capacity, initial_cap, zero_mem) \
|
||||
if ((base)->capacity < num) { \
|
||||
@@ -271,8 +262,6 @@ bool set_iutf8(int, bool);
|
||||
color_type colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval);
|
||||
void copy_color_table_to_buffer(ColorProfile *self, color_type *address, int offset, size_t stride);
|
||||
|
||||
unsigned int safe_wcwidth(uint32_t ch);
|
||||
void change_wcwidth(bool use9);
|
||||
void set_mouse_cursor(MouseShape);
|
||||
void mouse_event(int, int);
|
||||
void focus_in_event();
|
||||
@@ -280,6 +269,5 @@ void wakeup_io_loop(bool);
|
||||
void scroll_event(double, double);
|
||||
void fake_scroll(int, bool);
|
||||
void set_special_key_combo(int glfw_key, int mods);
|
||||
void on_text_input(unsigned int codepoint, int mods);
|
||||
void on_key_input(int key, int scancode, int action, int mods);
|
||||
void on_key_input(int key, int scancode, int action, int mods, const char*, int);
|
||||
void request_window_attention(id_type, bool);
|
||||
|
||||
216
kitty/emoji.h
generated
216
kitty/emoji.h
generated
@@ -1,4 +1,4 @@
|
||||
// unicode data, built from the unicode standard on: 2018-01-18
|
||||
// unicode data, built from the unicode standard on: 2018-05-01
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
@@ -8,6 +8,28 @@ START_ALLOW_CASE_RANGE
|
||||
static inline bool
|
||||
is_emoji(char_type code) {
|
||||
switch(code) {
|
||||
case 0x23:
|
||||
return true;
|
||||
case 0x2a:
|
||||
return true;
|
||||
case 0x30 ... 0x39:
|
||||
return true;
|
||||
case 0xa9:
|
||||
return true;
|
||||
case 0xae:
|
||||
return true;
|
||||
case 0x200d:
|
||||
return true;
|
||||
case 0x203c:
|
||||
return true;
|
||||
case 0x2049:
|
||||
return true;
|
||||
case 0x20e3:
|
||||
return true;
|
||||
case 0x2122:
|
||||
return true;
|
||||
case 0x2139:
|
||||
return true;
|
||||
case 0x2194 ... 0x2199:
|
||||
return true;
|
||||
case 0x21a9 ... 0x21aa:
|
||||
@@ -16,6 +38,8 @@ is_emoji(char_type code) {
|
||||
return true;
|
||||
case 0x2328:
|
||||
return true;
|
||||
case 0x2388:
|
||||
return true;
|
||||
case 0x23cf:
|
||||
return true;
|
||||
case 0x23e9 ... 0x23f3:
|
||||
@@ -32,89 +56,15 @@ is_emoji(char_type code) {
|
||||
return true;
|
||||
case 0x25fb ... 0x25fe:
|
||||
return true;
|
||||
case 0x2600 ... 0x2604:
|
||||
case 0x2600 ... 0x2605:
|
||||
return true;
|
||||
case 0x260e:
|
||||
case 0x2607 ... 0x2612:
|
||||
return true;
|
||||
case 0x2611:
|
||||
case 0x2614 ... 0x2685:
|
||||
return true;
|
||||
case 0x2614 ... 0x2615:
|
||||
case 0x2690 ... 0x2705:
|
||||
return true;
|
||||
case 0x2618:
|
||||
return true;
|
||||
case 0x261d:
|
||||
return true;
|
||||
case 0x2620:
|
||||
return true;
|
||||
case 0x2622 ... 0x2623:
|
||||
return true;
|
||||
case 0x2626:
|
||||
return true;
|
||||
case 0x262a:
|
||||
return true;
|
||||
case 0x262e ... 0x262f:
|
||||
return true;
|
||||
case 0x2638 ... 0x263a:
|
||||
return true;
|
||||
case 0x2640:
|
||||
return true;
|
||||
case 0x2642:
|
||||
return true;
|
||||
case 0x2648 ... 0x2653:
|
||||
return true;
|
||||
case 0x2660:
|
||||
return true;
|
||||
case 0x2663:
|
||||
return true;
|
||||
case 0x2665 ... 0x2666:
|
||||
return true;
|
||||
case 0x2668:
|
||||
return true;
|
||||
case 0x267b:
|
||||
return true;
|
||||
case 0x267f:
|
||||
return true;
|
||||
case 0x2692 ... 0x2697:
|
||||
return true;
|
||||
case 0x2699:
|
||||
return true;
|
||||
case 0x269b ... 0x269c:
|
||||
return true;
|
||||
case 0x26a0 ... 0x26a1:
|
||||
return true;
|
||||
case 0x26aa ... 0x26ab:
|
||||
return true;
|
||||
case 0x26b0 ... 0x26b1:
|
||||
return true;
|
||||
case 0x26bd ... 0x26be:
|
||||
return true;
|
||||
case 0x26c4 ... 0x26c5:
|
||||
return true;
|
||||
case 0x26c8:
|
||||
return true;
|
||||
case 0x26ce ... 0x26cf:
|
||||
return true;
|
||||
case 0x26d1:
|
||||
return true;
|
||||
case 0x26d3 ... 0x26d4:
|
||||
return true;
|
||||
case 0x26e9 ... 0x26ea:
|
||||
return true;
|
||||
case 0x26f0 ... 0x26f5:
|
||||
return true;
|
||||
case 0x26f7 ... 0x26fa:
|
||||
return true;
|
||||
case 0x26fd:
|
||||
return true;
|
||||
case 0x2702:
|
||||
return true;
|
||||
case 0x2705:
|
||||
return true;
|
||||
case 0x2708 ... 0x270d:
|
||||
return true;
|
||||
case 0x270f:
|
||||
return true;
|
||||
case 0x2712:
|
||||
case 0x2708 ... 0x2712:
|
||||
return true;
|
||||
case 0x2714:
|
||||
return true;
|
||||
@@ -140,7 +90,7 @@ is_emoji(char_type code) {
|
||||
return true;
|
||||
case 0x2757:
|
||||
return true;
|
||||
case 0x2763 ... 0x2764:
|
||||
case 0x2763 ... 0x2767:
|
||||
return true;
|
||||
case 0x2795 ... 0x2797:
|
||||
return true;
|
||||
@@ -168,11 +118,15 @@ is_emoji(char_type code) {
|
||||
return true;
|
||||
case 0x3299:
|
||||
return true;
|
||||
case 0x1f004:
|
||||
case 0xfe0f:
|
||||
return true;
|
||||
case 0x1f0cf:
|
||||
case 0x1f000 ... 0x1f0ff:
|
||||
return true;
|
||||
case 0x1f170 ... 0x1f171:
|
||||
case 0x1f10d ... 0x1f10f:
|
||||
return true;
|
||||
case 0x1f12f:
|
||||
return true;
|
||||
case 0x1f16c ... 0x1f171:
|
||||
return true;
|
||||
case 0x1f17e ... 0x1f17f:
|
||||
return true;
|
||||
@@ -180,9 +134,9 @@ is_emoji(char_type code) {
|
||||
return true;
|
||||
case 0x1f191 ... 0x1f19a:
|
||||
return true;
|
||||
case 0x1f1e6 ... 0x1f1ff:
|
||||
case 0x1f1ad ... 0x1f1ff:
|
||||
return true;
|
||||
case 0x1f201 ... 0x1f202:
|
||||
case 0x1f201 ... 0x1f20f:
|
||||
return true;
|
||||
case 0x1f21a:
|
||||
return true;
|
||||
@@ -190,95 +144,35 @@ is_emoji(char_type code) {
|
||||
return true;
|
||||
case 0x1f232 ... 0x1f23a:
|
||||
return true;
|
||||
case 0x1f250 ... 0x1f251:
|
||||
case 0x1f23c ... 0x1f23f:
|
||||
return true;
|
||||
case 0x1f300 ... 0x1f321:
|
||||
case 0x1f249 ... 0x1f53d:
|
||||
return true;
|
||||
case 0x1f324 ... 0x1f393:
|
||||
case 0x1f546 ... 0x1f64f:
|
||||
return true;
|
||||
case 0x1f396 ... 0x1f397:
|
||||
case 0x1f680 ... 0x1f6ff:
|
||||
return true;
|
||||
case 0x1f399 ... 0x1f39b:
|
||||
case 0x1f774 ... 0x1f77f:
|
||||
return true;
|
||||
case 0x1f39e ... 0x1f3f0:
|
||||
case 0x1f7d5 ... 0x1f7ff:
|
||||
return true;
|
||||
case 0x1f3f3 ... 0x1f3f5:
|
||||
case 0x1f80c ... 0x1f80f:
|
||||
return true;
|
||||
case 0x1f3f7 ... 0x1f4fd:
|
||||
case 0x1f848 ... 0x1f84f:
|
||||
return true;
|
||||
case 0x1f4ff ... 0x1f53d:
|
||||
case 0x1f85a ... 0x1f85f:
|
||||
return true;
|
||||
case 0x1f549 ... 0x1f54e:
|
||||
case 0x1f888 ... 0x1f88f:
|
||||
return true;
|
||||
case 0x1f550 ... 0x1f567:
|
||||
case 0x1f8ae ... 0x1f8ff:
|
||||
return true;
|
||||
case 0x1f56f ... 0x1f570:
|
||||
case 0x1f90c ... 0x1f93a:
|
||||
return true;
|
||||
case 0x1f573 ... 0x1f57a:
|
||||
case 0x1f93c ... 0x1f945:
|
||||
return true;
|
||||
case 0x1f587:
|
||||
case 0x1f947 ... 0x1fffd:
|
||||
return true;
|
||||
case 0x1f58a ... 0x1f58d:
|
||||
return true;
|
||||
case 0x1f590:
|
||||
return true;
|
||||
case 0x1f595 ... 0x1f596:
|
||||
return true;
|
||||
case 0x1f5a4 ... 0x1f5a5:
|
||||
return true;
|
||||
case 0x1f5a8:
|
||||
return true;
|
||||
case 0x1f5b1 ... 0x1f5b2:
|
||||
return true;
|
||||
case 0x1f5bc:
|
||||
return true;
|
||||
case 0x1f5c2 ... 0x1f5c4:
|
||||
return true;
|
||||
case 0x1f5d1 ... 0x1f5d3:
|
||||
return true;
|
||||
case 0x1f5dc ... 0x1f5de:
|
||||
return true;
|
||||
case 0x1f5e1:
|
||||
return true;
|
||||
case 0x1f5e3:
|
||||
return true;
|
||||
case 0x1f5e8:
|
||||
return true;
|
||||
case 0x1f5ef:
|
||||
return true;
|
||||
case 0x1f5f3:
|
||||
return true;
|
||||
case 0x1f5fa ... 0x1f64f:
|
||||
return true;
|
||||
case 0x1f680 ... 0x1f6c5:
|
||||
return true;
|
||||
case 0x1f6cb ... 0x1f6d2:
|
||||
return true;
|
||||
case 0x1f6e0 ... 0x1f6e5:
|
||||
return true;
|
||||
case 0x1f6e9:
|
||||
return true;
|
||||
case 0x1f6eb ... 0x1f6ec:
|
||||
return true;
|
||||
case 0x1f6f0:
|
||||
return true;
|
||||
case 0x1f6f3 ... 0x1f6f8:
|
||||
return true;
|
||||
case 0x1f910 ... 0x1f93a:
|
||||
return true;
|
||||
case 0x1f93c ... 0x1f93e:
|
||||
return true;
|
||||
case 0x1f940 ... 0x1f945:
|
||||
return true;
|
||||
case 0x1f947 ... 0x1f94c:
|
||||
return true;
|
||||
case 0x1f950 ... 0x1f96b:
|
||||
return true;
|
||||
case 0x1f980 ... 0x1f997:
|
||||
return true;
|
||||
case 0x1f9c0:
|
||||
return true;
|
||||
case 0x1f9d0 ... 0x1f9e6:
|
||||
case 0xe0020 ... 0xe007f:
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
|
||||
@@ -189,15 +189,14 @@ end:
|
||||
}
|
||||
|
||||
PyObject*
|
||||
create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic) {
|
||||
create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation) {
|
||||
PyObject *ans = NULL;
|
||||
FcPattern *pat = FcPatternCreate();
|
||||
if (pat == NULL) return PyErr_NoMemory();
|
||||
bool emoji = is_emoji(cell->ch);
|
||||
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)(emoji ? "emoji" : "monospace"), "family");
|
||||
if (!emoji && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
|
||||
if (!emoji && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
|
||||
if (emoji) { AP(FcPatternAddBool, FC_COLOR, OPT(prefer_color_emoji), "color"); }
|
||||
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)(emoji_presentation ? "emoji" : "monospace"), "family");
|
||||
if (!emoji_presentation && bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
|
||||
if (!emoji_presentation && italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
|
||||
if (emoji_presentation) { AP(FcPatternAddBool, FC_COLOR, true, "color"); }
|
||||
size_t num = cell_as_unicode(cell, true, char_buf, ' ');
|
||||
add_charset(pat, num);
|
||||
PyObject *d = _fc_match(pat);
|
||||
|
||||
137
kitty/fonts.c
137
kitty/fonts.c
@@ -41,6 +41,7 @@ struct SpritePosition {
|
||||
#define SPECIAL_VALUE_MASK 2
|
||||
#define EMPTY_FILLED_MASK 4
|
||||
#define EMPTY_VALUE_MASK 8
|
||||
#define SPECIAL_GLYPH_CACHE_SIZE 1024
|
||||
|
||||
struct SpecialGlyphCache {
|
||||
SpecialGlyphCache *next;
|
||||
@@ -63,8 +64,8 @@ typedef struct {
|
||||
PyObject *face;
|
||||
// Map glyphs to sprite map co-ords
|
||||
SpritePosition sprite_map[1024];
|
||||
SpecialGlyphCache special_glyph_cache[1024];
|
||||
bool bold, italic;
|
||||
SpecialGlyphCache special_glyph_cache[SPECIAL_GLYPH_CACHE_SIZE];
|
||||
bool bold, italic, emoji_presentation;
|
||||
} Font;
|
||||
|
||||
typedef struct {
|
||||
@@ -128,7 +129,7 @@ extra_glyphs_equal(ExtraGlyphs *a, ExtraGlyphs *b) {
|
||||
|
||||
static SpritePosition*
|
||||
sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, uint8_t ligature_index, int *error) {
|
||||
glyph_index idx = glyph & 0x3ff;
|
||||
glyph_index idx = glyph & (SPECIAL_GLYPH_CACHE_SIZE - 1);
|
||||
SpritePosition *s = font->sprite_map + idx;
|
||||
// Optimize for the common case of glyph under 1024 already in the cache
|
||||
if (LIKELY(s->glyph == glyph && s->filled && extra_glyphs_equal(&s->extra_glyphs, extra_glyphs) && s->ligature_index == ligature_index)) return s; // Cache hit
|
||||
@@ -156,15 +157,16 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui
|
||||
}
|
||||
|
||||
static inline SpecialGlyphCache*
|
||||
special_glyph_cache_for(Font *font, glyph_index glyph, uint8_t mask) {
|
||||
special_glyph_cache_for(Font *font, glyph_index glyph, uint8_t filled_mask) {
|
||||
SpecialGlyphCache *s = font->special_glyph_cache + (glyph & 0x3ff);
|
||||
// Optimize for the common case of glyph under 1024 already in the cache
|
||||
if (LIKELY(s->glyph == glyph && s->data & mask)) return s; // Cache hit
|
||||
// Optimize for the common case of glyph under SPECIAL_GLYPH_CACHE_SIZE already in the cache
|
||||
if (LIKELY(s->glyph == glyph && s->data & filled_mask)) return s; // Cache hit
|
||||
while(true) {
|
||||
if (s->data & mask) {
|
||||
if (s->data & filled_mask) {
|
||||
if (s->glyph == glyph) return s; // Cache hit
|
||||
} else {
|
||||
break;
|
||||
if (!s->glyph) break; // Empty cache slot
|
||||
else if (s->glyph == glyph) return s; // Cache slot that contains other data than the data indicated by filled_mask
|
||||
}
|
||||
if (!s->next) {
|
||||
s->next = calloc(1, sizeof(SpecialGlyphCache));
|
||||
@@ -248,12 +250,12 @@ desc_to_face(PyObject *desc) {
|
||||
|
||||
|
||||
static inline bool
|
||||
init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face) {
|
||||
init_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face, bool emoji_presentation) {
|
||||
PyObject *face;
|
||||
if (is_face) { face = descriptor; Py_INCREF(face); }
|
||||
else { face = desc_to_face(descriptor); if (face == NULL) return false; }
|
||||
f->face = face;
|
||||
f->bold = bold; f->italic = italic;
|
||||
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -281,7 +283,7 @@ python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, pixel* buf) {
|
||||
|
||||
|
||||
static inline PyObject*
|
||||
update_cell_metrics() {
|
||||
update_cell_metrics(bool on_dpi_change UNUSED) {
|
||||
#define CALL(idx, desired_height, force) { if (idx >= 0) { Font *f = fonts.fonts + idx; if ((f)->face) { if(!set_size_for_face((f)->face, desired_height, force)) return NULL; } clear_sprite_map((f)); }}
|
||||
CALL(BOX_FONT, 0, false); CALL(fonts.medium_font_idx, 0, false);
|
||||
CALL(fonts.bold_font_idx, 0, false); CALL(fonts.italic_font_idx, 0, false); CALL(fonts.bi_font_idx, 0, false);
|
||||
@@ -318,8 +320,9 @@ update_cell_metrics() {
|
||||
|
||||
static PyObject*
|
||||
set_font_size(PyObject UNUSED *m, PyObject *args) {
|
||||
if (!PyArg_ParseTuple(args, "f", &global_state.font_sz_in_pts)) return NULL;
|
||||
return update_cell_metrics();
|
||||
int on_dpi_change = 0;
|
||||
if (!PyArg_ParseTuple(args, "f|p", &global_state.font_sz_in_pts, &on_dpi_change)) return NULL;
|
||||
return update_cell_metrics(on_dpi_change != 0);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -327,63 +330,77 @@ face_has_codepoint(PyObject* face, char_type cp) {
|
||||
return glyph_id_for_codepoint(face, cp) > 0;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
has_emoji_presentation(Cell *cell) {
|
||||
return (cell->attrs & WIDTH_MASK) == 2 && is_emoji(cell->ch) && cell->cc_idx[0] != VS15;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
has_cell_text(Font *self, Cell *cell) {
|
||||
if (!face_has_codepoint(self->face, cell->ch)) return false;
|
||||
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
|
||||
if (!face_has_codepoint(self->face, codepoint_for_mark(cell->cc_idx[i]))) return false;
|
||||
combining_type cc_idx = cell->cc_idx[i];
|
||||
if (cc_idx == VS15 || cc_idx == VS16) continue;
|
||||
if (!face_has_codepoint(self->face, codepoint_for_mark(cc_idx))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void
|
||||
output_cell_fallback_data(Cell *cell, bool bold, bool italic, bool emoji_presentation, PyObject *face, bool new_face) {
|
||||
printf("U+%x ", cell->ch);
|
||||
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
|
||||
printf("U+%x ", codepoint_for_mark(cell->cc_idx[i]));
|
||||
}
|
||||
if (bold) printf("bold ");
|
||||
if (italic) printf("italic ");
|
||||
if (emoji_presentation) printf("emoji_presentation ");
|
||||
PyObject_Print(face, stdout, 0);
|
||||
if (new_face) printf(" (new face)");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static inline ssize_t
|
||||
load_fallback_font(Cell *cell, bool bold, bool italic) {
|
||||
if (fonts.fallback_fonts_count > 100) { fprintf(stderr, "Too many fallback fonts\n"); return MISSING_FONT; }
|
||||
load_fallback_font(Cell *cell, bool bold, bool italic, bool emoji_presentation) {
|
||||
if (fonts.fallback_fonts_count > 100) { log_error("Too many fallback fonts"); return MISSING_FONT; }
|
||||
ssize_t f;
|
||||
|
||||
if (bold) f = fonts.italic_font_idx > 0 ? fonts.bi_font_idx : fonts.bold_font_idx;
|
||||
else f = italic ? fonts.italic_font_idx : fonts.medium_font_idx;
|
||||
if (f < 0) f = fonts.medium_font_idx;
|
||||
|
||||
PyObject *face = create_fallback_face(fonts.fonts[f].face, cell, bold, italic);
|
||||
PyObject *face = create_fallback_face(fonts.fonts[f].face, cell, bold, italic, emoji_presentation);
|
||||
if (face == NULL) { PyErr_Print(); return MISSING_FONT; }
|
||||
if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; }
|
||||
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, face, true);
|
||||
set_size_for_face(face, cell_height, true);
|
||||
|
||||
ensure_space_for(&fonts, fonts, Font, fonts.fonts_count + 1, fonts_capacity, 5, true);
|
||||
ssize_t ans = fonts.first_fallback_font_idx + fonts.fallback_fonts_count;
|
||||
Font *af = &fonts.fonts[ans];
|
||||
if (!init_font(af, face, bold, italic, true)) fatal("Out of memory");
|
||||
if (!init_font(af, face, bold, italic, true, emoji_presentation)) fatal("Out of memory");
|
||||
Py_DECREF(face);
|
||||
fonts.fallback_fonts_count++;
|
||||
fonts.fonts_count++;
|
||||
return ans;
|
||||
}
|
||||
|
||||
|
||||
static inline ssize_t
|
||||
fallback_font(Cell *cell) {
|
||||
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
|
||||
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
|
||||
|
||||
// Load the emoji fallback font first as on Linux there are a bunch of
|
||||
// non-color fonts that provide some emoji glyphs.
|
||||
if (fonts.fallback_fonts_count < 1) {
|
||||
Cell c = {0};
|
||||
c.ch = 0x1f648; // 🙈
|
||||
load_fallback_font(&c, false, false);
|
||||
}
|
||||
bool emoji_presentation = has_emoji_presentation(cell);
|
||||
|
||||
// Check if one of the existing fallback fonts has this text
|
||||
for (size_t i = 0, j = fonts.first_fallback_font_idx; i < fonts.fallback_fonts_count; i++, j++) {
|
||||
Font *ff = fonts.fonts +j;
|
||||
if (ff->bold == bold && ff->italic == italic && has_cell_text(ff, cell)) {
|
||||
if (ff->bold == bold && ff->italic == italic && ff->emoji_presentation == emoji_presentation && has_cell_text(ff, cell)) {
|
||||
if (global_state.debug_font_fallback) output_cell_fallback_data(cell, bold, italic, emoji_presentation, ff->face, false);
|
||||
return j;
|
||||
}
|
||||
}
|
||||
|
||||
return load_fallback_font(cell, bold, italic);
|
||||
return load_fallback_font(cell, bold, italic, emoji_presentation);
|
||||
}
|
||||
|
||||
static inline ssize_t
|
||||
@@ -422,7 +439,7 @@ START_ALLOW_CASE_RANGE
|
||||
ans = fonts.bi_font_idx; break;
|
||||
}
|
||||
if (ans < 0) ans = fonts.medium_font_idx;
|
||||
if (has_cell_text(fonts.fonts + ans, cell)) return ans;
|
||||
if (!has_emoji_presentation(cell) && has_cell_text(fonts.fonts + ans, cell)) return ans;
|
||||
return fallback_font(cell);
|
||||
}
|
||||
END_ALLOW_CASE_RANGE
|
||||
@@ -538,7 +555,7 @@ render_group(unsigned int num_cells, unsigned int num_glyphs, Cell *cells, hb_gl
|
||||
}
|
||||
|
||||
clear_canvas();
|
||||
bool was_colored = is_emoji(cells->ch);
|
||||
bool was_colored = (cells->attrs & WIDTH_MASK) == 2 && is_emoji(cells->ch);
|
||||
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, &was_colored);
|
||||
if (PyErr_Occurred()) PyErr_Print();
|
||||
|
||||
@@ -748,7 +765,7 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
|
||||
if (num_cells_consumed) {
|
||||
if (num_cells_consumed > MAX_GLYPHS_IN_GROUP) {
|
||||
// Nasty, a single glyph used more than MAX_GLYPHS_IN_GROUP cells, we cannot render this case correctly
|
||||
fprintf(stderr, "The glyph: %u needs more than %u cells, cannot render it\n", glyph_id, MAX_GLYPHS_IN_GROUP);
|
||||
log_error("The glyph: %u needs more than %u cells, cannot render it", glyph_id, MAX_GLYPHS_IN_GROUP);
|
||||
current_group->num_glyphs--;
|
||||
while (num_cells_consumed) {
|
||||
G(group_idx)++; current_group = G(groups) + G(group_idx);
|
||||
@@ -777,6 +794,17 @@ shape_run(Cell *first_cell, index_type num_cells, Font *font) {
|
||||
#undef MAX_GLYPHS_IN_GROUP
|
||||
}
|
||||
|
||||
static inline void
|
||||
merge_groups_for_pua_space_ligature() {
|
||||
if (G(group_idx) == 1) {
|
||||
Group *g = G(groups), *g1 = G(groups) + 1;
|
||||
g->num_cells += g1->num_cells;
|
||||
g->num_glyphs += g1->num_glyphs;
|
||||
g->num_glyphs = MIN(g->num_glyphs, MAX_NUM_EXTRA_GLYPHS + 1);
|
||||
G(group_idx) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
render_groups(Font *font) {
|
||||
unsigned idx = 0;
|
||||
@@ -787,7 +815,10 @@ render_groups(Font *font) {
|
||||
/* printf("Group: idx: %u num_cells: %u num_glyphs: %u first_glyph_idx: %u first_cell_idx: %u total_num_glyphs: %zu\n", */
|
||||
/* idx, group->num_cells, group->num_glyphs, group->first_glyph_idx, group->first_cell_idx, group_state.num_glyphs); */
|
||||
glyph_index primary = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
|
||||
for (unsigned i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) ed.data[i-1] = G(info)[group->first_glyph_idx + i].codepoint;
|
||||
unsigned int i;
|
||||
int last = -1;
|
||||
for (i = 1; i < MIN(arraysz(ed.data) + 1, group->num_glyphs); i++) { last = i - 1; ed.data[last] = G(info)[group->first_glyph_idx + i].codepoint; }
|
||||
if ((size_t)(last + 1) < arraysz(ed.data)) ed.data[last + 1] = 0;
|
||||
render_group(group->num_cells, group->num_glyphs, G(first_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, font, primary, &ed);
|
||||
idx++;
|
||||
}
|
||||
@@ -821,7 +852,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
|
||||
PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS);
|
||||
for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", g + 1 < group->num_glyphs ? G(info)[group->first_glyph_idx + g].codepoint : 0));
|
||||
PyList_Append(ans, Py_BuildValue("IIIN", group->num_cells, group->num_glyphs, first_glyph, eg));
|
||||
PyList_Append(ans, Py_BuildValue("IIHN", group->num_cells, group->num_glyphs, first_glyph, eg));
|
||||
idx++;
|
||||
}
|
||||
if (face) { Py_CLEAR(face); free(font); }
|
||||
@@ -830,10 +861,11 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
#undef G
|
||||
|
||||
static inline void
|
||||
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) {
|
||||
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx, bool pua_space_ligature) {
|
||||
switch(font_idx) {
|
||||
default:
|
||||
shape_run(first_cell, num_cells, &fonts.fonts[font_idx]);
|
||||
if (pua_space_ligature) merge_groups_for_pua_space_ligature();
|
||||
render_groups(&fonts.fonts[font_idx]);
|
||||
break;
|
||||
case BLANK_FONT:
|
||||
@@ -848,9 +880,14 @@ render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) {
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_private_use(char_type ch) {
|
||||
return (0xe000 <= ch && ch <= 0xf8ff) || (0xF0000 <= ch && ch <= 0xFFFFF) || (0x100000 <= ch && ch <= 0x10FFFF);
|
||||
}
|
||||
|
||||
void
|
||||
render_line(Line *line) {
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(line->cells + first_cell_in_run, i - first_cell_in_run, run_font_idx);
|
||||
#define RENDER if (run_font_idx != NO_FONT && i > first_cell_in_run) render_run(line->cells + first_cell_in_run, i - first_cell_in_run, run_font_idx, false);
|
||||
ssize_t run_font_idx = NO_FONT;
|
||||
index_type first_cell_in_run, i;
|
||||
attrs_type prev_width = 0;
|
||||
@@ -858,6 +895,22 @@ render_line(Line *line) {
|
||||
if (prev_width == 2) { prev_width = 0; continue; }
|
||||
Cell *cell = line->cells + i;
|
||||
ssize_t cell_font_idx = font_for_cell(cell);
|
||||
if (is_private_use(cell->ch) && i + 1 < line->xnum && (line->cells[i+1].ch == ' ' || line->cells[i+1].ch == 0) && cell_font_idx != BOX_FONT && cell_font_idx != MISSING_FONT) {
|
||||
// We have a private use char followed by a space char, render it as a two cell ligature.
|
||||
Cell *space_cell = line->cells + i+1;
|
||||
// Ensure the space cell uses the foreground colors from the PUA cell
|
||||
// This is needed because there are stupid applications like
|
||||
// powerline that use PUA+space with different foreground colors
|
||||
// for the space and the PUA. See for example: https://github.com/kovidgoyal/kitty/issues/467
|
||||
space_cell->fg = cell->fg; space_cell->decoration_fg = cell->decoration_fg;
|
||||
RENDER;
|
||||
render_run(line->cells + i, 2, cell_font_idx, true);
|
||||
run_font_idx = NO_FONT;
|
||||
first_cell_in_run = i + 2;
|
||||
prev_width = line->cells[i+1].attrs & WIDTH_MASK;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
prev_width = cell->attrs & WIDTH_MASK;
|
||||
if (run_font_idx == NO_FONT) run_font_idx = cell_font_idx;
|
||||
if (run_font_idx == cell_font_idx) continue;
|
||||
@@ -880,7 +933,7 @@ set_font(PyObject UNUSED *m, PyObject *args) {
|
||||
for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i);
|
||||
ensure_space_for(&fonts, fonts, Font, num_fonts, fonts_capacity, 5, true);
|
||||
fonts.fonts_count = 1;
|
||||
#define A(attr, bold, italic) { if(attr) { if (!init_font(&fonts.fonts[fonts.fonts_count], attr, bold, italic, false)) return NULL; fonts.attr##_font_idx = fonts.fonts_count++; } else fonts.attr##_font_idx = -1; }
|
||||
#define A(attr, bold, italic) { if(attr) { if (!init_font(&fonts.fonts[fonts.fonts_count], attr, bold, italic, false, false)) return NULL; fonts.attr##_font_idx = fonts.fonts_count++; } else fonts.attr##_font_idx = -1; }
|
||||
A(medium, false, false);
|
||||
A(bold, true, false); A(italic, false, true); A(bi, true, true);
|
||||
#undef A
|
||||
@@ -892,7 +945,7 @@ set_font(PyObject UNUSED *m, PyObject *args) {
|
||||
PyObject *face;
|
||||
int bold, italic;
|
||||
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, i), "Opp", &face, &bold, &italic)) return NULL;
|
||||
if (!init_font(fonts.fonts + fonts.fonts_count++, face, bold != 0, italic != 0, false)) return NULL;
|
||||
if (!init_font(fonts.fonts + fonts.fonts_count++, face, bold != 0, italic != 0, false, false)) return NULL;
|
||||
}
|
||||
for (size_t i = 0; i < fonts.symbol_maps_count; i++) {
|
||||
unsigned int left, right, font_idx;
|
||||
@@ -901,7 +954,7 @@ set_font(PyObject UNUSED *m, PyObject *args) {
|
||||
}
|
||||
fonts.first_fallback_font_idx = fonts.fonts_count;
|
||||
fonts.fallback_fonts_count = 0;
|
||||
return update_cell_metrics();
|
||||
return update_cell_metrics(false);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -935,7 +988,7 @@ static PyObject*
|
||||
test_sprite_position_for(PyObject UNUSED *self, PyObject *args) {
|
||||
glyph_index glyph;
|
||||
ExtraGlyphs extra_glyphs = {{0}};
|
||||
if (!PyArg_ParseTuple(args, "H|I", &glyph, &extra_glyphs.data)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "H|H", &glyph, &extra_glyphs.data)) return NULL;
|
||||
int error;
|
||||
SpritePosition *pos = sprite_position_for(&fonts.fonts[fonts.medium_font_idx], glyph, &extra_glyphs, 0, &error);
|
||||
if (pos == NULL) { sprite_map_set_error(error); return NULL; }
|
||||
@@ -1019,7 +1072,7 @@ concat_cells(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
current_fonts(PyObject UNUSED *self) {
|
||||
current_fonts(PYNOARG) {
|
||||
PyObject *ans = PyDict_New();
|
||||
if (!ans) return NULL;
|
||||
#define SET(key, val) {if (PyDict_SetItemString(ans, #key, fonts.fonts[val].face) != 0) { goto error; }}
|
||||
@@ -1050,7 +1103,7 @@ get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
||||
static Py_UCS4 char_buf[2 + arraysz(cell.cc_idx)];
|
||||
if (!PyUnicode_AsUCS4(text, char_buf, arraysz(char_buf), 1)) return NULL;
|
||||
cell.ch = char_buf[0];
|
||||
for (unsigned i = 0; i + 1 < PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
|
||||
for (unsigned i = 0; i + 1 < (unsigned) PyUnicode_GetLength(text) && i < arraysz(cell.cc_idx); i++) cell.cc_idx[i] = mark_for_codepoint(char_buf[i + 1]);
|
||||
if (bold) cell.attrs |= 1 << BOLD_SHIFT;
|
||||
if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
|
||||
ssize_t ans = fallback_font(&cell);
|
||||
|
||||
@@ -21,7 +21,7 @@ hb_font_t* harfbuzz_font_for_face(PyObject*);
|
||||
bool set_size_for_face(PyObject*, unsigned int, bool);
|
||||
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
|
||||
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored);
|
||||
PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic);
|
||||
PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic, bool emoji_presentation);
|
||||
PyObject* specialize_font_descriptor(PyObject *base_descriptor);
|
||||
PyObject* face_from_path(const char *path, int index);
|
||||
PyObject* face_from_descriptor(PyObject*);
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
from kitty.fast_data_types import coretext_all_fonts
|
||||
from kitty.utils import safe_print
|
||||
from kitty.utils import log_error
|
||||
|
||||
attr_map = {(False, False): 'font_family',
|
||||
(True, False): 'bold_font',
|
||||
@@ -63,10 +62,7 @@ def find_best_match(family, bold=False, italic=False):
|
||||
# Let CoreText choose the font if the family exists, otherwise
|
||||
# fallback to Menlo
|
||||
if q not in font_map['family_map']:
|
||||
safe_print(
|
||||
'The font {} was not found, falling back to Menlo'.format(family),
|
||||
file=sys.stderr
|
||||
)
|
||||
log_error('The font {} was not found, falling back to Menlo'.format(family))
|
||||
family = 'Menlo'
|
||||
return {
|
||||
'monospace': True,
|
||||
|
||||
@@ -10,7 +10,7 @@ from math import ceil, floor, pi, sin, sqrt
|
||||
from kitty.config import defaults
|
||||
from kitty.constants import is_macos
|
||||
from kitty.fast_data_types import (
|
||||
Screen, change_wcwidth, get_fallback_font, send_prerendered_sprites,
|
||||
Screen, get_fallback_font, send_prerendered_sprites,
|
||||
set_font, set_font_size, set_logical_dpi, set_options,
|
||||
set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line,
|
||||
test_shape
|
||||
@@ -61,9 +61,9 @@ def set_font_family(opts=None, override_font_size=None):
|
||||
return cell_width, cell_height
|
||||
|
||||
|
||||
def resize_fonts(new_sz):
|
||||
def resize_fonts(new_sz, on_dpi_change=False):
|
||||
s = set_font_family.state
|
||||
cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(new_sz)
|
||||
cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(new_sz, on_dpi_change)
|
||||
set_font_family.state = FontState(
|
||||
s.family, new_sz, cell_width, cell_height, baseline,
|
||||
underline_position, underline_thickness
|
||||
@@ -260,7 +260,6 @@ def test_fallback_font(qtext=None, bold=False, italic=False):
|
||||
|
||||
|
||||
def showcase():
|
||||
change_wcwidth(True)
|
||||
f = 'monospace' if is_macos else 'Liberation Mono'
|
||||
test_render_string('He\u0347\u0305llo\u0337, w\u0302or\u0306l\u0354d!', family=f)
|
||||
test_render_string('你好,世界', family=f)
|
||||
|
||||
@@ -214,15 +214,20 @@ ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_dat
|
||||
|
||||
PyObject*
|
||||
face_from_descriptor(PyObject *descriptor) {
|
||||
#define D(key, conv) { PyObject *t = PyDict_GetItemString(descriptor, #key); if (t == NULL) return NULL; key = conv(t); t = NULL; }
|
||||
char *path;
|
||||
long index;
|
||||
bool hinting;
|
||||
long hint_style;
|
||||
D(path, PyUnicode_AsUTF8);
|
||||
D(index, PyLong_AsLong);
|
||||
D(hinting, PyObject_IsTrue);
|
||||
D(hint_style, PyLong_AsLong);
|
||||
#define D(key, conv, missing_ok) { \
|
||||
PyObject *t = PyDict_GetItemString(descriptor, #key); \
|
||||
if (t == NULL) { \
|
||||
if (!missing_ok) { PyErr_SetString(PyExc_KeyError, "font descriptor is missing the key: " #key); return NULL; } \
|
||||
} else key = conv(t); \
|
||||
}
|
||||
char *path = NULL;
|
||||
long index = 0;
|
||||
bool hinting = false;
|
||||
long hint_style = 0;
|
||||
D(path, PyUnicode_AsUTF8, false);
|
||||
D(index, PyLong_AsLong, true);
|
||||
D(hinting, PyObject_IsTrue, true);
|
||||
D(hint_style, PyLong_AsLong, true);
|
||||
#undef D
|
||||
Face *self = (Face *)Face_Type.tp_alloc(&Face_Type, 0);
|
||||
if (self != NULL) {
|
||||
@@ -279,7 +284,7 @@ calc_cell_width(Face *self) {
|
||||
for (char_type i = 32; i < 128; i++) {
|
||||
int glyph_index = FT_Get_Char_Index(self->face, i);
|
||||
if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) {
|
||||
ans = MAX(ans, (unsigned long)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
|
||||
ans = MAX(ans, (unsigned int)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
@@ -324,7 +329,6 @@ typedef struct {
|
||||
unsigned int factor, right_edge;
|
||||
} ProcessedBitmap;
|
||||
|
||||
|
||||
static inline void
|
||||
trim_borders(ProcessedBitmap *ans, size_t extra) {
|
||||
bool column_has_text = false;
|
||||
@@ -354,10 +358,14 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
|
||||
ans->rows = bitmap->rows;
|
||||
ans->pixel_mode = bitmap->pixel_mode;
|
||||
if (ans->width > max_width) {
|
||||
size_t extra = bitmap->width - max_width;
|
||||
size_t extra = ans->width - max_width;
|
||||
if (italic && extra < cell_width / 2) {
|
||||
trim_borders(ans, extra);
|
||||
} else if (rescale && self->is_scalable && extra > MAX(2, cell_width / 4)) {
|
||||
} else if (extra == 2 && num_cells == 1) {
|
||||
// there exist fonts that have bitmaps just a couple of pixels
|
||||
// wider than their advances, rather than rescale, which looks
|
||||
// bad, we just crop the bitmap on the right. See https://github.com/kovidgoyal/kitty/issues/352
|
||||
} else if (rescale && self->is_scalable && extra > 1) {
|
||||
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height;
|
||||
float ar = (float)max_width / (float)bitmap->width;
|
||||
if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0)) {
|
||||
@@ -532,7 +540,8 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
display_name(Face *self) {
|
||||
display_name(PyObject *s, PyObject *a UNUSED) {
|
||||
Face *self = (Face*)s;
|
||||
const char *psname = FT_Get_Postscript_Name(self->face);
|
||||
if (psname) return Py_BuildValue("s", psname);
|
||||
Py_INCREF(self->path);
|
||||
@@ -540,8 +549,8 @@ display_name(Face *self) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
extra_data(Face *self) {
|
||||
return PyLong_FromVoidPtr(self->extra_data);
|
||||
extra_data(PyObject *self, PyObject *a UNUSED) {
|
||||
return PyLong_FromVoidPtr(((Face*)self)->extra_data);
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
6265
kitty/gl-wrapper.c
generated
6265
kitty/gl-wrapper.c
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user