Compare commits
382 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9f3dfc27da | ||
|
|
4b4f6bc928 | ||
|
|
273735f9dc | ||
|
|
b95e0fb80e | ||
|
|
6ad21fc98f | ||
|
|
bff9b03935 | ||
|
|
edf9413356 | ||
|
|
c74f4a8165 | ||
|
|
8201cd3288 | ||
|
|
1603310b86 | ||
|
|
627b91ffba | ||
|
|
9ad65f2e1d | ||
|
|
a6a606ff3d | ||
|
|
63317f9c78 | ||
|
|
46d9dfb614 | ||
|
|
be3b33af06 | ||
|
|
04e1b8b1ae | ||
|
|
213768b6e0 | ||
|
|
aff1abdb26 | ||
|
|
44a413df81 | ||
|
|
4f12c91fff | ||
|
|
b6188bf436 | ||
|
|
b9a14e0e4a | ||
|
|
6882c23f43 | ||
|
|
80301d465b | ||
|
|
32632264ee | ||
|
|
409bd37db5 | ||
|
|
5fd4bc965f | ||
|
|
e86e5ce1d8 | ||
|
|
5faa649452 | ||
|
|
2ddc7e8c80 | ||
|
|
ed700ff830 | ||
|
|
33ed873997 | ||
|
|
9125c00f96 | ||
|
|
73fe3293be | ||
|
|
804c4fbe19 | ||
|
|
d6c82ba21c | ||
|
|
67308f2ca1 | ||
|
|
4a17a946fb | ||
|
|
1ae7ae4a1d | ||
|
|
404ca23de3 | ||
|
|
d385952fef | ||
|
|
f03cac8a9d | ||
|
|
4b501fd694 | ||
|
|
872d39eb2c | ||
|
|
0341cae922 | ||
|
|
5357a9a1cc | ||
|
|
3695538a11 | ||
|
|
7780d52930 | ||
|
|
6a23b4ee47 | ||
|
|
f48b770036 | ||
|
|
81b5d2c4f2 | ||
|
|
d43d6ab0a9 | ||
|
|
5a4e883360 | ||
|
|
eb93cd4048 | ||
|
|
d83e3fddb0 | ||
|
|
80ca25e879 | ||
|
|
c86b34aacd | ||
|
|
8da426039b | ||
|
|
9184e9a28f | ||
|
|
578d537d5f | ||
|
|
236c92b2b4 | ||
|
|
66a46279f4 | ||
|
|
eb35edbe20 | ||
|
|
7cf2404331 | ||
|
|
9ccc6bf835 | ||
|
|
025a46bb35 | ||
|
|
677e8a1ae9 | ||
|
|
03702772d3 | ||
|
|
3cd744b247 | ||
|
|
88523b2692 | ||
|
|
dcb39eb210 | ||
|
|
102823809b | ||
|
|
23f2b29069 | ||
|
|
7b33a87725 | ||
|
|
6dde573ed9 | ||
|
|
566e641be1 | ||
|
|
08d5448523 | ||
|
|
c11a20f1ec | ||
|
|
e5b9bd2e5a | ||
|
|
6ad49bd7fb | ||
|
|
5d082d8d5a | ||
|
|
977575da73 | ||
|
|
bced6f31b1 | ||
|
|
6bf04e22cc | ||
|
|
2f9784809d | ||
|
|
3e85497c2c | ||
|
|
3b551a1dee | ||
|
|
d2fdb1c1eb | ||
|
|
db92af68e9 | ||
|
|
815087460c | ||
|
|
b4cb9d7c4d | ||
|
|
c6d10c6a23 | ||
|
|
464c7a80e2 | ||
|
|
4f56cce727 | ||
|
|
7dea6e0ef4 | ||
|
|
8c143db381 | ||
|
|
0cea6d5a1b | ||
|
|
eff881f4bb | ||
|
|
c64fd1760c | ||
|
|
9e361700d2 | ||
|
|
1f976644bf | ||
|
|
f3cb68ee40 | ||
|
|
85a3da057f | ||
|
|
c551384369 | ||
|
|
ce230b071b | ||
|
|
13d5f04ad9 | ||
|
|
3b861de3d2 | ||
|
|
1ef010689d | ||
|
|
3523ab283f | ||
|
|
5b2a9b101d | ||
|
|
fdcfcfa1b9 | ||
|
|
88e9c21a3b | ||
|
|
973352a98d | ||
|
|
f06f871dfc | ||
|
|
e03c713294 | ||
|
|
aa107956f9 | ||
|
|
d725578e1b | ||
|
|
239eb8202b | ||
|
|
58d7439719 | ||
|
|
cc145ec3dd | ||
|
|
847ce37160 | ||
|
|
0305009766 | ||
|
|
fe1b056371 | ||
|
|
0c4804db79 | ||
|
|
f7e84df332 | ||
|
|
bd654d22f4 | ||
|
|
115de36599 | ||
|
|
b01c3483de | ||
|
|
db7f566a0c | ||
|
|
9f806e76e7 | ||
|
|
12bdd4dddd | ||
|
|
72a1eb237d | ||
|
|
6a89289907 | ||
|
|
7ed835cf13 | ||
|
|
4621ff41d9 | ||
|
|
60e17d5224 | ||
|
|
1e2f7c705d | ||
|
|
1ebf0b95c4 | ||
|
|
c5acd94456 | ||
|
|
448ffe01c3 | ||
|
|
5c882f3290 | ||
|
|
70a4720bf9 | ||
|
|
c8aee8c881 | ||
|
|
c2ad0607b8 | ||
|
|
d1282b9f55 | ||
|
|
11ee317884 | ||
|
|
e69de2f968 | ||
|
|
2f4f3e3331 | ||
|
|
020f8a2047 | ||
|
|
b8faba2a16 | ||
|
|
0fcce6ec58 | ||
|
|
15f5ea92b8 | ||
|
|
2bbb5d25eb | ||
|
|
8d94285a5d | ||
|
|
eed11361c5 |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1,11 +1,13 @@
|
||||
kitty/wcwidth9.h linguist-generated=true
|
||||
kitty/wcwidth-std.h linguist-generated=true
|
||||
kitty/emoji.h linguist-generated=true
|
||||
kitty/keys.h linguist-generated=true
|
||||
kitty/charsets.c linguist-generated=true
|
||||
kitty/key_encoding.py linguist-generated=true
|
||||
kitty/unicode-data.c
|
||||
kitty/rgb.py linguist-generated=true
|
||||
kitty/gl-wrapper.* linguist-generated=true
|
||||
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
|
||||
|
||||
@@ -56,7 +56,7 @@ matrix:
|
||||
env:
|
||||
- RUN_FLAKE=1 BUILD_PKG=1
|
||||
language: python
|
||||
python: "3.5"
|
||||
python: "3.6"
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
@@ -94,8 +94,8 @@ install: |
|
||||
if [[ "$TRAVIS_OS_NAME" == 'osx' ]]; then
|
||||
if [[ "$USE_BREW" == "1" ]]; then
|
||||
brew update;
|
||||
brew install python3;
|
||||
brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype --without-cairo;
|
||||
brew upgrade python;
|
||||
brew install harfbuzz --without-glib --without-gobject-introspection --without-graphite2 --without-icu4c --without-freetype;
|
||||
else
|
||||
mkdir -p $SW;
|
||||
curl https://download.calibre-ebook.com/travis/kitty/osx.tar.xz | tar xJ -C $SW;
|
||||
@@ -123,6 +123,7 @@ before_script:
|
||||
- $PYTHON setup.py build --debug --verbose $SANITIZE_ARG;
|
||||
|
||||
script:
|
||||
- if grep -Inr '\s$' kitty kitty_tests *.py *.asciidoc .gitattributes .gitignore; then echo Trailing whitespace found, aborting.; exit 1; fi
|
||||
- if [[ -z $SANITIZE_ARG ]]; then $PYTHON test.py; else ./asan-launcher test.py; fi
|
||||
- if [[ "$RUN_FLAKE" == "1" ]]; then flake8 --count .; fi
|
||||
- if [[ "$BUILD_PKG" == "1" ]]; then $PYTHON setup.py linux-package; fi
|
||||
|
||||
185
CHANGELOG.rst
185
CHANGELOG.rst
@@ -3,6 +3,191 @@ Changelog
|
||||
|
||||
kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator.
|
||||
|
||||
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]
|
||||
---------------------------
|
||||
|
||||
- Add an option to adjust the width of character cells
|
||||
|
||||
- Fix selecting text with the mouse in the scrollback buffer selecting text
|
||||
from the line above the actually selected line
|
||||
|
||||
- Fix some italic fonts having the right edge of characters cut-off,
|
||||
unnecessarily
|
||||
|
||||
|
||||
version 0.7.0 [2018-01-24]
|
||||
---------------------------
|
||||
|
||||
- Allow controlling kitty from the shell prompt/scripts. You can
|
||||
open/close/rename windows and tabs and even send input to specific windows.
|
||||
See the README for details.
|
||||
|
||||
- Add option to put tab bar at the top instead of the bottom
|
||||
|
||||
- Add option to override the default shell
|
||||
|
||||
- Add "Horizontal" and "Vertical" window layouts
|
||||
|
||||
- Sessions: Allow setting titles and working directories for individual windows
|
||||
|
||||
- Option to copy to clipboard on mouse select
|
||||
|
||||
- Fix incorrect reporting of mouse move events when using the SGR protocol
|
||||
|
||||
- Make alt+backspace delete the previous word
|
||||
|
||||
- Take the mouse wheel multiplier option in to account when generating fake key
|
||||
scroll events
|
||||
|
||||
- macOS: Fix closing top-level window does not transfer focus to other
|
||||
top-level windows.
|
||||
|
||||
- macOS: Fix alt+arrow keys not working when disabling the macos_option_as_alt
|
||||
config option.
|
||||
|
||||
- kitty icat: Workaround for bug in ImageMagick that would cause some images
|
||||
to fail to display at certain sizes.
|
||||
|
||||
- Fix rendering of text with ligature fonts that do not use dummy glyphs
|
||||
|
||||
- Fix a regression that caused copying of the selection to clipboard to only
|
||||
copy the visible part of the selection
|
||||
|
||||
- Fix incorrect handling of some unicode combining marks that are not re-ordered
|
||||
|
||||
- Fix handling on non-BMP combining characters
|
||||
|
||||
- Drop the dependency on libunistring
|
||||
|
||||
|
||||
version 0.6.1 [2017-12-28]
|
||||
---------------------------
|
||||
|
||||
- Add an option to fade the text in inactive windows
|
||||
|
||||
- Add new actions to open windows/tabs/etc. with the working directory set to
|
||||
the working directory of the current window.
|
||||
|
||||
- Automatically adjust cell size when DPI changes, for example when kitty is
|
||||
moved from one monitor to another with a different DPI
|
||||
|
||||
- Ensure underlines are rendered even for fonts with very poor metrics
|
||||
|
||||
- Fix some emoji glyphs not colored on Linux
|
||||
|
||||
- Internal wcwidth() implementation is now auto-generated from the unicode
|
||||
standard database
|
||||
|
||||
- Allow configuring the modifiers to use for rectangular selection with the
|
||||
mouse.
|
||||
|
||||
- Fix incorrect minimum wayland version in the build script
|
||||
|
||||
- Fix a crash when detecting a URL that ends at the end of the line
|
||||
|
||||
- Fix regression that broke drawing of hollow cursor when window loses focus
|
||||
|
||||
|
||||
version 0.6.0 [2017-12-18]
|
||||
---------------------------
|
||||
|
||||
|
||||
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.
|
||||
11
Makefile
11
Makefile
@@ -21,3 +21,14 @@ debug:
|
||||
# Build with the ASAN and UBSAN sanitizers
|
||||
asan:
|
||||
python3 setup.py build $(VVAL) --debug --sanitize
|
||||
|
||||
profile:
|
||||
python3 setup.py build $(VVAL) --profile
|
||||
|
||||
logo/kitty.iconset/icon_256x256.png: logo/kitty.svg logo/make.py
|
||||
logo/make.py
|
||||
|
||||
rendered_logo: logo/kitty.iconset/icon_256x256.png
|
||||
|
||||
app: rendered_logo
|
||||
python3 setup.py kitty.app $(VVAL)
|
||||
|
||||
260
README.asciidoc
260
README.asciidoc
@@ -6,11 +6,13 @@
|
||||
: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_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 +31,7 @@
|
||||
: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_simple_kitten_text_url_hints: pass:quotes[`ctrl+shift+e`]
|
||||
: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`]
|
||||
@@ -51,30 +54,32 @@ image::https://travis-ci.org/kovidgoyal/kitty.svg?branch=master[Build status, li
|
||||
|
||||
== Major Features
|
||||
|
||||
* Supports tiling multiple terminal windows side by side in different
|
||||
layouts without needing to use an extra program like tmux
|
||||
* 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 startup sessions which allow you to specify the window/tab layout,
|
||||
working directories and programs to run on startup.
|
||||
* Supports tiling multiple terminal windows side by side in different
|
||||
link:#layouts[layouts] without needing to use an extra program like tmux
|
||||
|
||||
* Uses OpenGL for rendering, offloads rendering to the GPU for lower system
|
||||
load and buttery smooth scrolling. Uses threaded rendering to minimize input
|
||||
latency.
|
||||
* Can be link:remote-control.asciidoc[controlled from scripts or the shell prompt], even over SSH.
|
||||
|
||||
* 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[URL 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.
|
||||
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.
|
||||
|
||||
* Easily hackable (UI layer written in python, inner loops in C for
|
||||
speed). Less than fifteen thousand lines of code.
|
||||
|
||||
image::screenshot.png?raw=true[Screenshot, showing three programs in the "Tall" layout]
|
||||
|
||||
@@ -89,8 +94,7 @@ the following dependencies are installed first.
|
||||
=== Dependencies
|
||||
|
||||
* python >= 3.5
|
||||
* harfbuzz >= 1.5.0
|
||||
* libunistring
|
||||
* harfbuzz >= 1.5.0
|
||||
* zlib
|
||||
* libpng
|
||||
* freetype (not needed on macOS)
|
||||
@@ -98,6 +102,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`, `libgl1-mesa` and `xkbcommon-x11`, if they are not already installed by your distro.
|
||||
|
||||
=== Install and run from source
|
||||
|
||||
@@ -108,21 +113,24 @@ git clone https://github.com/kovidgoyal/kitty && cd kitty
|
||||
Now build the C parts of kitty with the following command:
|
||||
|
||||
....
|
||||
python3 setup.py build
|
||||
make
|
||||
....
|
||||
|
||||
You can run kitty, as:
|
||||
|
||||
....
|
||||
python3 /path/to/kitty/folder
|
||||
python3 .
|
||||
....
|
||||
|
||||
=== Linux packages
|
||||
|
||||
* Arch linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
|
||||
* Arch Linux: AUR git package https://aur.archlinux.org/packages/kitty-git/
|
||||
|
||||
* Arch Linux: AUR stable package https://aur.archlinux.org/packages/kitty/
|
||||
|
||||
* NixOS / nixpkgs: https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/misc/kitty/default.nix
|
||||
|
||||
* Gentoo: https://packages.gentoo.org/packages/x11-terms/kitty
|
||||
|
||||
=== macOS packages
|
||||
|
||||
@@ -222,42 +230,83 @@ windows are:
|
||||
|
||||
|Increase font size | {sc_increase_font_size}
|
||||
|Decrease font size | {sc_decrease_font_size}
|
||||
|Restore font size | {sc_restore_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}
|
||||
|===
|
||||
|
||||
|
||||
== Controlling kitty from scripts
|
||||
|
||||
You can control kitty from the command line/scripts by sending it messages.
|
||||
You can tell kitty to open/close/rename tabs and windows. You can even send
|
||||
arbitrary input/text to any specified window. Messages can be sent using `kitty
|
||||
@`. Note that you must set `allow_remote_control yes` in your
|
||||
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.
|
||||
|
||||
== URL hints
|
||||
|
||||
kitty has a _hints mode_ to click URLs visible on the screen by using only the keyboard.
|
||||
Press {sc_run_simple_kitten_text_url_hints} to activate hints mode, as shown below.
|
||||
|
||||
image::hints_mode.png?raw=true[URL hints mode]
|
||||
|
||||
== 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.
|
||||
* Tall -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
|
||||
* Grid -- All windows are shown in a grid
|
||||
* 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
|
||||
@@ -266,13 +315,13 @@ 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]. By default kitty looks for a config file in the OS config
|
||||
directory (usually `~/.config/kitty/kitty.conf` on Linux and
|
||||
`~/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.
|
||||
|
||||
== Startup Sessions
|
||||
|
||||
@@ -283,11 +332,13 @@ command line flag. For example:
|
||||
....
|
||||
# Set the window layout for the current tab
|
||||
layout tall
|
||||
# Set the working directory for the current tab
|
||||
# Set the working directory for windows in the current tab
|
||||
cd ~
|
||||
# Create a window and run the specified command in it
|
||||
launch zsh
|
||||
launch vim
|
||||
# Set the title for the next window
|
||||
title Chat with x
|
||||
launch irssi --profile x
|
||||
|
||||
# Create a new tab (the part after new_tab is the optional tab name which will
|
||||
@@ -333,18 +384,20 @@ 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 the
|
||||
`--profile` flag which will create an executable called `kitty-profile`. Run
|
||||
that and perform the task you want to analyse, for example, scrolling a large
|
||||
link:https://github.com/gperftools/gperftools[gperftools]. Build kitty with
|
||||
`make profile` which will create an executable called `kitty-profile`. Run
|
||||
that and perform the task you want to analyse, for example, scrolling a large
|
||||
file with `less`. After you quit, function call statistics will be printed to
|
||||
`stdout` and you can use tools like *kcachegrind* for more detailed analysis.
|
||||
|
||||
Here are some CPU usage numbers for the task of scrolling a file continuously in less.
|
||||
The CPU usage is for the terminal process and X together and is measured using htop.
|
||||
The measurements are taken at the same font and window size for all terminals on a
|
||||
`Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz` CPU with a
|
||||
The measurements are taken at the same font and window size for all terminals on a
|
||||
`Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz` CPU with a
|
||||
`Advanced Micro Devices, Inc. [AMD/ATI] Cape Verde XT [Radeon HD 7770/8760 / R7 250X]` GPU.
|
||||
|
||||
|===
|
||||
@@ -393,76 +446,85 @@ 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?
|
||||
|
||||
You dont need to build kitty.app to run kitty from source. Simply install the dependencies
|
||||
listed above using brew/MacPorts or similar and then compile and run with:
|
||||
Install `optipng` and `librsvg` using `brew` or similar (needed for the logo generation step).
|
||||
|
||||
And run:
|
||||
```
|
||||
make
|
||||
python3 .
|
||||
make app
|
||||
```
|
||||
|
||||
You can also create a shell script to run kitty:
|
||||
This `kitty.app` unlike the released one does not include its own copy of
|
||||
python and the other dependencies. So if you ever un-install/upgrade those dependencies
|
||||
you might have to rebuild the app.
|
||||
|
||||
```
|
||||
#!/bin/sh
|
||||
exec python3 /path/to/kitty/source/dir
|
||||
```
|
||||
|
||||
And using something like link:https://mathiasbynens.be/notes/shell-script-mac-apps[app-wrapper-for-shell-script]
|
||||
you can create a simple launcher app to run the shell script.
|
||||
|
||||
If you only want to make some small changes to a python file, you can do that
|
||||
and simply replace the corresponding file inside an existing kitty.app. (Note
|
||||
that kitty.app will contain `.pyo` files which are compiled versions of `.py`
|
||||
files, delete the `.pyo` file and replace it with a `.py` file).
|
||||
|
||||
Finally, note that the released kitty.dmg is built automatically by using the
|
||||
`kitty` branch of
|
||||
Note that the released kitty.dmg includes all dependencies, unlike the
|
||||
`kitty.app` built above and is built automatically by using the `kitty` branch of
|
||||
link:https://github.com/kovidgoyal/build-calibre[build-calibre] however, that
|
||||
is designed to run on linux and is not for the faint of heart.
|
||||
is designed to run on Linux and is not for the faint of heart.
|
||||
|
||||
== A tribute
|
||||
|
||||
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:
|
||||
|
||||
....
|
||||
ssh myserver mkdir -p \~/.terminfo
|
||||
infocmp xterm-kitty | ssh myserver "cat > /tmp/kitty.terminfo"
|
||||
ssh myserver tic -x -o \~/.terminfo /tmp/kitty.terminfo
|
||||
....
|
||||
|
||||
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.
|
||||
|
||||
|
||||
=== 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
|
||||
`~/Library/Preferences/kitty/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
|
||||
|
||||
@@ -470,4 +532,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/
|
||||
|
||||
65
__main__.py
65
__main__.py
@@ -4,12 +4,65 @@
|
||||
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'icat':
|
||||
|
||||
def icat(args):
|
||||
from kitty.icat import main
|
||||
main(sys.argv[1:])
|
||||
elif len(sys.argv) > 1 and sys.argv[1] == 'list-fonts':
|
||||
main(args)
|
||||
|
||||
|
||||
def list_fonts(args):
|
||||
from kitty.fonts.list import main
|
||||
main(sys.argv[1:])
|
||||
else:
|
||||
from kitty.main import main
|
||||
main(args)
|
||||
|
||||
|
||||
def remote_control(args):
|
||||
from kitty.remote_control import main
|
||||
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 namespaced(args):
|
||||
func = namespaced_entry_points[args[1]]
|
||||
func(args[1:])
|
||||
|
||||
|
||||
entry_points = {
|
||||
# These two are here for backwards compat
|
||||
'icat': icat,
|
||||
'list-fonts': list_fonts,
|
||||
'runpy': runpy,
|
||||
'launch': launch,
|
||||
|
||||
'@': remote_control,
|
||||
'+': namespaced,
|
||||
}
|
||||
namespaced_entry_points = {k: v for k, v in entry_points.items() if k[0] not in '+@'}
|
||||
|
||||
|
||||
def main():
|
||||
first_arg = '' if len(sys.argv) < 2 else sys.argv[1]
|
||||
func = entry_points.get(first_arg)
|
||||
if func is None:
|
||||
if first_arg.startswith('@'):
|
||||
remote_control(['@', first_arg[1:]] + sys.argv[2:])
|
||||
elif first_arg.startswith('+'):
|
||||
namespaced(['+', first_arg[1:]] + sys.argv[2:])
|
||||
else:
|
||||
from kitty.main import main
|
||||
main()
|
||||
else:
|
||||
func(sys.argv[1:])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -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/wcwidth9.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h') kitty
|
||||
cloc --exclude-list-file <(echo -e 'kitty/wcwidth-std.h\nkitty/glfw.c\nkitty/keys.h\nkitty/charsets.c\nkitty/unicode-data.c\nkitty/key_encoding.py\nkitty/rgb.py\nkitty/gl.h\nkitty/gl-wrapper.h\nkitty/gl-wrapper.c\nkitty/khrplatform.h\nkitty/glfw-wrapper.h\nkitty/glfw-wrapper.c\nkitty/emoji.h\nkittens/unicode_input/names.h') kitty kittens
|
||||
|
||||
75
gen-emoji.py
75
gen-emoji.py
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from functools import partial
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
from urllib.request import urlopen
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
raw = urlopen('http://unicode.org/Public/emoji/5.0/emoji-data.txt').read().decode('utf-8')
|
||||
seen = set()
|
||||
cmap = defaultdict(set)
|
||||
for line in raw.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
spec, rest = line.partition(';')[::2]
|
||||
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
|
||||
if '.' in spec:
|
||||
spec = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
|
||||
spec = set(range(spec[0], spec[1] + 1))
|
||||
else:
|
||||
spec = {int(spec, 16)}
|
||||
cmap[rest] |= spec
|
||||
seen |= spec
|
||||
items = list(seen)
|
||||
|
||||
|
||||
def get_ranges(items):
|
||||
items.sort()
|
||||
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
|
||||
group = tuple(map(itemgetter(1), g))
|
||||
a, b = group[0], group[-1]
|
||||
if a == b:
|
||||
yield a
|
||||
else:
|
||||
yield a, b
|
||||
|
||||
|
||||
def write_case(spec, p):
|
||||
if isinstance(spec, tuple):
|
||||
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
|
||||
else:
|
||||
p('\t\tcase 0x{:x}:'.format(spec))
|
||||
|
||||
|
||||
with open('kitty/emoji.h', 'w') as f:
|
||||
p = partial(print, file=f)
|
||||
p('#pragma once')
|
||||
p('#include "data-types.h"\n')
|
||||
p('START_ALLOW_CASE_RANGE')
|
||||
p('static inline bool is_emoji(uint32_t code) {')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(items):
|
||||
last = spec[1] if isinstance(spec, tuple) else spec
|
||||
if last < 0x231a:
|
||||
continue
|
||||
write_case(spec, p)
|
||||
p('\t\t\treturn true;')
|
||||
p('\t\tdefault: return false;')
|
||||
p('\t}')
|
||||
p('\treturn false; \n}')
|
||||
p('static inline bool is_emoji_modifier(uint32_t code) {')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(cmap['Emoji_Modifier'])):
|
||||
write_case(spec, p)
|
||||
p('\t\t\treturn true;')
|
||||
p('\t\tdefault: return false;')
|
||||
p('\t}')
|
||||
p('\treturn false; \n}')
|
||||
p('END_ALLOW_CASE_RANGE')
|
||||
354
gen-wcwidth.py
Executable file
354
gen-wcwidth.py
Executable file
@@ -0,0 +1,354 @@
|
||||
#!/usr/bin/env python3
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
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
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def get_data(fname, folder='UCD'):
|
||||
url = f'https://www.unicode.org/Public/{folder}/latest/{fname}'
|
||||
bn = os.path.basename(url)
|
||||
local = os.path.join('/tmp', bn)
|
||||
if os.path.exists(local):
|
||||
data = open(local, 'rb').read()
|
||||
else:
|
||||
data = urlopen(url).read()
|
||||
open(local, 'wb').write(data)
|
||||
for line in data.decode('utf-8').splitlines():
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#'):
|
||||
yield line
|
||||
|
||||
|
||||
# Map of class names to set of codepoints in class
|
||||
class_maps = {}
|
||||
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
|
||||
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]
|
||||
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]
|
||||
codepoints = (codepoint,)
|
||||
if first is None:
|
||||
if desc.endswith(', First>'):
|
||||
first = codepoint
|
||||
continue
|
||||
else:
|
||||
codepoints = range(first, codepoint + 1)
|
||||
first = None
|
||||
for codepoint in codepoints:
|
||||
s.add(codepoint)
|
||||
not_assigned.discard(codepoint)
|
||||
if category.startswith('M'):
|
||||
marks.add(codepoint)
|
||||
|
||||
|
||||
def split_two(line):
|
||||
spec, rest = line.split(';', 1)
|
||||
spec, rest = spec.strip(), rest.strip().split(' ', 1)[0].strip()
|
||||
if '..' in spec:
|
||||
chars = tuple(map(lambda x: int(x, 16), filter(None, spec.split('.'))))
|
||||
chars = set(range(chars[0], chars[1] + 1))
|
||||
else:
|
||||
chars = {int(spec, 16)}
|
||||
return chars, rest
|
||||
|
||||
|
||||
all_emoji = set()
|
||||
emoji_categories = {}
|
||||
emoji_presentation_bases = set()
|
||||
|
||||
|
||||
def parse_emoji():
|
||||
for line in get_data('emoji-data.txt', 'emoji'):
|
||||
chars, rest = split_two(line)
|
||||
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()
|
||||
|
||||
|
||||
def parse_eaw():
|
||||
global doublewidth, ambiguous
|
||||
seen = set()
|
||||
for line in get_data('ucd/EastAsianWidth.txt'):
|
||||
chars, eaw = split_two(line)
|
||||
if eaw == 'A':
|
||||
ambiguous |= chars
|
||||
seen |= chars
|
||||
elif eaw == 'W' or eaw == 'F':
|
||||
doublewidth |= chars
|
||||
seen |= chars
|
||||
doublewidth |= set(range(0x3400, 0x4DBF + 1)) - seen
|
||||
doublewidth |= set(range(0x4E00, 0x9FFF + 1)) - seen
|
||||
doublewidth |= set(range(0xF900, 0xFAFF + 1)) - seen
|
||||
doublewidth |= set(range(0x20000, 0x2FFFD + 1)) - seen
|
||||
doublewidth |= set(range(0x30000, 0x3FFFD + 1)) - seen
|
||||
|
||||
|
||||
def get_ranges(items):
|
||||
items.sort()
|
||||
for k, g in groupby(enumerate(items), lambda m: m[0]-m[1]):
|
||||
group = tuple(map(itemgetter(1), g))
|
||||
a, b = group[0], group[-1]
|
||||
if a == b:
|
||||
yield a
|
||||
else:
|
||||
yield a, b
|
||||
|
||||
|
||||
def write_case(spec, p):
|
||||
if isinstance(spec, tuple):
|
||||
p('\t\tcase 0x{:x} ... 0x{:x}:'.format(*spec))
|
||||
else:
|
||||
p('\t\tcase 0x{:x}:'.format(spec))
|
||||
|
||||
|
||||
@contextmanager
|
||||
def create_header(path, 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')
|
||||
if include_data_types:
|
||||
p('#include "data-types.h"\n')
|
||||
p('START_ALLOW_CASE_RANGE')
|
||||
p()
|
||||
yield p
|
||||
p()
|
||||
if include_data_types:
|
||||
p('END_ALLOW_CASE_RANGE')
|
||||
f.close()
|
||||
|
||||
|
||||
def gen_emoji():
|
||||
with create_header('kitty/emoji.h') as p:
|
||||
p('static inline bool\nis_emoji(char_type code) {')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(all_emoji)):
|
||||
write_case(spec, p)
|
||||
p('\t\t\treturn true;')
|
||||
p('\t\tdefault: return false;')
|
||||
p('\t}')
|
||||
p('\treturn false;\n}')
|
||||
p('static inline bool\nis_emoji_modifier(char_type code) {')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(emoji_categories['Emoji_Modifier'])):
|
||||
write_case(spec, p)
|
||||
p('\t\t\treturn true;')
|
||||
p('\t\tdefault: return false;')
|
||||
p('\t}')
|
||||
p('\treturn false;\n}')
|
||||
|
||||
|
||||
def category_test(name, p, classes, comment, static=False):
|
||||
static = 'static inline ' if static else ''
|
||||
chars = set()
|
||||
for c in classes:
|
||||
chars |= class_maps[c]
|
||||
p(f'{static}bool\n{name}(char_type code) {{')
|
||||
p(f'\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||
p('\tswitch(code) {')
|
||||
for spec in get_ranges(list(chars)):
|
||||
write_case(spec, p)
|
||||
p(f'\t\t\treturn true;')
|
||||
p('\t} // }}}\n')
|
||||
p('\treturn false;\n}\n')
|
||||
|
||||
|
||||
def 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]]
|
||||
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} // }}}')
|
||||
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, cz, 'C and Z categories')
|
||||
category_test('is_P_category', p, {c for c in class_maps if c[0] == 'P'}, 'P category (punctuation)')
|
||||
mark_map = [0] + list(sorted(marks))
|
||||
p('char_type codepoint_for_mark(combining_type m) {')
|
||||
p(f'\tstatic char_type map[{len(mark_map)}] =', '{', ', '.join(map(str, mark_map)), '}; // {{{ mapping }}}')
|
||||
p('\tif (m < arraysz(map)) return map[m];')
|
||||
p('\treturn 0;')
|
||||
p('}\n')
|
||||
p('combining_type mark_for_codepoint(char_type c) {')
|
||||
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/url_hints/url_regex.py', 'w') as f:
|
||||
f.write("url_delimiters = '{}' # noqa".format(''.join(classes_to_regex(cz))))
|
||||
|
||||
|
||||
def gen_names():
|
||||
words = tuple(sorted(word_search_map))
|
||||
|
||||
with create_header('kittens/unicode_input/names.h') as p:
|
||||
cp_map = list(sorted(name_map))
|
||||
p(f'static const char* name_map[{len(cp_map)}] = {{' ' // {{{')
|
||||
for cp in cp_map:
|
||||
w = name_map[cp].replace('"', '\\"')
|
||||
p(f'\t"{w}",')
|
||||
p("}; // }}}\n")
|
||||
|
||||
p(f'static const char* idx_to_word[{len(words)}] = ' '{ // {{{')
|
||||
for s in words:
|
||||
s = s.replace('"', '\\"')
|
||||
p(f'\t"{s}",')
|
||||
p("}; // }}}\n")
|
||||
|
||||
first_letters = {ord(w[0]) for w in words if ord(w[0]) < 256}
|
||||
wmap = {w: i for i, w in enumerate(words)}
|
||||
p(f'static const unsigned short* words_for_first_letter[256] = ' '{ // {{{')
|
||||
for fl in range(0, 256):
|
||||
if fl in first_letters:
|
||||
winds = [str(wmap[w]) for w in words if w.startswith(chr(fl))]
|
||||
p(f'\t(const unsigned short[{len(winds) + 1}]){{{len(winds)}, ', ', '.join(winds), '},')
|
||||
else:
|
||||
p('NULL,')
|
||||
p("}; // }}}\n")
|
||||
|
||||
p(f'static const char_type* codepoints_for_word_idx[{len(words)}] = ' '{ // {{{')
|
||||
for s in words:
|
||||
cps = word_search_map[s]
|
||||
a = ', '.join(map(str, cps))
|
||||
p(f'\t(const char_type[{len(cps) + 1}]){{{len(cps)}, ', a, '},')
|
||||
p("}; // }}}\n")
|
||||
|
||||
p('static char_type mark_for_codepoint(char_type c) {')
|
||||
codepoint_to_mark_map(p, cp_map)
|
||||
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')
|
||||
|
||||
|
||||
def gen_wcwidth():
|
||||
seen = set()
|
||||
|
||||
def add(p, comment, chars_, ret):
|
||||
chars = chars_ - seen
|
||||
seen.update(chars)
|
||||
p(f'\t\t// {comment} ({len(chars)} codepoints)' + ' {{' '{')
|
||||
for spec in get_ranges(list(chars)):
|
||||
write_case(spec, p)
|
||||
p(f'\t\t\treturn {ret};')
|
||||
p('\t\t// }}}\n')
|
||||
|
||||
with create_header('kitty/wcwidth-std.h') as p:
|
||||
p('static int\nwcwidth_std(int32_t code) {')
|
||||
p('\tswitch(code) {')
|
||||
|
||||
non_printing = class_maps['Cc'] | class_maps['Cf'] | class_maps['Cs']
|
||||
add(p, 'Marks', marks | {0}, 0)
|
||||
add(p, 'Non-printing characters', non_printing, -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 Presentation', emoji_categories['Emoji_Presentation'], 2)
|
||||
|
||||
add(p, 'Not assigned in the unicode character database', not_assigned, -1)
|
||||
|
||||
p('\t\tdefault: return 1;')
|
||||
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()
|
||||
parse_eaw()
|
||||
gen_ucd()
|
||||
gen_wcwidth()
|
||||
gen_emoji()
|
||||
gen_names()
|
||||
@@ -264,7 +264,7 @@ void _glfwPollMonitorsNS(void)
|
||||
const CGSize size = CGDisplayScreenSize(displays[i]);
|
||||
char* name = getDisplayName(displays[i]);
|
||||
if (!name)
|
||||
name = strdup("Unknown");
|
||||
name = _glfw_strdup("Unknown");
|
||||
|
||||
monitor = _glfwAllocMonitor(name, size.width, size.height);
|
||||
monitor->ns.displayID = displays[i];
|
||||
@@ -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);
|
||||
|
||||
5
glfw/cocoa_platform.h
vendored
5
glfw/cocoa_platform.h
vendored
@@ -88,9 +88,10 @@ typedef struct _GLFWwindowNS
|
||||
|
||||
GLFWbool maximized;
|
||||
|
||||
// Cached window and framebuffer sizes used to filter out duplicate events
|
||||
// Cached window properties to filter out duplicate events
|
||||
int width, height;
|
||||
int fbWidth, fbHeight;
|
||||
float xscale, yscale;
|
||||
|
||||
// The total sum of the distances the cursor has been warped
|
||||
// since the last cursor motion event was processed
|
||||
@@ -163,6 +164,6 @@ 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);
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
#define NSEventModifierFlagControl NSControlKeyMask
|
||||
#define NSEventModifierFlagOption NSAlternateKeyMask
|
||||
#define NSEventModifierFlagShift NSShiftKeyMask
|
||||
#define NSEventModifierFlagCapsLock NSAlphaShiftKeyMask
|
||||
#define NSEventModifierFlagDeviceIndependentFlagsMask NSDeviceIndependentModifierFlagsMask
|
||||
#define NSEventMaskAny NSAnyEventMask
|
||||
#define NSEventTypeApplicationDefined NSApplicationDefined
|
||||
@@ -137,9 +138,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),
|
||||
@@ -149,7 +150,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
|
||||
@@ -177,6 +177,8 @@ static int translateFlags(NSUInteger flags)
|
||||
mods |= GLFW_MOD_ALT;
|
||||
if (flags & NSEventModifierFlagCommand)
|
||||
mods |= GLFW_MOD_SUPER;
|
||||
if (flags & NSEventModifierFlagCapsLock)
|
||||
mods |= GLFW_MOD_CAPS_LOCK;
|
||||
|
||||
return mods;
|
||||
}
|
||||
@@ -571,6 +573,16 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
window->ns.fbHeight = fbRect.size.height;
|
||||
_glfwInputFramebufferSize(window, fbRect.size.width, fbRect.size.height);
|
||||
}
|
||||
|
||||
const float xscale = fbRect.size.width / contentRect.size.width;
|
||||
const float yscale = fbRect.size.height / contentRect.size.height;
|
||||
|
||||
if (xscale != window->ns.xscale || yscale != window->ns.yscale)
|
||||
{
|
||||
window->ns.xscale = xscale;
|
||||
window->ns.yscale = yscale;
|
||||
_glfwInputWindowContentScale(window, xscale, yscale);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)drawRect:(NSRect)rect
|
||||
@@ -694,7 +706,7 @@ static const NSRange kEmptyRange = { NSNotFound, 0 };
|
||||
NSUInteger i;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
paths[i] = strdup([[e nextObject] UTF8String]);
|
||||
paths[i] = _glfw_strdup([[e nextObject] UTF8String]);
|
||||
|
||||
_glfwInputDrop(window, (int) count, (const char**) paths);
|
||||
|
||||
@@ -1087,8 +1099,8 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
[window->ns.object zoom:nil];
|
||||
}
|
||||
|
||||
if (wndconfig->ns.frame)
|
||||
[window->ns.object setFrameAutosaveName:[NSString stringWithUTF8String:wndconfig->title]];
|
||||
if (strlen(wndconfig->ns.frameName))
|
||||
[window->ns.object setFrameAutosaveName:[NSString stringWithUTF8String:wndconfig->ns.frameName]];
|
||||
|
||||
window->ns.view = [[GLFWContentView alloc] initWithGlfwWindow:window];
|
||||
|
||||
@@ -1159,11 +1171,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;
|
||||
@@ -1405,6 +1413,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)
|
||||
@@ -1475,6 +1484,20 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
return [window->ns.object isZoomed];
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
{
|
||||
const NSPoint point = [NSEvent mouseLocation];
|
||||
|
||||
if ([NSWindow windowNumberAtPoint:point belowWindowWithWindowNumber:0] !=
|
||||
[window->ns.object windowNumber])
|
||||
{
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
return NSPointInRect(point,
|
||||
[window->ns.object convertRectToScreen:[window->ns.view bounds]]);
|
||||
}
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
return ![window->ns.object isOpaque] && ![window->ns.view isOpaque];
|
||||
@@ -1511,6 +1534,9 @@ void _glfwPlatformSetWindowOpacity(_GLFWwindow* window, float opacity)
|
||||
|
||||
void _glfwPlatformPollEvents(void)
|
||||
{
|
||||
if (!initializeAppKit())
|
||||
return;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny
|
||||
@@ -1786,7 +1812,7 @@ const char* _glfwPlatformGetClipboardString(void)
|
||||
}
|
||||
|
||||
free(_glfw.ns.clipboardString);
|
||||
_glfw.ns.clipboardString = strdup([object UTF8String]);
|
||||
_glfw.ns.clipboardString = _glfw_strdup([object UTF8String]);
|
||||
|
||||
return _glfw.ns.clipboardString;
|
||||
}
|
||||
|
||||
55
glfw/context.c
vendored
55
glfw/context.c
vendored
@@ -38,8 +38,24 @@
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Checks whether the desired context attributes are valid
|
||||
//
|
||||
// This function checks things like whether the specified client API version
|
||||
// exists and whether all relevant options have supported and non-conflicting
|
||||
// values
|
||||
//
|
||||
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)
|
||||
@@ -155,6 +171,8 @@ GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig)
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
// Chooses the framebuffer config that best matches the desired one
|
||||
//
|
||||
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
|
||||
const _GLFWfbconfig* alternatives,
|
||||
unsigned int count)
|
||||
@@ -321,10 +339,13 @@ const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
|
||||
return closest;
|
||||
}
|
||||
|
||||
GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
// Retrieves the attributes of the current context
|
||||
//
|
||||
GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig)
|
||||
{
|
||||
int i;
|
||||
_GLFWwindow* window;
|
||||
_GLFWwindow* previous;
|
||||
const char* version;
|
||||
const char* prefixes[] =
|
||||
{
|
||||
@@ -334,11 +355,12 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
NULL
|
||||
};
|
||||
|
||||
window = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
|
||||
window->context.source = ctxconfig->source;
|
||||
window->context.client = GLFW_OPENGL_API;
|
||||
|
||||
previous = _glfwPlatformGetTls(&_glfw.contextSlot);;
|
||||
glfwMakeContextCurrent((GLFWwindow*) window);
|
||||
|
||||
window->context.GetIntegerv = (PFNGLGETINTEGERVPROC)
|
||||
window->context.getProcAddress("glGetIntegerv");
|
||||
window->context.GetString = (PFNGLGETSTRINGPROC)
|
||||
@@ -346,6 +368,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
if (!window->context.GetIntegerv || !window->context.GetString)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR, "Entry point retrieval is broken");
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -363,6 +386,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
"OpenGL ES version string retrieval is broken");
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -394,6 +418,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
"No version found in OpenGL ES version string");
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -423,6 +448,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
window->context.major, window->context.minor);
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -438,6 +464,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Entry point retrieval is broken");
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
}
|
||||
@@ -544,9 +571,12 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
|
||||
window->context.swapBuffers(window);
|
||||
}
|
||||
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
// Searches an extension string for the specified extension
|
||||
//
|
||||
GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions)
|
||||
{
|
||||
const char* start = extensions;
|
||||
@@ -587,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;
|
||||
}
|
||||
|
||||
@@ -616,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;
|
||||
}
|
||||
|
||||
@@ -632,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;
|
||||
}
|
||||
|
||||
@@ -649,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;
|
||||
}
|
||||
|
||||
@@ -714,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;
|
||||
}
|
||||
|
||||
|
||||
15
glfw/glfw.py
15
glfw/glfw.py
@@ -52,7 +52,7 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
ans.ldpaths.extend(('-framework', f))
|
||||
|
||||
elif module == 'wayland':
|
||||
at_least_version('wayland-protocols', 1, 1)
|
||||
at_least_version('wayland-protocols', *sinfo['wayland_protocols'])
|
||||
ans.wayland_packagedir = os.path.abspath(pkg_config('wayland-protocols', '--variable=pkgdatadir')[0])
|
||||
ans.wayland_scanner = os.path.abspath(pkg_config('wayland-scanner', '--variable=wayland_scanner')[0])
|
||||
ans.wayland_protocols = tuple(sinfo[module]['protocols'])
|
||||
@@ -69,6 +69,8 @@ def init_env(env, pkg_config, at_least_version, module='x11'):
|
||||
def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
|
||||
for protocol in env.wayland_protocols:
|
||||
src = os.path.join(env.wayland_packagedir, protocol)
|
||||
if not os.path.exists(src):
|
||||
raise SystemExit('The wayland-protocols package on your system is missing the {} protocol definition file'.format(protocol))
|
||||
for ext in 'hc':
|
||||
dest = wayland_protocol_file_name(src, ext)
|
||||
dest = os.path.join(dest_dir, dest)
|
||||
@@ -80,6 +82,7 @@ def build_wayland_protocols(env, run_tool, emphasis, newer, dest_dir):
|
||||
|
||||
def collect_source_information():
|
||||
raw = open('src/CMakeLists.txt').read()
|
||||
mraw = open('CMakeLists.txt').read()
|
||||
|
||||
def extract_sources(group, start_pos=0):
|
||||
for which in 'HEADERS SOURCES'.split():
|
||||
@@ -91,8 +94,11 @@ def collect_source_information():
|
||||
).group(1).strip().split()
|
||||
)
|
||||
|
||||
wayland_protocols = re.search('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,
|
||||
}
|
||||
for group in 'cocoa win32 x11 wayland osmesa'.split():
|
||||
m = re.search('_GLFW_' + group.upper(), raw)
|
||||
@@ -242,9 +248,10 @@ def main():
|
||||
sinfo = collect_source_information()
|
||||
files_to_copy = set()
|
||||
for x in sinfo.values():
|
||||
headers, sources = x['headers'], x['sources']
|
||||
for name in headers + sources:
|
||||
files_to_copy.add(os.path.abspath(os.path.join('src', name)))
|
||||
if isinstance(x, dict):
|
||||
headers, sources = x['headers'], x['sources']
|
||||
for name in headers + sources:
|
||||
files_to_copy.add(os.path.abspath(os.path.join('src', name)))
|
||||
glfw_header = os.path.abspath('include/GLFW/glfw3.h')
|
||||
glfw_native_header = os.path.abspath('include/GLFW/glfw3native.h')
|
||||
os.chdir(base)
|
||||
|
||||
366
glfw/glfw3.h
vendored
366
glfw/glfw3.h
vendored
@@ -493,17 +493,37 @@ extern "C" {
|
||||
* @{ */
|
||||
|
||||
/*! @brief If this bit is set one or more Shift keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Shift keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_SHIFT 0x0001
|
||||
/*! @brief If this bit is set one or more Control keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Control keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_CONTROL 0x0002
|
||||
/*! @brief If this bit is set one or more Alt keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Alt keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_ALT 0x0004
|
||||
/*! @brief If this bit is set one or more Super keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Super keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_SUPER 0x0008
|
||||
/*! @brief If this bit is set the Caps Lock key is enabled.
|
||||
*
|
||||
* If this bit is set the Caps Lock key is enabled and the @ref
|
||||
* GLFW_LOCK_KEY_MODS input mode is set.
|
||||
*/
|
||||
#define GLFW_MOD_CAPS_LOCK 0x0010
|
||||
/*! @brief If this bit is set the Num Lock key is enabled.
|
||||
*
|
||||
* If this bit is set the Num Lock key is enabled and the @ref
|
||||
* GLFW_LOCK_KEY_MODS input mode is set.
|
||||
*/
|
||||
#define GLFW_MOD_NUM_LOCK 0x0020
|
||||
|
||||
/*! @} */
|
||||
|
||||
@@ -794,6 +814,11 @@ extern "C" {
|
||||
* [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib).
|
||||
*/
|
||||
#define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A
|
||||
/*! @brief Mouse cursor hover window attribute.
|
||||
*
|
||||
* Mouse cursor hover [window attribute](@ref GLFW_HOVERED_attrib).
|
||||
*/
|
||||
#define GLFW_HOVERED 0x0002000B
|
||||
|
||||
/*! @brief Framebuffer bit depth hint.
|
||||
*
|
||||
@@ -944,8 +969,11 @@ extern "C" {
|
||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||
|
||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||
#define GLFW_COCOA_FRAME_AUTOSAVE 0x00023002
|
||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
|
||||
|
||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_NO_API 0
|
||||
@@ -963,6 +991,7 @@ extern "C" {
|
||||
#define GLFW_CURSOR 0x00033001
|
||||
#define GLFW_STICKY_KEYS 0x00033002
|
||||
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
|
||||
#define GLFW_LOCK_KEY_MODS 0x00033004
|
||||
|
||||
#define GLFW_CURSOR_NORMAL 0x00034001
|
||||
#define GLFW_CURSOR_HIDDEN 0x00034002
|
||||
@@ -1025,9 +1054,6 @@ extern "C" {
|
||||
|
||||
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
||||
#define GLFW_COCOA_MENUBAR 0x00051002
|
||||
|
||||
#define GLFW_X11_WM_CLASS_NAME 0x00052001
|
||||
#define GLFW_X11_WM_CLASS_CLASS 0x00052002
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_DONT_CARE -1
|
||||
@@ -1046,7 +1072,7 @@ extern "C" {
|
||||
* @sa @ref glfwGetProcAddress
|
||||
*
|
||||
* @since Added in version 3.0.
|
||||
|
||||
*
|
||||
* @ingroup context
|
||||
*/
|
||||
typedef void (*GLFWglproc)(void);
|
||||
@@ -1257,6 +1283,24 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
|
||||
*/
|
||||
typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
|
||||
|
||||
/*! @brief The function signature for window content scale callbacks.
|
||||
*
|
||||
* This is the function signature for window content scale callback
|
||||
* functions.
|
||||
*
|
||||
* @param[in] window The window whose content scale changed.
|
||||
* @param[in] xscale The new x-axis content scale of the window.
|
||||
* @param[in] yscale The new y-axis content scale of the window.
|
||||
*
|
||||
* @sa @ref window_scale
|
||||
* @sa @ref glfwSetWindowContentScaleCallback
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
|
||||
|
||||
/*! @brief The function signature for mouse button callbacks.
|
||||
*
|
||||
* This is the function signature for mouse button callback functions.
|
||||
@@ -1414,7 +1458,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
|
||||
@@ -1431,7 +1476,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
|
||||
@@ -1623,8 +1669,7 @@ GLFWAPI void glfwTerminate(void);
|
||||
|
||||
/*! @brief Sets the specified init hint to the desired value.
|
||||
*
|
||||
* This function sets hints for the next initialization of GLFW. Only integer
|
||||
* type hints can be set with this function.
|
||||
* This function sets hints for the next initialization of GLFW.
|
||||
*
|
||||
* The values you set hints to are never reset by GLFW, but they only take
|
||||
* effect during initialization. Once GLFW has been initialized, any values
|
||||
@@ -1633,7 +1678,7 @@ GLFWAPI void glfwTerminate(void);
|
||||
*
|
||||
* Some hints are platform specific. These may be set on any platform but they
|
||||
* will only affect their specific platform. Other platforms will ignore them.
|
||||
* Setting these hints requires no platform specific headers or functions.
|
||||
* Setting these hints requires no platform specific headers or functions.
|
||||
*
|
||||
* @param[in] hint The [init hint](@ref init_hints) to set.
|
||||
* @param[in] value The new value of the init hint.
|
||||
@@ -1647,7 +1692,6 @@ GLFWAPI void glfwTerminate(void);
|
||||
*
|
||||
* @sa init_hints
|
||||
* @sa glfwInit
|
||||
* @sa glfwInitHintString
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
@@ -1655,40 +1699,6 @@ GLFWAPI void glfwTerminate(void);
|
||||
*/
|
||||
GLFWAPI void glfwInitHint(int hint, int value);
|
||||
|
||||
/*! @brief Sets the specified init hint to the desired value.
|
||||
*
|
||||
* This function sets hints for the next initialization of GLFW. Only string
|
||||
* type hints can be set with this function.
|
||||
*
|
||||
* The values you set hints to are never reset by GLFW, but they only take
|
||||
* effect during initialization. Once GLFW has been initialized, any values
|
||||
* you set will be ignored until the library is terminated and initialized
|
||||
* again.
|
||||
*
|
||||
* Some hints are platform specific. These may be set on any platform but they
|
||||
* will only affect their specific platform. Other platforms will ignore them.
|
||||
* Setting these hints requires no platform specific headers or functions.
|
||||
*
|
||||
* @param[in] hint The [init hint](@ref init_hints) to set.
|
||||
* @param[in] value The new value of the init hint.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_INVALID_ENUM and @ref
|
||||
* GLFW_INVALID_VALUE.
|
||||
*
|
||||
* @remarks This function may be called before @ref glfwInit.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa init_hints
|
||||
* @sa glfwInit
|
||||
* @sa glfwInitHint
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup init
|
||||
*/
|
||||
GLFWAPI void glfwInitHintString(int hint, const char* value);
|
||||
|
||||
/*! @brief Retrieves the version of the GLFW library.
|
||||
*
|
||||
* This function retrieves the major, minor and revision numbers of the GLFW
|
||||
@@ -1985,6 +1995,56 @@ GLFWAPI void glfwGetMonitorContentScale(GLFWmonitor* monitor, float* xscale, flo
|
||||
*/
|
||||
GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* monitor);
|
||||
|
||||
/*! @brief Sets the user pointer of the specified monitor.
|
||||
*
|
||||
* This function sets the user-defined pointer of the specified monitor. The
|
||||
* current value is retained until the monitor is disconnected. The initial
|
||||
* value is `NULL`.
|
||||
*
|
||||
* This function may be called from the monitor callback, even for a monitor
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] monitor The monitor whose pointer to set.
|
||||
* @param[in] pointer The new value.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread. Access is not
|
||||
* synchronized.
|
||||
*
|
||||
* @sa @ref monitor_userptr
|
||||
* @sa @ref glfwGetMonitorUserPointer
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup monitor
|
||||
*/
|
||||
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* monitor, void* pointer);
|
||||
|
||||
/*! @brief Returns the user pointer of the specified monitor.
|
||||
*
|
||||
* This function returns the current value of the user-defined pointer of the
|
||||
* specified monitor. The initial value is `NULL`.
|
||||
*
|
||||
* This function may be called from the monitor callback, even for a monitor
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] monitor The monitor whose pointer to return.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread. Access is not
|
||||
* synchronized.
|
||||
*
|
||||
* @sa @ref monitor_userptr
|
||||
* @sa @ref glfwSetMonitorUserPointer
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup monitor
|
||||
*/
|
||||
GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* monitor);
|
||||
|
||||
/*! @brief Sets the monitor configuration callback.
|
||||
*
|
||||
* This function sets the monitor configuration callback, or removes the
|
||||
@@ -2184,6 +2244,7 @@ GLFWAPI void glfwSetGammaRamp(GLFWmonitor* monitor, const GLFWgammaramp* ramp);
|
||||
*
|
||||
* @sa @ref window_hints
|
||||
* @sa @ref glfwWindowHint
|
||||
* @sa @ref glfwWindowHintString
|
||||
*
|
||||
* @since Added in version 3.0.
|
||||
*
|
||||
@@ -2194,14 +2255,20 @@ GLFWAPI void glfwDefaultWindowHints(void);
|
||||
/*! @brief Sets the specified window hint to the desired value.
|
||||
*
|
||||
* This function sets hints for the next call to @ref glfwCreateWindow. The
|
||||
* hints, once set, retain their values until changed by a call to @ref
|
||||
* glfwWindowHint or @ref glfwDefaultWindowHints, or until the library is
|
||||
* terminated.
|
||||
* hints, once set, retain their values until changed by a call to this
|
||||
* function or @ref glfwDefaultWindowHints, or until the library is terminated.
|
||||
*
|
||||
* Only integer value hints can be set with this function. String value hints
|
||||
* are set with @ref glfwWindowHintString.
|
||||
*
|
||||
* This function does not check whether the specified hint values are valid.
|
||||
* If you set hints to invalid values this will instead be reported by the next
|
||||
* call to @ref glfwCreateWindow.
|
||||
*
|
||||
* Some hints are platform specific. These may be set on any platform but they
|
||||
* will only affect their specific platform. Other platforms will ignore them.
|
||||
* Setting these hints requires no platform specific headers or functions.
|
||||
*
|
||||
* @param[in] hint The [window hint](@ref window_hints) to set.
|
||||
* @param[in] value The new value of the window hint.
|
||||
*
|
||||
@@ -2211,6 +2278,7 @@ GLFWAPI void glfwDefaultWindowHints(void);
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_hints
|
||||
* @sa @ref glfwWindowHintString
|
||||
* @sa @ref glfwDefaultWindowHints
|
||||
*
|
||||
* @since Added in version 3.0. Replaces `glfwOpenWindowHint`.
|
||||
@@ -2219,6 +2287,44 @@ GLFWAPI void glfwDefaultWindowHints(void);
|
||||
*/
|
||||
GLFWAPI void glfwWindowHint(int hint, int value);
|
||||
|
||||
/*! @brief Sets the specified window hint to the desired value.
|
||||
*
|
||||
* This function sets hints for the next call to @ref glfwCreateWindow. The
|
||||
* hints, once set, retain their values until changed by a call to this
|
||||
* function or @ref glfwDefaultWindowHints, or until the library is terminated.
|
||||
*
|
||||
* Only string type hints can be set with this function. Integer value hints
|
||||
* are set with @ref glfwWindowHint.
|
||||
*
|
||||
* This function does not check whether the specified hint values are valid.
|
||||
* If you set hints to invalid values this will instead be reported by the next
|
||||
* call to @ref glfwCreateWindow.
|
||||
*
|
||||
* Some hints are platform specific. These may be set on any platform but they
|
||||
* will only affect their specific platform. Other platforms will ignore them.
|
||||
* Setting these hints requires no platform specific headers or functions.
|
||||
*
|
||||
* @param[in] hint The [window hint](@ref window_hints) to set.
|
||||
* @param[in] value The new value of the window hint.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_INVALID_ENUM.
|
||||
*
|
||||
* @pointer_lifetime The specified string is copied before this function
|
||||
* returns.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_hints
|
||||
* @sa @ref glfwWindowHint
|
||||
* @sa @ref glfwDefaultWindowHints
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
GLFWAPI void glfwWindowHintString(int hint, const char* value);
|
||||
|
||||
/*! @brief Creates a window and its associated context.
|
||||
*
|
||||
* This function creates a window and its associated OpenGL or OpenGL ES
|
||||
@@ -2329,9 +2435,8 @@ GLFWAPI void glfwWindowHint(int hint, int value);
|
||||
* `CMake/MacOSXBundleInfo.plist.in` in the source tree.
|
||||
*
|
||||
* @remark @macos When activating frame autosaving with
|
||||
* [GLFW_COCOA_FRAME_AUTOSAVE](@ref GLFW_COCOA_FRAME_AUTOSAVE_hint), the
|
||||
* specified window size may be overriden by a previously saved size and
|
||||
* position.
|
||||
* [GLFW_COCOA_FRAME_NAME](@ref GLFW_COCOA_FRAME_NAME_hint), the specified
|
||||
* window size and position may be overriden by previously saved values.
|
||||
*
|
||||
* @remark @x11 Some window managers will not respect the placement of
|
||||
* initially hidden windows.
|
||||
@@ -2341,28 +2446,22 @@ GLFWAPI void glfwWindowHint(int hint, int value);
|
||||
* query the final size, position or other attributes directly after window
|
||||
* creation.
|
||||
*
|
||||
* @remark @x11 The name and class of the `WM_CLASS` window property will by
|
||||
* default be set to the window title passed to this function. Set the @ref
|
||||
* GLFW_X11_WM_CLASS_NAME and @ref GLFW_X11_WM_CLASS_CLASS init hints before
|
||||
* initialization to override this. You can also set the title int he following
|
||||
* special format, which allows setting the two parts of the WM_CLASS property
|
||||
* and the window title independently:
|
||||
* <01> WM_CLASS name <30> WM_CLASS class <30> title
|
||||
* Here <01> refers to the byte value 01 (ASCII start-of-header) and <30> refers
|
||||
* to the byte value 30 (ASCII record separator).
|
||||
* @remark @x11 The class part of the `WM_CLASS` window property will by
|
||||
* default be set to the window title passed to this function. The instance
|
||||
* part will use the contents of the `RESOURCE_NAME` environment variable, if
|
||||
* present and not empty, or fall back to the window title. Set the @ref
|
||||
* 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.
|
||||
*
|
||||
* @remark @wayland The wl_shell protocol does not support window
|
||||
* icons, the window will inherit the one defined in the application's
|
||||
* desktop file, so this function emits @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark @wayland Screensaver inhibition requires the idle-inhibit protocol
|
||||
* to be implemented in the user's compositor.
|
||||
*
|
||||
@@ -2506,9 +2605,9 @@ GLFWAPI void glfwSetWindowTitle(GLFWwindow* window, const char* title);
|
||||
* [Bundle Programming Guide](https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFBundles/)
|
||||
* in the Mac Developer Library.
|
||||
*
|
||||
* @remark @wayland The wl_shell protocol does not support icons, the window
|
||||
* will inherit the one defined in the application's desktop file, so this
|
||||
* function emits @ref GLFW_PLATFORM_ERROR.
|
||||
* @remark @wayland There is no existing protocol to change an icon, the
|
||||
* window will thus inherit the one defined in the application's desktop file.
|
||||
* This function always emits @ref GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -2800,10 +2899,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
|
||||
@@ -2836,6 +2931,7 @@ GLFWAPI void glfwGetWindowFrameSize(GLFWwindow* window, int* left, int* top, int
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_scale
|
||||
* @sa @ref glfwSetWindowContentScaleCallback
|
||||
* @sa @ref glfwGetMonitorContentScale
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
@@ -2915,7 +3011,8 @@ GLFWAPI void glfwSetWindowOpacity(GLFWwindow* window, float opacity);
|
||||
* GLFW_PLATFORM_ERROR.
|
||||
*
|
||||
* @remark @wayland There is no concept of iconification in wl_shell, this
|
||||
* function will always emit @ref GLFW_PLATFORM_ERROR.
|
||||
* function will emit @ref GLFW_PLATFORM_ERROR when using this deprecated
|
||||
* protocol.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -3237,7 +3334,7 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* window, int attrib);
|
||||
* The supported attributes are [GLFW_DECORATED](@ref GLFW_DECORATED_attrib),
|
||||
* [GLFW_RESIZABLE](@ref GLFW_RESIZABLE_attrib),
|
||||
* [GLFW_FLOATING](@ref GLFW_FLOATING_attrib) and
|
||||
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib).
|
||||
* [GLFW_AUTO_ICONIFY](@ref GLFW_AUTO_ICONIFY_attrib).
|
||||
*
|
||||
* Some of these attributes are ignored for full screen windows. The new
|
||||
* value will take effect if the window is later made windowed.
|
||||
@@ -3467,7 +3564,7 @@ GLFWAPI GLFWwindowfocusfun glfwSetWindowFocusCallback(GLFWwindow* window, GLFWwi
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @remark @wayland The wl_shell protocol has no concept of iconification,
|
||||
* this callback will never be called.
|
||||
* this callback will never be called when using this deprecated protocol.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
@@ -3525,6 +3622,30 @@ GLFWAPI GLFWwindowmaximizefun glfwSetWindowMaximizeCallback(GLFWwindow* window,
|
||||
*/
|
||||
GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* window, GLFWframebuffersizefun cbfun);
|
||||
|
||||
/*! @brief Sets the window content scale callback for the specified window.
|
||||
*
|
||||
* This function sets the window content scale callback of the specified window,
|
||||
* which is called when the content scale of the specified window changes.
|
||||
*
|
||||
* @param[in] window The window whose callback to set.
|
||||
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
|
||||
* callback.
|
||||
* @return The previously set callback, or `NULL` if no callback was set or the
|
||||
* library had not been [initialized](@ref intro_init).
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function must only be called from the main thread.
|
||||
*
|
||||
* @sa @ref window_scale
|
||||
* @sa @ref glfwGetWindowContentScale
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* window, GLFWwindowcontentscalefun cbfun);
|
||||
|
||||
/*! @brief Processes all pending events.
|
||||
*
|
||||
* This function processes only those events that are already in the event
|
||||
@@ -3689,12 +3810,12 @@ GLFWAPI void glfwPostEmptyEvent(void);
|
||||
/*! @brief Returns the value of an input option for the specified window.
|
||||
*
|
||||
* This function returns the value of an input option for the specified window.
|
||||
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS or
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS.
|
||||
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
|
||||
*
|
||||
* @param[in] window The window to query.
|
||||
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS`.
|
||||
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
|
||||
* GLFW_INVALID_ENUM.
|
||||
@@ -3712,8 +3833,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
|
||||
/*! @brief Sets an input option for the specified window.
|
||||
*
|
||||
* This function sets an input mode option for the specified window. The mode
|
||||
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS or
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS.
|
||||
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
|
||||
* @ref GLFW_STICKY_MOUSE_BUTTONS or @ref GLFW_LOCK_KEY_MODS.
|
||||
*
|
||||
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
|
||||
* modes:
|
||||
@@ -3739,9 +3860,15 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
|
||||
* you are only interested in whether mouse buttons have been pressed but not
|
||||
* when or in which order.
|
||||
*
|
||||
* If the mode is `GLFW_LOCK_KEY_MODS`, the value must be either `GLFW_TRUE` to
|
||||
* enable lock key modifier bits, or `GLFW_FALSE` to disable them. If enabled,
|
||||
* callbacks that receive modifier bits will also have the @ref
|
||||
* GLFW_MOD_CAPS_LOCK bit set when the event was generated with Caps Lock on,
|
||||
* and the @ref GLFW_MOD_NUM_LOCK bit when Num Lock was on.
|
||||
*
|
||||
* @param[in] window The window whose input mode to set.
|
||||
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS` or
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS`.
|
||||
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
|
||||
* `GLFW_STICKY_MOUSE_BUTTONS` or `GLFW_LOCK_KEY_MODS`.
|
||||
* @param[in] value The new value of the specified input mode.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
|
||||
@@ -4481,10 +4608,10 @@ GLFWAPI const unsigned char* glfwGetJoystickButtons(int jid, int* count);
|
||||
* it with the corresponding direction.
|
||||
*
|
||||
* @code
|
||||
* if (hats[2] & GLFW_HAT_RIGHT)
|
||||
* {
|
||||
* // State of hat 2 could be right-up, right or right-down
|
||||
* }
|
||||
* if (hats[2] & GLFW_HAT_RIGHT)
|
||||
* {
|
||||
* // State of hat 2 could be right-up, right or right-down
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* If the specified joystick is not present this function will return `NULL`
|
||||
@@ -4588,6 +4715,56 @@ GLFWAPI const char* glfwGetJoystickName(int jid);
|
||||
*/
|
||||
GLFWAPI const char* glfwGetJoystickGUID(int jid);
|
||||
|
||||
/*! @brief Sets the user pointer of the specified joystick.
|
||||
*
|
||||
* This function sets the user-defined pointer of the specified joystick. The
|
||||
* current value is retained until the joystick is disconnected. The initial
|
||||
* value is `NULL`.
|
||||
*
|
||||
* This function may be called from the joystick callback, even for a joystick
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] joystick The joystick whose pointer to set.
|
||||
* @param[in] pointer The new value.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread. Access is not
|
||||
* synchronized.
|
||||
*
|
||||
* @sa @ref joystick_userptr
|
||||
* @sa @ref glfwGetJoystickUserPointer
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer);
|
||||
|
||||
/*! @brief Returns the user pointer of the specified joystick.
|
||||
*
|
||||
* This function returns the current value of the user-defined pointer of the
|
||||
* specified joystick. The initial value is `NULL`.
|
||||
*
|
||||
* This function may be called from the joystick callback, even for a joystick
|
||||
* that is being disconnected.
|
||||
*
|
||||
* @param[in] joystick The joystick whose pointer to return.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
*
|
||||
* @thread_safety This function may be called from any thread. Access is not
|
||||
* synchronized.
|
||||
*
|
||||
* @sa @ref joystick_userptr
|
||||
* @sa @ref glfwSetJoystickUserPointer
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup input
|
||||
*/
|
||||
GLFWAPI void* glfwGetJoystickUserPointer(int jid);
|
||||
|
||||
/*! @brief Returns whether the specified joystick has a gamepad mapping.
|
||||
*
|
||||
* This function returns whether the specified joystick is both present and has
|
||||
@@ -4864,7 +5041,7 @@ GLFWAPI void glfwSetTime(double time);
|
||||
* 1 / frequency seconds. To get the frequency, call @ref
|
||||
* glfwGetTimerFrequency.
|
||||
*
|
||||
* @return The value of the timer, or zero if an
|
||||
* @return The value of the timer, or zero if an
|
||||
* [error](@ref error_handling) occurred.
|
||||
*
|
||||
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED.
|
||||
@@ -5290,6 +5467,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
|
||||
@@ -5305,7 +5487,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
|
||||
|
||||
36
glfw/glx_context.c
vendored
36
glfw/glx_context.c
vendored
@@ -223,7 +223,7 @@ static GLFWglproc getProcAddressGLX(const char* procname)
|
||||
else if (_glfw.glx.GetProcAddressARB)
|
||||
return _glfw.glx.GetProcAddressARB((const GLubyte*) procname);
|
||||
else
|
||||
return dlsym(_glfw.glx.handle, procname);
|
||||
return _glfw_dlsym(_glfw.glx.handle, procname);
|
||||
}
|
||||
|
||||
// Destroy the OpenGL context
|
||||
@@ -271,7 +271,7 @@ GLFWbool _glfwInitGLX(void)
|
||||
|
||||
for (i = 0; sonames[i]; i++)
|
||||
{
|
||||
_glfw.glx.handle = dlopen(sonames[i], RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.glx.handle = _glfw_dlopen(sonames[i]);
|
||||
if (_glfw.glx.handle)
|
||||
break;
|
||||
}
|
||||
@@ -283,35 +283,35 @@ GLFWbool _glfwInitGLX(void)
|
||||
}
|
||||
|
||||
_glfw.glx.GetFBConfigs =
|
||||
dlsym(_glfw.glx.handle, "glXGetFBConfigs");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXGetFBConfigs");
|
||||
_glfw.glx.GetFBConfigAttrib =
|
||||
dlsym(_glfw.glx.handle, "glXGetFBConfigAttrib");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXGetFBConfigAttrib");
|
||||
_glfw.glx.GetClientString =
|
||||
dlsym(_glfw.glx.handle, "glXGetClientString");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXGetClientString");
|
||||
_glfw.glx.QueryExtension =
|
||||
dlsym(_glfw.glx.handle, "glXQueryExtension");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXQueryExtension");
|
||||
_glfw.glx.QueryVersion =
|
||||
dlsym(_glfw.glx.handle, "glXQueryVersion");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXQueryVersion");
|
||||
_glfw.glx.DestroyContext =
|
||||
dlsym(_glfw.glx.handle, "glXDestroyContext");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXDestroyContext");
|
||||
_glfw.glx.MakeCurrent =
|
||||
dlsym(_glfw.glx.handle, "glXMakeCurrent");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXMakeCurrent");
|
||||
_glfw.glx.SwapBuffers =
|
||||
dlsym(_glfw.glx.handle, "glXSwapBuffers");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXSwapBuffers");
|
||||
_glfw.glx.QueryExtensionsString =
|
||||
dlsym(_glfw.glx.handle, "glXQueryExtensionsString");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXQueryExtensionsString");
|
||||
_glfw.glx.CreateNewContext =
|
||||
dlsym(_glfw.glx.handle, "glXCreateNewContext");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXCreateNewContext");
|
||||
_glfw.glx.CreateWindow =
|
||||
dlsym(_glfw.glx.handle, "glXCreateWindow");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXCreateWindow");
|
||||
_glfw.glx.DestroyWindow =
|
||||
dlsym(_glfw.glx.handle, "glXDestroyWindow");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXDestroyWindow");
|
||||
_glfw.glx.GetProcAddress =
|
||||
dlsym(_glfw.glx.handle, "glXGetProcAddress");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXGetProcAddress");
|
||||
_glfw.glx.GetProcAddressARB =
|
||||
dlsym(_glfw.glx.handle, "glXGetProcAddressARB");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXGetProcAddressARB");
|
||||
_glfw.glx.GetVisualFromFBConfig =
|
||||
dlsym(_glfw.glx.handle, "glXGetVisualFromFBConfig");
|
||||
_glfw_dlsym(_glfw.glx.handle, "glXGetVisualFromFBConfig");
|
||||
|
||||
if (!_glfw.glx.GetFBConfigs ||
|
||||
!_glfw.glx.GetFBConfigAttrib ||
|
||||
@@ -428,7 +428,7 @@ void _glfwTerminateGLX(void)
|
||||
|
||||
if (_glfw.glx.handle)
|
||||
{
|
||||
dlclose(_glfw.glx.handle);
|
||||
_glfw_dlclose(_glfw.glx.handle);
|
||||
_glfw.glx.handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
46
glfw/init.c
vendored
46
glfw/init.c
vendored
@@ -35,8 +35,9 @@
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
// The global variables below comprise all global data in GLFW.
|
||||
// Any other global variable is a bug.
|
||||
// The global variables below comprise all mutable global data in GLFW
|
||||
//
|
||||
// Any other global variable is a bug
|
||||
|
||||
// Global state shared between compilation units of GLFW
|
||||
//
|
||||
@@ -53,10 +54,6 @@ static _GLFWinitconfig _glfwInitHints =
|
||||
{
|
||||
GLFW_TRUE, // macOS menu bar
|
||||
GLFW_TRUE // macOS bundle chdir
|
||||
},
|
||||
{
|
||||
"", // X11 WM_CLASS name
|
||||
"" // X11 WM_CLASS class
|
||||
}
|
||||
};
|
||||
|
||||
@@ -141,10 +138,25 @@ static void terminate(void)
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
char* _glfw_strdup(const char* source)
|
||||
{
|
||||
const size_t length = strlen(source);
|
||||
char* result = calloc(length + 1, 1);
|
||||
strcpy(result, source);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW event API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Notifies shared code of an error
|
||||
//
|
||||
void _glfwInputError(int code, const char* format, ...)
|
||||
{
|
||||
_GLFWerror* error;
|
||||
@@ -260,27 +272,7 @@ GLFWAPI void glfwInitHint(int hint, int value)
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_INVALID_ENUM,
|
||||
"Invalid integer type init hint 0x%08X", hint);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwInitHintString(int hint, const char* value)
|
||||
{
|
||||
assert(value != NULL);
|
||||
|
||||
switch (hint)
|
||||
{
|
||||
case GLFW_X11_WM_CLASS_NAME:
|
||||
strncpy(_glfwInitHints.x11.className, value,
|
||||
sizeof(_glfwInitHints.x11.className) - 1);
|
||||
return;
|
||||
case GLFW_X11_WM_CLASS_CLASS:
|
||||
strncpy(_glfwInitHints.x11.classClass, value,
|
||||
sizeof(_glfwInitHints.x11.classClass) - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_INVALID_ENUM,
|
||||
"Invalid string type init hint 0x%08X", hint);
|
||||
"Invalid init hint 0x%08X", hint);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwGetVersion(int* major, int* minor, int* rev)
|
||||
|
||||
212
glfw/input.c
vendored
212
glfw/input.c
vendored
@@ -57,6 +57,58 @@ static _GLFWmapping* findMapping(const char* guid)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Checks whether a gamepad mapping element is present in the hardware
|
||||
//
|
||||
static GLFWbool isValidElementForJoystick(const _GLFWmapelement* e,
|
||||
const _GLFWjoystick* js)
|
||||
{
|
||||
if (e->type == _GLFW_JOYSTICK_HATBIT && (e->index >> 4) >= js->hatCount)
|
||||
return GLFW_FALSE;
|
||||
else if (e->type == _GLFW_JOYSTICK_BUTTON && e->index >= js->buttonCount)
|
||||
return GLFW_FALSE;
|
||||
else if (e->type == _GLFW_JOYSTICK_AXIS && e->index >= js->axisCount)
|
||||
return GLFW_FALSE;
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
// Finds a mapping based on joystick GUID and verifies element indices
|
||||
//
|
||||
static _GLFWmapping* findValidMapping(const _GLFWjoystick* js)
|
||||
{
|
||||
_GLFWmapping* mapping = findMapping(js->guid);
|
||||
if (mapping)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++)
|
||||
{
|
||||
if (!isValidElementForJoystick(mapping->buttons + i, js))
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_VALUE,
|
||||
"Invalid button in gamepad mapping %s (%s)",
|
||||
mapping->guid,
|
||||
mapping->name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++)
|
||||
{
|
||||
if (!isValidElementForJoystick(mapping->axes + i, js))
|
||||
{
|
||||
_glfwInputError(GLFW_INVALID_VALUE,
|
||||
"Invalid axis in gamepad mapping %s (%s)",
|
||||
mapping->guid,
|
||||
mapping->name);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
// Parses an SDL_GameControllerDB line and adds it to the mapping list
|
||||
//
|
||||
static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
|
||||
@@ -115,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);
|
||||
@@ -125,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
|
||||
{
|
||||
@@ -172,6 +255,8 @@ static GLFWbool parseMapping(_GLFWmapping* mapping, const char* string)
|
||||
////// GLFW event API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Notifies shared code of a physical key event
|
||||
//
|
||||
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
if (key >= 0 && key <= GLFW_KEY_LAST)
|
||||
@@ -193,15 +278,24 @@ 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);
|
||||
|
||||
@@ -212,17 +306,24 @@ void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWb
|
||||
}
|
||||
}
|
||||
|
||||
// Notifies shared code of a scroll event
|
||||
//
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset)
|
||||
{
|
||||
if (window->callbacks.scroll)
|
||||
window->callbacks.scroll((GLFWwindow*) window, xoffset, yoffset);
|
||||
}
|
||||
|
||||
// Notifies shared code of a mouse button click event
|
||||
//
|
||||
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods)
|
||||
{
|
||||
if (button < 0 || button > GLFW_MOUSE_BUTTON_LAST)
|
||||
return;
|
||||
|
||||
if (!window->lockKeyMods)
|
||||
mods &= ~(GLFW_MOD_CAPS_LOCK | GLFW_MOD_NUM_LOCK);
|
||||
|
||||
if (action == GLFW_RELEASE && window->stickyMouseButtons)
|
||||
window->mouseButtons[button] = _GLFW_STICK;
|
||||
else
|
||||
@@ -232,6 +333,9 @@ void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods)
|
||||
window->callbacks.mouseButton((GLFWwindow*) window, button, action, mods);
|
||||
}
|
||||
|
||||
// Notifies shared code of a cursor motion event
|
||||
// The position is specified in client-area relative screen coordinates
|
||||
//
|
||||
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos)
|
||||
{
|
||||
if (window->virtualCursorPosX == xpos && window->virtualCursorPosY == ypos)
|
||||
@@ -244,18 +348,24 @@ void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos)
|
||||
window->callbacks.cursorPos((GLFWwindow*) window, xpos, ypos);
|
||||
}
|
||||
|
||||
// Notifies shared code of a cursor enter/leave event
|
||||
//
|
||||
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered)
|
||||
{
|
||||
if (window->callbacks.cursorEnter)
|
||||
window->callbacks.cursorEnter((GLFWwindow*) window, entered);
|
||||
}
|
||||
|
||||
// Notifies shared code of files or directories dropped on a window
|
||||
//
|
||||
void _glfwInputDrop(_GLFWwindow* window, int count, const char** paths)
|
||||
{
|
||||
if (window->callbacks.drop)
|
||||
window->callbacks.drop((GLFWwindow*) window, count, paths);
|
||||
}
|
||||
|
||||
// Notifies shared code of a joystick connection or disconnection
|
||||
//
|
||||
void _glfwInputJoystick(_GLFWjoystick* js, int event)
|
||||
{
|
||||
const int jid = (int) (js - _glfw.joysticks);
|
||||
@@ -264,16 +374,22 @@ void _glfwInputJoystick(_GLFWjoystick* js, int event)
|
||||
_glfw.callbacks.joystick(jid, event);
|
||||
}
|
||||
|
||||
// Notifies shared code of the new value of a joystick axis
|
||||
//
|
||||
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value)
|
||||
{
|
||||
js->axes[axis] = value;
|
||||
}
|
||||
|
||||
// Notifies shared code of the new value of a joystick button
|
||||
//
|
||||
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value)
|
||||
{
|
||||
js->buttons[button] = value;
|
||||
}
|
||||
|
||||
// Notifies shared code of the new value of a joystick hat
|
||||
//
|
||||
void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value)
|
||||
{
|
||||
const int base = js->buttonCount + hat * 4;
|
||||
@@ -291,6 +407,8 @@ void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value)
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Returns an available joystick object with arrays and name allocated
|
||||
//
|
||||
_GLFWjoystick* _glfwAllocJoystick(const char* name,
|
||||
const char* guid,
|
||||
int axisCount,
|
||||
@@ -311,20 +429,22 @@ _GLFWjoystick* _glfwAllocJoystick(const char* name,
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
js->present = GLFW_TRUE;
|
||||
js->name = strdup(name);
|
||||
js->name = _glfw_strdup(name);
|
||||
js->axes = calloc(axisCount, sizeof(float));
|
||||
js->buttons = calloc(buttonCount + hatCount * 4, 1);
|
||||
js->hats = calloc(hatCount, 1);
|
||||
js->axisCount = axisCount;
|
||||
js->buttonCount = buttonCount;
|
||||
js->hatCount = hatCount;
|
||||
js->mapping = findMapping(guid);
|
||||
|
||||
strcpy(js->guid, guid);
|
||||
js->mapping = findValidMapping(js);
|
||||
|
||||
return js;
|
||||
}
|
||||
|
||||
// Frees arrays and name and flags the joystick object as unused
|
||||
//
|
||||
void _glfwFreeJoystick(_GLFWjoystick* js)
|
||||
{
|
||||
free(js->name);
|
||||
@@ -354,6 +474,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* handle, int mode)
|
||||
return window->stickyKeys;
|
||||
case GLFW_STICKY_MOUSE_BUTTONS:
|
||||
return window->stickyMouseButtons;
|
||||
case GLFW_LOCK_KEY_MODS:
|
||||
return window->lockKeyMods;
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
|
||||
@@ -409,7 +531,7 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
|
||||
}
|
||||
}
|
||||
|
||||
window->stickyKeys = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
window->stickyKeys = value;
|
||||
}
|
||||
else if (mode == GLFW_STICKY_MOUSE_BUTTONS)
|
||||
{
|
||||
@@ -429,8 +551,10 @@ GLFWAPI void glfwSetInputMode(GLFWwindow* handle, int mode, int value)
|
||||
}
|
||||
}
|
||||
|
||||
window->stickyMouseButtons = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
window->stickyMouseButtons = value;
|
||||
}
|
||||
else if (mode == GLFW_LOCK_KEY_MODS)
|
||||
window->lockKeyMods = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
else
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid input mode 0x%08X", mode);
|
||||
}
|
||||
@@ -914,6 +1038,38 @@ GLFWAPI const char* glfwGetJoystickGUID(int jid)
|
||||
return js->guid;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetJoystickUserPointer(int jid, void* pointer)
|
||||
{
|
||||
_GLFWjoystick* js;
|
||||
|
||||
assert(jid >= GLFW_JOYSTICK_1);
|
||||
assert(jid <= GLFW_JOYSTICK_LAST);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return;
|
||||
|
||||
js->userPointer = pointer;
|
||||
}
|
||||
|
||||
GLFWAPI void* glfwGetJoystickUserPointer(int jid)
|
||||
{
|
||||
_GLFWjoystick* js;
|
||||
|
||||
assert(jid >= GLFW_JOYSTICK_1);
|
||||
assert(jid <= GLFW_JOYSTICK_LAST);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
|
||||
js = _glfw.joysticks + jid;
|
||||
if (!js->present)
|
||||
return NULL;
|
||||
|
||||
return js->userPointer;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWjoystickfun glfwSetJoystickCallback(GLFWjoystickfun cbfun)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
@@ -973,7 +1129,7 @@ GLFWAPI int glfwUpdateGamepadMappings(const char* string)
|
||||
{
|
||||
_GLFWjoystick* js = _glfw.joysticks + jid;
|
||||
if (js->present)
|
||||
js->mapping = findMapping(js->guid);
|
||||
js->mapping = findValidMapping(js);
|
||||
}
|
||||
|
||||
return GLFW_TRUE;
|
||||
@@ -1063,35 +1219,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->buttons[i].value >> 4;
|
||||
const unsigned int bit = js->mapping->buttons[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;
|
||||
|
||||
481
glfw/internal.h
vendored
481
glfw/internal.h
vendored
@@ -196,38 +196,6 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
|
||||
#error "No supported window creation API selected"
|
||||
#endif
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Doxygen group definitions
|
||||
//========================================================================
|
||||
|
||||
/*! @defgroup platform Platform interface
|
||||
* @brief The interface implemented by the platform-specific code.
|
||||
*
|
||||
* The platform API is the interface exposed by the platform-specific code for
|
||||
* each platform and is called by the shared code of the public API It mirrors
|
||||
* the public API except it uses objects instead of handles.
|
||||
*/
|
||||
/*! @defgroup event Event interface
|
||||
* @brief The interface used by the platform-specific code to report events.
|
||||
*
|
||||
* The event API is used by the platform-specific code to notify the shared
|
||||
* code of events that can be translated into state changes and/or callback
|
||||
* calls.
|
||||
*/
|
||||
/*! @defgroup utility Utility functions
|
||||
* @brief Various utility functions for internal use.
|
||||
*
|
||||
* These functions are shared code and may be used by any part of GLFW
|
||||
* Each platform may add its own utility functions, but those must only be
|
||||
* called by the platform-specific code
|
||||
*/
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Helper macros
|
||||
//========================================================================
|
||||
|
||||
// Constructs a version number string from the public header macros
|
||||
#define _GLFW_CONCAT_VERSION(m, n, r) #m "." #n "." #r
|
||||
#define _GLFW_MAKE_VERSION(m, n, r) _GLFW_CONCAT_VERSION(m, n, r)
|
||||
@@ -258,11 +226,8 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
|
||||
y = t; \
|
||||
}
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Platform-independent structures
|
||||
//========================================================================
|
||||
|
||||
// Per-thread error structure
|
||||
//
|
||||
struct _GLFWerror
|
||||
{
|
||||
_GLFWerror* next;
|
||||
@@ -270,10 +235,10 @@ struct _GLFWerror
|
||||
char description[_GLFW_MESSAGE_SIZE];
|
||||
};
|
||||
|
||||
/*! @brief Initialization configuration.
|
||||
*
|
||||
* Parameters relating to the initialization of the library.
|
||||
*/
|
||||
// Initialization configuration
|
||||
//
|
||||
// Parameters relating to the initialization of the library
|
||||
//
|
||||
struct _GLFWinitconfig
|
||||
{
|
||||
GLFWbool hatButtons;
|
||||
@@ -281,18 +246,14 @@ struct _GLFWinitconfig
|
||||
GLFWbool menubar;
|
||||
GLFWbool chdir;
|
||||
} ns;
|
||||
struct {
|
||||
char className[256];
|
||||
char classClass[256];
|
||||
} x11;
|
||||
};
|
||||
|
||||
/*! @brief Window configuration.
|
||||
*
|
||||
* Parameters relating to the creation of the window but not directly related
|
||||
* to the framebuffer. This is used to pass window creation parameters from
|
||||
* shared code to the platform API.
|
||||
*/
|
||||
// Window configuration
|
||||
//
|
||||
// Parameters relating to the creation of the window but not directly related
|
||||
// to the framebuffer. This is used to pass window creation parameters from
|
||||
// shared code to the platform API.
|
||||
//
|
||||
struct _GLFWwndconfig
|
||||
{
|
||||
int width;
|
||||
@@ -308,16 +269,20 @@ struct _GLFWwndconfig
|
||||
GLFWbool centerCursor;
|
||||
struct {
|
||||
GLFWbool retina;
|
||||
GLFWbool frame;
|
||||
char frameName[256];
|
||||
} ns;
|
||||
struct {
|
||||
char className[256];
|
||||
char instanceName[256];
|
||||
} x11;
|
||||
};
|
||||
|
||||
/*! @brief Context configuration.
|
||||
*
|
||||
* Parameters relating to the creation of the context but not directly related
|
||||
* to the framebuffer. This is used to pass context creation parameters from
|
||||
* shared code to the platform API.
|
||||
*/
|
||||
// Context configuration
|
||||
//
|
||||
// Parameters relating to the creation of the context but not directly related
|
||||
// to the framebuffer. This is used to pass context creation parameters from
|
||||
// shared code to the platform API.
|
||||
//
|
||||
struct _GLFWctxconfig
|
||||
{
|
||||
int client;
|
||||
@@ -336,14 +301,14 @@ struct _GLFWctxconfig
|
||||
} nsgl;
|
||||
};
|
||||
|
||||
/*! @brief Framebuffer configuration.
|
||||
*
|
||||
* This describes buffers and their sizes. It also contains
|
||||
* a platform-specific ID used to map back to the backend API object.
|
||||
*
|
||||
* It is used to pass framebuffer parameters from shared code to the platform
|
||||
* API and also to enumerate and select available framebuffer configs.
|
||||
*/
|
||||
// Framebuffer configuration
|
||||
//
|
||||
// This describes buffers and their sizes. It also contains
|
||||
// a platform-specific ID used to map back to the backend API object.
|
||||
//
|
||||
// It is used to pass framebuffer parameters from shared code to the platform
|
||||
// API and also to enumerate and select available framebuffer configs.
|
||||
//
|
||||
struct _GLFWfbconfig
|
||||
{
|
||||
int redBits;
|
||||
@@ -365,8 +330,8 @@ struct _GLFWfbconfig
|
||||
uintptr_t handle;
|
||||
};
|
||||
|
||||
/*! @brief Context structure.
|
||||
*/
|
||||
// Context structure
|
||||
//
|
||||
struct _GLFWcontext
|
||||
{
|
||||
int client;
|
||||
@@ -396,8 +361,8 @@ struct _GLFWcontext
|
||||
_GLFW_OSMESA_CONTEXT_STATE;
|
||||
};
|
||||
|
||||
/*! @brief Window and context structure.
|
||||
*/
|
||||
// Window and context structure
|
||||
//
|
||||
struct _GLFWwindow
|
||||
{
|
||||
struct _GLFWwindow* next;
|
||||
@@ -419,6 +384,7 @@ struct _GLFWwindow
|
||||
|
||||
GLFWbool stickyKeys;
|
||||
GLFWbool stickyMouseButtons;
|
||||
GLFWbool lockKeyMods;
|
||||
int cursorMode;
|
||||
char mouseButtons[GLFW_MOUSE_BUTTON_LAST + 1];
|
||||
char keys[GLFW_KEY_LAST + 1];
|
||||
@@ -436,6 +402,7 @@ struct _GLFWwindow
|
||||
GLFWwindowiconifyfun iconify;
|
||||
GLFWwindowmaximizefun maximize;
|
||||
GLFWframebuffersizefun fbsize;
|
||||
GLFWwindowcontentscalefun scale;
|
||||
GLFWmousebuttonfun mouseButton;
|
||||
GLFWcursorposfun cursorPos;
|
||||
GLFWcursorenterfun cursorEnter;
|
||||
@@ -450,11 +417,12 @@ struct _GLFWwindow
|
||||
_GLFW_PLATFORM_WINDOW_STATE;
|
||||
};
|
||||
|
||||
/*! @brief Monitor structure.
|
||||
*/
|
||||
// Monitor structure
|
||||
//
|
||||
struct _GLFWmonitor
|
||||
{
|
||||
char* name;
|
||||
void* userPointer;
|
||||
|
||||
// Physical dimensions in millimeters.
|
||||
int widthMM, heightMM;
|
||||
@@ -473,8 +441,8 @@ struct _GLFWmonitor
|
||||
_GLFW_PLATFORM_MONITOR_STATE;
|
||||
};
|
||||
|
||||
/*! @brief Cursor structure
|
||||
*/
|
||||
// Cursor structure
|
||||
//
|
||||
struct _GLFWcursor
|
||||
{
|
||||
_GLFWcursor* next;
|
||||
@@ -483,16 +451,18 @@ struct _GLFWcursor
|
||||
_GLFW_PLATFORM_CURSOR_STATE;
|
||||
};
|
||||
|
||||
/*! @brief Gamepad mapping element structure
|
||||
*/
|
||||
// Gamepad mapping element structure
|
||||
//
|
||||
struct _GLFWmapelement
|
||||
{
|
||||
uint8_t type;
|
||||
uint8_t value;
|
||||
uint8_t index;
|
||||
int8_t axisScale;
|
||||
int8_t axisOffset;
|
||||
};
|
||||
|
||||
/*! @brief Gamepad mapping structure
|
||||
*/
|
||||
// Gamepad mapping structure
|
||||
//
|
||||
struct _GLFWmapping
|
||||
{
|
||||
char name[128];
|
||||
@@ -501,8 +471,8 @@ struct _GLFWmapping
|
||||
_GLFWmapelement axes[6];
|
||||
};
|
||||
|
||||
/*! @brief Joystick structure
|
||||
*/
|
||||
// Joystick structure
|
||||
//
|
||||
struct _GLFWjoystick
|
||||
{
|
||||
GLFWbool present;
|
||||
@@ -513,6 +483,7 @@ struct _GLFWjoystick
|
||||
unsigned char* hats;
|
||||
int hatCount;
|
||||
char* name;
|
||||
void* userPointer;
|
||||
char guid[33];
|
||||
_GLFWmapping* mapping;
|
||||
|
||||
@@ -520,24 +491,24 @@ struct _GLFWjoystick
|
||||
_GLFW_PLATFORM_JOYSTICK_STATE;
|
||||
};
|
||||
|
||||
/*! @brief Thread local storage structure.
|
||||
*/
|
||||
// Thread local storage structure
|
||||
//
|
||||
struct _GLFWtls
|
||||
{
|
||||
// This is defined in the platform's thread.h
|
||||
_GLFW_PLATFORM_TLS_STATE;
|
||||
};
|
||||
|
||||
/*! @brief Mutex structure.
|
||||
*/
|
||||
// Mutex structure
|
||||
//
|
||||
struct _GLFWmutex
|
||||
{
|
||||
// This is defined in the platform's thread.h
|
||||
_GLFW_PLATFORM_MUTEX_STATE;
|
||||
};
|
||||
|
||||
/*! @brief Library global data.
|
||||
*/
|
||||
// Library global data
|
||||
//
|
||||
struct _GLFWlibrary
|
||||
{
|
||||
GLFWbool initialized;
|
||||
@@ -611,21 +582,14 @@ struct _GLFWlibrary
|
||||
_GLFW_OSMESA_LIBRARY_CONTEXT_STATE;
|
||||
};
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Global state shared between compilation units of GLFW
|
||||
//========================================================================
|
||||
|
||||
/*! @brief All global data shared between compilation units.
|
||||
*/
|
||||
//
|
||||
extern _GLFWlibrary _glfw;
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Platform API functions
|
||||
//========================================================================
|
||||
|
||||
/*! @addtogroup platform @{ */
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW platform API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
int _glfwPlatformInit(void);
|
||||
void _glfwPlatformTerminate(void);
|
||||
@@ -634,7 +598,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);
|
||||
@@ -642,8 +607,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);
|
||||
@@ -664,16 +631,22 @@ 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);
|
||||
@@ -682,11 +655,14 @@ void _glfwPlatformHideWindow(_GLFWwindow* window);
|
||||
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window);
|
||||
int _glfwPlatformWindowBell(_GLFWwindow* window, int64_t);
|
||||
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);
|
||||
int _glfwPlatformWindowMaximized(_GLFWwindow* window);
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window);
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window);
|
||||
float _glfwPlatformGetWindowOpacity(_GLFWwindow* window);
|
||||
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled);
|
||||
@@ -700,8 +676,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);
|
||||
@@ -713,307 +694,79 @@ void _glfwPlatformDestroyMutex(_GLFWmutex* mutex);
|
||||
void _glfwPlatformLockMutex(_GLFWmutex* mutex);
|
||||
void _glfwPlatformUnlockMutex(_GLFWmutex* mutex);
|
||||
|
||||
/*! @} */
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW event API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//========================================================================
|
||||
// Event API functions
|
||||
//========================================================================
|
||||
|
||||
/*! @brief Notifies shared code that a window has lost or received input focus.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] focused `GLFW_TRUE` if the window received focus, or `GLFW_FALSE`
|
||||
* if it lost focus.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused);
|
||||
|
||||
/*! @brief Notifies shared code that a window has moved.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] xpos The new x-coordinate of the client area of the window.
|
||||
* @param[in] ypos The new y-coordinate of the client area of the window.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputWindowPos(_GLFWwindow* window, int xpos, int ypos);
|
||||
|
||||
/*! @brief Notifies shared code that a window has been resized.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] width The new width of the client area of the window.
|
||||
* @param[in] height The new height of the client area of the window.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputWindowSize(_GLFWwindow* window, int width, int height);
|
||||
|
||||
/*! @brief Notifies shared code that a window framebuffer has been resized.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] width The new width, in pixels, of the framebuffer.
|
||||
* @param[in] height The new height, in pixels, of the framebuffer.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height);
|
||||
|
||||
/*! @brief Notifies shared code that a window has been iconified or restored.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] iconified `GLFW_TRUE` if the window was iconified, or
|
||||
* `GLFW_FALSE` if it was restored.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputWindowContentScale(_GLFWwindow* window,
|
||||
float xscale, float yscale);
|
||||
void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified);
|
||||
|
||||
/*! @brief Notifies shared code that a window has been maximized or restored.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] maximized `GLFW_TRUE` if the window was maximized, or
|
||||
* `GLFW_FALSE` if it was restored.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized);
|
||||
|
||||
/*! @brief Notifies shared code that a window's contents needs updating.
|
||||
* @param[in] window The window that received the event.
|
||||
*/
|
||||
void _glfwInputWindowDamage(_GLFWwindow* window);
|
||||
|
||||
/*! @brief Notifies shared code that the user wishes to close a window.
|
||||
* @param[in] window The window that received the event.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputWindowCloseRequest(_GLFWwindow* window);
|
||||
|
||||
/*! @brief Notifies shared code that a window has changed its desired monitor.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] monitor The new desired monitor, or `NULL`.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor);
|
||||
|
||||
/*! @brief Notifies shared code of a physical key event.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] key The key that was pressed or released.
|
||||
* @param[in] scancode The system-specific scan code of the key.
|
||||
* @param[in] action @ref GLFW_PRESS or @ref GLFW_RELEASE.
|
||||
* @param[in] mods The modifiers pressed when the event was generated.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputKey(_GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
|
||||
/*! @brief Notifies shared code of a Unicode character input event.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] codepoint The Unicode code point of the input character.
|
||||
* @param[in] mods Bit field describing which modifier keys were held down.
|
||||
* @param[in] plain `GLFW_TRUE` if the character is regular text input, or
|
||||
* `GLFW_FALSE` otherwise.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputChar(_GLFWwindow* window, unsigned int codepoint, int mods, GLFWbool plain);
|
||||
|
||||
/*! @brief Notifies shared code of a scroll event.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] xoffset The scroll offset along the x-axis.
|
||||
* @param[in] yoffset The scroll offset along the y-axis.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputKey(_GLFWwindow* window,
|
||||
int key, int scancode, int action, int mods);
|
||||
void _glfwInputChar(_GLFWwindow* window,
|
||||
unsigned int codepoint, int mods, GLFWbool plain);
|
||||
void _glfwInputScroll(_GLFWwindow* window, double xoffset, double yoffset);
|
||||
|
||||
/*! @brief Notifies shared code of a mouse button click event.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] button The button that was pressed or released.
|
||||
* @param[in] action @ref GLFW_PRESS or @ref GLFW_RELEASE.
|
||||
* @param[in] mods The modifiers pressed when the event was generated.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputMouseClick(_GLFWwindow* window, int button, int action, int mods);
|
||||
|
||||
/*! @brief Notifies shared code of a cursor motion event.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] xpos The new x-coordinate of the cursor, relative to the left
|
||||
* edge of the client area of the window.
|
||||
* @param[in] ypos The new y-coordinate of the cursor, relative to the top edge
|
||||
* of the client area of the window.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputCursorPos(_GLFWwindow* window, double xpos, double ypos);
|
||||
|
||||
/*! @brief Notifies shared code of a cursor enter/leave event.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] entered `GLFW_TRUE` if the cursor entered the client area of the
|
||||
* window, or `GLFW_FALSE` if it left it.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputCursorEnter(_GLFWwindow* window, GLFWbool entered);
|
||||
void _glfwInputDrop(_GLFWwindow* window, int count, const char** names);
|
||||
void _glfwInputJoystick(_GLFWjoystick* js, int event);
|
||||
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
|
||||
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value);
|
||||
void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value);
|
||||
|
||||
/*! @brief Notifies shared code of a monitor connection or disconnection.
|
||||
* @param[in] monitor The monitor that was connected or disconnected.
|
||||
* @param[in] action One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`.
|
||||
* @param[in] placement `_GLFW_INSERT_FIRST` or `_GLFW_INSERT_LAST`.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement);
|
||||
|
||||
/*! @brief Notifies shared code that a full screen window has acquired or
|
||||
* released a monitor.
|
||||
* @param[in] monitor The monitor that was acquired or released.
|
||||
* @param[in] window The window that acquired the monitor, or `NULL`.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window);
|
||||
|
||||
/*! @brief Notifies shared code of an error.
|
||||
* @param[in] code The error code most suitable for the error.
|
||||
* @param[in] format The `printf` style format string of the error
|
||||
* description.
|
||||
* @ingroup event
|
||||
*/
|
||||
#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
|
||||
|
||||
/*! @brief Notifies shared code of files or directories dropped on a window.
|
||||
* @param[in] window The window that received the event.
|
||||
* @param[in] count The number of dropped objects.
|
||||
* @param[in] names The names of the dropped objects.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputDrop(_GLFWwindow* window, int count, const char** names);
|
||||
|
||||
/*! @brief Notifies shared code of a joystick connection or disconnection.
|
||||
* @param[in] js The joystick that was connected or disconnected.
|
||||
* @param[in] event One of `GLFW_CONNECTED` or `GLFW_DISCONNECTED`.
|
||||
* @ingroup event
|
||||
*/
|
||||
void _glfwInputJoystick(_GLFWjoystick* js, int event);
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*! @brief Notifies shared code of the new value of a joystick axis.
|
||||
* @param[in] js The joystick whose axis to update.
|
||||
* @param[in] axis The index of the axis to update.
|
||||
* @param[in] value The new value of the axis.
|
||||
*/
|
||||
void _glfwInputJoystickAxis(_GLFWjoystick* js, int axis, float value);
|
||||
|
||||
/*! @brief Notifies shared code of the new value of a joystick button.
|
||||
* @param[in] js The joystick whose button to update.
|
||||
* @param[in] button The index of the button to update.
|
||||
* @param[in] value The new value of the button.
|
||||
*/
|
||||
void _glfwInputJoystickButton(_GLFWjoystick* js, int button, char value);
|
||||
|
||||
/*! @brief Notifies shared code of the new value of a joystick hat.
|
||||
* @param[in] js The joystick whose hat to update.
|
||||
* @param[in] button The index of the hat to update.
|
||||
* @param[in] value The new value of the hat.
|
||||
*/
|
||||
void _glfwInputJoystickHat(_GLFWjoystick* js, int hat, char value);
|
||||
|
||||
|
||||
//========================================================================
|
||||
// Utility functions
|
||||
//========================================================================
|
||||
|
||||
/*! @brief Chooses the video mode most closely matching the desired one.
|
||||
* @ingroup utility
|
||||
*/
|
||||
const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
|
||||
const GLFWvidmode* desired);
|
||||
|
||||
/*! @brief Performs lexical comparison between two @ref GLFWvidmode structures.
|
||||
* @ingroup utility
|
||||
*/
|
||||
int _glfwCompareVideoModes(const GLFWvidmode* first, const GLFWvidmode* second);
|
||||
|
||||
/*! @brief Splits a color depth into red, green and blue bit depths.
|
||||
* @ingroup utility
|
||||
*/
|
||||
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue);
|
||||
|
||||
/*! @brief Searches an extension string for the specified extension.
|
||||
* @param[in] string The extension string to search.
|
||||
* @param[in] extensions The extension to search for.
|
||||
* @return `GLFW_TRUE` if the extension was found, or `GLFW_FALSE` otherwise.
|
||||
* @ingroup utility
|
||||
*/
|
||||
GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions);
|
||||
|
||||
/*! @brief Chooses the framebuffer config that best matches the desired one.
|
||||
* @param[in] desired The desired framebuffer config.
|
||||
* @param[in] alternatives The framebuffer configs supported by the system.
|
||||
* @param[in] count The number of entries in the alternatives array.
|
||||
* @return The framebuffer config most closely matching the desired one, or @c
|
||||
* NULL if none fulfilled the hard constraints of the desired values.
|
||||
* @ingroup utility
|
||||
*/
|
||||
const _GLFWfbconfig* _glfwChooseFBConfig(const _GLFWfbconfig* desired,
|
||||
const _GLFWfbconfig* alternatives,
|
||||
unsigned int count);
|
||||
|
||||
/*! @brief Retrieves the attributes of the current context.
|
||||
* @param[in] ctxconfig The desired context attributes.
|
||||
* @return `GLFW_TRUE` if successful, or `GLFW_FALSE` if the context is
|
||||
* unusable.
|
||||
* @ingroup utility
|
||||
*/
|
||||
GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig);
|
||||
|
||||
/*! @brief Checks whether the desired context attributes are valid.
|
||||
* @param[in] ctxconfig The context attributes to check.
|
||||
* @return `GLFW_TRUE` if the context attributes are valid, or `GLFW_FALSE`
|
||||
* otherwise.
|
||||
* @ingroup utility
|
||||
*
|
||||
* This function checks things like whether the specified client API version
|
||||
* exists and whether all relevant options have supported and non-conflicting
|
||||
* values.
|
||||
*/
|
||||
GLFWbool _glfwRefreshContextAttribs(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig);
|
||||
GLFWbool _glfwIsValidContextConfig(const _GLFWctxconfig* ctxconfig);
|
||||
|
||||
/*! @brief Allocates red, green and blue value arrays of the specified size.
|
||||
* @ingroup utility
|
||||
*/
|
||||
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size);
|
||||
|
||||
/*! @brief Frees the red, green and blue value arrays and clears the struct.
|
||||
* @ingroup utility
|
||||
*/
|
||||
void _glfwFreeGammaArrays(GLFWgammaramp* ramp);
|
||||
|
||||
/*! @brief Allocates and returns a monitor object with the specified name
|
||||
* and dimensions.
|
||||
* @param[in] name The name of the monitor.
|
||||
* @param[in] widthMM The width, in mm, of the monitor's display area.
|
||||
* @param[in] heightMM The height, in mm, of the monitor's display area.
|
||||
* @return The newly created object.
|
||||
* @ingroup utility
|
||||
*/
|
||||
const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
|
||||
const GLFWvidmode* desired);
|
||||
int _glfwCompareVideoModes(const GLFWvidmode* first, const GLFWvidmode* second);
|
||||
_GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM);
|
||||
|
||||
/*! @brief Frees a monitor object and any data associated with it.
|
||||
* @ingroup utility
|
||||
*/
|
||||
void _glfwFreeMonitor(_GLFWmonitor* monitor);
|
||||
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size);
|
||||
void _glfwFreeGammaArrays(GLFWgammaramp* ramp);
|
||||
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue);
|
||||
|
||||
/*! @brief Returns an available joystick object with arrays and name allocated.
|
||||
* @ingroup utility
|
||||
*/
|
||||
_GLFWjoystick* _glfwAllocJoystick(const char* name,
|
||||
const char* guid,
|
||||
int axisCount,
|
||||
int buttonCount,
|
||||
int hatCount);
|
||||
|
||||
/*! @brief Frees arrays and name and flags the joystick object as unused.
|
||||
* @ingroup utility
|
||||
*/
|
||||
void _glfwFreeJoystick(_GLFWjoystick* js);
|
||||
|
||||
/*! @ingroup utility
|
||||
*/
|
||||
GLFWbool _glfwInitVulkan(int mode);
|
||||
|
||||
/*! @ingroup utility
|
||||
*/
|
||||
void _glfwTerminateVulkan(void);
|
||||
|
||||
/*! @ingroup utility
|
||||
*/
|
||||
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)
|
||||
|
||||
626
glfw/mappings.h
vendored
626
glfw/mappings.h
vendored
@@ -51,7 +51,7 @@
|
||||
// 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.
|
||||
// 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.
|
||||
@@ -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,",
|
||||
|
||||
51
glfw/monitor.c
vendored
51
glfw/monitor.c
vendored
@@ -86,6 +86,8 @@ static GLFWbool refreshVideoModes(_GLFWmonitor* monitor)
|
||||
////// GLFW event API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Notifies shared code of a monitor connection or disconnection
|
||||
//
|
||||
void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement)
|
||||
{
|
||||
if (action == GLFW_CONNECTED)
|
||||
@@ -141,6 +143,9 @@ void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement)
|
||||
_glfwFreeMonitor(monitor);
|
||||
}
|
||||
|
||||
// Notifies shared code that a full screen window has acquired or released
|
||||
// a monitor
|
||||
//
|
||||
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window)
|
||||
{
|
||||
monitor->window = window;
|
||||
@@ -151,6 +156,8 @@ void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window)
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Allocates and returns a monitor object with the specified name and dimensions
|
||||
//
|
||||
_GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM)
|
||||
{
|
||||
_GLFWmonitor* monitor = calloc(1, sizeof(_GLFWmonitor));
|
||||
@@ -158,16 +165,20 @@ _GLFWmonitor* _glfwAllocMonitor(const char* name, int widthMM, int heightMM)
|
||||
monitor->heightMM = heightMM;
|
||||
|
||||
if (name)
|
||||
monitor->name = strdup(name);
|
||||
monitor->name = _glfw_strdup(name);
|
||||
|
||||
return monitor;
|
||||
}
|
||||
|
||||
// Frees a monitor object and any data associated with it
|
||||
//
|
||||
void _glfwFreeMonitor(_GLFWmonitor* monitor)
|
||||
{
|
||||
if (monitor == NULL)
|
||||
return;
|
||||
|
||||
_glfwPlatformFreeMonitor(monitor);
|
||||
|
||||
_glfwFreeGammaArrays(&monitor->originalRamp);
|
||||
_glfwFreeGammaArrays(&monitor->currentRamp);
|
||||
|
||||
@@ -176,6 +187,8 @@ void _glfwFreeMonitor(_GLFWmonitor* monitor)
|
||||
free(monitor);
|
||||
}
|
||||
|
||||
// Allocates red, green and blue value arrays of the specified size
|
||||
//
|
||||
void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size)
|
||||
{
|
||||
ramp->red = calloc(size, sizeof(unsigned short));
|
||||
@@ -184,6 +197,8 @@ void _glfwAllocGammaArrays(GLFWgammaramp* ramp, unsigned int size)
|
||||
ramp->size = size;
|
||||
}
|
||||
|
||||
// Frees the red, green and blue value arrays and clears the struct
|
||||
//
|
||||
void _glfwFreeGammaArrays(GLFWgammaramp* ramp)
|
||||
{
|
||||
free(ramp->red);
|
||||
@@ -193,6 +208,8 @@ void _glfwFreeGammaArrays(GLFWgammaramp* ramp)
|
||||
memset(ramp, 0, sizeof(GLFWgammaramp));
|
||||
}
|
||||
|
||||
// Chooses the video mode most closely matching the desired one
|
||||
//
|
||||
const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
|
||||
const GLFWvidmode* desired)
|
||||
{
|
||||
@@ -243,11 +260,15 @@ const GLFWvidmode* _glfwChooseVideoMode(_GLFWmonitor* monitor,
|
||||
return closest;
|
||||
}
|
||||
|
||||
// Performs lexical comparison between two @ref GLFWvidmode structures
|
||||
//
|
||||
int _glfwCompareVideoModes(const GLFWvidmode* fm, const GLFWvidmode* sm)
|
||||
{
|
||||
return compareVideoModes(fm, sm);
|
||||
}
|
||||
|
||||
// Splits a color depth into red, green and blue bit depths
|
||||
//
|
||||
void _glfwSplitBPP(int bpp, int* red, int* green, int* blue)
|
||||
{
|
||||
int delta;
|
||||
@@ -351,6 +372,24 @@ GLFWAPI const char* glfwGetMonitorName(GLFWmonitor* handle)
|
||||
return monitor->name;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwSetMonitorUserPointer(GLFWmonitor* handle, void* pointer)
|
||||
{
|
||||
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
|
||||
assert(monitor != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
monitor->userPointer = pointer;
|
||||
}
|
||||
|
||||
GLFWAPI void* glfwGetMonitorUserPointer(GLFWmonitor* handle)
|
||||
{
|
||||
_GLFWmonitor* monitor = (_GLFWmonitor*) handle;
|
||||
assert(monitor != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
return monitor->userPointer;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWmonitorfun glfwSetMonitorCallback(GLFWmonitorfun cbfun)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
@@ -406,16 +445,16 @@ GLFWAPI void glfwSetGamma(GLFWmonitor* handle, float gamma)
|
||||
|
||||
for (i = 0; i < 256; i++)
|
||||
{
|
||||
double value;
|
||||
float value;
|
||||
|
||||
// Calculate intensity
|
||||
value = i / 255.0;
|
||||
value = i / 255.f;
|
||||
// Apply gamma curve
|
||||
value = pow(value, 1.0 / gamma) * 65535.0 + 0.5;
|
||||
value = powf(value, 1.f / gamma) * 65535.f + 0.5f;
|
||||
|
||||
// Clamp to value range
|
||||
if (value > 65535.0)
|
||||
value = 65535.0;
|
||||
if (value > 65535.f)
|
||||
value = 65535.f;
|
||||
|
||||
values[i] = (unsigned short) value;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
5
glfw/null_window.c
vendored
5
glfw/null_window.c
vendored
@@ -165,6 +165,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
{
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
return GLFW_FALSE;
|
||||
|
||||
@@ -63,6 +63,8 @@
|
||||
"osmesa_context.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"
|
||||
@@ -79,6 +81,10 @@
|
||||
"osmesa_context.c"
|
||||
]
|
||||
},
|
||||
"wayland_protocols": [
|
||||
1,
|
||||
12
|
||||
],
|
||||
"win32": {
|
||||
"headers": [
|
||||
"win32_platform.h",
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
91
glfw/wgl_context.c
vendored
91
glfw/wgl_context.c
vendored
@@ -329,21 +329,52 @@ static void destroyContextWGL(_GLFWwindow* window)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize WGL-specific extensions
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialize WGL
|
||||
//
|
||||
static void loadWGLExtensions(void)
|
||||
GLFWbool _glfwInitWGL(void)
|
||||
{
|
||||
PIXELFORMATDESCRIPTOR pfd;
|
||||
HGLRC rc;
|
||||
HDC dc = GetDC(_glfw.win32.helperWindowHandle);;
|
||||
HGLRC prc, rc;
|
||||
HDC pdc, dc;
|
||||
|
||||
_glfw.wgl.extensionsLoaded = GLFW_TRUE;
|
||||
if (_glfw.wgl.instance)
|
||||
return GLFW_TRUE;
|
||||
|
||||
_glfw.wgl.instance = LoadLibraryA("opengl32.dll");
|
||||
if (!_glfw.wgl.instance)
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"WGL: Failed to load opengl32.dll");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
_glfw.wgl.CreateContext = (PFN_wglCreateContext)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglCreateContext");
|
||||
_glfw.wgl.DeleteContext = (PFN_wglDeleteContext)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglDeleteContext");
|
||||
_glfw.wgl.GetProcAddress = (PFN_wglGetProcAddress)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglGetProcAddress");
|
||||
_glfw.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentDC");
|
||||
_glfw.wgl.GetCurrentContext = (PFN_wglGetCurrentContext)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentContext");
|
||||
_glfw.wgl.MakeCurrent = (PFN_wglMakeCurrent)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglMakeCurrent");
|
||||
_glfw.wgl.ShareLists = (PFN_wglShareLists)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglShareLists");
|
||||
|
||||
// NOTE: A dummy context has to be created for opengl32.dll to load the
|
||||
// OpenGL ICD, from which we can then query WGL extensions
|
||||
// NOTE: This code will accept the Microsoft GDI ICD; accelerated context
|
||||
// creation failure occurs during manual pixel format enumeration
|
||||
|
||||
dc = GetDC(_glfw.win32.helperWindowHandle);;
|
||||
|
||||
ZeroMemory(&pfd, sizeof(pfd));
|
||||
pfd.nSize = sizeof(pfd);
|
||||
pfd.nVersion = 1;
|
||||
@@ -355,7 +386,7 @@ static void loadWGLExtensions(void)
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"WGL: Failed to set pixel format for dummy context");
|
||||
return;
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
rc = wglCreateContext(dc);
|
||||
@@ -363,15 +394,19 @@ static void loadWGLExtensions(void)
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"WGL: Failed to create dummy context");
|
||||
return;
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
pdc = wglGetCurrentDC();
|
||||
prc = wglGetCurrentContext();
|
||||
|
||||
if (!wglMakeCurrent(dc, rc))
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"WGL: Failed to make dummy context current");
|
||||
wglMakeCurrent(pdc, prc);
|
||||
wglDeleteContext(rc);
|
||||
return;
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
// NOTE: Functions must be loaded first as they're needed to retrieve the
|
||||
@@ -414,43 +449,8 @@ static void loadWGLExtensions(void)
|
||||
_glfw.wgl.ARB_context_flush_control =
|
||||
extensionSupportedWGL("WGL_ARB_context_flush_control");
|
||||
|
||||
wglMakeCurrent(dc, NULL);
|
||||
wglMakeCurrent(pdc, prc);
|
||||
wglDeleteContext(rc);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
////// GLFW internal API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Initialize WGL
|
||||
//
|
||||
GLFWbool _glfwInitWGL(void)
|
||||
{
|
||||
if (_glfw.wgl.instance)
|
||||
return GLFW_TRUE;
|
||||
|
||||
_glfw.wgl.instance = LoadLibraryA("opengl32.dll");
|
||||
if (!_glfw.wgl.instance)
|
||||
{
|
||||
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
|
||||
"WGL: Failed to load opengl32.dll");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
_glfw.wgl.CreateContext = (PFN_wglCreateContext)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglCreateContext");
|
||||
_glfw.wgl.DeleteContext = (PFN_wglDeleteContext)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglDeleteContext");
|
||||
_glfw.wgl.GetProcAddress = (PFN_wglGetProcAddress)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglGetProcAddress");
|
||||
_glfw.wgl.GetCurrentDC = (PFN_wglGetCurrentDC)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglGetCurrentDC");
|
||||
_glfw.wgl.MakeCurrent = (PFN_wglMakeCurrent)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglMakeCurrent");
|
||||
_glfw.wgl.ShareLists = (PFN_wglShareLists)
|
||||
GetProcAddress(_glfw.wgl.instance, "wglShareLists");
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
@@ -480,9 +480,6 @@ GLFWbool _glfwCreateContextWGL(_GLFWwindow* window,
|
||||
PIXELFORMATDESCRIPTOR pfd;
|
||||
HGLRC share = NULL;
|
||||
|
||||
if (!_glfw.wgl.extensionsLoaded)
|
||||
loadWGLExtensions();
|
||||
|
||||
if (ctxconfig->share)
|
||||
share = ctxconfig->share->context.wgl.handle;
|
||||
|
||||
|
||||
5
glfw/wgl_context.h
vendored
5
glfw/wgl_context.h
vendored
@@ -86,6 +86,7 @@ typedef HGLRC (WINAPI * PFN_wglCreateContext)(HDC);
|
||||
typedef BOOL (WINAPI * PFN_wglDeleteContext)(HGLRC);
|
||||
typedef PROC (WINAPI * PFN_wglGetProcAddress)(LPCSTR);
|
||||
typedef HDC (WINAPI * PFN_wglGetCurrentDC)(void);
|
||||
typedef HGLRC (WINAPI * PFN_wglGetCurrentContext)(void);
|
||||
typedef BOOL (WINAPI * PFN_wglMakeCurrent)(HDC,HGLRC);
|
||||
typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC);
|
||||
|
||||
@@ -94,6 +95,7 @@ typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC);
|
||||
#define wglDeleteContext _glfw.wgl.DeleteContext
|
||||
#define wglGetProcAddress _glfw.wgl.GetProcAddress
|
||||
#define wglGetCurrentDC _glfw.wgl.GetCurrentDC
|
||||
#define wglGetCurrentContext _glfw.wgl.GetCurrentContext
|
||||
#define wglMakeCurrent _glfw.wgl.MakeCurrent
|
||||
#define wglShareLists _glfw.wgl.ShareLists
|
||||
|
||||
@@ -124,11 +126,10 @@ typedef struct _GLFWlibraryWGL
|
||||
PFN_wglDeleteContext DeleteContext;
|
||||
PFN_wglGetProcAddress GetProcAddress;
|
||||
PFN_wglGetCurrentDC GetCurrentDC;
|
||||
PFN_wglGetCurrentContext GetCurrentContext;
|
||||
PFN_wglMakeCurrent MakeCurrent;
|
||||
PFN_wglShareLists ShareLists;
|
||||
|
||||
GLFWbool extensionsLoaded;
|
||||
|
||||
PFNWGLSWAPINTERVALEXTPROC SwapIntervalEXT;
|
||||
PFNWGLGETPIXELFORMATATTRIBIVARBPROC GetPixelFormatAttribivARB;
|
||||
PFNWGLGETEXTENSIONSSTRINGEXTPROC GetExtensionsStringEXT;
|
||||
|
||||
10
glfw/win32_init.c
vendored
10
glfw/win32_init.c
vendored
@@ -351,9 +351,10 @@ static HWND createHelperWindow(void)
|
||||
dbi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
dbi.dbcc_classguid = GUID_DEVINTERFACE_HID;
|
||||
|
||||
RegisterDeviceNotificationW(window,
|
||||
(DEV_BROADCAST_HDR*) &dbi,
|
||||
DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
_glfw.win32.deviceNotificationHandle =
|
||||
RegisterDeviceNotificationW(window,
|
||||
(DEV_BROADCAST_HDR*) &dbi,
|
||||
DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
}
|
||||
|
||||
while (PeekMessageW(&msg, _glfw.win32.helperWindowHandle, 0, 0, PM_REMOVE))
|
||||
@@ -543,6 +544,9 @@ int _glfwPlatformInit(void)
|
||||
|
||||
void _glfwPlatformTerminate(void)
|
||||
{
|
||||
if (_glfw.win32.deviceNotificationHandle)
|
||||
UnregisterDeviceNotification(_glfw.win32.deviceNotificationHandle);
|
||||
|
||||
if (_glfw.win32.helperWindowHandle)
|
||||
DestroyWindow(_glfw.win32.helperWindowHandle);
|
||||
|
||||
|
||||
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;
|
||||
|
||||
19
glfw/win32_platform.h
vendored
19
glfw/win32_platform.h
vendored
@@ -67,11 +67,6 @@
|
||||
#include <xinput.h>
|
||||
#include <dbt.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <malloc.h>
|
||||
#define strdup _strdup
|
||||
#endif
|
||||
|
||||
// HACK: Define macros that some windows.h variants don't
|
||||
#ifndef WM_MOUSEHWHEEL
|
||||
#define WM_MOUSEHWHEEL 0x020E
|
||||
@@ -105,12 +100,11 @@
|
||||
#endif
|
||||
|
||||
#if WINVER < 0x0601
|
||||
typedef struct tagCHANGEFILTERSTRUCT
|
||||
typedef struct
|
||||
{
|
||||
DWORD cbSize;
|
||||
DWORD ExtStatus;
|
||||
|
||||
} CHANGEFILTERSTRUCT, *PCHANGEFILTERSTRUCT;
|
||||
} CHANGEFILTERSTRUCT;
|
||||
#ifndef MSGFLT_ALLOW
|
||||
#define MSGFLT_ALLOW 1
|
||||
#endif
|
||||
@@ -129,13 +123,13 @@ typedef struct
|
||||
#endif /*Windows Vista*/
|
||||
|
||||
#ifndef DPI_ENUMS_DECLARED
|
||||
typedef enum PROCESS_DPI_AWARENESS
|
||||
typedef enum
|
||||
{
|
||||
PROCESS_DPI_UNAWARE = 0,
|
||||
PROCESS_SYSTEM_DPI_AWARE = 1,
|
||||
PROCESS_PER_MONITOR_DPI_AWARE = 2
|
||||
} PROCESS_DPI_AWARENESS;
|
||||
typedef enum MONITOR_DPI_TYPE
|
||||
typedef enum
|
||||
{
|
||||
MDT_EFFECTIVE_DPI = 0,
|
||||
MDT_ANGULAR_DPI = 1,
|
||||
@@ -209,7 +203,7 @@ typedef HRESULT (WINAPI * PFN_DirectInput8Create)(HINSTANCE,DWORD,REFIID,LPVOID*
|
||||
|
||||
// user32.dll function pointer typedefs
|
||||
typedef BOOL (WINAPI * PFN_SetProcessDPIAware)(void);
|
||||
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,PCHANGEFILTERSTRUCT);
|
||||
typedef BOOL (WINAPI * PFN_ChangeWindowMessageFilterEx)(HWND,UINT,DWORD,CHANGEFILTERSTRUCT*);
|
||||
#define SetProcessDPIAware _glfw.win32.user32.SetProcessDPIAware_
|
||||
#define ChangeWindowMessageFilterEx _glfw.win32.user32.ChangeWindowMessageFilterEx_
|
||||
|
||||
@@ -289,6 +283,7 @@ typedef struct _GLFWwindowWin32
|
||||
typedef struct _GLFWlibraryWin32
|
||||
{
|
||||
HWND helperWindowHandle;
|
||||
HDEVNOTIFY deviceNotificationHandle;
|
||||
DWORD foregroundLockTimeout;
|
||||
int acquiredMonitorCount;
|
||||
char* clipboardString;
|
||||
@@ -403,7 +398,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);
|
||||
|
||||
|
||||
101
glfw/win32_window.c
vendored
101
glfw/win32_window.c
vendored
@@ -384,14 +384,18 @@ static int getKeyMods(void)
|
||||
{
|
||||
int mods = 0;
|
||||
|
||||
if (GetKeyState(VK_SHIFT) & (1 << 31))
|
||||
if (GetKeyState(VK_SHIFT) & 0x8000)
|
||||
mods |= GLFW_MOD_SHIFT;
|
||||
if (GetKeyState(VK_CONTROL) & (1 << 31))
|
||||
if (GetKeyState(VK_CONTROL) & 0x8000)
|
||||
mods |= GLFW_MOD_CONTROL;
|
||||
if (GetKeyState(VK_MENU) & (1 << 31))
|
||||
if (GetKeyState(VK_MENU) & 0x8000)
|
||||
mods |= GLFW_MOD_ALT;
|
||||
if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & (1 << 31))
|
||||
if ((GetKeyState(VK_LWIN) | GetKeyState(VK_RWIN)) & 0x8000)
|
||||
mods |= GLFW_MOD_SUPER;
|
||||
if (GetKeyState(VK_CAPITAL) & 1)
|
||||
mods |= GLFW_MOD_CAPS_LOCK;
|
||||
if (GetKeyState(VK_NUMLOCK) & 1)
|
||||
mods |= GLFW_MOD_NUM_LOCK;
|
||||
|
||||
return mods;
|
||||
}
|
||||
@@ -402,14 +406,18 @@ static int getAsyncKeyMods(void)
|
||||
{
|
||||
int mods = 0;
|
||||
|
||||
if (GetAsyncKeyState(VK_SHIFT) & (1 << 31))
|
||||
if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
|
||||
mods |= GLFW_MOD_SHIFT;
|
||||
if (GetAsyncKeyState(VK_CONTROL) & (1 << 31))
|
||||
if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
|
||||
mods |= GLFW_MOD_CONTROL;
|
||||
if (GetAsyncKeyState(VK_MENU) & (1 << 31))
|
||||
if (GetAsyncKeyState(VK_MENU) & 0x8000)
|
||||
mods |= GLFW_MOD_ALT;
|
||||
if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & (1 << 31))
|
||||
if ((GetAsyncKeyState(VK_LWIN) | GetAsyncKeyState(VK_RWIN)) & 0x8000)
|
||||
mods |= GLFW_MOD_SUPER;
|
||||
if (GetAsyncKeyState(VK_CAPITAL) & 1)
|
||||
mods |= GLFW_MOD_CAPS_LOCK;
|
||||
if (GetAsyncKeyState(VK_NUMLOCK) & 1)
|
||||
mods |= GLFW_MOD_NUM_LOCK;
|
||||
|
||||
return mods;
|
||||
}
|
||||
@@ -463,30 +471,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
|
||||
@@ -893,7 +900,10 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
if (iconified)
|
||||
releaseMonitor(window);
|
||||
else
|
||||
{
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
}
|
||||
|
||||
window->win32.iconified = iconified;
|
||||
@@ -988,6 +998,14 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_DPICHANGED:
|
||||
{
|
||||
const float xscale = HIWORD(wParam) / 96.f;
|
||||
const float yscale = LOWORD(wParam) / 96.f;
|
||||
_glfwInputWindowContentScale(window, xscale, yscale);
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_SETCURSOR:
|
||||
{
|
||||
if (LOWORD(lParam) == HTCLIENT)
|
||||
@@ -1224,11 +1242,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;
|
||||
@@ -1344,7 +1359,10 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height)
|
||||
if (window->monitor)
|
||||
{
|
||||
if (window->monitor->window == window)
|
||||
{
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1443,7 +1461,7 @@ void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformShowWindow(_GLFWwindow* window)
|
||||
{
|
||||
ShowWindow(window->win32.handle, SW_SHOW);
|
||||
ShowWindow(window->win32.handle, SW_SHOWNA);
|
||||
}
|
||||
|
||||
void _glfwPlatformHideWindow(_GLFWwindow* window)
|
||||
@@ -1479,7 +1497,10 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
if (monitor)
|
||||
{
|
||||
if (monitor->window == window)
|
||||
{
|
||||
acquireMonitor(window);
|
||||
fitToMonitor(window);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1502,20 +1523,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
|
||||
{
|
||||
@@ -1567,6 +1595,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
return IsZoomed(window->win32.handle);
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
{
|
||||
return cursorInClientArea(window);
|
||||
}
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
return window->win32.transparent && _glfwIsCompositionEnabledWin32();
|
||||
|
||||
108
glfw/window.c
vendored
108
glfw/window.c
vendored
@@ -38,6 +38,8 @@
|
||||
////// GLFW event API //////
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Notifies shared code that a window has lost or received input focus
|
||||
//
|
||||
void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
||||
{
|
||||
if (window->callbacks.focus)
|
||||
@@ -64,42 +66,68 @@ void _glfwInputWindowFocus(_GLFWwindow* window, GLFWbool focused)
|
||||
}
|
||||
}
|
||||
|
||||
// Notifies shared code that a window has moved
|
||||
// The position is specified in client-area relative screen coordinates
|
||||
//
|
||||
void _glfwInputWindowPos(_GLFWwindow* window, int x, int y)
|
||||
{
|
||||
if (window->callbacks.pos)
|
||||
window->callbacks.pos((GLFWwindow*) window, x, y);
|
||||
}
|
||||
|
||||
// Notifies shared code that a window has been resized
|
||||
// The size is specified in screen coordinates
|
||||
//
|
||||
void _glfwInputWindowSize(_GLFWwindow* window, int width, int height)
|
||||
{
|
||||
if (window->callbacks.size)
|
||||
window->callbacks.size((GLFWwindow*) window, width, height);
|
||||
}
|
||||
|
||||
// Notifies shared code that a window has been iconified or restored
|
||||
//
|
||||
void _glfwInputWindowIconify(_GLFWwindow* window, GLFWbool iconified)
|
||||
{
|
||||
if (window->callbacks.iconify)
|
||||
window->callbacks.iconify((GLFWwindow*) window, iconified);
|
||||
}
|
||||
|
||||
// Notifies shared code that a window has been maximized or restored
|
||||
//
|
||||
void _glfwInputWindowMaximize(_GLFWwindow* window, GLFWbool maximized)
|
||||
{
|
||||
if (window->callbacks.maximize)
|
||||
window->callbacks.maximize((GLFWwindow*) window, maximized);
|
||||
}
|
||||
|
||||
// Notifies shared code that a window framebuffer has been resized
|
||||
// The size is specified in pixels
|
||||
//
|
||||
void _glfwInputFramebufferSize(_GLFWwindow* window, int width, int height)
|
||||
{
|
||||
if (window->callbacks.fbsize)
|
||||
window->callbacks.fbsize((GLFWwindow*) window, width, height);
|
||||
}
|
||||
|
||||
// Notifies shared code that a window content scale has changed
|
||||
// The scale is specified as the ratio between the current and default DPI
|
||||
//
|
||||
void _glfwInputWindowContentScale(_GLFWwindow* window, float xscale, float yscale)
|
||||
{
|
||||
if (window->callbacks.scale)
|
||||
window->callbacks.scale((GLFWwindow*) window, xscale, yscale);
|
||||
}
|
||||
|
||||
// Notifies shared code that the window contents needs updating
|
||||
//
|
||||
void _glfwInputWindowDamage(_GLFWwindow* window)
|
||||
{
|
||||
if (window->callbacks.refresh)
|
||||
window->callbacks.refresh((GLFWwindow*) window);
|
||||
}
|
||||
|
||||
// Notifies shared code that the user wishes to close a window
|
||||
//
|
||||
void _glfwInputWindowCloseRequest(_GLFWwindow* window)
|
||||
{
|
||||
window->shouldClose = GLFW_TRUE;
|
||||
@@ -108,6 +136,8 @@ void _glfwInputWindowCloseRequest(_GLFWwindow* window)
|
||||
window->callbacks.close((GLFWwindow*) window);
|
||||
}
|
||||
|
||||
// Notifies shared code that a window has changed its desired monitor
|
||||
//
|
||||
void _glfwInputWindowMonitor(_GLFWwindow* window, _GLFWmonitor* monitor)
|
||||
{
|
||||
window->monitor = monitor;
|
||||
@@ -127,7 +157,6 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
_GLFWctxconfig ctxconfig;
|
||||
_GLFWwndconfig wndconfig;
|
||||
_GLFWwindow* window;
|
||||
_GLFWwindow* previous;
|
||||
|
||||
assert(title != NULL);
|
||||
assert(width >= 0);
|
||||
@@ -153,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;
|
||||
|
||||
@@ -191,36 +210,32 @@ GLFWAPI GLFWwindow* glfwCreateWindow(int width, int height,
|
||||
window->numer = GLFW_DONT_CARE;
|
||||
window->denom = GLFW_DONT_CARE;
|
||||
|
||||
// Save the currently current context so it can be restored later
|
||||
previous = _glfwPlatformGetTls(&_glfw.contextSlot);
|
||||
if (ctxconfig.client != GLFW_NO_API)
|
||||
glfwMakeContextCurrent(NULL);
|
||||
|
||||
// Open the actual window and create its context
|
||||
if (!_glfwPlatformCreateWindow(window, &wndconfig, &ctxconfig, &fbconfig))
|
||||
{
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
glfwDestroyWindow((GLFWwindow*) window);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (ctxconfig.client != GLFW_NO_API)
|
||||
{
|
||||
window->context.makeCurrent(window);
|
||||
|
||||
// Retrieve the actual (as opposed to requested) context attributes
|
||||
if (!_glfwRefreshContextAttribs(&ctxconfig))
|
||||
if (!_glfwRefreshContextAttribs(window, &ctxconfig))
|
||||
{
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
glfwDestroyWindow((GLFWwindow*) window);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Restore the previously current context (or NULL)
|
||||
glfwMakeContextCurrent((GLFWwindow*) previous);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
@@ -349,9 +364,6 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
case GLFW_COCOA_RETINA_FRAMEBUFFER:
|
||||
_glfw.hints.window.ns.retina = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
case GLFW_COCOA_FRAME_AUTOSAVE:
|
||||
_glfw.hints.window.ns.frame = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
case GLFW_COCOA_GRAPHICS_SWITCHING:
|
||||
_glfw.hints.context.nsgl.offline = value ? GLFW_TRUE : GLFW_FALSE;
|
||||
return;
|
||||
@@ -396,6 +408,31 @@ GLFWAPI void glfwWindowHint(int hint, int value)
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint 0x%08X", hint);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwWindowHintString(int hint, const char* value)
|
||||
{
|
||||
assert(value != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
switch (hint)
|
||||
{
|
||||
case GLFW_COCOA_FRAME_NAME:
|
||||
strncpy(_glfw.hints.window.ns.frameName, value,
|
||||
sizeof(_glfw.hints.window.ns.frameName) - 1);
|
||||
return;
|
||||
case GLFW_X11_CLASS_NAME:
|
||||
strncpy(_glfw.hints.window.x11.className, value,
|
||||
sizeof(_glfw.hints.window.x11.className) - 1);
|
||||
return;
|
||||
case GLFW_X11_INSTANCE_NAME:
|
||||
strncpy(_glfw.hints.window.x11.instanceName, value,
|
||||
sizeof(_glfw.hints.window.x11.instanceName) - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
_glfwInputError(GLFW_INVALID_ENUM, "Invalid window hint string 0x%08X", hint);
|
||||
}
|
||||
|
||||
GLFWAPI void glfwDestroyWindow(GLFWwindow* handle)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
@@ -781,6 +818,8 @@ GLFWAPI int glfwGetWindowAttrib(GLFWwindow* handle, int attrib)
|
||||
return _glfwPlatformWindowVisible(window);
|
||||
case GLFW_MAXIMIZED:
|
||||
return _glfwPlatformWindowMaximized(window);
|
||||
case GLFW_HOVERED:
|
||||
return _glfwPlatformWindowHovered(window);
|
||||
case GLFW_TRANSPARENT_FRAMEBUFFER:
|
||||
return _glfwPlatformFramebufferTransparent(window);
|
||||
case GLFW_RESIZABLE:
|
||||
@@ -1015,6 +1054,17 @@ GLFWAPI GLFWframebuffersizefun glfwSetFramebufferSizeCallback(GLFWwindow* handle
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
GLFWAPI GLFWwindowcontentscalefun glfwSetWindowContentScaleCallback(GLFWwindow* handle,
|
||||
GLFWwindowcontentscalefun cbfun)
|
||||
{
|
||||
_GLFWwindow* window = (_GLFWwindow*) handle;
|
||||
assert(window != NULL);
|
||||
|
||||
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
|
||||
_GLFW_SWAP_POINTERS(window->callbacks.scale, cbfun);
|
||||
return cbfun;
|
||||
}
|
||||
|
||||
GLFWAPI void glfwPollEvents(void)
|
||||
{
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
454
glfw/wl_init.c
vendored
454
glfw/wl_init.c
vendored
@@ -32,9 +32,9 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <unistd.h>
|
||||
#include <wayland-client.h>
|
||||
#include <wayland-cursor.h>
|
||||
|
||||
|
||||
static inline int min(int n1, int n2)
|
||||
@@ -42,6 +42,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,
|
||||
@@ -49,11 +82,25 @@ static void pointerHandleEnter(void* data,
|
||||
wl_fixed_t sx,
|
||||
wl_fixed_t sy)
|
||||
{
|
||||
_GLFWwindow* window = wl_surface_get_user_data(surface);
|
||||
// Happens in the case we just destroyed the surface.
|
||||
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;
|
||||
|
||||
window->wl.hovered = GLFW_TRUE;
|
||||
|
||||
_glfwPlatformSetCursor(window, window->wl.currentCursor);
|
||||
_glfwInputCursorEnter(window, GLFW_TRUE);
|
||||
}
|
||||
@@ -68,11 +115,46 @@ static void pointerHandleLeave(void* data,
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
window->wl.hovered = GLFW_FALSE;
|
||||
|
||||
_glfw.wl.pointerSerial = serial;
|
||||
_glfw.wl.pointerFocus = NULL;
|
||||
_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,
|
||||
@@ -80,6 +162,7 @@ static void pointerHandleMotion(void* data,
|
||||
wl_fixed_t sy)
|
||||
{
|
||||
_GLFWwindow* window = _glfw.wl.pointerFocus;
|
||||
const char* cursorName;
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
@@ -92,13 +175,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,
|
||||
@@ -107,8 +224,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;
|
||||
|
||||
@@ -125,37 +310,28 @@ 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.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;
|
||||
|
||||
/* 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;
|
||||
assert(axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL ||
|
||||
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
|
||||
|
||||
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;
|
||||
default:
|
||||
assert(GLFW_FALSE);
|
||||
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);
|
||||
}
|
||||
@@ -264,6 +440,10 @@ static void keyboardHandleKeymap(void* data,
|
||||
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,
|
||||
@@ -272,7 +452,17 @@ static void keyboardHandleEnter(void* data,
|
||||
struct wl_surface* surface,
|
||||
struct wl_array* keys)
|
||||
{
|
||||
// Happens in the case we just destroyed the surface.
|
||||
if (!surface)
|
||||
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);
|
||||
@@ -322,7 +512,7 @@ static xkb_keysym_t composeSymbol(xkb_keysym_t sym)
|
||||
}
|
||||
#endif
|
||||
|
||||
static void inputChar(_GLFWwindow* window, uint32_t key)
|
||||
static GLFWbool inputChar(_GLFWwindow* window, uint32_t key)
|
||||
{
|
||||
uint32_t code, numSyms;
|
||||
long cp;
|
||||
@@ -347,6 +537,8 @@ static void inputChar(_GLFWwindow* window, uint32_t key)
|
||||
_glfwInputChar(window, cp, mods, plain);
|
||||
}
|
||||
}
|
||||
|
||||
return xkb_keymap_key_repeats(_glfw.wl.xkb.keymap, syms[0]);
|
||||
}
|
||||
|
||||
static void keyboardHandleKey(void* data,
|
||||
@@ -359,6 +551,8 @@ static void keyboardHandleKey(void* data,
|
||||
int keyCode;
|
||||
int action;
|
||||
_GLFWwindow* window = _glfw.wl.keyboardFocus;
|
||||
GLFWbool shouldRepeat;
|
||||
struct itimerspec timer = {};
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
@@ -371,7 +565,20 @@ static void keyboardHandleKey(void* data,
|
||||
_glfw.wl.xkb.modifiers);
|
||||
|
||||
if (action == GLFW_PRESS)
|
||||
inputChar(window, key);
|
||||
{
|
||||
shouldRepeat = inputChar(window, key);
|
||||
|
||||
if (shouldRepeat && _glfw.wl.keyboardRepeatRate > 0)
|
||||
{
|
||||
_glfw.wl.keyboardLastKey = keyCode;
|
||||
_glfw.wl.keyboardLastScancode = key;
|
||||
timer.it_interval.tv_sec = _glfw.wl.keyboardRepeatRate / 1000;
|
||||
timer.it_interval.tv_nsec = (_glfw.wl.keyboardRepeatRate % 1000) * 1000000;
|
||||
timer.it_value.tv_sec = _glfw.wl.keyboardRepeatDelay / 1000;
|
||||
timer.it_value.tv_nsec = (_glfw.wl.keyboardRepeatDelay % 1000) * 1000000;
|
||||
}
|
||||
}
|
||||
timerfd_settime(_glfw.wl.timerfd, 0, &timer, NULL);
|
||||
}
|
||||
|
||||
static void keyboardHandleModifiers(void* data,
|
||||
@@ -409,15 +616,36 @@ static void keyboardHandleModifiers(void* data,
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION
|
||||
static void keyboardHandleRepeatInfo(void* data,
|
||||
struct wl_keyboard* keyboard,
|
||||
int32_t rate,
|
||||
int32_t delay)
|
||||
{
|
||||
if (keyboard != _glfw.wl.keyboard)
|
||||
return;
|
||||
|
||||
_glfw.wl.keyboardRepeatRate = rate;
|
||||
_glfw.wl.keyboardRepeatDelay = delay;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct wl_keyboard_listener keyboardListener = {
|
||||
keyboardHandleKeymap,
|
||||
keyboardHandleEnter,
|
||||
keyboardHandleLeave,
|
||||
keyboardHandleKey,
|
||||
keyboardHandleModifiers,
|
||||
#ifdef WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION
|
||||
keyboardHandleRepeatInfo,
|
||||
#endif
|
||||
};
|
||||
|
||||
static void seatHandleCapabilities(void* data,
|
||||
@@ -447,8 +675,26 @@ 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,
|
||||
struct xdg_wm_base* wmBase,
|
||||
uint32_t serial)
|
||||
{
|
||||
xdg_wm_base_pong(wmBase, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_wm_base_listener wmBaseListener = {
|
||||
wmBaseHandlePing
|
||||
};
|
||||
|
||||
static void registryHandleGlobal(void* data,
|
||||
@@ -464,6 +710,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 =
|
||||
@@ -482,11 +733,24 @@ 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);
|
||||
}
|
||||
}
|
||||
else if (strcmp(interface, "xdg_wm_base") == 0)
|
||||
{
|
||||
_glfw.wl.wmBase =
|
||||
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 =
|
||||
@@ -514,6 +778,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -663,50 +939,84 @@ static void createKeyTables(void)
|
||||
|
||||
int _glfwPlatformInit(void)
|
||||
{
|
||||
_glfw.wl.xkb.handle = dlopen("libxkbcommon.so.0", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.wl.cursor.handle = _glfw_dlopen("libwayland-cursor.so.0");
|
||||
if (!_glfw.wl.cursor.handle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to open libwayland-cursor");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
_glfw.wl.cursor.theme_load = (PFN_wl_cursor_theme_load)
|
||||
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_theme_load");
|
||||
_glfw.wl.cursor.theme_destroy = (PFN_wl_cursor_theme_destroy)
|
||||
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_theme_destroy");
|
||||
_glfw.wl.cursor.theme_get_cursor = (PFN_wl_cursor_theme_get_cursor)
|
||||
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_theme_get_cursor");
|
||||
_glfw.wl.cursor.image_get_buffer = (PFN_wl_cursor_image_get_buffer)
|
||||
_glfw_dlsym(_glfw.wl.cursor.handle, "wl_cursor_image_get_buffer");
|
||||
|
||||
_glfw.wl.egl.handle = _glfw_dlopen("libwayland-egl.so.1");
|
||||
if (!_glfw.wl.egl.handle)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Failed to open libwayland-egl");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
_glfw.wl.egl.window_create = (PFN_wl_egl_window_create)
|
||||
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_create");
|
||||
_glfw.wl.egl.window_destroy = (PFN_wl_egl_window_destroy)
|
||||
_glfw_dlsym(_glfw.wl.egl.handle, "wl_egl_window_destroy");
|
||||
_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.");
|
||||
"Wayland: Failed to open libxkbcommon");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
_glfw.wl.xkb.context_new = (PFN_xkb_context_new)
|
||||
dlsym(_glfw.wl.xkb.handle, "xkb_context_new");
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_context_new");
|
||||
_glfw.wl.xkb.context_unref = (PFN_xkb_context_unref)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "xkb_keymap_mod_get_index");
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_mod_get_index");
|
||||
_glfw.wl.xkb.keymap_key_repeats = (PFN_xkb_keymap_key_repeats)
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_keymap_key_repeats");
|
||||
_glfw.wl.xkb.state_new = (PFN_xkb_state_new)
|
||||
dlsym(_glfw.wl.xkb.handle, "xkb_state_new");
|
||||
_glfw_dlsym(_glfw.wl.xkb.handle, "xkb_state_new");
|
||||
_glfw.wl.xkb.state_unref = (PFN_xkb_state_unref)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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)
|
||||
dlsym(_glfw.wl.xkb.handle, "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);
|
||||
@@ -741,13 +1051,17 @@ int _glfwPlatformInit(void)
|
||||
|
||||
_glfwInitTimerPOSIX();
|
||||
|
||||
_glfw.wl.timerfd = -1;
|
||||
if (_glfw.wl.seatVersion >= 4)
|
||||
_glfw.wl.timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
|
||||
|
||||
if (_glfw.wl.pointer && _glfw.wl.shm)
|
||||
{
|
||||
_glfw.wl.cursorTheme = wl_cursor_theme_load(NULL, 32, _glfw.wl.shm);
|
||||
if (!_glfw.wl.cursorTheme)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Unable to load default cursor theme\n");
|
||||
"Wayland: Unable to load default cursor theme");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
_glfw.wl.cursorSurface =
|
||||
@@ -759,30 +1073,52 @@ int _glfwPlatformInit(void)
|
||||
|
||||
void _glfwPlatformTerminate(void)
|
||||
{
|
||||
_glfwTerminateEGL();
|
||||
_glfwTerminateJoysticksLinux();
|
||||
_glfwTerminateEGL();
|
||||
if (_glfw.wl.egl.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.wl.egl.handle);
|
||||
_glfw.wl.egl.handle = NULL;
|
||||
}
|
||||
|
||||
#ifdef HAVE_XKBCOMMON_COMPOSE_H
|
||||
xkb_compose_state_unref(_glfw.wl.xkb.composeState);
|
||||
if (_glfw.wl.xkb.composeState)
|
||||
xkb_compose_state_unref(_glfw.wl.xkb.composeState);
|
||||
#endif
|
||||
|
||||
xkb_keymap_unref(_glfw.wl.xkb.keymap);
|
||||
xkb_state_unref(_glfw.wl.xkb.state);
|
||||
xkb_context_unref(_glfw.wl.xkb.context);
|
||||
|
||||
dlclose(_glfw.wl.xkb.handle);
|
||||
_glfw.wl.xkb.handle = NULL;
|
||||
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;
|
||||
}
|
||||
|
||||
if (_glfw.wl.cursorTheme)
|
||||
wl_cursor_theme_destroy(_glfw.wl.cursorTheme);
|
||||
if (_glfw.wl.cursor.handle)
|
||||
{
|
||||
_glfw_dlclose(_glfw.wl.cursor.handle);
|
||||
_glfw.wl.cursor.handle = NULL;
|
||||
}
|
||||
|
||||
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)
|
||||
wl_pointer_destroy(_glfw.wl.pointer);
|
||||
if (_glfw.wl.keyboard)
|
||||
|
||||
9
glfw/wl_monitor.c
vendored
9
glfw/wl_monitor.c
vendored
@@ -52,7 +52,7 @@ static void geometry(void* data,
|
||||
monitor->heightMM = physicalHeight;
|
||||
|
||||
snprintf(name, sizeof(name), "%s %s", make, model);
|
||||
monitor->name = strdup(name);
|
||||
monitor->name = _glfw_strdup(name);
|
||||
}
|
||||
|
||||
static void mode(void* data,
|
||||
@@ -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)
|
||||
|
||||
99
glfw/wl_platform.h
vendored
99
glfw/wl_platform.h
vendored
@@ -52,6 +52,8 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#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"
|
||||
@@ -71,11 +73,40 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
|
||||
#define _GLFW_PLATFORM_CONTEXT_STATE
|
||||
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE
|
||||
|
||||
struct wl_cursor_image {
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t hotspot_x;
|
||||
uint32_t hotspot_y;
|
||||
uint32_t delay;
|
||||
};
|
||||
struct wl_cursor {
|
||||
unsigned int image_count;
|
||||
struct wl_cursor_image** images;
|
||||
char* name;
|
||||
};
|
||||
typedef struct wl_cursor_theme* (* PFN_wl_cursor_theme_load)(const char*, int, struct wl_shm*);
|
||||
typedef void (* PFN_wl_cursor_theme_destroy)(struct wl_cursor_theme*);
|
||||
typedef struct wl_cursor* (* PFN_wl_cursor_theme_get_cursor)(struct wl_cursor_theme*, const char*);
|
||||
typedef struct wl_buffer* (* PFN_wl_cursor_image_get_buffer)(struct wl_cursor_image*);
|
||||
#define wl_cursor_theme_load _glfw.wl.cursor.theme_load
|
||||
#define wl_cursor_theme_destroy _glfw.wl.cursor.theme_destroy
|
||||
#define wl_cursor_theme_get_cursor _glfw.wl.cursor.theme_get_cursor
|
||||
#define wl_cursor_image_get_buffer _glfw.wl.cursor.image_get_buffer
|
||||
|
||||
typedef struct wl_egl_window* (* PFN_wl_egl_window_create)(struct wl_surface*, int, int);
|
||||
typedef void (* PFN_wl_egl_window_destroy)(struct wl_egl_window*);
|
||||
typedef void (* PFN_wl_egl_window_resize)(struct wl_egl_window*, int, int, int, int);
|
||||
#define wl_egl_window_create _glfw.wl.egl.window_create
|
||||
#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 int (* PFN_xkb_keymap_key_repeats)(struct xkb_keymap*, xkb_keycode_t);
|
||||
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**);
|
||||
@@ -86,6 +117,7 @@ typedef xkb_mod_mask_t (* PFN_xkb_state_serialize_mods)(struct xkb_state*, enum
|
||||
#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_keymap_key_repeats _glfw.wl.xkb.keymap_key_repeats
|
||||
#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
|
||||
@@ -109,6 +141,28 @@ typedef xkb_keysym_t (* PFN_xkb_compose_state_get_one_sym)(struct xkb_compose_st
|
||||
#define xkb_compose_state_get_one_sym _glfw.wl.xkb.compose_state_get_one_sym
|
||||
#endif
|
||||
|
||||
#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)
|
||||
|
||||
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
|
||||
//
|
||||
@@ -117,12 +171,18 @@ typedef struct _GLFWwindowWayland
|
||||
int width, height;
|
||||
GLFWbool visible;
|
||||
GLFWbool maximized;
|
||||
GLFWbool hovered;
|
||||
GLFWbool transparent;
|
||||
struct wl_surface* surface;
|
||||
struct wl_egl_window* native;
|
||||
struct wl_shell_surface* shellSurface;
|
||||
struct wl_callback* callback;
|
||||
|
||||
struct {
|
||||
struct xdg_surface* surface;
|
||||
struct xdg_toplevel* toplevel;
|
||||
} xdg;
|
||||
|
||||
_GLFWcursor* currentCursor;
|
||||
double cursorPosX, cursorPosY;
|
||||
|
||||
@@ -142,6 +202,15 @@ typedef struct _GLFWwindowWayland
|
||||
|
||||
struct zwp_idle_inhibitor_v1* idleInhibitor;
|
||||
|
||||
// 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
|
||||
@@ -151,21 +220,30 @@ 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;
|
||||
|
||||
int32_t keyboardRepeatRate;
|
||||
int32_t keyboardRepeatDelay;
|
||||
int keyboardLastKey;
|
||||
int keyboardLastScancode;
|
||||
int timerfd;
|
||||
short int keycodes[256];
|
||||
short int scancodes[GLFW_KEY_LAST + 1];
|
||||
|
||||
@@ -183,6 +261,8 @@ typedef struct _GLFWlibraryWayland
|
||||
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;
|
||||
@@ -190,6 +270,7 @@ typedef struct _GLFWlibraryWayland
|
||||
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_keymap_key_repeats keymap_key_repeats;
|
||||
PFN_xkb_state_new state_new;
|
||||
PFN_xkb_state_unref state_unref;
|
||||
PFN_xkb_state_key_get_syms state_key_get_syms;
|
||||
@@ -210,6 +291,23 @@ typedef struct _GLFWlibraryWayland
|
||||
_GLFWwindow* pointerFocus;
|
||||
_GLFWwindow* keyboardFocus;
|
||||
|
||||
struct {
|
||||
void* handle;
|
||||
|
||||
PFN_wl_cursor_theme_load theme_load;
|
||||
PFN_wl_cursor_theme_destroy theme_destroy;
|
||||
PFN_wl_cursor_theme_get_cursor theme_get_cursor;
|
||||
PFN_wl_cursor_image_get_buffer image_get_buffer;
|
||||
} cursor;
|
||||
|
||||
struct {
|
||||
void* handle;
|
||||
|
||||
PFN_wl_egl_window_create window_create;
|
||||
PFN_wl_egl_window_destroy window_destroy;
|
||||
PFN_wl_egl_window_resize window_resize;
|
||||
} egl;
|
||||
|
||||
} _GLFWlibraryWayland;
|
||||
|
||||
// Wayland-specific per-monitor data
|
||||
@@ -217,6 +315,7 @@ typedef struct _GLFWlibraryWayland
|
||||
typedef struct _GLFWmonitorWayland
|
||||
{
|
||||
struct wl_output* output;
|
||||
int name;
|
||||
int currentMode;
|
||||
|
||||
int x;
|
||||
|
||||
781
glfw/wl_window.c
vendored
781
glfw/wl_window.c
vendored
@@ -35,11 +35,9 @@
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/timerfd.h>
|
||||
#include <poll.h>
|
||||
|
||||
#include <wayland-egl.h>
|
||||
#include <wayland-cursor.h>
|
||||
|
||||
|
||||
static void handlePing(void* data,
|
||||
struct wl_shell_surface* shellSurface,
|
||||
@@ -60,6 +58,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;
|
||||
@@ -97,9 +105,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;
|
||||
@@ -120,11 +384,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);
|
||||
resizeWindow(window);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,21 +435,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)
|
||||
@@ -233,15 +479,49 @@ 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)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: wl_shell protocol not available");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
window->wl.shellSurface = wl_shell_get_shell_surface(_glfw.wl.shell,
|
||||
window->wl.surface);
|
||||
if (!window->wl.shellSurface)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Shell surface creation failed");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
wl_shell_surface_add_listener(window->wl.shellSurface,
|
||||
&shellSurfaceListener,
|
||||
@@ -252,12 +532,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)
|
||||
{
|
||||
@@ -275,16 +550,144 @@ static GLFWbool createShellSurface(_GLFWwindow* window)
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static int
|
||||
createTmpfileCloexec(char* tmpname)
|
||||
static void xdgToplevelHandleConfigure(void* data,
|
||||
struct xdg_toplevel* toplevel,
|
||||
int32_t width,
|
||||
int32_t height,
|
||||
struct wl_array* states)
|
||||
{
|
||||
int fd;
|
||||
_GLFWwindow* window = data;
|
||||
float aspectRatio;
|
||||
float targetRatio;
|
||||
uint32_t* state;
|
||||
GLFWbool maximized = GLFW_FALSE;
|
||||
GLFWbool fullscreen = GLFW_FALSE;
|
||||
GLFWbool activated = GLFW_FALSE;
|
||||
|
||||
fd = mkostemp(tmpname, O_CLOEXEC);
|
||||
if (fd >= 0)
|
||||
unlink(tmpname);
|
||||
wl_array_for_each(state, states)
|
||||
{
|
||||
switch (*state)
|
||||
{
|
||||
case XDG_TOPLEVEL_STATE_MAXIMIZED:
|
||||
maximized = GLFW_TRUE;
|
||||
break;
|
||||
case XDG_TOPLEVEL_STATE_FULLSCREEN:
|
||||
fullscreen = GLFW_TRUE;
|
||||
break;
|
||||
case XDG_TOPLEVEL_STATE_RESIZING:
|
||||
break;
|
||||
case XDG_TOPLEVEL_STATE_ACTIVATED:
|
||||
activated = GLFW_TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return fd;
|
||||
if (width != 0 && height != 0)
|
||||
{
|
||||
if (!maximized && !fullscreen)
|
||||
{
|
||||
if (window->numer != GLFW_DONT_CARE && window->denom != GLFW_DONT_CARE)
|
||||
{
|
||||
aspectRatio = (float)width / (float)height;
|
||||
targetRatio = (float)window->numer / (float)window->denom;
|
||||
if (aspectRatio < targetRatio)
|
||||
height = width / targetRatio;
|
||||
else if (aspectRatio > targetRatio)
|
||||
width = height * targetRatio;
|
||||
}
|
||||
}
|
||||
|
||||
_glfwInputWindowSize(window, width, height);
|
||||
_glfwPlatformSetWindowSize(window, width, height);
|
||||
_glfwInputWindowDamage(window);
|
||||
}
|
||||
|
||||
if (!window->wl.justCreated && !activated && window->autoIconify)
|
||||
_glfwPlatformIconifyWindow(window);
|
||||
_glfwInputWindowFocus(window, activated);
|
||||
window->wl.justCreated = GLFW_FALSE;
|
||||
}
|
||||
|
||||
static void xdgToplevelHandleClose(void* data,
|
||||
struct xdg_toplevel* toplevel)
|
||||
{
|
||||
_GLFWwindow* window = data;
|
||||
_glfwInputWindowCloseRequest(window);
|
||||
}
|
||||
|
||||
static const struct xdg_toplevel_listener xdgToplevelListener = {
|
||||
xdgToplevelHandleConfigure,
|
||||
xdgToplevelHandleClose
|
||||
};
|
||||
|
||||
static void xdgSurfaceHandleConfigure(void* data,
|
||||
struct xdg_surface* surface,
|
||||
uint32_t serial)
|
||||
{
|
||||
xdg_surface_ack_configure(surface, serial);
|
||||
}
|
||||
|
||||
static const struct xdg_surface_listener xdgSurfaceListener = {
|
||||
xdgSurfaceHandleConfigure
|
||||
};
|
||||
|
||||
static GLFWbool createXdgSurface(_GLFWwindow* window)
|
||||
{
|
||||
window->wl.xdg.surface = xdg_wm_base_get_xdg_surface(_glfw.wl.wmBase,
|
||||
window->wl.surface);
|
||||
if (!window->wl.xdg.surface)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: xdg-surface creation failed");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
xdg_surface_add_listener(window->wl.xdg.surface,
|
||||
&xdgSurfaceListener,
|
||||
window);
|
||||
|
||||
window->wl.xdg.toplevel = xdg_surface_get_toplevel(window->wl.xdg.surface);
|
||||
if (!window->wl.xdg.toplevel)
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: xdg-toplevel creation failed");
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
xdg_toplevel_add_listener(window->wl.xdg.toplevel,
|
||||
&xdgToplevelListener,
|
||||
window);
|
||||
|
||||
if (window->wl.title)
|
||||
xdg_toplevel_set_title(window->wl.xdg.toplevel, window->wl.title);
|
||||
|
||||
if (window->minwidth != GLFW_DONT_CARE && window->minheight != GLFW_DONT_CARE)
|
||||
xdg_toplevel_set_min_size(window->wl.xdg.toplevel,
|
||||
window->minwidth, window->minheight);
|
||||
if (window->maxwidth != GLFW_DONT_CARE && window->maxheight != GLFW_DONT_CARE)
|
||||
xdg_toplevel_set_max_size(window->wl.xdg.toplevel,
|
||||
window->maxwidth, window->maxheight);
|
||||
|
||||
if (window->monitor)
|
||||
{
|
||||
xdg_toplevel_set_fullscreen(window->wl.xdg.toplevel,
|
||||
window->monitor->wl.output);
|
||||
setIdleInhibitor(window, GLFW_TRUE);
|
||||
}
|
||||
else if (window->wl.maximized)
|
||||
{
|
||||
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
}
|
||||
|
||||
wl_surface_commit(window->wl.surface);
|
||||
wl_display_roundtrip(_glfw.wl.display);
|
||||
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -293,7 +696,10 @@ handleEvents(int timeout)
|
||||
struct wl_display* display = _glfw.wl.display;
|
||||
struct pollfd fds[] = {
|
||||
{ wl_display_get_fd(display), POLLIN },
|
||||
{ _glfw.wl.timerfd, POLLIN },
|
||||
};
|
||||
ssize_t read_ret;
|
||||
uint64_t repeats, i;
|
||||
|
||||
while (wl_display_prepare_read(display) != 0)
|
||||
wl_display_dispatch_pending(display);
|
||||
@@ -313,10 +719,29 @@ handleEvents(int timeout)
|
||||
return;
|
||||
}
|
||||
|
||||
if (poll(fds, 1, timeout) > 0)
|
||||
if (poll(fds, 2, timeout) > 0)
|
||||
{
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
if (fds[0].revents & POLLIN)
|
||||
{
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
|
||||
if (fds[1].revents & POLLIN)
|
||||
{
|
||||
read_ret = read(_glfw.wl.timerfd, &repeats, sizeof(repeats));
|
||||
if (read_ret != 8)
|
||||
return;
|
||||
|
||||
for (i = 0; i < repeats; ++i)
|
||||
_glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey,
|
||||
_glfw.wl.keyboardLastScancode, GLFW_REPEAT,
|
||||
_glfw.wl.xkb.modifiers);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -324,62 +749,6 @@ handleEvents(int timeout)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
// Translates a GLFW standard cursor to a theme cursor name
|
||||
//
|
||||
static char *translateCursorShape(int shape)
|
||||
@@ -411,6 +780,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
const _GLFWctxconfig* ctxconfig,
|
||||
const _GLFWfbconfig* fbconfig)
|
||||
{
|
||||
window->wl.justCreated = GLFW_TRUE;
|
||||
window->wl.transparent = fbconfig->transparent;
|
||||
|
||||
if (!createSurface(window, wndconfig))
|
||||
@@ -436,17 +806,27 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
|
||||
}
|
||||
|
||||
if (wndconfig->title)
|
||||
window->wl.title = strdup(wndconfig->title);
|
||||
window->wl.title = _glfw_strdup(wndconfig->title);
|
||||
|
||||
if (wndconfig->visible)
|
||||
{
|
||||
if (!createShellSurface(window))
|
||||
return GLFW_FALSE;
|
||||
if (_glfw.wl.wmBase)
|
||||
{
|
||||
if (!createXdgSurface(window))
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!createShellSurface(window))
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
window->wl.visible = GLFW_TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
window->wl.xdg.surface = NULL;
|
||||
window->wl.xdg.toplevel = NULL;
|
||||
window->wl.shellSurface = NULL;
|
||||
window->wl.visible = GLFW_FALSE;
|
||||
}
|
||||
@@ -479,12 +859,22 @@ 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);
|
||||
|
||||
if (window->wl.shellSurface)
|
||||
wl_shell_surface_destroy(window->wl.shellSurface);
|
||||
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_destroy(window->wl.xdg.toplevel);
|
||||
|
||||
if (window->wl.xdg.surface)
|
||||
xdg_surface_destroy(window->wl.xdg.surface);
|
||||
|
||||
if (window->wl.surface)
|
||||
wl_surface_destroy(window->wl.surface);
|
||||
|
||||
@@ -496,8 +886,10 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title)
|
||||
{
|
||||
if (window->wl.title)
|
||||
free(window->wl.title);
|
||||
window->wl.title = strdup(title);
|
||||
if (window->wl.shellSurface)
|
||||
window->wl.title = _glfw_strdup(title);
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_set_title(window->wl.xdg.toplevel, title);
|
||||
else if (window->wl.shellSurface)
|
||||
wl_shell_surface_set_title(window->wl.shellSurface, title);
|
||||
}
|
||||
|
||||
@@ -535,22 +927,33 @@ 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,
|
||||
int minwidth, int minheight,
|
||||
int maxwidth, int maxheight)
|
||||
{
|
||||
// TODO: find out how to trigger a resize.
|
||||
// The actual limits are checked in the wl_shell_surface::configure handler.
|
||||
if (_glfw.wl.wmBase)
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
{
|
||||
if (minwidth == GLFW_DONT_CARE || minheight == GLFW_DONT_CARE)
|
||||
minwidth = minheight = 0;
|
||||
if (maxwidth == GLFW_DONT_CARE || maxheight == GLFW_DONT_CARE)
|
||||
maxwidth = maxheight = 0;
|
||||
xdg_toplevel_set_min_size(window->wl.xdg.toplevel, minwidth, minheight);
|
||||
xdg_toplevel_set_max_size(window->wl.xdg.toplevel, maxwidth, maxheight);
|
||||
wl_surface_commit(window->wl.surface);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: find out how to trigger a resize.
|
||||
// The actual limits are checked in the wl_shell_surface::configure handler.
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformSetWindowAspectRatio(_GLFWwindow* window, int numer, int denom)
|
||||
@@ -570,8 +973,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,
|
||||
@@ -585,41 +997,59 @@ void _glfwPlatformGetWindowContentScale(_GLFWwindow* window,
|
||||
|
||||
void _glfwPlatformIconifyWindow(_GLFWwindow* window)
|
||||
{
|
||||
// TODO: move to xdg_shell instead of wl_shell.
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Iconify window not supported");
|
||||
if (_glfw.wl.wmBase)
|
||||
{
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_set_minimized(window->wl.xdg.toplevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
_glfwInputError(GLFW_PLATFORM_ERROR,
|
||||
"Wayland: Iconify window not supported on wl_shell");
|
||||
}
|
||||
}
|
||||
|
||||
void _glfwPlatformRestoreWindow(_GLFWwindow* window)
|
||||
{
|
||||
// TODO: also do the same for iconified.
|
||||
if (window->monitor || window->wl.maximized)
|
||||
if (window->wl.xdg.toplevel)
|
||||
{
|
||||
if (window->wl.shellSurface)
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
|
||||
window->wl.maximized = GLFW_FALSE;
|
||||
if (window->monitor)
|
||||
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
|
||||
if (window->wl.maximized)
|
||||
xdg_toplevel_unset_maximized(window->wl.xdg.toplevel);
|
||||
// There is no way to unset minimized, or even to know if we are
|
||||
// minimized, so there is nothing to do here.
|
||||
}
|
||||
else if (window->wl.shellSurface)
|
||||
{
|
||||
if (window->monitor || window->wl.maximized)
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
}
|
||||
_glfwInputWindowMonitor(window, NULL);
|
||||
window->wl.maximized = GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformMaximizeWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (!window->monitor && !window->wl.maximized)
|
||||
if (window->wl.xdg.toplevel)
|
||||
{
|
||||
if (window->wl.shellSurface)
|
||||
{
|
||||
// Let the compositor select the best output.
|
||||
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
|
||||
}
|
||||
window->wl.maximized = GLFW_TRUE;
|
||||
xdg_toplevel_set_maximized(window->wl.xdg.toplevel);
|
||||
}
|
||||
else if (window->wl.shellSurface)
|
||||
{
|
||||
// Let the compositor select the best output.
|
||||
wl_shell_surface_set_maximized(window->wl.shellSurface, NULL);
|
||||
}
|
||||
window->wl.maximized = GLFW_TRUE;
|
||||
}
|
||||
|
||||
void _glfwPlatformShowWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (!window->monitor)
|
||||
if (!window->wl.visible)
|
||||
{
|
||||
if (!window->wl.shellSurface)
|
||||
if (_glfw.wl.wmBase)
|
||||
createXdgSurface(window);
|
||||
else if (!window->wl.shellSurface)
|
||||
createShellSurface(window);
|
||||
window->wl.visible = GLFW_TRUE;
|
||||
}
|
||||
@@ -627,12 +1057,19 @@ void _glfwPlatformShowWindow(_GLFWwindow* window)
|
||||
|
||||
void _glfwPlatformHideWindow(_GLFWwindow* window)
|
||||
{
|
||||
if (!window->monitor)
|
||||
if (window->wl.xdg.toplevel)
|
||||
{
|
||||
if (window->wl.shellSurface)
|
||||
wl_shell_surface_destroy(window->wl.shellSurface);
|
||||
window->wl.visible = GLFW_FALSE;
|
||||
xdg_toplevel_destroy(window->wl.xdg.toplevel);
|
||||
xdg_surface_destroy(window->wl.xdg.surface);
|
||||
window->wl.xdg.toplevel = NULL;
|
||||
window->wl.xdg.surface = NULL;
|
||||
}
|
||||
else if (window->wl.shellSurface)
|
||||
{
|
||||
wl_shell_surface_destroy(window->wl.shellSurface);
|
||||
window->wl.shellSurface = NULL;
|
||||
}
|
||||
window->wl.visible = GLFW_FALSE;
|
||||
}
|
||||
|
||||
void _glfwPlatformRequestWindowAttention(_GLFWwindow* window)
|
||||
@@ -664,17 +1101,17 @@ void _glfwPlatformSetWindowMonitor(_GLFWwindow* window,
|
||||
{
|
||||
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);
|
||||
setIdleInhibitor(window, GLFW_TRUE);
|
||||
setFullscreen(window, monitor, refreshRate);
|
||||
}
|
||||
else
|
||||
{
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
if (window->wl.xdg.toplevel)
|
||||
xdg_toplevel_unset_fullscreen(window->wl.xdg.toplevel);
|
||||
else if (window->wl.shellSurface)
|
||||
wl_shell_surface_set_toplevel(window->wl.shellSurface);
|
||||
setIdleInhibitor(window, GLFW_FALSE);
|
||||
if (window->decorated)
|
||||
createDecorations(window);
|
||||
}
|
||||
_glfwInputWindowMonitor(window, monitor);
|
||||
}
|
||||
@@ -686,7 +1123,8 @@ int _glfwPlatformWindowFocused(_GLFWwindow* window)
|
||||
|
||||
int _glfwPlatformWindowIconified(_GLFWwindow* window)
|
||||
{
|
||||
// TODO: move to xdg_shell, wl_shell doesn't have any iconified concept.
|
||||
// wl_shell doesn't have any iconified concept, and xdg-shell doesn’t give
|
||||
// any way to request whether a surface is iconified.
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
@@ -700,6 +1138,11 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
return window->wl.maximized;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
{
|
||||
return window->wl.hovered;
|
||||
}
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
return window->wl.transparent;
|
||||
@@ -714,9 +1157,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)
|
||||
@@ -796,53 +1243,7 @@ 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;
|
||||
|
||||
90
glfw/x11_init.c
vendored
90
glfw/x11_init.c
vendored
@@ -461,17 +461,17 @@ static void detectEWMH(void)
|
||||
//
|
||||
static GLFWbool initExtensions(void)
|
||||
{
|
||||
_glfw.x11.vidmode.handle = dlopen("libXxf86vm.so.1", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.x11.vidmode.handle = _glfw_dlopen("libXxf86vm.so.1");
|
||||
if (_glfw.x11.vidmode.handle)
|
||||
{
|
||||
_glfw.x11.vidmode.QueryExtension = (PFN_XF86VidModeQueryExtension)
|
||||
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeQueryExtension");
|
||||
_glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeQueryExtension");
|
||||
_glfw.x11.vidmode.GetGammaRamp = (PFN_XF86VidModeGetGammaRamp)
|
||||
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRamp");
|
||||
_glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRamp");
|
||||
_glfw.x11.vidmode.SetGammaRamp = (PFN_XF86VidModeSetGammaRamp)
|
||||
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeSetGammaRamp");
|
||||
_glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeSetGammaRamp");
|
||||
_glfw.x11.vidmode.GetGammaRampSize = (PFN_XF86VidModeGetGammaRampSize)
|
||||
dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRampSize");
|
||||
_glfw_dlsym(_glfw.x11.vidmode.handle, "XF86VidModeGetGammaRampSize");
|
||||
|
||||
_glfw.x11.vidmode.available =
|
||||
XF86VidModeQueryExtension(_glfw.x11.display,
|
||||
@@ -479,13 +479,13 @@ static GLFWbool initExtensions(void)
|
||||
&_glfw.x11.vidmode.errorBase);
|
||||
}
|
||||
|
||||
_glfw.x11.xi.handle = dlopen("libXi.so.6", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.x11.xi.handle = _glfw_dlopen("libXi.so.6");
|
||||
if (_glfw.x11.xi.handle)
|
||||
{
|
||||
_glfw.x11.xi.QueryVersion = (PFN_XIQueryVersion)
|
||||
dlsym(_glfw.x11.xi.handle, "XIQueryVersion");
|
||||
_glfw_dlsym(_glfw.x11.xi.handle, "XIQueryVersion");
|
||||
_glfw.x11.xi.SelectEvents = (PFN_XISelectEvents)
|
||||
dlsym(_glfw.x11.xi.handle, "XISelectEvents");
|
||||
_glfw_dlsym(_glfw.x11.xi.handle, "XISelectEvents");
|
||||
|
||||
if (XQueryExtension(_glfw.x11.display,
|
||||
"XInputExtension",
|
||||
@@ -505,45 +505,45 @@ static GLFWbool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
_glfw.x11.randr.handle = dlopen("libXrandr.so.2", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.x11.randr.handle = _glfw_dlopen("libXrandr.so.2");
|
||||
if (_glfw.x11.randr.handle)
|
||||
{
|
||||
_glfw.x11.randr.AllocGamma = (PFN_XRRAllocGamma)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRAllocGamma");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRAllocGamma");
|
||||
_glfw.x11.randr.FreeGamma = (PFN_XRRFreeGamma)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRFreeGamma");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeGamma");
|
||||
_glfw.x11.randr.FreeCrtcInfo = (PFN_XRRFreeCrtcInfo)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRFreeCrtcInfo");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeCrtcInfo");
|
||||
_glfw.x11.randr.FreeGamma = (PFN_XRRFreeGamma)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRFreeGamma");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeGamma");
|
||||
_glfw.x11.randr.FreeOutputInfo = (PFN_XRRFreeOutputInfo)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRFreeOutputInfo");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeOutputInfo");
|
||||
_glfw.x11.randr.FreeScreenResources = (PFN_XRRFreeScreenResources)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRFreeScreenResources");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRFreeScreenResources");
|
||||
_glfw.x11.randr.GetCrtcGamma = (PFN_XRRGetCrtcGamma)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGamma");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGamma");
|
||||
_glfw.x11.randr.GetCrtcGammaSize = (PFN_XRRGetCrtcGammaSize)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGammaSize");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRGetCrtcGammaSize");
|
||||
_glfw.x11.randr.GetCrtcInfo = (PFN_XRRGetCrtcInfo)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRGetCrtcInfo");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRGetCrtcInfo");
|
||||
_glfw.x11.randr.GetOutputInfo = (PFN_XRRGetOutputInfo)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRGetOutputInfo");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRGetOutputInfo");
|
||||
_glfw.x11.randr.GetOutputPrimary = (PFN_XRRGetOutputPrimary)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRGetOutputPrimary");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRGetOutputPrimary");
|
||||
_glfw.x11.randr.GetScreenResourcesCurrent = (PFN_XRRGetScreenResourcesCurrent)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRGetScreenResourcesCurrent");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRGetScreenResourcesCurrent");
|
||||
_glfw.x11.randr.QueryExtension = (PFN_XRRQueryExtension)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRQueryExtension");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRQueryExtension");
|
||||
_glfw.x11.randr.QueryVersion = (PFN_XRRQueryVersion)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRQueryVersion");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRQueryVersion");
|
||||
_glfw.x11.randr.SelectInput = (PFN_XRRSelectInput)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRSelectInput");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRSelectInput");
|
||||
_glfw.x11.randr.SetCrtcConfig = (PFN_XRRSetCrtcConfig)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRSetCrtcConfig");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRSetCrtcConfig");
|
||||
_glfw.x11.randr.SetCrtcGamma = (PFN_XRRSetCrtcGamma)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRSetCrtcGamma");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRSetCrtcGamma");
|
||||
_glfw.x11.randr.UpdateConfiguration = (PFN_XRRUpdateConfiguration)
|
||||
dlsym(_glfw.x11.randr.handle, "XRRUpdateConfiguration");
|
||||
_glfw_dlsym(_glfw.x11.randr.handle, "XRRUpdateConfiguration");
|
||||
|
||||
if (XRRQueryExtension(_glfw.x11.display,
|
||||
&_glfw.x11.randr.eventBase,
|
||||
@@ -593,26 +593,26 @@ static GLFWbool initExtensions(void)
|
||||
RROutputChangeNotifyMask);
|
||||
}
|
||||
|
||||
_glfw.x11.xcursor.handle = dlopen("libXcursor.so.1", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.x11.xcursor.handle = _glfw_dlopen("libXcursor.so.1");
|
||||
if (_glfw.x11.xcursor.handle)
|
||||
{
|
||||
_glfw.x11.xcursor.ImageCreate = (PFN_XcursorImageCreate)
|
||||
dlsym(_glfw.x11.xcursor.handle, "XcursorImageCreate");
|
||||
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageCreate");
|
||||
_glfw.x11.xcursor.ImageDestroy = (PFN_XcursorImageDestroy)
|
||||
dlsym(_glfw.x11.xcursor.handle, "XcursorImageDestroy");
|
||||
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageDestroy");
|
||||
_glfw.x11.xcursor.ImageLoadCursor = (PFN_XcursorImageLoadCursor)
|
||||
dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor");
|
||||
_glfw_dlsym(_glfw.x11.xcursor.handle, "XcursorImageLoadCursor");
|
||||
}
|
||||
|
||||
_glfw.x11.xinerama.handle = dlopen("libXinerama.so.1", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.x11.xinerama.handle = _glfw_dlopen("libXinerama.so.1");
|
||||
if (_glfw.x11.xinerama.handle)
|
||||
{
|
||||
_glfw.x11.xinerama.IsActive = (PFN_XineramaIsActive)
|
||||
dlsym(_glfw.x11.xinerama.handle, "XineramaIsActive");
|
||||
_glfw_dlsym(_glfw.x11.xinerama.handle, "XineramaIsActive");
|
||||
_glfw.x11.xinerama.QueryExtension = (PFN_XineramaQueryExtension)
|
||||
dlsym(_glfw.x11.xinerama.handle, "XineramaQueryExtension");
|
||||
_glfw_dlsym(_glfw.x11.xinerama.handle, "XineramaQueryExtension");
|
||||
_glfw.x11.xinerama.QueryScreens = (PFN_XineramaQueryScreens)
|
||||
dlsym(_glfw.x11.xinerama.handle, "XineramaQueryScreens");
|
||||
_glfw_dlsym(_glfw.x11.xinerama.handle, "XineramaQueryScreens");
|
||||
|
||||
if (XineramaQueryExtension(_glfw.x11.display,
|
||||
&_glfw.x11.xinerama.major,
|
||||
@@ -644,22 +644,22 @@ static GLFWbool initExtensions(void)
|
||||
}
|
||||
}
|
||||
|
||||
_glfw.x11.x11xcb.handle = dlopen("libX11-xcb.so.1", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.x11.x11xcb.handle = _glfw_dlopen("libX11-xcb.so.1");
|
||||
if (_glfw.x11.x11xcb.handle)
|
||||
{
|
||||
_glfw.x11.x11xcb.GetXCBConnection = (PFN_XGetXCBConnection)
|
||||
dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
|
||||
_glfw_dlsym(_glfw.x11.x11xcb.handle, "XGetXCBConnection");
|
||||
}
|
||||
|
||||
_glfw.x11.xrender.handle = dlopen("libXrender.so.1", RTLD_LAZY | RTLD_GLOBAL);
|
||||
_glfw.x11.xrender.handle = _glfw_dlopen("libXrender.so.1");
|
||||
if (_glfw.x11.xrender.handle)
|
||||
{
|
||||
_glfw.x11.xrender.QueryExtension = (PFN_XRenderQueryExtension)
|
||||
dlsym(_glfw.x11.xrender.handle, "XRenderQueryExtension");
|
||||
_glfw_dlsym(_glfw.x11.xrender.handle, "XRenderQueryExtension");
|
||||
_glfw.x11.xrender.QueryVersion = (PFN_XRenderQueryVersion)
|
||||
dlsym(_glfw.x11.xrender.handle, "XRenderQueryVersion");
|
||||
_glfw_dlsym(_glfw.x11.xrender.handle, "XRenderQueryVersion");
|
||||
_glfw.x11.xrender.FindVisualFormat = (PFN_XRenderFindVisualFormat)
|
||||
dlsym(_glfw.x11.xrender.handle, "XRenderFindVisualFormat");
|
||||
_glfw_dlsym(_glfw.x11.xrender.handle, "XRenderFindVisualFormat");
|
||||
|
||||
if (XRenderQueryExtension(_glfw.x11.display,
|
||||
&_glfw.x11.xrender.errorBase,
|
||||
@@ -1002,25 +1002,25 @@ void _glfwPlatformTerminate(void)
|
||||
|
||||
if (_glfw.x11.x11xcb.handle)
|
||||
{
|
||||
dlclose(_glfw.x11.x11xcb.handle);
|
||||
_glfw_dlclose(_glfw.x11.x11xcb.handle);
|
||||
_glfw.x11.x11xcb.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xcursor.handle)
|
||||
{
|
||||
dlclose(_glfw.x11.xcursor.handle);
|
||||
_glfw_dlclose(_glfw.x11.xcursor.handle);
|
||||
_glfw.x11.xcursor.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.randr.handle)
|
||||
{
|
||||
dlclose(_glfw.x11.randr.handle);
|
||||
_glfw_dlclose(_glfw.x11.randr.handle);
|
||||
_glfw.x11.randr.handle = NULL;
|
||||
}
|
||||
|
||||
if (_glfw.x11.xinerama.handle)
|
||||
{
|
||||
dlclose(_glfw.x11.xinerama.handle);
|
||||
_glfw_dlclose(_glfw.x11.xinerama.handle);
|
||||
_glfw.x11.xinerama.handle = NULL;
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
|
||||
2
glfw/x11_platform.h
vendored
2
glfw/x11_platform.h
vendored
@@ -425,7 +425,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);
|
||||
|
||||
95
glfw/x11_window.c
vendored
95
glfw/x11_window.c
vendored
@@ -212,6 +212,10 @@ static int translateState(int state)
|
||||
mods |= GLFW_MOD_ALT;
|
||||
if (state & Mod4Mask)
|
||||
mods |= GLFW_MOD_SUPER;
|
||||
if (state & LockMask)
|
||||
mods |= GLFW_MOD_CAPS_LOCK;
|
||||
if (state & Mod2Mask)
|
||||
mods |= GLFW_MOD_NUM_LOCK;
|
||||
|
||||
return mods;
|
||||
}
|
||||
@@ -703,49 +707,34 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
|
||||
updateNormalHints(window, wndconfig->width, wndconfig->height);
|
||||
|
||||
// Set ICCCM WM_CLASS property and window title
|
||||
// Set ICCCM WM_CLASS property
|
||||
{
|
||||
XClassHint* hint = XAllocClassHint();
|
||||
char *wm_cclass = NULL, *wm_cname = NULL;
|
||||
const char *real_title = wndconfig->title;
|
||||
|
||||
if (strlen(_glfw.hints.init.x11.className) &&
|
||||
strlen(_glfw.hints.init.x11.classClass))
|
||||
if (strlen(wndconfig->x11.instanceName) &&
|
||||
strlen(wndconfig->x11.className))
|
||||
{
|
||||
hint->res_name = (char*) _glfw.hints.init.x11.className;
|
||||
hint->res_class = (char*) _glfw.hints.init.x11.classClass;
|
||||
}
|
||||
else if (strlen(real_title))
|
||||
{
|
||||
if (*real_title == 1) {
|
||||
char *p = strchr(real_title, 30);
|
||||
if (p && p > real_title + 1) {
|
||||
wm_cname = calloc(p - real_title + 1, 1);
|
||||
if (wm_cname) memcpy(wm_cname, real_title + 1, p - real_title - 1);
|
||||
hint->res_name = wm_cname;
|
||||
char *q = strchr(p + 1, 30);
|
||||
if (q && q > p + 1) {
|
||||
wm_cclass = calloc(q - p + 1, 1);
|
||||
if (wm_cclass) memcpy(wm_cclass, p + 1, q - p - 1);
|
||||
hint->res_class = wm_cclass;
|
||||
real_title = q + 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hint->res_name = (char*) real_title;
|
||||
hint->res_class = (char*) real_title;
|
||||
}
|
||||
hint->res_name = (char*) wndconfig->x11.instanceName;
|
||||
hint->res_class = (char*) wndconfig->x11.className;
|
||||
}
|
||||
else
|
||||
{
|
||||
hint->res_name = (char*) "glfw-application";
|
||||
hint->res_class = (char*) "GLFW-Application";
|
||||
const char* resourceName = getenv("RESOURCE_NAME");
|
||||
if (resourceName && strlen(resourceName))
|
||||
hint->res_name = (char*) resourceName;
|
||||
else if (strlen(wndconfig->title))
|
||||
hint->res_name = (char*) wndconfig->title;
|
||||
else
|
||||
hint->res_name = (char*) "glfw-application";
|
||||
|
||||
if (strlen(wndconfig->title))
|
||||
hint->res_class = (char*) wndconfig->title;
|
||||
else
|
||||
hint->res_class = (char*) "GLFW-Application";
|
||||
}
|
||||
|
||||
XSetClassHint(_glfw.x11.display, window->x11.handle, hint);
|
||||
XFree(hint);
|
||||
free(wm_cclass); free(wm_cname);
|
||||
_glfwPlatformSetWindowTitle(window, real_title);
|
||||
}
|
||||
|
||||
// Announce support for Xdnd (drag and drop)
|
||||
@@ -756,6 +745,7 @@ static GLFWbool createNativeWindow(_GLFWwindow* window,
|
||||
PropModeReplace, (unsigned char*) &version, 1);
|
||||
}
|
||||
|
||||
_glfwPlatformSetWindowTitle(window, wndconfig->title);
|
||||
|
||||
if (_glfw.x11.im)
|
||||
{
|
||||
@@ -1066,7 +1056,7 @@ static const char* getSelectionString(Atom selection)
|
||||
if (targets[i] == XA_STRING)
|
||||
*selectionString = convertLatin1toUTF8(data);
|
||||
else
|
||||
*selectionString = strdup(data);
|
||||
*selectionString = _glfw_strdup(data);
|
||||
}
|
||||
|
||||
XFree(data);
|
||||
@@ -1086,10 +1076,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
|
||||
@@ -1107,7 +1095,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)
|
||||
{
|
||||
@@ -1123,7 +1111,6 @@ static GLFWbool acquireMonitor(_GLFWwindow* window)
|
||||
}
|
||||
|
||||
_glfwInputMonitorWindow(window->monitor, window);
|
||||
return status;
|
||||
}
|
||||
|
||||
// Remove the window and restore the original video mode
|
||||
@@ -1987,11 +1974,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);
|
||||
@@ -2468,6 +2451,28 @@ int _glfwPlatformWindowMaximized(_GLFWwindow* window)
|
||||
return maximized;
|
||||
}
|
||||
|
||||
int _glfwPlatformWindowHovered(_GLFWwindow* window)
|
||||
{
|
||||
Window w = _glfw.x11.root;
|
||||
while (w)
|
||||
{
|
||||
Window root;
|
||||
int rootX, rootY, childX, childY;
|
||||
unsigned int mask;
|
||||
|
||||
if (!XQueryPointer(_glfw.x11.display, w,
|
||||
&root, &w, &rootX, &rootY, &childX, &childY, &mask))
|
||||
{
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
if (w == window->x11.handle)
|
||||
return GLFW_TRUE;
|
||||
}
|
||||
|
||||
return GLFW_FALSE;
|
||||
}
|
||||
|
||||
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
|
||||
{
|
||||
if (!window->x11.transparent)
|
||||
@@ -2827,7 +2832,7 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
|
||||
void _glfwPlatformSetClipboardString(const char* string)
|
||||
{
|
||||
free(_glfw.x11.clipboardString);
|
||||
_glfw.x11.clipboardString = strdup(string);
|
||||
_glfw.x11.clipboardString = _glfw_strdup(string);
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.CLIPBOARD,
|
||||
@@ -3019,7 +3024,7 @@ GLFWAPI void glfwSetX11SelectionString(const char* string)
|
||||
_GLFW_REQUIRE_INIT();
|
||||
|
||||
free(_glfw.x11.primarySelectionString);
|
||||
_glfw.x11.primarySelectionString = strdup(string);
|
||||
_glfw.x11.primarySelectionString = _glfw_strdup(string);
|
||||
|
||||
XSetSelectionOwner(_glfw.x11.display,
|
||||
_glfw.x11.PRIMARY,
|
||||
|
||||
@@ -8,7 +8,7 @@ to render arbitrary pixel (raster) graphics to the screen of the terminal
|
||||
emulator. The major design goals are
|
||||
|
||||
* Should not require terminal emulators to understand image formats.
|
||||
* Should allow specifying graphics to be drawn at individual pixel positions.
|
||||
* Should allow specifying graphics to be drawn at individual pixel positions.
|
||||
* The graphics should integrate with the text, in particular it should be possible to draw graphics
|
||||
below as well as above the text, with alpha blending. The graphics should also scroll with the text, automatically.
|
||||
* Should use optimizations when the client is running on the same computer as the terminal emulator.
|
||||
@@ -21,23 +21,37 @@ To see a quick demo, inside a kitty terminal run:
|
||||
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
|
||||
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;
|
||||
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`
|
||||
|
||||
@@ -50,7 +64,7 @@ All graphics escape codes are of the form:
|
||||
```
|
||||
|
||||
This is a so-called _Application Programming Command (APC)_. Most terminal
|
||||
emulators ignore APC codes, making it safe to use.
|
||||
emulators ignore APC codes, making it safe to use.
|
||||
|
||||
The control data is a comma-separated list of `key=value` pairs. The payload
|
||||
is arbitrary binary data, base64-encoded to prevent interoperation problems
|
||||
@@ -124,12 +138,12 @@ The transmission medium is specified using the `t` key. The `t` key defaults to
|
||||
and can take the values:
|
||||
|
||||
|===
|
||||
| Value of `t` | Meaning
|
||||
| Value of `t` | Meaning
|
||||
|
||||
| d | Direct (the data is transmitted within the escape code itself)
|
||||
| f | A simple file
|
||||
| t | A temporary file, the terminal emulator will delete the file after reading the pixel data
|
||||
| s | A http://man7.org/linux/man-pages/man7/shm_overview.7.html[POSIX shared memory object]. The terminal emulator will delete it after reading the pixel data
|
||||
| s | A http://man7.org/linux/man-pages/man7/shm_overview.7.html[POSIX shared memory object]. The terminal emulator will delete it after reading the pixel data
|
||||
|===
|
||||
|
||||
==== Local client
|
||||
@@ -175,9 +189,9 @@ if the data is split into three chunks, the client would send the following
|
||||
sequence of escape codes to the terminal emulator:
|
||||
|
||||
```
|
||||
<ESC>_Gs=100,v=30,m=1;<encoded pixel data first chunk><ESC>\
|
||||
<ESC>_Gm=1;<encoded pixel data second chunk><ESC>\
|
||||
<ESC>_Gm=0;<encoded pixel data last chunk><ESC>\
|
||||
<ESC>_Gs=100,v=30,m=1;<encoded pixel data first chunk><ESC>\
|
||||
<ESC>_Gm=1;<encoded pixel data second chunk><ESC>\
|
||||
<ESC>_Gm=0;<encoded pixel data last chunk><ESC>\
|
||||
```
|
||||
|
||||
Note that only the first escape code needs to have the full set of control
|
||||
@@ -189,7 +203,7 @@ before sending any other graphics related escape codes.
|
||||
=== Detecting available transmission mediums
|
||||
|
||||
Since a client has no a-priori knowledge of whether it shares a filesystem/shared memory
|
||||
with the terminal emulator, it can send an id with the control data, using the `i` key
|
||||
with the terminal emulator, it can send an id with the control data, using the `i` key
|
||||
(which can be an arbitrary positive integer up to 4294967295, it must not be zero).
|
||||
If it does so, the terminal emulator will reply after trying to load the image, saying
|
||||
whether loading was successful or not. For example:
|
||||
@@ -207,7 +221,7 @@ to which the terminal emulator will reply (after trying to load the data):
|
||||
Here the `i` value will be the same as was sent by the client in the original
|
||||
request. The message data will be a ASCII encoded string containing only
|
||||
printable characters and spaces. The string will be `OK` if reading the pixel
|
||||
data succeeded or an error message.
|
||||
data succeeded or an error message.
|
||||
|
||||
Sometimes, using an id is not appropriate, for example, if you do not want to
|
||||
replace a previously sent image with the same id, or if you are sending a dummy
|
||||
@@ -245,7 +259,7 @@ be re-transmitted.
|
||||
=== Controlling displayed image layout
|
||||
|
||||
The image is rendered at the current cursor position, from the upper left corner of
|
||||
the current cell. You can also specify extra `X=3` and `Y=4` pixel offsets to display from
|
||||
the current cell. You can also specify extra `X=3` and `Y=4` pixel offsets to display from
|
||||
a different origin within the cell. Note that the offsets must be smaller that the size of the cell.
|
||||
|
||||
By default, the entire image will be displayed (images wider than the available
|
||||
@@ -255,7 +269,7 @@ the top-left corner, width and height of the source rectangle.
|
||||
|
||||
You can also ask the terminal emulator to display the image in a specified rectangle
|
||||
(num of columns / num of lines), using the control codes `c,r`. `c` is the number of columns
|
||||
and `r` the number of rows. The image will be scaled (enlarged/shrunk) as needed to fit
|
||||
and `r` the number of rows. The image will be scaled (enlarged/shrunk) as needed to fit
|
||||
the specified area. Note that if you specify a start cell offset via the `X,Y` keys, it is not
|
||||
added to the number of rows/columns.
|
||||
|
||||
@@ -269,7 +283,7 @@ allows rendering of text on top of images.
|
||||
|
||||
Images can be deleted by using the delete action `a=d`. If specified without any
|
||||
other keys, it will delete all images visible on screen. To delete specific images,
|
||||
use the `d` key as described in the table below. Note that each value of d has
|
||||
use the `d` key as described in the table below. Note that each value of d has
|
||||
both a lowercase and an uppercase variant. The lowercase variant only deletes the
|
||||
images without necessarily freeing up the stored image data, so that the images can be
|
||||
re-displayed without needing to resend the data. The uppercase variants will delete
|
||||
@@ -278,7 +292,7 @@ scrollback buffer. The values of the `x` and `y` keys are the same as cursor pos
|
||||
x=1, y=1 is the top left cell).
|
||||
|
||||
|===
|
||||
| Value of `d` | Meaning
|
||||
| Value of `d` | Meaning
|
||||
|
||||
| `a` or `A` | Delete all images visible on screen
|
||||
| `i` or `I` | Delete all images with the specified id, specified using the `i` key.
|
||||
@@ -313,7 +327,7 @@ delete older images to make space for the new one.
|
||||
== Control data reference
|
||||
|
||||
The table below shows all the control data keys as well as what values they can
|
||||
take, and the default value they take when missing. All integers are 32-bit.
|
||||
take, and the default value they take when missing. All integers are 32-bit.
|
||||
|
||||
[cols="^1,<3,^1,<6"]
|
||||
|===
|
||||
|
||||
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/tui/__init__.py
Normal file
0
kittens/tui/__init__.py
Normal file
43
kittens/tui/handler.py
Normal file
43
kittens/tui/handler.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/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):
|
||||
self.screen_size, self.quit_loop = screen_size, quit_loop
|
||||
self.wakeup = wakeup
|
||||
|
||||
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 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()
|
||||
374
kittens/tui/loop.py
Normal file
374
kittens/tui/loop.py
Normal file
@@ -0,0 +1,374 @@
|
||||
#!/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 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 init_state, reset_state, clear_screen
|
||||
|
||||
|
||||
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, screen_size, quit_loop, wakeup):
|
||||
Handler.initialize(self, screen_size, quit_loop, wakeup)
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
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)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
self.return_code = 1
|
||||
keep_going = False
|
||||
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:
|
||||
try:
|
||||
key.data(handler)
|
||||
except Exception:
|
||||
import traceback
|
||||
tb = traceback.format_exc()
|
||||
self.return_code = 1
|
||||
keep_going = False
|
||||
break
|
||||
if tb is not None:
|
||||
self._report_error_loop(tb, term_manager)
|
||||
|
||||
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)
|
||||
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)
|
||||
153
kittens/tui/operations.py
Normal file
153
kittens/tui/operations.py
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
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)
|
||||
0
kittens/unicode_input/__init__.py
Normal file
0
kittens/unicode_input/__init__.py
Normal file
477
kittens/unicode_input/main.py
Normal file
477
kittens/unicode_input/main.py
Normal file
@@ -0,0 +1,477 @@
|
||||
#!/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
|
||||
import sys
|
||||
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)
|
||||
|
||||
|
||||
@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]:
|
||||
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 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, *args):
|
||||
Handler.initialize(self, *args)
|
||||
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 run_loop(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:
|
||||
print('OK:', hex(ord(handler.current_char))[2:])
|
||||
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 loop.return_code
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
try:
|
||||
raise SystemExit(run_loop(args))
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input(_('Press Enter to quit.'))
|
||||
63052
kittens/unicode_input/names.h
generated
Normal file
63052
kittens/unicode_input/names.h
generated
Normal file
File diff suppressed because one or more lines are too long
81
kittens/unicode_input/unicode_names.c
Normal file
81
kittens/unicode_input/unicode_names.c
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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(idx_to_word));
|
||||
if (!ans) return NULL;
|
||||
for (size_t i = 0; i < arraysz(idx_to_word); i++) {
|
||||
PyObject *w = PyUnicode_FromString(idx_to_word[i]);
|
||||
if (w == NULL) { Py_DECREF(ans); return NULL; }
|
||||
PyTuple_SET_ITEM(ans, i, w);
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline PyObject*
|
||||
codepoints_for_word(const char *word, size_t len) {
|
||||
PyObject *ans = PyFrozenSet_New(NULL); if (ans == NULL) return NULL;
|
||||
const unsigned short *words = words_for_first_letter[(unsigned)*word];
|
||||
if (words == NULL) return ans;
|
||||
for (unsigned short i = 1; i <= words[0]; i++) {
|
||||
unsigned short word_idx = words[i];
|
||||
const char *w = idx_to_word[word_idx];
|
||||
if (strncmp(word, w, len) == 0 && strlen(w) == len) {
|
||||
const char_type* codepoints = codepoints_for_word_idx[word_idx];
|
||||
for (char_type i = 1; i <= codepoints[0]; 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; }
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
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[] = {
|
||||
METHODB(all_words, METH_NOARGS),
|
||||
{"codepoints_for_word", (PyCFunction)cfw, METH_VARARGS, ""},
|
||||
{"name_for_codepoint", (PyCFunction)nfc, METH_VARARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
0
kittens/url_hints/__init__.py
Normal file
0
kittens/url_hints/__init__.py
Normal file
247
kittens/url_hints/main.py
Normal file
247
kittens/url_hints/main.py
Normal file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import re
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
from functools import lru_cache, partial
|
||||
from gettext import gettext as _
|
||||
|
||||
from kitty.cli import parse_args
|
||||
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
|
||||
from kitty.utils import command_for_open
|
||||
|
||||
from ..tui.handler import Handler
|
||||
from ..tui.loop import Loop
|
||||
from ..tui.operations import (
|
||||
clear_screen, faint, set_cursor_visible, set_window_title, styled
|
||||
)
|
||||
|
||||
Mark = namedtuple('Mark', 'index start end text')
|
||||
URL_PREFIXES = 'http https file ftp'.split()
|
||||
HINT_ALPHABET = string.digits + string.ascii_lowercase
|
||||
|
||||
|
||||
@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 URLHints(Handler):
|
||||
|
||||
def __init__(self, lines, index_map):
|
||||
self.lines, self.index_map = tuple(lines), index_map
|
||||
self.current_input = ''
|
||||
self.current_text = None
|
||||
self.chosen = None
|
||||
|
||||
def init_terminal_state(self):
|
||||
self.write(set_cursor_visible(False))
|
||||
self.write(set_window_title(_('Choose URL')))
|
||||
|
||||
def initialize(self, *args):
|
||||
Handler.initialize(self, *args)
|
||||
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 = [
|
||||
t for idx, t 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]
|
||||
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, line):
|
||||
for m in pat.finditer(line):
|
||||
s, e = m.span()
|
||||
if e - s > 2:
|
||||
yield s, e
|
||||
|
||||
|
||||
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
|
||||
yield s, e
|
||||
|
||||
|
||||
def mark(finditer, line, index_map):
|
||||
marks = []
|
||||
for s, e in finditer(line):
|
||||
idx = len(index_map)
|
||||
text = line[s:e]
|
||||
marks.append(Mark(idx, s, e, text))
|
||||
index_map[idx] = text
|
||||
return line, marks
|
||||
|
||||
|
||||
def run_loop(args, lines, index_map):
|
||||
loop = Loop()
|
||||
handler = URLHints(lines, index_map)
|
||||
loop.loop(handler)
|
||||
if handler.chosen and loop.return_code == 0:
|
||||
cmd = command_for_open(args.program)
|
||||
ret = subprocess.Popen(cmd + [handler.chosen]).wait()
|
||||
if ret != 0:
|
||||
print('URL handler "{}" failed with return code: {}'.format(' '.join(cmd), ret), file=sys.stderr)
|
||||
input('Press Enter to quit')
|
||||
loop.return_code = ret
|
||||
raise SystemExit(loop.return_code)
|
||||
|
||||
|
||||
def run(args, source_file=None):
|
||||
if source_file is None:
|
||||
text = sys.stdin.buffer.read().decode('utf-8')
|
||||
sys.stdin = open('/dev/tty')
|
||||
else:
|
||||
with open(source_file, 'r') as f:
|
||||
text = f.read()
|
||||
if args.regex is None:
|
||||
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))
|
||||
else:
|
||||
finditer = partial(regex_finditer, re.compile(args.regex))
|
||||
lines = []
|
||||
index_map = {}
|
||||
for line in text.splitlines():
|
||||
marked = mark(finditer, line, index_map)
|
||||
lines.append(marked)
|
||||
if not index_map:
|
||||
input(_('No URLs found, press Enter to abort.'))
|
||||
return
|
||||
|
||||
try:
|
||||
run_loop(args, lines, index_map)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
input(_('Press Enter to quit'))
|
||||
|
||||
|
||||
OPTIONS = partial('''\
|
||||
--program
|
||||
default=default
|
||||
What program to use to open matched URLs. Defaults
|
||||
to the default URL open program for the operating system.
|
||||
|
||||
|
||||
--regex
|
||||
Instead of searching for URLs search for the specified regular
|
||||
expression instead.
|
||||
|
||||
|
||||
--url-prefixes
|
||||
default={0}
|
||||
Comma separeted list of recognized URL prefixes. Defaults to:
|
||||
{0}
|
||||
'''.format, ','.join(sorted(URL_PREFIXES)))
|
||||
|
||||
|
||||
def main(args=sys.argv):
|
||||
msg = 'Highlight URLs inside the specified text'
|
||||
try:
|
||||
args, items = parse_args(args[1:], OPTIONS, '[path to file or omit to use stdin]', msg, 'url_hints')
|
||||
except SystemExit as e:
|
||||
print(e.args[0], file=sys.stderr)
|
||||
input('Press enter to quit...')
|
||||
return 1
|
||||
run(args, (items or [None])[0])
|
||||
1
kittens/url_hints/url_regex.py
Normal file
1
kittens/url_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
|
||||
@@ -5,10 +5,10 @@
|
||||
#define bottom -1.0f
|
||||
|
||||
const vec2 pos_map[] = vec2[4](
|
||||
vec2(right, top),
|
||||
vec2(right, bottom),
|
||||
vec2(left, bottom),
|
||||
vec2(left, top)
|
||||
vec2(right, top),
|
||||
vec2(right, bottom),
|
||||
vec2(left, bottom),
|
||||
vec2(left, top)
|
||||
);
|
||||
|
||||
out vec2 texcoord;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#version GLSL_VERSION
|
||||
uniform uvec2 viewport;
|
||||
uniform vec3 default_bg;
|
||||
uniform vec3 active_border_color;
|
||||
uniform vec3 inactive_border_color;
|
||||
in uvec4 rect; // left, top, right, bottom
|
||||
in uint rect_color;
|
||||
out vec3 color;
|
||||
@@ -9,12 +12,13 @@ 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),
|
||||
uvec2(RIGHT, BOTTOM),
|
||||
uvec2(LEFT, BOTTOM),
|
||||
uvec2(LEFT, TOP)
|
||||
uvec2(RIGHT, TOP),
|
||||
uvec2(RIGHT, BOTTOM),
|
||||
uvec2(LEFT, BOTTOM),
|
||||
uvec2(LEFT, TOP)
|
||||
);
|
||||
|
||||
vec2 to_opengl(uint x, uint y) {
|
||||
@@ -25,11 +29,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;
|
||||
}
|
||||
|
||||
@@ -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 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
|
||||
)
|
||||
|
||||
350
kitty/boss.py
350
kitty/boss.py
@@ -2,26 +2,34 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import atexit
|
||||
import re
|
||||
import socket
|
||||
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 set_boss, wakeup
|
||||
from .config import (
|
||||
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
|
||||
)
|
||||
from .constants import appname, 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, show_window,
|
||||
toggle_fullscreen, viewport_for_window
|
||||
layout_sprite_map, mark_os_window_for_close, set_clipboard_string,
|
||||
set_dpi_from_os_window, show_window, toggle_fullscreen,
|
||||
viewport_for_window
|
||||
)
|
||||
from .fonts.render import prerender, resize_fonts, set_font_family
|
||||
from .keys import get_shortcut
|
||||
from .remote_control import handle_cmd
|
||||
from .session import create_session
|
||||
from .tabs import SpecialWindow, TabManager
|
||||
from .tabs import SpecialWindow, SpecialWindowInstance, TabManager
|
||||
from .utils import (
|
||||
encode_wm_class, end_startup_notification, get_primary_selection,
|
||||
init_startup_notification, open_url, safe_print, set_primary_selection,
|
||||
single_instance
|
||||
end_startup_notification, get_primary_selection, init_startup_notification,
|
||||
log_error, open_url, parse_address_spec, remove_socket_file, safe_print,
|
||||
set_primary_selection, single_instance
|
||||
)
|
||||
|
||||
|
||||
@@ -30,6 +38,15 @@ def initialize_renderer():
|
||||
prerender()
|
||||
|
||||
|
||||
def listen_on(spec):
|
||||
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):
|
||||
@@ -59,17 +76,21 @@ 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.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
|
||||
@@ -79,46 +100,147 @@ class Boss:
|
||||
startup_session = create_session(opts, args)
|
||||
self.add_os_window(startup_session, os_window_id=os_window_id)
|
||||
|
||||
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, visible=True):
|
||||
def add_os_window(self, startup_session, os_window_id=None, wclass=None, wname=None, size=None, startup_id=None):
|
||||
dpi_changed = False
|
||||
if os_window_id is None:
|
||||
w, h = initial_window_size(self.opts) if size is None else size
|
||||
os_window_id = create_os_window(w, h, encode_wm_class(wname or self.args.name, wclass or self.args.cls), visible)
|
||||
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:
|
||||
ctx = init_startup_notification(os_window_id, startup_id)
|
||||
dpi_changed = show_window(os_window_id)
|
||||
if startup_id:
|
||||
end_startup_notification(ctx)
|
||||
tm = TabManager(os_window_id, self.opts, self.args, startup_session)
|
||||
self.os_window_map[os_window_id] = tm
|
||||
return os_window_id
|
||||
if dpi_changed:
|
||||
self.on_dpi_change(os_window_id)
|
||||
|
||||
def list_os_windows(self):
|
||||
for os_window_id, tm in self.os_window_map.items():
|
||||
yield {
|
||||
'id': os_window_id,
|
||||
'tabs': list(tm.list_tabs()),
|
||||
}
|
||||
|
||||
def match_windows(self, match):
|
||||
try:
|
||||
field, exp = match.split(':', 1)
|
||||
except ValueError:
|
||||
return
|
||||
pat = re.compile(exp)
|
||||
for tm in self.os_window_map.values():
|
||||
for tab in tm:
|
||||
for window in tab:
|
||||
if window.matches(field, pat):
|
||||
yield window
|
||||
|
||||
def tab_for_window(self, window):
|
||||
for tm in self.os_window_map.values():
|
||||
for tab in tm:
|
||||
for w in tab:
|
||||
if w.id == window.id:
|
||||
return tab
|
||||
|
||||
def match_tabs(self, match):
|
||||
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
|
||||
if not found:
|
||||
tabs = {self.tab_for_window(w) for w in self.match_windows(match)}
|
||||
for tab in tabs:
|
||||
if tab:
|
||||
yield tab
|
||||
|
||||
def set_active_window(self, window):
|
||||
for tm in self.os_window_map.values():
|
||||
for tab in tm:
|
||||
for w in tab:
|
||||
if w.id == window.id:
|
||||
if tab is not self.active_tab:
|
||||
tm.set_active_tab(tab)
|
||||
tab.set_active_window(w)
|
||||
return
|
||||
|
||||
def _new_os_window(self, args, cwd_from=None):
|
||||
sw = self.args_to_special_window(args, cwd_from) if args else None
|
||||
startup_session = create_session(self.opts, special_window=sw, cwd_from=cwd_from)
|
||||
self.add_os_window(startup_session)
|
||||
|
||||
def new_os_window(self, *args):
|
||||
sw = self.args_to_special_window(args) if args else None
|
||||
startup_session = create_session(self.opts, special_window=sw)
|
||||
return self.add_os_window(startup_session)
|
||||
self._new_os_window(args)
|
||||
|
||||
def new_os_window_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
self._new_os_window(args, cwd_from)
|
||||
|
||||
def add_child(self, window):
|
||||
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):
|
||||
def _handle_remote_command(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)}
|
||||
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):
|
||||
import json
|
||||
for msg in messages:
|
||||
msg = json.loads(msg.decode('utf-8'))
|
||||
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)
|
||||
os_window_id = self.add_os_window(session, wclass=args.cls, wname=args.name, size=initial_window_size(opts), visible=False)
|
||||
if startup_id:
|
||||
ctx = init_startup_notification(os_window_id, startup_id)
|
||||
show_window(os_window_id)
|
||||
if startup_id:
|
||||
end_startup_notification(ctx)
|
||||
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 = self._handle_remote_command(cmd, window)
|
||||
if response is not None:
|
||||
if window is not None:
|
||||
window.send_cmd_response(response)
|
||||
|
||||
def on_child_death(self, window_id):
|
||||
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)
|
||||
@@ -162,33 +284,35 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.activate_tab_at(x)
|
||||
|
||||
def on_window_resize(self, os_window_id, w, h):
|
||||
def on_window_resize(self, os_window_id, w, h, dpi_changed):
|
||||
tm = self.os_window_map.get(os_window_id)
|
||||
if tm is not None:
|
||||
tm.resize()
|
||||
if dpi_changed:
|
||||
if set_dpi_from_os_window(os_window_id):
|
||||
self.on_dpi_change(os_window_id)
|
||||
else:
|
||||
tm.resize()
|
||||
else:
|
||||
tm.resize()
|
||||
|
||||
def increase_font_size(self):
|
||||
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):
|
||||
if new_size == self.current_font_size:
|
||||
return
|
||||
self.current_font_size = new_size
|
||||
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:
|
||||
@@ -199,6 +323,15 @@ class Boss:
|
||||
tm.refresh_sprite_positions()
|
||||
glfw_post_empty_event()
|
||||
|
||||
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)
|
||||
|
||||
def on_dpi_change(self, os_window_id):
|
||||
self._change_font_size()
|
||||
|
||||
@property
|
||||
def active_tab_manager(self):
|
||||
os_window_id = current_os_window()
|
||||
@@ -222,6 +355,15 @@ class Boss:
|
||||
self.current_key_press_info = key, scancode, action, mods
|
||||
return self.dispatch_action(key_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:
|
||||
f = getattr(self, key_action.func, None)
|
||||
@@ -262,22 +404,70 @@ 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:
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
tab.new_special_window(
|
||||
SpecialWindow(
|
||||
self.opts.scrollback_pager, data, _('History')))
|
||||
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 input_unicode_character(self):
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
overlay_window = tab.new_special_window(
|
||||
SpecialWindow(
|
||||
['kitty', '+runpy', 'from kittens.unicode_input.main import main; main()'],
|
||||
overlay_for=w.id))
|
||||
overlay_window.action_on_close = partial(self.send_unicode_character, w.id)
|
||||
|
||||
def send_unicode_character(self, target_window_id, source_window):
|
||||
w = self.window_id_map.get(target_window_id)
|
||||
if w is not None:
|
||||
output = str(source_window.screen.linebuf.line(0))
|
||||
if output.startswith('OK: '):
|
||||
try:
|
||||
text = chr(int(output.partition(' ')[2], 16))
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
w.paste(text)
|
||||
|
||||
def run_simple_kitten(self, type_of_input, kitten, *args):
|
||||
import shlex
|
||||
w = self.active_window
|
||||
tab = self.active_tab
|
||||
if w is not None and tab is not None and w.overlay_for is None:
|
||||
cmdline = args[0] if args else ''
|
||||
args = shlex.split(cmdline) if cmdline else []
|
||||
if '--program' not in cmdline:
|
||||
args.extend(('--program', self.opts.open_url_with))
|
||||
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))
|
||||
tab.new_special_window(
|
||||
SpecialWindow(
|
||||
['kitty', '+runpy', 'from kittens.{}.main import main; main()'.format(kitten)] + args,
|
||||
stdin=data,
|
||||
overlay_for=w.id))
|
||||
|
||||
def switch_focus_to(self, window_idx):
|
||||
tab = self.active_tab
|
||||
@@ -297,8 +487,6 @@ class Boss:
|
||||
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()
|
||||
@@ -326,12 +514,19 @@ class Boss:
|
||||
text = w.text_for_selection()
|
||||
if text:
|
||||
set_primary_selection(text)
|
||||
if self.opts.copy_on_select:
|
||||
set_clipboard_string(text)
|
||||
|
||||
def goto_tab(self, tab_num):
|
||||
tm = self.active_tab_manager
|
||||
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:
|
||||
@@ -342,7 +537,7 @@ class Boss:
|
||||
if tm is not None:
|
||||
tm.next_tab(-1)
|
||||
|
||||
def args_to_special_window(self, args):
|
||||
def args_to_special_window(self, args, cwd_from=None):
|
||||
args = list(args)
|
||||
stdin = None
|
||||
w = self.active_window
|
||||
@@ -351,9 +546,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
|
||||
@@ -368,23 +567,44 @@ class Boss:
|
||||
if not arg:
|
||||
continue
|
||||
cmd.append(arg)
|
||||
return SpecialWindow(cmd, stdin)
|
||||
return SpecialWindow(cmd, stdin, cwd_from=cwd_from)
|
||||
|
||||
def new_tab(self, *args):
|
||||
def _new_tab(self, args, cwd_from=None):
|
||||
special_window = None
|
||||
if args:
|
||||
special_window = self.args_to_special_window(args)
|
||||
if isinstance(args, SpecialWindowInstance):
|
||||
special_window = args
|
||||
else:
|
||||
special_window = self.args_to_special_window(args, cwd_from=cwd_from)
|
||||
tm = self.active_tab_manager
|
||||
if tm is not None:
|
||||
tm.new_tab(special_window=special_window)
|
||||
tm.new_tab(special_window=special_window, cwd_from=cwd_from)
|
||||
|
||||
def new_window(self, *args):
|
||||
def new_tab(self, *args):
|
||||
self._new_tab(args)
|
||||
|
||||
def new_tab_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
cwd_from = w.child.pid if w is not None else None
|
||||
self._new_tab(args, cwd_from=cwd_from)
|
||||
|
||||
def _new_window(self, args, cwd_from=None):
|
||||
tab = self.active_tab
|
||||
if tab is not None:
|
||||
if args:
|
||||
tab.new_special_window(self.args_to_special_window(args))
|
||||
tab.new_special_window(self.args_to_special_window(args, cwd_from=cwd_from))
|
||||
else:
|
||||
tab.new_window()
|
||||
tab.new_window(cwd_from=cwd_from)
|
||||
|
||||
def new_window(self, *args):
|
||||
self._new_window(args)
|
||||
|
||||
def new_window_with_cwd(self, *args):
|
||||
w = self.active_window
|
||||
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)
|
||||
|
||||
def move_tab_forward(self):
|
||||
tm = self.active_tab_manager
|
||||
@@ -395,9 +615,3 @@ class Boss:
|
||||
tm = self.active_tab_manager
|
||||
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')))
|
||||
|
||||
@@ -19,6 +19,7 @@ in float bg_alpha;
|
||||
|
||||
#ifdef NEEDS_FOREGROUND
|
||||
uniform sampler2DArray sprites;
|
||||
uniform float inactive_text_alpha;
|
||||
in vec3 sprite_pos;
|
||||
in vec3 underline_pos;
|
||||
in vec3 strike_pos;
|
||||
@@ -34,7 +35,7 @@ vec4 alpha_blend(vec3 over, float over_alpha, vec3 under, float under_alpha) {
|
||||
// Alpha blend two colors returning the resulting color pre-multiplied by its alpha
|
||||
// and its alpha.
|
||||
// See https://en.wikipedia.org/wiki/Alpha_compositing
|
||||
float alpha = mix(under_alpha, 1.0f, over_alpha);
|
||||
float alpha = mix(under_alpha, 1.0f, over_alpha);
|
||||
vec3 combined_color = mix(under * under_alpha, over, over_alpha);
|
||||
return vec4(combined_color, alpha);
|
||||
}
|
||||
@@ -45,7 +46,7 @@ vec3 premul_blend(vec3 over, float over_alpha, vec3 under) {
|
||||
|
||||
vec4 alpha_blend_premul(vec3 over, float over_alpha, vec3 under, float under_alpha) {
|
||||
// Same as alpha_blend() except that it assumes over and under are both premultiplied.
|
||||
float alpha = mix(under_alpha, 1.0f, over_alpha);
|
||||
float alpha = mix(under_alpha, 1.0f, over_alpha);
|
||||
return vec4(premul_blend(over, over_alpha, under), alpha);
|
||||
}
|
||||
// }}}
|
||||
@@ -60,7 +61,7 @@ vec4 calculate_foreground() {
|
||||
// Since strike and text are the same color, we simply add the alpha values
|
||||
float combined_alpha = min(text_alpha + strike_alpha, 1.0f);
|
||||
// Underline color might be different, so alpha blend
|
||||
return alpha_blend(decoration_fg, underline_alpha, fg, combined_alpha);
|
||||
return alpha_blend(decoration_fg, underline_alpha * inactive_text_alpha, fg, combined_alpha * inactive_text_alpha);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -70,7 +71,7 @@ void main() {
|
||||
final_color = vec4(background.rgb * bg_alpha, bg_alpha);
|
||||
#else
|
||||
final_color = vec4(background.rgb, 1.0f);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SPECIAL
|
||||
@@ -78,10 +79,10 @@ void main() {
|
||||
final_color = vec4(background.rgb * bg_alpha, bg_alpha);
|
||||
#else
|
||||
final_color = vec4(background.rgb, bg_alpha);
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(FOREGROUND) || defined(SIMPLE)
|
||||
#if defined(FOREGROUND) || defined(SIMPLE)
|
||||
// FOREGROUND or SIMPLE
|
||||
vec4 fg = calculate_foreground(); // pre-multiplied foreground
|
||||
|
||||
@@ -99,7 +100,7 @@ void main() {
|
||||
final_color = alpha_blend_premul(fg.rgb, fg.a, background * bg_alpha, bg_alpha);
|
||||
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
|
||||
#else
|
||||
// since background alpha is 1.0, it is effectively premultipled
|
||||
// since background alpha is 1.0, it is effectively pre-multiplied
|
||||
final_color = vec4(premul_blend(fg.rgb, fg.a, background), 1.0f);
|
||||
final_color = vec4(final_color.rgb / final_color.a, final_color.a);
|
||||
#endif
|
||||
|
||||
@@ -12,7 +12,7 @@ layout(std140) uniform CellRenderData {
|
||||
|
||||
uint xnum, ynum, cursor_x, cursor_y, cursor_w;
|
||||
|
||||
uint color_table[256];
|
||||
uint color_table[256];
|
||||
};
|
||||
|
||||
// Have to use fixed locations here as all variants of the cell program share the same VAO
|
||||
@@ -21,7 +21,7 @@ layout(location=1) in uvec4 sprite_coords;
|
||||
layout(location=2) in uint is_selected;
|
||||
|
||||
|
||||
|
||||
|
||||
const uvec2 cell_pos_map[] = uvec2[4](
|
||||
uvec2(1, 0), // right, top
|
||||
uvec2(1, 1), // right, bottom
|
||||
@@ -118,27 +118,27 @@ float is_cursor(uint x, uint y) {
|
||||
void main() {
|
||||
|
||||
// set cell vertex position {{{
|
||||
uint instance_id = uint(gl_InstanceID);
|
||||
uint instance_id = uint(gl_InstanceID);
|
||||
/* The current cell being rendered */
|
||||
uint r = instance_id / xnum;
|
||||
uint c = instance_id - r * xnum;
|
||||
uint r = instance_id / xnum;
|
||||
uint c = instance_id - r * xnum;
|
||||
|
||||
/* The position of this vertex, at a corner of the cell */
|
||||
float left = xstart + c * dx;
|
||||
float top = ystart - r * dy;
|
||||
vec2 xpos = vec2(left, left + dx);
|
||||
vec2 ypos = vec2(top, top - dy);
|
||||
uvec2 pos = cell_pos_map[gl_VertexID];
|
||||
/* The position of this vertex, at a corner of the cell */
|
||||
float left = xstart + c * dx;
|
||||
float top = ystart - r * dy;
|
||||
vec2 xpos = vec2(left, left + dx);
|
||||
vec2 ypos = vec2(top, top - dy);
|
||||
uvec2 pos = cell_pos_map[gl_VertexID];
|
||||
gl_Position = vec4(xpos[pos.x], ypos[pos.y], 0, 1);
|
||||
|
||||
// }}}
|
||||
|
||||
|
||||
// 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];
|
||||
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];
|
||||
float cursor = is_cursor(c, r);
|
||||
vec3 bg = to_color(colors[bg_index], default_colors[bg_index]);
|
||||
// }}}
|
||||
@@ -150,7 +150,7 @@ void main() {
|
||||
sprite_pos = to_sprite_pos(pos, sprite_coords.x, sprite_coords.y, sprite_coords.z & Z_MASK);
|
||||
colored_sprite = float((sprite_coords.z & COLOR_MASK) >> 14);
|
||||
|
||||
// Foreground
|
||||
// Foreground
|
||||
uint resolved_fg = resolve_color(colors[fg_index], default_colors[fg_index]);
|
||||
foreground = color_to_vec(resolved_fg);
|
||||
// Selection
|
||||
@@ -165,7 +165,7 @@ void main() {
|
||||
foreground = choose_color(cursor, bg, foreground);
|
||||
decoration_fg = choose_color(cursor, bg, decoration_fg);
|
||||
#endif
|
||||
// }}}
|
||||
// }}}
|
||||
|
||||
// Background {{{
|
||||
#ifdef NEEDS_BACKROUND
|
||||
|
||||
18
kitty/charsets.c
generated
18
kitty/charsets.c
generated
@@ -43,7 +43,7 @@ static uint32_t charset_translations[5][256] = {
|
||||
0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
|
||||
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
|
||||
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
|
||||
},
|
||||
},
|
||||
/* VT100 graphics mapped to Unicode */
|
||||
{
|
||||
0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
|
||||
@@ -81,7 +81,7 @@ static uint32_t charset_translations[5][256] = {
|
||||
},
|
||||
/* IBM Codepage 437 mapped to Unicode */
|
||||
{
|
||||
0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
|
||||
0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
|
||||
0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
|
||||
0x25b6, 0x25c0, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
|
||||
0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
|
||||
@@ -113,7 +113,7 @@ static uint32_t charset_translations[5][256] = {
|
||||
0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
|
||||
0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
|
||||
0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
|
||||
},
|
||||
},
|
||||
// VAX 42 map
|
||||
{
|
||||
0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
|
||||
@@ -183,13 +183,13 @@ static uint32_t charset_translations[5][256] = {
|
||||
0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
|
||||
0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
|
||||
0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
uint32_t*
|
||||
uint32_t*
|
||||
translation_table(uint32_t which) {
|
||||
switch(which){
|
||||
switch(which){
|
||||
case 'B':
|
||||
return charset_translations[0];
|
||||
case '0':
|
||||
@@ -208,7 +208,7 @@ translation_table(uint32_t which) {
|
||||
uint32_t *latin1_charset = charset_translations[0];
|
||||
|
||||
// UTF-8 decode taken from: http://bjoern.hoehrmann.de/utf-8/decoder/dfa/
|
||||
|
||||
|
||||
static const uint8_t utf8_data[] = {
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 00..1f
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 20..3f
|
||||
@@ -226,7 +226,7 @@ static const uint8_t utf8_data[] = {
|
||||
1,3,1,1,1,1,1,3,1,3,1,1,1,1,1,1,1,3,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // s7..s8
|
||||
};
|
||||
|
||||
uint32_t
|
||||
uint32_t
|
||||
decode_utf8(uint32_t* state, uint32_t* codep, uint8_t byte) {
|
||||
uint32_t type = utf8_data[byte];
|
||||
|
||||
@@ -278,7 +278,7 @@ base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, size_t dest_cap
|
||||
if (!src_sz) { *dest_sz = 0; return NULL; }
|
||||
if (src_sz % 4 != 0) return "base64 encoded data must have a length that is a multiple of four";
|
||||
*dest_sz = (src_sz / 4) * 3;
|
||||
if (src[src_sz - 1] == '=') (*dest_sz)--;
|
||||
if (src[src_sz - 1] == '=') (*dest_sz)--;
|
||||
if (src[src_sz - 2] == '=') (*dest_sz)--;
|
||||
if (*dest_sz > dest_capacity) return "output buffer too small";
|
||||
for (size_t i = 0, j = 0; i < src_sz;) {
|
||||
|
||||
@@ -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,30 +71,19 @@ 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 {{{
|
||||
|
||||
#define FREE_CHILD(x) \
|
||||
Py_CLEAR((x).screen); x = EMPTY_CHILD;
|
||||
|
||||
#define XREF_CHILD(x, OP) OP(x.screen);
|
||||
#define XREF_CHILD(x, OP) OP(x.screen);
|
||||
#define INCREF_CHILD(x) XREF_CHILD(x, Py_INCREF)
|
||||
#define DECREF_CHILD(x) XREF_CHILD(x, Py_DECREF)
|
||||
|
||||
@@ -138,12 +126,12 @@ self_pipe(int fds[2]) {
|
||||
static PyObject *
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
ChildMonitor *self;
|
||||
PyObject *dump_callback, *death_notify;
|
||||
int talk_fd = -1;
|
||||
PyObject *dump_callback, *death_notify;
|
||||
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,19 +139,19 @@ 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) {
|
||||
self->dump_callback = dump_callback; Py_INCREF(dump_callback);
|
||||
parse_func = parse_worker_dump;
|
||||
} else parse_func = parse_worker;
|
||||
self->count = 0;
|
||||
self->count = 0;
|
||||
fds[0].fd = wakeup_fds[0]; fds[1].fd = signal_fds[0];
|
||||
fds[0].events = POLLIN; fds[1].events = POLLIN;
|
||||
the_monitor = self;
|
||||
@@ -187,7 +175,7 @@ dealloc(ChildMonitor* self) {
|
||||
}
|
||||
close(wakeup_fds[0]);
|
||||
close(wakeup_fds[1]);
|
||||
close(signal_fds[0]);
|
||||
close(signal_fds[0]);
|
||||
close(signal_fds[1]);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -243,7 +228,7 @@ add_child(ChildMonitor *self, PyObject *args) {
|
||||
#define A(attr) &add_queue[add_queue_count].attr
|
||||
if (!PyArg_ParseTuple(args, "kiiO", A(id), A(pid), A(fd), A(screen))) {
|
||||
children_mutex(unlock);
|
||||
return NULL;
|
||||
return NULL;
|
||||
}
|
||||
#undef A
|
||||
INCREF_CHILD(add_queue[add_queue_count]);
|
||||
@@ -259,14 +244,14 @@ schedule_write_to_child(unsigned long id, const char *data, size_t sz) {
|
||||
bool found = false;
|
||||
children_mutex(lock);
|
||||
for (size_t i = 0; i < self->count; i++) {
|
||||
if (children[i].id == id) {
|
||||
if (children[i].id == id) {
|
||||
found = true;
|
||||
Screen *screen = children[i].screen;
|
||||
screen_mutex(lock, write);
|
||||
size_t space_left = screen->write_buf_sz - screen->write_buf_used;
|
||||
if (space_left < sz) {
|
||||
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;
|
||||
}
|
||||
@@ -295,17 +280,27 @@ needs_write(ChildMonitor UNUSED *self, PyObject *args) {
|
||||
#define needs_write_doc "needs_write(id, data) -> Queue data to be written to child."
|
||||
unsigned long id, sz;
|
||||
const char *data;
|
||||
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "ks#", &id, &data, &sz)) return NULL;
|
||||
if (schedule_write_to_child(id, data, sz)) { Py_RETURN_TRUE; }
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -332,7 +327,7 @@ parse_input(ChildMonitor *self) {
|
||||
PyObject *msg = NULL;
|
||||
children_mutex(lock);
|
||||
while (remove_queue_count) {
|
||||
remove_queue_count--;
|
||||
remove_queue_count--;
|
||||
remove_notify[remove_count] = remove_queue[remove_queue_count].id;
|
||||
remove_count++;
|
||||
FREE_CHILD(remove_queue[remove_queue_count]);
|
||||
@@ -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;
|
||||
@@ -517,13 +518,23 @@ collect_cursor_info(CursorRenderInfo *ans, Window *w, double now, OSWindow *os_w
|
||||
ColorProfile *cp = rd->screen->color_profile;
|
||||
ans->shape = cursor->shape ? cursor->shape : OPT(cursor_shape);
|
||||
ans->color = colorprofile_to_color(cp, cp->overridden.cursor_color, cp->configured.cursor_color);
|
||||
if (ans->shape == CURSOR_BLOCK) return;
|
||||
ans->is_focused = os_window->is_focused;
|
||||
if (ans->shape == CURSOR_BLOCK && ans->is_focused) return;
|
||||
double left = rd->xstart + cursor->x * rd->dx;
|
||||
double top = rd->ystart - cursor->y * rd->dy;
|
||||
unsigned long mult = MAX(1, screen_current_char_width(rd->screen));
|
||||
double right = left + (ans->shape == CURSOR_BEAM ? cursor_width(1.5, true, os_window) : rd->dx * mult);
|
||||
double bottom = top - rd->dy;
|
||||
if (ans->shape == CURSOR_UNDERLINE) top = bottom + cursor_width(2.0, false, os_window);
|
||||
switch (ans->shape) {
|
||||
case CURSOR_UNDERLINE:
|
||||
top = bottom + cursor_width(2.0, false, os_window);
|
||||
break;
|
||||
case CURSOR_BLOCK:
|
||||
top -= 2.0 / os_window->viewport_height; // 1px adjustment for width of line
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ans->left = left; ans->right = right; ans->top = top; ans->bottom = bottom;
|
||||
}
|
||||
|
||||
@@ -544,29 +555,39 @@ update_window_title(Window *w, OSWindow *os_window) {
|
||||
static PyObject*
|
||||
simple_render_screen(PyObject UNUSED *self, PyObject *args) {
|
||||
#define simple_render_screen_doc "Render a Screen object, with no cursor"
|
||||
Screen *screen;
|
||||
Screen *screen;
|
||||
float xstart, ystart, dx, dy;
|
||||
static ssize_t vao_idx = -1, gvao_idx = -1;
|
||||
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;
|
||||
draw_cells(vao_idx, gvao_idx, xstart, ystart, dx, dy, screen, current_os_window());
|
||||
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 (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 (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);
|
||||
}
|
||||
@@ -574,48 +595,71 @@ 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;
|
||||
draw_cells(WD.vao_idx, WD.gvao_idx, WD.xstart, WD.ystart, WD.dx, WD.dy, WD.screen, os_window);
|
||||
if (is_active_window && WD.screen->cursor_render_info.is_visible && WD.screen->cursor_render_info.shape != CURSOR_BLOCK) {
|
||||
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);
|
||||
}
|
||||
if (WD.screen->start_visual_bell_at != 0) {
|
||||
double bell_left = global_state.opts.visual_bell_duration - (now - WD.screen->start_visual_bell_at);
|
||||
set_maximum_wait(bell_left);
|
||||
}
|
||||
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
|
||||
render(double now) {
|
||||
double time_since_last_render = now - last_render_at;
|
||||
if (time_since_last_render < OPT(repaint_delay)) {
|
||||
if (time_since_last_render < OPT(repaint_delay)) {
|
||||
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;
|
||||
render_os_window(w, now, &active_window_id);
|
||||
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);
|
||||
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
|
||||
@@ -647,7 +691,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);
|
||||
@@ -663,7 +707,7 @@ cm_thread_write(PyObject UNUSED *self, PyObject *args) {
|
||||
if (!PyArg_ParseTuple(args, "is#", &fd, &buf, &sz)) return NULL;
|
||||
ThreadWriteData *data = alloc_twd(sz);
|
||||
if (data == NULL) return PyErr_NoMemory();
|
||||
data->fd = fd;
|
||||
data->fd = fd;
|
||||
memcpy(data->buf, buf, data->sz);
|
||||
int ret = pthread_create(&thread, NULL, thread_write, data);
|
||||
if (ret != 0) { free_twd(data); return PyErr_SetFromErrno(PyExc_OSError); }
|
||||
@@ -682,17 +726,17 @@ 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 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;
|
||||
|
||||
@@ -711,12 +755,12 @@ main_loop(ChildMonitor *self) {
|
||||
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);
|
||||
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);
|
||||
remove_os_window(os_window->id);
|
||||
} else has_open_windows = true;
|
||||
}
|
||||
}
|
||||
@@ -748,7 +792,7 @@ hangup(pid_t pid) {
|
||||
if (errno == ESRCH) return;
|
||||
if (errno != 0) { perror("Failed to get process group id for child"); return; }
|
||||
if (killpg(pgid, SIGHUP) != 0) {
|
||||
if (errno != ESRCH) perror("Failed to kill child");
|
||||
if (errno != ESRCH) perror("Failed to kill child");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,7 +807,7 @@ cleanup_child(ssize_t i) {
|
||||
static inline void
|
||||
remove_children(ChildMonitor *self) {
|
||||
if (self->count > 0) {
|
||||
size_t count = 0;
|
||||
size_t count = 0;
|
||||
for (ssize_t i = self->count - 1; i >= 0; i--) {
|
||||
if (children[i].needs_removal) {
|
||||
count++;
|
||||
@@ -831,6 +875,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;
|
||||
@@ -839,7 +935,7 @@ write_to_child(int fd, Screen *screen) {
|
||||
while (written < screen->write_buf_used) {
|
||||
ret = write(fd, screen->write_buf + written, screen->write_buf_used - written);
|
||||
if (ret > 0) { written += ret; }
|
||||
else if (ret == 0) {
|
||||
else if (ret == 0) {
|
||||
// could mean anything, ignore
|
||||
break;
|
||||
} else {
|
||||
@@ -858,7 +954,7 @@ io_loop(void *data) {
|
||||
// The I/O thread loop
|
||||
size_t i;
|
||||
int ret;
|
||||
bool has_more, data_received;
|
||||
bool has_more, data_received;
|
||||
Screen *screen;
|
||||
ChildMonitor *self = (ChildMonitor*)data;
|
||||
set_thread_name("KittyChildMon");
|
||||
@@ -880,18 +976,18 @@ io_loop(void *data) {
|
||||
ret = poll(fds, self->count + EXTRA_FDS, -1);
|
||||
if (ret > 0) {
|
||||
if (fds[0].revents && POLLIN) drain_fd(fds[0].fd); // wakeup
|
||||
if (fds[1].revents && POLLIN) {
|
||||
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)) {
|
||||
data_received = true;
|
||||
has_more = read_bytes(fds[EXTRA_FDS + i].fd, children[i].screen);
|
||||
if (!has_more) {
|
||||
if (!has_more) {
|
||||
// child is dead
|
||||
children_mutex(lock);
|
||||
children[i].needs_removal = true;
|
||||
@@ -906,7 +1002,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
|
||||
@@ -933,66 +1029,265 @@ 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 {{{
|
||||
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)
|
||||
@@ -1007,15 +1302,23 @@ PyTypeObject ChildMonitor_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.ChildMonitor",
|
||||
.tp_basicsize = sizeof(ChildMonitor),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "ChildMonitor",
|
||||
.tp_methods = methods,
|
||||
.tp_new = new,
|
||||
.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 */
|
||||
};
|
||||
|
||||
@@ -1023,10 +1326,9 @@ bool
|
||||
init_child_monitor(PyObject *module) {
|
||||
if (PyType_Ready(&ChildMonitor_Type) < 0) return false;
|
||||
if (PyModule_AddObject(module, "ChildMonitor", (PyObject *)&ChildMonitor_Type) != 0) return false;
|
||||
Py_INCREF(&ChildMonitor_Type);
|
||||
Py_INCREF(&ChildMonitor_Type);
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// }}}
|
||||
|
||||
|
||||
113
kitty/child.c
Normal file
113
kitty/child.c
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* child.c
|
||||
* Copyright (C) 2018 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline char**
|
||||
serialize_string_tuple(PyObject *src) {
|
||||
Py_ssize_t sz = PyTuple_GET_SIZE(src);
|
||||
char **ans = calloc(sz + 1, sizeof(char*));
|
||||
if (!ans) fatal("Out of memory");
|
||||
for (Py_ssize_t i = 0; i < sz; i++) ans[i] = PyUnicode_AsUTF8(PyTuple_GET_ITEM(src, i));
|
||||
return ans;
|
||||
}
|
||||
|
||||
extern char **environ;
|
||||
|
||||
static inline void
|
||||
write_to_stderr(const char *text) {
|
||||
size_t sz = strlen(text);
|
||||
size_t written = 0;
|
||||
while(written < sz) {
|
||||
ssize_t amt = write(2, text + written, sz - written);
|
||||
if (amt == 0) break;
|
||||
if (amt < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR) continue;
|
||||
break;
|
||||
}
|
||||
written += amt;
|
||||
}
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
spawn(PyObject *self UNUSED, PyObject *args) {
|
||||
PyObject *argv_p, *env_p;
|
||||
int master, slave, stdin_read_fd, stdin_write_fd;
|
||||
char *cwd, *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);
|
||||
char **env = serialize_string_tuple(env_p);
|
||||
|
||||
#define exit_on_err(m) { write_to_stderr(m); write_to_stderr(": "); write_to_stderr(strerror(errno)); exit(EXIT_FAILURE); }
|
||||
pid_t pid = fork();
|
||||
switch(pid) {
|
||||
case 0:
|
||||
// child
|
||||
// Use only signal-safe functions (man 7 signal-safety)
|
||||
if (chdir(cwd) != 0) { if (chdir("/") != 0) {} }; // ignore failure to chdir to /
|
||||
if (setsid() == -1) exit_on_err("setsid() in child process failed");
|
||||
if (dup2(slave, 1) == -1) exit_on_err("dup2() failed for fd number 1");
|
||||
if (dup2(slave, 2) == -1) exit_on_err("dup2() failed for fd number 2");
|
||||
if (stdin_read_fd > -1) {
|
||||
if (dup2(stdin_read_fd, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
close(stdin_read_fd);
|
||||
close(stdin_write_fd);
|
||||
} else {
|
||||
if (dup2(slave, 0) == -1) exit_on_err("dup2() failed for fd number 0");
|
||||
}
|
||||
close(slave);
|
||||
close(master);
|
||||
for (int c = 3; c < 201; c++) close(c);
|
||||
|
||||
// Establish the controlling terminal (see man 7 credentials)
|
||||
int tfd = open(name, O_RDWR);
|
||||
if (tfd == -1) exit_on_err("Failed to open controlling terminal");
|
||||
close(tfd);
|
||||
|
||||
environ = env;
|
||||
execvp(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: ");
|
||||
write_to_stderr(argv[0]);
|
||||
write_to_stderr("\nWith error: ");
|
||||
write_to_stderr(strerror(errno));
|
||||
write_to_stderr("\nPress Enter to exit.\n");
|
||||
execlp("sh", "sh", "-c", "read w", NULL);
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
case -1:
|
||||
PyErr_SetFromErrno(PyExc_OSError);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#undef exit_on_err
|
||||
free(argv);
|
||||
free(env);
|
||||
if (PyErr_Occurred()) return NULL;
|
||||
return PyLong_FromLong(pid);
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
METHODB(spawn, METH_VARARGS),
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
bool
|
||||
init_child(PyObject *module) {
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
109
kitty/child.py
109
kitty/child.py
@@ -4,11 +4,26 @@
|
||||
|
||||
import fcntl
|
||||
import os
|
||||
import sys
|
||||
|
||||
import kitty.fast_data_types as fast_data_types
|
||||
|
||||
from .constants import terminfo_dir
|
||||
from .constants import is_macos, shell_path, terminfo_dir
|
||||
|
||||
|
||||
def cwd_of_process(pid):
|
||||
if is_macos:
|
||||
from kitty.fast_data_types import cwd_of_process
|
||||
ans = cwd_of_process(pid)
|
||||
else:
|
||||
ans = '/proc/{}/cwd'.format(pid)
|
||||
return os.path.realpath(ans)
|
||||
|
||||
|
||||
def cmdline_of_process(pid):
|
||||
if is_macos:
|
||||
# TODO: macOS implementation, see DarwinProcess.c in htop for inspiration
|
||||
raise NotImplementedError()
|
||||
return open('/proc/{}/cmdline'.format(pid), 'rb').read().decode('utf-8').split('\0')
|
||||
|
||||
|
||||
def remove_cloexec(fd):
|
||||
@@ -20,9 +35,17 @@ class Child:
|
||||
child_fd = pid = None
|
||||
forked = False
|
||||
|
||||
def __init__(self, argv, cwd, opts, stdin=None, env=None):
|
||||
def __init__(self, argv, cwd, opts, stdin=None, env=None, cwd_from=None):
|
||||
self.argv = argv
|
||||
self.cwd = os.path.abspath(os.path.expandvars(os.path.expanduser(cwd or os.getcwd())))
|
||||
if cwd_from is not None:
|
||||
try:
|
||||
cwd = cwd_of_process(cwd_from)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
cwd = os.path.expandvars(os.path.expanduser(cwd or os.getcwd()))
|
||||
self.cwd = os.path.abspath(cwd)
|
||||
self.opts = opts
|
||||
self.stdin = stdin
|
||||
self.env = env or {}
|
||||
@@ -38,44 +61,40 @@ class Child:
|
||||
if stdin is not None:
|
||||
stdin_read_fd, stdin_write_fd = os.pipe()
|
||||
remove_cloexec(stdin_read_fd)
|
||||
env = self.env
|
||||
pid = os.fork()
|
||||
if pid == 0: # child
|
||||
try:
|
||||
os.chdir(self.cwd)
|
||||
except EnvironmentError:
|
||||
os.chdir('/')
|
||||
os.setsid()
|
||||
for i in range(3):
|
||||
if stdin is not None and i == 0:
|
||||
os.dup2(stdin_read_fd, i)
|
||||
os.close(stdin_read_fd), os.close(stdin_write_fd)
|
||||
else:
|
||||
os.dup2(slave, i)
|
||||
os.close(slave), os.close(master)
|
||||
os.closerange(3, 200)
|
||||
# Establish the controlling terminal (see man 7 credentials)
|
||||
os.close(os.open(os.ttyname(1), os.O_RDWR))
|
||||
os.environ.update(env)
|
||||
os.environ['TERM'] = self.opts.term
|
||||
os.environ['COLORTERM'] = 'truecolor'
|
||||
if os.path.isdir(terminfo_dir):
|
||||
os.environ['TERMINFO'] = terminfo_dir
|
||||
try:
|
||||
os.execvp(self.argv[0], self.argv)
|
||||
except Exception as err:
|
||||
# Report he failure and exec a shell instead so that
|
||||
# we are not left with a forked but not execed process
|
||||
print('Could not launch:', self.argv[0])
|
||||
print('\t', err)
|
||||
print('\nPress Enter to exit:', end=' ')
|
||||
sys.stdout.flush()
|
||||
os.execvp('/bin/sh', ['/bin/sh', '-c', 'read w'])
|
||||
else: # master
|
||||
os.close(slave)
|
||||
self.pid = pid
|
||||
self.child_fd = master
|
||||
if stdin is not None:
|
||||
os.close(stdin_read_fd)
|
||||
fast_data_types.thread_write(stdin_write_fd, stdin)
|
||||
return pid
|
||||
else:
|
||||
stdin_read_fd = stdin_write_fd = -1
|
||||
env = os.environ.copy()
|
||||
env.update(self.env)
|
||||
env['TERM'] = self.opts.term
|
||||
env['COLORTERM'] = 'truecolor'
|
||||
if os.path.isdir(terminfo_dir):
|
||||
env['TERMINFO'] = terminfo_dir
|
||||
env = tuple('{}={}'.format(k, v) for k, v in env.items())
|
||||
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
|
||||
if stdin is not None:
|
||||
os.close(stdin_read_fd)
|
||||
fast_data_types.thread_write(stdin_write_fd, stdin)
|
||||
return pid
|
||||
|
||||
@property
|
||||
def cmdline(self):
|
||||
try:
|
||||
return cmdline_of_process(self.pid)
|
||||
except Exception:
|
||||
return list(self.argv)
|
||||
|
||||
@property
|
||||
def current_cwd(self):
|
||||
try:
|
||||
return cwd_of_process(self.pid)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
35
kitty/cli.py
35
kitty/cli.py
@@ -84,6 +84,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 +117,11 @@ 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.
|
||||
'''
|
||||
|
||||
|
||||
@@ -206,7 +221,7 @@ def prettify(text):
|
||||
def sub(m):
|
||||
t = m.group(2)
|
||||
for ch in m.group(1):
|
||||
t = {'C': cyan, '_': italic, '*': bold}[ch](t)
|
||||
t = {'C': cyan, '_': italic, '*': bold, 'G': green, 'T': title}[ch](t)
|
||||
return t
|
||||
|
||||
text = re.sub(r'[|]([a-zA-Z_*]+?) (.+?)[|]', sub, text)
|
||||
@@ -263,18 +278,26 @@ def print_help_for_seq(seq, usage, message, appname):
|
||||
if leading_indent is None:
|
||||
leading_indent = indent
|
||||
j = '\n' + (' ' * indent)
|
||||
a((' ' * leading_indent) + j.join(wrap(text, limit=linesz - indent)))
|
||||
lines = []
|
||||
for l in text.splitlines():
|
||||
if l:
|
||||
lines.extend(wrap(l, limit=linesz - indent))
|
||||
else:
|
||||
lines.append('')
|
||||
a((' ' * leading_indent) + j.join(lines))
|
||||
|
||||
usage = usage or '[program-to-run ...]'
|
||||
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 |_ {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal'
|
||||
'Run the |G {appname}| terminal emulator. You can also specify the |_ program| to run inside |_ {appname}| as normal'
|
||||
' arguments following the |_ options|. For example: {appname} /bin/sh'
|
||||
).format(appname=appname)
|
||||
wa(prettify(message))
|
||||
a('')
|
||||
a('{}:'.format(title('Options')))
|
||||
if seq:
|
||||
a('{}:'.format(title('Options')))
|
||||
for opt in seq:
|
||||
if isinstance(opt, str):
|
||||
a('{}:'.format(title(opt)))
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
*/
|
||||
|
||||
|
||||
#include "data-types.h"
|
||||
#include "state.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
// Needed for _NSGetProgname
|
||||
#include <crt_externs.h>
|
||||
typedef void* rusage_info_t; // needed for libproc.h
|
||||
#include <libproc.h>
|
||||
|
||||
#if (MAC_OS_X_VERSION_MAX_ALLOWED < 101200)
|
||||
#define NSWindowStyleMaskResizable NSResizableWindowMask
|
||||
@@ -21,8 +23,11 @@
|
||||
#endif
|
||||
|
||||
static NSMenuItem* title_menu = NULL;
|
||||
static bool change_titlebar_color = false;
|
||||
static color_type titlebar_color = 0;
|
||||
|
||||
static NSString*
|
||||
|
||||
static NSString*
|
||||
find_app_name(void) {
|
||||
size_t i;
|
||||
NSDictionary* infoDictionary = [[NSBundle mainBundle] infoDictionary];
|
||||
@@ -54,11 +59,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 =
|
||||
@@ -70,6 +99,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"];
|
||||
@@ -119,7 +151,7 @@ cocoa_create_global_menu(void) {
|
||||
setKeyEquivalentModifierMask:NSEventModifierFlagControl | NSEventModifierFlagCommand];
|
||||
[NSApp setWindowsMenu:windowMenu];
|
||||
[windowMenu release];
|
||||
|
||||
[preferences_menu_item release];
|
||||
|
||||
[bar release];
|
||||
}
|
||||
@@ -141,7 +173,7 @@ cocoa_update_title(PyObject *pytitle) {
|
||||
bool
|
||||
cocoa_make_window_resizable(void *w) {
|
||||
NSWindow *window = (NSWindow*)w;
|
||||
|
||||
|
||||
@try {
|
||||
[window setStyleMask:
|
||||
[window styleMask] | NSWindowStyleMaskResizable];
|
||||
@@ -152,7 +184,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];
|
||||
@@ -166,8 +198,68 @@ cocoa_get_lang(PyObject UNUSED *self) {
|
||||
return Py_BuildValue("s", [locale UTF8String]);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
cwd_of_process(PyObject *self UNUSED, PyObject *pid_) {
|
||||
long pid = PyLong_AsLong(pid_);
|
||||
struct proc_vnodepathinfo vpi;
|
||||
int ret = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));
|
||||
if (ret < 0) { PyErr_SetFromErrno(PyExc_OSError); return NULL; }
|
||||
return PyUnicode_FromString(vpi.pvi_cdir.vip_path);
|
||||
}
|
||||
|
||||
static inline color_type
|
||||
color_as_int(PyObject *color) {
|
||||
#define I(n, s) ((PyLong_AsUnsignedLong(PyTuple_GET_ITEM(color, n)) & 0xff) << s)
|
||||
return (I(0, 16) | I(1, 8) | I(2, 0)) & 0xffffff;
|
||||
#undef I
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
macos_change_titlebar_color(PyObject *self UNUSED, PyObject *val) {
|
||||
if (val == Py_None) change_titlebar_color = false;
|
||||
else if (val == Py_True) {
|
||||
change_titlebar_color = true;
|
||||
titlebar_color = OPT(background);
|
||||
} else {
|
||||
if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_TypeError, "Not a color tuple"); return NULL; }
|
||||
change_titlebar_color = true;
|
||||
titlebar_color = color_as_int(val);
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
void
|
||||
cocoa_set_titlebar_color(void *w)
|
||||
{
|
||||
if (!change_titlebar_color) return;
|
||||
|
||||
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, ""},
|
||||
{"cocoa_get_lang", (PyCFunction)cocoa_get_lang, METH_NOARGS, ""},
|
||||
{"cwd_of_process", (PyCFunction)cwd_of_process, METH_O, ""},
|
||||
{"macos_change_titlebar_color", (PyCFunction)macos_change_titlebar_color, METH_O, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ PyObject* create_256_color_table() {
|
||||
uint8_t v = 8 + i * 10;
|
||||
FG_BG_256[j] = (v << 16) | (v << 8) | v;
|
||||
}
|
||||
|
||||
|
||||
PyObject *ans = PyTuple_New(255);
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
for (i=0; i < 255; i++) {
|
||||
@@ -62,7 +62,7 @@ new(PyTypeObject *type, PyObject UNUSED *args, PyObject UNUSED *kwds) {
|
||||
if (FG_BG_256[255] == 0) create_256_color_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;
|
||||
self->dirty = true;
|
||||
}
|
||||
return (PyObject*) self;
|
||||
}
|
||||
@@ -93,7 +93,7 @@ update_ansi_color_table(ColorProfile *self, PyObject *val) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
color_type
|
||||
color_type
|
||||
colorprofile_to_color(ColorProfile *self, color_type entry, color_type defval) {
|
||||
color_type t = entry & 0xFF, r;
|
||||
switch(t) {
|
||||
@@ -128,12 +128,12 @@ as_color(ColorProfile *self, PyObject *val) {
|
||||
default:
|
||||
ans = Py_None; Py_INCREF(Py_None);
|
||||
}
|
||||
if (ans == NULL) ans = Py_BuildValue("BBB", (unsigned char)(col >> 16), (unsigned char)((col >> 8) & 0xFF), (unsigned char)(col & 0xFF));
|
||||
if (ans == NULL) ans = Py_BuildValue("BBB", (unsigned char)(col >> 16), (unsigned char)((col >> 8) & 0xFF), (unsigned char)(col & 0xFF));
|
||||
return ans;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -177,13 +177,13 @@ copy_color_table_to_buffer(ColorProfile *self, color_type *buf, int offset, size
|
||||
}
|
||||
self->dirty = false;
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
@@ -227,13 +227,13 @@ PyTypeObject ColorProfile_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.ColorProfile",
|
||||
.tp_basicsize = sizeof(ColorProfile),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "ColorProfile",
|
||||
.tp_members = members,
|
||||
.tp_methods = methods,
|
||||
.tp_getset = getsetters,
|
||||
.tp_new = new,
|
||||
.tp_new = new,
|
||||
};
|
||||
|
||||
INIT_TYPE(ColorProfile)
|
||||
|
||||
286
kitty/config.py
286
kitty/config.py
@@ -10,15 +10,18 @@ import shlex
|
||||
import sys
|
||||
import tempfile
|
||||
from collections import namedtuple
|
||||
from contextlib import contextmanager
|
||||
|
||||
from . import fast_data_types as defines
|
||||
from .constants import config_dir
|
||||
from .config_utils import (
|
||||
init_config, parse_config_base, positive_float, positive_int, to_bool,
|
||||
to_color, unit_float
|
||||
)
|
||||
from .constants import cache_dir, defconf
|
||||
from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE
|
||||
from .layout import all_layouts
|
||||
from .rgb import to_color
|
||||
from .utils import safe_print
|
||||
from .utils import log_error
|
||||
|
||||
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
|
||||
MINIMUM_FONT_SIZE = 4
|
||||
|
||||
|
||||
@@ -44,26 +47,18 @@ def to_cursor_shape(x):
|
||||
)
|
||||
|
||||
|
||||
def to_bool(x):
|
||||
return x.lower() in 'y yes true'.split()
|
||||
|
||||
|
||||
def parse_mods(parts):
|
||||
|
||||
def map_mod(m):
|
||||
return {'CTRL': 'CONTROL', 'CMD': 'CONTROL'}.get(m, m)
|
||||
return {'CTRL': 'CONTROL', 'CMD': 'SUPER', 'OPTION': 'ALT'}.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(
|
||||
parts.join('+')))
|
||||
return
|
||||
|
||||
return mods
|
||||
@@ -94,7 +89,10 @@ def parse_shortcut(sc):
|
||||
|
||||
|
||||
KeyAction = namedtuple('KeyAction', 'func args')
|
||||
shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab'}
|
||||
shlex_actions = {
|
||||
'pass_selection_to_program', 'new_window', 'new_tab', 'new_os_window',
|
||||
'new_window_with_cwd', 'new_tab_with_cwd', 'new_os_window_with_cwd'
|
||||
}
|
||||
|
||||
|
||||
def parse_key_action(action):
|
||||
@@ -109,8 +107,14 @@ def parse_key_action(action):
|
||||
args = tuple(map(parse_key_action, filter(None, parts)))
|
||||
elif func == 'send_text':
|
||||
args = rest.split(' ', 1)
|
||||
elif func == 'run_simple_kitten':
|
||||
args = rest.split(' ', 2)
|
||||
elif func == 'goto_tab':
|
||||
args = (max(0, int(rest)),)
|
||||
args = (max(0, int(rest)), )
|
||||
elif func == 'goto_layout':
|
||||
args = [rest]
|
||||
elif func == 'set_font_size':
|
||||
args = (float(rest),)
|
||||
elif func in shlex_actions:
|
||||
args = shlex.split(rest)
|
||||
return KeyAction(func, args)
|
||||
@@ -123,18 +127,14 @@ def parse_key(val, keymap):
|
||||
return
|
||||
mods, key = parse_shortcut(sc)
|
||||
if key is None:
|
||||
safe_print(
|
||||
'Shortcut: {} has an unknown key, ignoring'.format(val),
|
||||
file=sys.stderr
|
||||
)
|
||||
log_error('Shortcut: {} has unknown key, ignoring'.format(
|
||||
val))
|
||||
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
|
||||
@@ -145,9 +145,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:
|
||||
@@ -174,17 +173,16 @@ def parse_symbol_map(val):
|
||||
|
||||
|
||||
def parse_send_text_bytes(text):
|
||||
return ast.literal_eval("'''" + text + "'''").encode('utf-8')
|
||||
return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''"
|
||||
).encode('utf-8')
|
||||
|
||||
|
||||
def parse_send_text(val, keymap):
|
||||
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:
|
||||
@@ -195,7 +193,7 @@ def parse_send_text(val, keymap):
|
||||
return parse_key(key_str, keymap)
|
||||
|
||||
|
||||
def to_open_url_modifiers(val):
|
||||
def to_modifiers(val):
|
||||
return parse_mods(val.split('+'))
|
||||
|
||||
|
||||
@@ -209,24 +207,21 @@ def to_layout_names(raw):
|
||||
return parts
|
||||
|
||||
|
||||
def positive_int(x):
|
||||
return max(0, int(x))
|
||||
|
||||
|
||||
def positive_float(x):
|
||||
return max(0, float(x))
|
||||
|
||||
|
||||
def unit_float(x):
|
||||
return max(0, min(float(x), 1))
|
||||
|
||||
|
||||
def adjust_line_height(x):
|
||||
if x.endswith('%'):
|
||||
return float(x[:-1].strip()) / 100.0
|
||||
return int(x)
|
||||
|
||||
|
||||
def macos_titlebar_color(x):
|
||||
x = x.strip('"')
|
||||
if x == 'system':
|
||||
return
|
||||
if x == 'background':
|
||||
return True
|
||||
return to_color(x)
|
||||
|
||||
|
||||
def box_drawing_scale(x):
|
||||
ans = tuple(float(x.strip()) for x in x.split(','))
|
||||
if len(ans) != 4:
|
||||
@@ -240,33 +235,46 @@ def tab_separator(x):
|
||||
x = x[1:-1]
|
||||
break
|
||||
if not x.strip():
|
||||
x = defaults.tab_separator
|
||||
x = ('\xa0' * len(x)) if x else defaults.tab_separator
|
||||
return 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):
|
||||
return {'top': 1, 'bottom': 3}.get(x.lower(), 3)
|
||||
|
||||
|
||||
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,
|
||||
'font_size': to_font_size,
|
||||
'font_size_delta': positive_float,
|
||||
'focus_follows_mouse': to_bool,
|
||||
'cursor_shape': to_cursor_shape,
|
||||
'open_url_modifiers': to_open_url_modifiers,
|
||||
'open_url_modifiers': to_modifiers,
|
||||
'rectangle_select_modifiers': to_modifiers,
|
||||
'repaint_delay': positive_int,
|
||||
'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,
|
||||
@@ -281,29 +289,50 @@ 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_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,
|
||||
'tab_bar_edge': tab_bar_edge,
|
||||
}
|
||||
|
||||
for name in (
|
||||
'foreground background cursor active_border_color inactive_border_color'
|
||||
' selection_foreground selection_background url_color'
|
||||
).split():
|
||||
type_map[name] = lambda x: to_color(x, validate=True)
|
||||
type_map[name] = to_color
|
||||
for i in range(16):
|
||||
type_map['color%d' % i] = lambda x: to_color(x, validate=True)
|
||||
type_map['color%d' % i] = to_color
|
||||
for a in ('active', 'inactive'):
|
||||
for b in ('foreground', 'background'):
|
||||
type_map['%s_tab_%s' % (a, b)] = lambda x: to_color(x, validate=True)
|
||||
type_map['%s_tab_%s' % (a, b)] = to_color
|
||||
|
||||
|
||||
def special_handling(key, val, ans):
|
||||
if key == 'map':
|
||||
parse_key(val, ans['keymap'])
|
||||
return True
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
return True
|
||||
if key == 'send_text':
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['keymap'])
|
||||
return True
|
||||
|
||||
|
||||
defaults = None
|
||||
default_config_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)), 'kitty.conf'
|
||||
)
|
||||
|
||||
|
||||
def parse_config(lines, check_keys=True):
|
||||
@@ -311,46 +340,22 @@ def parse_config(lines, check_keys=True):
|
||||
'keymap': {},
|
||||
'symbol_map': {},
|
||||
}
|
||||
if check_keys:
|
||||
all_keys = defaults._asdict()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
m = key_pat.match(line)
|
||||
if m is not None:
|
||||
key, val = m.groups()
|
||||
if key == 'map':
|
||||
parse_key(val, ans['keymap'])
|
||||
continue
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
continue
|
||||
if key == 'send_text':
|
||||
# For legacy compatibility
|
||||
parse_send_text(val, ans['keymap'])
|
||||
continue
|
||||
if check_keys:
|
||||
if key not in all_keys:
|
||||
safe_print(
|
||||
'Ignoring unknown config key: {}'.format(key),
|
||||
file=sys.stderr
|
||||
)
|
||||
continue
|
||||
tm = type_map.get(key)
|
||||
if tm is not None:
|
||||
val = tm(val)
|
||||
ans[key] = val
|
||||
parse_config_base(
|
||||
lines,
|
||||
defaults,
|
||||
type_map,
|
||||
special_handling,
|
||||
ans,
|
||||
check_keys=check_keys
|
||||
)
|
||||
return ans
|
||||
|
||||
|
||||
with open(
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), 'kitty.conf'), 'rb'
|
||||
) as f:
|
||||
defaults = parse_config(f.read().decode('utf-8').splitlines(), check_keys=False)
|
||||
Options = namedtuple('Defaults', ','.join(defaults.keys()))
|
||||
defaults = Options(**defaults)
|
||||
actions = frozenset(a.func for a in defaults.keymap.values()) | frozenset({'combine', 'send_text', 'goto_tab'})
|
||||
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 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'})
|
||||
|
||||
|
||||
@@ -392,7 +397,7 @@ def load_config(*paths, overrides=None) -> Options:
|
||||
if not path:
|
||||
continue
|
||||
try:
|
||||
f = open(path)
|
||||
f = open(path, encoding='utf-8', errors='replace')
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
with f:
|
||||
@@ -415,56 +420,75 @@ def build_ansi_color_table(opts: Options = defaults):
|
||||
return list(map(col, range(16)))
|
||||
|
||||
|
||||
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):
|
||||
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
|
||||
|
||||
63
kitty/config_utils.py
Normal file
63
kitty/config_utils.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>
|
||||
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
from .utils import log_error
|
||||
from .rgb import to_color as as_color
|
||||
|
||||
key_pat = re.compile(r'([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$')
|
||||
|
||||
|
||||
def to_color(x):
|
||||
return as_color(x, validate=True)
|
||||
|
||||
|
||||
def positive_int(x):
|
||||
return max(0, int(x))
|
||||
|
||||
|
||||
def positive_float(x):
|
||||
return max(0, float(x))
|
||||
|
||||
|
||||
def unit_float(x):
|
||||
return max(0, min(float(x), 1))
|
||||
|
||||
|
||||
def to_bool(x):
|
||||
return x.lower() in 'y yes true'.split()
|
||||
|
||||
|
||||
def parse_config_base(
|
||||
lines, defaults, type_map, special_handling, ans, check_keys=True
|
||||
):
|
||||
if check_keys:
|
||||
all_keys = defaults._asdict()
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
m = key_pat.match(line)
|
||||
if m is not None:
|
||||
key, val = m.groups()
|
||||
if special_handling(key, val, ans):
|
||||
continue
|
||||
if check_keys:
|
||||
if key not in all_keys:
|
||||
log_error('Ignoring unknown config key: {}'.format(key))
|
||||
continue
|
||||
tm = type_map.get(key)
|
||||
if tm is not None:
|
||||
val = tm(val)
|
||||
ans[key] = val
|
||||
|
||||
|
||||
def init_config(defaults_path, parse_config):
|
||||
with open(defaults_path, encoding='utf-8', errors='replace') as f:
|
||||
defaults = parse_config(f.read().splitlines(), check_keys=False)
|
||||
Options = namedtuple('Defaults', ','.join(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, 6, 0)
|
||||
version = (0, 8, 2)
|
||||
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')
|
||||
@@ -22,9 +24,8 @@ WindowGeometry = namedtuple('WindowGeometry', 'left top right bottom xnum ynum')
|
||||
|
||||
|
||||
def _get_config_dir():
|
||||
# This must be called before calling setApplicationName
|
||||
if 'KITTY_CONFIG_DIRECTORY' in os.environ:
|
||||
return os.path.abspath(os.path.expanduser(os.environ['VISE_CONFIG_DIRECTORY']))
|
||||
return os.path.abspath(os.path.expanduser(os.environ['KITTY_CONFIG_DIRECTORY']))
|
||||
|
||||
candidate = os.path.abspath(os.path.expanduser(os.environ.get('XDG_CONFIG_HOME') or ('~/Library/Preferences' if is_macos else '~/.config')))
|
||||
ans = os.path.join(candidate, appname)
|
||||
@@ -37,6 +38,28 @@ 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)
|
||||
try:
|
||||
os.makedirs(candidate)
|
||||
except FileExistsError:
|
||||
pass
|
||||
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
|
||||
|
||||
@@ -56,7 +79,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'
|
||||
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
// Sharp control codes
|
||||
// -------------------
|
||||
|
||||
// Align display
|
||||
// Align display
|
||||
#define DECALN '8'
|
||||
|
||||
// Esc control codes
|
||||
@@ -180,7 +180,7 @@
|
||||
|
||||
// Scroll down by the specified number of lines
|
||||
#define SD 'T'
|
||||
|
||||
|
||||
// *Erase character*: Erase the indicated # of characters on the
|
||||
// current line.
|
||||
#define ECH 'X'
|
||||
@@ -212,7 +212,7 @@
|
||||
|
||||
// *Select graphics rendition*: The terminal can display the following
|
||||
// character attributes that change the character display without
|
||||
// changing the character
|
||||
// changing the character
|
||||
#define SGR 'm'
|
||||
|
||||
// *Device status report*.
|
||||
@@ -230,12 +230,6 @@
|
||||
// Forward tab
|
||||
#define CHT 'I'
|
||||
|
||||
// Save cursor
|
||||
#define SC 's'
|
||||
|
||||
// Restore cursor
|
||||
#define RC 'u'
|
||||
|
||||
// Misc sequences
|
||||
// ----------------
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ dealloc(CTFace* self) {
|
||||
static PyObject*
|
||||
font_descriptor_to_python(CTFontDescriptorRef descriptor) {
|
||||
NSURL *url = (NSURL *) CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute);
|
||||
NSString *psName = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute);
|
||||
NSString *psName = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontNameAttribute);
|
||||
NSString *family = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute);
|
||||
NSString *style = (NSString *) CTFontDescriptorCopyAttribute(descriptor, kCTFontStyleNameAttribute);
|
||||
NSDictionary *traits = (NSDictionary *) CTFontDescriptorCopyAttribute(descriptor, kCTFontTraitsAttribute);
|
||||
@@ -94,8 +94,8 @@ font_descriptor_to_python(CTFontDescriptorRef descriptor) {
|
||||
NSNumber *weightVal = traits[(id)kCTFontWeightTrait];
|
||||
NSNumber *widthVal = traits[(id)kCTFontWidthTrait];
|
||||
|
||||
PyObject *ans = Py_BuildValue("{ssssssss sOsOsO sfsfsI}",
|
||||
"path", [[url path] UTF8String],
|
||||
PyObject *ans = Py_BuildValue("{ssssssss sOsOsO sfsfsI}",
|
||||
"path", [[url path] UTF8String],
|
||||
"postscript_name", [psName UTF8String],
|
||||
"family", [family UTF8String],
|
||||
"style", [style UTF8String],
|
||||
@@ -148,7 +148,7 @@ static PyObject*
|
||||
coretext_all_fonts(PyObject UNUSED *_self) {
|
||||
static CTFontCollectionRef collection = NULL;
|
||||
if (collection == NULL) collection = CTFontCollectionCreateFromAvailableFonts(NULL);
|
||||
NSArray *matches = (NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection);
|
||||
NSArray *matches = (NSArray *) CTFontCollectionCreateMatchingFontDescriptors(collection);
|
||||
PyObject *ans = PyTuple_New([matches count]), *temp;
|
||||
if (ans == NULL) return PyErr_NoMemory();
|
||||
for (unsigned int i = 0; i < [matches count]; i++) {
|
||||
@@ -173,24 +173,28 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
|
||||
if (new_font == old_font) { CFRelease(new_font); continue; }
|
||||
return new_font;
|
||||
}
|
||||
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
|
||||
PyErr_SetString(PyExc_ValueError, "CoreText returned the same font as a fallback font");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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[128] = {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);
|
||||
}
|
||||
|
||||
unsigned int
|
||||
unsigned int
|
||||
glyph_id_for_codepoint(PyObject *s, char_type ch) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
unichar chars[2] = {0};
|
||||
@@ -200,6 +204,15 @@ glyph_id_for_codepoint(PyObject *s, char_type ch) {
|
||||
return glyphs[0];
|
||||
}
|
||||
|
||||
bool
|
||||
is_glyph_empty(PyObject *s, glyph_index g) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
CGGlyph gg = g;
|
||||
CGRect bounds;
|
||||
CTFontGetBoundingRectsForGlyphs(self->ct_font, kCTFontOrientationHorizontal, &gg, &bounds, 1);
|
||||
return bounds.size.width <= 0;
|
||||
}
|
||||
|
||||
static inline float
|
||||
scaled_point_sz() {
|
||||
return ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts;
|
||||
@@ -216,7 +229,7 @@ set_size_for_face(PyObject *s, unsigned int UNUSED desired_height, bool force) {
|
||||
return true;
|
||||
}
|
||||
|
||||
hb_font_t*
|
||||
hb_font_t*
|
||||
harfbuzz_font_for_face(PyObject* s) {
|
||||
CTFace *self = (CTFace*)s;
|
||||
if (!self->hb_font) {
|
||||
@@ -227,7 +240,7 @@ harfbuzz_font_for_face(PyObject* s) {
|
||||
return self->hb_font;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
|
||||
// See https://developer.apple.com/library/content/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/TypoFeatures/TextSystemFeatures.html
|
||||
CTFace *self = (CTFace*)s;
|
||||
@@ -241,10 +254,10 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
|
||||
if (glyphs[i]) {
|
||||
w = (unsigned int)(ceilf(
|
||||
CTFontGetAdvancesForGlyphs(self->ct_font, kCTFontOrientationHorizontal, glyphs+i, NULL, 1)));
|
||||
if (w > width) width = w;
|
||||
if (w > width) width = w;
|
||||
}
|
||||
}
|
||||
*cell_width = MAX(1, width);
|
||||
*cell_width = MAX(1, width);
|
||||
*underline_position = floor(self->ascent - self->underline_position + 0.5);
|
||||
*underline_thickness = (unsigned int)ceil(MAX(0.1, self->underline_thickness));
|
||||
*baseline = (unsigned int)self->ascent;
|
||||
@@ -252,7 +265,7 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
|
||||
// Let CoreText's layout engine calculate the line height. Slower, but hopefully more accurate.
|
||||
CFStringRef ts = CFSTR("test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test test");
|
||||
CFMutableAttributedStringRef test_string = CFAttributedStringCreateMutable(kCFAllocatorDefault, CFStringGetLength(ts));
|
||||
CFAttributedStringReplaceString(test_string, CFRangeMake(0, 0), ts);
|
||||
CFAttributedStringReplaceString(test_string, CFRangeMake(0, 0), ts);
|
||||
CFAttributedStringSetAttribute(test_string, CFRangeMake(0, CFStringGetLength(ts)), kCTFontAttributeName, self->ct_font);
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathAddRect(path, NULL, CGRectMake(10, 10, 200, 200));
|
||||
@@ -263,7 +276,7 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
|
||||
CTFrameGetLineOrigins(test_frame, CFRangeMake(0, 1), &origin1);
|
||||
CTFrameGetLineOrigins(test_frame, CFRangeMake(1, 1), &origin2);
|
||||
CGFloat line_height = origin1.y - origin2.y;
|
||||
CFRelease(test_frame); CFRelease(path); CFRelease(framesetter);
|
||||
CFRelease(test_frame); CFRelease(path); CFRelease(framesetter);
|
||||
*cell_height = MAX(4, (unsigned int)ceilf(line_height));
|
||||
#undef count
|
||||
}
|
||||
@@ -278,7 +291,7 @@ face_from_descriptor(PyObject *descriptor) {
|
||||
return (PyObject*) ct_face(font);
|
||||
}
|
||||
|
||||
PyObject*
|
||||
PyObject*
|
||||
face_from_path(const char *path, int UNUSED index) {
|
||||
CFStringRef s = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8);
|
||||
CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, s, kCFURLPOSIXPathStyle, false);
|
||||
@@ -315,7 +328,7 @@ render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int widt
|
||||
if (ctx == NULL) fatal("Out of memory");
|
||||
CGContextSetShouldAntialias(ctx, true);
|
||||
CGContextSetShouldSmoothFonts(ctx, true); // sub-pixel antialias
|
||||
CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
|
||||
CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
|
||||
CGAffineTransform transform = CGAffineTransformIdentity;
|
||||
CGContextSetTextDrawingMode(ctx, kCGTextFill);
|
||||
CGGlyph glyph = glyph_id;
|
||||
@@ -353,7 +366,7 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned
|
||||
CGContextSetGrayFillColor(render_ctx, 1, 1); // white glyphs
|
||||
CGContextSetTextDrawingMode(render_ctx, kCGTextFill);
|
||||
CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity);
|
||||
CGContextSetTextPosition(render_ctx, 0, height - baseline);
|
||||
CGContextSetTextPosition(render_ctx, 0, height - baseline);
|
||||
CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx);
|
||||
}
|
||||
|
||||
@@ -420,7 +433,7 @@ static PyMethodDef methods[] = {
|
||||
static PyObject *
|
||||
repr(CTFace *self) {
|
||||
char buf[1024] = {0};
|
||||
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
|
||||
snprintf(buf, sizeof(buf)/sizeof(buf[0]), "ascent=%.1f, descent=%.1f, leading=%.1f, point_sz=%.1f, scaled_point_sz=%.1f, underline_position=%.1f underline_thickness=%.1f",
|
||||
(self->ascent), (self->descent), (self->leading), (self->point_sz), (self->scaled_point_sz), (self->underline_position), (self->underline_thickness));
|
||||
return PyUnicode_FromFormat(
|
||||
"Face(family=%U, full_name=%U, postscript_name=%U, path=%U, units_per_em=%u, %s)",
|
||||
@@ -455,8 +468,8 @@ PyTypeObject CTFace_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.CTFace",
|
||||
.tp_basicsize = sizeof(CTFace),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "CoreText Font face",
|
||||
.tp_methods = methods,
|
||||
.tp_members = members,
|
||||
@@ -465,10 +478,10 @@ PyTypeObject CTFace_Type = {
|
||||
|
||||
|
||||
|
||||
int
|
||||
int
|
||||
init_CoreText(PyObject *module) {
|
||||
if (PyType_Ready(&CTFace_Type) < 0) return 0;
|
||||
if (PyModule_AddObject(module, "CTFace", (PyObject *)&CTFace_Type) != 0) return 0;
|
||||
if (PyModule_AddObject(module, "CTFace", (PyObject *)&CTFace_Type) != 0) return 0;
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return 0;
|
||||
if (Py_AtExit(finalize) != 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to register the CoreText at exit handler");
|
||||
|
||||
@@ -39,7 +39,7 @@ repr(Cursor *self) {
|
||||
);
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
cursor_reset_display_attrs(Cursor *self) {
|
||||
self->bg = 0; self->fg = 0; self->decoration_fg = 0;
|
||||
self->decoration = 0; self->bold = false; self->italic = false; self->reverse = false; self->strikethrough = false;
|
||||
@@ -50,25 +50,25 @@ static inline void
|
||||
parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t *result) {
|
||||
unsigned int attr;
|
||||
uint8_t r, g, b;
|
||||
if (*i < count) {
|
||||
if (*i < count) {
|
||||
attr = params[(*i)++];
|
||||
switch(attr) {
|
||||
case 5:
|
||||
if (*i < count) *result = (params[(*i)++] & 0xFF) << 8 | 1;
|
||||
break;
|
||||
switch(attr) {
|
||||
case 5:
|
||||
if (*i < count) *result = (params[(*i)++] & 0xFF) << 8 | 1;
|
||||
break;
|
||||
case 2: \
|
||||
if (*i < count - 2) {
|
||||
/* Ignore the first parameter in a four parameter RGB */
|
||||
/* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */
|
||||
if (*i < count - 3) (*i)++;
|
||||
r = params[(*i)++] & 0xFF;
|
||||
g = params[(*i)++] & 0xFF;
|
||||
b = params[(*i)++] & 0xFF;
|
||||
*result = r << 24 | g << 16 | b << 8 | 2;
|
||||
if (*i < count - 2) {
|
||||
/* Ignore the first parameter in a four parameter RGB */
|
||||
/* sequence (unused color space id), see https://github.com/kovidgoyal/kitty/issues/227 */
|
||||
if (*i < count - 3) (*i)++;
|
||||
r = params[(*i)++] & 0xFF;
|
||||
g = params[(*i)++] & 0xFF;
|
||||
b = params[(*i)++] & 0xFF;
|
||||
*result = r << 24 | g << 16 | b << 8 | 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,13 +109,13 @@ START_ALLOW_CASE_RANGE
|
||||
self->strikethrough = false; break;
|
||||
case 30 ... 37:
|
||||
self->fg = ((attr - 30) << 8) | 1; break;
|
||||
case 38:
|
||||
case 38:
|
||||
SET_COLOR(fg);
|
||||
case 39:
|
||||
self->fg = 0; break;
|
||||
case 40 ... 47:
|
||||
self->bg = ((attr - 40) << 8) | 1; break;
|
||||
case 48:
|
||||
case 48:
|
||||
SET_COLOR(bg);
|
||||
case 49:
|
||||
self->bg = 0; break;
|
||||
@@ -124,7 +124,7 @@ START_ALLOW_CASE_RANGE
|
||||
case 100 ... 107:
|
||||
self->bg = ((attr - 100 + 8) << 8) | 1; break;
|
||||
case DECORATION_FG_CODE:
|
||||
SET_COLOR(decoration_fg);
|
||||
SET_COLOR(decoration_fg);
|
||||
case DECORATION_FG_CODE + 1:
|
||||
self->decoration_fg = 0; break;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
|
||||
attr = params[i++];
|
||||
switch(attr) {
|
||||
case 0:
|
||||
RANGE { cell->attrs &= WIDTH_MASK; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; }
|
||||
RANGE { cell->attrs &= WIDTH_MASK; cell->fg = 0; cell->bg = 0; cell->decoration_fg = 0; }
|
||||
break;
|
||||
case 1:
|
||||
SET(BOLD_SHIFT);
|
||||
@@ -177,13 +177,13 @@ apply_sgr_to_cells(Cell *first_cell, unsigned int cell_count, unsigned int *para
|
||||
START_ALLOW_CASE_RANGE
|
||||
case 30 ... 37:
|
||||
SIMPLE(fg, ((attr - 30) << 8) | 1);
|
||||
case 38:
|
||||
case 38:
|
||||
SET_COLOR(fg);
|
||||
case 39:
|
||||
SIMPLE(fg, 0);
|
||||
case 40 ... 47:
|
||||
SIMPLE(bg, ((attr - 40) << 8) | 1);
|
||||
case 48:
|
||||
case 48:
|
||||
SET_COLOR(bg);
|
||||
case 49:
|
||||
SIMPLE(bg, 0);
|
||||
@@ -224,14 +224,14 @@ color_as_sgr(char *buf, size_t sz, unsigned long val, unsigned simple_code, unsi
|
||||
static inline const char*
|
||||
decoration_as_sgr(uint8_t decoration) {
|
||||
switch(decoration) {
|
||||
case 1: return "4";
|
||||
case 2: return "4:2";
|
||||
case 1: return "4";
|
||||
case 2: return "4:2";
|
||||
case 3: return "4:3";
|
||||
default: return "24";
|
||||
}
|
||||
}
|
||||
|
||||
const char*
|
||||
const char*
|
||||
cursor_as_sgr(Cursor *self, Cursor *prev) {
|
||||
static char buf[128];
|
||||
#define SZ sizeof(buf) - (p - buf) - 2
|
||||
@@ -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;
|
||||
@@ -267,12 +267,12 @@ void cursor_reset(Cursor *self) {
|
||||
|
||||
void cursor_copy_to(Cursor *src, Cursor *dest) {
|
||||
#define CCY(x) dest->x = src->x;
|
||||
CCY(x); CCY(y); CCY(shape); CCY(blink);
|
||||
CCY(bold); CCY(italic); CCY(strikethrough); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg);
|
||||
CCY(x); CCY(y); CCY(shape); CCY(blink);
|
||||
CCY(bold); CCY(italic); CCY(strikethrough); CCY(reverse); CCY(decoration); CCY(fg); CCY(bg); CCY(decoration_fg);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
copy(Cursor *self);
|
||||
copy(Cursor *self, PyObject*);
|
||||
#define copy_doc "Create a clone of this cursor"
|
||||
|
||||
// Boilerplate {{{
|
||||
@@ -317,21 +317,21 @@ PyTypeObject Cursor_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.Cursor",
|
||||
.tp_basicsize = sizeof(Cursor),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_repr = (reprfunc)repr,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "Cursors",
|
||||
.tp_richcompare = richcmp,
|
||||
.tp_richcompare = richcmp,
|
||||
.tp_methods = methods,
|
||||
.tp_members = members,
|
||||
.tp_members = members,
|
||||
.tp_getset = getseters,
|
||||
.tp_new = new,
|
||||
.tp_new = new,
|
||||
};
|
||||
|
||||
RICHCMP(Cursor)
|
||||
|
||||
// }}}
|
||||
|
||||
|
||||
Cursor*
|
||||
cursor_copy(Cursor *self) {
|
||||
Cursor* ans;
|
||||
@@ -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
|
||||
@@ -163,8 +123,8 @@ static PyMethodDef module_methods[] = {
|
||||
static struct PyModuleDef module = {
|
||||
.m_base = PyModuleDef_HEAD_INIT,
|
||||
.m_name = "fast_data_types", /* name of module */
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_doc = NULL,
|
||||
.m_size = -1,
|
||||
.m_methods = module_methods
|
||||
};
|
||||
|
||||
@@ -180,10 +140,14 @@ extern bool init_fontconfig_library(PyObject*);
|
||||
extern bool init_desktop(PyObject*);
|
||||
extern bool init_fonts(PyObject*);
|
||||
extern bool init_glfw(PyObject *m);
|
||||
extern bool init_child(PyObject *m);
|
||||
extern bool init_state(PyObject *module);
|
||||
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);
|
||||
@@ -203,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;
|
||||
@@ -211,10 +176,13 @@ PyInit_fast_data_types(void) {
|
||||
if (!init_ColorProfile(m)) return NULL;
|
||||
if (!init_Screen(m)) return NULL;
|
||||
if (!init_glfw(m)) return NULL;
|
||||
if (!init_child(m)) return NULL;
|
||||
if (!init_state(m)) return NULL;
|
||||
if (!init_keys(m)) return NULL;
|
||||
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;
|
||||
@@ -227,7 +195,7 @@ PyInit_fast_data_types(void) {
|
||||
|
||||
#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), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL;
|
||||
OOF(ch), OOF(fg), OOF(bg), OOF(decoration_fg), OOF(cc_idx), OOF(sprite_x), OOF(sprite_y), OOF(sprite_z), "size", sizeof(Cell))) != 0) return NULL;
|
||||
#undef OOF
|
||||
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
|
||||
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#define OPENGL_REQUIRED_VERSION_MAJOR 3
|
||||
#define OPENGL_REQUIRED_VERSION_MINOR 3
|
||||
#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)
|
||||
@@ -25,12 +26,14 @@
|
||||
#define MIN(x, y) (((x) > (y)) ? (y) : (x))
|
||||
#define xstr(s) str(s)
|
||||
#define str(s) #s
|
||||
#define fatal(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); exit(EXIT_FAILURE); }
|
||||
#define arraysz(x) (sizeof(x)/sizeof(x[0]))
|
||||
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;
|
||||
typedef uint32_t color_type;
|
||||
typedef uint32_t combining_type;
|
||||
typedef uint16_t combining_type;
|
||||
typedef uint32_t pixel;
|
||||
typedef unsigned int index_type;
|
||||
typedef uint16_t sprite_index;
|
||||
@@ -55,8 +58,6 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
|
||||
#define REVERSE_SHIFT 6
|
||||
#define STRIKE_SHIFT 7
|
||||
#define COL_MASK 0xFFFFFFFF
|
||||
#define CC_MASK 0xFFFF
|
||||
#define CC_SHIFT 16
|
||||
#define UTF8_ACCEPT 0
|
||||
#define UTF8_REJECT 1
|
||||
#define DECORATION_FG_CODE 58
|
||||
@@ -69,7 +70,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
|
||||
|
||||
#define CURSOR_TO_ATTRS(c, w) \
|
||||
((w) | (((c->decoration & 3) << DECORATION_SHIFT) | ((c->bold & 1) << BOLD_SHIFT) | \
|
||||
((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | ((c->strikethrough & 1) << STRIKE_SHIFT)))
|
||||
((c->italic & 1) << ITALIC_SHIFT) | ((c->reverse & 1) << REVERSE_SHIFT) | ((c->strikethrough & 1) << STRIKE_SHIFT)))
|
||||
|
||||
#define ATTRS_TO_CURSOR(a, c) \
|
||||
(c)->decoration = (a >> DECORATION_SHIFT) & 3; (c)->bold = (a >> BOLD_SHIFT) & 1; (c)->italic = (a >> ITALIC_SHIFT) & 1; \
|
||||
@@ -133,11 +134,12 @@ typedef struct {
|
||||
} Region;
|
||||
|
||||
typedef struct {
|
||||
char_type ch;
|
||||
color_type fg, bg, decoration_fg;
|
||||
combining_type cc;
|
||||
sprite_index sprite_x, sprite_y, sprite_z;
|
||||
attrs_type attrs;
|
||||
// The following are only needed on the CPU, not the GPU
|
||||
char_type ch;
|
||||
combining_type cc_idx[2];
|
||||
} Cell;
|
||||
|
||||
typedef struct {
|
||||
@@ -181,7 +183,7 @@ typedef struct {
|
||||
} Cursor;
|
||||
|
||||
typedef struct {
|
||||
bool is_visible;
|
||||
bool is_visible, is_focused;
|
||||
CursorShape shape;
|
||||
unsigned int x, y;
|
||||
double left, right, top, bottom;
|
||||
@@ -201,29 +203,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 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 +217,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) { \
|
||||
@@ -243,7 +230,7 @@ typedef struct {
|
||||
}
|
||||
|
||||
|
||||
// Global functions
|
||||
// Global functions
|
||||
const char* base64_decode(const uint32_t *src, size_t src_sz, uint8_t *dest, size_t dest_capacity, size_t *dest_sz);
|
||||
Line* alloc_line();
|
||||
Cursor* alloc_cursor();
|
||||
@@ -271,14 +258,12 @@ 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();
|
||||
void wakeup_io_loop(bool);
|
||||
void scroll_event(double, double);
|
||||
void fake_scroll(bool);
|
||||
void fake_scroll(int, bool);
|
||||
void set_special_key_combo(int glfw_key, int mods);
|
||||
void on_text_input(unsigned int codepoint, int mods);
|
||||
void on_key_input(int key, int scancode, int action, int mods);
|
||||
|
||||
232
kitty/emoji.h
generated
232
kitty/emoji.h
generated
@@ -1,13 +1,45 @@
|
||||
// unicode data, built from the unicode standard on: 2018-02-09
|
||||
// see gen-wcwidth.py
|
||||
#pragma once
|
||||
#include "data-types.h"
|
||||
|
||||
START_ALLOW_CASE_RANGE
|
||||
static inline bool is_emoji(uint32_t code) {
|
||||
|
||||
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:
|
||||
return true;
|
||||
case 0x231a ... 0x231b:
|
||||
return true;
|
||||
case 0x2328:
|
||||
return true;
|
||||
case 0x2388:
|
||||
return true;
|
||||
case 0x23cf:
|
||||
return true;
|
||||
case 0x23e9 ... 0x23f3:
|
||||
@@ -24,89 +56,15 @@ static inline bool is_emoji(uint32_t 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;
|
||||
@@ -132,7 +90,7 @@ static inline bool is_emoji(uint32_t code) {
|
||||
return true;
|
||||
case 0x2757:
|
||||
return true;
|
||||
case 0x2763 ... 0x2764:
|
||||
case 0x2763 ... 0x2767:
|
||||
return true;
|
||||
case 0x2795 ... 0x2797:
|
||||
return true;
|
||||
@@ -160,11 +118,15 @@ static inline bool is_emoji(uint32_t 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;
|
||||
@@ -172,9 +134,9 @@ static inline bool is_emoji(uint32_t 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;
|
||||
@@ -182,106 +144,48 @@ static inline bool is_emoji(uint32_t 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;
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
static inline bool is_emoji_modifier(uint32_t code) {
|
||||
static inline bool
|
||||
is_emoji_modifier(char_type code) {
|
||||
switch(code) {
|
||||
case 0x1f3fb ... 0x1f3ff:
|
||||
return true;
|
||||
default: return false;
|
||||
}
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
END_ALLOW_CASE_RANGE
|
||||
|
||||
@@ -131,9 +131,9 @@ add_charset(FcPattern *pat, size_t num) {
|
||||
charset = FcCharSetCreate();
|
||||
if (charset == NULL) { PyErr_NoMemory(); goto end; }
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
if (!FcCharSetAddChar(charset, char_buf[i])) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset");
|
||||
goto end;
|
||||
if (!FcCharSetAddChar(charset, char_buf[i])) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset");
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
AP(FcPatternAddCharSet, FC_CHARSET, charset, "charset");
|
||||
@@ -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);
|
||||
@@ -214,7 +213,7 @@ static PyMethodDef module_methods[] = {
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
bool
|
||||
bool
|
||||
init_fontconfig_library(PyObject *module) {
|
||||
if (!FcInit()) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize the fontconfig library");
|
||||
|
||||
520
kitty/fonts.c
520
kitty/fonts.c
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* vim:fileencoding=utf-8
|
||||
* fonts.c
|
||||
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
@@ -8,11 +9,11 @@
|
||||
#include "fonts.h"
|
||||
#include "state.h"
|
||||
#include "emoji.h"
|
||||
#include "unicode-data.h"
|
||||
|
||||
#define MISSING_GLYPH 4
|
||||
#define MAX_NUM_EXTRA_GLYPHS 8
|
||||
|
||||
typedef uint16_t glyph_index;
|
||||
typedef void (*send_sprite_to_gpu_func)(unsigned int, unsigned int, unsigned int, pixel*);
|
||||
send_sprite_to_gpu_func current_send_sprite_to_gpu = NULL;
|
||||
static PyObject *python_send_to_gpu_impl = NULL;
|
||||
@@ -36,11 +37,16 @@ struct SpritePosition {
|
||||
ExtraGlyphs extra_glyphs;
|
||||
};
|
||||
|
||||
#define SPECIAL_FILLED_MASK 1
|
||||
#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;
|
||||
glyph_index glyph;
|
||||
bool is_special, filled;
|
||||
uint8_t data;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -51,15 +57,15 @@ typedef struct {
|
||||
|
||||
static GPUSpriteTracker sprite_tracker = {0};
|
||||
static hb_buffer_t *harfbuzz_buffer = NULL;
|
||||
static char_type shape_buffer[2048] = {0};
|
||||
static char_type shape_buffer[4096] = {0};
|
||||
|
||||
|
||||
typedef struct {
|
||||
PyObject *face;
|
||||
// Map glyphs to sprite map co-ords
|
||||
SpritePosition sprite_map[1024];
|
||||
SpecialGlyphCache special_glyph_cache[1024];
|
||||
bool bold, italic;
|
||||
SpritePosition sprite_map[1024];
|
||||
SpecialGlyphCache special_glyph_cache[SPECIAL_GLYPH_CACHE_SIZE];
|
||||
bool bold, italic, emoji_presentation;
|
||||
} Font;
|
||||
|
||||
typedef struct {
|
||||
@@ -79,7 +85,7 @@ static Fonts fonts = {0};
|
||||
|
||||
// Sprites {{{
|
||||
|
||||
static inline void
|
||||
static inline void
|
||||
sprite_map_set_error(int error) {
|
||||
switch(error) {
|
||||
case 1:
|
||||
@@ -123,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
|
||||
@@ -150,20 +156,21 @@ sprite_position_for(Font *font, glyph_index glyph, ExtraGlyphs *extra_glyphs, ui
|
||||
return s;
|
||||
}
|
||||
|
||||
static SpecialGlyphCache*
|
||||
special_glyph_cache_for(Font *font, glyph_index glyph) {
|
||||
static inline SpecialGlyphCache*
|
||||
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->filled)) 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->filled) {
|
||||
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));
|
||||
if (s->next == NULL) return NULL;
|
||||
if (s->next == NULL) return NULL;
|
||||
}
|
||||
s = s->next;
|
||||
}
|
||||
@@ -211,7 +218,7 @@ clear_sprite_map(Font *font) {
|
||||
|
||||
void
|
||||
clear_special_glyph_cache(Font *font) {
|
||||
#define CLEAR(s) s->filled = false; s->glyph = 0;
|
||||
#define CLEAR(s) s->data = 0; s->glyph = 0;
|
||||
SpecialGlyphCache *s;
|
||||
for (size_t i = 0; i < sizeof(font->special_glyph_cache)/sizeof(font->special_glyph_cache[0]); i++) {
|
||||
s = font->special_glyph_cache + i;
|
||||
@@ -243,18 +250,18 @@ 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->face = face;
|
||||
f->bold = bold; f->italic = italic; f->emoji_presentation = emoji_presentation;
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void
|
||||
del_font(Font *f) {
|
||||
Py_CLEAR(f->face);
|
||||
del_font(Font *f) {
|
||||
Py_CLEAR(f->face);
|
||||
free_maps(f);
|
||||
f->bold = false; f->italic = false;
|
||||
}
|
||||
@@ -262,7 +269,7 @@ del_font(Font *f) {
|
||||
static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0;
|
||||
static pixel *canvas = NULL;
|
||||
#define CELLS_IN_CANVAS ((MAX_NUM_EXTRA_GLYPHS + 1) * 3)
|
||||
static inline void
|
||||
static inline void
|
||||
clear_canvas(void) { memset(canvas, 0, CELLS_IN_CANVAS * cell_width * cell_height * sizeof(pixel)); }
|
||||
|
||||
static void
|
||||
@@ -276,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);
|
||||
@@ -285,10 +292,14 @@ update_cell_metrics() {
|
||||
unsigned int before_cell_height = cell_height;
|
||||
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
|
||||
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
|
||||
if (OPT(adjust_column_width_px != 0)) cell_width += OPT(adjust_column_width_px);
|
||||
if (OPT(adjust_column_width_frac) != 0.f) cell_height *= OPT(adjust_column_width_frac);
|
||||
int line_height_adjustment = cell_height - before_cell_height;
|
||||
if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; }
|
||||
if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; }
|
||||
underline_position = MIN(cell_height - 1, underline_position);
|
||||
// ensure there is at least a couple of pixels available to render styled underlines
|
||||
while (underline_position > baseline + 1 && cell_height - underline_position < 2) underline_position--;
|
||||
if (line_height_adjustment > 1) {
|
||||
baseline += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
|
||||
underline_position += MIN(cell_height - 1, (unsigned)line_height_adjustment / 2);
|
||||
@@ -309,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
|
||||
@@ -318,49 +330,77 @@ face_has_codepoint(PyObject* face, char_type cp) {
|
||||
return glyph_id_for_codepoint(face, cp) > 0;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
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;
|
||||
if (cell->cc) {
|
||||
if (!face_has_codepoint(self->face, cell->cc & CC_MASK)) return false;
|
||||
char_type cc = cell->cc >> 16;
|
||||
if (cc && !face_has_codepoint(self->face, cc)) return false;
|
||||
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) {
|
||||
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, 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, 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, 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;
|
||||
ssize_t f;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (fonts.fallback_fonts_count > 100) { fprintf(stderr, "Too many fallback fonts\n"); return MISSING_FONT; }
|
||||
|
||||
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);
|
||||
if (face == NULL) { PyErr_Print(); return MISSING_FONT; }
|
||||
if (face == Py_None) { Py_DECREF(face); return MISSING_FONT; }
|
||||
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");
|
||||
Py_DECREF(face);
|
||||
fonts.fallback_fonts_count++;
|
||||
fonts.fonts_count++;
|
||||
return ans;
|
||||
return load_fallback_font(cell, bold, italic, emoji_presentation);
|
||||
}
|
||||
|
||||
static inline ssize_t
|
||||
@@ -399,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
|
||||
@@ -441,7 +481,7 @@ render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *de
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
render_box_cell(Cell *cell) {
|
||||
int error = 0;
|
||||
glyph_index glyph = box_glyph_id(cell->ch);
|
||||
@@ -467,19 +507,17 @@ render_box_cell(Cell *cell) {
|
||||
}
|
||||
|
||||
static inline void
|
||||
load_hb_buffer(Cell *first_cell, index_type num_cells) {
|
||||
load_hb_buffer(Cell *first_cell, index_type num_cells) {
|
||||
index_type num;
|
||||
hb_buffer_clear_contents(harfbuzz_buffer);
|
||||
while (num_cells) {
|
||||
attrs_type prev_width = 0;
|
||||
for (num = 0; num_cells && num < sizeof(shape_buffer)/sizeof(shape_buffer[0]) - 20; first_cell++, num_cells--) {
|
||||
for (num = 0; num_cells && num < arraysz(shape_buffer) - 20 - arraysz(first_cell->cc_idx); first_cell++, num_cells--) {
|
||||
if (prev_width == 2) { prev_width = 0; continue; }
|
||||
shape_buffer[num++] = first_cell->ch;
|
||||
prev_width = first_cell->attrs & WIDTH_MASK;
|
||||
if (first_cell->cc) {
|
||||
shape_buffer[num++] = first_cell->cc & CC_MASK;
|
||||
combining_type cc2 = first_cell->cc >> 16;
|
||||
if (cc2) shape_buffer[num++] = cc2 & CC_MASK;
|
||||
for (unsigned i = 0; i < arraysz(first_cell->cc_idx) && first_cell->cc_idx[i]; i++) {
|
||||
shape_buffer[num++] = codepoint_for_mark(first_cell->cc_idx[i]);
|
||||
}
|
||||
}
|
||||
hb_buffer_add_utf32(harfbuzz_buffer, shape_buffer, num, 0, num);
|
||||
@@ -517,14 +555,14 @@ 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();
|
||||
|
||||
for (unsigned int i = 0; i < num_cells; i++) {
|
||||
for (unsigned int i = 0; i < num_cells; i++) {
|
||||
sprite_position[i]->rendered = true;
|
||||
sprite_position[i]->colored = was_colored;
|
||||
set_cell_sprite(cells + i, sprite_position[i]);
|
||||
set_cell_sprite(cells + i, sprite_position[i]);
|
||||
pixel *buf = num_cells == 1 ? canvas : extract_cell_from_canvas(i, num_cells);
|
||||
current_send_sprite_to_gpu(sprite_position[i]->x, sprite_position[i]->y, sprite_position[i]->z, buf);
|
||||
}
|
||||
@@ -538,28 +576,84 @@ typedef struct {
|
||||
char_type current_codepoint;
|
||||
} CellData;
|
||||
|
||||
typedef struct {
|
||||
unsigned int first_glyph_idx, first_cell_idx, num_glyphs, num_cells;
|
||||
} Group;
|
||||
|
||||
typedef struct {
|
||||
uint32_t previous_cluster;
|
||||
bool prev_was_special, prev_was_empty;
|
||||
CellData current_cell_data;
|
||||
Group *groups;
|
||||
size_t groups_capacity, group_idx, glyph_idx, cell_idx, num_cells, num_glyphs;
|
||||
Cell *first_cell, *last_cell;
|
||||
hb_glyph_info_t *info;
|
||||
hb_glyph_position_t *positions;
|
||||
} GroupState;
|
||||
|
||||
static GroupState group_state = {0};
|
||||
|
||||
static inline unsigned int
|
||||
num_codepoints_in_cell(Cell *cell) {
|
||||
unsigned int ans = 1;
|
||||
for (unsigned i = 0; i < arraysz(cell->cc_idx) && cell->cc_idx[i]; i++) ans++;
|
||||
return ans;
|
||||
}
|
||||
|
||||
static inline void
|
||||
shape(Cell *first_cell, index_type num_cells, hb_font_t *font) {
|
||||
if (group_state.groups_capacity <= 2 * num_cells) {
|
||||
group_state.groups_capacity = MAX(128, 2 * num_cells); // avoid unnecessary reallocs
|
||||
group_state.groups = realloc(group_state.groups, sizeof(Group) * group_state.groups_capacity);
|
||||
if (!group_state.groups) fatal("Out of memory");
|
||||
}
|
||||
group_state.previous_cluster = UINT32_MAX;
|
||||
group_state.prev_was_special = false;
|
||||
group_state.prev_was_empty = false;
|
||||
group_state.current_cell_data.cell = first_cell; group_state.current_cell_data.num_codepoints = num_codepoints_in_cell(first_cell); group_state.current_cell_data.codepoints_consumed = 0; group_state.current_cell_data.current_codepoint = first_cell->ch;
|
||||
memset(group_state.groups, 0, sizeof(Group) * group_state.groups_capacity);
|
||||
group_state.group_idx = 0;
|
||||
group_state.glyph_idx = 0;
|
||||
group_state.cell_idx = 0;
|
||||
group_state.num_cells = num_cells;
|
||||
group_state.first_cell = first_cell;
|
||||
group_state.last_cell = first_cell + (num_cells ? num_cells - 1 : 0);
|
||||
load_hb_buffer(first_cell, num_cells);
|
||||
hb_shape(font, harfbuzz_buffer, NULL, 0);
|
||||
unsigned int info_length, positions_length;
|
||||
group_state.info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
|
||||
group_state.positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length);
|
||||
if (!group_state.info || !group_state.positions) group_state.num_glyphs = 0;
|
||||
else group_state.num_glyphs = MIN(info_length, positions_length);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
is_special_glyph(glyph_index glyph_id, Font *font, CellData* cell_data) {
|
||||
// A glyph is special if the codepoint it corresponds to matches a
|
||||
// different glyph in the font
|
||||
SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id);
|
||||
SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, SPECIAL_FILLED_MASK);
|
||||
if (s == NULL) return false;
|
||||
if (!s->filled) {
|
||||
s->is_special = cell_data->current_codepoint ? (
|
||||
glyph_id != glyph_id_for_codepoint(font->face, cell_data->current_codepoint) ? true : false)
|
||||
if (!(s->data & SPECIAL_FILLED_MASK)) {
|
||||
bool is_special = cell_data->current_codepoint ? (
|
||||
glyph_id != glyph_id_for_codepoint(font->face, cell_data->current_codepoint) ? true : false)
|
||||
:
|
||||
false;
|
||||
s->filled = true;
|
||||
}
|
||||
return s->is_special;
|
||||
uint8_t val = is_special ? SPECIAL_VALUE_MASK : 0;
|
||||
s->data |= val | SPECIAL_FILLED_MASK;
|
||||
}
|
||||
return s->data & SPECIAL_VALUE_MASK;
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
num_codepoints_in_cell(Cell *cell) {
|
||||
unsigned int ans = 1;
|
||||
if (cell->cc) ans += ((cell->cc >> CC_SHIFT) & CC_MASK) ? 2 : 1;
|
||||
return ans;
|
||||
static inline bool
|
||||
is_empty_glyph(glyph_index glyph_id, Font *font) {
|
||||
// A glyph is empty if its metrics have a width of zero
|
||||
SpecialGlyphCache *s = special_glyph_cache_for(font, glyph_id, EMPTY_FILLED_MASK);
|
||||
if (s == NULL) return false;
|
||||
if (!(s->data & EMPTY_FILLED_MASK)) {
|
||||
uint8_t val = is_glyph_empty(font->face, glyph_id) ? EMPTY_VALUE_MASK : 0;
|
||||
s->data |= val | EMPTY_FILLED_MASK;
|
||||
}
|
||||
return s->data & EMPTY_VALUE_MASK;
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
@@ -579,100 +673,154 @@ check_cell_consumed(CellData *cell_data, Cell *last_cell) {
|
||||
case 0:
|
||||
cell_data->current_codepoint = cell_data->cell->ch;
|
||||
break;
|
||||
case 1:
|
||||
cell_data->current_codepoint = cell_data->cell->cc & CC_MASK;
|
||||
break;
|
||||
case 2:
|
||||
cell_data->current_codepoint = (cell_data->cell->cc >> CC_SHIFT) & CC_MASK;
|
||||
break;
|
||||
default:
|
||||
cell_data->current_codepoint = 0;
|
||||
cell_data->current_codepoint = codepoint_for_mark(cell_data->cell->cc_idx[cell_data->codepoints_consumed - 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline glyph_index
|
||||
next_group(Font *font, unsigned int *num_group_cells, unsigned int *num_group_glyphs, Cell *cells, hb_glyph_info_t *info, unsigned int max_num_glyphs, unsigned int max_num_cells, ExtraGlyphs *extra_glyphs) {
|
||||
// See https://github.com/behdad/harfbuzz/issues/615 for a discussion of
|
||||
// how to break text into cells. In addition, we have to deal with
|
||||
// monospace ligature fonts that use dummy glyphs of zero size to implement
|
||||
// their ligatures.
|
||||
|
||||
CellData cell_data;
|
||||
cell_data.cell = cells; cell_data.num_codepoints = num_codepoints_in_cell(cells); cell_data.codepoints_consumed = 0; cell_data.current_codepoint = cells->ch;
|
||||
#define LIMIT (MAX_NUM_EXTRA_GLYPHS + 1)
|
||||
glyph_index glyphs_in_group[LIMIT];
|
||||
unsigned int ncells = 0, nglyphs = 0, n;
|
||||
uint32_t previous_cluster = UINT32_MAX, cluster;
|
||||
Cell *last_cell = cells + max_num_cells;
|
||||
unsigned int cell_limit = MIN(max_num_cells, LIMIT + 1), glyph_limit = MIN(LIMIT, max_num_glyphs);
|
||||
bool is_special, prev_was_special = false;
|
||||
|
||||
while(nglyphs < glyph_limit && ncells < cell_limit) {
|
||||
glyph_index glyph_id = info[nglyphs].codepoint;
|
||||
cluster = info[nglyphs].cluster;
|
||||
is_special = is_special_glyph(glyph_id, font, &cell_data);
|
||||
if (prev_was_special && !is_special) break;
|
||||
glyphs_in_group[nglyphs++] = glyph_id;
|
||||
// Soak up a number of codepoints indicated by the difference in cluster numbers.
|
||||
if (cluster > previous_cluster || nglyphs == 1) {
|
||||
n = nglyphs == 1 ? 1 : cluster - previous_cluster;
|
||||
unsigned int before = ncells;
|
||||
while(n-- && ncells < max_num_cells) ncells += check_cell_consumed(&cell_data, last_cell);
|
||||
if (ncells > before && !is_special) break;
|
||||
}
|
||||
previous_cluster = cluster;
|
||||
prev_was_special = is_special;
|
||||
}
|
||||
*num_group_cells = MAX(1, MIN(ncells, cell_limit));
|
||||
*num_group_glyphs = MAX(1, MIN(nglyphs, glyph_limit));
|
||||
memset(extra_glyphs, 0, sizeof(ExtraGlyphs));
|
||||
|
||||
switch(nglyphs) {
|
||||
case 0:
|
||||
case 1:
|
||||
break;
|
||||
default:
|
||||
memcpy(&extra_glyphs->data, glyphs_in_group + 1, sizeof(glyph_index) * MIN(MAX_NUM_EXTRA_GLYPHS, nglyphs - 1));
|
||||
break;
|
||||
}
|
||||
#undef LIMIT
|
||||
return glyphs_in_group[0];
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
shape(Cell *first_cell, index_type num_cells, hb_font_t *font, hb_glyph_info_t **info, hb_glyph_position_t **positions) {
|
||||
load_hb_buffer(first_cell, num_cells);
|
||||
hb_shape(font, harfbuzz_buffer, NULL, 0);
|
||||
unsigned int info_length, positions_length;
|
||||
*info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
|
||||
*positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length);
|
||||
if (!info || !positions) return 0;
|
||||
return MIN(info_length, positions_length);
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
shape_run(Cell *first_cell, index_type num_cells, Font *font) {
|
||||
hb_glyph_info_t *info;
|
||||
hb_glyph_position_t *positions;
|
||||
unsigned int num_glyphs = shape(first_cell, num_cells, harfbuzz_font_for_face(font->face), &info, &positions);
|
||||
shape(first_cell, num_cells, harfbuzz_font_for_face(font->face));
|
||||
#if 0
|
||||
// You can also generate this easily using hb-shape --show-extents --cluster-level=1 --shapers=ot /path/to/font/file text
|
||||
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
|
||||
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, (char*)canvas, sizeof(pixel) * CELLS_IN_CANVAS * cell_width * cell_height, NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
|
||||
printf("\n%s\n", (char*)canvas);
|
||||
clear_canvas();
|
||||
#endif
|
||||
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells;
|
||||
ExtraGlyphs extra_glyphs;
|
||||
glyph_index first_glyph;
|
||||
while(run_pos < num_glyphs && cell_pos < num_cells) {
|
||||
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, first_cell + cell_pos, info + run_pos, num_glyphs - run_pos, num_cells - cell_pos, &extra_glyphs);
|
||||
/* printf("Group: num_group_cells: %u num_group_glyphs: %u\n", num_group_cells, num_group_glyphs); */
|
||||
render_group(num_group_cells, num_group_glyphs, first_cell + cell_pos, info + run_pos, positions + run_pos, font, first_glyph, &extra_glyphs);
|
||||
run_pos += num_group_glyphs; cell_pos += num_group_cells;
|
||||
/* Now distribute the glyphs into groups of cells
|
||||
* Considerations to keep in mind:
|
||||
* Group sizes should be as small as possible for best performance
|
||||
* Combining chars can result in multiple glyphs rendered into a single cell
|
||||
* Emoji and East Asian wide chars can cause a single glyph to be rendered over multiple cells
|
||||
* Ligature fonts, take two common approaches:
|
||||
* 1. ABC becomes EMPTY, EMPTY, WIDE GLYPH this means we have to render N glyphs in N cells (example Fira Code)
|
||||
* 2. ABC becomes WIDE GLYPH this means we have to render one glyph in N cells (example Operator Mono Lig)
|
||||
*
|
||||
* We rely on the cluster numbers from harfbuzz to tell us how many unicode codepoints a glyph corresponds to.
|
||||
* Then we check if the glyph is a ligature glyph (is_special_glyph) and if it is an empty glyph. These three
|
||||
* datapoints give us enough information to satisfy the constraint above, for a wide variety of fonts.
|
||||
*/
|
||||
uint32_t cluster, next_cluster;
|
||||
bool add_to_current_group;
|
||||
#define G(x) (group_state.x)
|
||||
#define MAX_GLYPHS_IN_GROUP (MAX_NUM_EXTRA_GLYPHS + 1)
|
||||
while (G(glyph_idx) < G(num_glyphs) && G(cell_idx) < G(num_cells)) {
|
||||
glyph_index glyph_id = G(info)[G(glyph_idx)].codepoint;
|
||||
cluster = G(info)[G(glyph_idx)].cluster;
|
||||
bool is_special = is_special_glyph(glyph_id, font, &G(current_cell_data));
|
||||
bool is_empty = is_special && is_empty_glyph(glyph_id, font);
|
||||
uint32_t num_codepoints_used_by_glyph = 0;
|
||||
bool is_last_glyph = G(glyph_idx) == G(num_glyphs) - 1;
|
||||
Group *current_group = G(groups) + G(group_idx);
|
||||
if (is_last_glyph) {
|
||||
num_codepoints_used_by_glyph = UINT32_MAX;
|
||||
} else {
|
||||
next_cluster = G(info)[G(glyph_idx) + 1].cluster;
|
||||
if (next_cluster > cluster) num_codepoints_used_by_glyph = next_cluster - cluster;
|
||||
}
|
||||
if (!current_group->num_glyphs) {
|
||||
add_to_current_group = true;
|
||||
} else {
|
||||
if (is_special) {
|
||||
add_to_current_group = G(prev_was_empty);
|
||||
} else {
|
||||
add_to_current_group = !G(prev_was_special);
|
||||
}
|
||||
}
|
||||
if (current_group->num_glyphs >= MAX_GLYPHS_IN_GROUP || current_group->num_cells >= MAX_GLYPHS_IN_GROUP) add_to_current_group = false;
|
||||
|
||||
if (!add_to_current_group) { G(group_idx)++; current_group = G(groups) + G(group_idx); }
|
||||
if (!current_group->num_glyphs++) {
|
||||
current_group->first_glyph_idx = G(glyph_idx);
|
||||
current_group->first_cell_idx = G(cell_idx);
|
||||
}
|
||||
#define MOVE_GLYPH_TO_NEXT_GROUP(start_cell_idx) { \
|
||||
current_group->num_glyphs--; \
|
||||
G(group_idx)++; current_group = G(groups) + G(group_idx); \
|
||||
current_group->first_cell_idx = start_cell_idx; \
|
||||
current_group->num_glyphs = 1; \
|
||||
current_group->first_glyph_idx = G(glyph_idx); \
|
||||
}
|
||||
if (is_last_glyph) {
|
||||
// soak up all remaining cells
|
||||
if (G(cell_idx) < G(num_cells)) {
|
||||
unsigned int num_left = G(num_cells) - G(cell_idx);
|
||||
if (current_group->num_cells + num_left > MAX_GLYPHS_IN_GROUP) MOVE_GLYPH_TO_NEXT_GROUP(G(cell_idx));
|
||||
current_group->num_cells += num_left;
|
||||
if (current_group->num_cells > MAX_GLYPHS_IN_GROUP) current_group->num_cells = MAX_GLYPHS_IN_GROUP; // leave any trailing cells empty
|
||||
G(cell_idx) += num_left;
|
||||
}
|
||||
} else {
|
||||
unsigned int num_cells_consumed = 0, start_cell_idx = G(cell_idx);
|
||||
while (num_codepoints_used_by_glyph && G(cell_idx) < G(num_cells)) {
|
||||
unsigned int w = check_cell_consumed(&G(current_cell_data), G(last_cell));
|
||||
G(cell_idx) += w;
|
||||
num_cells_consumed += w;
|
||||
num_codepoints_used_by_glyph--;
|
||||
}
|
||||
if (num_cells_consumed) {
|
||||
if (num_cells_consumed > MAX_GLYPHS_IN_GROUP) {
|
||||
// Nasty, a single glyph used more than MAX_GLYPHS_IN_GROUP cells, we cannot render this case correctly
|
||||
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);
|
||||
current_group->num_glyphs = 1; current_group->first_glyph_idx = G(glyph_idx);
|
||||
current_group->num_cells = MIN(num_cells_consumed, MAX_GLYPHS_IN_GROUP);
|
||||
current_group->first_cell_idx = start_cell_idx;
|
||||
start_cell_idx += current_group->num_cells;
|
||||
num_cells_consumed -= current_group->num_cells;
|
||||
}
|
||||
} else {
|
||||
if (num_cells_consumed + current_group->num_cells > MAX_GLYPHS_IN_GROUP) MOVE_GLYPH_TO_NEXT_GROUP(start_cell_idx);
|
||||
current_group->num_cells += num_cells_consumed;
|
||||
if (!is_special) { // not a ligature, end the group
|
||||
G(group_idx)++; current_group = G(groups) + G(group_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
G(prev_was_special) = is_special;
|
||||
G(prev_was_empty) = is_empty;
|
||||
G(previous_cluster) = cluster;
|
||||
G(glyph_idx)++;
|
||||
}
|
||||
#undef MOVE_GLYPH_TO_NEXT_GROUP
|
||||
#undef MAX_GLYPHS_IN_GROUP
|
||||
}
|
||||
|
||||
static inline void
|
||||
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;
|
||||
ExtraGlyphs ed;
|
||||
while (idx <= G(group_idx)) {
|
||||
Group *group = G(groups) + idx;
|
||||
if (!group->num_cells) break;
|
||||
/* printf("Group: idx: %u num_cells: %u num_glyphs: %u first_glyph_idx: %u first_cell_idx: %u total_num_glyphs: %zu\n", */
|
||||
/* idx, group->num_cells, group->num_glyphs, group->first_glyph_idx, group->first_cell_idx, group_state.num_glyphs); */
|
||||
glyph_index primary = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,31 +839,34 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
|
||||
if (face == NULL) return NULL;
|
||||
font = calloc(1, sizeof(Font));
|
||||
font->face = face;
|
||||
}
|
||||
hb_glyph_info_t *info;
|
||||
hb_glyph_position_t *positions;
|
||||
unsigned int num_glyphs = shape(line->cells, num, harfbuzz_font_for_face(font->face), &info, &positions);
|
||||
}
|
||||
shape_run(line->cells, num, font);
|
||||
|
||||
PyObject *ans = PyList_New(0);
|
||||
unsigned int run_pos = 0, cell_pos = 0, num_group_glyphs, num_group_cells;
|
||||
ExtraGlyphs extra_glyphs;
|
||||
unsigned int idx = 0;
|
||||
glyph_index first_glyph;
|
||||
while(run_pos < num_glyphs && cell_pos < num) {
|
||||
first_glyph = next_group(font, &num_group_cells, &num_group_glyphs, line->cells + cell_pos, info + run_pos, num_glyphs - run_pos, num - cell_pos, &extra_glyphs);
|
||||
while (idx <= G(group_idx)) {
|
||||
Group *group = G(groups) + idx;
|
||||
if (!group->num_cells) break;
|
||||
first_glyph = group->num_glyphs ? G(info)[group->first_glyph_idx].codepoint : 0;
|
||||
|
||||
PyObject *eg = PyTuple_New(MAX_NUM_EXTRA_GLYPHS);
|
||||
for (size_t g = 0; g < MAX_NUM_EXTRA_GLYPHS; g++) PyTuple_SET_ITEM(eg, g, Py_BuildValue("H", extra_glyphs.data[g]));
|
||||
PyList_Append(ans, Py_BuildValue("IIIN", num_group_cells, num_group_glyphs, first_glyph, eg));
|
||||
run_pos += num_group_glyphs; cell_pos += num_group_cells;
|
||||
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));
|
||||
idx++;
|
||||
}
|
||||
if (face) { Py_CLEAR(face); free(font); }
|
||||
return ans;
|
||||
}
|
||||
#undef G
|
||||
|
||||
static inline void
|
||||
render_run(Cell *first_cell, index_type num_cells, ssize_t font_idx) {
|
||||
static inline void
|
||||
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:
|
||||
while(num_cells--) set_sprite(first_cell++, 0, 0, 0);
|
||||
@@ -729,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;
|
||||
@@ -739,6 +895,16 @@ 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.
|
||||
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;
|
||||
@@ -761,7 +927,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
|
||||
@@ -773,7 +939,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;
|
||||
@@ -782,7 +948,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
|
||||
@@ -793,6 +959,7 @@ finalize(void) {
|
||||
for (size_t i = 0; i < fonts.fonts_count; i++) del_font(fonts.fonts + i);
|
||||
free(fonts.symbol_maps); free(fonts.fonts);
|
||||
if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer);
|
||||
free(group_state.groups);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
@@ -899,7 +1066,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; }}
|
||||
@@ -923,15 +1090,14 @@ error:
|
||||
|
||||
static PyObject*
|
||||
get_fallback_font(PyObject UNUSED *self, PyObject *args) {
|
||||
PyObject *text;
|
||||
PyObject *text;
|
||||
int bold, italic;
|
||||
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
|
||||
static Py_UCS4 char_buf[16];
|
||||
if (!PyUnicode_AsUCS4(text, char_buf, sizeof(char_buf)/sizeof(char_buf[0]), 1)) return NULL;
|
||||
Cell cell = {0};
|
||||
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];
|
||||
if (PyUnicode_GetLength(text) > 1) cell.cc |= char_buf[1] & CC_MASK;
|
||||
if (PyUnicode_GetLength(text) > 2) cell.cc |= (char_buf[2] & CC_MASK) << 16;
|
||||
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);
|
||||
@@ -956,7 +1122,7 @@ static PyMethodDef module_methods[] = {
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
bool
|
||||
bool
|
||||
init_fonts(PyObject *module) {
|
||||
if (Py_AtExit(finalize) != 0) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to register the fonts module at exit handler");
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
|
||||
|
||||
// API that font backends need to implement
|
||||
typedef uint16_t glyph_index;
|
||||
unsigned int glyph_id_for_codepoint(PyObject *, char_type);
|
||||
bool is_glyph_empty(PyObject *, glyph_index);
|
||||
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*);
|
||||
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
|
||||
@@ -73,7 +73,7 @@ def resize_fonts(new_sz):
|
||||
|
||||
def add_line(buf, cell_width, position, thickness, cell_height):
|
||||
y = position - thickness // 2
|
||||
while thickness > 0 and y > -1 and y < cell_height - 1:
|
||||
while thickness > 0 and y > -1 and y < cell_height:
|
||||
thickness -= 1
|
||||
ctypes.memset(ctypes.addressof(buf) + (cell_width * y), 255, cell_width)
|
||||
y += 1
|
||||
@@ -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)
|
||||
|
||||
@@ -38,7 +38,7 @@ PyTypeObject Face_Type;
|
||||
|
||||
static PyObject* FreeType_Exception = NULL;
|
||||
|
||||
void
|
||||
void
|
||||
set_freetype_error(const char* prefix, int err_code) {
|
||||
int i = 0;
|
||||
#undef FTERRORS_H_
|
||||
@@ -54,7 +54,7 @@ set_freetype_error(const char* prefix, int err_code) {
|
||||
|
||||
#ifdef FT_ERRORS_H
|
||||
#include FT_ERRORS_H
|
||||
#else
|
||||
#else
|
||||
FT_ERROR_START_LIST FT_ERROR_END_LIST
|
||||
#endif
|
||||
|
||||
@@ -121,8 +121,8 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
|
||||
return true;
|
||||
}
|
||||
}
|
||||
set_freetype_error("Failed to set char size, with error:", error);
|
||||
return false;
|
||||
set_freetype_error("Failed to set char size, with error:", error);
|
||||
return false;
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle) {
|
||||
PyObject*
|
||||
ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float apple_leading) {
|
||||
Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0);
|
||||
if (ans == NULL) return NULL;
|
||||
if (ans == NULL) return NULL;
|
||||
int error = FT_New_Memory_Face(library, data, sz, 0, &ans->face);
|
||||
if(error) { set_freetype_error("Failed to load memory face, with error:", error); Py_CLEAR(ans); return NULL; }
|
||||
if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; }
|
||||
@@ -232,7 +232,7 @@ face_from_descriptor(PyObject *descriptor) {
|
||||
}
|
||||
return (PyObject*)self;
|
||||
}
|
||||
|
||||
|
||||
PyObject*
|
||||
face_from_path(const char *path, int index) {
|
||||
Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0);
|
||||
@@ -265,7 +265,7 @@ repr(Face *self) {
|
||||
}
|
||||
|
||||
|
||||
static inline bool
|
||||
static inline bool
|
||||
load_glyph(Face *self, int glyph_index, int load_type) {
|
||||
int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
|
||||
int error = FT_Load_Glyph(self->face, glyph_index, flags);
|
||||
@@ -285,7 +285,7 @@ calc_cell_width(Face *self) {
|
||||
return ans;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
|
||||
Face *self = (Face*)s;
|
||||
*cell_width = calc_cell_width(self);
|
||||
@@ -295,11 +295,22 @@ cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, u
|
||||
*underline_thickness = MAX(1, font_units_to_pixels(self, self->underline_thickness));
|
||||
}
|
||||
|
||||
unsigned int
|
||||
unsigned int
|
||||
glyph_id_for_codepoint(PyObject *s, char_type cp) {
|
||||
return FT_Get_Char_Index(((Face*)s)->face, cp);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
is_glyph_empty(PyObject *s, glyph_index g) {
|
||||
Face *self = (Face*)s;
|
||||
if (!load_glyph(self, g, FT_LOAD_DEFAULT)) { PyErr_Print(); return false; }
|
||||
#define M self->face->glyph->metrics
|
||||
/* printf("glyph: %u horiBearingX: %ld horiBearingY: %ld width: %ld height: %ld\n", g, M.horiBearingX, M.horiBearingY, M.width, M.height); */
|
||||
return M.width == 0;
|
||||
#undef M
|
||||
}
|
||||
|
||||
hb_font_t*
|
||||
harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; }
|
||||
|
||||
@@ -326,6 +337,7 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
|
||||
if (!column_has_text) { ans->width--; extra--; }
|
||||
}
|
||||
|
||||
// Remove any remaining extra columns from the left edge of the bitmap
|
||||
ans->start_x = extra;
|
||||
ans->width -= extra;
|
||||
}
|
||||
@@ -345,7 +357,7 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
|
||||
size_t extra = bitmap->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 (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)) {
|
||||
@@ -376,12 +388,12 @@ downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_hei
|
||||
for (unsigned int y=sr; y < MIN(sr + factor, bm->rows); y++) {
|
||||
uint8_t *p = bm->buf + (y * bm->stride) + sc * 4;
|
||||
for (unsigned int x=sc; x < MIN(sc + factor, bm->width); x++, count++) {
|
||||
b += *(p++); g += *(p++); r += *(p++); a += *(p++);
|
||||
b += *(p++); g += *(p++); r += *(p++); a += *(p++);
|
||||
}
|
||||
}
|
||||
if (count) {
|
||||
d[0] = b / count; d[1] = g / count; d[2] = r / count; d[3] = a / count;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -450,7 +462,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size
|
||||
// We want the glyph to be positioned inside the cell based on the bearingX
|
||||
// and bearingY values, making sure that it does not overflow the cell.
|
||||
|
||||
Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width }, dest = { .bottom = cell_height, .right = cell_width };
|
||||
Region src = { .left = bm->start_x, .bottom = bm->rows, .right = bm->width + bm->start_x }, dest = { .bottom = cell_height, .right = cell_width };
|
||||
|
||||
// Calculate column bounds
|
||||
float bearing_x = (float)metrics->horiBearingX / 64.f;
|
||||
@@ -506,7 +518,7 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf
|
||||
y = (float)positions[i].y_offset / 64.0f;
|
||||
if ((*was_colored || self->face->glyph->metrics.width > 0) && bm.width > 0) place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, &self->face->glyph->metrics, baseline);
|
||||
x += (float)positions[i].x_advance / 64.0f;
|
||||
if (bm.needs_free) free(bm.buf);
|
||||
if (bm.needs_free) free(bm.buf);
|
||||
}
|
||||
|
||||
// center the glyphs in the canvas
|
||||
@@ -520,7 +532,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);
|
||||
@@ -528,8 +541,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 {{{
|
||||
@@ -560,8 +573,8 @@ PyTypeObject Face_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.Face",
|
||||
.tp_basicsize = sizeof(Face),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "FreeType Font face",
|
||||
.tp_methods = methods,
|
||||
.tp_members = members,
|
||||
@@ -573,11 +586,11 @@ free_freetype() {
|
||||
FT_Done_FreeType(library);
|
||||
}
|
||||
|
||||
bool
|
||||
bool
|
||||
init_freetype_library(PyObject *m) {
|
||||
if (PyType_Ready(&Face_Type) < 0) return 0;
|
||||
if (PyModule_AddObject(m, "Face", (PyObject *)&Face_Type) != 0) return 0;
|
||||
Py_INCREF(&Face_Type);
|
||||
if (PyModule_AddObject(m, "Face", (PyObject *)&Face_Type) != 0) return 0;
|
||||
Py_INCREF(&Face_Type);
|
||||
FreeType_Exception = PyErr_NewException("fast_data_types.FreeTypeError", NULL, NULL);
|
||||
if (FreeType_Exception == NULL) return false;
|
||||
if (PyModule_AddObject(m, "FreeTypeError", FreeType_Exception) != 0) return false;
|
||||
|
||||
1622
kitty/gl-wrapper.c
generated
1622
kitty/gl-wrapper.c
generated
File diff suppressed because it is too large
Load Diff
45
kitty/gl.h
45
kitty/gl.h
@@ -22,24 +22,24 @@ static void
|
||||
check_for_gl_error(const char *name, void UNUSED *funcptr, int UNUSED len_args, ...) {
|
||||
#define f(msg) fatal("OpenGL error: %s (calling function: %s)", msg, name); break;
|
||||
GLenum code = glad_glGetError();
|
||||
switch(code) {
|
||||
switch(code) {
|
||||
case GL_NO_ERROR: break;
|
||||
case GL_INVALID_ENUM:
|
||||
f("An enum value is invalid (GL_INVALID_ENUM)");
|
||||
case GL_INVALID_VALUE:
|
||||
f("An numeric value is invalid (GL_INVALID_VALUE)");
|
||||
case GL_INVALID_OPERATION:
|
||||
f("This operation is invalid (GL_INVALID_OPERATION)");
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)");
|
||||
case GL_OUT_OF_MEMORY:
|
||||
f("There is not enough memory left to execute the command. (GL_OUT_OF_MEMORY)");
|
||||
case GL_STACK_UNDERFLOW:
|
||||
f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_UNDERFLOW)");
|
||||
case GL_STACK_OVERFLOW:
|
||||
f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_OVERFLOW)");
|
||||
default:
|
||||
fatal("An unknown OpenGL error occurred with code: %d (calling function: %s)", code, name);
|
||||
case GL_INVALID_ENUM:
|
||||
f("An enum value is invalid (GL_INVALID_ENUM)");
|
||||
case GL_INVALID_VALUE:
|
||||
f("An numeric value is invalid (GL_INVALID_VALUE)");
|
||||
case GL_INVALID_OPERATION:
|
||||
f("This operation is invalid (GL_INVALID_OPERATION)");
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION:
|
||||
f("The framebuffer object is not complete (GL_INVALID_FRAMEBUFFER_OPERATION)");
|
||||
case GL_OUT_OF_MEMORY:
|
||||
f("There is not enough memory left to execute the command. (GL_OUT_OF_MEMORY)");
|
||||
case GL_STACK_UNDERFLOW:
|
||||
f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_UNDERFLOW)");
|
||||
case GL_STACK_OVERFLOW:
|
||||
f("An attempt has been made to perform an operation that would cause an internal stack to underflow. (GL_STACK_OVERFLOW)");
|
||||
default:
|
||||
fatal("An unknown OpenGL error occurred with code: %d (calling function: %s)", code, name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -59,6 +59,9 @@ gl_init() {
|
||||
ARB_TEST(texture_storage);
|
||||
#undef ARB_TEST
|
||||
glad_loaded = true;
|
||||
if (GLVersion.major < OPENGL_REQUIRED_VERSION_MAJOR || (GLVersion.major == OPENGL_REQUIRED_VERSION_MAJOR && GLVersion.minor < OPENGL_REQUIRED_VERSION_MINOR)) {
|
||||
fatal("OpenGL version is %d.%d, version >= 3.3 required for kitty", GLVersion.major, GLVersion.minor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +76,7 @@ update_surface_size(int w, int h, GLuint offscreen_texture_id) {
|
||||
|
||||
void
|
||||
free_texture(GLuint *tex_id) {
|
||||
glDeleteTextures(1, tex_id);
|
||||
glDeleteTextures(1, tex_id);
|
||||
*tex_id = 0;
|
||||
}
|
||||
|
||||
@@ -114,7 +117,7 @@ compile_shader(GLenum shader_type, const char *source) {
|
||||
if (ret != GL_TRUE) {
|
||||
GLsizei len;
|
||||
glGetShaderInfoLog(shader_id, sizeof(glbuf), &len, glbuf);
|
||||
fprintf(stderr, "Failed to compile GLSL shader!\n%s", glbuf);
|
||||
log_error("Failed to compile GLSL shader!\n%s", glbuf);
|
||||
glDeleteShader(shader_id);
|
||||
PyErr_SetString(PyExc_ValueError, "Failed to compile shader");
|
||||
return 0;
|
||||
@@ -288,7 +291,7 @@ add_buffer_to_vao(ssize_t vao_idx, GLenum usage) {
|
||||
static void
|
||||
add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) {
|
||||
VAO *vao = vaos + vao_idx;
|
||||
if (!vao->num_buffers) fatal("You must create a buffer for this attribute first");
|
||||
if (!vao->num_buffers) fatal("You must create a buffer for this attribute first");
|
||||
ssize_t buf = vao->buffers[vao->num_buffers - 1];
|
||||
bind_buffer(buf);
|
||||
glEnableVertexAttribArray(aloc);
|
||||
@@ -315,7 +318,7 @@ add_located_attribute_to_vao(ssize_t vao_idx, GLint aloc, GLint size, GLenum dat
|
||||
static inline void
|
||||
add_attribute_to_vao(int p, ssize_t vao_idx, const char *name, GLint size, GLenum data_type, GLsizei stride, void *offset, GLuint divisor) {
|
||||
GLint aloc = attrib_location(p, name);
|
||||
if (aloc == -1) fatal("No attribute named: %s found in this program", name);
|
||||
if (aloc == -1) fatal("No attribute named: %s found in this program", name);
|
||||
add_located_attribute_to_vao(vao_idx, aloc, size, data_type, stride, offset, divisor);
|
||||
}
|
||||
|
||||
|
||||
21
kitty/glfw-wrapper.c
generated
21
kitty/glfw-wrapper.c
generated
@@ -23,9 +23,6 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwInitHint_impl) = dlsym(handle, "glfwInitHint");
|
||||
if (glfwInitHint_impl == NULL) fail("Failed to load glfw function glfwInitHint with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwInitHintString_impl) = dlsym(handle, "glfwInitHintString");
|
||||
if (glfwInitHintString_impl == NULL) fail("Failed to load glfw function glfwInitHintString with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwGetVersion_impl) = dlsym(handle, "glfwGetVersion");
|
||||
if (glfwGetVersion_impl == NULL) fail("Failed to load glfw function glfwGetVersion with error: %s", dlerror());
|
||||
|
||||
@@ -56,6 +53,12 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwGetMonitorName_impl) = dlsym(handle, "glfwGetMonitorName");
|
||||
if (glfwGetMonitorName_impl == NULL) fail("Failed to load glfw function glfwGetMonitorName with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetMonitorUserPointer_impl) = dlsym(handle, "glfwSetMonitorUserPointer");
|
||||
if (glfwSetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetMonitorUserPointer with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwGetMonitorUserPointer_impl) = dlsym(handle, "glfwGetMonitorUserPointer");
|
||||
if (glfwGetMonitorUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetMonitorUserPointer with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetMonitorCallback_impl) = dlsym(handle, "glfwSetMonitorCallback");
|
||||
if (glfwSetMonitorCallback_impl == NULL) fail("Failed to load glfw function glfwSetMonitorCallback with error: %s", dlerror());
|
||||
|
||||
@@ -80,6 +83,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwWindowHint_impl) = dlsym(handle, "glfwWindowHint");
|
||||
if (glfwWindowHint_impl == NULL) fail("Failed to load glfw function glfwWindowHint with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwWindowHintString_impl) = dlsym(handle, "glfwWindowHintString");
|
||||
if (glfwWindowHintString_impl == NULL) fail("Failed to load glfw function glfwWindowHintString with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwCreateWindow_impl) = dlsym(handle, "glfwCreateWindow");
|
||||
if (glfwCreateWindow_impl == NULL) fail("Failed to load glfw function glfwCreateWindow with error: %s", dlerror());
|
||||
|
||||
@@ -197,6 +203,9 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwSetFramebufferSizeCallback_impl) = dlsym(handle, "glfwSetFramebufferSizeCallback");
|
||||
if (glfwSetFramebufferSizeCallback_impl == NULL) fail("Failed to load glfw function glfwSetFramebufferSizeCallback with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetWindowContentScaleCallback_impl) = dlsym(handle, "glfwSetWindowContentScaleCallback");
|
||||
if (glfwSetWindowContentScaleCallback_impl == NULL) fail("Failed to load glfw function glfwSetWindowContentScaleCallback with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwPollEvents_impl) = dlsym(handle, "glfwPollEvents");
|
||||
if (glfwPollEvents_impl == NULL) fail("Failed to load glfw function glfwPollEvents with error: %s", dlerror());
|
||||
|
||||
@@ -287,6 +296,12 @@ load_glfw(const char* path) {
|
||||
*(void **) (&glfwGetJoystickGUID_impl) = dlsym(handle, "glfwGetJoystickGUID");
|
||||
if (glfwGetJoystickGUID_impl == NULL) fail("Failed to load glfw function glfwGetJoystickGUID with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwSetJoystickUserPointer_impl) = dlsym(handle, "glfwSetJoystickUserPointer");
|
||||
if (glfwSetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwSetJoystickUserPointer with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwGetJoystickUserPointer_impl) = dlsym(handle, "glfwGetJoystickUserPointer");
|
||||
if (glfwGetJoystickUserPointer_impl == NULL) fail("Failed to load glfw function glfwGetJoystickUserPointer with error: %s", dlerror());
|
||||
|
||||
*(void **) (&glfwJoystickIsGamepad_impl) = dlsym(handle, "glfwJoystickIsGamepad");
|
||||
if (glfwJoystickIsGamepad_impl == NULL) fail("Failed to load glfw function glfwJoystickIsGamepad with error: %s", dlerror());
|
||||
|
||||
|
||||
88
kitty/glfw-wrapper.h
generated
88
kitty/glfw-wrapper.h
generated
@@ -251,17 +251,37 @@
|
||||
* @{ */
|
||||
|
||||
/*! @brief If this bit is set one or more Shift keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Shift keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_SHIFT 0x0001
|
||||
/*! @brief If this bit is set one or more Control keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Control keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_CONTROL 0x0002
|
||||
/*! @brief If this bit is set one or more Alt keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Alt keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_ALT 0x0004
|
||||
/*! @brief If this bit is set one or more Super keys were held down.
|
||||
*
|
||||
* If this bit is set one or more Super keys were held down.
|
||||
*/
|
||||
#define GLFW_MOD_SUPER 0x0008
|
||||
/*! @brief If this bit is set the Caps Lock key is enabled.
|
||||
*
|
||||
* If this bit is set the Caps Lock key is enabled and the @ref
|
||||
* GLFW_LOCK_KEY_MODS input mode is set.
|
||||
*/
|
||||
#define GLFW_MOD_CAPS_LOCK 0x0010
|
||||
/*! @brief If this bit is set the Num Lock key is enabled.
|
||||
*
|
||||
* If this bit is set the Num Lock key is enabled and the @ref
|
||||
* GLFW_LOCK_KEY_MODS input mode is set.
|
||||
*/
|
||||
#define GLFW_MOD_NUM_LOCK 0x0020
|
||||
|
||||
/*! @} */
|
||||
|
||||
@@ -552,6 +572,11 @@
|
||||
* [window attribute](@ref GLFW_TRANSPARENT_FRAMEBUFFER_attrib).
|
||||
*/
|
||||
#define GLFW_TRANSPARENT_FRAMEBUFFER 0x0002000A
|
||||
/*! @brief Mouse cursor hover window attribute.
|
||||
*
|
||||
* Mouse cursor hover [window attribute](@ref GLFW_HOVERED_attrib).
|
||||
*/
|
||||
#define GLFW_HOVERED 0x0002000B
|
||||
|
||||
/*! @brief Framebuffer bit depth hint.
|
||||
*
|
||||
@@ -702,8 +727,11 @@
|
||||
#define GLFW_CONTEXT_CREATION_API 0x0002200B
|
||||
|
||||
#define GLFW_COCOA_RETINA_FRAMEBUFFER 0x00023001
|
||||
#define GLFW_COCOA_FRAME_AUTOSAVE 0x00023002
|
||||
#define GLFW_COCOA_FRAME_NAME 0x00023002
|
||||
#define GLFW_COCOA_GRAPHICS_SWITCHING 0x00023003
|
||||
|
||||
#define GLFW_X11_CLASS_NAME 0x00024001
|
||||
#define GLFW_X11_INSTANCE_NAME 0x00024002
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_NO_API 0
|
||||
@@ -721,6 +749,7 @@
|
||||
#define GLFW_CURSOR 0x00033001
|
||||
#define GLFW_STICKY_KEYS 0x00033002
|
||||
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
|
||||
#define GLFW_LOCK_KEY_MODS 0x00033004
|
||||
|
||||
#define GLFW_CURSOR_NORMAL 0x00034001
|
||||
#define GLFW_CURSOR_HIDDEN 0x00034002
|
||||
@@ -783,9 +812,6 @@
|
||||
|
||||
#define GLFW_COCOA_CHDIR_RESOURCES 0x00051001
|
||||
#define GLFW_COCOA_MENUBAR 0x00051002
|
||||
|
||||
#define GLFW_X11_WM_CLASS_NAME 0x00052001
|
||||
#define GLFW_X11_WM_CLASS_CLASS 0x00052002
|
||||
/*! @} */
|
||||
|
||||
#define GLFW_DONT_CARE -1
|
||||
@@ -804,7 +830,7 @@
|
||||
* @sa @ref glfwGetProcAddress
|
||||
*
|
||||
* @since Added in version 3.0.
|
||||
|
||||
*
|
||||
* @ingroup context
|
||||
*/
|
||||
typedef void (*GLFWglproc)(void);
|
||||
@@ -1015,6 +1041,24 @@ typedef void (* GLFWwindowmaximizefun)(GLFWwindow*,int);
|
||||
*/
|
||||
typedef void (* GLFWframebuffersizefun)(GLFWwindow*,int,int);
|
||||
|
||||
/*! @brief The function signature for window content scale callbacks.
|
||||
*
|
||||
* This is the function signature for window content scale callback
|
||||
* functions.
|
||||
*
|
||||
* @param[in] window The window whose content scale changed.
|
||||
* @param[in] xscale The new x-axis content scale of the window.
|
||||
* @param[in] yscale The new y-axis content scale of the window.
|
||||
*
|
||||
* @sa @ref window_scale
|
||||
* @sa @ref glfwSetWindowContentScaleCallback
|
||||
*
|
||||
* @since Added in version 3.3.
|
||||
*
|
||||
* @ingroup window
|
||||
*/
|
||||
typedef void (* GLFWwindowcontentscalefun)(GLFWwindow*,float,float);
|
||||
|
||||
/*! @brief The function signature for mouse button callbacks.
|
||||
*
|
||||
* This is the function signature for mouse button callback functions.
|
||||
@@ -1172,7 +1216,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
|
||||
@@ -1189,7 +1234,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
|
||||
@@ -1359,10 +1405,6 @@ typedef void (*glfwInitHint_func)(int, int);
|
||||
glfwInitHint_func glfwInitHint_impl;
|
||||
#define glfwInitHint glfwInitHint_impl
|
||||
|
||||
typedef void (*glfwInitHintString_func)(int, const char*);
|
||||
glfwInitHintString_func glfwInitHintString_impl;
|
||||
#define glfwInitHintString glfwInitHintString_impl
|
||||
|
||||
typedef void (*glfwGetVersion_func)(int*, int*, int*);
|
||||
glfwGetVersion_func glfwGetVersion_impl;
|
||||
#define glfwGetVersion glfwGetVersion_impl
|
||||
@@ -1403,6 +1445,14 @@ typedef const char* (*glfwGetMonitorName_func)(GLFWmonitor*);
|
||||
glfwGetMonitorName_func glfwGetMonitorName_impl;
|
||||
#define glfwGetMonitorName glfwGetMonitorName_impl
|
||||
|
||||
typedef void (*glfwSetMonitorUserPointer_func)(GLFWmonitor*, void*);
|
||||
glfwSetMonitorUserPointer_func glfwSetMonitorUserPointer_impl;
|
||||
#define glfwSetMonitorUserPointer glfwSetMonitorUserPointer_impl
|
||||
|
||||
typedef void* (*glfwGetMonitorUserPointer_func)(GLFWmonitor*);
|
||||
glfwGetMonitorUserPointer_func glfwGetMonitorUserPointer_impl;
|
||||
#define glfwGetMonitorUserPointer glfwGetMonitorUserPointer_impl
|
||||
|
||||
typedef GLFWmonitorfun (*glfwSetMonitorCallback_func)(GLFWmonitorfun);
|
||||
glfwSetMonitorCallback_func glfwSetMonitorCallback_impl;
|
||||
#define glfwSetMonitorCallback glfwSetMonitorCallback_impl
|
||||
@@ -1435,6 +1485,10 @@ typedef void (*glfwWindowHint_func)(int, int);
|
||||
glfwWindowHint_func glfwWindowHint_impl;
|
||||
#define glfwWindowHint glfwWindowHint_impl
|
||||
|
||||
typedef void (*glfwWindowHintString_func)(int, const char*);
|
||||
glfwWindowHintString_func glfwWindowHintString_impl;
|
||||
#define glfwWindowHintString glfwWindowHintString_impl
|
||||
|
||||
typedef GLFWwindow* (*glfwCreateWindow_func)(int, int, const char*, GLFWmonitor*, GLFWwindow*);
|
||||
glfwCreateWindow_func glfwCreateWindow_impl;
|
||||
#define glfwCreateWindow glfwCreateWindow_impl
|
||||
@@ -1591,6 +1645,10 @@ typedef GLFWframebuffersizefun (*glfwSetFramebufferSizeCallback_func)(GLFWwindow
|
||||
glfwSetFramebufferSizeCallback_func glfwSetFramebufferSizeCallback_impl;
|
||||
#define glfwSetFramebufferSizeCallback glfwSetFramebufferSizeCallback_impl
|
||||
|
||||
typedef GLFWwindowcontentscalefun (*glfwSetWindowContentScaleCallback_func)(GLFWwindow*, GLFWwindowcontentscalefun);
|
||||
glfwSetWindowContentScaleCallback_func glfwSetWindowContentScaleCallback_impl;
|
||||
#define glfwSetWindowContentScaleCallback glfwSetWindowContentScaleCallback_impl
|
||||
|
||||
typedef void (*glfwPollEvents_func)();
|
||||
glfwPollEvents_func glfwPollEvents_impl;
|
||||
#define glfwPollEvents glfwPollEvents_impl
|
||||
@@ -1711,6 +1769,14 @@ typedef const char* (*glfwGetJoystickGUID_func)(int);
|
||||
glfwGetJoystickGUID_func glfwGetJoystickGUID_impl;
|
||||
#define glfwGetJoystickGUID glfwGetJoystickGUID_impl
|
||||
|
||||
typedef void (*glfwSetJoystickUserPointer_func)(int, void*);
|
||||
glfwSetJoystickUserPointer_func glfwSetJoystickUserPointer_impl;
|
||||
#define glfwSetJoystickUserPointer glfwSetJoystickUserPointer_impl
|
||||
|
||||
typedef void* (*glfwGetJoystickUserPointer_func)(int);
|
||||
glfwGetJoystickUserPointer_func glfwGetJoystickUserPointer_impl;
|
||||
#define glfwGetJoystickUserPointer glfwGetJoystickUserPointer_impl
|
||||
|
||||
typedef int (*glfwJoystickIsGamepad_func)(int);
|
||||
glfwJoystickIsGamepad_func glfwJoystickIsGamepad_impl;
|
||||
#define glfwJoystickIsGamepad glfwJoystickIsGamepad_impl
|
||||
|
||||
209
kitty/glfw.c
209
kitty/glfw.c
@@ -9,6 +9,7 @@
|
||||
#include "glfw-wrapper.h"
|
||||
extern bool cocoa_make_window_resizable(void *w);
|
||||
extern void cocoa_create_global_menu(void);
|
||||
extern void cocoa_set_titlebar_color(void *w);
|
||||
|
||||
#if GLFW_KEY_LAST >= MAX_KEY_COUNT
|
||||
#error "glfw has too many keys, you should increase MAX_KEY_COUNT"
|
||||
@@ -21,20 +22,25 @@ update_os_window_viewport(OSWindow *window, bool notify_boss) {
|
||||
int w, h;
|
||||
glfwGetFramebufferSize(window->handle, &window->viewport_width, &window->viewport_height);
|
||||
glfwGetWindowSize(window->handle, &w, &h);
|
||||
double xr = window->viewport_x_ratio, yr = window->viewport_y_ratio;
|
||||
window->viewport_x_ratio = (double)window->viewport_width / (double)w;
|
||||
window->viewport_y_ratio = (double)window->viewport_height / (double)h;
|
||||
bool dpi_changed = (xr != 0.0 && xr != window->viewport_x_ratio) || (yr != 0.0 && yr != window->viewport_y_ratio);
|
||||
window->viewport_size_dirty = true;
|
||||
window->has_pending_resizes = false;
|
||||
window->viewport_width = MAX(window->viewport_width, 100);
|
||||
window->viewport_height = MAX(window->viewport_height, 100);
|
||||
window->window_width = MAX(w, 100);
|
||||
window->window_height = MAX(h, 100);
|
||||
if (notify_boss) {
|
||||
call_boss(on_window_resize, "Kii", window->id, window->viewport_width, window->viewport_height);
|
||||
call_boss(on_window_resize, "KiiO", window->id, window->viewport_width, window->viewport_height, dpi_changed ? Py_True : Py_False);
|
||||
}
|
||||
window->last_resize_at = monotonic();
|
||||
}
|
||||
|
||||
|
||||
// callbacks {{{
|
||||
|
||||
void
|
||||
void
|
||||
update_os_window_references() {
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
@@ -68,25 +74,28 @@ is_window_ready_for_callbacks() {
|
||||
|
||||
static inline void
|
||||
show_mouse_cursor(GLFWwindow *w) {
|
||||
if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); }
|
||||
if (glfwGetInputMode(w, GLFW_CURSOR) != GLFW_CURSOR_NORMAL) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); }
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
framebuffer_size_callback(GLFWwindow *w, int width, int height) {
|
||||
if (!set_callback_window(w)) return;
|
||||
if (width > 100 && height > 100) {
|
||||
double now = monotonic();
|
||||
OSWindow *window = global_state.callback_os_window;
|
||||
if (now - window->last_resize_at < RESIZE_DEBOUNCE_TIME) { window->has_pending_resizes = true; global_state.has_pending_resizes = true; }
|
||||
else {
|
||||
update_os_window_viewport(global_state.callback_os_window, is_window_ready_for_callbacks());
|
||||
glfwPostEmptyEvent();
|
||||
}
|
||||
} else fprintf(stderr, "Ignoring resize request for tiny size: %dx%d\n", width, height);
|
||||
window->has_pending_resizes = true; global_state.has_pending_resizes = true;
|
||||
window->last_resize_event_at = monotonic();
|
||||
} else log_error("Ignoring resize request for tiny size: %dx%d", width, height);
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
refresh_callback(GLFWwindow *w) {
|
||||
if (!set_callback_window(w)) return;
|
||||
global_state.callback_os_window->is_damaged = true;
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) {
|
||||
if (!set_callback_window(w)) return;
|
||||
global_state.callback_os_window->cursor_blink_zero_time = monotonic();
|
||||
@@ -94,7 +103,7 @@ char_mods_callback(GLFWwindow *w, unsigned int codepoint, int mods) {
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) {
|
||||
if (!set_callback_window(w)) return;
|
||||
global_state.callback_os_window->cursor_blink_zero_time = monotonic();
|
||||
@@ -105,7 +114,7 @@ key_callback(GLFWwindow *w, int key, int scancode, int action, int mods) {
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
|
||||
if (!set_callback_window(w)) return;
|
||||
show_mouse_cursor(w);
|
||||
@@ -118,7 +127,7 @@ mouse_button_callback(GLFWwindow *w, int button, int action, int mods) {
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
cursor_pos_callback(GLFWwindow *w, double x, double y) {
|
||||
if (!set_callback_window(w)) return;
|
||||
show_mouse_cursor(w);
|
||||
@@ -131,7 +140,7 @@ cursor_pos_callback(GLFWwindow *w, double x, double y) {
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
scroll_callback(GLFWwindow *w, double xoffset, double yoffset) {
|
||||
if (!set_callback_window(w)) return;
|
||||
show_mouse_cursor(w);
|
||||
@@ -141,13 +150,40 @@ scroll_callback(GLFWwindow *w, double xoffset, double yoffset) {
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
static struct {
|
||||
id_type entries[16];
|
||||
int next_entry;
|
||||
} focus_history;
|
||||
|
||||
#ifdef __APPLE__
|
||||
static inline id_type
|
||||
pop_focus_history() {
|
||||
int index = --focus_history.next_entry;
|
||||
if (index < 0) {
|
||||
focus_history.next_entry = index = 0;
|
||||
}
|
||||
|
||||
id_type result = focus_history.entries[index];
|
||||
focus_history.entries[index] = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline void
|
||||
push_focus_history(OSWindow *w) {
|
||||
focus_history.entries[focus_history.next_entry++] = w->id;
|
||||
focus_history.next_entry %= (sizeof(focus_history.entries) / sizeof(*(focus_history.entries)));
|
||||
}
|
||||
|
||||
static void
|
||||
window_focus_callback(GLFWwindow *w, int focused) {
|
||||
if (!set_callback_window(w)) return;
|
||||
global_state.callback_os_window->is_focused = focused ? true : false;
|
||||
if (focused) {
|
||||
show_mouse_cursor(w);
|
||||
show_mouse_cursor(w);
|
||||
focus_in_event();
|
||||
push_focus_history(global_state.callback_os_window);
|
||||
}
|
||||
double now = monotonic();
|
||||
global_state.callback_os_window->last_mouse_activity_at = now;
|
||||
@@ -158,7 +194,7 @@ window_focus_callback(GLFWwindow *w, int focused) {
|
||||
global_state.callback_os_window = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
static void
|
||||
drop_callback(GLFWwindow *w, int count, const char **paths) {
|
||||
if (!set_callback_window(w)) return;
|
||||
PyObject *p = PyTuple_New(count);
|
||||
@@ -201,7 +237,7 @@ set_default_window_icon(PyObject UNUSED *self, PyObject *args) {
|
||||
|
||||
static GLFWwindow *current_os_window_ctx = NULL;
|
||||
|
||||
void
|
||||
void
|
||||
make_os_window_context_current(OSWindow *w) {
|
||||
if (w->handle != current_os_window_ctx) {
|
||||
current_os_window_ctx = w->handle;
|
||||
@@ -246,8 +282,8 @@ current_monitor(GLFWwindow *window) {
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
set_dpi_from_window(OSWindow *w) {
|
||||
void
|
||||
set_dpi_from_os_window(OSWindow *w) {
|
||||
GLFWmonitor *monitor = NULL;
|
||||
if (w) { monitor = current_monitor(w->handle); }
|
||||
if (monitor == NULL) monitor = glfwGetPrimaryMonitor();
|
||||
@@ -262,16 +298,17 @@ set_dpi_from_window(OSWindow *w) {
|
||||
global_state.logical_dpi_y = yscale * factor;
|
||||
}
|
||||
|
||||
static bool is_first_window = true;
|
||||
|
||||
static PyObject*
|
||||
create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
int width, height, visible = 1, swap_interval = 0, x = -1, y = -1;
|
||||
char *title;
|
||||
int width, height, x = -1, y = -1;
|
||||
char *title, *wm_class_class, *wm_class_name;
|
||||
PyObject *load_programs = NULL;
|
||||
if (!PyArg_ParseTuple(args, "iis|pOiii", &width, &height, &title, &visible, &load_programs, &swap_interval, &x, &y)) return NULL;
|
||||
bool is_first_window = standard_cursor == NULL;
|
||||
if (!PyArg_ParseTuple(args, "iisss|Oiii", &width, &height, &title, &wm_class_name, &wm_class_class, &load_programs, &x, &y)) return NULL;
|
||||
|
||||
if (is_first_window) {
|
||||
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, OPENGL_REQUIRED_VERSION_MAJOR);
|
||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, OPENGL_REQUIRED_VERSION_MINOR);
|
||||
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||
@@ -288,29 +325,33 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
click_cursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
|
||||
arrow_cursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||
if (standard_cursor == NULL || click_cursor == NULL || arrow_cursor == NULL) {
|
||||
PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL;
|
||||
PyErr_SetString(PyExc_ValueError, "Failed to create standard mouse cursors"); return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __APPLE__
|
||||
glfwWindowHintString(GLFW_X11_INSTANCE_NAME, wm_class_name);
|
||||
glfwWindowHintString(GLFW_X11_CLASS_NAME, wm_class_class);
|
||||
#endif
|
||||
|
||||
if (global_state.num_os_windows >= MAX_CHILDREN) {
|
||||
PyErr_SetString(PyExc_ValueError, "Too many windows");
|
||||
return NULL;
|
||||
}
|
||||
glfwWindowHint(GLFW_VISIBLE, visible ? GLFW_TRUE : GLFW_FALSE);
|
||||
bool want_semi_transparent = (1.0 - OPT(background_opacity) > 0.1) ? true : false;
|
||||
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, want_semi_transparent);
|
||||
GLFWwindow *glfw_window = glfwCreateWindow(width, height, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL);
|
||||
if (glfw_window == NULL) {
|
||||
fprintf(stderr, "Failed to create a window at size: %dx%d, using a standard size instead.\n", width, height);
|
||||
log_error("Failed to create a window at size: %dx%d, using a standard size instead.", width, height);
|
||||
glfw_window = glfwCreateWindow(640, 400, title, NULL, global_state.num_os_windows ? global_state.os_windows[0].handle : NULL);
|
||||
}
|
||||
if (glfw_window == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to create GLFWwindow"); return NULL; }
|
||||
glfwMakeContextCurrent(glfw_window);
|
||||
if (x != -1 && y != -1) glfwSetWindowPos(glfw_window, x, y);
|
||||
current_os_window_ctx = glfw_window;
|
||||
glfwSwapInterval(swap_interval); // a value of 1 makes mouse selection laggy
|
||||
if (is_first_window) {
|
||||
set_dpi_from_window(NULL);
|
||||
glfwSwapInterval(OPT(sync_to_monitor) ? 1 : 0); // a value of 1 makes mouse selection laggy
|
||||
if (is_first_window) {
|
||||
set_dpi_from_os_window(NULL);
|
||||
gl_init();
|
||||
PyObject *ret = PyObject_CallFunction(load_programs, "i", glfwGetWindowAttrib(glfw_window, GLFW_TRANSPARENT_FRAMEBUFFER));
|
||||
if (ret == NULL) return NULL;
|
||||
@@ -318,6 +359,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
#ifdef __APPLE__
|
||||
cocoa_create_global_menu();
|
||||
#endif
|
||||
is_first_window = false;
|
||||
}
|
||||
|
||||
OSWindow *w = add_os_window();
|
||||
@@ -332,6 +374,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
glfwSetCursor(glfw_window, standard_cursor);
|
||||
update_os_window_viewport(w, false);
|
||||
glfwSetFramebufferSizeCallback(glfw_window, framebuffer_size_callback);
|
||||
glfwSetWindowRefreshCallback(glfw_window, refresh_callback);
|
||||
glfwSetCharModsCallback(glfw_window, char_mods_callback);
|
||||
glfwSetMouseButtonCallback(glfw_window, mouse_button_callback);
|
||||
glfwSetScrollCallback(glfw_window, scroll_callback);
|
||||
@@ -342,8 +385,9 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
#ifdef __APPLE__
|
||||
if (OPT(macos_hide_titlebar)) {
|
||||
if (glfwGetCocoaWindow) { if (!cocoa_make_window_resizable(glfwGetCocoaWindow(glfw_window))) { PyErr_Print(); } }
|
||||
else fprintf(stderr, "Failed to load glfwGetCocoaWindow\n");
|
||||
else log_error("Failed to load glfwGetCocoaWindow");
|
||||
}
|
||||
cocoa_set_titlebar_color(glfwGetCocoaWindow(glfw_window));
|
||||
#endif
|
||||
double now = monotonic();
|
||||
w->is_focused = true;
|
||||
@@ -353,7 +397,7 @@ create_os_window(PyObject UNUSED *self, PyObject *args) {
|
||||
if (want_semi_transparent && !w->is_semi_transparent) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Failed to enable transparency. This happens when your desktop environment does not support compositing.\n");
|
||||
log_error("Failed to enable transparency. This happens when your desktop environment does not support compositing.");
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
@@ -364,30 +408,61 @@ static PyObject*
|
||||
show_window(PyObject UNUSED *self, PyObject *args) {
|
||||
id_type os_window_id;
|
||||
int yes = 1;
|
||||
bool dpi_changed = false;
|
||||
if (!PyArg_ParseTuple(args, "K|p", &os_window_id, &yes)) return NULL;
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *w = global_state.os_windows + i;
|
||||
if (w->id == os_window_id) {
|
||||
if (yes) glfwShowWindow(w->handle); else glfwHideWindow(w->handle);
|
||||
if (yes) {
|
||||
bool first_show = !w->shown_once;
|
||||
glfwShowWindow(w->handle);
|
||||
w->shown_once = true;
|
||||
push_focus_history(w);
|
||||
if (first_show) {
|
||||
double before_x = global_state.logical_dpi_x, before_y = global_state.logical_dpi_y;
|
||||
set_dpi_from_os_window(w);
|
||||
dpi_changed = before_x != global_state.logical_dpi_x || before_y != global_state.logical_dpi_y;
|
||||
w->has_pending_resizes = true;
|
||||
global_state.has_pending_resizes = true;
|
||||
}
|
||||
} else glfwHideWindow(w->handle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
if (dpi_changed) Py_RETURN_TRUE;
|
||||
Py_RETURN_FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
destroy_os_window(OSWindow *w) {
|
||||
if (w->handle) {
|
||||
glfwDestroyWindow(w->handle);
|
||||
glfwDestroyWindow(w->handle);
|
||||
if (current_os_window_ctx == w->handle) current_os_window_ctx = NULL;
|
||||
}
|
||||
w->handle = NULL;
|
||||
#ifdef __APPLE__
|
||||
// On macOS when closing a window, any other existing windows belonging to the same application do not
|
||||
// automatically get focus, so we do it manually.
|
||||
bool change_focus = true;
|
||||
while (change_focus) {
|
||||
id_type new_focus_id = pop_focus_history();
|
||||
if (new_focus_id == 0) break;
|
||||
for (size_t i = 0; i < global_state.num_os_windows; i++) {
|
||||
OSWindow *c = global_state.os_windows + i;
|
||||
if (c->id != w->id && c->handle && c->shown_once && (c->id == new_focus_id)) {
|
||||
glfwFocusWindow(c->handle);
|
||||
change_focus = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Global functions {{{
|
||||
static void
|
||||
static void
|
||||
error_callback(int error, const char* description) {
|
||||
fprintf(stderr, "[glfw error %d]: %s\n", error, description);
|
||||
log_error("[glfw error %d]: %s", error, description);
|
||||
}
|
||||
|
||||
|
||||
@@ -408,7 +483,7 @@ glfw_init(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_terminate(PyObject UNUSED *self) {
|
||||
glfw_terminate(PYNOARG) {
|
||||
glfwTerminate();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -426,13 +501,13 @@ glfw_wait_events(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_post_empty_event(PyObject UNUSED *self) {
|
||||
glfw_post_empty_event(PYNOARG) {
|
||||
glfwPostEmptyEvent();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_poll_events(PyObject UNUSED *self) {
|
||||
glfw_poll_events(PYNOARG) {
|
||||
glfwPollEvents();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
@@ -450,7 +525,7 @@ get_physical_dpi(GLFWmonitor *m) {
|
||||
}
|
||||
|
||||
PyObject*
|
||||
glfw_get_physical_dpi(PyObject UNUSED *self) {
|
||||
glfw_get_physical_dpi(PYNOARG) {
|
||||
GLFWmonitor *m = glfwGetPrimaryMonitor();
|
||||
if (m == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to get primary monitor"); return NULL; }
|
||||
return get_physical_dpi(m);
|
||||
@@ -475,14 +550,14 @@ glfw_window_hint(PyObject UNUSED *self, PyObject *args) {
|
||||
// }}}
|
||||
|
||||
static PyObject*
|
||||
get_clipboard_string(PyObject UNUSED *self) {
|
||||
get_clipboard_string(PYNOARG) {
|
||||
OSWindow *w = current_os_window();
|
||||
if (w) return Py_BuildValue("s", glfwGetClipboardString(w->handle));
|
||||
return Py_BuildValue("s", "");
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_content_scale_for_window(PyObject UNUSED *self) {
|
||||
get_content_scale_for_window(PYNOARG) {
|
||||
OSWindow *w = global_state.callback_os_window ? global_state.callback_os_window : global_state.os_windows;
|
||||
float xscale, yscale;
|
||||
glfwGetWindowContentScale(w->handle, &xscale, &yscale);
|
||||
@@ -499,7 +574,7 @@ set_clipboard_string(PyObject UNUSED *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
toggle_fullscreen(PyObject UNUSED *self) {
|
||||
toggle_fullscreen(PYNOARG) {
|
||||
GLFWmonitor *monitor;
|
||||
OSWindow *w = current_os_window();
|
||||
if (!w) Py_RETURN_NONE;
|
||||
@@ -522,11 +597,11 @@ toggle_fullscreen(PyObject UNUSED *self) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
ring_audio_bell(OSWindow *w) {
|
||||
static double last_bell_at = -1;
|
||||
double now = monotonic();
|
||||
if (now - last_bell_at <= 0.1) return;
|
||||
if (now - last_bell_at <= 0.1) return;
|
||||
last_bell_at = now;
|
||||
if (w->handle) {
|
||||
glfwWindowBell(w->handle, OPT(x11_bell_volume));
|
||||
@@ -555,7 +630,7 @@ hide_mouse(OSWindow *w) {
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
swap_window_buffers(OSWindow *w) {
|
||||
glfwSwapBuffers(w->handle);
|
||||
}
|
||||
@@ -571,7 +646,7 @@ wakeup_main_loop() {
|
||||
glfwPostEmptyEvent();
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
mark_os_window_for_close(OSWindow* w, bool yes) {
|
||||
glfwSetWindowShouldClose(w->handle, yes);
|
||||
}
|
||||
@@ -589,14 +664,14 @@ should_os_window_close(OSWindow* w) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
primary_monitor_size(PyObject UNUSED *self) {
|
||||
primary_monitor_size(PYNOARG) {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
return Py_BuildValue("ii", mode->width, mode->height);
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
primary_monitor_content_scale(PyObject UNUSED *self) {
|
||||
primary_monitor_content_scale(PYNOARG) {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
float xscale, yscale;
|
||||
glfwGetMonitorContentScale(monitor, &xscale, &yscale);
|
||||
@@ -604,10 +679,10 @@ primary_monitor_content_scale(PyObject UNUSED *self) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
x11_display(PyObject UNUSED *self) {
|
||||
x11_display(PYNOARG) {
|
||||
if (glfwGetX11Display) {
|
||||
return PyLong_FromVoidPtr(glfwGetX11Display());
|
||||
} else fprintf(stderr, "Failed to load glfwGetX11Display\n");
|
||||
} else log_error("Failed to load glfwGetX11Display");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -626,10 +701,10 @@ x11_window_id(PyObject UNUSED *self, PyObject *os_wid) {
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
get_primary_selection(PyObject UNUSED *self) {
|
||||
get_primary_selection(PYNOARG) {
|
||||
if (glfwGetX11SelectionString) {
|
||||
return Py_BuildValue("y", glfwGetX11SelectionString());
|
||||
} else fprintf(stderr, "Failed to load glfwGetX11SelectionString\n");
|
||||
} else log_error("Failed to load glfwGetX11SelectionString");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -638,7 +713,7 @@ set_primary_selection(PyObject UNUSED *self, PyObject *args) {
|
||||
char *text;
|
||||
if (!PyArg_ParseTuple(args, "s", &text)) return NULL;
|
||||
if (glfwSetX11SelectionString) glfwSetX11SelectionString(text);
|
||||
else fprintf(stderr, "Failed to load glfwSetX11SelectionString\n");
|
||||
else log_error("Failed to load glfwSetX11SelectionString");
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -694,14 +769,14 @@ static PyMethodDef module_methods[] = {
|
||||
METHODB(x11_window_id, METH_O),
|
||||
METHODB(set_primary_selection, METH_VARARGS),
|
||||
METHODB(glfw_poll_events, METH_NOARGS),
|
||||
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
|
||||
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
|
||||
{"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_VARARGS, ""},
|
||||
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""},
|
||||
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
|
||||
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""},
|
||||
{"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""},
|
||||
{"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""},
|
||||
{"glfw_init", (PyCFunction)glfw_init, METH_VARARGS, ""},
|
||||
{"glfw_terminate", (PyCFunction)glfw_terminate, METH_NOARGS, ""},
|
||||
{"glfw_wait_events", (PyCFunction)glfw_wait_events, METH_VARARGS, ""},
|
||||
{"glfw_post_empty_event", (PyCFunction)glfw_post_empty_event, METH_NOARGS, ""},
|
||||
{"glfw_get_physical_dpi", (PyCFunction)glfw_get_physical_dpi, METH_NOARGS, ""},
|
||||
{"glfw_get_key_name", (PyCFunction)glfw_get_key_name, METH_VARARGS, ""},
|
||||
{"glfw_primary_monitor_size", (PyCFunction)primary_monitor_size, METH_NOARGS, ""},
|
||||
{"glfw_primary_monitor_content_scale", (PyCFunction)primary_monitor_content_scale, METH_NOARGS, ""},
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
@@ -710,8 +785,6 @@ bool
|
||||
init_glfw(PyObject *m) {
|
||||
if (PyModule_AddFunctions(m, module_methods) != 0) return false;
|
||||
#define ADDC(n) if(PyModule_AddIntConstant(m, #n, n) != 0) return false;
|
||||
ADDC(GLFW_X11_WM_CLASS_NAME)
|
||||
ADDC(GLFW_X11_WM_CLASS_CLASS)
|
||||
ADDC(GLFW_RELEASE);
|
||||
ADDC(GLFW_PRESS);
|
||||
ADDC(GLFW_REPEAT);
|
||||
|
||||
@@ -21,7 +21,7 @@ PyTypeObject GraphicsManager_Type;
|
||||
|
||||
#define STORAGE_LIMIT (320 * (1024 * 1024))
|
||||
|
||||
#define REPORT_ERROR(...) { fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); }
|
||||
#define REPORT_ERROR(...) { log_error(__VA_ARGS__); }
|
||||
|
||||
|
||||
static bool send_to_gpu = true;
|
||||
@@ -81,7 +81,7 @@ static inline void
|
||||
remove_from_array(void *array, size_t item_size, size_t idx, size_t array_count) {
|
||||
size_t num_to_right = array_count - 1 - idx;
|
||||
uint8_t *p = (uint8_t*)array;
|
||||
if (num_to_right > 0) memmove(p + (idx * item_size), p + ((idx + 1) * item_size), num_to_right * item_size);
|
||||
if (num_to_right > 0) memmove(p + (idx * item_size), p + ((idx + 1) * item_size), num_to_right * item_size);
|
||||
memset(p + (item_size * (array_count - 1)), 0, item_size);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ trim_predicate(Image *img) {
|
||||
static int
|
||||
oldest_last(const void* a, const void *b) {
|
||||
double ans = ((Image*)(b))->atime - ((Image*)(a))->atime;
|
||||
return ans < 0 ? -1 : (ans == 0 ? 0 : 1);
|
||||
return ans < 0 ? -1 : (ans == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
static inline void
|
||||
@@ -171,7 +171,7 @@ mmap_img_file(GraphicsManager UNUSED *self, Image *img, int fd, size_t sz, off_t
|
||||
sz = s.st_size;
|
||||
}
|
||||
void *addr = mmap(0, sz, PROT_READ, MAP_SHARED, fd, offset);
|
||||
if (addr == MAP_FAILED) ABRT(EBADF, "Failed to map image file fd: %d at offset: %zd with size: %zu with error: [%d] %s", fd, offset, sz, errno, strerror(errno));
|
||||
if (addr == MAP_FAILED) ABRT(EBADF, "Failed to map image file fd: %d at offset: %zd with size: %zu with error: [%d] %s", fd, offset, sz, errno, strerror(errno));
|
||||
img->load_data.mapped_file = addr;
|
||||
img->load_data.mapped_file_sz = sz;
|
||||
return true;
|
||||
@@ -267,7 +267,7 @@ inflate_png_inner(struct png_jmp_data *d, uint8_t *buf, size_t bufsz) {
|
||||
if (!info) ABRT(ENOMEM, "Failed to create PNG info structure");
|
||||
|
||||
if (setjmp(jb)) goto err;
|
||||
|
||||
|
||||
png_set_read_fn(png, &f, read_png_from_buffer);
|
||||
png_read_info(png, info);
|
||||
png_byte color_type, bit_depth;
|
||||
@@ -312,7 +312,7 @@ inflate_png(GraphicsManager UNUSED *self, Image *img, uint8_t *buf, size_t bufsz
|
||||
if (d.ok) {
|
||||
free_load_data(&img->load_data);
|
||||
img->load_data.buf = d.decompressed;
|
||||
img->load_data.buf_capacity = d.sz;
|
||||
img->load_data.buf_capacity = d.sz;
|
||||
img->load_data.buf_used = d.sz;
|
||||
img->load_data.data_sz = d.sz;
|
||||
img->width = d.width; img->height = d.height;
|
||||
@@ -376,7 +376,7 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
img->atime = monotonic(); img->used_storage = 0;
|
||||
img->width = g->data_width; img->height = g->data_height;
|
||||
switch(fmt) {
|
||||
case PNG:
|
||||
case PNG:
|
||||
if (g->data_sz > MAX_DATA_SZ) ABRT(EINVAL, "PNG data size too large");
|
||||
img->load_data.is_4byte_aligned = true;
|
||||
img->load_data.is_opaque = false;
|
||||
@@ -509,11 +509,11 @@ handle_add_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_
|
||||
|
||||
static inline const char*
|
||||
create_add_response(GraphicsManager UNUSED *self, bool data_loaded, uint32_t iid) {
|
||||
static char rbuf[sizeof(add_response)/sizeof(add_response[0])];
|
||||
static char rbuf[sizeof(add_response)/sizeof(add_response[0]) + 64];
|
||||
if (iid) {
|
||||
if (!has_add_respose) {
|
||||
if (!data_loaded) return NULL;
|
||||
snprintf(add_response, 10, "OK");
|
||||
snprintf(add_response, 10, "OK");
|
||||
}
|
||||
snprintf(rbuf, sizeof(rbuf)/sizeof(rbuf[0]) - 1, "Gi=%u;%s", iid, add_response);
|
||||
return rbuf;
|
||||
@@ -541,18 +541,18 @@ update_dest_rect(ImageRef *ref, uint32_t num_cols, uint32_t num_rows) {
|
||||
t = ref->src_width + ref->cell_x_offset;
|
||||
num_cols = t / global_state.cell_width;
|
||||
if (t > num_cols * global_state.cell_width) num_cols += 1;
|
||||
}
|
||||
}
|
||||
if (num_rows == 0) {
|
||||
t = ref->src_height + ref->cell_y_offset;
|
||||
num_rows = t / global_state.cell_height;
|
||||
if (t > num_rows * global_state.cell_height) num_rows += 1;
|
||||
}
|
||||
}
|
||||
ref->effective_num_rows = num_rows;
|
||||
ref->effective_num_cols = num_cols;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
static void
|
||||
handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, bool *is_dirty, Image *img) {
|
||||
has_add_respose = false;
|
||||
if (img == NULL) img = img_by_client_id(self, g->id);
|
||||
@@ -584,12 +584,12 @@ handle_put_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c, b
|
||||
c->x += ref->effective_num_cols; c->y += ref->effective_num_rows - 1;
|
||||
}
|
||||
|
||||
static int
|
||||
cmp_by_zindex_and_image(const void *a_, const void *b_) {
|
||||
static int
|
||||
cmp_by_zindex_and_image(const void *a_, const void *b_) {
|
||||
const ImageRenderData *a = (const ImageRenderData*)a_, *b = (const ImageRenderData*)b_;
|
||||
int ans = a->z_index - b->z_index;
|
||||
if (ans == 0) ans = a->image_id - b->image_id;
|
||||
return ans;
|
||||
return ans;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -656,9 +656,9 @@ filter_refs(GraphicsManager *self, const void* data, bool free_images, bool (*fi
|
||||
size_t i, j;
|
||||
|
||||
if (self->image_count) {
|
||||
for (i = self->image_count; i-- > 0;) {
|
||||
for (i = self->image_count; i-- > 0;) {
|
||||
img = self->images + i;
|
||||
for (j = img->refcnt; j-- > 0;) {
|
||||
for (j = img->refcnt; j-- > 0;) {
|
||||
ref = img->refs + j;
|
||||
if (filter_func(ref, img, data)) {
|
||||
remove_from_array(img->refs, sizeof(ImageRef), j, img->refcnt--);
|
||||
@@ -718,7 +718,7 @@ scroll_filter_margins_func(ImageRef* ref, Image* img, const void* data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
grman_scroll_images(GraphicsManager *self, const ScrollData *data) {
|
||||
filter_refs(self, data, true, data->has_margins ? scroll_filter_margins_func : scroll_filter_func);
|
||||
}
|
||||
@@ -728,9 +728,14 @@ clear_filter_func(ImageRef *ref, Image UNUSED *img, const void UNUSED *data) {
|
||||
return ref->start_row + (int32_t)ref->effective_num_rows > 0;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
clear_all_filter_func(ImageRef *ref UNUSED, Image UNUSED *img, const void UNUSED *data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
grman_clear(GraphicsManager *self) {
|
||||
filter_refs(self, NULL, true, clear_filter_func);
|
||||
grman_clear(GraphicsManager *self, bool all) {
|
||||
filter_refs(self, NULL, true, all ? clear_all_filter_func : clear_filter_func);
|
||||
}
|
||||
|
||||
static inline bool
|
||||
@@ -799,18 +804,18 @@ handle_delete_command(GraphicsManager *self, const GraphicsCommand *g, Cursor *c
|
||||
|
||||
// }}}
|
||||
|
||||
void
|
||||
void
|
||||
grman_resize(GraphicsManager *self, index_type UNUSED old_lines, index_type UNUSED lines, index_type UNUSED old_columns, index_type UNUSED columns) {
|
||||
self->layers_dirty = true;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
grman_rescale(GraphicsManager *self, unsigned int UNUSED old_cell_width, unsigned int UNUSED old_cell_height) {
|
||||
ImageRef *ref; Image *img;
|
||||
self->layers_dirty = true;
|
||||
for (size_t i = self->image_count; i-- > 0;) {
|
||||
for (size_t i = self->image_count; i-- > 0;) {
|
||||
img = self->images + i;
|
||||
for (size_t j = img->refcnt; j-- > 0;) {
|
||||
for (size_t j = img->refcnt; j-- > 0;) {
|
||||
ref = img->refs + j;
|
||||
ref->cell_x_offset = MIN(ref->cell_x_offset, global_state.cell_width - 1);
|
||||
ref->cell_y_offset = MIN(ref->cell_y_offset, global_state.cell_height - 1);
|
||||
@@ -925,7 +930,7 @@ W(update_layers) {
|
||||
for (size_t i = 0; i < self->count; i++) {
|
||||
ImageRenderData *r = self->render_data + i;
|
||||
#define R(offset) Py_BuildValue("{sf sf sf sf}", "left", r->vertices[offset + 8], "top", r->vertices[offset + 1], "right", r->vertices[offset], "bottom", r->vertices[offset + 5])
|
||||
PyTuple_SET_ITEM(ans, i,
|
||||
PyTuple_SET_ITEM(ans, i,
|
||||
Py_BuildValue("{sN sN sI si sI}", "src_rect", R(0), "dest_rect", R(2), "group_count", r->group_count, "z_index", r->z_index, "image_id", r->image_id)
|
||||
);
|
||||
#undef R
|
||||
@@ -950,10 +955,10 @@ PyTypeObject GraphicsManager_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.GraphicsManager",
|
||||
.tp_basicsize = sizeof(GraphicsManager),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "GraphicsManager",
|
||||
.tp_new = new,
|
||||
.tp_new = new,
|
||||
.tp_methods = methods,
|
||||
.tp_members = members,
|
||||
};
|
||||
@@ -969,7 +974,7 @@ static PyMethodDef module_methods[] = {
|
||||
bool
|
||||
init_graphics(PyObject *module) {
|
||||
if (PyType_Ready(&GraphicsManager_Type) < 0) return false;
|
||||
if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false;
|
||||
if (PyModule_AddObject(module, "GraphicsManager", (PyObject *)&GraphicsManager_Type) != 0) return false;
|
||||
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
||||
Py_INCREF(&GraphicsManager_Type);
|
||||
return true;
|
||||
|
||||
@@ -83,7 +83,7 @@ typedef struct {
|
||||
} ScrollData;
|
||||
|
||||
GraphicsManager* grman_alloc();
|
||||
void grman_clear(GraphicsManager*);
|
||||
void grman_clear(GraphicsManager*, bool);
|
||||
const char* grman_handle_command(GraphicsManager *self, const GraphicsCommand *g, const uint8_t *payload, Cursor *c, bool *is_dirty);
|
||||
bool grman_update_layers(GraphicsManager *self, unsigned int scrolled_by, float screen_left, float screen_top, float dx, float dy, unsigned int num_cols, unsigned int num_rows);
|
||||
void grman_scroll_images(GraphicsManager *self, const ScrollData*);
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
#define ALPHA_TYPE
|
||||
|
||||
uniform sampler2D image;
|
||||
uniform float inactive_text_alpha;
|
||||
|
||||
in vec2 texcoord;
|
||||
out vec4 color;
|
||||
|
||||
void main() {
|
||||
color = texture(image, texcoord);
|
||||
color.a *= inactive_text_alpha;
|
||||
#ifdef PREMULT
|
||||
color = vec4(color.rgb * color.a, color.a);
|
||||
#endif
|
||||
|
||||
@@ -58,7 +58,7 @@ dealloc(HistoryBuf* self) {
|
||||
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||
}
|
||||
|
||||
static inline index_type
|
||||
static inline index_type
|
||||
index_of(HistoryBuf *self, index_type lnum) {
|
||||
// The index (buffer position) of the line with line number lnum
|
||||
// This is reverse indexing, i.e. lnum = 0 corresponds to the *last* line in the buffer.
|
||||
@@ -67,7 +67,7 @@ index_of(HistoryBuf *self, index_type lnum) {
|
||||
return (self->start_of_data + idx) % self->ynum;
|
||||
}
|
||||
|
||||
static inline void
|
||||
static inline void
|
||||
init_line(HistoryBuf *self, index_type num, Line *l) {
|
||||
// Initialize the line l, setting its pointer to the offsets for the line at index (buffer position) num
|
||||
l->cells = lineptr(self, num);
|
||||
@@ -75,22 +75,28 @@ init_line(HistoryBuf *self, index_type num, Line *l) {
|
||||
l->has_dirty_text = self->line_attrs[num] & TEXT_DIRTY_MASK ? true : false;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
historybuf_init_line(HistoryBuf *self, index_type lnum, Line *l) {
|
||||
init_line(self, index_of(self, lnum), l);
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
historybuf_mark_line_clean(HistoryBuf *self, index_type y) {
|
||||
self->line_attrs[index_of(self, y)] &= ~TEXT_DIRTY_MASK;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
historybuf_mark_line_dirty(HistoryBuf *self, index_type y) {
|
||||
self->line_attrs[index_of(self, y)] |= TEXT_DIRTY_MASK;
|
||||
}
|
||||
|
||||
static inline index_type
|
||||
inline void
|
||||
historybuf_clear(HistoryBuf *self) {
|
||||
self->count = 0;
|
||||
self->start_of_data = 0;
|
||||
}
|
||||
|
||||
static inline index_type
|
||||
historybuf_push(HistoryBuf *self) {
|
||||
index_type idx = (self->start_of_data + self->count) % self->ynum;
|
||||
init_line(self, idx, self->line);
|
||||
@@ -124,7 +130,7 @@ historybuf_resize(HistoryBuf *self, index_type lines) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
void
|
||||
historybuf_add_line(HistoryBuf *self, const Line *line) {
|
||||
index_type idx = historybuf_push(self);
|
||||
copy_line(line, self->line);
|
||||
@@ -155,7 +161,7 @@ __str__(HistoryBuf *self) {
|
||||
if (lines == NULL) return PyErr_NoMemory();
|
||||
for (index_type i = 0; i < self->count; i++) {
|
||||
init_line(self, index_of(self, i), self->line);
|
||||
PyObject *t = PyObject_Str((PyObject*)self->line);
|
||||
PyObject *t = line_as_unicode(self->line);
|
||||
if (t == NULL) { Py_CLEAR(lines); return NULL; }
|
||||
PyTuple_SET_ITEM(lines, i, t);
|
||||
}
|
||||
@@ -196,8 +202,20 @@ as_ansi(HistoryBuf *self, PyObject *callback) {
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
static inline Line*
|
||||
get_line(HistoryBuf *self, index_type y, Line *l) { init_line(self, y, l); return l; }
|
||||
|
||||
static PyObject*
|
||||
dirty_lines(HistoryBuf *self) {
|
||||
as_text(HistoryBuf *self, PyObject *args) {
|
||||
Line l = {.xnum=self->xnum};
|
||||
#define gl(self, y) get_line(self, y, &l);
|
||||
as_text_generic(args, self, gl, self->count, self->xnum, callback, as_ansi);
|
||||
#undef gl
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
dirty_lines(HistoryBuf *self, PyObject *a UNUSED) {
|
||||
#define dirty_lines_doc "dirty_lines() -> Line numbers of all lines that have dirty text."
|
||||
PyObject *ans = PyList_New(0);
|
||||
for (index_type i = 0; i < self->ynum; i++) {
|
||||
@@ -217,6 +235,7 @@ static PyMethodDef methods[] = {
|
||||
METHOD(change_num_of_lines, METH_O)
|
||||
METHOD(line, METH_O)
|
||||
METHOD(as_ansi, METH_O)
|
||||
METHODB(as_text, METH_VARARGS),
|
||||
METHOD(dirty_lines, METH_NOARGS)
|
||||
METHOD(push, METH_VARARGS)
|
||||
METHOD(rewrap, METH_VARARGS)
|
||||
@@ -234,11 +253,11 @@ PyTypeObject HistoryBuf_Type = {
|
||||
PyVarObject_HEAD_INIT(NULL, 0)
|
||||
.tp_name = "fast_data_types.HistoryBuf",
|
||||
.tp_basicsize = sizeof(HistoryBuf),
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_dealloc = (destructor)dealloc,
|
||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||
.tp_doc = "History buffers",
|
||||
.tp_methods = methods,
|
||||
.tp_members = members,
|
||||
.tp_members = members,
|
||||
.tp_str = (reprfunc)__str__,
|
||||
.tp_new = new
|
||||
};
|
||||
@@ -258,9 +277,9 @@ HistoryBuf *alloc_historybuf(unsigned int lines, unsigned int columns) {
|
||||
|
||||
#define is_src_line_continued(src_y) (map_src_index(src_y) < src->ynum - 1 ? (src->line_attrs[map_src_index(src_y + 1)] & CONTINUED_MASK) : false)
|
||||
|
||||
#define next_dest_line(cont) dest->line_attrs[historybuf_push(dest)] = cont & CONTINUED_MASK; dest->line->continued = cont;
|
||||
#define next_dest_line(cont) dest->line_attrs[historybuf_push(dest)] = cont & CONTINUED_MASK; dest->line->continued = cont;
|
||||
|
||||
#define first_dest_line next_dest_line(false);
|
||||
#define first_dest_line next_dest_line(false);
|
||||
|
||||
#include "rewrap.h"
|
||||
|
||||
@@ -273,8 +292,9 @@ void historybuf_rewrap(HistoryBuf *self, HistoryBuf *other) {
|
||||
return;
|
||||
}
|
||||
other->count = 0; other->start_of_data = 0;
|
||||
index_type x = 0, y = 0;
|
||||
if (self->count > 0) {
|
||||
rewrap_inner(self, other, self->count, NULL);
|
||||
rewrap_inner(self, other, self->count, NULL, &x, &y);
|
||||
for (index_type i = 0; i < other->count; i++) other->line_attrs[(other->start_of_data + i) % other->ynum] |= TEXT_DIRTY_MASK;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,27 +2,25 @@
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import array
|
||||
import codecs
|
||||
import fcntl
|
||||
import mimetypes
|
||||
import os
|
||||
import re
|
||||
import selectors
|
||||
import signal
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
import zlib
|
||||
from base64 import standard_b64encode
|
||||
from collections import namedtuple
|
||||
from math import ceil, floor
|
||||
from tempfile import NamedTemporaryFile
|
||||
from time import monotonic
|
||||
|
||||
from kitty.constants import appname
|
||||
from kitty.cli import parse_args
|
||||
from kitty.constants import appname
|
||||
from kitty.utils import read_with_timeout
|
||||
|
||||
try:
|
||||
fsenc = sys.getfilesystemencoding() or 'utf-8'
|
||||
@@ -56,6 +54,13 @@ are in cells (i.e. cursor positions) with the origin |_ (0, 0)| at
|
||||
the top-left corner of the screen.
|
||||
|
||||
|
||||
--scale-up
|
||||
type=bool-set
|
||||
When used in combination with |_ --place| it will cause images that
|
||||
are smaller than the specified area to be scaled up to use as much
|
||||
of the specified area as possible.
|
||||
|
||||
|
||||
--clear
|
||||
type=bool-set
|
||||
Remove all images currently displayed on the screen.
|
||||
@@ -98,14 +103,18 @@ def options_spec():
|
||||
Size = namedtuple('Size', 'rows cols width height')
|
||||
|
||||
|
||||
def screen_size(refresh=False):
|
||||
if refresh or getattr(screen_size, 'ans', None) is None:
|
||||
s = struct.pack('HHHH', 0, 0, 0, 0)
|
||||
x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
|
||||
screen_size.ans = Size(*struct.unpack('HHHH', x))
|
||||
def screen_size():
|
||||
if screen_size.changed:
|
||||
buf = array.array('H', [0, 0, 0, 0])
|
||||
fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, buf)
|
||||
screen_size.ans = Size(*buf)
|
||||
screen_size.changed = False
|
||||
return screen_size.ans
|
||||
|
||||
|
||||
screen_size.changed = True
|
||||
|
||||
|
||||
def write_gr_cmd(cmd, payload=None):
|
||||
cmd = ','.join('{}={}'.format(k, v) for k, v in cmd.items())
|
||||
w = sys.stdout.buffer.write
|
||||
@@ -131,6 +140,17 @@ def fit_image(width, height, pwidth, pheight):
|
||||
return int(width), int(height)
|
||||
|
||||
|
||||
def calculate_in_cell_x_offset(width, cell_width, align):
|
||||
if align == 'left':
|
||||
return 0
|
||||
extra_pixels = width % cell_width
|
||||
if not extra_pixels:
|
||||
return 0
|
||||
if align == 'right':
|
||||
return cell_width - extra_pixels
|
||||
return (cell_width - extra_pixels) // 2
|
||||
|
||||
|
||||
def set_cursor(cmd, width, height, align):
|
||||
ss = screen_size()
|
||||
cw = int(ss.width / ss.cols)
|
||||
@@ -139,12 +159,9 @@ def set_cursor(cmd, width, height, align):
|
||||
w, h = fit_image(width, height, ss.width, height)
|
||||
ch = int(ss.height / ss.rows)
|
||||
num_of_rows_needed = int(ceil(height / ch))
|
||||
y_off = height % ch
|
||||
cmd['c'], cmd['r'] = ss.cols, num_of_rows_needed
|
||||
cmd['Y'] = y_off
|
||||
else:
|
||||
x_off = width % cw
|
||||
cmd['X'] = x_off
|
||||
cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
|
||||
extra_cells = 0
|
||||
if align == 'center':
|
||||
extra_cells = (ss.cols - num_of_cells_needed) // 2
|
||||
@@ -159,8 +176,7 @@ def set_cursor_for_place(place, cmd, width, height, align):
|
||||
ss = screen_size()
|
||||
cw = int(ss.width / ss.cols)
|
||||
num_of_cells_needed = int(ceil(width / cw))
|
||||
x_off = width % cw
|
||||
cmd['X'] = x_off
|
||||
cmd['X'] = calculate_in_cell_x_offset(width, cw, align)
|
||||
extra_cells = 0
|
||||
if align == 'center':
|
||||
extra_cells = (place.width - num_of_cells_needed) // 2
|
||||
@@ -221,14 +237,29 @@ def identify(path):
|
||||
return ImageData(parts[0].lower(), int(parts[1]), int(parts[2]), mode)
|
||||
|
||||
|
||||
def convert(path, m, available_width, available_height):
|
||||
def convert(path, m, available_width, available_height, scale_up):
|
||||
width, height = m.width, m.height
|
||||
cmd = ['convert', '-background', 'none', path]
|
||||
if scale_up:
|
||||
if width < available_width:
|
||||
r = available_width / width
|
||||
width, height = available_width, int(height * r)
|
||||
if width > available_width or height > available_height:
|
||||
width, height = fit_image(width, height, available_width, available_height)
|
||||
cmd += ['-resize', '{}x{}'.format(width, height)]
|
||||
with NamedTemporaryFile(prefix='icat-', suffix='.' + m.mode, delete=False) as outfile:
|
||||
run_imagemagick(path, cmd + [outfile.name])
|
||||
# ImageMagick sometimes generated rgba images smaller than the specified
|
||||
# size. See https://github.com/kovidgoyal/kitty/issues/276 for examples
|
||||
sz = os.path.getsize(outfile.name)
|
||||
bytes_per_pixel = 3 if m.mode == 'rgb' else 4
|
||||
expected_size = bytes_per_pixel * width * height
|
||||
if sz < expected_size:
|
||||
missing = expected_size - sz
|
||||
if missing % (bytes_per_pixel * width) != 0:
|
||||
raise SystemExit('ImageMagick failed to convert {} correctly, it generated {} < {} of data'.format(path, sz, expected_size))
|
||||
height -= missing // (bytes_per_pixel * width)
|
||||
|
||||
return outfile.name, width, height
|
||||
|
||||
|
||||
@@ -238,6 +269,7 @@ def process(path, args):
|
||||
available_width = args.place.width * (ss.width / ss.cols) if args.place else ss.width
|
||||
available_height = args.place.height * (ss.height / ss.rows) if args.place else 10 * m.height
|
||||
needs_scaling = m.width > available_width or m.height > available_height
|
||||
needs_scaling = needs_scaling or args.scale_up
|
||||
if m.fmt == 'png' and not needs_scaling:
|
||||
outfile = path
|
||||
transmit_mode = 'f'
|
||||
@@ -246,7 +278,7 @@ def process(path, args):
|
||||
else:
|
||||
fmt = 24 if m.mode == 'rgb' else 32
|
||||
transmit_mode = 't'
|
||||
outfile, width, height = convert(path, m, available_width, available_height)
|
||||
outfile, width, height = convert(path, m, available_width, available_height, args.scale_up)
|
||||
show(outfile, width, height, fmt, transmit_mode, align=args.align, place=args.place)
|
||||
if not args.place:
|
||||
print() # ensure cursor is on a new line
|
||||
@@ -261,17 +293,11 @@ def scan(d):
|
||||
|
||||
|
||||
def detect_support(wait_for=10, silent=False):
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
oldfl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl | os.O_NONBLOCK)
|
||||
if not silent:
|
||||
print('Checking for graphics ({}s max. wait)...'.format(wait_for), end='\r')
|
||||
sys.stdout.flush()
|
||||
tty.setraw(fd)
|
||||
try:
|
||||
received = b''
|
||||
start_time = monotonic()
|
||||
responses = {}
|
||||
|
||||
def parse_responses():
|
||||
@@ -282,29 +308,20 @@ def detect_support(wait_for=10, silent=False):
|
||||
if iid not in responses:
|
||||
responses[iid] = m.group(2) == b'OK'
|
||||
|
||||
def read():
|
||||
def more_needed(data):
|
||||
nonlocal received
|
||||
d = sys.stdin.buffer.read()
|
||||
if not d: # EOF
|
||||
responses[1] = responses[2] = False
|
||||
return
|
||||
received += d
|
||||
received += data
|
||||
parse_responses()
|
||||
return 1 not in responses or 2 not in responses
|
||||
|
||||
with NamedTemporaryFile() as f:
|
||||
f.write(b'abcd'), f.flush()
|
||||
write_gr_cmd(dict(a='q', s=1, v=1, i=1), standard_b64encode(b'abcd'))
|
||||
write_gr_cmd(dict(a='q', s=1, v=1, i=2, t='f'), standard_b64encode(f.name.encode(fsenc)))
|
||||
sel = selectors.DefaultSelector()
|
||||
sel.register(sys.stdin, selectors.EVENT_READ, read)
|
||||
while monotonic() - start_time < wait_for and 1 not in responses and 2 not in responses:
|
||||
for key, mask in sel.select(0.1):
|
||||
read()
|
||||
read_with_timeout(more_needed, timeout=wait_for)
|
||||
finally:
|
||||
if not silent:
|
||||
sys.stdout.buffer.write(b'\033[J'), sys.stdout.flush()
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, oldfl)
|
||||
detect_support.has_files = bool(responses.get(2))
|
||||
return responses.get(1, False)
|
||||
|
||||
@@ -324,7 +341,7 @@ def main(args=sys.argv):
|
||||
' Directories are scanned recursively for image files.')
|
||||
args, items = parse_args(args[1:], options_spec, 'image-file ...', msg, '{} icat'.format(appname))
|
||||
|
||||
signal.signal(signal.SIGWINCH, lambda: screen_size(refresh=True))
|
||||
signal.signal(signal.SIGWINCH, lambda signum, frame: setattr(screen_size, 'changed', True))
|
||||
if not sys.stdout.isatty() or not sys.stdin.isatty():
|
||||
raise SystemExit(
|
||||
'Must be run in a terminal, stdout and/or stdin is currently not a terminal'
|
||||
|
||||
24
kitty/key_encoding.py
generated
24
kitty/key_encoding.py
generated
@@ -3,6 +3,7 @@
|
||||
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import string
|
||||
from collections import namedtuple
|
||||
|
||||
from . import fast_data_types as defines
|
||||
|
||||
@@ -302,3 +303,26 @@ def update_encoding():
|
||||
f.seek(0), f.truncate()
|
||||
f.write(nraw)
|
||||
subprocess.check_call(['yapf', '-i', __file__])
|
||||
|
||||
|
||||
PRESS, REPEAT, RELEASE = 1, 2, 4
|
||||
SHIFT, ALT, CTRL, SUPER = 1, 2, 4, 8
|
||||
KeyEvent = namedtuple('KeyEvent', 'type mods key')
|
||||
type_map = {'p': PRESS, 't': REPEAT, 'r': RELEASE}
|
||||
mod_map = {c: i for i, c in enumerate('ABCDEFGHIJKLMNOP')}
|
||||
key_rmap = {}
|
||||
g = globals()
|
||||
for key_name, enc in ENCODING.items():
|
||||
key_name = key_name.replace(' ', '_')
|
||||
g[key_name] = key_name
|
||||
key_rmap[enc] = key_name
|
||||
del key_name, enc, g
|
||||
enter_key = KeyEvent(PRESS, 0, ENCODING['ENTER'])
|
||||
backspace_key = KeyEvent(PRESS, 0, ENCODING['BACKSPACE'])
|
||||
|
||||
|
||||
def decode_key_event(text):
|
||||
typ = type_map[text[1]]
|
||||
mods = mod_map[text[2]]
|
||||
key = key_rmap[text[3:5]]
|
||||
return KeyEvent(typ, mods, key)
|
||||
|
||||
28
kitty/keys.c
28
kitty/keys.c
@@ -11,6 +11,8 @@
|
||||
#include "glfw-wrapper.h"
|
||||
#include "control-codes.h"
|
||||
|
||||
static bool needs_special_handling[128 * 16] = {0};
|
||||
|
||||
const char*
|
||||
key_to_bytes(int glfw_key, bool smkx, bool extended, int mods, int action) {
|
||||
if ((action & 3) == 3) return NULL;
|
||||
@@ -61,7 +63,7 @@ static inline bool
|
||||
is_modifier_key(int key) {
|
||||
switch(key) {
|
||||
case GLFW_KEY_LEFT_SHIFT:
|
||||
case GLFW_KEY_RIGHT_SHIFT:
|
||||
case GLFW_KEY_RIGHT_SHIFT:
|
||||
case GLFW_KEY_LEFT_ALT:
|
||||
case GLFW_KEY_RIGHT_ALT:
|
||||
case GLFW_KEY_LEFT_CONTROL:
|
||||
@@ -83,7 +85,7 @@ get_localized_key(int key, int scancode) {
|
||||
// key names {{{
|
||||
K('A', A); K('a', A);
|
||||
K('B', B); K('b', B);
|
||||
K('C', C); K('c', C);
|
||||
K('C', C); K('c', C);
|
||||
K('D', D); K('d', D);
|
||||
K('E', E); K('e', E);
|
||||
K('F', F); K('f', F);
|
||||
@@ -139,8 +141,10 @@ send_key_to_child(Window *w, int key, int mods, int action) {
|
||||
Screen *screen = w->render_data.screen;
|
||||
const char *data = key_to_bytes(key, screen->modes.mDECCKM, screen->modes.mEXTENDED_KEYBOARD, mods, action);
|
||||
if (data) {
|
||||
if (screen->modes.mEXTENDED_KEYBOARD) write_escape_code_to_child(screen, APC, data + 1);
|
||||
else {
|
||||
if (screen->modes.mEXTENDED_KEYBOARD) {
|
||||
if (*data == 1) schedule_write_to_child(w->id, (data + 1), 1);
|
||||
else write_escape_code_to_child(screen, APC, data + 1);
|
||||
} else {
|
||||
if (*data > 2 && data[1] == 0x1b && data[2] == '[') { // CSI code
|
||||
write_escape_code_to_child(screen, CSI, data + 3);
|
||||
} else schedule_write_to_child(w->id, (data + 1), *data);
|
||||
@@ -176,7 +180,9 @@ on_key_input(int key, int scancode, int action, int mods) {
|
||||
screen_history_scroll(screen, SCROLL_FULL, false); // scroll back to bottom
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods)) return;
|
||||
if (!OPT(macos_option_as_alt) && IS_ALT_MODS(mods) && !(
|
||||
key == GLFW_KEY_UP || key == GLFW_KEY_DOWN || key == GLFW_KEY_LEFT || key == GLFW_KEY_RIGHT) // alt+arrow keys do not generate text on macOS
|
||||
) return;
|
||||
#endif
|
||||
if (
|
||||
action == GLFW_PRESS ||
|
||||
@@ -188,12 +194,14 @@ on_key_input(int key, int scancode, int action, int mods) {
|
||||
}
|
||||
|
||||
void
|
||||
fake_scroll(bool upwards) {
|
||||
fake_scroll(int amount, bool upwards) {
|
||||
Window *w = active_window();
|
||||
if (!w) return;
|
||||
Screen *screen = w->render_data.screen;
|
||||
send_key_to_child(w, upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN, 0, GLFW_PRESS);
|
||||
if (screen->modes.mEXTENDED_KEYBOARD) send_key_to_child(w, upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN, 0, GLFW_RELEASE);
|
||||
int key = upwards ? GLFW_KEY_UP : GLFW_KEY_DOWN;
|
||||
while (amount-- > 0) {
|
||||
send_key_to_child(w, key, 0, GLFW_PRESS);
|
||||
send_key_to_child(w, key, 0, GLFW_RELEASE);
|
||||
}
|
||||
}
|
||||
|
||||
#define PYWRAP1(name) static PyObject* py##name(PyObject UNUSED *self, PyObject *args)
|
||||
@@ -209,7 +217,7 @@ PYWRAP1(key_to_bytes) {
|
||||
}
|
||||
|
||||
static PyMethodDef module_methods[] = {
|
||||
M(key_to_bytes, METH_VARARGS),
|
||||
M(key_to_bytes, METH_VARARGS),
|
||||
{0}
|
||||
};
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user